Пример #1
0
class GetModelItemByID(Command):
    """
    Fetch model items from the database by a set of ids

    Returns a tuple of model items
    """

    fetched = CommandEvent("fetched", str, tuple)

    def __init__(self):
        super().__init__()

        self.fetched_items = tuple()

    def _query(self, q, limit, offset):
        if offset:
            q = q.offset(offset)

        return q.limit(limit).all()

    def main(self,
             model: db.Base,
             ids: set,
             limit: int = 999,
             filter: str = "",
             order_by: str = "",
             offset: int = 0) -> tuple:

        log.d("Fetching items from a set with", len(ids), "ids", "offset:",
              offset, "limit:", limit)
        if not ids:
            return tuple()

        s = constants.db_session()

        q = s.query(model)

        if filter:
            q = q.filter(db.sa_text(filter))

        if order_by:
            q = q.order_by(db.sa_text(order_by))

        id_amount = len(ids)
        # TODO: only SQLite has 999 variables limit
        _max_variables = 900
        if id_amount > _max_variables:
            fetched_list = [x for x in q.all() if x.id in ids]
            fetched_list = fetched_list[offset:][:limit]
            self.fetched_items = tuple(fetched_list)
        elif id_amount == 1:
            self.fetched_items = (q.get(ids.pop()), )
        else:
            q = q.filter(model.id.in_(ids))
            self.fetched_items = tuple(self._query(q, limit, offset))

        self.fetched.emit(db.model_name(model), self.fetched_items)
        log.d("Returning", len(self.fetched_items), "fetched items")
        return self.fetched_items
Пример #2
0
class GetModelItems(Command):
    """
    Fetch model items from the database

    Returns a tuple of model items
    """

    fetched = CommandEvent("fetched", str, tuple)

    def __init__(self):
        super().__init__()

        self.fetched_items = tuple()

    def _query(self, q, limit, offset):
        if offset:
            q = q.offset(offset)

        return q.limit(limit).all()

    def main(self,
             model: db.Base,
             limit: int = 999,
             filter: str = "",
             order_by: str = "",
             offset: int = 0,
             join: str = "") -> tuple:

        s = constants.db_session()

        q = s.query(model)

        if join:
            if not isinstance(join, (list, tuple)):
                join = [join]

            for j in join:
                if isinstance(j, str):
                    q = q.join(db.sa_text(j))
                else:
                    q = q.join(j)

        if filter:
            if isinstance(filter, str):
                q = q.filter(db.sa_text(filter))
            else:
                q = q.filter(filter)

        if order_by:
            q = q.order_by(db.sa_text(order_by))

        self.fetched_items = tuple(self._query(q, limit, offset))

        self.fetched.emit(db.model_name(model), self.fetched_items)
        return self.fetched_items
Пример #3
0
class InitApplication(Command):
    """
    Initialize the appplication
    """

    init = CommandEvent("init",
                        __doc="""
                        Emitted on application startup where everything has been initialized after the server has started.
                        """)

    def __init__(self, priority=constants.Priority.Normal):
        super().__init__(priority)

    def main(self):
        self.init.emit()
Пример #4
0
class AddGallery(UndoCommand):
    """
    Add a gallery
    """

    added = CommandEvent("added", db.Gallery)

    def __init__(self):
        super().__init__()

    def main(self, gallery: db.Gallery) -> None:
        pass

    def undo(self):
        return super().undo()
Пример #5
0
class ShutdownApplication(Command):
    """
    Shutdown the appplication
    """

    shutdown = CommandEvent("shutdown",
                            __doc="""
                            Emitted when about to shutdown
                            """)

    def __init__(self, priority=constants.Priority.Normal):
        super().__init__(priority)

    def main(self):
        self.shutdown.emit()
Пример #6
0
class RestartApplication(Command):
    """
    Restart the appplication
    """

    restart = CommandEvent("restart",
                           __doc="""
                           Emitted when about to restart
                           """)

    def __init__(self, priority=constants.Priority.Normal):
        super().__init__(priority)

    def main(self):
        self.restart.emit()
Пример #7
0
class ParseTerm(Command):
    """
    Parse a single term

    By default, the following operators are parsed for:
    - '' = ''
    - '<' = 'less'
    - '>' = 'great'

    Returns a namedtuple of strings: Term(namespace, tag, operator)
    """

    parse = CommandEntry("parse", tuple, str)
    parsed = CommandEvent("parsed", Term)

    def __init__(self):
        super().__init__()
        self.filter = ''
        self.term = None

    @parse.default()
    def _parse_term(term):
        s = term.split(':', 1)
        ns = s[0] if len(s) == 2 else ''
        tag = s[1] if len(s) == 2 else term
        operator = ''

        if tag.startswith('<'):
            operator = 'less'
        elif tag.startswith('>'):
            operator = 'great'

        return (ns, tag, operator)

    def main(self, term: str) -> Term:

        self.filter = term

        with self.parse.call(self.filter) as plg:
            t = plg.first()
            if not len(t) == 3:
                t = plg.default()

            self.term = Term(*t)

        self.parsed.emit(self.term)

        return self.term
Пример #8
0
class RenameGallery(UndoCommand):
    """
    Rename a gallery
    """

    renamed = CommandEvent("renamed", str)
    rename = CommandEntry("rename", None, str, str)

    def __init__(self):
        super().__init__()
        self.title = None
        self.old_title = None

    @rename.default()
    def _set_title(old_title, new_title):
        return new_title

    def main(self, title: db.Title, new_title: str) -> None:

        self.title = title
        self.old_title = title.name

        with self.rename.call(title.name, new_title) as plg:
            title.name = plg.first()

            with utils.session() as s:
                s.add(title)

        self.renamed.emit(title.name)

    def undo(self):
        self.title.name = self.old_title

        with utils.session() as s:
            s.add(self.title)

        self.renamed.emit(self.old_title)
