Newer
Older
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
paths = reload(paths)
utils = reload(utils)
categories = reload(categories)
ui = reload(ui)
bkit_oauth = reload(bkit_oauth)
version_checker = reload(version_checker)
tasks_queue = reload(tasks_queue)
from blenderkit import paths, utils, categories, ui, colors, bkit_oauth, version_checker, tasks_queue, rerequests
import blenderkit
from bpy.app.handlers import persistent
from bpy.props import ( # TODO only keep the ones actually used when cleaning
IntProperty,
FloatProperty,
FloatVectorProperty,
StringProperty,
EnumProperty,
BoolProperty,
PointerProperty,
)
from bpy.types import (
Operator,
Panel,
AddonPreferences,
PropertyGroup,
UIList
)
import requests, os, random
import time
import threading
def check_errors(rdata):
if rdata.get('statusCode') == 401:
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
if user_preferences.api_key != '':
if user_preferences.enable_oauth:
bkit_oauth.refresh_token_thread()
return False, 'Use login panel to connect your profile.'
return True, ''
search_threads = []
thumb_sml_download_threads = {}
thumb_full_download_threads = {}
reports = ''
rtips = ['Click or drag model or material in scene to link/append ',
"Please rate responsively and plentifully. This helps us distribute rewards to the authors.",
"Click on brushes to link them into scene.",
"All materials and brushes are free.",
"Storage for public assets is unlimited.",
"Locked models are available if you subscribe to Full plan.",
"Login to upload your own models, materials or brushes.",
"Use 'A' key over asset bar to search assets by same author.",
"Use 'W' key over asset bar to open Authors webpage.", ]
def refresh_token_timer():
''' this timer gets run every time the token needs refresh. It refreshes tokens and also categories.'''
utils.p('refresh timer')
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
fetch_server_data()
categories.load_categories()
return max(3600, user_preferences.api_key_life - 3600)
def update_assets_data(): # updates assets data on scene load.
'''updates some properties that were changed on scenes with older assets.
The properties were mainly changed from snake_case to CamelCase to fit the data that is coming from the server.
'''
for ob in bpy.context.scene.objects:
if ob.get('asset_data') != None:
ad = ob['asset_data']
if not ad.get('assetBaseId'):
ad['assetBaseId'] = ad['asset_base_id'], # this should stay ONLY for compatibility with older scenes
ad['assetType'] = ad['asset_type'], # this should stay ONLY for compatibility with older scenes
ad['canDownload'] = ad['can_download'], # this should stay ONLY for compatibility with older scenes
ad['verificationStatus'] = ad[
'verification_status'], # this should stay ONLY for compatibility with older scenes
ad['author'] = {}
ad['author']['id'] = ad['author_id'], # this should stay ONLY for compatibility with older scenes
'''
Loads categories , checks timers registration, and updates scene asset data.
Should (probably)also update asset data from server (after user consent)
'''
wm = bpy.context.window_manager
fetch_server_data()
categories.load_categories()
if not bpy.app.timers.is_registered(refresh_token_timer):
bpy.app.timers.register(refresh_token_timer, persistent=True, first_interval=36000)
update_assets_data()
''' download categories , profile, and refresh token if needed.'''
if not bpy.app.background:
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
api_key = user_preferences.api_key
# Only refresh new type of tokens(by length), and only one hour before the token timeouts.
if user_preferences.enable_oauth and \
len(user_preferences.api_key) < 38 and len(user_preferences.api_key) > 0 and \
user_preferences.api_key_timeout < time.time() + 3600:
bkit_oauth.refresh_token_thread()
if api_key != '' and bpy.context.window_manager.get('bkit profile') == None:
if bpy.context.window_manager.get('bkit_categories') is None:
categories.fetch_categories_thread(api_key)
first_time = True
'''
Checks clipboard for an exact string containing asset ID.
The string is generated on www.blenderkit.com as for example here:
https://www.blenderkit.com/get-blenderkit/54ff5c85-2c73-49e9-ba80-aec18616a408/
'''
# clipboard monitoring to search assets from web
if platform.system() != 'Linux':
global last_clipboard
if bpy.context.window_manager.clipboard != last_clipboard:
last_clipboard = bpy.context.window_manager.clipboard
instr = 'asset_base_id:'
# first check if contains asset id, then asset type
if last_clipboard[:len(instr)] == instr:
atstr = 'asset_type:'
ati = last_clipboard.find(atstr)
# this only checks if the asset_type keyword is there but let's the keywords update function do the parsing.
if ati > -1:
search_props = utils.get_search_props()
search_props.search_keywords = last_clipboard
# don't run search after this - assigning to keywords runs the search_update function.
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def parse_result(r):
'''
needed to generate some extra data in the result(by now)
Parameters
----------
r - search result, also called asset_data
'''
scene = bpy.context.scene
# TODO remove this fix when filesSize is fixed.
# this is a temporary fix for too big numbers from the server.
try:
r['filesSize'] = int(r['filesSize'] / 1024)
except:
utils.p('asset with no files-size')
asset_type = r['assetType']
if len(r['files']) > 0:
allthumbs = []
durl, tname = None, None
for f in r['files']:
if f['fileType'] == 'thumbnail':
tname = paths.extract_filename_from_url(f['fileThumbnailLarge'])
small_tname = paths.extract_filename_from_url(f['fileThumbnail'])
allthumbs.append(tname) # TODO just first thumb is used now.
tdict = {}
for i, t in enumerate(allthumbs):
tdict['thumbnail_%i'] = t
if f['fileType'] == 'blend':
durl = f['downloadUrl'].split('?')[0]
# fname = paths.extract_filename_from_url(f['filePath'])
if durl and tname:
tooltip = generate_tooltip(r)
# for some reason, the id was still int on some occurances. investigate this.
r['author']['id'] = str(r['author']['id'])
# some helper props, but generally shouldn't be renaming/duplifiying original properties,
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# so blender's data is same as on server.
asset_data = {'thumbnail': tname,
'thumbnail_small': small_tname,
# 'thumbnails':allthumbs,
'download_url': durl,
# 'id': r['id'],
# 'asset_base_id': r['assetBaseId'],#this should stay ONLY for compatibility with older scenes
# 'name': r['name'],
# 'asset_type': r['assetType'], #this should stay ONLY for compatibility with older scenes
'tooltip': tooltip,
# 'tags': r['tags'],
# 'can_download': r.get('canDownload', True),#this should stay ONLY for compatibility with older scenes
# 'verification_status': r['verificationStatus'],#this should stay ONLY for compatibility with older scenes
# 'author_id': r['author']['id'],#this should stay ONLY for compatibility with older scenes
# 'author': r['author']['firstName'] + ' ' + r['author']['lastName']
# 'description': r['description'],
}
asset_data['downloaded'] = 0
# parse extra params needed for blender here
params = utils.params_to_dict(r['parameters'])
if asset_type == 'model':
if params.get('boundBoxMinX') != None:
bbox = {
'bbox_min': (
float(params['boundBoxMinX']),
float(params['boundBoxMinY']),
float(params['boundBoxMinZ'])),
'bbox_max': (
float(params['boundBoxMaxX']),
float(params['boundBoxMaxY']),
float(params['boundBoxMaxZ']))
}
else:
bbox = {
'bbox_min': (-.5, -.5, 0),
'bbox_max': (.5, .5, 1)
}
asset_data.update(bbox)
if asset_type == 'material':
asset_data['texture_size_meters'] = params.get('textureSizeMeters', 1.0)
asset_data.update(tdict)
if r['assetBaseId'] in scene.get('assets used', {}).keys():
asset_data['downloaded'] = 100
# attempt to switch to use original data gradually, since the parsing as itself should become obsolete.
asset_data.update(r)
# this makes a first search after opening blender. showing latest assets.
global first_time
preferences = bpy.context.preferences.addons['blenderkit'].preferences
first_time = False
# TODO here it should check if there are some results, and only open assetbar if this is the case, not search.
# if bpy.context.scene.get('search results') is None:
search()
preferences.first_run = False
utils.get_largest_3dview()
ui.update_ui_size(ui.active_area, ui.active_region)
ui.add_report(text='BlenderKit Tip: ' + random.choice(rtips), timeout=12, color=colors.GREEN)
if preferences.first_run:
search()
preferences.first_run = False
# don't do anything while dragging - this could switch asset during drag, and make results list length different,
# causing a lot of throuble.
if bpy.context.scene.blenderkitUI.dragging:
return 0.5
for thread in search_threads:
# TODO this doesn't check all processes when one gets removed,
# but most of the time only one is running anyway
if not thread[0].is_alive():
search_threads.remove(thread) #
icons_dir = thread[1]
scene = bpy.context.scene
# these 2 lines should update the previews enum and set the first result as active.
s = bpy.context.scene
asset_type = thread[2]
if asset_type == 'model':
props = scene.blenderkit_models
# json_filepath = os.path.join(icons_dir, 'model_searchresult.json')
if asset_type == 'scene':
props = scene.blenderkit_scene
# json_filepath = os.path.join(icons_dir, 'scene_searchresult.json')
if asset_type == 'material':
props = scene.blenderkit_mat
# json_filepath = os.path.join(icons_dir, 'material_searchresult.json')
if asset_type == 'brush':
props = scene.blenderkit_brush
# json_filepath = os.path.join(icons_dir, 'brush_searchresult.json')
search_name = f'bkit {asset_type} search'
s[search_name] = []
global reports
if reports != '':
props.report = str(reports)
return .2
result_field = []
ok, error = check_errors(rdata)
if ok:
bpy.ops.object.run_assetbar_fix_context()
asset_data = parse_result(r)
if asset_data != None:
result_field.append(asset_data)
s[search_name] = result_field
s['search results'] = result_field
s[search_name + ' orig'] = copy.deepcopy(rdata)
s['search results orig'] = s[search_name + ' orig']
load_previews()
ui_props = bpy.context.scene.blenderkitUI
if len(result_field) < ui_props.scrolloffset:
ui_props.scrolloffset = 0
props.is_searching = False
props.search_error = False
props.report = 'Found %i results. ' % (s['search results orig']['count'])
tasks_queue.add_task((ui.add_report, ('No matching results found.',)))
else:
print('error', error)
props.report = error
props.search_error = True
scene = bpy.context.scene
# FIRST START SEARCH
props = scene.blenderkitUI
directory = paths.get_temp_dir('%s_search' % props.asset_type.lower())
s = bpy.context.scene
results = s.get('search results')
#
if results is not None:
inames = []
tpaths = []
i = 0
for r in results:
tpath = os.path.join(directory, r['thumbnail_small'])
iname = utils.previmg_name(i)
if os.path.exists(tpath): # sometimes we are unlucky...
img = bpy.data.images.get(iname)
if img is None:
img = bpy.data.images.load(tpath)
img.name = iname
elif img.filepath != tpath:
# had to add this check for autopacking files...
if img.packed_file is not None:
img.unpack(method='USE_ORIGINAL')
img.filepath = tpath
img.reload()
img.colorspace_settings.name = 'sRGB'
def split_subs(text, threshold=40):
# temporarily disable this, to be able to do this in drawing code
Vilem Duha
committed
# first handle if there's an \n line ending
i_rn = text.find('\n')
if 1 < i_rn < threshold:
i = i_rn
else:
i = text.rfind(' ', 0, threshold)
i1 = text.rfind(',', 0, threshold)
i2 = text.rfind('.', 0, threshold)
i = max(i, i1, i2)
if i <= 0:
i = threshold
lines.append(text[:i])
text = text[i:]
lines.append(text)
return lines
def list_to_str(input):
output = ''
for i, text in enumerate(input):
output += text
if i < len(input) - 1:
output += ', '
return output
def writeblock(t, input, width=40): # for longer texts
dlines = split_subs(input, threshold=width)
for i, l in enumerate(dlines):
t += '%s\n' % l
return t
def writeblockm(tooltip, mdata, key='', pretext=None, width=40): # for longer texts
if mdata.get(key) == None:
return tooltip
else:
intext = mdata[key]
if type(intext) == list:
intext = list_to_str(intext)
intext = round(intext, 3)
intext = str(intext)
if intext.rstrip() == '':
return tooltip
if pretext == None:
pretext = key
if pretext != '':
pretext = pretext + ': '
text = pretext + intext
dlines = split_subs(text, threshold=width)
for i, l in enumerate(dlines):
tooltip += '%s\n' % l
return tooltip
def fmt_length(prop):
prop = str(round(prop, 2)) + 'm'
return prop
def has(mdata, prop):
if mdata.get(prop) is not None and mdata[prop] is not None and mdata[prop] is not False:
return True
else:
return False
def generate_tooltip(mdata):
else:
mparams = mdata['parameters']
t = ''
t = writeblock(t, mdata['name'], width=col_w)
t += '\n'
t = writeblockm(t, mdata, key='description', pretext='', width=col_w)
if mdata['description'] != '':
t += '\n'
bools = (('rig', None), ('animated', None), ('manifold', 'non-manifold'), ('scene', None), ('simulation', None),
('uv', None))
for b in bools:
if mparams.get(b[0]):
mdata['tags'].append(b[0])
elif b[1] != None:
mdata['tags'].append(b[1])
bools_data = ('adult',)
for b in bools_data:
if mdata.get(b) and mdata[b]:
mdata['tags'].append(b)
t = writeblockm(t, mparams, key='designer', pretext='designer', width=col_w)
t = writeblockm(t, mparams, key='manufacturer', pretext='manufacturer', width=col_w)
t = writeblockm(t, mparams, key='designCollection', pretext='design collection', width=col_w)
# t = writeblockm(t, mparams, key='engines', pretext='engine', width = col_w)
# t = writeblockm(t, mparams, key='model_style', pretext='style', width = col_w)
# t = writeblockm(t, mparams, key='material_style', pretext='style', width = col_w)
# t = writeblockm(t, mdata, key='tags', width = col_w)
# t = writeblockm(t, mparams, key='condition', pretext='condition', width = col_w)
# t = writeblockm(t, mparams, key='productionLevel', pretext='production level', width = col_w)
t = writeblockm(t, mparams, key='pbrType', pretext='pbr', width=col_w)
t = writeblockm(t, mparams, key='designYear', pretext='design year', width=col_w)
if has(mparams, 'dimensionX'):
t += 'size: %s, %s, %s\n' % (fmt_length(mparams['dimensionX']),
fmt_length(mparams['dimensionY']),
fmt_length(mparams['dimensionZ']))
if has(mparams, 'faceCount'):
t += 'face count: %s, render: %s\n' % (mparams['faceCount'], mparams['faceCountRender'])
# write files size - this doesn't reflect true file size, since files size is computed from all asset files, including resolutions.
if mdata.get('filesSize'):
fs = mdata['filesSize']
fsmb = fs // 1024
fskb = fs % 1024
if fsmb == 0:
t += 'files size: %iKB\n' % fskb
else:
t += 'files size: %iMB %iKB\n' % (fsmb, fskb)
# t = writeblockm(t, mparams, key='meshPolyType', pretext='mesh type', width = col_w)
# t = writeblockm(t, mparams, key='objectCount', pretext='nubmber of objects', width = col_w)
# t = writeblockm(t, mparams, key='materials', width = col_w)
# t = writeblockm(t, mparams, key='modifiers', width = col_w)
# t = writeblockm(t, mparams, key='shaders', width = col_w)
t += 'texture size: %s\n' % fmt_length(mparams['textureSizeMeters'])
if has(mparams, 'textureResolutionMax') and mparams['textureResolutionMax'] > 0:
if mparams['textureResolutionMin'] == mparams['textureResolutionMax']:
t = writeblockm(t, mparams, key='textureResolutionMin', pretext='texture resolution', width=col_w)
else:
t += 'tex resolution: %i - %i\n' % (mparams['textureResolutionMin'], mparams['textureResolutionMax'])
if has(mparams, 'thumbnailScale'):
t = writeblockm(t, mparams, key='thumbnailScale', pretext='preview scale', width=col_w)
t = writeblockm(t, mdata, key='license', width=col_w)
# generator is for both upload preview and search, this is only after search
# if mdata.get('versionNumber'):
# # t = writeblockm(t, mdata, key='versionNumber', pretext='version', width = col_w)
# a_id = mdata['author'].get('id')
# if a_id != None:
# adata = bpy.context.window_manager['bkit authors'].get(str(a_id))
# if adata != None:
# t += generate_author_textblock(adata)
# t += '\n'
if len(t.split('\n')) < 6:
t += '\n'
t += get_random_tip(mdata)
t += '\n'
def get_random_tip(mdata):
t = ''
tip = 'Tip: ' + random.choice(rtips)
t = writeblock(t, tip)
return t
# at = mdata['assetType']
# if at == 'brush' or at == 'texture':
# t += 'click to link %s' % mdata['assetType']
# if at == 'model' or at == 'material':
# tips = ['Click or drag in scene to link/append %s' % mdata['assetType'],
# "'A' key to search assets by same author",
# "'W' key to open Authors webpage",
# ]
# tip = 'Tip: ' + random.choice(tips)
# t = writeblock(t, tip)
col_w = 40
if len(adata['firstName'] + adata['lastName']) > 0:
t = 'Author:\n'
t += '%s %s\n' % (adata['firstName'], adata['lastName'])
t += '\n'
if adata.get('aboutMeUrl') is not None:
t = writeblockm(t, adata, key='aboutMeUrl', pretext='', width=col_w)
t += '\n'
if adata.get('aboutMe') is not None:
t = writeblockm(t, adata, key='aboutMe', pretext='', width=col_w)
t += '\n'
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
return t
def get_items_models(self, context):
global search_items_models
return search_items_models
def get_items_brushes(self, context):
global search_items_brushes
return search_items_brushes
def get_items_materials(self, context):
global search_items_materials
return search_items_materials
def get_items_textures(self, context):
global search_items_textures
return search_items_textures
class ThumbDownloader(threading.Thread):
query = None
def __init__(self, url, path):
super(ThumbDownloader, self).__init__()
self.url = url
self.path = path
self._stop_event = threading.Event()
def stop(self):
self._stop_event.set()
def stopped(self):
return self._stop_event.is_set()
def run(self):
if r.status_code == 200:
with open(self.path, 'wb') as f:
f.write(r.content)
# ORIGINALLY WE DOWNLOADED THUMBNAILS AS STREAM, BUT THIS WAS TOO SLOW.
# with open(path, 'wb') as f:
# for chunk in r.iter_content(1048576*4):
# f.write(chunk)
def write_gravatar(a_id, gravatar_path):
'''
Write down gravatar path, as a result of thread-based gravatar image download.
This should happen on timer in queue.
'''
# print('write author', a_id, type(a_id))
if authors.get(a_id) is not None:
adata = authors.get(a_id)
adata['gravatarImg'] = gravatar_path
def fetch_gravatar(adata):
utils.p('fetch gravatar')
if adata.get('gravatarHash') is not None:
gravatar_path = paths.get_temp_dir(subdir='bkit_g/') + adata['gravatarHash'] + '.jpg'
if os.path.exists(gravatar_path):
tasks_queue.add_task((write_gravatar, (adata['id'], gravatar_path)))
return;
url = "https://www.gravatar.com/avatar/" + adata['gravatarHash'] + '?d=404'
r = rerequests.get(url, stream=False)
with open(gravatar_path, 'wb') as f:
f.write(r.content)
tasks_queue.add_task((write_gravatar, (adata['id'], gravatar_path)))
elif r.status_code == '404':
adata['gravatarHash'] = None
utils.p('gravatar for author not available.')
''' Writes author info (now from search results) and fetches gravatar if needed.'''
global fetching_gravatars
a_id = str(r['author']['id'])
preferences = bpy.context.preferences.addons['blenderkit'].preferences
authors = bpy.context.window_manager.get('bkit authors', {})
if authors == {}:
bpy.context.window_manager['bkit authors'] = authors
a = authors.get(a_id)
if a is None: # or a is '' or (a.get('gravatarHash') is not None and a.get('gravatarImg') is None):
a = r['author']
a['id'] = a_id
a['tooltip'] = generate_author_textblock(a)
authors[a_id] = a
if fetching_gravatars.get(a['id']) is None:
fetching_gravatars[a['id']] = True
thread = threading.Thread(target=fetch_gravatar, args=(a.copy(),), daemon=True)
thread.start()
return a
def write_profile(adata):
utils.p('writing profile')
# we have to convert to MiB here, numbers too big for python int type
if user.get('sumAssetFilesSize') is not None:
user['sumAssetFilesSize'] /= (1024 * 1024)
if user.get('sumPrivateAssetFilesSize') is not None:
user['sumPrivateAssetFilesSize'] /= (1024 * 1024)
if user.get('remainingPrivateQuota') is not None:
user['remainingPrivateQuota'] /= (1024 * 1024)
Vilem Duha
committed
if adata.get('canEditAllAssets') is True:
user['exmenu'] = True
else:
user['exmenu'] = False
def request_profile(api_key):
a_url = paths.get_api_url() + 'me/'
headers = utils.get_headers(api_key)
adata = r.json()
if adata.get('user') is None:
utils.p(adata)
utils.p('getting profile failed')
return None
return adata
def fetch_profile(api_key):
utils.p('fetch profile')
try:
adata = request_profile(api_key)
if adata is not None:
tasks_queue.add_task((write_profile, (adata,)))
except Exception as e:
utils.p(e)
def get_profile():
preferences = bpy.context.preferences.addons['blenderkit'].preferences
a = bpy.context.window_manager.get('bkit profile')
thread = threading.Thread(target=fetch_profile, args=(preferences.api_key,), daemon=True)
thread.start()
class Searcher(threading.Thread):
query = None
super(Searcher, self).__init__()
self.query = query
self.params = params
self._stop_event = threading.Event()
def stop(self):
self._stop_event.set()
def stopped(self):
return self._stop_event.is_set()
query = self.query
params = self.params
# build a new request
url = paths.get_api_url() + 'search/'
# build request manually
# TODO use real queries
requeststring = '?query='
#
if query.get('query') not in ('', None):
requeststring += query['query'].lower()
for i, q in enumerate(query):
if q != 'query':
requeststring += '+'
requeststring += q + ':' + str(query[q]).lower()
# result ordering: _score - relevance, score - BlenderKit score
if query.get('query') is None and query.get('category_subtree') == None:
# assumes no keywords and no category, thus an empty search that is triggered on start.
# orders by last core file upload
requeststring += '+order:created'
else:
requeststring += '+order:-last_upload'
elif query.get('author_id') is not None and utils.profile_is_validator():
requeststring += '+order:-created'
else:
if query.get('category_subtree') is not None:
requeststring += '+order:-score,_score'
else:
requeststring += '+order:_score'
requeststring += '&addon_version=%s' % params['addon_version']
if params.get('scene_uuid') is not None:
requeststring += '&scene_uuid=%s' % params['scene_uuid']
query = self.query
params = self.params
global reports
t = time.time()
mt('search thread started')
tempdir = paths.get_temp_dir('%s_search' % query['asset_type'])
# json_filepath = os.path.join(tempdir, '%s_searchresult.json' % query['asset_type'])
headers = utils.get_headers(params['api_key'])
rdata = {}
rdata['results'] = []
if params['get_next']:
r = rerequests.get(urlquery, headers=headers) # , params = rparameters)
except requests.exceptions.RequestException as e:
print(e)
reports = e
# props.report = e
return
mt('response is back ')
try:
rdata = r.json()
except Exception as inst:
reports = r.text
print(inst)
mt('data parsed ')
# print('number of results: ', len(rdata.get('results', [])))
return
mt('search finished')
i = 0
thumb_small_urls = []
thumb_small_filepaths = []
thumb_full_urls = []
thumb_full_filepaths = []
# END OF PARSING
for d in rdata.get('results', []):
for f in d['files']:
# TODO move validation of published assets to server, too manmy checks here.
if f['fileType'] == 'thumbnail' and f['fileThumbnail'] != None and f['fileThumbnailLarge'] != None:
if f['fileThumbnail'] == None:
f['fileThumbnail'] = 'NONE'
if f['fileThumbnailLarge'] == None:
f['fileThumbnailLarge'] = 'NONE'
thumb_small_urls.append(f['fileThumbnail'])
thumb_full_urls.append(f['fileThumbnailLarge'])
imgname = paths.extract_filename_from_url(f['fileThumbnail'])
imgpath = os.path.join(tempdir, imgname)
thumb_small_filepaths.append(imgpath)
imgname = paths.extract_filename_from_url(f['fileThumbnailLarge'])
imgpath = os.path.join(tempdir, imgname)
thumb_full_filepaths.append(imgpath)
sml_thbs = zip(thumb_small_filepaths, thumb_small_urls)
full_thbs = zip(thumb_full_filepaths, thumb_full_urls)
# we save here because a missing thumbnail check is in the previous loop
# we can also prepend previous results. These have downloaded thumbnails already...
rdata['results'][0:0] = self.result['results']
self.result = rdata
# with open(json_filepath, 'w') as outfile:
# json.dump(rdata, outfile)
killthreads_sml = []
for k in thumb_sml_download_threads.keys():
if k not in thumb_small_filepaths:
killthreads_sml.append(k) # do actual killing here?
killthreads_full = []
for k in thumb_full_download_threads.keys():
if k not in thumb_full_filepaths:
killthreads_full.append(k) # do actual killing here?
# TODO do the killing/ stopping here! remember threads might have finished inbetween!
if self.stopped():
return
# this loop handles downloading of small thumbnails
for imgpath, url in sml_thbs:
if imgpath not in thumb_sml_download_threads and not os.path.exists(imgpath):
thread = ThumbDownloader(url, imgpath)
# thread = threading.Thread(target=download_thumbnail, args=([url, imgpath]),
# daemon=True)
thread.start()
thumb_sml_download_threads[imgpath] = thread
# threads.append(thread)
if len(thumb_sml_download_threads) > maxthreads:
while len(thumb_sml_download_threads) > maxthreads:
threads_copy = thumb_sml_download_threads.copy() # because for loop can erase some of the items.
for tk, thread in threads_copy.items():
if not thread.is_alive():
thread.join()
return
idx = 0
while len(thumb_sml_download_threads) > 0:
threads_copy = thumb_sml_download_threads.copy() # because for loop can erase some of the items.
for tk, thread in threads_copy.items():
if not thread.is_alive():
thread.join()
del (thumb_sml_download_threads[tk])
i += 1
if self.stopped():
return
# start downloading full thumbs in the end
for imgpath, url in full_thbs:
if imgpath not in thumb_full_download_threads and not os.path.exists(imgpath):
thread = ThumbDownloader(url, imgpath)
# thread = threading.Thread(target=download_thumbnail, args=([url, imgpath]),
# daemon=True)
thread.start()
thumb_full_download_threads[imgpath] = thread
mt('thumbnails finished')
def build_query_common(query, props):
'''add shared parameters to query'''
query_common = {}
query_common["query"] = props.search_keywords
if props.search_verification_status != 'ALL':
query_common['verification_status'] = props.search_verification_status.lower()
query_common["files_size_gte"] = props.search_file_size_min * 1024 * 1024
query_common["files_size_lte"] = props.search_file_size_max * 1024 * 1024