def check_update(push: bool = False): """ Check for new release Args: push: whether to push out notifications if an update is found Returns: .. code-block:: guess { 'url' : str, 'tag' : str 'changes' : str, } or ``null`` |async command| .. seealso:: :func:`.get_changelog` -- when a new update is found, its changelog is immediately available here """ upd = meta_cmd.CheckUpdate(AsyncService.generic).run(force=True, push=push) return message.Identity('update', upd)
def get_changelog(): """ Get the changelog in markdown formatted text The changelog returned is for the current release or a new update Returns: .. code-block:: guess { 'version': str, 'changes': str } .. seealso:: :func:`.check_update` """ ch = {'version': '', 'changes': ''} lr = constants.internaldb.latest_release.get(None) if lr: ch['changes'] = lr.get("changes", "") ch['version'] = lr.get("tag", "") return message.Identity("changelog", ch)
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 scan_galleries(path: str, scan_options: dict = {}): """ Scan for galleries in the given directory/archive Args: path: path to directory/archive that exists on this system scan_options: options to apply to the scanning process, see :ref:`Settings` for available scanning options Returns: .. code-block:: guess { 'command_id': int, 'view_id': int } |async command| |temp view| """ path = io_cmd.CoreFS(path) if not path.exists: raise exceptions.CoreError( utils.this_function(), f"Path does not exists on this system: '{path.path}'") view_id = next(constants.general_counter) cmd_id = gallery_cmd.ScanGallery(services.AsyncService.generic).run( path, scan_options, view_id=view_id) return message.Identity('data', {'command_id': cmd_id, 'view_id': view_id})
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_command_value(command_ids: list): """ Get the returned command value Args: command_ids: list of command ids Returns: ``` { command_id : value } ``` """ _command_msg(command_ids) values = {} for i in command_ids: cmd = Service.get_command(i) if cmd.state not in (command.CommandState.finished, command.CommandState.stopped): if cmd.state == command.CommandState.failed: raise exceptions.CommandError(utils.this_function(), "Command with ID '{}' has failed".format(i)) raise exceptions.CommandError(utils.this_function(), "Command with ID '{}' has not finished running".format(i)) if isinstance(cmd.value, message.CoreMessage): values[i] = cmd.value.json_friendly(include_key=False) else: values[i] = cmd.value if constants.debug: cmd._log_stats(arrow.now()) return message.Identity('command_value', values)
def get_translations(locale: str = None): """ Get all translations for given locale You can find more about translations :ref:`here <Translations>`. Args: locale: locale to get translations from (will override default locale) Returns: .. code-block:: guess { 'namespace.translation_id' : string } .. seealso:: :func:`.get_locales` """ trs = {} locale = helpers._get_locale(locale).lower() container = i18n.translations.container if locale in container: trs = container[locale].copy() else: try: translate("general.locale") trs = container[locale].copy() except exceptions.APIError: pass return message.Identity("translations", trs)
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 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 submit_temporary_view(view_type: enums.TemporaryViewType = enums.TemporaryViewType.GalleryAddition, view_id: int = None,): """ not ready yet... Args: view_type: type of temporary view view_id: id of a specific view Returns: [] |async command| """ view_type = enums.TemporaryViewType.get(view_type) cmd_id = None if view_type == enums.TemporaryViewType.GalleryAddition: c = constants.store.galleryfs_addition.get({}) if view_id: c = list(c.get(view_id, tuple())) else: c = list(itertools.chain(*c.values())) cmd_id = database_cmd.AddItem(services.AsyncService.generic).run(c) return message.Identity('command_id', cmd_id)
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_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_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 translate(t_id: str, locale: str = None, default: str = None, placeholder: str = {}, count: int = None): """ Get a translation by translation id. Raises error if a default value was not provided and no translation was found. You can find more about translations :ref:`here <Translations>`. Args: t_id: translation id locale: locale to get translations from (will override default locale) default: default text when no translation was found placeholder: ? count: pluralization Returns: string .. seealso:: :func:`.get_locales` """ kwargs = {} trs = default kwargs["locale"] = helpers._get_locale(locale).lower() if placeholder: kwargs.update(placeholder), if count is not None: kwargs["count"] = count if default: kwargs["default"] = default if not t_id and default is None: raise exceptions.APIError(utils.this_function(), "Invalid translation id: {}".format(t_id)) elif t_id: try: trs = i18n.t(t_id, **kwargs) except KeyError as e: if default is None: raise exceptions.APIError( utils.this_function(), "Translation id '{}' not found".format(t_id)) except i18n.loaders.loader.I18nFileLoadError as e: if default is None: log.exception( "Failed to load translation file '{}' with key '{}'". format( locale if locale else config.translation_locale.value, t_id)) raise exceptions.APIError( utils.this_function(), "Failed to load translation file: {}".format(e.args)) return message.Identity("translation", trs)
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 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 restart_application(): """ Restart the application Returns: This function will not return """ meta_cmd.RestartApplication().run() return message.Identity('status', True)
def reply_notification(msg_id: int, action_values: dict): """ Not ready yet... """ s = False if constants.notification: constants.notification.reply(msg_id, action_values) s = True return message.Identity("status", s)
def get_notification(scope=None, msg_id: int = None, expired: bool = False): """ Not ready yet... """ msg = None if constants.notification: msg = constants.notification._fetch(scope=scope, expired=expired) return msg if msg else message.Identity("notification", msg)
def shutdown_application(): """ Shutdown the application Returns: This function will not return """ meta_cmd.ShutdownApplication().run() return message.Identity('status', True)
def get_locales(): """ Retrieve available translation locales Returns: .. code-block:: guess { str : { 'locale' : str 'namespaces': [str, ...] } } .. seealso:: :func:`.translate` """ if constants.translations is None: trs_dict = {} for f in os.scandir(constants.dir_translations): if not f.is_file or not f.name.endswith(".yaml"): continue n = f.name.split(".") if len(n) == 3: t_locale, t_ns, _ = n l_dict = trs_dict.setdefault(t_locale, {}) if 'locale' not in l_dict: t_general = "{}.general.yaml".format(t_locale) t_general_path = os.path.join(constants.dir_translations, t_general) if not os.path.exists(t_general_path): continue try: with open(t_general_path, "r", encoding="utf-8") as rf: f_dict = yaml.safe_load(rf) if 'locale' in f_dict: l_dict['locale'] = f_dict['locale'] except yaml.YAMLError as e: log.w( "Failed to load translation file {}:".format( t_general), e) continue l_dict.setdefault('namespaces', []).append(t_ns) constants.translations = trs_dict d = {} for a, b in constants.translations.items(): if 'locale' not in b: continue d[a] = b return message.Identity("locales", d)
def get_version(): """ Get version of components: 'core', 'db' and 'torrent' Returns: a dict of component: list of major, minor, patch """ vs = dict(core=list(constants.version), db=list(constants.version_db), torrent=(0, 0, 0)) return message.Identity("version", vs)
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 temporary_view( view_type: enums.TemporaryViewType = enums.TemporaryViewType. GalleryAddition, view_id: int = None, limit: int = 100, offset: int = 0, # sort_by: enums.ItemSort = None, # sort_desc: bool=False, ): """ not ready yet... Args: view_type: type of temporary view view_id: id of a specific view limit: amount of items per page offset: offset the results by n items Returns: .. code-block:: guess { 'items': [ ... ], 'count': int # count of all items in view } """ view_type = enums.TemporaryViewType.get(view_type) d = {'items': [], 'count': 0} msg_obj = None sess = constants.db_session() sess.autoflush = False if view_type == enums.TemporaryViewType.GalleryAddition: msg_obj = message.GalleryFS c = constants.store.galleryfs_addition.get({}) if view_id: c = list(c.get(view_id, tuple())) else: c = list(itertools.chain(*c.values())) d['count'] = len(c) d['items'] = [ msg_obj(x).json_friendly(False) if msg_obj else x for x in c[offset:offset + limit] ] return message.Identity('items', d)
def get_tags_count(): """ Get count of namespacetags in the database Returns: .. code-block:: guess { 'count' : int } """ s = constants.db_session() return message.Identity('count', {'count': s.query(db.NamespaceTags).count()})
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 get_version(): """ Get version of components: 'core', 'db' and 'torrent' Returns: .. code-block:: guess { 'core' : [int, int, int], 'db' : [int, int, int], 'torrent' : [int, int, int], } """ vs = dict(core=list(constants.version), db=list(constants.version_db), torrent=(0, 0, 0)) return message.Identity("version", vs)
def update_application(download_url: str = None, restart: bool = True): """ Update the application with a new release. If download_url is not provided, a check for a new release will occur Args: download_url: a url to the release file, can be path to file on the system restart: restart the application after installing the new update Returns: A ``bool`` indicating whether the install was successful or not |async command| """ upd = meta_cmd.UpdateApplication(AsyncService.generic).run( download_url, restart) return message.Identity('update', upd)
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)