Пример #9
0
class ParseSearch(Command):
    """
    Parse a search query

    Dividies term into ns:tag pieces, returns a tuple of ns:tag pieces
    """

    parse = CommandEntry("parse", tuple, str)
    parsed = CommandEvent("parsed", tuple)

    def __init__(self):
        super().__init__()
        self.filter = ''
        self.pieces = tuple()

    @parse.default()
    def _get_terms(term):

        # some variables we will use
        pieces = []
        piece = ''
        qoute_level = 0
        bracket_level = 0
        brackets_tags = {}
        current_bracket_ns = ''
        end_of_bracket = False
        blacklist = ['[', ']', '"', ',']

        for n, x in enumerate(term):
            # if we meet brackets
            if x == '[':
                bracket_level += 1
                brackets_tags[piece] = set()  # we want unique tags!
                current_bracket_ns = piece
            elif x == ']':
                bracket_level -= 1
                end_of_bracket = True

            # if we meet a double qoute
            if x == '"':
                if qoute_level > 0:
                    qoute_level -= 1
                else:
                    qoute_level += 1

            # if we meet a whitespace, comma or end of term and are not in a
            # double qoute
            if (x == ' ' or x == ','
                    or n == len(term) - 1) and qoute_level == 0:
                # if end of term and x is allowed
                if (n == len(term) - 1) and x not in blacklist and x != ' ':
                    piece += x
                if piece:
                    if bracket_level > 0 or end_of_bracket:  # if we are inside a bracket we put piece in the set
                        end_of_bracket = False
                        if piece.startswith(current_bracket_ns):
                            piece = piece[len(current_bracket_ns):]
                        if piece:
                            try:
                                brackets_tags[current_bracket_ns].add(piece)
                            except KeyError:  # keyerror when there is a closing bracket without a starting bracket
                                pass
                    else:
                        pieces.append(piece)  # else put it in the normal list
                piece = ''
                continue

            # else append to the buffers
            if x not in blacklist:
                if qoute_level > 0:  # we want to include everything if in double qoute
                    piece += x
                elif x != ' ':
                    piece += x

        # now for the bracket tags
        for ns in brackets_tags:
            for tag in brackets_tags[ns]:
                ns_tag = ns
                # if they want to exlucde this tag
                if tag[0] == '-':
                    if ns_tag[0] != '-':
                        ns_tag = '-' + ns
                    tag = tag[1:]  # remove the '-'

                # put them together
                ns_tag += tag

                # done
                pieces.append(ns_tag)

        return tuple(pieces)

    def main(self, search_filter: str) -> tuple:

        self.filter = search_filter

        pieces = set()

        with self.parse.call(search_filter) as plg:
            for p in plg.all(default=True):
                for x in p:
                    pieces.add(x)
        self.pieces = tuple(pieces)

        self.parsed.emit(self.pieces)

        return self.pieces
Пример #10
0
class ModelFilter(Command):
    """
    Perform a full search on database model
    Returns a set of ids of matched model items
    """

    separate = CommandEntry("separate", tuple, tuple)
    include = CommandEntry("include", set, str, set)
    exclude = CommandEntry("exclude", set, str, set)
    empty = CommandEntry("empty", set, str)

    included = CommandEvent("included", str, set)
    excluded = CommandEvent("excluded", str, set)
    matched = CommandEvent("matched", str, set)

    def __init__(self):
        super().__init__()
        self._model = None
        self.parsesearchfilter = None
        self.included_ids = set()
        self.excluded_ids = set()
        self.matched_ids = set()

    @separate.default()
    def _separate(pecies):

        include = []
        exclude = []

        for p in pecies:
            if p.startswith('-'):
                exclude.append(p[1:])  # remove '-' at the start
            else:
                include.append(p)

        return tuple(include), tuple(exclude)

    @staticmethod
    def _match(model_name, pieces):
        ""
        model = database_cmd.GetModelClass().run(model_name)
        partialfilter = PartialModelFilter()
        matched = set()

        for p in pieces:
            m = partialfilter.run(model, p)
            matched.update(m)

        return matched

    @include.default()
    def _include(model_name, pieces):
        return ModelFilter._match(model_name, pieces)

    @exclude.default()
    def _exclude(model_name, pieces):
        return ModelFilter._match(model_name, pieces)

    @empty.default()
    def _empty(model_name):
        model = database_cmd.GetModelClass().run(model_name)
        s = constants.db_session()
        return set(x[0] for x in s.query(model.id).all())

    def main(self, model: db.Base, search_filter: str) -> set:
        assert issubclass(model, db.Base)

        self._model = model
        model_name = db.model_name(self._model)

        if search_filter:

            self.parsesearchfilter = ParseSearch()

            pieces = self.parsesearchfilter.run(search_filter)

            options = get_search_options()

            include = set()
            exclude = set()

            with self.separate.call(pieces) as plg:

                for p in plg.all():
                    if len(p) == 2:
                        include.update(p[0])
                        exclude.update(p[1])

            with self.include.call(model_name, include) as plg:

                for i in plg.all():
                    if options.get("all"):
                        if self.included_ids:
                            self.included_ids.intersection_update(i)
                        else:
                            self.included_ids.update(i)
                    else:
                        self.included_ids.update(i)

            self.included.emit(model_name, self.included_ids)

            with self.exclude.call(model_name, exclude) as plg:

                for i in plg.all():
                    self.excluded_ids.update(i)

            self.excluded.emit(self._model.__name__, self.excluded_ids)

            self.matched_ids = self.included_ids
            self.matched_ids.difference_update(self.excluded_ids)

        else:

            with self.empty.call(model_name) as plg:
                for i in plg.all():
                    self.matched_ids.update(i)

        self.matched.emit(self._model.__name__, self.matched_ids)

        return self.matched_ids
