def _search(self): error = None query = self._input.text() if len(query.split()) == 1 and all(o not in query.strip() for o in '=<>'): objectid = query.strip() try: entries = [self.store.get(objectid)] except KeyError: error = _("objectid '{oid}' not found", oid=objectid) else: try: conditions = parse_expression(query) except tdparser.Error as e: error = e.args[0] else: conditions = self._alter_search_conditions(conditions) entries = self.store.query(conditions, limit=self.MAX_RESULTS) self._result_tree.clear() if error is not None: w = QtWidgets.QTreeWidgetItem([error]) w.setForeground(0, QtGui.QColor(255, 0, 0)) self._result_tree.addTopLevelItem(w) self._result_tree.setFirstItemColumnSpanned(w, True) else: for i, entry in enumerate(entries): file_item = FileItem(entry) f = file_item.font(0) f.setBold(True) file_item.setFont(0, f) self._result_tree.addTopLevelItem(file_item) self._result_tree.setFirstItemColumnSpanned(file_item, True) for k, v in entry.metadata.items(): file_item.addChild(MetadataItem(entry, k, v)) if i + 1 == self.MAX_RESULTS: last_item = QtWidgets.QTreeWidgetItem([ _("... stripped after {nb} results...", nb=self.MAX_RESULTS) ]) f = last_item.font(0) f.setBold(True) f.setItalic(True) last_item.setFont(0, f) self._result_tree.addTopLevelItem(last_item) self._result_tree.setFirstItemColumnSpanned( last_item, True) break if self._result_tree.topLevelItemCount() == 0: w = QtWidgets.QTreeWidgetItem([_("No matches")]) self._result_tree.addTopLevelItem(w) self._result_tree.setFirstItemColumnSpanned(w, True) self._result_tree.expandAll() self._set_needs_refresh(False)
def _create_buttons(self): buttons = [] # Open button; uses the system to choose the program to open with # (on Windows, might ask you what to use every time because of filename # scheme) open_button = QtWidgets.QPushButton(_("Open")) if openfile is not None: open_button.clicked.connect(self._openfile) buttons.append(('single', open_button)) else: open_button.setEnabled(False) # Copy hash button copy_button = QtWidgets.QPushButton(_("Copy ID")) copy_button.clicked.connect(self._copy_objectid) buttons.append(('single', copy_button)) # Edit metadata button edit_button = QtWidgets.QPushButton(_("Edit metadata...")) edit_button.clicked.connect(self._edit_metadata) buttons.append(('single', edit_button)) # Delete button, removes what's selected (with confirmation) remove_button = QtWidgets.QPushButton(_("Delete")) remove_button.clicked.connect(self._delete) buttons.append(('multi', remove_button)) return buttons
def parse_new_metadata(args): """Parses a list of key=value or key=type:value arguments. """ metadata = {} for a in args: k = a.split('=', 1) if len(k) != 2: sys.stderr.write( _("Metadata should have format key=value or " "key=type:value (eg. age=int:23)\n")) sys.exit(1) k, v = k if ':' in v: t, v = v.split(':', 1) else: t = 'str' if k in metadata: sys.stderr.write("Multiple values for key %s\n" % k) sys.exit(1) if t == 'int': v = int(v) elif t == 'str': if isinstance(v, bytes): v = v.decode(locale.getpreferredencoding()) else: sys.stderr.write( _( "Metadata has unknown type '{t}'! Only 'str' " "and 'int' are supported.\n" "If you meant a string with a ':', use " "'str:mystring'", t=t)) sys.exit(1) metadata[k] = {'type': t, 'value': v} return metadata
def parse_new_metadata(args): """Parses a list of key=value or key=type:value arguments. """ metadata = {} for a in args: k = a.split('=', 1) if len(k) != 2: sys.stderr.write(_("Metadata should have format key=value or " "key=type:value (eg. age=int:23)\n")) sys.exit(1) k, v = k if ':' in v: t, v = v.split(':', 1) else: t = 'str' if k in metadata: sys.stderr.write("Multiple values for key %s\n" % k) sys.exit(1) if t == 'int': v = int(v) elif t == 'str': if isinstance(v, bytes): v = v.decode(locale.getpreferredencoding()) else: sys.stderr.write(_("Metadata has unknown type '{t}'! Only 'str' " "and 'int' are supported.\n" "If you meant a string with a ':', use " "'str:mystring'", t=t)) sys.exit(1) metadata[k] = {'type': t, 'value': v} return metadata
def cmd_print(store, args): """Print command. print [-m] [-t] <filehash> [...] print [-m] [-t] [key1=value1] [...] """ meta = False types = False while args and args[0][0] == '-': if args[0] == '-m': meta = True elif args[0] == '-t': types = True elif args[0] == '--': del args[0] break else: sys.stderr.write(_("Unknown option: {opt}\n", opt=args[0])) sys.exit(1) del args[0] h, metadata = parse_query_metadata(args) if h is not None: try: entry = store.get(h) except KeyError: sys.stderr.write(_("Objectid not found\n")) sys.exit(2) else: entries = store.query(metadata) try: entry = next(entries) except StopIteration: sys.stderr.write(_("No match found\n")) sys.exit(2) try: next(entries) except StopIteration: pass else: sys.stderr.write(_("Warning: more matching files exist\n")) if meta: for k, v in entry.metadata.items(): if k == 'hash': continue if types: if isinstance(v, int_types): v = 'int:%d' % v else: # isinstance(v, string_types): v = 'str:%s' % v sys.stdout.write("%s\t%s\n" % (k, v)) else: if os.path.isdir(entry.filename): sys.stderr.write(_("Error: match found but is a directory\n")) sys.exit(2) fp = entry.open() try: for chunk in BufferedReader(fp): sys.stdout.write(chunk) finally: fp.close()
def _search(self): error = None query = self._input.text() if len(query.split()) == 1 and all(o not in query.strip() for o in '=<>'): objectid = query.strip() try: entries = [self.store.get(objectid)] except KeyError: error = _("objectid '{oid}' not found", oid=objectid) else: try: conditions = parse_expression(query) except tdparser.Error as e: error = e.args[0] else: conditions = self._alter_search_conditions(conditions) entries = self.store.query(conditions, limit=self.MAX_RESULTS) self._result_tree.clear() if error is not None: w = QtWidgets.QTreeWidgetItem([error]) w.setForeground(0, QtGui.QColor(255, 0, 0)) self._result_tree.addTopLevelItem(w) self._result_tree.setFirstItemColumnSpanned(w, True) else: for i, entry in enumerate(entries): file_item = FileItem(entry) f = file_item.font(0) f.setBold(True) file_item.setFont(0, f) self._result_tree.addTopLevelItem(file_item) self._result_tree.setFirstItemColumnSpanned(file_item, True) for k, v in entry.metadata.items(): file_item.addChild(MetadataItem(entry, k, v)) if i + 1 == self.MAX_RESULTS: last_item = QtWidgets.QTreeWidgetItem( [_("... stripped after {nb} results...", nb=self.MAX_RESULTS)]) f = last_item.font(0) f.setBold(True) f.setItalic(True) last_item.setFont(0, f) self._result_tree.addTopLevelItem(last_item) self._result_tree.setFirstItemColumnSpanned(last_item, True) break if self._result_tree.topLevelItemCount() == 0: w = QtWidgets.QTreeWidgetItem([_("No matches")]) self._result_tree.addTopLevelItem(w) self._result_tree.setFirstItemColumnSpanned(w, True) self._result_tree.expandAll() self._set_needs_refresh(False)
def parse_query_metadata(args): """Parses a list of key=value arguments or a hash value. Returns (hash:str, metadata:dict) """ if len(args) == 1 and '=' not in args[0]: return args[0], None else: metadata = {} for a in args: k = a.split('=', 1) if len(k) != 2: sys.stderr.write(_("Metadata should have format key=value, " "key=type:value (eg. age=int:23) or " "key=type:req (eg. age=int:>21)\n")) sys.exit(1) k, v = k if ':' in v: t, v = v.split(':', 1) if t == 'int': if v[0] == '>': req, v = 'gt', v[1:] elif v[0] == '<': req, v = 'lt', v[1:] else: req = 'equal' v = int(v) elif t == 'str': req = 'equal' else: sys.stderr.write(_("Metadata has unknown type '{t}'! " "Only 'str' and 'int' are supported.\n" "If you meant a string with a ':', " "use 'str:mystring'", t=t)) sys.exit(1) else: t = 'str' req = 'equal' if t == 'str' and isinstance(v, bytes): v = v.decode(locale.getpreferredencoding()) if k in metadata: if t != metadata[k]['type']: sys.stderr.write(_("Differing types for conditions on " "key {k}: {t1}, {t2}\n", k=k, t1=metadata[k]['type'], t2=t)) sys.exit(1) if req in metadata[k]: sys.stderr.write(_("Multiple conditions {cond} on key " "{k}\n", cond=req, k=k)) sys.exit(1) metadata[k][req] = v else: metadata[k] = {'type': t, req: v} return None, metadata
def main(args): warnings.filterwarnings('always', category=UsageWarning) usage = _( "usage: {bin} <store> create\n" " or: {bin} <store> add <filename> [key1=value1] [...]\n" " or: {bin} <store> write [key1=value1] [...]\n" " or: {bin} <store> query [-d] [-t] [key1=value1] [...]\n" " or: {bin} <store> print [-m] [-t] <filehash> [...]\n" " or: {bin} <store> print [-m] [-t] [key1=value1] [...]\n" " or: {bin} <store> remove [-f] <filehash>\n" " or: {bin} <store> remove [-f] <key1=value1> [...]\n" " or: {bin} <store> verify\n" " or: {bin} <store> view\n", bin='file_archive') if len(args) < 2: sys.stderr.write(usage) sys.exit(1) store = args[0] command = args[1] if command == 'create': try: FileStore.create_store(store) except Exception as e: sys.stderr.write(_("Can't create store: {err}\n", err=e.args[0])) sys.exit(3) sys.exit(0) try: store = FileStore(store) except Exception as e: sys.stderr.write(_("Invalid store: {err}\n", err=e.args[0])) sys.exit(3) try: try: func = commands[command] except KeyError: sys.stderr.write(usage) sys.exit(1) try: func(store, args[2:]) except Exception: import traceback traceback.print_exc() sys.exit(3) finally: store.close() sys.exit(0)
def __init__(self, store): QtWidgets.QMainWindow.__init__(self) self.setWindowTitle(self.WINDOW_TITLE) self.store = store searchbar = QtWidgets.QHBoxLayout() self._needs_refresh = False # Input line for the query self._input = QtWidgets.QLineEdit() self._input.setPlaceholderText(_("Enter query here")) self._input.returnPressed.connect(self._search) self._input.textEdited.connect(lambda t: self._set_needs_refresh()) searchbar.addWidget(self._input) # Search button self._searchbutton = QtWidgets.QPushButton(_("Search")) self._searchbutton.clicked.connect(self._search) searchbar.addWidget(self._searchbutton) results = QtWidgets.QHBoxLayout() # Result view, as a tree with metadata self._result_tree = QtWidgets.QTreeWidget() self._result_tree.setColumnCount(3) self._result_tree.setHeaderLabels([_("Key"), _("Value"), _("Type")]) self._result_tree.itemSelectionChanged.connect(self._selection_changed) results.addWidget(self._result_tree) # Buttons, enabled/disabled when the selection changes buttons = QtWidgets.QVBoxLayout() self._buttons = self._create_buttons() for _name, button in self._buttons: buttons.addWidget(button) self._selection_changed() results.addLayout(buttons) layout = QtWidgets.QVBoxLayout() layout.addLayout(searchbar) layout.addLayout(results) widget = QtWidgets.QWidget() widget.setLayout(layout) self.setCentralWidget(widget) self._search()
def _delete(self): items = self._result_tree.selectedItems() if not items: return confirm = QtWidgets.QMessageBox.question( self, _("Are you sure?"), _n("You are about to delete {num} entry from the store. " "Please confirm.", "You are about to delete {num} entries from the store. " "Please confirm.", len(items), num=len(items)), QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel) if confirm == QtWidgets.QMessageBox.Ok: ids = set([i.entry.objectid for i in items]) i = 0 while i < self._result_tree.topLevelItemCount(): oid = self._result_tree.topLevelItem(i).entry.objectid if oid in ids: self.store.remove(oid) self._result_tree.takeTopLevelItem(i) else: i += 1
def cmd_query(store, args): """Query command. query [-d] [-t] [key1=value1] [...] """ pydict = False types = False while args and args[0][0] == '-': if args[0] == '-d': pydict = True elif args[0] == '-t': types = True elif args[0] == '--': del args[0] break else: sys.stderr.write(_("Unknown option: {opt}\n", opt=args[0])) sys.exit(1) del args[0] h, metadata = parse_query_metadata(args) if h is not None: entries = [store.get(h)] else: entries = store.query(metadata) if not pydict: for entry in sorted(entries, key=lambda e: e.objectid): sys.stdout.write("%s\n" % entry.objectid) for k, v in sorted(entry.metadata.items(), key=lambda p: p[0]): if types: if isinstance(v, int_types): v = 'int:%d' % v else: # isinstance(v, string_types): v = 'str:%s' % v sys.stdout.write("\t%s\t%s\n" % (k, v)) else: sys.stdout.write('{') for entry_nb, entry in enumerate(sorted(entries, key=lambda e: e.objectid)): sys.stdout.write(',\n' if entry_nb > 0 else '\n') sys.stdout.write(' "%s": {' % entry.objectid) for meta_nb, (k, v) in enumerate(sorted(entry.metadata.items(), key=lambda p: p[0])): sys.stdout.write(',\n' if meta_nb > 0 else '\n') if types: if isinstance(v, int_types): v = '{"type": "int", "value": %d}' % v else: # isinstance(v, string_types): assert isinstance(v, unicode_type) v = '{"type": "str", "value": %s}' % json.dumps(v) else: if isinstance(v, int_types): v = '%d' % v else: # isinstance(v, string_types): assert isinstance(v, unicode_type) v = json.dumps(v) k = json.dumps(k) sys.stdout.write(" %s: %s" % (k, v)) sys.stdout.write('\n }') sys.stdout.write('\n}\n')
def cmd_remove(store, args): """Remove command. remove [-f] <filehash> remove [-f] <key1=value1> [...] """ if args and args[0] == '-f': del args[0] force = True else: force = False h, metadata = parse_query_metadata(args) if h is not None: store.remove(h) else: entries = store.query(metadata) if not args and not force: nb = sum(1 for e in entries) if nb: sys.stderr.write(_( "Error: not removing files unconditionally unless -f " "is given\n" "(command would have removed {nb} files)\n", nb=nb)) sys.exit(1) for e in entries: store.remove(e)
def _delete(self): items = self._result_tree.selectedItems() if not items: return confirm = QtWidgets.QMessageBox.question( self, _("Are you sure?"), _n( "You are about to delete {num} entry from the store. " "Please confirm.", "You are about to delete {num} entries from the store. " "Please confirm.", len(items), num=len(items)), QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel) if confirm == QtWidgets.QMessageBox.Ok: ids = set([i.entry.objectid for i in items]) i = 0 while i < self._result_tree.topLevelItemCount(): oid = self._result_tree.topLevelItem(i).entry.objectid if oid in ids: self.store.remove(oid) self._result_tree.takeTopLevelItem(i) else: i += 1
def cmd_remove(store, args): """Remove command. remove [-f] <filehash> remove [-f] <key1=value1> [...] """ if args and args[0] == '-f': del args[0] force = True else: force = False h, metadata = parse_query_metadata(args) if h is not None: store.remove(h) else: entries = store.query(metadata) if not args and not force: nb = sum(1 for e in entries) if nb: sys.stderr.write( _( "Error: not removing files unconditionally unless -f " "is given\n" "(command would have removed {nb} files)\n", nb=nb)) sys.exit(1) for e in entries: store.remove(e)
def cmd_query(store, args): """Query command. query [-d] [-t] [key1=value1] [...] """ pydict = False types = False while args and args[0][0] == '-': if args[0] == '-d': pydict = True elif args[0] == '-t': types = True elif args[0] == '--': del args[0] break else: sys.stderr.write(_("Unknown option: {opt}\n", opt=args[0])) sys.exit(1) del args[0] h, metadata = parse_query_metadata(args) if h is not None: entries = [store.get(h)] else: entries = store.query(metadata) if not pydict: for entry in sorted(entries, key=lambda e: e.objectid): sys.stdout.write("%s\n" % entry.objectid) for k, v in sorted(entry.metadata.items(), key=lambda p: p[0]): if types: if isinstance(v, int_types): v = 'int:%d' % v else: # isinstance(v, string_types): v = 'str:%s' % v sys.stdout.write("\t%s\t%s\n" % (k, v)) else: sys.stdout.write('{') for entry_nb, entry in enumerate( sorted(entries, key=lambda e: e.objectid)): sys.stdout.write(',\n' if entry_nb > 0 else '\n') sys.stdout.write(' "%s": {' % entry.objectid) for meta_nb, (k, v) in enumerate( sorted(entry.metadata.items(), key=lambda p: p[0])): sys.stdout.write(',\n' if meta_nb > 0 else '\n') if types: if isinstance(v, int_types): v = '{"type": "int", "value": %d}' % v else: # isinstance(v, string_types): assert isinstance(v, unicode_type) v = '{"type": "str", "value": %s}' % json.dumps(v) else: if isinstance(v, int_types): v = '%d' % v else: # isinstance(v, string_types): assert isinstance(v, unicode_type) v = json.dumps(v) k = json.dumps(k) sys.stdout.write(" %s: %s" % (k, v)) sys.stdout.write('\n }') sys.stdout.write('\n}\n')
def cmd_verify(store, args): """Verify command. This command accepts no argument. """ if args: sys.stderr.write(_("verify command accepts no argument\n")) sys.exit(1) store.verify()
def cmd_add(store, args): """Add command. add <filename> [key1=value1] [...] """ if not args: sys.stderr.write(_("Missing filename\n")) sys.exit(1) filename = args[0] if not os.path.exists(filename): sys.stderr.write(_("Path does not exist: {path}\n", path=filename)) sys.exit(1) metadata = parse_new_metadata(args[1:]) if os.path.isdir(filename): entry = store.add_directory(filename, metadata) else: entry = store.add_file(filename, metadata) sys.stdout.write('%s\n' % entry.objectid)
def _ok_clicked(self): error = None metadata = {} for row in range(self._table.rowCount()): key = self._table.item(row, 0).text() type_ = self._table.cellWidget(row, 1).currentText() value = self._table.item(row, 2).text() if not key: error = _("Empty key") if type_ == 'int': try: value = int(value) except ValueError: error = (_("Invalid int value for %(key)s (row %(row)d)") % { 'key': key, 'row': row + 1 }) elif type_ != 'str': error = _("Invalid type (row %d)") % (row + 1) if key in metadata: error = _("Duplicate key %s" % key) if error is not None: break metadata[key] = {'type': type_, 'value': value} if error is not None: QtWidgets.QMessageBox.critical(self, _("Invalid values"), error) return metadata['hash'] = self._entry.metadata['hash'] self._parent.change_metadata(self._entry.objectid, metadata, self._remove_original.isChecked()) self.setVisible(False)
def _ok_clicked(self): error = None metadata = {} for row in range(self._table.rowCount()): key = self._table.item(row, 0).text() type_ = self._table.cellWidget(row, 1).currentText() value = self._table.item(row, 2).text() if not key: error = _("Empty key") if type_ == 'int': try: value = int(value) except ValueError: error = (_("Invalid int value for %(key)s (row %(row)d)") % {'key': key, 'row': row + 1}) elif type_ != 'str': error = _("Invalid type (row %d)") % (row + 1) if key in metadata: error = _("Duplicate key %s" % key) if error is not None: break metadata[key] = {'type': type_, 'value': value} if error is not None: QtWidgets.QMessageBox.critical(self, _("Invalid values"), error) return metadata['hash'] = self._entry.metadata['hash'] self._parent.change_metadata(self._entry.objectid, metadata, self._remove_original.isChecked()) self.setVisible(False)
def __init__(self, entry, parent): QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.Dialog) self.setWindowModality(QtCore.Qt.ApplicationModal) self._entry = entry self._parent = parent label = QtWidgets.QLabel(_("Editing entry %s") % entry.objectid) self._table = QtWidgets.QTableWidget() self._table.setColumnCount(3) self._table.setHorizontalHeaderLabels(['key', 'type', 'value']) self._table.setSortingEnabled(True) self._table.sortByColumn(0, QtCore.Qt.AscendingOrder) self._table.resizeColumnsToContents() scrollarea = QtWidgets.QScrollArea() scrollarea.setWidgetResizable(True) scrollarea.setWidget(self._table) plus = QtWidgets.QPushButton(_("+")) plus.clicked.connect(self._add_row) minus = QtWidgets.QPushButton(_("-")) minus.clicked.connect(self._remove_row) controls = QtWidgets.QVBoxLayout() controls.addWidget(plus) controls.addWidget(minus) table_row = QtWidgets.QHBoxLayout() table_row.addWidget(scrollarea) table_row.addLayout(controls) self._remove_original = QtWidgets.QCheckBox(_("Remove original entry")) self._remove_original.setChecked(False) self._remove_original.stateChanged.connect(self._mode_changed) self._ok_button = QtWidgets.QPushButton(_("Create new entry")) self._ok_button.clicked.connect(self._ok_clicked) cancel_button = QtWidgets.QPushButton(_("Cancel")) cancel_button.clicked.connect(lambda: self.setVisible(False)) buttons = QtWidgets.QHBoxLayout() buttons.addStretch() buttons.addWidget(self._ok_button) buttons.addWidget(cancel_button) layout = QtWidgets.QVBoxLayout() layout.addWidget(label) layout.addLayout(table_row) layout.addWidget(self._remove_original) layout.addLayout(buttons) self.setLayout(layout) self._table.setSortingEnabled(False) for k, v in self._entry.metadata.items(): if k == 'hash': continue self._add_row(k, v, sorting_disabled=True) self._table.setSortingEnabled(True)
class StoreViewerWindow(QtWidgets.QMainWindow): WINDOW_TITLE = _("file_archive viewer") MAX_RESULTS = 100 def __init__(self, store): QtWidgets.QMainWindow.__init__(self) self.setWindowTitle(self.WINDOW_TITLE) self.store = store searchbar = QtWidgets.QHBoxLayout() self._needs_refresh = False # Input line for the query self._input = QtWidgets.QLineEdit() self._input.setPlaceholderText(_("Enter query here")) self._input.returnPressed.connect(self._search) self._input.textEdited.connect(lambda t: self._set_needs_refresh()) searchbar.addWidget(self._input) # Search button self._searchbutton = QtWidgets.QPushButton(_("Search")) self._searchbutton.clicked.connect(self._search) searchbar.addWidget(self._searchbutton) results = QtWidgets.QHBoxLayout() # Result view, as a tree with metadata self._result_tree = QtWidgets.QTreeWidget() self._result_tree.setColumnCount(3) self._result_tree.setHeaderLabels([_("Key"), _("Value"), _("Type")]) self._result_tree.itemSelectionChanged.connect(self._selection_changed) results.addWidget(self._result_tree) # Buttons, enabled/disabled when the selection changes buttons = QtWidgets.QVBoxLayout() self._buttons = self._create_buttons() for _name, button in self._buttons: buttons.addWidget(button) self._selection_changed() results.addLayout(buttons) layout = QtWidgets.QVBoxLayout() layout.addLayout(searchbar) layout.addLayout(results) widget = QtWidgets.QWidget() widget.setLayout(layout) self.setCentralWidget(widget) self._search() def _create_buttons(self): buttons = [] # Open button; uses the system to choose the program to open with # (on Windows, might ask you what to use every time because of filename # scheme) open_button = QtWidgets.QPushButton(_("Open")) if openfile is not None: open_button.clicked.connect(self._openfile) buttons.append(('single', open_button)) else: open_button.setEnabled(False) # Copy hash button copy_button = QtWidgets.QPushButton(_("Copy ID")) copy_button.clicked.connect(self._copy_objectid) buttons.append(('single', copy_button)) # Edit metadata button edit_button = QtWidgets.QPushButton(_("Edit metadata...")) edit_button.clicked.connect(self._edit_metadata) buttons.append(('single', edit_button)) # Delete button, removes what's selected (with confirmation) remove_button = QtWidgets.QPushButton(_("Delete")) remove_button.clicked.connect(self._delete) buttons.append(('multi', remove_button)) return buttons def _set_needs_refresh(self, needs=True): if needs == self._needs_refresh: return if needs: self._searchbutton.setStyleSheet('font-weight: bold;') else: self._searchbutton.setStyleSheet('') self._needs_refresh = needs def _alter_search_conditions(self, conditions): return conditions def _search(self): error = None query = self._input.text() if len(query.split()) == 1 and all(o not in query.strip() for o in '=<>'): objectid = query.strip() try: entries = [self.store.get(objectid)] except KeyError: error = _("objectid '{oid}' not found", oid=objectid) else: try: conditions = parse_expression(query) except tdparser.Error as e: error = e.args[0] else: conditions = self._alter_search_conditions(conditions) entries = self.store.query(conditions, limit=self.MAX_RESULTS) self._result_tree.clear() if error is not None: w = QtWidgets.QTreeWidgetItem([error]) w.setForeground(0, QtGui.QColor(255, 0, 0)) self._result_tree.addTopLevelItem(w) self._result_tree.setFirstItemColumnSpanned(w, True) else: for i, entry in enumerate(entries): file_item = FileItem(entry) f = file_item.font(0) f.setBold(True) file_item.setFont(0, f) self._result_tree.addTopLevelItem(file_item) self._result_tree.setFirstItemColumnSpanned(file_item, True) for k, v in entry.metadata.items(): file_item.addChild(MetadataItem(entry, k, v)) if i + 1 == self.MAX_RESULTS: last_item = QtWidgets.QTreeWidgetItem([ _("... stripped after {nb} results...", nb=self.MAX_RESULTS) ]) f = last_item.font(0) f.setBold(True) f.setItalic(True) last_item.setFont(0, f) self._result_tree.addTopLevelItem(last_item) self._result_tree.setFirstItemColumnSpanned( last_item, True) break if self._result_tree.topLevelItemCount() == 0: w = QtWidgets.QTreeWidgetItem([_("No matches")]) self._result_tree.addTopLevelItem(w) self._result_tree.setFirstItemColumnSpanned(w, True) self._result_tree.expandAll() self._set_needs_refresh(False) def _selection_changed(self): items = self._result_tree.selectedItems() for t, button in self._buttons: if t == 'single': button.setEnabled( len(items) == 1 and isinstance(items[0], FileItem)) elif t == 'multi': button.setEnabled( bool(items) and all(isinstance(i, FileItem) for i in items)) def _openfile(self): item = self._result_tree.currentItem() if item is not None: openfile(item.entry.filename) def _copy_objectid(self): items = self._result_tree.selectedItems() if not items: return objectid = items[0].entry.objectid clipboard = QtWidgets.QApplication.clipboard() clipboard.setText(objectid) def _edit_metadata(self): items = self._result_tree.selectedItems() if not items: return entry = items[0].entry editor = MetadataEditor(entry, self) editor.show() def change_metadata(self, old_objectid, metadata, remove_original=False): new_objectid = hash_metadata(metadata) if new_objectid == old_objectid: return self.store.metadata.add(new_objectid, metadata) if remove_original: self.store.remove(old_objectid) self._search() def _delete(self): items = self._result_tree.selectedItems() if not items: return confirm = QtWidgets.QMessageBox.question( self, _("Are you sure?"), _n( "You are about to delete {num} entry from the store. " "Please confirm.", "You are about to delete {num} entries from the store. " "Please confirm.", len(items), num=len(items)), QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel) if confirm == QtWidgets.QMessageBox.Ok: ids = set([i.entry.objectid for i in items]) i = 0 while i < self._result_tree.topLevelItemCount(): oid = self._result_tree.topLevelItem(i).entry.objectid if oid in ids: self.store.remove(oid) self._result_tree.takeTopLevelItem(i) else: i += 1
def _mode_changed(self, remove_original): if remove_original: self._ok_button.setText(_("Replace entry")) else: self._ok_button.setText(_("Create new entry"))
def cmd_view(store, args): if args: sys.stderr.write(_("view command accepts no argument\n")) sys.exit(1) from file_archive.viewer import run_viewer run_viewer(store)
def parse_query_metadata(args): """Parses a list of key=value arguments or a hash value. Returns (hash:str, metadata:dict) """ if len(args) == 1 and '=' not in args[0]: return args[0], None else: metadata = {} for a in args: k = a.split('=', 1) if len(k) != 2: sys.stderr.write( _("Metadata should have format key=value, " "key=type:value (eg. age=int:23) or " "key=type:req (eg. age=int:>21)\n")) sys.exit(1) k, v = k if ':' in v: t, v = v.split(':', 1) if t == 'int': if v[0] == '>': req, v = 'gt', v[1:] elif v[0] == '<': req, v = 'lt', v[1:] else: req = 'equal' v = int(v) elif t == 'str': req = 'equal' else: sys.stderr.write( _( "Metadata has unknown type '{t}'! " "Only 'str' and 'int' are supported.\n" "If you meant a string with a ':', " "use 'str:mystring'", t=t)) sys.exit(1) else: t = 'str' req = 'equal' if t == 'str' and isinstance(v, bytes): v = v.decode(locale.getpreferredencoding()) if k in metadata: if t != metadata[k]['type']: sys.stderr.write( _( "Differing types for conditions on " "key {k}: {t1}, {t2}\n", k=k, t1=metadata[k]['type'], t2=t)) sys.exit(1) if req in metadata[k]: sys.stderr.write( _("Multiple conditions {cond} on key " "{k}\n", cond=req, k=k)) sys.exit(1) metadata[k][req] = v else: metadata[k] = {'type': t, req: v} return None, metadata