Esempio n. 1
0
 def __init__(self, **kwargs):
     super(DownloadBookTask, self).__init__(**kwargs)
     self._book = None
     self._priority = 'medium'
     self._library = kwargs['library']
     self.identifier = kwargs['identifier']
     self.logger.info('Download books: Downloading {}'.format(
         self.identifier))
     self.notifications_manager = NotificationManager()
     self._download_type = None
Esempio n. 2
0
class NotificationsCleanerTask(TaskBase):

    def __init__(self, **kwargs):
        super(NotificationsCleanerTask, self).__init__(**kwargs)
        self._notification_manager = NotificationManager()

    def create_pipeline(self):
        return [
            self._clean_expired_notifications,
        ]

    def _clean_expired_notifications(self):
        for notification in self._notification_manager.get_all_notifications():
            if notification.is_expired() and not notification.is_sticky:
                self._notification_manager.remove_notification(notification)
Esempio n. 3
0
    def __init__(self, **kwargs):
        self._help_opened = False
        self._help_key_down = False
        self.timeout_event = None
        super(ScribeWidget, self).__init__(**kwargs)

        try:
            self.config = Scribe3Configuration()
            self.config.validate()
            self.init_pid_file()

        except CredentialsError as e:
            # if this is raised, we show the wizard
            msg = '[b]It looks like there is an issue with your credentials[/b].\n' \
                  '\n{}\nClick the button to begin the wizard.'.format(e.msg)
            popup = InfoPopup(title='Logged out', message=msg, auto_dismiss=False)
            popup.bind(on_submit=self.begin_wizard)
            popup.open()
            return

        except ScribeException as e:
            # if this is raised, the app can no longer continue
            msg = 'Scribe3 could not initialize\nbecause of the following error:' \
                  '\n\n[b]{}[/b].\n\nClick the button to close.'.format(str(e))
            popup = InfoPopup(title='Initialization error', message=msg, auto_dismiss=False)
            app = App.get_running_app()
            popup.bind(on_submit=app.stop)
            popup.open()
            return

        #self.config.subscribe(get_adapter('config'))

        md_version = {'tts_version': scribe_globals.BUILD_NUMBER}
        set_sc_metadata(md_version)

        screen_manager = self.ids._screen_manager
        screen_manager.bind(current=self._on_current_screen)

        self._init_top_bar(self.config)
        self.help = Help()

        self.help.fbind('on_open', self._on_help_open)
        self.help.fbind('on_dismiss', self._on_help_dismiss)

        self.notifications_manager = NotificationManager()

        Clock.schedule_once(self._postponed_init, -1)
Esempio n. 4
0
)

from ia_scribe.tasks.book_tasks.petabox import get_pending_catalog_tasks
from ia_scribe.book.scandata import ScanData
from ia_scribe.ia_services.iabdash import push_event
from ia_scribe import scribe_globals
from ia_scribe.config.config import Scribe3Configuration
from ia_scribe.exceptions import ScribeException
from ia_scribe.book.upload_status import UploadStatus
from ia_scribe.ia_services.btserver import get_ia_session
from ia_scribe.notifications.notifications_manager import NotificationManager

from os.path import join

config = Scribe3Configuration()
notifications_manager = NotificationManager()


def _get_email():
    return config.get('email')


def _set_upload_lock_file(book, Logger):
    Logger.debug('Setting upload lock file')
    with open(join(book['path'], "upload_lock"), 'w+') as fp:
        pid_path = os.path.join(scribe_globals.CONFIG_DIR, 'scribe_pid')
        with open(pid_path) as f:
            pid_num = str(f.read().strip())
            fp.write(pid_num)

Esempio n. 5
0
 def __init__(self, **kwargs):
     super(NotificationsCleanerTask, self).__init__(**kwargs)
     self._notification_manager = NotificationManager()
Esempio n. 6
0
from ia_scribe.config.config import Scribe3Configuration
from ia_scribe.notifications.notifications_manager import NotificationManager

nm = NotificationManager()
fun = nm.add_notification

config = Scribe3Configuration()

LIBRARY_EVENTS = [
    'book_created',
    'book_deleted',
]
BOOK_EVENTS = [
    'identifier-changed',
    'state_change',
    'book_update',
]
IGNORE_EVENTS = [
    'message-updated',
    'reloaded_metadata',
    'reloaded_scandata',
]