Пример #11
0
class PartialModelFilter(Command):
    """
    Perform a partial search on database model with a single term

    Accepts any term

    By default, the following models are supported:

    - User
    - NamespaceTags
    - Tag
    - Namespace
    - Artist
    - Circle
    - Status
    - Grouping
    - Language
    - Category
    - Collection
    - Gallery
    - Title
    - GalleryUrl

    Returns a set with ids of matched model items
    """

    models = CommandEntry("models", tuple)

    match_model = CommandEntry("match_model", set, str, str, str, dict)
    matched = CommandEvent("matched", set)

    def __init__(self):
        super().__init__()
        self.model = None
        self.term = ''
        self._supported_models = set()
        self.matched_ids = set()

    @models.default()
    def _models():
        return (db.NamespaceTags, db.Tag, db.Namespace, db.Artist, db.Circle,
                db.Status, db.Grouping, db.Language, db.Category,
                db.Collection, db.Gallery, db.Title, db.GalleryUrl)

    @staticmethod
    def _match_string_column(column, term, options):

        expr = None
        tag = term.tag

        if options.get("regex"):
            if options.get("case"):
                expr = column.regexp
            else:
                expr = column.iregexp
        else:
            if not options.get("whole"):
                tag = '%' + tag + '%'

            if options.get("case"):
                expr = column.like
            else:
                expr = column.ilike

        return expr(tag)

    @staticmethod
    def _match_integer_column(session, parent_model, column, term, options):

        return []

    @match_model.default(capture=True)
    def _match_gallery(parent_model,
                       child_model,
                       term,
                       options,
                       capture=db.model_name(db.Gallery)):
        get_model = database_cmd.GetModelClass()
        parent_model = get_model.run(parent_model)
        child_model = get_model.run(child_model)

        match_string = PartialModelFilter._match_string_column
        match_int = PartialModelFilter._match_integer_column
        term = ParseTerm().run(term)
        ids = set()

        s = constants.db_session()

        if term.namespace:
            lower_ns = term.namespace.lower()
            if lower_ns == 'path':
                ids.update(x[0] for x in s.query(parent_model.id).filter(
                    match_string(db.Gallery.path, term, options)).all())
            elif lower_ns in ("rating", "stars"):
                ids.update(x[0] for x in s.query(parent_model.id).filter(
                    match_int(db.Gallery.rating, term, options)).all())

        return ids

    @match_model.default(capture=True)
    def _match_title(parent_model,
                     child_model,
                     term,
                     options,
                     capture=db.model_name(db.Title)):
        get_model = database_cmd.GetModelClass()
        parent_model = get_model.run(parent_model)
        child_model = get_model.run(child_model)

        match_string = PartialModelFilter._match_string_column
        term = ParseTerm().run(term)
        ids = set()

        if issubclass(parent_model, db.Gallery):
            if term.namespace.lower() == 'title' or not term.namespace:
                s = constants.db_session()
                ids.update(x[0] for x in s.query(parent_model.id).join(
                    parent_model.titles).filter(
                        match_string(child_model.name, term, options)).all())
        else:
            raise NotImplementedError(
                "Title on {} has not been implemented".format(parent_model))

        return ids

    @match_model.default(capture=True)
    def _match_namemixin(parent_model,
                         child_model,
                         term,
                         options,
                         capture=[
                             db.model_name(x) for x in _models()
                             if issubclass(x, db.NameMixin)
                         ]):
        get_model = database_cmd.GetModelClass()
        parent_model = get_model.run(parent_model)
        child_model = get_model.run(child_model)
        match_string = PartialModelFilter._match_string_column
        term = ParseTerm().run(term)
        ids = set()

        s = constants.db_session()

        col = db.relationship_column(parent_model, child_model)

        ids.update(x[0] for x in s.query(parent_model.id).join(col).filter(
            match_string(child_model.name, term, options)).all())

        return ids

    def main(self, model: db.Base, term: str) -> set:

        self.model = model
        model_name = db.model_name(self.model)
        self.term = term

        with self.models.call() as plg:
            for p in plg.all(default=True):
                self._supported_models.update(p)

        if self.model not in self._supported_models:
            raise exceptions.CommandError(
                utils.this_command(self),
                "Model '{}' is not supported".format(model))

        related_models = db.related_classes(model)

        sess = constants.db_session()

        model_count = sess.query(model).count()

        with self.match_model.call_capture(model_name, model_name,
                                           model_name, self.term,
                                           get_search_options()) as plg:
            for i in plg.all():
                self.matched_ids.update(i)
                if len(self.matched_ids) == model_count:
                    break

        has_all = False
        for m in related_models:
            if m in self._supported_models:
                with self.match_model.call_capture(
                        db.model_name(m), model_name, db.model_name(m),
                        self.term, get_search_options()) as plg:
                    for i in plg.all():
                        self.matched_ids.update(i)
                        if len(self.matched_ids) == model_count:
                            has_all = True
                            break
            if has_all:
                break

        self.matched.emit(self.matched_ids)

        return self.matched_ids
Пример #12
0
class GetModelImage(AsyncCommand):
    """
    Fetch a database model item's image

    By default, the following models are supported

    - Gallery
    - Page
    - Grouping
    - Collection
    - GalleryFilter

    Returns a Profile database item
    """

    models = CommandEntry("models", tuple)
    generate = CommandEntry("generate", str, str, int, utils.ImageSize)

    cover_event = CommandEvent('cover', object)

    def __init__(self, service=None):
        super().__init__(service, priority=constants.Priority.Low)
        self.model = None
        self.cover = None
        self._supported_models = set()

    @models.default()
    def _models():
        return (db.Grouping, db.Collection, db.Gallery, db.Page,
                db.GalleryFilter)

    @generate.default(capture=True)
    def _generate(model,
                  item_id,
                  size,
                  capture=[db.model_name(x) for x in (db.Page, db.Gallery)]):
        im_path = ""
        model = GetModelClass().run(model)

        if model == db.Gallery:
            page = GetSession().run().query(db.Page.path).filter(
                db.and_op(db.Page.gallery_id == item_id,
                          db.Page.number == 1)).one_or_none()
        else:
            page = GetSession().run().query(
                db.Page.path).filter(db.Page.id == item_id).one_or_none()

        if page:
            im_path = page[0]

        if im_path:
            im_props = io_cmd.ImageProperties(size, 0, constants.dir_thumbs)
            im_path = io_cmd.ImageItem(None, im_path, im_props).main()
        return im_path

    def main(self, model: db.Base, item_id: int,
             image_size: enums.ImageSize) -> db.Profile:

        self.model = model

        if image_size == enums.ImageSize.Original:
            image_size = utils.ImageSize(0, 0)
        else:
            image_size = utils.ImageSize(
                *constants.image_sizes[image_size.name.lower()])

        with self.models.call() as plg:
            for p in plg.all(default=True):
                self._supported_models.update(p)

        if self.model not in self._supported_models:
            raise exceptions.CommandError(
                utils.this_command(self),
                "Model '{}' is not supported".format(model))

        img_hash = io_cmd.ImageItem.gen_hash(model, image_size, item_id)

        cover_path = ""
        generate = True
        sess = constants.db_session()
        self.cover = sess.query(
            db.Profile).filter(db.Profile.data == img_hash).one_or_none()

        if self.cover:
            if io_cmd.CoreFS(self.cover.path).exists:
                generate = False
            else:
                cover_path = self.cover.path
        if generate:
            self.cover = self.run_native(self._generate_and_add, img_hash,
                                         generate, cover_path, model, item_id,
                                         image_size).get()
        self.cover_event.emit(self.cover)
        return self.cover

    def _generate_and_add(self, img_hash, generate, cover_path, model, item_id,
                          image_size):

        sess = constants.db_session()

        model_name = db.model_name(model)

        new = False
        if cover_path:
            self.cover = sess.query(
                db.Profile).filter(db.Profile.data == img_hash).one_or_none()
        else:
            self.cover = db.Profile()
            new = True

        if generate:
            with self.generate.call_capture(model_name, model_name, item_id,
                                            image_size) as plg:
                self.cover.path = plg.first()

            self.cover.data = img_hash
            self.cover.size = str(tuple(image_size))

        if self.cover.path and generate:
            if new:
                s = constants.db_session()
                i = s.query(model).get(item_id)
                i.profiles.append(self.cover)
            sess.commit()
        elif not self.cover.path:
            self.cover = None
        return self.cover
