Пример #1
0
    def mainLoop(self):

        logging.debug("Monitor: started main loop.")
        self.session = self.dm.Session()
        self.library = Library(self.dm.Session)

        observer = Observer()
        self.eventHandler = MonitorEventHandler(self)
        for path in self.paths:
            if os.path.exists(path):
                observer.schedule(self.eventHandler, path, recursive=True)
        observer.start()

        while True:
            try:
                (msg, args) = self.queue.get(block=True, timeout=1)
            except:
                msg = None

            #dispatch messages
            if msg == "scan":
                self.dofullScan(self.paths)

            if msg == "events":
                self.doEventProcessing(args)

            #time.sleep(1)
            if self.quit:
                break

        self.session.close()
        self.session = None
        observer.stop()
        logging.debug("Monitor: stopped main loop.")
Пример #2
0
    def mainLoop(self):
        try:
            global args
            logging.debug("Monitor: started main loop.")
            self.session = self.dm.Session()
            self.library = Library(self.dm.Session)

            observer = Observer()
            self.eventHandler = MonitorEventHandler(self)
            for path in self.paths:
                if os.path.exists(path):
                    observer.schedule(self.eventHandler, path, recursive=True)
            observer.start()

            while True:
                self._loop()
                # time.sleep(1)
                if self.quit:
                    break

            self.session.close()
            self.session = None
            observer.stop()
            logging.debug("Monitor: stopped main loop.")
        except Exception as e:
            logging.exception(e)
Пример #3
0
    def mainLoop(self):

        logging.debug("Monitor: started main loop.")
        self.session = self.dm.Session()
        self.library = Library(self.dm.Session)

        observer = Observer()
        self.eventHandler = MonitorEventHandler(self)
        for path in self.paths:
            if os.path.exists(path):
                observer.schedule(self.eventHandler, path, recursive=True)
        observer.start()

        while True:
            try:
                (msg, args) = self.queue.get(block=True, timeout=1)
            except:
                msg = None

            #dispatch messages
            if msg == "scan":
                self.dofullScan(self.paths)

            if msg == "events":
                self.doEventProcessing(args)

            #time.sleep(1)
            if self.quit:
                break

        self.session.close()
        self.session = None
        observer.stop()
        logging.debug("Monitor: stopped main loop.")
