def get_items( item_type: enums.ItemType = enums.ItemType.Gallery, limit: int = 100, offset: int = None, ): """ Get a list of items Args: item_type: type of item to get limit: limit the amount of items returned offset: offset the results by n items Returns: .. code-block:: guess [ item message object, ... ] """ item_type = enums.ItemType.get(item_type) db_msg, db_model = item_type._msg_and_model() items = database_cmd.GetModelItems().run(db_model, limit=limit, offset=offset) item_list = message.List(db.model_name(db_model), db_msg) [item_list.append(db_msg(i)) for i in items] return item_list
def get_view_count(item_type: enums.ItemType=enums.ItemType.Gallery, item_id: int = None, related_type: enums.ItemType = None, search_query: str = "", search_options: dict = {}, filter_id: int = None, view_filter: enums.ViewType = enums.ViewType.Library): """ Get count of items in view Args: item_type: possible items are :py:attr:`.ItemType.Gallery`, :py:attr:`.ItemType.Collection`, :py:attr:`.ItemType.Grouping` search_query: filter item by search terms search_options: options to apply when filtering, see :ref:`Settings` for available search options filter_id: current :py:attr:`.ItemType.GalleryFilter` item id view_filter: type of view, set ``None`` to not apply any filter related_type: child item item_id: id of parent item Returns: .. code-block:: guess { 'count' : int } """ view_filter, item_type, db_msg, db_model, model_ids, filter_op, join_exp, metatag_name = _view_helper( item_type, search_query, filter_id, view_filter, item_id, related_type, search_options) return message.Identity('count', {'count': database_cmd.GetModelItems().run( db_model, model_ids, filter=filter_op, join=join_exp, count=True)})
def get_item(item_type: enums.ItemType = enums.ItemType.Gallery, item_id: int = 0): """ Get item Args: item_type: type of item to get item_id: id of item Returns: item message object """ if not item_id: raise exceptions.APIError( utils.this_function(), f"A valid item id is required, not {item_id}") item_type = enums.ItemType.get(item_type) db_msg, db_model = item_type._msg_and_model() item = database_cmd.GetModelItems().run(db_model, {item_id})[0] if not item: raise exceptions.DatabaseItemNotFoundError( utils.this_function(), "'{}' with id '{}' was not found".format(item_type.name, item_id)) return db_msg(item)
def main(self, gallery_or_id: db.Gallery=None, number: int=None, args=tuple()) -> bool: assert isinstance(gallery_or_id, (db.Gallery, int)) if isinstance(gallery_or_id, int): gallery = database_cmd.GetModelItems().run(db.Gallery, {gallery_or_id}) if gallery: gallery = gallery[0] else: gallery = gallery_or_id self.gallery = gallery if number is None: number = 1 opened = False if self.gallery.pages.count(): with self._resolve.call(self.gallery, number) as plg: r = plg.first_or_default() if len(r) == 2: self.path, self.first_file = r args = args if args else tuple(x.strip() for x in config.external_image_viewer_args.value.split()) with self._open.call(self.path, self.first_file, self.gallery, args) as plg: try: opened = plg.first_or_default() except OSError as e: raise exceptions.CommandError(utils.this_command(self), "Failed to open gallery with external viewer: {}".format(e.args[1])) else: log.w("Error opening gallery (), no page count".format(self.gallery.id)) if opened: self._opened.emit(self.path, self.first_file, self.gallery, number) return opened
def _calculate(self, gallery_or_id, all_gallery_tags={}): assert isinstance(gallery_or_id, (str, int, db.Gallery)) sess = constants.db_session() with db.no_autoflush(sess): data = {} g_id = gallery_or_id.id if isinstance( gallery_or_id, db.Gallery) else gallery_or_id g_id = str(g_id) # because JSON keys are str tag_count = 0 tag_count_minimum = 3 if g_id in all_gallery_tags: g_tags = all_gallery_tags[g_id] for a, b in g_tags.items(): tag_count += len(b) self.set_max_progress(len(g_tags) + 3) else: if isinstance(gallery_or_id, db.Gallery): g_tags = gallery_or_id else: g_tags = database_cmd.GetModelItems().run( db.Taggable, join=db.Gallery.taggable, filter=db.Gallery.id == int(g_id)) if g_tags: g_tags = g_tags[0] tag_count = g_tags.tags.count() if tag_count > tag_count_minimum: g_tags = g_tags.compact_tags(g_tags.tags.all()) self.next_progress() if g_tags and tag_count > tag_count_minimum: log.d("Calculating similarity") g_tags = self._get_set(g_tags) data[g_id] = gl_data = {} update_dict = not all_gallery_tags max_prog = 3 for t_id, t in all_gallery_tags.items( ) or constants.db_session().query( db.Gallery.id, db.Taggable).join(db.Gallery.taggable): t_id = str(t_id) self.next_progress() if update_dict: all_gallery_tags[t_id] = t.compact_tags(t.tags.all()) max_prog += 1 self.set_max_progress(max_prog) if t_id == g_id: continue t_tags = self._get_set(all_gallery_tags[t_id]) if (math.sqrt(len(g_tags)) * math.sqrt(len(t_tags))) != 0: cos = len(g_tags & t_tags) / (math.sqrt( len(g_tags))) * math.sqrt(len(t_tags)) else: cos = 0 if cos: gl_data[t_id] = cos log.d("Finished calculating similarity") self.next_progress() return data
def search_tags( search_query: str = "", search_options: dict = {}, only_namespace: bool = False, only_tag: bool = False, sort_by: enums.ItemSort = None, sort_desc: bool = False, limit: int = 100, offset: int = None, ): """ Search for tags Args: search_query: search string search_options: options to apply when filtering, see :ref:`Settings` for available search options only_namespace: only search for matching namespace <not implemented yet> only_tag: only search for matching tag <not implemented yet> sort_by: either a :py:class:`.ItemSort` or a sort index sort_desc: order descending (default is ascending) limit: limit the amount of items returned offset: offset the results by n items Returns: .. code-block:: guess { namespace : [ tag message object, ...], ... } """ if search_options: search_option_names = [ x.name for x in search_cmd._get_search_options() ] for n in search_options: if n not in search_option_names: raise exceptions.APIError( utils.this_function(), "Invalid search option name '{}'".format(n)) db_model = db.NamespaceTags model_ids = search_cmd.ModelFilter().run(db_model, search_query, search_options) order_exp, group_exp, join_exp = helpers._sort_helper( sort_by, sort_desc, db_model) items = database_cmd.GetModelItems().run(db_model, model_ids, limit=limit, offset=offset, join=join_exp, order_by=order_exp, group_by=group_exp) msg = _contruct_tags_msg(items) return message.Identity('tags', msg)
def get_page(page_id: int = None, gallery_id: int = None, number: int = None, prev: bool = False): """ Get next/prev page by either gallery or page id Args: page_id: id of page gallery_id: id of gallery number: retrieve specific page number prev: by default next page is retrieved, to retrieve prev page set this to true Returns: Page object """ if not (gallery_id or page_id): raise exceptions.APIError( utils.this_function(), "Either a gallery id or page id is required") if number is None: number = 0 item = None if page_id: p = database_cmd.GetModelItems().run(db.Page, {page_id})[0] if number and p and number == p.number: item = p elif p: number = number or p.number gallery_id = p.gallery_id if not item: f = db.Page.number < number if prev else db.Page.number == number f = db.and_op(f, db.Page.gallery_id == gallery_id) item = database_cmd.GetModelItems().run( db.Page, order_by=db.Page.number.desc() if prev else db.Page.number, filter=f, limit=1) if item: item = item[0] return message.Page(item) if item else None
def library_view(item_type: enums.ItemType = enums.ItemType.Gallery, item_id: int = None, related_type: enums.ItemType = None, page: int = 0, limit: int = 100, sort_by: enums.ItemSort = None, sort_desc: bool=False, search_query: str = "", search_options: dict = {}, filter_id: int = None, view_filter: enums.ViewType = enums.ViewType.Library): """ Fetch items from the database. Provides pagination. Args: item_type: possible items are :py:attr:`.ItemType.Gallery`, :py:attr:`.ItemType.Collection`, :py:attr:`.ItemType.Grouping` page: current page (zero-indexed) sort_by: either a :py:class:`.ItemSort` or a sort index sort_desc: order descending (default is ascending) limit: amount of items per page search_query: filter item by search terms search_options: options to apply when filtering, see :ref:`Settings` for available search options filter_id: current :py:attr:`.ItemType.GalleryFilter` item id view_filter: type of view, set ``None`` to not apply any filter related_type: child item item_id: id of parent item Returns: .. code-block:: guess [ item message object, ... ] .. seealso:: :func:`.get_sort_indexes` """ view_filter, item_type, db_msg, db_model, model_ids, filter_op, join_exp, metatag_name = _view_helper( item_type, search_query, filter_id, view_filter, item_id, related_type, search_options) items = message.List(db_model.__name__.lower(), db_msg) order_exp, group_exp, sort_joins = helpers._sort_helper(sort_by, sort_desc, db_model) if sort_joins: join_exp.extend(sort_joins) # need unique but ordered results, cannot use set so we make use with this join_exp = tuple(OrderedDict([(x, None) for x in join_exp]).keys()) [items.append(db_msg(x)) for x in database_cmd.GetModelItems().run( db_model, model_ids, limit=limit, offset=page * limit, filter=filter_op, join=join_exp, order_by=order_exp, group_by=group_exp)] return items
def open_gallery(item_id: int = 0, item_type: enums.ItemType = enums.ItemType.Gallery, viewer_args: str = None): """ Open a gallery or page in an external viewer Args: item_id: id of item item_type: possible items are :py:attr:`.ItemType.Gallery`, :py:attr:`.ItemType.Page` viewer_args: commandline arguments to supply the viewer, overriding the default viewer arguments specified in settings Returns: bool indicating if item was successfully opened """ item_type = enums.ItemType.get(item_type) _, db_model = item_type._msg_and_model( (enums.ItemType.Gallery, enums.ItemType.Page)) kwargs = {} if item_type == enums.ItemType.Page: p = database_cmd.GetModelItems().run(db_model, {item_id}, columns=(db_model.gallery_id, db_model.number)) else: p = database_cmd.GetModelItems().run(db_model, {item_id}) if not p: raise exceptions.DatabaseItemNotFoundError( utils.this_function(), "{} with item id '{}' not found".format(item_type, item_id)) p = p[0] if item_type == enums.ItemType.Page: kwargs['gallery_or_id'] = p[0] kwargs['number'] = p[1] else: kwargs["gallery_or_id"] = p if viewer_args: kwargs['args'] = tuple(x.strip() for x in viewer_args.split()) opened = gallery_cmd.OpenGallery().run(**kwargs) return message.Identity("status", opened)
def get_tags(item_type: enums.ItemType = enums.ItemType.Gallery, item_id: int = 0, raw: bool = False): """ Get tags for item Args: item_type: possible items are :attr:`.ItemType.Gallery`, :attr:`.ItemType.Page`, :attr:`.ItemType.Grouping`, :attr:`.ItemType.Collection` item_id: id of item to fetch tags for raw: if true, tags from descendant ItemType's will not be included (this only makes sense when ItemType is :attr:`.ItemType.Gallery`) Returns: .. code-block:: guess { namespace : [ tag message object, ...], ... } """ if not item_id: raise exceptions.APIError(utils.this_function(), "item_id must be a valid item id") item_type = enums.ItemType.get(item_type) _, db_item = item_type._msg_and_model( (enums.ItemType.Gallery, enums.ItemType.Collection, enums.ItemType.Grouping, enums.ItemType.Page)) db_obj = database_cmd.GetModelItems().run(db_item, {item_id}) if db_obj: db_obj = db_obj[0] nstags = [] if db_obj: g_objs = [] if issubclass(db_item, db.TaggableMixin): nstags = db_obj.tags.all() if not raw and isinstance(db_obj, db.Gallery): g_objs.append(db_obj) else: for g in db_obj.galleries.all(): nstags.extend(g.tags.all()) if not raw: g_objs.append(g) for g_obj in g_objs: for p in g_obj.pages.all(): # TODO: we only need tags nstags.extend(p.tags.all()) msg = _contruct_tags_msg(nstags) return message.Identity('tags', msg)
def add_to_filter(gallery_id: int = 0, item_id: int = 0, item: dict = {}): """ Add a gallery to a galleryfilter Args: gallery_id: id of gallery item_id: id of existing galleryfilter, mutually exclusive with ``item`` parameter item: filter message object, mutually exclusive with ``item_id`` parameter Returns: bool whether gallery was added to filter or not """ if not gallery_id: raise exceptions.APIError(utils.this_function(), "gallery_id must be a valid gallery id") g = database_cmd.GetModelItems().run(db.Gallery, {gallery_id}) if not g: raise exceptions.DatabaseItemNotFoundError( utils.this_function(), "'{}' with id '{}' was not found".format( enums.ItemType.Gallery.name, gallery_id)) g = g[0] if item_id: p = database_cmd.GetModelItems().run(db.GalleryFilter, {item_id}) if not p: raise exceptions.DatabaseItemNotFoundError( utils.this_function(), "'{}' with id '{}' was not found".format( enums.ItemType.GalleryFilter.name, item_id)) p = p[0] elif item: p = message.GalleryFilter.from_json(item) g.filters.append(p) s = constants.db_session() s.add(g) s.commit() return message.Identity("status", True)
def get_related_items( item_type: enums.ItemType = enums.ItemType.Gallery, item_id: int = 0, related_type: enums.ItemType = enums.ItemType.Page, limit: int = 100, offset: int = None, ): """ Get item related to given item Args: item_type: parent item item_id: id of parent item related_type: child item limit: limit the amount of items returned offset: offset the results by n items Returns: .. code-block:: guess [ related item message object, ... ] """ if not item_id: raise exceptions.APIError(utils.this_function(), "item_id must be a valid item id") item_type = enums.ItemType.get(item_type) related_type = enums.ItemType.get(related_type) _, parent_model = item_type._msg_and_model() child_msg, child_model = related_type._msg_and_model() col = db.relationship_column(parent_model, child_model) if not col: raise exceptions.APIError( utils.this_function(), "{} has no relationship with {}".format(related_type, item_type)) s = constants.db_session() q = s.query(child_model.id).join(col).filter(parent_model.id == item_id) if offset: q = q.offset(offset) item_ids = q.limit(limit).all() items = database_cmd.GetModelItems().run(child_model, {x[0] for x in item_ids}) item_list = message.List(db.model_name(child_model), child_msg) [item_list.append(child_msg(x)) for x in items] return item_list
def _get_similar(kwargs, similar_items): item_list = message.List(db.model_name(kwargs['db_model']), kwargs['db_msg']) items = [] if similar_items: similar_items = similar_items[:kwargs['limit']] db_items = {} # needed to sort them the way they came in for g in database_cmd.GetModelItems().run(kwargs['db_model'], set(similar_items)): db_items[g.id] = g [items.append(db_items[x]) for x in similar_items if x in db_items] [item_list.append(kwargs['db_msg'](x)) for x in items] return item_list
def remove_from_filter(gallery_id: int = 0, item_id: int = 0): """ Remove a gallery from a galleryfilter Args: gallery_id: id of gallery item_id: id of existing galleryfilter Returns: bool whether gallery was removed from filter or not """ if not gallery_id: raise exceptions.APIError(utils.this_function(), "gallery_id must be a valid gallery id") g = database_cmd.GetModelItems().run(db.Gallery, {gallery_id}) if not g: raise exceptions.DatabaseItemNotFoundError( utils.this_function(), "'{}' with id '{}' was not found".format( enums.ItemType.Gallery.name, gallery_id)) g = g[0] p = database_cmd.GetModelItems().run(db.GalleryFilter, {item_id}) if not p: raise exceptions.DatabaseItemNotFoundError( utils.this_function(), "'{}' with id '{}' was not found".format( enums.ItemType.GalleryFilter.name, item_id)) p = p[0] g.filters.remove(p) s = constants.db_session() s.add(g) s.commit() return message.Identity("status", True)
def update_metatags(item_type: enums.ItemType = enums.ItemType.Gallery, item_id: int = 0, metatags: dict = {}): """ Update metatags for an item Args: item_type: possible items are :py:attr:`.ItemType.Gallery`, :py:attr:`.ItemType.Page`, :py:attr:`.ItemType.Artist`, :py:attr:`.ItemType.Collection` item_id: id of item metatag: a dict of ``{ metatag_name : bool }`` Returns: bool indicating whether metatags were updated """ if not item_id: raise exceptions.APIError(utils.this_function(), "item_id must be a valid item id") item_type = enums.ItemType.get(item_type) _, db_item = item_type._msg_and_model( (enums.ItemType.Gallery, enums.ItemType.Collection, enums.ItemType.Page, enums.ItemType.Artist)) t = database_cmd.GetModelItems().run(db_item, {item_id}) if not t: raise exceptions.DatabaseItemNotFoundError( utils.this_function(), "{} with item id '{}' not found".format(item_type, item_id)) t = t[0] mtags = {} anames = db.MetaTag.all_names() for m, v in metatags.items(): if m not in anames: raise exceptions.APIError(utils.this_function(), f"Metatag name '{m}' does not exist") mtags[m] = v st = True if t: t.update("metatags", mtags) db.object_session(t).commit() else: st = False return message.Identity('status', st)
def get_all_tags(limit: int=100, offset: int=None): """ Get all tags from the db Args: limit: limit the amount of items returned offset: offset the results by n items Returns: .. code-block:: guess { namespace : [ tag message object, ...], ... } """ db_obj = database_cmd.GetModelItems().run(db.NamespaceTags, limit=limit, offset=offset) msg = _contruct_tags_msg(db_obj) return message.Identity('tags', msg)
def get_items(item_type: enums.ItemType = enums.ItemType.Gallery, limit: int = 100): """ Get a list of items Args: item_type: type of item to get limit: limit the amount of items returned Returns: list of item message objects """ item_type = enums.ItemType.get(item_type) db_msg, db_model = item_type._msg_and_model() items = database_cmd.GetModelItems().run(db_model, limit=limit) item_list = message.List(db.model_name(db_model), db_msg) [item_list.append(db_msg(i)) for i in items] return item_list
def update_item_tags(item_type: enums.ItemType = enums.ItemType.Gallery, item_id: int=0, tags: dict={}): """ Update tags on an item Args: item_type: possible items are :attr:`.ItemType.Gallery`, :attr:`.ItemType.Page`, :attr:`.ItemType.Grouping`, :attr:`.ItemType.Collection` item_id: id of item to update tags for tags: tags Returns: bool whether tags were updated or not """ if not item_id: raise exceptions.APIError(utils.this_function(), "item_id must be a valid item id") item_type = enums.ItemType.get(item_type) _, db_item = item_type._msg_and_model( (enums.ItemType.Gallery, enums.ItemType.Collection, enums.ItemType.Grouping, enums.ItemType.Page)) db_obj = database_cmd.GetModelItems().run(db_item, {item_id}) if not db_obj: raise exceptions.DatabaseItemNotFoundError(utils.this_function(), "'{}' with id '{}' was not found".format(item_type.name, item_id)) db_obj = db_obj[0] tags = _decontruct_tags_msg(tags) s = constants.db_session() with db.no_autoflush(s): s.add(db_obj) db_obj.tags = tags s.commit() return message.Identity("status", True)
def search_item( item_type: enums.ItemType = enums.ItemType.Gallery, search_query: str = "", search_options: dict = {}, sort_by: enums.ItemSort = None, sort_desc: bool = False, full_search: bool = True, limit: int = 100, offset: int = None, ): """ Search for item Args: item_type: all of :py:attr:`.ItemType` except :py:attr:`.ItemType.Page` and :py:attr:`.ItemType.GalleryFilter` search_query: filter item by search terms search_options: options to apply when filtering, see :ref:`Settings` for available search options sort_by: either a :py:class:`.ItemSort` or a sort index sort_desc: order descending (default is ascending) limit: amount of items offset: offset the results by n items Returns: .. code-block:: guess [ item message object, ... ] .. seealso:: :func:`.get_sort_indexes` """ item_type = enums.ItemType.get(item_type) if search_options: search_option_names = [ x.name for x in search_cmd._get_search_options() ] for n in search_options: if n not in search_option_names: raise exceptions.APIError( utils.this_function(), "Invalid search option name '{}'".format(n)) if item_type in (enums.ItemType.Page, enums.ItemType.GalleryFilter): raise exceptions.APIError(utils.this_function(), "Unsupported itemtype {}".format(item_type)) db_msg, db_model = item_type._msg_and_model() model_ids = set() if full_search: model_ids = search_cmd.ModelFilter().run(db_model, search_query, search_options) items = message.List("items", db_msg) order_exp, group_exp, join_exp = helpers._sort_helper( sort_by, sort_desc, db_model) [ items.append(db_msg(x)) for x in database_cmd.GetModelItems().run(db_model, model_ids, limit=limit, offset=offset, join=join_exp, order_by=order_exp, group_by=group_exp) ] return items
def source_exists(item_type: enums.ItemType = enums.ItemType.Gallery, item_id: int = 0, check_all: bool = False): """ Check if gallery/page source exists on disk Args: item_type: possible items are :py:attr:`.ItemType.Gallery`, :py:attr:`.ItemType.Page` item_id: id of item check_all: goes through all pages and checks them, default behaviour is to only check parent files/folders. Only relevant for :py:attr:`.ItemType.Gallery` Returns: .. code-block:: guess { 'exists' : bool 'missing' : [ {'id': int, 'item_type': item_type}, ... ] } """ item_type = enums.ItemType.get(item_type) _, db_model = item_type._msg_and_model( (enums.ItemType.Gallery, enums.ItemType.Page)) if item_type == enums.ItemType.Page: item = database_cmd.GetModelItems().run(db_model, {item_id}, columns=(db.Page.path, )) elif item_type == enums.ItemType.Gallery: item = database_cmd.GetModelItems().run( db_model, {item_id}, columns=(db.Gallery.single_source, )) if not item: raise exceptions.DatabaseItemNotFoundError( utils.this_function(), "'{}' with id '{}' was not found".format(item_type.name, item_id)) else: item = item[0] paths = {} not_empty = True if item_type == enums.ItemType.Page: paths[item_id] = (item[0], item_type.value) elif item_type == enums.ItemType.Gallery: s = constants.db_session() if item and not check_all: p = s.query(db.Page.path).filter(db.Gallery.id == item_id).first() if p: paths[item_id] = (os.path.split(p[0])[0], item_type.value) else: not_empty = True else: ps = s.query( db.Page.id, db.Page.path).filter(db.Page.gallery_id == item_id).all() for p in ps: paths[p[0]] = (p[1], enums.ItemType.Page.value) not_empty = bool(ps) missing = [] for t_id in paths: src, t_type = paths[t_id] try: e = io_cmd.CoreFS(src).exists except exceptions.ArchiveExistError: e = False if not e: missing.append({'id': t_id, 'item_type': t_type}) return message.Identity("exists", { 'exists': not missing and not_empty, 'missing': missing })