Пример #13
0
class GetModelItemByID(Command):
    """
    Fetch model items from the database by a set of ids

    Returns a tuple of model items
    """

    fetched = CommandEvent("fetched", str, tuple)

    count = CommandEvent("count", str, int)

    def __init__(self):
        super().__init__()

        self.fetched_items = tuple()

    def _query(self, q, limit, offset):
        if offset:
            q = q.offset(offset)

        return q.limit(limit).all()

    def _get_sql(self, expr):
        if isinstance(expr, str):
            return db.sa_text(expr)
        else:
            return expr

    def main(self,
             model: db.Base,
             ids: set = None,
             limit: int = 999,
             filter: str = None,
             order_by: str = None,
             offset: int = 0,
             columns: tuple = tuple(),
             join: str = None,
             count: bool = False) -> tuple:
        if ids is None:
            log.d("Fetching items", "offset:", offset, "limit:", limit)
        else:
            log.d("Fetching items from a set with", len(ids), "ids", "offset:",
                  offset, "limit:", limit)

        if ids is not None and not ids:
            return tuple()

        s = constants.db_session()
        if count:
            q = s.query(model.id)
        elif columns:
            q = s.query(*columns)
        else:
            q = s.query(model)

        if join is not None:
            if not isinstance(join, (list, tuple)):
                join = [join]
            for j in join:
                q = q.join(self._get_sql(j))
        if filter is not None:
            q = q.filter(self._get_sql(filter))

        if order_by is not None:
            q = q.order_by(self._get_sql(order_by))

        if ids:
            id_amount = len(ids)
            # TODO: only SQLite has 999 variables limit
            _max_variables = 900
            if id_amount > _max_variables:
                if count:
                    fetched_list = [x for x in q.all() if x[0] in ids]
                else:
                    fetched_list = [x for x in q.all() if x.id in ids]

                fetched_list = fetched_list[offset:][:limit]
                self.fetched_items = tuple(fetched_list) if not count else len(
                    fetched_list)
            elif id_amount == 1:
                self.fetched_items = (q.get(
                    ids.pop()), ) if not count else q.count()
            else:
                q = q.filter(model.id.in_(ids))
                self.fetched_items = tuple(self._query(
                    q, limit, offset)) if not count else q.count()
        else:
            self.fetched_items = tuple(self._query(
                q, limit, offset)) if not count else q.count()

        if count:
            self.fetched_items = q.count()
            self.count.emit(db.model_name(model), self.fetched_items)
            log.d("Returning items count ", self.fetched_items)
        else:
            self.fetched.emit(db.model_name(model), self.fetched_items)
            self.count.emit(db.model_name(model), len(self.fetched_items))
            log.d("Returning", len(self.fetched_items), "fetched items")
        return self.fetched_items
Пример #14
0
class GetModelImage(AsyncCommand):
    """
    Fetch a database model item's image

    By default, the following models are supported

    - Gallery
    - Page
    - Grouping
    - Collection
    - GalleryFilter

    Returns a Profile database item

    Args:
        model: a database model
        item_id: id of database item
        image_size: size of image

    Returns:
        a database :class:`.db.Profile` object

    """

    models: tuple = CommandEntry("models",
                                 __doc="""
                                 Called to fetch the supported database models
                                 """,
                                 __doc_return="""
                                 a tuple of database models :class:`.db.Base`
                                 """)
    generate: str = CommandEntry("generate",
                                 CParam("model_name", str,
                                        "name of a database model"),
                                 CParam("item_id", int, "id of database item"),
                                 CParam("image_size", utils.ImageSize,
                                        "size of image"),
                                 __capture=(str, "name of database model"),
                                 __doc="""
                               Called to generate an image file of database item
                               """,
                                 __doc_return="""
                               path to image file
                               """)
    invalidate: bool = CommandEntry("invalidate",
                                    CParam("model_name", str,
                                           "name of a database model"),
                                    CParam("item_id", int,
                                           "id of database item"),
                                    CParam("image_size", utils.ImageSize,
                                           "size of image"),
                                    __capture=(str, "name of database model"),
                                    __doc="""
                                Called to check if a new image should be forcefully generated
                                """,
                                    __doc_return="""
                                bool indicating wether an image should be generated or not
                                """)

    cover_event = CommandEvent('cover',
                               CParam(
                                   "profile_item", object,
                                   "database item with the generated image"),
                               __doc="""
                               Emitted at the end of the process with :class:`.db.Profile` database item or ``None``
                               """)

    def main(self, model: db.Base, item_id: int,
             image_size: enums.ImageSize) -> db.Profile:
        self.model = model

        if image_size == enums.ImageSize.Original:
            image_size = utils.ImageSize(0, 0)
        else:
            image_size = utils.ImageSize(
                *constants.image_sizes[image_size.name.lower()])

        with self.models.call() as plg:
            for p in plg.all(default=True):
                self._supported_models.update(p)

        if self.model not in self._supported_models:
            raise exceptions.CommandError(
                utils.this_command(self),
                "Model '{}' is not supported".format(model))

        img_hash = io_cmd.ImageItem.gen_hash(model, image_size, item_id)

        generate = True
        sess = constants.db_session()

        profile_size = str(tuple(image_size))

        self.cover = sess.query(db.Profile).filter(
            db.and_op(db.Profile.data == img_hash,
                      db.Profile.size == profile_size)).first()

        old_img_hash = None
        if self.cover:
            if io_cmd.CoreFS(self.cover.path).exists:
                generate = False
            else:
                old_img_hash = self.cover.data

        self.next_progress()
        if not generate:
            model_name = db.model_name(model)
            with self.invalidate.call_capture(model_name, model_name, item_id,
                                              image_size) as plg:
                if plg.first_or_default():
                    generate = True

        self.next_progress()
        if generate:
            constants.task_command.thumbnail_cleaner.wake_up()
            self.cover = self.run_native(self._generate_and_add, img_hash,
                                         old_img_hash, generate, model,
                                         item_id, image_size,
                                         profile_size).get()
        self.cover_event.emit(self.cover)
        return self.cover

    def __init__(self, service=None):
        super().__init__(service, priority=constants.Priority.Low)
        self.model = None
        self.cover = None
        self._supported_models = set()

    @models.default()
    def _models():
        return (db.Grouping, db.Collection, db.Gallery, db.Page,
                db.GalleryFilter)

    @generate.default(capture=True)
    def _generate_gallery_and_page(
            model,
            item_id,
            size,
            capture=[db.model_name(x) for x in (db.Page, db.Gallery)]):
        im_path = ""
        model = GetModelClass().run(model)

        if model == db.Gallery:
            page = GetSession().run().query(db.Page.path).filter(
                db.and_op(db.Page.gallery_id == item_id,
                          db.Page.number == 1)).one_or_none()
        else:
            page = GetSession().run().query(
                db.Page.path).filter(db.Page.id == item_id).one_or_none()

        if page:
            im_path = page[0]

        if im_path:
            im_props = io_cmd.ImageProperties(size, 0, constants.dir_thumbs)
            im_path = io_cmd.ImageItem(im_path, im_props).main()
        return im_path

    @invalidate.default(capture=True)
    def _invalidate_gallery_and_page(
            model,
            item_id,
            size,
            capture=[db.model_name(x) for x in (db.Page, db.Gallery)]):
        return False

    @generate.default(capture=True)
    def _generate_collection(model,
                             item_id,
                             size,
                             capture=db.model_name(db.Collection)):
        im_path = ""
        model = GetModelClass().run(model)

        page = GetSession().run().query(db.Page.path).join(
            db.Collection.galleries).join(db.Gallery.pages).filter(
                db.and_op(db.Collection.id == item_id,
                          db.Page.number == 1)).first()

        # gallery sorted by insertion:
        # page = GetSession().run().query(
        #    db.Page.path, db.gallery_collections.c.timestamp.label("timestamp")).join(db.Collection.galleries).join(db.Gallery.pages).filter(
        #    db.and_op(
        #        db.Collection.id == item_id,
        #        db.Page.number == 1)).sort_by("timestamp").first()
        if page:
            im_path = page[0]

        if im_path:
            im_props = io_cmd.ImageProperties(size, 0, constants.dir_thumbs)
            im_path = io_cmd.ImageItem(im_path, im_props).main()
        return im_path

    @invalidate.default(capture=True)
    def _invalidate_collection(model,
                               item_id,
                               size,
                               capture=db.model_name(db.Collection)):
        return False

    @async_utils.defer
    def _update_db(self, stale_cover, item_id, model, old_hash):
        log.d("Updating profile for database item", model)
        s = constants.db_session()
        cover = s.query(db.Profile).filter(
            db.and_op(db.Profile.data == old_hash,
                      db.Profile.size == stale_cover.size)).all()

        if len(cover) > 1:
            cover, *cover_ex = cover
            for x in cover_ex:
                s.delete(x)
        elif cover:
            cover = cover[0]

        new = False

        if cover:
            # sometimes an identical img has already been generated and exists so we shouldnt do anything
            fs = io_cmd.CoreFS(cover.path)
            if (cover.path != stale_cover.path) and fs.exists:
                fs.delete()
        else:
            cover = db.Profile()
            new = True

        cover.data = stale_cover.data
        cover.path = stale_cover.path
        cover.size = stale_cover.size

        if new or not s.query(db.Profile).join(
                db.relationship_column(model, db.Profile)).filter(
                    db.and_op(db.Profile.id == cover.id, model.id
                              == item_id)).scalar():
            log.d("Adding new profile to database item", model,
                  "()".format(item_id))
            i = s.query(model).get(item_id)
            i.profiles.append(cover)

        s.commit()
        self.next_progress()

    def _generate_and_add(self, img_hash, old_img_hash, generate, model,
                          item_id, image_size, profile_size):

        model_name = db.model_name(model)

        cover = db.Profile()
        if generate:
            log.d("Generating new profile", image_size, "for database item",
                  model)
            with self.generate.call_capture(model_name, model_name, item_id,
                                            image_size) as plg:
                p = plg.first_or_default()
                if not p:
                    p = ""
                cover.path = p

            cover.data = img_hash
            cover.size = profile_size
        self.next_progress()
        if cover.path and generate:
            log.d("Updating database")
            self._update_db(cover, item_id, model, old_img_hash)
        elif not cover.path:
            cover = None
        return cover