Пример #4
0
class Monitor():
    def __init__(self, dm, paths):

        self.dm = dm
        self.style = MetaDataStyle.CIX
        self.queue = queue.Queue(0)
        self.paths = paths
        self.eventList = []
        self.mutex = threading.Lock()
        self.eventProcessingTimer = None
        self.quit_when_done = False  # for debugging/testing
        self.status = "IDLE"
        self.statusdetail = ""
        self.scancomplete_ts = ""

    def start(self):
        self.thread = threading.Thread(target=self.mainLoop)
        self.thread.daemon = True
        self.quit = False
        self.thread.start()

    def stop(self):
        self.quit = True
        self.thread.join()

    def mainLoop(self):

        logging.debug("Monitor: started main loop.")
        self.session = self.dm.Session()
        self.library = Library(self.dm.Session)

        observer = Observer()
        self.eventHandler = MonitorEventHandler(self)
        for path in self.paths:
            if os.path.exists(path):
                observer.schedule(self.eventHandler, path, recursive=True)
        observer.start()

        while True:
            try:
                (msg, args) = self.queue.get(block=True, timeout=1)
            except:
                msg = None

            #dispatch messages
            if msg == "scan":
                self.dofullScan(self.paths)

            if msg == "events":
                self.doEventProcessing(args)

            #time.sleep(1)
            if self.quit:
                break

        self.session.close()
        self.session = None
        observer.stop()
        logging.debug("Monitor: stopped main loop.")

    def scan(self):
        self.queue.put(("scan", None))

    def handleSingleEvent(self, event):
        # events may happen in clumps.  start a timer
        # to defer processing.  if the timer is already going,
        # it will be canceled

        # in the future there can be more smarts about
        # granular file events.  for now this will be
        # good enough to just get a a trigger that *something*
        # changed

        self.mutex.acquire()

        if self.eventProcessingTimer is not None:
            self.eventProcessingTimer.cancel()
        self.eventProcessingTimer = threading.Timer(30,
                                                    self.handleEventProcessing)
        self.eventProcessingTimer.start()

        self.mutex.release()

    def handleEventProcessing(self):

        # trigger a full rescan
        self.mutex.acquire()

        self.scan()

        # remove the timer
        if self.eventProcessingTimer is not None:
            self.eventProcessingTimer = None

        self.mutex.release()

    def checkIfRemovedOrModified(self, comic, pathlist):
        remove = False

        def inFolderlist(filepath, pathlist):
            for p in pathlist:
                if p in filepath:
                    return True
            return False

        if not (os.path.exists(comic.path)):
            # file is missing, remove it from the comic table, add it to deleted table
            logging.debug(u"Removing missing {0}".format(comic.path))
            remove = True
        elif not inFolderlist(comic.path, pathlist):
            logging.debug(u"Removing unwanted {0}".format(comic.path))
            remove = True
        else:
            # file exists.  check the mod date.
            # if it's been modified, remove it, and it'll be re-added
            #curr = datetime.datetime.fromtimestamp(os.path.getmtime(comic.path))
            curr = datetime.utcfromtimestamp(os.path.getmtime(comic.path))
            prev = comic.mod_ts
            if curr != prev:
                logging.debug(u"Removed modifed {0}".format(comic.path))
                remove = True

        return remove

    def getComicMetadata(self, path):

        ca = ComicArchive(
            path, default_image_path=AppFolders.imagePath("default.jpg"))

        if ca.seemsToBeAComicArchive():
            logging.debug(u"Reading in {0} {1}\r".format(
                self.read_count, path))
            sys.stdout.flush()
            self.read_count += 1

            if ca.hasMetadata(MetaDataStyle.CIX):
                style = MetaDataStyle.CIX
            elif ca.hasMetadata(MetaDataStyle.CBI):
                style = MetaDataStyle.CBI
            else:
                style = None

            if style is not None:
                md = ca.readMetadata(style)
            else:
                # No metadata in comic.  make some guesses from the filename
                md = ca.metadataFromFilename()

            md.path = ca.path
            md.page_count = ca.page_count
            md.mod_ts = datetime.utcfromtimestamp(os.path.getmtime(ca.path))
            md.filesize = os.path.getsize(md.path)
            md.hash = ""

            #thumbnail generation
            image_data = ca.getPage(0)
            #now resize it
            thumb = BytesIO()
            comicstreamerlib.utils.resize(image_data, (200, 200), thumb)
            md.thumbnail = thumb.getvalue()

            return md
        return None

    def setStatusDetail(self, detail, level=logging.DEBUG):
        self.statusdetail = detail
        if level == logging.DEBUG:
            logging.debug(detail)
        else:
            logging.info(detail)

    def setStatusDetailOnly(self, detail):
        self.statusdetail = detail

    def commitMetadataList(self, md_list):
        comics = []
        for md in md_list:
            self.add_count += 1
            comic = self.library.createComicFromMetadata(md)
            comics.append(comic)
            if self.quit:
                self.setStatusDetail(u"Monitor: halting scan!")
                return

        self.library.addComics(comics)

    def createAddRemoveLists(self, dirs):
        ix = {}
        db_set = set()
        current_set = set()
        filelist = comicstreamerlib.utils.get_recursive_filelist(dirs)
        for path in filelist:
            current_set.add(
                (path, datetime.utcfromtimestamp(os.path.getmtime(path))))
        logging.info("NEW -- current_set size [%d]" % len(current_set))

        for comic_id, path, md_ts in self.library.getComicPaths():
            db_set.add((path, md_ts))
            ix[path] = comic_id
        to_add = current_set - db_set
        to_remove = db_set - current_set
        logging.info("NEW -- db_set size [%d]" % len(db_set))
        logging.info("NEW -- to_add size [%d]" % len(to_add))
        logging.info("NEW -- to_remove size [%d]" % len(to_remove))

        return [r[0] for r in to_add], [ix[r[0]] for r in to_remove]

    def dofullScan(self, dirs):

        self.status = "SCANNING"

        logging.info(u"Monitor: Beginning file scan...")
        self.setStatusDetail(
            u"Monitor: Making a list of all files in the folders...")

        self.add_count = 0
        self.remove_count = 0

        filelist, to_remove = self.createAddRemoveLists(dirs)

        self.setStatusDetail(
            u"Monitor: Removing missing or modified files from db ({0} files)".
            format(len(to_remove)), logging.INFO)
        if len(to_remove) > 0:
            self.library.deleteComics(to_remove)

        self.setStatusDetail(u"Monitor: {0} new files to scan...".format(
            len(filelist)), logging.INFO)

        md_list = []
        self.read_count = 0
        for filename in filelist:
            md = self.getComicMetadata(filename)
            if md is not None:
                md_list.append(md)
            self.setStatusDetailOnly(
                u"Monitor: {0} files: {1} scanned, {2} added to library...".
                format(len(filelist), self.read_count, self.add_count))
            if self.quit:
                self.setStatusDetail(u"Monitor: halting scan!")
                return

            #every so often, commit to DB
            if self.read_count % 10 == 0 and self.read_count != 0:
                if len(md_list) > 0:
                    self.commitMetadataList(md_list)
                    md_list = []

        if len(md_list) > 0:
            self.commitMetadataList(md_list)

        self.setStatusDetail(
            u"Monitor: finished scanning metadata in {0} of {1} files".format(
                self.read_count, len(filelist)), logging.INFO)

        self.status = "IDLE"
        self.statusdetail = ""
        self.scancomplete_ts = int(
            time.mktime(datetime.utcnow().timetuple()) * 1000)

        logging.info("Monitor: Added {0} comics".format(self.add_count))
        logging.info("Monitor: Removed {0} comics".format(self.remove_count))

        if self.quit_when_done:
            self.quit = True

    def doEventProcessing(self, eventList):
        logging.debug(u"Monitor: event_list:{0}".format(eventList))