def book_adapter(book, event_type):
    if event_type in IGNORE_EVENTS:
        return
    elif event_type in BOOK_EVENTS:
        if config.is_true('show_book_notifications'):
            fun(title='{}'.format(book.name_human_readable()),
                message='{}'.format(book.last_activity),
Esempio n. 7
0
class NotificationCenterApp(App):
    nm = ObjectProperty()

    def build(self):
        root = NotificationCenterWidget(
                                        pos_hint={'x': 0.0, 'center_y': 0.5},
                                        size_hint=(1.0, 1.0)
                                        )
        return root

    def on_start(self):
        super(NotificationCenterApp, self).on_start()
        self.root_window.size = (1000, 600)
        self.nm = NotificationManager()
        self.create_dummy_notifications()
        self.root.attach(self.nm, book_handler=self.book_handler)

    @staticmethod
    def book_handler(*args):
        pass

    def create_dummy_notifications(self):
        self.nm.add_notification(title='Test 1', message='Test notification ' * 20)
        self.nm.add_notification(title='Test 1', message='')
        self.nm.add_notification(title='Command and control', message='I have connected')
        self.nm.add_notification(title='Command and control', message='I have Joined a channel')
        self.nm.add_notification(title='Command and control', message='I have done something else')
        self.nm.add_notification(title='Command and control', message='I have just received a message from someone')
        self.nm.add_notification(title='A rather long title for a notification, that is for sure',
                                 message='I have just recieved a message from Central Command')

        self.nm.add_notification(title='Test 2', message='This is a book notification' * 10, book=object)
        self.nm.add_notification(title='Test 3', message='This is a system error notification' * 30, is_error=True)
        self.nm.add_notification(title='Test 4', message='This is a book error notification',  book=object, is_error=True)
        for i in range(1, 30):
            self.nm.add_notification(title='AUtomated test notification number {}'.format(i), message='Automated notification')
        msg = '''Distinctio rem tenetur cumque. Est dicta dolores necessitatibus laudantium. Optio consequatur provident voluptatibus. Quia architecto debitis error. Eveniet expedita quia odio culpa. Cumque inventore voluptate reprehenderit. Repellat aut molestias natus. Magnam nihil nam delectus sit distinctio earum non fuga. Est fugit quia a architecto sit expedita. Non voluptas dolores voluptatem quia enim. Est autem ut ratione modi nisi. Debitis dolorem quae vero qui aut laboriosam. Non dolorem sit suscipit eligendi. Vel nihil consequatur numquam. Tenetur est placeat dignissimos voluptas corrupti voluptatem doloribus. Sed eligendi ducimus eveniet itaque dolorem eum sunt. Similique et suscipit ut perspiciatis et omnis. Itaque occaecati commodi rerum doloremque non quis ut voluptatem. Harum tempore officiis et atque animi possimus. Et reiciendis quis molestiae qui voluptatem adipisci aperiam. Dolorem natus officia impedit. Cupiditate aspernatur doloremque harum ex reiciendis est. '''
        self.nm.add_notification(title='Sticky notifcation', message=msg, is_sticky=True)
Esempio n. 8
0
 def on_start(self):
     super(NotificationCenterApp, self).on_start()
     self.root_window.size = (1000, 600)
     self.nm = NotificationManager()
     self.create_dummy_notifications()
     self.root.attach(self.nm, book_handler=self.book_handler)
Esempio n. 9
0
class DownloadCDTask(TaskBase):
    def __init__(self, **kwargs):
        super(DownloadCDTask, self).__init__(**kwargs)
        self._cd = None
        self._priority = 'medium'
        self._library = kwargs['library']
        self.identifier = kwargs['identifier']
        self.logger.info('Download CD: Downloading {}'.format(self.identifier))
        self.notifications_manager = NotificationManager()
        self._download_type = None
        self.start_time = None
        self.total_time = None

    def create_pipeline(self):
        return [
            self._begin,
            self._get_ia_session,
            self._load_item,
            self._verify_is_CD,
            self._verify_is_stub_item,
            self._create_stub_CD,
            self._create_files,
            self._create_scandata,
            self._set_states,
            self._release_lock,
            self._send_stats,
        ]

    def _begin(self):
        self.start_time = time.time()

    def handle_event(self, event_name, *args, **kwargs):
        if event_name == 'on_state' and self.state == CANCELLED_WITH_ERROR:
            if self._cd:
                self._cd.do_move_to_trash()
                self._cd.do_delete_anyway()

    def _get_ia_session(self):
        self.dispatch_progress('Getting IA session')
        self._ia_session = get_ia_session()

    def _load_item(self):
        self.dispatch_progress('Loading item')
        self.item = self._ia_session.get_item(self.identifier)
        self.logger.info('Download CD: target item: {}'.format(
            self.item.identifier))

    def _verify_is_CD(self):
        self.dispatch_progress('Verifying this is an ArchiveCD item')
        mediatype = self.item.metadata.get('mediatype')
        software_version = self.item.metadata.get('software_version')
        assert mediatype == 'audio', 'This is not an audio item. It is {}.'.format(
            mediatype)
        assert software_version is not None, 'This item was not created with ArchiveCD'
        assert 'ArchiveCD' in software_version, 'This item was not created with ArchiveCD'

    def _verify_is_stub_item(self):
        self.dispatch_progress('Verifying this is a stub item')
        stub_file = self.item.get_file('stub.txt')
        #if not stub_file.exists:
        #    raise Exception('No stub file found!')

    def _create_stub_CD(self):
        self.dispatch_progress('Creating local CD')
        message = "This CD is being downloaded and no actions are available just yet."
        cd_id = str(uuid4())
        self._cd = self._library.new_cd(cd_id,
                                        status='download_incomplete',
                                        error=message)
        self._cd.set_lock()
        self._cd.logger.info('Download CD: Created stub CD {}'.format(
            self._cd))

    def _create_files(self):
        self.dispatch_progress('Downloading files')
        ret = []
        with open(os.path.join(self._cd.path, 'identifier.txt'), 'w+') as fp:
            fp.write(self.item.identifier)
        ret.append(fp.name)
        self._cd.logger.info('Download CD: Created {}'.format(fp.name))

        with open(os.path.join(self._cd.path, 'downloaded'), 'w+') as fp:
            fp.write('True')
            ret.append(fp.name)
        self._cd.logger.info('Download CD: Created {}'.format(fp.name))

        with open(os.path.join(self._cd.path, 'uuid'), 'w+') as fp:
            fp.write(self._cd.uuid)
        ret.append(fp.name)
        self._cd.logger.info('Download CD: Created {}'.format(fp.name))

        self.item.get_file(self.item.identifier + '_meta.xml') \
            .download(file_path=self._cd.path + '/metadata.xml')
        ret.append('{}'.format(self._cd.path + '/metadata.xml'))
        self._cd.logger.info('Download CD: Created metadata.xml')
        self._cd.reload_metadata()

        if not os.path.exists(os.path.join(self._cd.path, 'thumbs')):
            os.makedirs(os.path.join(self._cd.path, 'thumbs'))
            ret.append('{}'.format(self._cd.path + '/thumbs'))
        self._cd.logger.info('Download CD: Created thumbs directory')

        self.item.get_file(self.item.identifier + '_itemimage.png') \
            .download(file_path=self._cd.path + '/cover.png')
        ret.append('{}'.format(self._cd.path + '/cover.png'))
        self._cd.logger.info('Download CD: Downloaded cover')

        self._files = ret

        self._cd.logger.info('Download CD: Created files.')

    def _create_scandata(self):
        self.dispatch_progress('Creating scandata')

        self._scandata=\
            ScanData(self._cd.path)
        self._scandata.save()
        self._cd.reload_scandata()

        self._cd.logger.info('Download CD: Created scandata.')

    def _set_states(self):
        self.dispatch_progress('Setting states')
        self._cd.do_finish_download()

    def _send_stats(self):
        self.dispatch_progress('Notifying iabdash')
        payload = {
            'files': self._files,
            'total_time': self.total_time,
        }
        push_event('tts-cd-downloaded', payload, 'cd', self.identifier,
                   os.path.join(self._cd.path, "iabdash.log"))

        self.notifications_manager.add_notification(
            title='Downloaded',
            message="CD {} has been downloaded.".format(self.identifier),
            show_system_tile=False,
            book=self._cd)

    def _release_lock(self):
        self.total_time = time.time() - self.start_time
        self._cd.logger.info('Download CD: ------ DONE. Downloaded {0} in '
                             '{1}s ----------'.format(self.identifier,
                                                      self.total_time))
        self._cd.release_lock()
Esempio n. 10
0
class DownloadBookTask(TaskBase):
    def __init__(self, **kwargs):
        super(DownloadBookTask, self).__init__(**kwargs)
        self._book = None
        self._priority = 'medium'
        self._library = kwargs['library']
        self.identifier = kwargs['identifier']
        self.logger.info('Download books: Downloading {}'.format(
            self.identifier))
        self.notifications_manager = NotificationManager()
        self._download_type = None

    def create_pipeline(self):
        return [
            self._get_ia_session,
            self._load_item,
            self._validate_repub_state,
            self._create_stub_book,
            self._load_book_metadata,
            self._create_files,
            self._create_scandata,
            self._get_checkout_information,
            self._write_claimer_file,
            self._download_proxies,
            self._set_states,
            self._release_lock,
            self._send_stats,
        ]

    def handle_event(self, event_name, *args, **kwargs):
        if event_name == 'on_state' and self.state == CANCELLED_WITH_ERROR:
            if self._book:
                self._book.do_move_to_trash()
                self._book.do_delete_anyway()

    def _get_ia_session(self):
        self.dispatch_progress('Getting IA session')
        self._ia_session = get_ia_session()

    def _load_item(self):
        self.dispatch_progress('Loading item')
        try:
            self.item = self._ia_session.get_item(self.identifier)
            assert self.item.metadata['repub_state'] is not None
        except Exception as e:
            self.logger.error('No repub_state or item darkened. Skipping...')
            raise e
        self.logger.info(
            'Download book: target item: {} (repub_state = {})'.format(
                self.item.identifier, self.item.metadata['repub_state']))

    def _validate_repub_state(self):
        self.dispatch_progress('Validating repub state')
        is_repub_state_valid = lambda x: int(x.metadata[
            'repub_state']) in scribe_globals.ALLOWED_DOWNLOAD_REPUB_STATES
        self.logger.info('Validating repub_state {}'.format(
            int(self.item.metadata['repub_state'])))

        if not is_repub_state_valid(self.item):
            msg = 'Download book: Repub state is not 31 or 34 or 41' \
                  '(is {1}), refusing to download item {0}' \
                .format(self.item.identifier, self.item.metadata['repub_state'])
            self.logger.error(msg)
            raise Exception(msg)

    def _create_stub_book(self):
        self.dispatch_progress('Creating local book')
        message = "This books is being downloaded and no actions are available just yet."
        book_id = str(uuid4())
        self._book = self._library.new_book(book_id,
                                            status='download_incomplete',
                                            error=message)
        self._book.set_lock()
        self._book.logger.info('Download book: Created stub book {}'.format(
            self._book))

    def _load_book_metadata(self):
        self.dispatch_progress('Loading metadata')
        md_url = ('https://{}/RePublisher/RePublisher-viewScanData.php'
                  '?id={}'.format(self.item.d1, self.identifier))
        self._md = requests.get(md_url, timeout=5)
        self._book.logger.info(
            'Download book: Fetch scandata from cluster: {}'.format(
                self._md.status_code))

    def _create_files(self):
        self.dispatch_progress('Downloading files')
        ret = []
        with open(os.path.join(self._book.path, 'identifier.txt'), 'w+') as fp:
            fp.write(self.item.identifier)
        ret.append(fp.name)
        self._book.logger.info('Download book: Created {}'.format(fp.name))

        with open(os.path.join(self._book.path, 'downloaded'), 'w+') as fp:
            fp.write('True')
            ret.append(fp.name)
        self._book.logger.info('Download book: Created {}'.format(fp.name))

        with open(os.path.join(self._book.path, 'uuid'), 'w+') as fp:
            fp.write(self._book.uuid)
        ret.append(fp.name)
        self._book.logger.info('Download book: Created {}'.format(fp.name))

        with open(os.path.join(self._book.path, 'scandata.xml'), 'w+') as fp:
            fp.write(self._md.content.decode())
        ret.append(fp.name)
        self._book.logger.info('Download book: Created {}'.format(fp.name))

        self.item.get_file(self.item.identifier + '_meta.xml') \
            .download(file_path=self._book.path + '/metadata.xml')
        ret.append('{}'.format(self._book.path + '/metadata.xml'))
        self._book.logger.info('Download book: Created metadata.xml')
        self._book.reload_metadata()

        if not os.path.exists(os.path.join(self._book.path, 'reshooting')):
            os.makedirs(os.path.join(self._book.path, 'reshooting'))
            ret.append('{}'.format(self._book.path + '/reshooting'))
        self._book.logger.info('Download book: Created reshooting directory')

        self._files = ret

        self._book.logger.info('Download book: Created files, now converting '
                               'scandata from RePublisher XML to Scribe3 JSON')

    def _create_scandata(self):
        self.dispatch_progress('Creating scandata')
        sc_path = os.path.join(self._book.path, 'scandata.xml')

        tree = book_helpers.validate_scandata_xml(sc_path, self._book)
        scandata_xml = book_helpers.create_normalized_scandata(
            tree, self._book)
        json_data = book_helpers.convert_normalized_scandata_to_json(
            scandata_xml)

        json_new = {}

        self._book.logger.info('Download book: Now converting to Scribe3 JSON')
        json_new['bookData'] = book_helpers.build_bookdata(
            json_data, self._book)
        json_new['pageData'] = book_helpers.build_pagedata(
            json_data, self._book)

        with open(os.path.join(self._book.path, 'scandata.json'),
                  'w') as outfile:
            json.dump(json_new, outfile)
            self._book.logger.info('Download book: Created {}'.format(
                outfile.name))

        self._scandata = ScanData(self._book.path)
        self._scandata.save()

        self._book.reload_scandata()

        self._book.logger.info('Download book: Created scandata.')

    def _get_checkout_information(self):
        self.dispatch_progress('Pulling checkout information')
        book_checkout_url = ('https://{}/RePublisher/RePublisher-'
                             'checkoutBook.php?peek=true&id={}'.format(
                                 self.item.d1, self._book.identifier))

        self._book.logger.info(
            'Getting checkout information from {}'.format(book_checkout_url))
        ret = self._ia_session.get(book_checkout_url)
        self._book.logger.info('Got {} ({})'.format(ret.text, ret.status_code))
        self._checkout_info = json.loads(ret.text)

    def _write_claimer_file(self):
        self.dispatch_progress('Writing claimer file')
        if 'claimed_by' in self._checkout_info and self._checkout_info[
                'claimed_by'] != False:
            claimer = self._checkout_info['claimed_by']
        else:
            claimer = '-'

        with open(os.path.join(self._book.path, 'claimer'), 'w+') as fp:
            fp.write(claimer)
        self._claimer = claimer
        self._book.logger.info('This book was claimed by {}'.format(claimer))

    def _download_proxies(self):
        self.dispatch_progress('Downloading proxies')
        all_ok = True
        counter = 0
        page_data = self._scandata.dump_raw()['pageData']
        for i, page in enumerate(page_data):
            self.dispatch_progress('Downloading proxies [{}/{}]'.format(
                i, len(page_data)))
            if int(page) != i:
                self._book.logger.error('Download book: Download Proxies: '
                                        'CRITICAL MISMATCH')
                break
            short_msg = 'Download pics | {percent:.1f}% | {n}/{total}'.format(
                percent=i * 100 / len(page_data),
                n=i,
                total=len(page_data),
            )
            self._book.update_message(short_msg)
            url = book_helpers.get_cluster_proxy_url_by_leaf(
                self._scandata, self.item, page)
            res = self.download_proxy_image(page, self._book, url)

            all_ok = all_ok and res
            counter += 1
            if res:
                self._book.logger.debug(
                    'Download book: Got proxies for leaf #{0}'.format(page))
            else:
                self._book.logger.error(
                    'Download book: Error downloading leaf #{0}'.format(page))

            try:
                leafnr = self._scandata.get_page_num(page)['num']
            except Exception:
                pass

        self._book.logger.info(
            'Download book: Downloaded {} proxy images.'.format(counter))

        return all_ok

    def _set_states(self):
        self.dispatch_progress('Setting states')
        self._book.error = None
        if int(self.item.metadata['repub_state']) == 31:
            book_final_repub_state = 32
            self._download_type = 'corrections'
            self._book.do_end_download_correction()
        elif int(self.item.metadata['repub_state']) == 41:
            book_final_repub_state = 42
            self._download_type = 'foldouts'
            self._book.do_end_download_foldout()
        else:
            self._book.logger(
                'Error while processing item in repub_state {}'.format(
                    self.item.metadata['repub_state']))
            raise Exception(
                'remote repub state in inconsistent with book download')

        self._book.logger.info(
            'Setting remote repub_state to {}'.format(book_final_repub_state))
        mdapi_response = self.item.modify_metadata(
            {'repub_state': book_final_repub_state})
        self._book.logger.info(
            'Response from MDAPI: {}'.format(mdapi_response))
        if mdapi_response:
            self._mdapi_response_text = mdapi_response.text
            self._book.logger.info('Body of MDAPI: {}'.format(
                self._mdapi_response_text))
            if mdapi_response.status_code != 200:
                raise Exception(
                    'MDAPI response was not OK! - Got this instead: {} - {}'.
                    format(mdapi_response.status_code, mdapi_response.text))

            self._book.logger.info(
                'Download book: Set book repub_state to {}'.format(
                    book_final_repub_state))
            self._book_final_repub_state = book_final_repub_state
        else:
            raise Exception('No response from MDAPI. Aborting download.')

    def _send_stats(self):
        self.dispatch_progress('Notifying iabdash')
        payload = {
            'repub_state': self._book_final_repub_state,
            'checkout_info': self._checkout_info,
            'claimer': self._claimer,
            'files': self._files,
        }
        push_event('tts-book-downloaded', payload, 'book', self.identifier,
                   os.path.join(self._book.path, "iabdash.log"))
        self.notifications_manager.add_notification(
            title='Downloaded',
            message="{} has been downloaded and is ready for {}.".format(
                self.identifier, self._download_type),
            show_system_tile=False,
            book=self._book)

    def _release_lock(self):
        total_time = 100
        self._book.logger.info('Download book: ------ DONE. Downloaded {0} in '
                               '{1}s ----------'.format(
                                   self.identifier, total_time))
        self._book.release_lock()

    def download_proxy_image(
        self,
        page,
        book,
        url,
    ):
        def is_proxy_valid(proxy_path):
            return True

        file_target = '{n:04d}.jpg'.format(n=int(page))
        dest = os.path.os.path.join(book.path, "thumbnails", file_target)

        if url is not None:
            image = self._ia_session.get(url).content
            with open(dest, 'wb+') as proxy:
                book.logger.debug('Writing {}'.format(dest))
                proxy.write(image)
                book.logger.info('Download book: Written {}'.format(
                    proxy.name))
        else:
            import shutil
            book.logger.debug('Page {} has no proxy, adding missing '
                              'image at {}'.format(page, dest))
            shutil.copyfile(scribe_globals.MISSING_IMAGE, dest)
        ret = is_proxy_valid(dest)
        return ret
Esempio n. 11
0
class ScribeWidget(BoxLayout):

    books_db = ObjectProperty()
    task_scheduler = ObjectProperty()
    config = ObjectProperty()
    notifications_manager = ObjectProperty()
    rcs = ObjectProperty()

    top_bar = ObjectProperty()

    _cameras_status = StringProperty()

    def __init__(self, **kwargs):
        self._help_opened = False
        self._help_key_down = False
        self.timeout_event = None
        super(ScribeWidget, self).__init__(**kwargs)

        try:
            self.config = Scribe3Configuration()
            self.config.validate()
            self.init_pid_file()

        except CredentialsError as e:
            # if this is raised, we show the wizard
            msg = '[b]It looks like there is an issue with your credentials[/b].\n' \
                  '\n{}\nClick the button to begin the wizard.'.format(e.msg)
            popup = InfoPopup(title='Logged out', message=msg, auto_dismiss=False)
            popup.bind(on_submit=self.begin_wizard)
            popup.open()
            return

        except ScribeException as e:
            # if this is raised, the app can no longer continue
            msg = 'Scribe3 could not initialize\nbecause of the following error:' \
                  '\n\n[b]{}[/b].\n\nClick the button to close.'.format(str(e))
            popup = InfoPopup(title='Initialization error', message=msg, auto_dismiss=False)
            app = App.get_running_app()
            popup.bind(on_submit=app.stop)
            popup.open()
            return

        #self.config.subscribe(get_adapter('config'))

        md_version = {'tts_version': scribe_globals.BUILD_NUMBER}
        set_sc_metadata(md_version)

        screen_manager = self.ids._screen_manager
        screen_manager.bind(current=self._on_current_screen)

        self._init_top_bar(self.config)
        self.help = Help()

        self.help.fbind('on_open', self._on_help_open)
        self.help.fbind('on_dismiss', self._on_help_dismiss)

        self.notifications_manager = NotificationManager()

        Clock.schedule_once(self._postponed_init, -1)

    def _postponed_init(self, *args):
        self.cameras = Cameras()
        self.cameras.fbind('camera_ports', self.on_camera_ports)
        self.on_camera_ports(self.cameras, self.cameras.camera_ports)
        self.cameras.initialize()

        self.left_queue, \
        self.right_queue, \
        self.foldout_queue = _setup_camera_threads(self.cameras)

        manager = self.ids._screen_manager
        if manager.current == 'upload_screen':
            self.ids._upload_screen.use_tooltips = True
        self.help.target_widget = manager
        md_screen = self.ids._book_metadata_screen

        md_screen.camera_system = self.cameras
        md_screen.bind(on_done=self._on_book_metadata_screen_done,
                       on_cancel=self._on_book_metadata_screen_cancel)
        md_screen.backend.bind(on_new_book_created=self._on_new_book_created)
        capture_screen = self.ids._capture_screen
        capture_screen.bind(on_book_reset=self._on_book_reset,
                            on_start_new_book=self._on_start_new_book,
                            on_edit_metadata=self._on_book_edit_metadata)
        Window.bind(on_key_down=self._on_key_down)
        Window.bind(on_key_up=self._on_key_up)

        self.task_scheduler.start()

        self.setup_rcs()

        extensions_screen = self.ids['_extensions_screen']
        extensions_screen.task_scheduler = self.task_scheduler
        extensions_screen.library = self.books_db

        user_switch_screen = self.ids['_user_switch_screen']
        user_switch_screen.task_scheduler = self.task_scheduler

        if not self.config.is_true('stats_disabled'):
            self.setup_stats_backend()

        self.setup_notification_center()

        if self.config.is_true('enable_c2'):
            self.setup_command_and_control()

        if self.config.is_true('enable_webapi'):
            self.setup_webapi()

        logout_timeout = self.config.get_numeric_or_none('logout_timeout')
        if logout_timeout:
            Logger.info('Detected logout timeout of {} seconds; initializing scheduler'.format(logout_timeout))
            self.timeout_event = Clock.schedule_interval(self.do_timeout, logout_timeout)

    def refresh_timeout(self):
        timeout = self.config.get_numeric_or_none('logout_timeout')
        if self.timeout_event:
            self.timeout_event.cancel()
            if timeout:
                self.timeout_event = Clock.schedule_interval(self.do_timeout, timeout)

    def do_timeout(self, dt):
        current = self.get_current_screen()
        if self.config.is_true('do_not_logout_from_capture_screens'):
            IGNORE_LOGOUT_SCREENS.extend(['capture_screen', 'reshoot_screen'])
        if current not in IGNORE_LOGOUT_SCREENS:
            self.show_user_switch_screen()

    def setup_rcs(self):
        self.rcs = RCS()
        self.rcs.attach_scheduler(self.task_scheduler)
        self.rcs.schedule_sync()

    def setup_stats_backend(self):
        from ia_scribe.breadcrumbs.api import get_adapter, log_event, process_stats
        self.books_db.subscribe(get_adapter('library'))
        self.ids._screen_manager.bind(current=get_adapter('screen_manager'))
        self.top_bar.bind(on_option_select=get_adapter('top_bar'))
        self.cameras.fbind('camera_ports', get_adapter('cameras'))
        log_event('app', 'started', None, None)

        stats_processor = GenericFunctionTask(
            name='Stats cruncher',
            function=process_stats,
            periodic=False
        )
        self.task_scheduler.schedule(stats_processor)

    def setup_notification_center(self):
        self.books_db.subscribe(notifications_book_adapter)
        self.books_db.subscribe(notifications_book_error_adapter, topic='errors')
        self.notifications_manager.bind(on_notification_added=self.top_bar.highlight_notification)
        interval = self.config.get_numeric_or_none('notification_cleanup_interval')
        if interval:
            task = NotificationsCleanerTask(periodic=True,
                                        interval=interval)
            self.task_scheduler.schedule(task)

    def setup_command_and_control(self):
        # fix for pyinstaller packages app to avoid ReactorAlreadyInstalledError
        import sys
        if 'twisted.internet.reactor' in sys.modules:
            del sys.modules['twisted.internet.reactor']

        # install twisted reactor
        install_twisted_reactor()
        from ia_scribe.ia_services import command_and_control

        self.c2 = command_and_control.S3C2()
        # Attach settings screen widget to c2
        self.ids._screen_manager.get_screen('settings_screen').screens['c2'].ids['c2_widget'].attach(self.c2)

        if not command_and_control.registration_ok:
            self.notifications_manager.add_notification(title='C2 error',
                                                        message='Command and control service failed to load. '
                                                        'The error was: [b]{}[/b] | Contact an admin to resolve.'.format(
                                                            command_and_control.registration_error),
                                                        is_sticky=True,
                                                        is_error=True,
                                                        notification_type='system', )
            return

        self.c2.callback = partial(self.notifications_manager.add_notification,
                              title='Command and Control', notification_type='system')
        self.c2.connect_to_server()



    def setup_webapi(self):
        from ia_scribe.services.webserver import start_webserver
        start_webserver()

    def toggle_worker(self):
        self.ids._upload_widget.toggle_worker(self.ids.button_toggle_worker, self.ids.button_task_manager)

    def show_task_status(self):
        root = StakhanovWidget()
        root.new_manager.attach_scheduler(self.task_scheduler)
        popup = Popup(
            title='Tasks list', content=root, size_hint=(None, None),
            size=('1040dp', '800dp')
        )
        popup.bind(on_dismiss=root.release_refs)
        popup.open()

    def show_notification_center(self):
        root = NotificationCenterWidget()
        root.attach(self.notifications_manager, self.ids._upload_widget._book_handler)
        popup = Popup(
            title='Notifications', content=root, size_hint=(None, None),
            size=('1080dp', '880dp')
        )
        popup.bind(on_dismiss=root.detach)
        popup.open()
        self.top_bar.remove_highlight_notification()

    def show_help_center(self):
        manager = self.ids._screen_manager
        manager.transition.direction = 'left'
        manager.current = 'help_center_screen'

    def show_cameras_status(self):
        self.show_camera_screen()

    def init_pid_file(self):
        Logger.info('PID Init: Begin')
        config_dir = scribe_globals.CONFIG_DIR
        if not os.path.exists(config_dir):
            os.makedirs(config_dir)
        if not os.access(config_dir, os.W_OK | os.X_OK):
            raise ScribeException('Config dir "{}" not writable'
                                  .format(config_dir))
        # Check to see if another copy of the app is running
        # and if needed, remove stale pidfiles
        path = os.path.join(config_dir, 'scribe_pid')
        Logger.info('PID Init: Looking for pidfile at {}'.format(path))
        if os.path.exists(path):
            f = open(path)
            old_pid = f.read().strip()
            f.close()
            pid_dir = os.path.join('/proc', old_pid)
            if os.path.exists(pid_dir):
                Logger.error('There seems to be a pid file at {}. Try '
                             'removing it and relaunching '
                             'the application.'.format(str(pid_dir)))
                raise ScribeException('Another copy of the Scribe application '
                                      'is running!')
            else:
                os.unlink(path)

        pid = os.getpid()
        f = open(path, 'w')
        f.write(str(pid))
        f.close()
        Logger.info('PID Init: End')

    def begin_wizard(self, *args, **kwargs):
        if len(args) >1:
            try:
                args[0].dismiss()
            except:
                pass

        self.ids._screen_manager.transition.direction = 'right'
        self.ids._screen_manager.current = 'wizard_screen'

    def back_to_library(self):
        '''Set the screen_manager go back to the library UI view.'''
        manager = self.ids._screen_manager
        if manager.current != 'upload_screen':
            manager.transition.direction = 'right'
            manager.current = 'upload_screen'

    def _init_top_bar(self, config):
        self.top_bar = top_bar = TopBar()
        meta = get_sc_metadata()
        top_bar.username = meta.get('operator', None) or ''
        top_bar.machine_id = meta.get('scanner', None) or ''
        top_bar.bind(on_option_select=self._on_top_bar_option_select)
        self.config.subscribe(self._on_config_change)
        # TODO: Better way to check if user is logged in
        if 's3' in self.config:
            self.add_widget(top_bar, len(self.children))

    def _on_config_change(self, event_type, payload):
        key, value = payload
        # though we read this value from metadata.xml, we use the shadow
        # value in configuration to get a change notification
        if event_type=='key_set' and key == 'operator':
            meta = get_sc_metadata()
            self.top_bar.username = meta.get('operator', None) or ''

    def _on_top_bar_option_select(self, top_bar, option):
        # TODO: Don't hardcode top bar options
        if option == 'home':
            self.back_to_library()
        elif option == 'change_user':
            self.show_user_switch_screen()
        elif option == 'settings':
            self.show_settings_screen()
        elif option == 'logout':
            self.show_logout_screen()
        elif option == 'calibrate_cameras':
            self.show_calibration_screen(target_screen='upload_screen')
        elif option == 'help':
            link = 'https://wiki.archive.org/twiki/bin/view/BooksDigitization/WebHome'
            webbrowser.open(link)
        elif option == 'visit':
            link = 'https://archive.org'
            webbrowser.open(link)
        elif option == 'extensions':
            self.show_extensions_screen()
        elif option == 'cli':
            self.show_cli_popup()
        elif option == 'stats':
            self.show_stats_screen()
        elif option == 'notification_center':
            self.show_notification_center()
        elif option == 'help_center':
            self.show_help_center()
        elif option == 'exit':
            self.exit_app()

    def _on_current_screen(self, screen_manager, current):
        self.top_bar.use_back_button = current not in ['upload_screen', 'user_switch_screen']

    def _on_book_metadata_screen_done(self, md_screen):
        capture_screen = self.ids._capture_screen
        capture_screen.target_extra = md_screen.target_extra
        capture_screen.book_dir = md_screen.backend.book_path
        manager = self.ids._screen_manager
        manager.transition.direction = 'up'
        manager.current = capture_screen.name

    def _on_book_metadata_screen_cancel(self, md_screen):
        manager = self.ids._screen_manager
        if md_screen.target_extra:
            manager.transition.direction = 'up'
            self.ids._capture_screen.target_extra = md_screen.target_extra
            manager.current = self.ids._capture_screen.name
        else:
            manager.transition.direction = 'left'
            manager.current = self.ids._upload_screen.name

    def _on_book_reset(self, capture_screen):
        self.back_to_library()

    def _on_start_new_book(self, capture_screen):
        md_screen = self.ids._book_metadata_screen
        md_screen.backend.create_new_book()
        manager = self.ids._screen_manager
        manager.transition.direction = 'left'
        manager.current = md_screen.name

    def _on_book_edit_metadata(self, capture_screen):
        md_screen = self.ids._book_metadata_screen
        md_screen.target_extra = capture_screen.create_state()
        md_screen.backend.book_path = capture_screen.book_dir
        manager = self.ids._screen_manager
        manager.transition.direction = 'down'
        manager.current = md_screen.name

    def _on_new_book_created(self, md_screen_backend, book):
        pass

    def _on_key_down(self, window, keycode, scancode, codepoint=None,
                     modifiers=None, **kwargs):
        if scancode == 58 and not self._help_key_down:
            self._help_key_down = True
            self.help.dismiss() if self._help_opened else self.help.open()
            return True

    def _on_key_up(self, window, keycode, scancode, codepoint=None,
                   modifiers=None, **kwargs):
        if scancode == 58 and self._help_key_down:
            self._help_key_down = False
            return True

    def _on_help_open(self, help):
        manager = self.ids._screen_manager
        manager.current_screen.disabled = True
        self._help_key_down = False
        self._help_opened = True

    def _on_help_dismiss(self, help):
        manager = self.ids._screen_manager
        manager.current_screen.disabled = False
        self._help_key_down = False
        self._help_opened = False

    def on_camera_ports(self, cameras, camera_ports):
        num_cams = cameras.get_num_cameras()
        temp = ['| {} camera(s)'.format(num_cams)]
        if num_cams != 0:
            for side, metadata in cameras.get_active_cameras().items():
                temp.append('{}: {}'.format(side, metadata['model']))
        self._cameras_status = ' | '.join(temp)

    def show_user_switch_screen(self):
        manager = self.ids._screen_manager
        manager.transition.direction = 'left'
        manager.current = 'user_switch_screen'

    def show_upload_screen(self):
        manager = self.ids._screen_manager
        manager.transition.direction = 'left'
        manager.current = 'upload_screen'

    def show_metadata_screen(self):
        manager = self.ids._screen_manager
        manager.get_screen('settings_screen').setup_manager(manager)
        manager.get_screen('settings_screen').go_screen('metadata')
        manager.transition.direction = 'left'
        manager.current = 'settings_screen'

    def show_camera_screen(self):
        manager = self.ids._screen_manager
        manager.get_screen('settings_screen').setup_manager(manager)
        manager.get_screen('settings_screen').go_screen('camera')
        manager.transition.direction = 'left'
        manager.current = 'settings_screen'

    def show_settings_screen(self):
        manager = self.ids._screen_manager
        manager.get_screen('settings_screen').setup_manager(manager)
        manager.transition.direction = 'left'
        manager.current = 'settings_screen'

    def show_logout_screen(self):
        # Only using logout screen method `archive_logout`
        # which logouts the user and restarts the app
        manager = self.ids._screen_manager
        login_screen = manager.get_screen('login_screen')
        login_screen.archive_logout()

    def show_calibration_screen(self, target_screen='capture_screen',
                                extra=None):
        calibration_screen = self.ids['_calibration_screen']
        calibration_screen.target_screen = target_screen
        calibration_screen.target_extra = extra
        manager = self.ids._screen_manager
        manager.transition.direction = 'left'
        manager.current = 'calibration_screen'

    def show_extensions_screen(self):
        manager = self.ids._screen_manager
        manager.transition.direction = 'left'
        manager.current = 'extensions_screen'

    def show_stats_screen(self):
        manager = self.ids._screen_manager
        manager.transition.direction = 'left'
        manager.current = 'stats_screen'

    def show_cli_popup(self):
        app = App.get_running_app()
        root = app.get_popup(CLIWidgetPopup,
                             size_hint=(.90, .90))
        root.screen_manager = self.ids._screen_manager
        root.open()

    def get_current_screen(self):
        return self.ids._screen_manager.current

    def get_current_user(self):
        return self.top_bar.username

    def exit_app(self):
        app = App.get_running_app()
        app.stop()