Пример #15
0
class OpenGallery(Command):
    """
    Open a gallery in an external viewer

    Args:
        gallery_or_id: a :class:`.db.Gallery` database item or an item id thereof
        number: page number
        args: arguments to pass to the external program

    Returns:
        bool indicating wether the gallery was successfully opened
    """

    _opened = CommandEvent("opened",
                           CParam("parent_path", str, "path to parent folder or archive"),
                           CParam("child_path", str, "path to opened file in folder or archive"),
                           CParam("gallery", db.Gallery, "database item object that was opened"),
                           CParam("number", int, "page number"),
                           __doc="""
                           Emitted when a gallery or page was successfully opened
                           """)

    _open: bool = CommandEntry("open",
                               CParam("parent_path", str, "path to parent folder or archive"),
                               CParam("child_path", str, "path to opened file in folder or archive"),
                               CParam("gallery", db.Gallery, "database item object that was opened"),
                               CParam("arguments", tuple, "a tuple of arguments to pass to the external program"),
                               __doc="""
                               Called to open the given file in an external program
                               """,
                               __doc_return="""
                               a bool indicating whether the file could be opened or not
                               """)
    _resolve: tuple = CommandEntry("resolve",
                                   CParam("gallery", db.Gallery, "database item object that was opened"),
                                   CParam("number", int, "page number"),
                                   __doc="""
                                   Called to resolve the parent (containing folder or archive) and child (the file, usually the first) paths
                                   """,
                                   __doc_return="""
                                   a tuple with two items, the parent (``str``) and child (``str``) paths
                                   """
                                   )

    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 __init__(self):
        super().__init__()
        self.path = ""
        self.first_file = ""
        self.gallery = None

    @_open.default()
    def _open_gallery(parent, child, gallery, args):
        ex_path = config.external_image_viewer.value.strip()
        log.d("Opening gallery ({}):\n\tparent:{}\n\tchild:{}".format(gallery.id, parent, child))
        log.d("External viewer:", ex_path)
        log.d("External args:", args)
        opened = False
        path = parent
        if child and config.send_path_to_first_file.value:
            path = child

        if ex_path:
            subprocess.Popen((ex_path, path, *args))
            opened = True
        else:
            io_cmd.CoreFS.open_with_default(path)
            opened = True

        return opened

    @_resolve.default()
    def _resolve_gallery(gallery, number):
        parent = ""
        child = ""
        if gallery.single_source:
            if gallery.pages.count():
                # TODO: when number 1 doesnt exist?
                first_page = gallery.pages.filter(db.Page.number == number).first()
                if first_page:
                    if first_page.in_archive:
                        p = io_cmd.CoreFS(first_page.path)
                        parent = p.archive_path
                        child = p.path
                    else:
                        child = first_page.path
                        parent = os.path.split(first_page.path)[0]
        else:
            raise NotImplementedError

        return parent, child
