def action(self): """Save the entered text to the BookmarkDB.""" index = self.parent().selectionModel().currentIndex() text = u'{}'.format(index.data(common.DescriptionRole)) if text.lower() == self.text().lower(): self.hide() return if not index.data(common.ParentPathRole): self.hide() return p = index.data(QtCore.Qt.StatusTipRole) if common.is_collapsed(p): k = common.proxy_path(index) else: k = p db = bookmark_db.get_db( index.data(common.ParentPathRole)[0], index.data(common.ParentPathRole)[1], index.data(common.ParentPathRole)[2] ) db.setValue(k, u'description', self.text()) source_index = index.model().mapToSource(index) data = source_index.model().model_data()[source_index.row()] data[common.DescriptionRole] = self.text() self.parent().update(source_index) self.hide()
def update_description(index, res): db = bookmark_db.get_db( index.data(common.ParentPathRole)[0], index.data(common.ParentPathRole)[1], index.data(common.ParentPathRole)[2], ) source_index = self.model().mapToSource(index) data = source_index.model().model_data()[source_index.row()] self.model().sourceModel().update_description(db, data)
def setUpClass(cls): super(TestSQLite, cls).setUpClass() import bookmarks.bookmark_db as bookmark_db cls.db = bookmark_db.get_db( cls.server, cls.job, cls.bookmarks[0] )
def export_favourites(): """Saves all favourites including the descriptions and the thumbnails.""" try: import uuid import bookmarks.settings as settings import bookmarks.bookmark_db as bookmark_db res = QtWidgets.QFileDialog.getSaveFileName( caption=u'Select where to save your favourites', filter=u'*.favourites', dir=QtCore.QStandardPaths.writableLocation( QtCore.QStandardPaths.HomeLocation), ) destination, _ = res if not destination: return favourites = settings.local_settings.favourites() server, job, root = get_favourite_parent_paths() db = bookmark_db.get_db(server, job, root) zip_path = u'{}/{}/{}/{}.zip'.format(server, job, root, uuid.uuid4()) # Make sure the temp folder exists QtCore.QFileInfo(zip_path).dir().mkpath(u'.') with zipfile.ZipFile(zip_path, 'a') as z: # Adding thumbnail to zip for favourite in favourites: file_info = QtCore.QFileInfo(db.thumbnail_path(favourite)) if not file_info.exists(): continue z.write(file_info.filePath(), file_info.fileName()) z.writestr(u'favourites', u'\n'.join(favourites)) file_info = QtCore.QFileInfo(zip_path) if not file_info.exists(): raise RuntimeError( u'Unexpected error occured: could not find the favourites file' ) QtCore.QDir().rename(file_info.filePath(), destination) if not QtCore.QFileInfo(destination).exists(): raise RuntimeError( u'Unexpected error occured: could not find the favourites file' ) reveal(destination) except Exception as e: import bookmarks.log as log import bookmarks.common_ui as common_ui common_ui.ErrorBox(u'Could not save the favourites.', u'{}'.format(e)).open() log.error(u'Exporting favourites failed.') raise
def refresh(self): """Populates the list from the database.""" if not self.parent(): return if not self.index.isValid(): return if not self.index.data(common.FileInfoLoaded): return db = bookmark_db.get_db( self.index.data(common.ParentPathRole)[0], self.index.data(common.ParentPathRole)[1], self.index.data(common.ParentPathRole)[2]) if self.index.data(common.TypeRole) == common.FileItem: k = self.index.data(QtCore.Qt.StatusTipRole) elif self.index.data(common.TypeRole) == common.SequenceItem: k = common.proxy_path(self.index) v = db.value(k, u'notes') if not v: return try: v = base64.b64decode(v) d = json.loads(v) except: log.error(u'Error decoding notes from JSON') return if not v: return self.clear() keys = sorted(d.keys()) try: for k in keys: self.add_item(text=d[k][u'text'], checked=d[k][u'checked']) except: log.error(u'Error adding notes') common_ui.ErrorBox(u'Error refreshing the data', u'').open() raise
def init_database_values(self, compare=False): """Load the current settings from the database and apply them to the UI controls. """ def set_saved(k): v = db.value(1, k.replace(u'_editor', u''), table=u'properties') if not v: return if compare: return v getattr(self, k).setText(unicode(v) if v else u'') def emit_text(k): getattr(self, k).textEdited.emit(getattr(self, k).text()) controls = (u'framerate_editor', u'width_editor', u'height_editor', u'prefix_editor', u'startframe_editor', u'duration_editor', u'identifier_editor', u'slacktoken_editor') db = bookmark_db.get_db(self.server, self.job, self.root) d = {} with db.transactions(): try: for control in controls: d[control] = set_saved(control) if compare: continue emit_text(control) except Exception as e: common_ui.ErrorBox( u'Could not save the properties.', u'There seems to be an error with the database:\n{}'. format(e), ).open() log.error(u'Error saving properties to the database') raise return d
def save_settings(self): """Saves the current list of todo items to the assets configuration file.""" if not self.index.isValid(): return data = {} for n in xrange(len(self.todoeditors_widget.items)): item = self.todoeditors_widget.items[n] editor = item.findChild(TodoItemEditor) checkbox = item.findChild(CheckBoxButton) if not editor.document().toPlainText(): continue data[n] = { u'checked': not checkbox.checked, u'text': editor.document().toHtml(), } k = common.proxy_path(self.index) db = bookmark_db.get_db( self.index.data(common.ParentPathRole)[0], self.index.data(common.ParentPathRole)[1], self.index.data(common.ParentPathRole)[2]) try: v = json.dumps(data, ensure_ascii=False, encoding='utf-8') v = base64.b64encode(v.encode('utf-8')) except: s = u'Error saving notes.' log.error(s) common_ui.ErrorBox(u'Error saving notes.', s).open() raise db.setValue(k, u'notes', v) todo_count = len([k for k in data if not data[k][u'checked']]) self.index.model().setData(self.index, todo_count, role=common.TodoCountRole)
def save_values_to_database(self, compare=False): """Save the current UI values to the database. """ def save(k, db): """Performs the save to the database.""" v = getattr(self, k).text() if compare: return v k = k.replace(u'_editor', u'') db.setValue(1, k, v, table=u'properties') return None controls = (u'framerate_editor', u'width_editor', u'height_editor', u'prefix_editor', u'startframe_editor', u'duration_editor', u'identifier_editor', u'slacktoken_editor') db = bookmark_db.get_db(self.server, self.job, self.root) d = {} with db.transactions(): for control in controls: d[control] = save(control, db) return d
def __initdata__(self): """Collects the data needed to populate the bookmarks model. Bookmarks are made up of a tuple of ``(server, job, root)`` values and are stored in the local user system settings, eg. the Registry in under windows. Each bookmarks can be associated with a thumbnail, custom description and a list of comments, todo items. Note: This model does not have threads associated with it as fetching necessary data is relatively inexpensive. """ def dflags(): """The default flags to apply to the item.""" return (QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemNeverHasChildren | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable) task_folder = self.task_folder() active_paths = settings.local_settings.verify_paths() favourites = settings.local_settings.favourites() bookmarks = settings.local_settings.value(u'bookmarks') bookmarks = bookmarks if bookmarks else {} _height = self.ROW_SIZE.height() - common.ROW_SEPARATOR() for k, v in bookmarks.iteritems(): if not all(v.values()): continue file_info = QtCore.QFileInfo(k) exists = file_info.exists() if exists: flags = dflags() pixmap = images.ImageCache.get_rsc_pixmap( u'bookmark_sm', common.ADD, _height) else: flags = dflags() | common.MarkedAsArchived pixmap = images.ImageCache.get_rsc_pixmap( u'failed', common.REMOVE, _height) placeholder_image = pixmap default_thumbnail_image = pixmap filepath = file_info.filePath().lower() # Active Flag if all((v[u'server'] == active_paths[u'server'], v[u'job'] == active_paths[u'job'], v[u'root'] == active_paths[u'root'])): flags = flags | common.MarkedAsActive # Favourite Flag if filepath in favourites: flags = flags | common.MarkedAsFavourite text = u'{} | {}'.format( v[u'job'], v[u'root'], # v[u'root'].split(u'/').pop(), ) data = self.INTERNAL_MODEL_DATA[task_folder][common.FileItem] idx = len(data) data[idx] = common.DataDict({ QtCore.Qt.DisplayRole: text, QtCore.Qt.EditRole: text, QtCore.Qt.StatusTipRole: filepath, QtCore.Qt.ToolTipRole: filepath, QtCore.Qt.SizeHintRole: self.ROW_SIZE, # common.TextSegmentRole: self.get_text_segments(text), # common.EntryRole: [], common.FlagsRole: flags, common.ParentPathRole: (v[u'server'], v[u'job'], v[u'root']), common.DescriptionRole: u'', common.TodoCountRole: 0, common.FileDetailsRole: None, common.SequenceRole: None, common.EntryRole: [], common.FileInfoLoaded: False, common.StartpathRole: None, common.EndpathRole: None, # common.ThumbnailLoaded: False, # common.TypeRole: common.FileItem, # common.SortByNameRole: text, common.SortByLastModifiedRole: file_info.lastModified().toMSecsSinceEpoch(), common.SortBySizeRole: file_info.size(), # common.AssetCountRole: 0, # common.IdRole: idx }) db = None n = 0 db = bookmark_db.get_db( v[u'server'], v[u'job'], v[u'root'], ) with db.transactions(): # Item flags flags = data[idx][common.FlagsRole] v = db.value(data[idx][QtCore.Qt.StatusTipRole], u'flags') flags = flags | v if v is not None else flags data[idx][common.FlagsRole] = flags # Todos are a little more convoluted - the todo count refers to # all the current outstanding todos af all assets, including # the bookmark itself n = 0 for v in db.values(u'notes').itervalues(): if not v: continue if v[u'notes']: try: v = base64.b64decode(v[u'notes']) d = json.loads(v) n += len([ k for k in d if not d[k][u'checked'] and d[k][u'text'] ]) except (ValueError, TypeError): log.error(u'Error decoding JSON notes') data[idx][common.TodoCountRole] = n self.update_description(db, data[idx]) self.activeChanged.emit(self.active_index())
def import_favourites(source=None): """Import a previously exported favourites file. Args: source (unicode): Path to a file. Defaults to `None`. """ try: import bookmarks.settings as settings import bookmarks.bookmark_db as bookmark_db if not isinstance(source, unicode): res = QtWidgets.QFileDialog.getOpenFileName( caption=u'Select the favourites file to import', filter=u'*.favourites' # options=QtWidgets.QFileDialog.ShowDirsOnly ) source, _ = res if not source: return current_favourites = settings.local_settings.favourites() create_temp_dir() with zipfile.ZipFile(source) as zip: namelist = zip.namelist() namelist = [f.lower() for f in namelist] if u'favourites' not in namelist: import bookmarks.log as log import bookmarks.common_ui as common_ui s = u'The favourites list is missing from the archive.' common_ui.ErrorBox( u'Invalid ".favourites" file', s, ).open() log.error(s) raise RuntimeError(s) with zip.open(u'favourites') as f: favourites = f.readlines() favourites = [unicode(f).strip().lower() for f in favourites] server, job, root = get_favourite_parent_paths() db = bookmark_db.get_db(server, job, root) for favourite in favourites: file_info = QtCore.QFileInfo(db.thumbnail_path(favourite)) if file_info.fileName().lower() in namelist: dest = u'{}/{}/{}/.bookmark'.format(server, job, root) zip.extract(file_info.fileName(), dest) if favourite not in current_favourites: current_favourites.append(favourite) current_favourites = sorted(list(set(current_favourites))) settings.local_settings.setValue(u'favourites', current_favourites) except Exception as e: import bookmarks.log as log import bookmarks.common_ui as common_ui common_ui.ErrorBox(u'Could not import the favourites.', u'{}'.format(e)).open() log.error(u'Import favourites failed.') raise
def process_data(self, ref): """Populates the item with the missing file information. Args: ref (weakref): An internal model data DataDict instance's weakref. Returns: bool: `True` if all went well, `False` otherwise. """ def is_valid(): return False if not ref() or self.interrupt or ref()[ common.FileInfoLoaded] else True if not is_valid(): return False try: pp = ref()[common.ParentPathRole] db = bookmark_db.get_db(pp[0], pp[1], pp[2]) if not is_valid(): return False collapsed = common.is_collapsed(ref()[QtCore.Qt.StatusTipRole]) seq = ref()[common.SequenceRole] if not is_valid(): return False proxy_k = common.proxy_path(ref()) if collapsed: k = proxy_k else: if not is_valid(): return False k = ref()[QtCore.Qt.StatusTipRole] # Issues SQLite "BEGIN" with db.transactions(): # Description v = db.value(k, u'description') if v: if not is_valid(): return False ref()[common.DescriptionRole] = v v = db.value(k, u'notes') count = 0 if v: try: v = base64.b64decode(v) v = json.loads(v) count = [ k for k in v if v[k][u'text'] and not v[k][u'checked'] ] count = len(count) except: log.error(u'Could not read notes') if not is_valid(): return False ref()[common.TodoCountRole] = count # Item flags if not is_valid(): return False flags = ref( )[common. FlagsRole] | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsDragEnabled v = db.value(k, u'flags') if v: flags = flags | v v = db.value(proxy_k, u'flags') if v: flags = flags | v if not is_valid(): return False ref()[common.FlagsRole] = flags # For sequences we will work out the name of the sequence based on # the frames. if not is_valid(): return False if ref()[common.TypeRole] == common.SequenceItem: if not is_valid(): return False frs = ref()[common.FramesRole] intframes = [int(f) for f in frs] padding = len(frs[0]) rangestring = common.get_ranges(intframes, padding) if not is_valid(): return False seq = ref()[common.SequenceRole] startpath = \ seq.group(1) + \ unicode(min(intframes)).zfill(padding) + \ seq.group(3) + \ u'.' + \ seq.group(4) endpath = \ seq.group(1) + \ unicode(max(intframes)).zfill(padding) + \ seq.group(3) + \ u'.' + \ seq.group(4) seqpath = \ seq.group(1) + \ u'[' + rangestring + u']' + \ seq.group(3) + \ u'.' + \ seq.group(4) seqname = seqpath.split(u'/')[-1] # Setting the path names if not is_valid(): return False ref()[common.StartpathRole] = startpath if not is_valid(): return False ref()[common.EndpathRole] = endpath if not is_valid(): return False ref()[QtCore.Qt.StatusTipRole] = seqpath if not is_valid(): return False ref()[QtCore.Qt.ToolTipRole] = seqpath if not ref(): return False ref()[QtCore.Qt.DisplayRole] = seqname if not is_valid(): return False ref()[QtCore.Qt.EditRole] = seqname if not is_valid(): return False # We saved the DirEntry instances previously in `__initdata__` but # only for the thread to extract the information from it. if not is_valid(): return False er = ref()[common.EntryRole] if er: mtime = 0 for entry in er: stat = entry.stat() mtime = stat.st_mtime if stat.st_mtime > mtime else mtime if not is_valid(): return False ref()[common.SortBySizeRole] += stat.st_size if not is_valid(): return False ref()[common.SortByLastModifiedRole] = mtime mtime = common.qlast_modified(mtime) if not is_valid(): return False info_string = \ unicode(len(intframes)) + u'f;' + \ mtime.toString(u'dd') + u'/' + \ mtime.toString(u'MM') + u'/' + \ mtime.toString(u'yyyy') + u' ' + \ mtime.toString(u'hh') + u':' + \ mtime.toString(u'mm') + u';' + \ common.byte_to_string(ref()[common.SortBySizeRole]) if not is_valid(): return False ref()[common.FileDetailsRole] = info_string if not is_valid(): return False if ref()[common.TypeRole] == common.FileItem: if not is_valid(): return False er = ref()[common.EntryRole] if er: stat = er[0].stat() mtime = stat.st_mtime ref()[common.SortByLastModifiedRole] = mtime mtime = common.qlast_modified(mtime) ref()[common.SortBySizeRole] = stat.st_size info_string = \ mtime.toString(u'dd') + u'/' + \ mtime.toString(u'MM') + u'/' + \ mtime.toString(u'yyyy') + u' ' + \ mtime.toString(u'hh') + u':' + \ mtime.toString(u'mm') + u';' + \ common.byte_to_string(ref()[common.SortBySizeRole]) if not is_valid(): return False ref()[common.FileDetailsRole] = info_string if not is_valid(): return False # Finally, set flag to mark this loaded if not is_valid(): return False return True except: log.error(u'Error processing file info.') finally: if ref(): ref()[common.FileInfoLoaded] = True
def __initdata__(self): """Collects the data needed to populate the bookmarks model by querrying the path stored in ``self.parent_path``. Note: Getting asset information is relatively cheap, hence the model does not have any threads associated with it. """ def dflags(): """The default flags to apply to the item.""" return (QtCore.Qt.ItemNeverHasChildren | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable) if not self.parent_path: return if not all(self.parent_path): return task_folder = self.task_folder() dtype = self.data_type() self.INTERNAL_MODEL_DATA[task_folder] = common.DataDict({ common.FileItem: common.DataDict(), common.SequenceItem: common.DataDict() }) favourites = settings.local_settings.favourites() sfavourites = set(favourites) activeasset = settings.local_settings.value(u'activepath/asset') server, job, root = self.parent_path bookmark_path = u'{}/{}/{}'.format(server, job, root) # Let's get the identifier from the bookmark database db = bookmark_db.get_db(server, job, root) ASSET_IDENTIFIER = db.value(1, u'identifier', table='properties') nth = 1 c = 0 for entry in _scandir.scandir(bookmark_path): if entry.name.startswith(u'.'): continue if not entry.is_dir(): continue filepath = entry.path.replace(u'\\', u'/') if ASSET_IDENTIFIER: identifier = u'{}/{}'.format(filepath, ASSET_IDENTIFIER) if not QtCore.QFileInfo(identifier).exists(): continue # Progress bar c += 1 if not c % nth: self.progressMessage.emit(u'Found {} assets...'.format(c)) QtWidgets.QApplication.instance().processEvents( QtCore.QEventLoop.ExcludeUserInputEvents) filename = entry.name flags = dflags() if filepath.lower() in sfavourites: flags = flags | common.MarkedAsFavourite if activeasset: if activeasset.lower() == filename.lower(): flags = flags | common.MarkedAsActive idx = len(self.INTERNAL_MODEL_DATA[task_folder][dtype]) name = re.sub(ur'[_]{1,}', u' ', filename).strip(u'_') self.INTERNAL_MODEL_DATA[task_folder][dtype][ idx] = common.DataDict({ QtCore.Qt.DisplayRole: name, QtCore.Qt.EditRole: filename, QtCore.Qt.StatusTipRole: filepath, QtCore.Qt.SizeHintRole: self.ROW_SIZE, # common.EntryRole: [ entry, ], common.FlagsRole: flags, common.ParentPathRole: (server, job, root, filename), common.DescriptionRole: u'', common.TodoCountRole: 0, common.FileDetailsRole: u'', common.SequenceRole: None, common.FramesRole: [], common.FileInfoLoaded: False, common.StartpathRole: None, common.EndpathRole: None, # common.ThumbnailLoaded: False, # common.TypeRole: common.FileItem, # common.SortByNameRole: common.namekey(filepath), common.SortByLastModifiedRole: 0, common.SortBySizeRole: 0, # common.IdRole: idx }) # Explicitly emit signal to notify the other dependent model self.activeChanged.emit(self.active_index())