def get_related_count(item_type: enums.ItemType = enums.ItemType.Gallery, item_id: int = 0, related_type: enums.ItemType = enums.ItemType.Page): """ Get count of items related to given item Args: item_type: parent item item_id: id of parent item related_type: child item Returns: ``` { 'id': int, 'count': int } ``` """ 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() count = s.query( child_model.id).join(col).filter(parent_model.id == item_id).count() return message.Identity('count', {'id': item_id, 'count': count})
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 update_item(item_type: enums.ItemType = enums.ItemType.Gallery, item: dict = {}, options: dict = {}): """ Update an existing item Args: item_type: type of item to create item: item messeage object Returns: bool indicating whether item was updated """ if not item: raise exceptions.APIError(utils.this_function(), "Item must be a message object") if not item.get('id'): raise exceptions.APIError(utils.this_function(), "Item must have a valid id") item_type = enums.ItemType.get(item_type) db_msg, db_model = item_type._msg_and_model() db_obj = db_msg.from_json(item, ignore_empty=False, skip_updating_existing=False) status = database_cmd.UpdateItem().main(db_obj, options=options) return message.Identity('status', status)
def new_item(item_type: enums.ItemType = enums.ItemType.Gallery, item: dict = {}, options: dict = {}): """ Create a new item and add it to the database Args: item_type: type of item to create item: item messeage object Returns: [] |async command| """ if not item: raise exceptions.APIError(utils.this_function(), "item must be a message object") if item.get('id', False) and not constants.dev: raise exceptions.APIError(utils.this_function(), "cannot create item with an id") item_type = enums.ItemType.get(item_type) db_msg, db_model = item_type._msg_and_model() db_obj = db_msg.from_json(item) cmd_id = database_cmd.AddItem(services.AsyncService.generic).run( db_obj, options=options) return message.Identity('command_id', cmd_id)
def get_common_tags(item_type: enums.ItemType = enums.ItemType.Collection, item_id: int = 0, limit: int = 10): """ Get the most common tags for item Args: item_type: possible items are :attr:`.ItemType.Artist`, :attr:`.ItemType.Grouping`, :attr:`.ItemType.Collection` item_id: id of item to fetch tags for limit: limit amount of tags returned Returns: .. code-block:: guess { namespace : [ tag message object, ...], ... } """ item_type = enums.ItemType.get(item_type) _, db_item = item_type._msg_and_model( (enums.ItemType.Artist, enums.ItemType.Collection, enums.ItemType.Grouping)) nstags = database_cmd.MostCommonTags().run(db_item, item_id, limit) msg = _contruct_tags_msg(nstags) return message.Identity('tags', msg)
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: ``` { namespace : list of tag message objects } ``` """ 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.GetModelItemByID().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 = {} _msg = {} for nstag in nstags: ns = nstag.namespace.name if ns not in msg: msg[ns] = [] _msg[ns] = [] if nstag.tag.name not in _msg[ns]: msg[ns].append( message.Tag(nstag.tag, nstag).json_friendly(include_key=False)) _msg[ns].append(nstag.tag.name) return message.Identity('tags', msg)
def get_view_count(item_type: enums.ItemType = enums.ItemType.Gallery, search_query: str = "", 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 filter_id: current filter list id view_filter: ... Returns: ``` { 'count': int } ``` """ view_filter = enums.ViewType.get(view_filter) item_type = enums.ItemType.get(item_type) db_msg, db_model = item_type._msg_and_model( (enums.ItemType.Gallery, enums.ItemType.Collection, enums.ItemType.Grouping)) model_ids = search_cmd.ModelFilter().run(db_model, search_query) return message.Identity('count', {'count': len(model_ids)})
def get_similar(item_type: enums.ItemType = enums.ItemType.Gallery, item_id: int = 0, limit=10): """ Get similar items Args: item_type: possible items are :py:attr:`.ItemType.Gallery` item_id: id of item limit: amount of items Returns: .. code-block:: guess [ item message object, ... ] |async command| """ item_type = enums.ItemType.get(item_type) db_msg, db_model = item_type._msg_and_model((enums.ItemType.Gallery, )) c = gallery_cmd.SimilarGallery() services.AsyncService.generic.add_command( c, functools.partial(_get_similar, { 'limit': limit, 'db_model': db_model, 'db_msg': db_msg })) return message.Identity('command', c.start(item_id))
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_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 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 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_image(item_type: enums.ItemType = enums.ItemType.Gallery, item_ids: list = [], size: enums.ImageSize = enums.ImageSize.Medium, url: bool = False, uri: bool = False): """ Get image for item. Image content is base64 encoded. Args: item_type: possible items are :py:attr:`.ItemType.Gallery`, :py:attr:`.ItemType.Artist`, :py:attr:`.ItemType.Collection`, :py:attr:`.ItemType.Grouping`, :py:attr:`.ItemType.Page` item_ids: list of item ids size: size of image url: replace image content with http url to image file uri: turn raw base64 string into an URI Returns: .. code-block:: guess { item_id : command_id } .. seealso:: :func:`.get_image_from_path` """ item_type = enums.ItemType.get(item_type) size = enums.ImageSize.get(size) _, db_item = item_type._msg_and_model( (enums.ItemType.Gallery, enums.ItemType.Collection, enums.ItemType.Grouping, enums.ItemType.Page, enums.ItemType.Artist)) content = {} command_dec = functools.partial(_get_image, {'url': url, 'uri': uri}) for i in item_ids: c = database_cmd.GetModelImage() services.ImageService.generic.add_command(c, command_dec) content[i] = c.start(db_item, i, size) return message.Identity('image', content)
def library_view(item_type: enums.ItemType = enums.ItemType.Gallery, page: int = 0, limit: int = 100, sort_by: str = "", search_query: str = "", filter_id: int = None, view_filter: enums.ViewType = enums.ViewType.Library, ctx=None): """ 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: name of column to order by ... limit: amount of items per page search_query: filter item by search terms filter_id: current filter list id view_filter: ... Returns: list of item message objects """ utils.require_context(ctx) view_filter = enums.ViewType.get(view_filter) item_type = enums.ItemType.get(item_type) db_msg, db_model = item_type._msg_and_model( (enums.ItemType.Gallery, enums.ItemType.Collection, enums.ItemType.Grouping)) items = message.List(db_model.__name__.lower(), db_msg) model_ids = search_cmd.ModelFilter().run(db_model, search_query) [ items.append(db_msg(x)) for x in database_cmd.GetModelItemByID().run( db_model, model_ids, limit=limit, offset=page * limit) ] 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_count(item_type: enums.ItemType = enums.ItemType.Gallery): """ Get count of items in the database Args: item_type: type of item Returns: ``` { 'count': int } ``` """ item_type = enums.ItemType.get(item_type) _, db_model = item_type._msg_and_model() s = constants.db_session() return message.Identity('count', {'count': s.query(db_model).count()})
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 _view_helper(item_type: enums.ItemType=enums.ItemType.Gallery, search_query: str = "", filter_id: int = None, view_filter: enums.ViewType = enums.ViewType.Library, item_id: int = None, related_type: enums.ItemType = None, search_options: dict = {}, ): if view_filter is not None: view_filter = enums.ViewType.get(view_filter) if related_type is not None: related_type = enums.ItemType.get(related_type) 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)) filter_op = [] join_exp = [] parent_model = None db_msg, db_model = item_type._msg_and_model( (enums.ItemType.Gallery, enums.ItemType.Collection, enums.ItemType.Grouping)) if related_type: parent_model = db_model db_msg, db_model = related_type._msg_and_model( (enums.ItemType.Gallery, enums.ItemType.Page)) col = db.relationship_column(parent_model, db_model) if not col: raise exceptions.APIError( utils.this_function(), "{} has no relationship with {}".format( related_type, item_type)) if item_id is None: raise exceptions.APIError(utils.this_function(), "Missing id of parent item") if filter_id: if db_model != db.Gallery: g_col = db.relationship_column(db_model, db.Gallery) if not g_col: raise exceptions.APIError( utils.this_function(), "Cannot use {} because {} has no relationship with {}".format( enums.ItemType.GalleryFilter, related_type if related_type else item_type, enums.ItemType.Gallery)) join_exp.append(g_col) join_exp.append(db.relationship_column(db.Gallery, db.GalleryFilter)) filter_op.append(db.GalleryFilter.id == filter_id) model_ids = None if not db_model == db.Page: model_ids = search_cmd.ModelFilter().run(db_model, search_query, search_options) metatag_name = None if view_filter == enums.ViewType.Favorite: metatag_name = db.MetaTag.names.favorite elif view_filter == enums.ViewType.Inbox: metatag_name = db.MetaTag.names.inbox elif view_filter == enums.ViewType.Trash: metatag_name = db.MetaTag.names.trash if metatag_name: if hasattr(db_model, "metatags"): filter_op.append(db.MetaTag.name == metatag_name) join_exp.append(db_model.metatags) elif view_filter == enums.ViewType.Library: if hasattr(db_model, "metatags"): filter_op.append(~db_model.metatags.any(db.MetaTag.name == db.MetaTag.names.inbox)) filter_op.append(~db_model.metatags.any(db.MetaTag.name == db.MetaTag.names.trash)) elif view_filter == enums.ViewType.All: if hasattr(db_model, "metatags"): filter_op.append(~db_model.metatags.any(db.MetaTag.name == db.MetaTag.names.trash)) if related_type: filter_op.append(parent_model.id == item_id) join_exp.append(col) if len(filter_op) > 1: filter_op = db.and_op(*filter_op) elif filter_op: filter_op = filter_op[0] else: filter_op = None return view_filter, item_type, db_msg, db_model, model_ids, filter_op, join_exp, metatag_name
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 })
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