Пример #16
0
class ModelFilter(Command):
    """
    Perform a full search on database model

    Args:
        model: a database model item
        search_filter: a search query
        match_options: search options, refer to :ref:`Settings`

    Returns:
        a set of ids of matched database model items
    """

    separate: tuple = CommandEntry("separate",
                                   CParam("pieces", tuple, "a tuple of terms"),
                                   __doc="""
                                   Called to separate terms that include and terms that exclude from eachother
                                   """,
                                   __doc_return="""
                                   a tuple of two tuples where the first tuple contains terms that include
                                   and the second contains terms that exclude
                                   """
                                   )
    include: set = CommandEntry("include",
                                CParam("model_name", str, "name of a database model"),
                                CParam("pieces", set, "a set of terms"),
                                CParam("options", ChainMap, "search options"),
                                __doc="""
                                Called to match database items of the given model to include in the final results
                                """,
                                __doc_return="""
                                a ``set`` of ids of the database items that match
                                """
                                )
    exclude: set = CommandEntry("exclude",
                                CParam("model_name", str, "name of a database model"),
                                CParam("pieces", set, "a set of terms"),
                                CParam("options", ChainMap, "search options"),
                                __doc="""
                                Called to match database items of the given model to exclude in the final results
                                """,
                                __doc_return="""
                                a ``set`` of ids of the database items that match
                                """
                                )
    empty: set = CommandEntry("empty",
                              CParam("model_name", str, "name of a database model"),
                              __doc="""
                                Called when the search query is empty
                                """,
                              __doc_return="""
                                a ``set`` of ids of the database items that match when a search query is empty
                                """)

    included = CommandEvent("included",
                            CParam("model_name", str, "name of a database model"),
                            CParam("matched_ids", set, "a ``set`` of ids of the database items that match for inclusion"),
                            __doc="""
                            Emitted after the match
                            """)

    excluded = CommandEvent("excluded",
                            CParam("model_name", str, "name of a database model"),
                            CParam("matched_ids", set, "a ``set`` of ids of the database items that match for exclusion"),
                            __doc="""
                            Emitted after the match
                            """)

    matched = CommandEvent("matched",
                           CParam("model_name", str, "name of a database model"),
                           CParam("matched_ids", set, "a ``set`` of ids of the database items that match"),
                           __doc="""
                            Emitted at the end of the process with the final results
                            """)

    def main(self, model: db.Base, search_filter: str, match_options: dict = {}) -> typing.Set[int]:
        assert issubclass(model, db.Base)

        self._model = model
        model_name = db.model_name(self._model)

        if search_filter:

            self.parsesearchfilter = ParseSearch()

            pieces = self.parsesearchfilter.run(search_filter)

            options = get_search_options(match_options)

            include = set()
            exclude = set()

            with self.separate.call(pieces) as plg:

                for p in plg.all(True):
                    if len(p) == 2:
                        include.update(p[0])
                        exclude.update(p[1])

            if options.get("all"):
                for n, p in enumerate(include):
                    with self.include.call(model_name, {p}, options) as plg:
                        for i in plg.all_or_default():
                            if n != 0:
                                self.included_ids.intersection_update(i)
                            else:
                                self.included_ids.update(i)
            else:
                with self.include.call(model_name, include, options) as plg:

                    for n, i in enumerate(plg.all_or_default()):
                        self.included_ids.update(i)

            self.included.emit(model_name, self.included_ids)

            with self.exclude.call(model_name, exclude, options) as plg:

                for i in plg.all_or_default():
                    self.excluded_ids.update(i)

            self.excluded.emit(self._model.__name__, self.excluded_ids)

            self.matched_ids = self.included_ids
            self.matched_ids.difference_update(self.excluded_ids)

        else:

            with self.empty.call(model_name) as plg:
                for i in plg.all_or_default():
                    self.matched_ids.update(i)

        self.matched.emit(self._model.__name__, self.matched_ids)

        return self.matched_ids

    def __init__(self):
        super().__init__()
        self._model = None
        self.parsesearchfilter = None
        self.included_ids = set()
        self.excluded_ids = set()
        self.matched_ids = set()

    @separate.default()
    def _separate(pecies):

        include = []
        exclude = []

        for p in pecies:
            if p.startswith('-'):
                exclude.append(p[1:])  # remove '-' at the start
            else:
                include.append(p)

        return tuple(include), tuple(exclude)

    @staticmethod
    def _match(model_name, pieces, options):
        ""
        model = database_cmd.GetModelClass().run(model_name)
        partialfilter = PartialModelFilter()
        matched = set()

        for p in pieces:
            m = partialfilter.run(model, p, options)
            matched.update(m)

        return matched

    @include.default()
    def _include(model_name, pieces, options):
        return ModelFilter._match(model_name, pieces, options)

    @exclude.default()
    def _exclude(model_name, pieces, options):
        return ModelFilter._match(model_name, pieces, options)

    @empty.default()
    def _empty(model_name):
        model = database_cmd.GetModelClass().run(model_name)
        s = constants.db_session()
        return set(x[0] for x in s.query(model.id).all())
Пример #17
0
class ParseTerm(Command):
    """
    Parse a single term

    By default, the following operators are parsed for:
    - '' to ''
    - '<' to 'less'
    - '>' to 'great'

    Args:
        term: a single term

    Returns:
        a namedtuple of namespace, tag and operator
    """

    parse: tuple = CommandEntry("parse",
                                CParam('term', str, "a single term"),
                                __doc="""
                                Called to parse the term into ``namespace``, ``tag`` and ``operator``
                                """,
                                __doc_return="""
                                a tuple of strings (namespace, tag, operator)
                                """)

    parsed = CommandEvent("parsed",
                          CParam('term', Term, "the parsed term"),
                          __doc="""
                          Emitted after a term has been parsed
                          """)

    def __init__(self):
        super().__init__()
        self.filter = ''
        self.term = None

    @parse.default()
    def _parse_term(term):
        s = term.split(':', 1)
        ns = s[0] if len(s) == 2 else ''
        tag = s[1] if len(s) == 2 else term
        operator = ''

        if tag.startswith('<'):
            operator = 'less'
        elif tag.startswith('>'):
            operator = 'great'
        return (ns, tag, operator)

    def main(self, term: str) -> Term:

        self.filter = term

        with self.parse.call(self.filter) as plg:
            t = plg.first_or_default()
            if not len(t) == 3:
                t = plg.default()
            self.term = Term(*t)

        self.parsed.emit(self.term)

        return self.term