Пример #5
0
class Monitor():
    def __init__(self, dm, paths):

        self.dm = dm
        self.style = MetaDataStyle.CIX
        self.queue = queue.Queue(0)
        self.paths = paths
        self.eventList = []
        self.mutex = threading.Lock()
        self.eventProcessingTimer = None
        self.quit_when_done = False  # for debugging/testing
        self.status = "IDLE"
        self.statusdetail = ""
        self.scancomplete_ts = ""

    def start(self):
        self.thread = threading.Thread(target=self.mainLoop)
        self.thread.daemon = True
        self.quit = False
        self.thread.start()

    def stop(self):
        self.quit = True
        self.thread.join()

    def mainLoop(self):

        logging.debug("Monitor: started main loop.")
        self.session = self.dm.Session()
        self.library = Library(self.dm.Session)

        observer = Observer()
        self.eventHandler = MonitorEventHandler(self)
        for path in self.paths:
            if os.path.exists(path):
                observer.schedule(self.eventHandler, path, recursive=True)
        observer.start()

        while True:
            try:
                (msg, args) = self.queue.get(block=True, timeout=1)
            except:
                msg = None

            #dispatch messages
            if msg == "scan":
                self.dofullScan(self.paths)

            if msg == "events":
                self.doEventProcessing(args)

            #time.sleep(1)
            if self.quit:
                break

        self.session.close()
        self.session = None
        observer.stop()
        logging.debug("Monitor: stopped main loop.")

    def scan(self):
        self.queue.put(("scan", None))

    def handleSingleEvent(self, event):
        # events may happen in clumps.  start a timer
        # to defer processing.  if the timer is already going,
        # it will be canceled

        # in the future there can be more smarts about
        # granular file events.  for now this will be
        # good enough to just get a a trigger that *something*
        # changed

        self.mutex.acquire()

        if self.eventProcessingTimer is not None:
            self.eventProcessingTimer.cancel()
        self.eventProcessingTimer = threading.Timer(30,
                                                    self.handleEventProcessing)
        self.eventProcessingTimer.start()

        self.mutex.release()

    def handleEventProcessing(self):

        # trigger a full rescan
        self.mutex.acquire()

        self.scan()

        # remove the timer
        if self.eventProcessingTimer is not None:
            self.eventProcessingTimer = None

        self.mutex.release()

    def checkIfRemovedOrModified(self, comic, pathlist):
        remove = False

        def inFolderlist(filepath, pathlist):
            for p in pathlist:
                if p in filepath:
                    return True
            return False

        if not (os.path.exists(comic.path)):
            # file is missing, remove it from the comic table, add it to deleted table
            logging.debug(u"Removing missing {0}".format(comic.path))
            remove = True
        elif not inFolderlist(comic.path, pathlist):
            logging.debug(u"Removing unwanted {0}".format(comic.path))
            remove = True
        else:
            # file exists.  check the mod date.
            # if it's been modified, remove it, and it'll be re-added
            #curr = datetime.datetime.fromtimestamp(os.path.getmtime(comic.path))
            curr = datetime.utcfromtimestamp(os.path.getmtime(comic.path))
            prev = comic.mod_ts
            if curr != prev:
                logging.debug(u"Removed modifed {0}".format(comic.path))
                remove = True

        return remove

    def getComicMetadata(self, path):

        ca = ComicArchive(
            path, default_image_path=AppFolders.imagePath("default.jpg"))

        if ca.seemsToBeAComicArchive():
            logging.debug(u"Reading in {0} {1}\r".format(
                self.read_count, path))
            sys.stdout.flush()
            self.read_count += 1

            if ca.hasMetadata(MetaDataStyle.CIX):
                style = MetaDataStyle.CIX
            elif ca.hasMetadata(MetaDataStyle.CBI):
                style = MetaDataStyle.CBI
            else:
                style = None

            if style is not None:
                md = ca.readMetadata(style)
            else:
                # No metadata in comic.  make some guesses from the filename
                md = ca.metadataFromFilename()

            md.path = ca.path
            md.page_count = ca.page_count
            md.mod_ts = datetime.utcfromtimestamp(os.path.getmtime(ca.path))
            md.filesize = os.path.getsize(md.path)
            md.hash = ""

            #thumbnail generation
            image_data = ca.getPage(0)
            #now resize it
            thumb = BytesIO()
            comicstreamerlib.utils.resize(image_data, (200, 200), thumb)
            md.thumbnail = thumb.getvalue()

            return md
        return None

    def setStatusDetail(self, detail, level=logging.DEBUG):
        self.statusdetail = detail
        if level == logging.DEBUG:
            logging.debug(detail)
        else:
            logging.info(detail)

    def setStatusDetailOnly(self, detail):
        self.statusdetail = detail

    def commitMetadataList(self, md_list):
        comics = []
        for md in md_list:
            self.add_count += 1
            comic = self.library.createComicFromMetadata(md)
            comics.append(comic)
            if self.quit:
                self.setStatusDetail(u"Monitor: halting scan!")
                return

        self.library.addComics(comics)

    def createAddRemoveLists(self, dirs):
        ix = {}
        db_set = set()
        current_set = set()
        filelist = comicstreamerlib.utils.get_recursive_filelist(dirs)
        for path in filelist:
            current_set.add(
                (path, datetime.utcfromtimestamp(os.path.getmtime(path))))
        logging.info("NEW -- current_set size [%d]" % len(current_set))

        for comic_id, path, md_ts in self.library.getComicPaths():
            db_set.add((path, md_ts))
            ix[path] = comic_id
        to_add = current_set - db_set
        to_remove = db_set - current_set
        logging.info("NEW -- db_set size [%d]" % len(db_set))
        logging.info("NEW -- to_add size [%d]" % len(to_add))
        logging.info("NEW -- to_remove size [%d]" % len(to_remove))

        return [r[0] for r in to_add], [ix[r[0]] for r in to_remove]

    def dofullScan(self, dirs):

        self.status = "SCANNING"

        logging.info(u"Monitor: Beginning file scan...")
        self.setStatusDetail(
            u"Monitor: Making a list of all files in the folders...")

        self.add_count = 0
        self.remove_count = 0

        filelist, to_remove = self.createAddRemoveLists(dirs)

        self.setStatusDetail(
            u"Monitor: Removing missing or modified files from db ({0} files)".
            format(len(to_remove)), logging.INFO)
        if len(to_remove) > 0:
            self.library.deleteComics(to_remove)

        self.setStatusDetail(
            u"Monitor: {0} new files to scan...".format(len(filelist)),
            logging.INFO)

        md_list = []
        self.read_count = 0
        for filename in filelist:
            md = self.getComicMetadata(filename)
            if md is not None:
                md_list.append(md)
            self.setStatusDetailOnly(
                u"Monitor: {0} files: {1} scanned, {2} added to library...".
                format(len(filelist), self.read_count, self.add_count))
            if self.quit:
                self.setStatusDetail(u"Monitor: halting scan!")
                return

            #every so often, commit to DB
            if self.read_count % 10 == 0 and self.read_count != 0:
                if len(md_list) > 0:
                    self.commitMetadataList(md_list)
                    md_list = []

        if len(md_list) > 0:
            self.commitMetadataList(md_list)

        self.setStatusDetail(
            u"Monitor: finished scanning metadata in {0} of {1} files".format(
                self.read_count, len(filelist)), logging.INFO)

        self.status = "IDLE"
        self.statusdetail = ""
        self.scancomplete_ts = int(
            time.mktime(datetime.utcnow().timetuple()) * 1000)

        logging.info("Monitor: Added {0} comics".format(self.add_count))
        logging.info("Monitor: Removed {0} comics".format(self.remove_count))

        if self.quit_when_done:
            self.quit = True

    def doEventProcessing(self, eventList):
        logging.debug(u"Monitor: event_list:{0}".format(eventList))