Пример #18
0
class PartialModelFilter(Command):
    """
    Perform a partial search on database model with a single term

    Accepts any term

    By default, the following models are supported:

    - User
    - NamespaceTags
    - Tag
    - Namespace
    - Artist
    - Circle
    - Parody
    - Status
    - Grouping
    - Language
    - Category
    - Collection
    - Gallery
    - Title
    - Url

    Args:
        model: a database model item
        term: a single term, like ``rating:5``
        match_options: search options, refer to :ref:`Settings`

    Returns:
        a ``set`` with ids of matched database model items
    """

    models: tuple = CommandEntry("models",
                                 __doc="""
                                 Called to get a tuple of supported database models
                                 """,
                                 __doc_return="""
                                 a tuple of database model items
                                 """)

    match_model: set = CommandEntry("match_model",
                                    CParam("parent_model_name", str, "name of parent database model"),
                                    CParam("child_model_name", str, "name of child database model"),
                                    CParam("term", str, "a single term"),
                                    CParam("options", ChainMap, "search options"),
                                    __capture=(str, "a database model name"),
                                    __doc="""
                                  Called to perform the matching on database items of the given model
                                  """,
                                    __doc_return="""
                                  a ``set`` of ids of the database items that match
                                  """)
    matched = CommandEvent("matched",
                           CParam("matched_ids", set, "a ``set`` of ids of the database items that match"),
                           __doc="""
                           Emitted at the end of the process
                           """
                           )

    def main(self, model: db.Base, term: str, match_options: dict = {}) -> typing.Set[int]:

        self.model = model
        model_name = db.model_name(self.model)
        self.term = term
        with self.models.call() as plg:
            for p in plg.all(default=True):
                self._supported_models.update(p)

        if self.model not in self._supported_models:
            raise exceptions.CommandError(utils.this_command(self),
                                          "Model '{}' is not supported".format(model))

        options = get_search_options(match_options)
        log.d("Match options", options)

        related_models = db.related_classes(model)

        sess = constants.db_session()

        model_count = sess.query(model).count()

        with self.match_model.call_capture(model_name, model_name, model_name, self.term, options) as plg:
            for i in plg.all_or_default():
                self.matched_ids.update(i)
                if len(self.matched_ids) == model_count:
                    break

        has_all = False
        for m in related_models:
            if m in self._supported_models:
                with self.match_model.call_capture(db.model_name(m), model_name, db.model_name(m), self.term, options) as plg:
                    for i in plg.all_or_default():
                        self.matched_ids.update(i)
                        if len(self.matched_ids) == model_count:
                            has_all = True
                            break
            if has_all:
                break

        self.matched.emit(self.matched_ids)

        return self.matched_ids

    def __init__(self):
        super().__init__()
        self.model = None
        self.term = ''
        self._supported_models = set()
        self.matched_ids = set()

    @models.default()
    def _models():
        return (db.Taggable,
                db.Artist,
                db.Circle,
                db.Parody,
                db.Status,
                db.Grouping,
                db.Language,
                db.Category,
                db.Collection,
                db.Gallery,
                db.Title,
                db.Namespace,
                db.Tag,
                db.NamespaceTags,
                db.Url)

    @staticmethod
    def _match_string_column(column, value, options, **kwargs):

        options.update(kwargs)

        expr = None

        if options.get("regex"):
            if options.get("case"):
                expr = column.regexp
            else:
                expr = column.iregexp
        else:
            if not options.get("whole"):
                value = '%' + value + '%'
            if options.get("case"):
                expr = column.like
            else:
                expr = column.ilike

        return expr(value)

    @staticmethod
    def _match_integer_column(column, value, options, **kwargs):

        options.update(kwargs)

        return None

    @match_model.default(capture=True)
    def _match_gallery(parent_model, child_model, term,
                       options, capture=db.model_name(db.Gallery)):
        get_model = database_cmd.GetModelClass()
        parent_model = get_model.run(parent_model)
        child_model = get_model.run(child_model)

        match_string = PartialModelFilter._match_string_column
        match_int = PartialModelFilter._match_integer_column
        term = ParseTerm().run(term)
        ids = set()

        col_on_parent = db.relationship_column(parent_model, child_model)
        s = constants.db_session()
        q = s.query(parent_model.id)
        if col_on_parent:
            q = q.join(col_on_parent)
        if term.namespace:
            lower_ns = term.namespace.lower()
            if lower_ns == 'path':
                ids.update(x[0] for x in q.filter(match_string(db.Gallery.path,
                                                               term.tag,
                                                               options)).all())
            elif lower_ns in ("rating", "stars"):
                ids.update(x[0] for x in q.filter(match_int(db.Gallery.rating,
                                                            term.tag,
                                                            options)).all())

        return ids

    @match_model.default(capture=True)
    def _match_title(parent_model, child_model, term, options,
                     capture=db.model_name(db.Title)):
        get_model = database_cmd.GetModelClass()
        parent_model = get_model.run(parent_model)
        child_model = get_model.run(child_model)

        match_string = PartialModelFilter._match_string_column
        term = ParseTerm().run(term)
        ids = set()

        if term.namespace.lower() == 'title' or not term.namespace:

            col_on_parent = db.relationship_column(parent_model, child_model)

            s = constants.db_session()
            q = s.query(parent_model.id)
            if col_on_parent:
                q = q.join(col_on_parent)
            ids.update(x[0] for x in q.filter(match_string(child_model.name, term.tag, options)).all())
        return ids

    @match_model.default(capture=True)
    def _match_tags(parent_model, child_model, term, options,
                    capture=[db.model_name(x) for x in (db.Taggable, db.NamespaceTags)]):
        get_model = database_cmd.GetModelClass()
        parent_model = get_model.run(parent_model)
        child_model = get_model.run(child_model)

        match_string = PartialModelFilter._match_string_column
        term = ParseTerm().run(term)
        ids = set()

        col_on_parent = db.relationship_column(parent_model, child_model)
        col_on_child = db.relationship_column(child_model, db.NamespaceTags)
        col_tag = db.relationship_column(db.NamespaceTags, db.Tag)
        s = constants.db_session()
        q = s.query(parent_model.id)
        if col_on_parent and parent_model != child_model:
            q = q.join(col_on_parent)
        if col_on_child and parent_model != child_model:
            q = q.join(col_on_child)
        if term.namespace:
            col_ns = db.relationship_column(db.NamespaceTags, db.Namespace)
            items = q.join(col_ns).join(col_tag).filter(db.and_op(
                match_string(db.Namespace.name, term.namespace, options, whole=True),
                match_string(db.Tag.name, term.tag, options))).all()
        else:
            items = q.join(col_tag).filter(
                match_string(db.Tag.name, term.tag, options)).all()

        ids.update(x[0] for x in items)
        return ids

    @match_model.default(capture=True)
    def _match_artist(parent_model, child_model, term, options,
                      capture=db.model_name(db.Artist)):
        get_model = database_cmd.GetModelClass()
        parent_model = get_model.run(parent_model)
        child_model = get_model.run(child_model)

        match_string = PartialModelFilter._match_string_column
        term = ParseTerm().run(term)
        ids = set()

        if term.namespace.lower() == 'artist' or not term.namespace:
            col_on_parent = db.relationship_column(parent_model, child_model)

            s = constants.db_session()
            q = s.query(parent_model.id)
            if col_on_parent:
                q = q.join(col_on_parent)
            ids.update(
                x[0] for x in q.join(
                    child_model.names).filter(
                    match_string(
                        db.ArtistName.name,
                        term.tag,
                        options)).all())
        return ids

    @match_model.default(capture=True)
    def _match_parody(parent_model, child_model, term, options,
                      capture=db.model_name(db.Parody)):
        get_model = database_cmd.GetModelClass()
        parent_model = get_model.run(parent_model)
        child_model = get_model.run(child_model)

        match_string = PartialModelFilter._match_string_column
        term = ParseTerm().run(term)
        ids = set()

        if term.namespace.lower() == 'parody' or not term.namespace:
            col_on_parent = db.relationship_column(parent_model, child_model)

            s = constants.db_session()
            q = s.query(parent_model.id)
            if col_on_parent:
                q = q.join(col_on_parent)
            ids.update(
                x[0] for x in q.join(
                    child_model.names).filter(
                    match_string(
                        db.ParodyName.name,
                        term.tag,
                        options)).all())
        return ids

    @match_model.default(capture=True)
    def _match_namemixin(parent_model, child_model, term, options,
                         capture=[db.model_name(x) for x in _models() if issubclass(x, (db.NameMixin, db.Url))]):
        get_model = database_cmd.GetModelClass()
        parent_model = get_model.run(parent_model)
        child_model = get_model.run(child_model)
        match_string = PartialModelFilter._match_string_column
        term = ParseTerm().run(term)
        ids = set()

        if term.namespace.lower() == child_model.__name__.lower() or not term.namespace:
            col_on_parent = db.relationship_column(parent_model, child_model)
            s = constants.db_session()
            q = s.query(parent_model.id)
            if col_on_parent:
                q = q.join(col_on_parent)
            ids.update(x[0] for x in q.filter(match_string(child_model.name,
                                                           term.tag,
                                                           options)).all())
        return ids
Пример #19
0
class GetModelItems(Command):
    """
    Fetch model items from the database

    Args:
        model: a database model item
        ids: fetch items in this set of item ids or set ``None`` to fetch all
        columns: a tuple of database item columns to fetch
        limit: amount to limit the results, set ``None`` for no limit
        offset: amount to offset the results
        count: only return the count of items
        filter: either a textual SQL criterion or a database criterion expression (can also be a tuple)
        order_by: either a textual SQL criterion or a database model item attribute (can also be a tuple)
        group_by: either a textual SQL criterion or a database model item attribute (can also be a tuple)
        join: either a textual SQL criterion or a database model item attribute (can also be a tuple)
        count: return count of items
        cache: cache results

    Returns:
        a tuple of database model items or ``int`` if ``count`` was set to true
    """

    fetched = CommandEvent(
        "fetched",
        CParam("model_name", str, "name of a database model"),
        CParam("items", tuple, "fetched items"),
        __doc="""
                          Emitted when items were fetched successfully
                          """,
    )

    count = CommandEvent("count",
                         CParam("model_name", str, "name of a database model"),
                         CParam("item_count", int, "count of items"),
                         __doc="""
                        Emitted when query was successful
                        """)

    def __init__(self):
        super().__init__()

        self.fetched_items = tuple()
        self.cache = True

    def _query(self, q, limit, offset):
        if offset:
            q = q.offset(offset)

        if limit:
            q = q.limit(limit)

        self._invalidate_query(q)
        return q.all()

    def _invalidate_query(self, q):
        if self.cache and constants.invalidator.dirty_database:
            q.invalidate()

    def _get_sql(self, expr):
        if isinstance(expr, str):
            return db.sa_text(expr)
        else:
            return expr

    def _get_count(self, q):
        self._invalidate_query(q)
        return q.count()

    def main(self,
             model: db.Base,
             ids: set = None,
             limit: int = 999,
             filter: str = None,
             order_by: str = None,
             group_by: str = None,
             offset: int = 0,
             columns: tuple = tuple(),
             join: str = None,
             count: bool = False,
             cache=True) -> typing.Union[tuple, int]:
        self.cache = cache
        if ids is None:
            log.d("Fetching items", "offset:", offset, "limit:", limit)
        else:
            log.d("Fetching items from a set with", len(ids), "ids", "offset:",
                  offset, "limit:", limit)

        if not offset:
            offset = 0
        if not limit:
            limit = 0

        if (ids is not None and not ids) or\
           (ids and len(ids) == 1 and all(x == 0 for x in ids)):
            return 0 if count else tuple()

        criteria = False

        s = constants.db_session()
        if count:
            q = s.query(model.id)
        elif columns:
            q = s.query(*columns)
        else:
            q = s.query(model)

        if cache:
            q.options(db_cache.FromCache('db'))

        if join is not None:
            criteria = True
            if not isinstance(join, (list, tuple)):
                join = [join]
            for j in join:
                q = q.join(self._get_sql(j))

        if group_by is not None:
            criteria = True
            if not isinstance(group_by, (list, tuple)):
                group_by = [group_by]
            q = q.group_by(*(self._get_sql(g) for g in group_by))

        if order_by is not None:
            criteria = True
            if not isinstance(order_by, (list, tuple)):
                order_by = [order_by]
            q = make_order_by_deterministic(
                q.order_by(*(self._get_sql(o) for o in order_by)))

        if filter is not None:
            criteria = True
            q = q.filter(self._get_sql(filter))

        if ids:
            id_amount = len(ids)
            _max_variables = 900
            if id_amount > _max_variables and config.dialect.value == constants.Dialect.SQLITE:
                if count:
                    fetched_list = [
                        x for x in self._query(q, None, None) if x[0] in ids
                    ]
                else:
                    fetched_list = [
                        x for x in self._query(q, None, None) if x.id in ids
                    ]
                    if not limit:
                        limit = len(fetched_list)
                    fetched_list = fetched_list[offset:][:limit]

                self.fetched_items = tuple(fetched_list) if not count else len(
                    fetched_list)
            elif id_amount == 1 and (not columns and not criteria):
                self._invalidate_query(q)
                self.fetched_items = (q.get(
                    ids.pop()), ) if not count else self._get_count(q)
            else:
                q = q.filter(model.id.in_(ids))
                self.fetched_items = tuple(self._query(
                    q, limit, offset)) if not count else self._get_count(q)
        else:
            self.fetched_items = tuple(self._query(
                q, limit, offset)) if not count else self._get_count(q)

        if count:
            self.count.emit(db.model_name(model), self.fetched_items)
            log.d("Returning items count ", self.fetched_items)
        else:
            self.fetched.emit(db.model_name(model), self.fetched_items)
            self.count.emit(db.model_name(model), len(self.fetched_items))
            log.d("Returning", len(self.fetched_items), "fetched items")
        return self.fetched_items
Пример #20
0
class UpdateApplication(AsyncCommand):
    """
    Check for new release and update the application

    Args:
        download_url: url to file which is to be downloaded, if ``None`` the url will be retrieved with :func:`CheckUpdate`
        restart: call :func:`RestartApplication` when the update has been registered
        silent: supress all errors
        push: push notifications on update

    Returns:
        bool indicating whether the update has been registered or not
    """

    update = CommandEvent(
        "update",
        CParam("status", bool,
               "whether the update has been registered or not"),
        CParam(
            "restart", bool,
            "whether the call :func:`RestartApplication` if the update was registered"
        ),
        __doc="""
                          Emitted at the end of the process
                          """)

    def __init__(self, service=None, priority=constants.Priority.Low):
        return super().__init__(service, priority)

    def main(self,
             download_url: str = None,
             restart: bool = True,
             silent: bool = True,
             push: bool = False) -> bool:
        self.set_progress(type_=enums.ProgressType.UpdateApplication)
        self.set_max_progress(3)
        st = False
        if download_url:
            rel = download_url
        else:
            rel = updater.check_release(silent=silent, cmd=self)
            if rel:
                rel = rel['url']
        self.set_progress(1)
        if rel:
            new_rel = updater.get_release(rel, silent=silent, cmd=self)
            self.set_progress(2)
            if new_rel:
                st = updater.register_release(new_rel['path'],
                                              silent,
                                              restart=restart)
                self.set_progress(3)
                if push:
                    if restart:
                        m = "Restarting and installing new update..."
                    else:
                        m = "The update will be installed on the next startup"
                    msg = message.Notification(
                        m, "A new update is pending to be installed")
                    msg.id = constants.PushID.Update.value
                    self.push(msg)
        self.update.emit(st, restart)
        return st