Пример #6
0
    def __init__(self, config, opts):
        comicstreamerlib.utils.fix_output_encoding()

        self.config = config
        self.opts = opts

        self.port = self.config['general']['port']
        self.webroot = self.config['general']['webroot']

        self.comicArchiveList = []

        # if len(self.config['general']['folder_list']) == 0:
        #    logging.error("No folders on either command-line or config file.  Quitting.")
        #    sys.exit(-1)

        self.dm = DataManager()
        self.library = Library(self.dm.Session)

        if opts.reset or opts.reset_and_run:
            logging.info("Deleting any existing database!")
            self.dm.delete()

        # quit on a standard reset
        if opts.reset:
            sys.exit(0)

        try:
            self.dm.create()
        except SchemaVersionException:
            msg = "Couldn't open database.  Probably the schema has changed."
            logging.error(msg)
            comicstreamerlib.utils.alert("Schema change", msg)
            sys.exit(-1)

        try:
            self.listen(self.port, no_keep_alive=True)
        except Exception as e:
            logging.error(e)
            msg = "Couldn't open socket on port {0}. (Maybe ComicStreamer is already running?) Quitting.".format(
                self.port)
            logging.error(msg)
            comicstreamerlib.utils.alert("Port not available", msg)
            sys.exit(-1)

        logging.info("Stream server running on port {0}...".format(self.port))

        # http_server = tornado.httpserver.HTTPServer(self, no_keep_alive = True, ssl_options={
        #    "certfile": "server.crt",
        #    "keyfile": "server.key",
        # })
        # http_server.listen(port+1)

        self.version = comicstreamerlib.csversion.version

        handlers = [
            # Web Pages
            (self.webroot + r"/", MainHandler),
            (self.webroot + r"/(.*)\.html", GenericPageHandler),
            (self.webroot + r"/about", AboutPageHandler),
            (self.webroot + r"/control", ControlPageHandler),
            (self.webroot + r"/configure", ConfigPageHandler),
            (self.webroot + r"/log", LogPageHandler),
            (self.webroot + r"/comiclist/browse", ComicListBrowserHandler),
            (self.webroot + r"/folders/browse(/.*)*", FoldersBrowserHandler),
            (self.webroot + r"/entities/browse(/.*)*", EntitiesBrowserHandler),
            (self.webroot + r"/comic/([0-9]+)/reader", ReaderHandler),
            (self.webroot + r"/login", LoginHandler),
            # Data
            (self.webroot + r"/dbinfo", DBInfoAPIHandler),
            (self.webroot + r"/version", VersionAPIHandler),
            (self.webroot + r"/deleted", DeletedAPIHandler),
            (self.webroot + r"/comic/([0-9]+)", ComicAPIHandler),
            (self.webroot + r"/comiclist", ComicListAPIHandler),
            (self.webroot + r"/comic/([0-9]+)/page/([0-9]+|clear)/bookmark", ComicBookmarkAPIHandler),
            (self.webroot + r"/comic/([0-9]+)/page/([0-9]+)", ComicPageAPIHandler),
            (self.webroot + r"/comic/([0-9]+)/thumbnail", ThumbnailAPIHandler),
            (self.webroot + r"/comic/([0-9]+)/file", FileAPIHandler),
            (self.webroot + r"/entities(/.*)*", EntityAPIHandler),
            (self.webroot + r"/folders(/.*)*", FolderAPIHandler),
            (self.webroot + r"/command", CommandAPIHandler),
            (self.webroot + r"/scanstatus", ScanStatusAPIHandler),
            # (r'/favicon.ico', tornado.web.StaticFileHandler, {'path': os.path.join(AppFolders.appBase(), "static","images")}),
            (self.webroot + r'/.*', UnknownHandler),
        ]

        settings = dict(
            template_path=os.path.join(AppFolders.appBase(), "templates"),
            static_path=os.path.join(AppFolders.appBase(), "static"),
            static_url_prefix=self.webroot + "/static/",
            debug=True,
            # autoreload=False,
            login_url=self.webroot + "/login",
            cookie_secret=self.config['security']['cookie_secret'],
            xsrf_cookies=True,
        )

        tornado.web.Application.__init__(self, handlers, **settings)

        if not opts.no_monitor:
            logging.debug("Going to scan the following folders:")
            for l in self.config['general']['folder_list']:
                logging.debug(u"   {0}".format(repr(l)))

            self.monitor = Monitor(self.dm,
                                   self.config['general']['folder_list'])
            self.monitor.start()
            self.monitor.scan()

        self.bookmarker = Bookmarker(self.dm)
        self.bookmarker.start()

        if opts.launch_browser and self.config['general']['launch_browser']:
            if ((platform.system() == "Linux" and ('DISPLAY' in os.environ))
                    or (platform.system() == "Darwin"
                        and not ('SSH_TTY' in os.environ))
                    or platform.system() == "Windows"):
                webbrowser.open(
                    "http://localhost:{0}".format(self.port), new=0)