Beispiel #1
0
    def __init__(self, configManager, startupSlide, show_panel, command_queue,
                 cmdline_args, lockfile_fd, auto_start=True,
                 restart_after_minute=-1):

        """FileRock client initialization.

        Loads and configures all application components.

        @param configManager:
                    Instance of filerockclient.config.configManager
        @param startupSlide:
                    Boolean telling whether the startup slides should
                    be shown.
        @param show_panel:
                    Boolean telling whether the UI should appear to the
                    user after the startup.
        @param command_queue:
                    Instance of Queue.Queue where to put commands for
                    filerockclient.application.Application.
        @param cmdline_args:
                    List of arguments which the client has been invoked with.
        @param lockfile_fd:
                    File descriptor of the lock file which ensures there
                    is only one instance of FileRock Client running.
                    Child processes have to close it to avoid stale locks.
        @param auto_start:
                    Boolean telling whether the client should automatically
                    connect to the server after initialization.
        @param restart_after_minute:
                    Number of minutes after which the application must
                    be restarted. There is no restart to do if it is
                    less than 0.
        """
        self.logger = logging.getLogger("FR")
        self.cfg = configManager
        self.startupSlide = startupSlide
        self.show_panel = show_panel
        self.auto_start = auto_start
        self.cmdline_args = cmdline_args
        self.lockfile_fd = lockfile_fd
        self.restart_after_time = restart_after_minute
        self.sys_config_path = self.cfg.get_config_dir()
        self.temp_dir = self.cfg.get('Application Paths', 'temp_dir')

        self.logger.info(
            u"Hello, this is FileRock client (version %s)"
            % CURRENT_CLIENT_VERSION)

        self.logger.debug(u"Initializing Metadata DB...")
        database_file = self.cfg.get('Application Paths', 'metadatadb')
        self._metadata_db = MetadataDB(database_file)
        self._metadata_db_exists_at_start = not self._metadata_db.recreated

        self.logger.debug(u"Initializing InternalFacade...")
        self._internal_facade = InternalFacade(
            self, command_queue, logging.getLogger('FR.InternalFacade'))

        self.logger.debug(u"Initializing UIController...")
        self._ui_controller = UIController(
            self._metadata_db, logging.getLogger('FR.UIController'))

        self.logger.debug(u"Initializing ClientFacade...")
        self._client_facade = ClientFacade(
            self, command_queue, logging.getLogger('FR.ClientFacade'))

        self.logger.debug(u"Initializing zombie ClientFacade...")
        import copy
        self._zombie_client_facade = copy.copy(self._client_facade)
        self._zombie_client_facade._set_zombie()

        self._scheduler = Scheduler()

        self.logger.debug(u"Initializing Hashes DB...")
        hashesdb_file = self.cfg.get('Application Paths', 'hashesdb')
        self.hashesDB = HashesDB(hashesdb_file)

        self.logger.debug(u"Initializing Storage Cache...")
        self.storage_cache = StorageCache(
                    self.cfg.get('Application Paths', 'storage_cache_db'))

        self.logger.debug(u"Initializing Linker...")
        self.linker = Linker(self.cfg, self._ui_controller)

        self.logger.debug(u"Initializing OS settings manager...")
        self.os_settings_manager = OsConfig(
                                        cmdline_args=self.cmdline_args
                                    )

        self._warebox = None
        self.queue = None
        self.connector = None
        self.FSWatcher = None
        self.startup_synchronization = None
        self._server_session = None
Beispiel #2
0
    def __init__(self,
                 configManager,
                 startupSlide,
                 show_panel,
                 command_queue,
                 cmdline_args,
                 lockfile_fd,
                 auto_start=True,
                 restart_after_minute=-1):
        """FileRock client initialization.

        Loads and configures all application components.

        @param configManager:
                    Instance of filerockclient.config.configManager
        @param startupSlide:
                    Boolean telling whether the startup slides should
                    be shown.
        @param show_panel:
                    Boolean telling whether the UI should appear to the
                    user after the startup.
        @param command_queue:
                    Instance of Queue.Queue where to put commands for
                    filerockclient.application.Application.
        @param cmdline_args:
                    List of arguments which the client has been invoked with.
        @param lockfile_fd:
                    File descriptor of the lock file which ensures there
                    is only one instance of FileRock Client running.
                    Child processes have to close it to avoid stale locks.
        @param auto_start:
                    Boolean telling whether the client should automatically
                    connect to the server after initialization.
        @param restart_after_minute:
                    Number of minutes after which the application must
                    be restarted. There is no restart to do if it is
                    less than 0.
        """
        self.logger = logging.getLogger("FR")
        self.cfg = configManager
        self.startupSlide = startupSlide
        self.show_panel = show_panel
        self.auto_start = auto_start
        self.cmdline_args = cmdline_args
        self.lockfile_fd = lockfile_fd
        self.restart_after_time = restart_after_minute
        self.sys_config_path = self.cfg.get_config_dir()
        self.temp_dir = self.cfg.get('Application Paths', 'temp_dir')

        self.logger.info(u"Hello, this is FileRock client (version %s)" %
                         CURRENT_CLIENT_VERSION)
        self.logger.debug(u"python variable __debug__==%r" % __debug__)

        self.logger.debug(u"Initializing Metadata DB...")
        database_file = self.cfg.get('Application Paths', 'metadatadb')
        self._metadata_db = MetadataDB(database_file)
        self._metadata_db_exists_at_start = not self._metadata_db.recreated

        self.logger.debug(u"Initializing InternalFacade...")
        self._internal_facade = InternalFacade(
            self, command_queue, logging.getLogger('FR.InternalFacade'))

        self.logger.debug(u"Initializing UIController...")
        self._ui_controller = UIController(
            self._metadata_db, logging.getLogger('FR.UIController'))

        self.logger.debug(u"Initializing ClientFacade...")
        self._client_facade = ClientFacade(
            self, command_queue, logging.getLogger('FR.ClientFacade'))

        self.logger.debug(u"Initializing zombie ClientFacade...")
        import copy
        self._zombie_client_facade = copy.copy(self._client_facade)
        self._zombie_client_facade._set_zombie()

        self._scheduler = Scheduler()

        self.logger.debug(u"Initializing Hashes DB...")
        hashesdb_file = self.cfg.get('Application Paths', 'hashesdb')
        self.hashesDB = HashesDB(hashesdb_file)

        self.logger.debug(u"Initializing Storage Cache...")
        self.storage_cache = StorageCache(
            self.cfg.get('Application Paths', 'storage_cache_db'))

        self.logger.debug(u"Initializing Linker...")
        self.linker = Linker(self.cfg, self._ui_controller)

        self.logger.debug(u"Initializing OS settings manager...")
        self.os_settings_manager = OsConfig(cmdline_args=self.cmdline_args)

        self._warebox = None
        self.queue = None
        self.connector = None
        self.FSWatcher = None
        self.startup_synchronization = None
        self._server_session = None
Beispiel #3
0
class Core(object):
    """The main class of FileRock client.

    This is the place where things begin and end. The application
    services are started and terminated through this interface.
    This class creates and registers all other domain components, except
    for user interfaces which are handled elsewhere.
    Performs initial checks at startup and offers a public interface
    for registering UI objects.
    """

    def __init__(self, configManager, startupSlide, show_panel, command_queue,
                 cmdline_args, lockfile_fd, auto_start=True,
                 restart_after_minute=-1):

        """FileRock client initialization.

        Loads and configures all application components.

        @param configManager:
                    Instance of filerockclient.config.configManager
        @param startupSlide:
                    Boolean telling whether the startup slides should
                    be shown.
        @param show_panel:
                    Boolean telling whether the UI should appear to the
                    user after the startup.
        @param command_queue:
                    Instance of Queue.Queue where to put commands for
                    filerockclient.application.Application.
        @param cmdline_args:
                    List of arguments which the client has been invoked with.
        @param lockfile_fd:
                    File descriptor of the lock file which ensures there
                    is only one instance of FileRock Client running.
                    Child processes have to close it to avoid stale locks.
        @param auto_start:
                    Boolean telling whether the client should automatically
                    connect to the server after initialization.
        @param restart_after_minute:
                    Number of minutes after which the application must
                    be restarted. There is no restart to do if it is
                    less than 0.
        """
        self.logger = logging.getLogger("FR")
        self.cfg = configManager
        self.startupSlide = startupSlide
        self.show_panel = show_panel
        self.auto_start = auto_start
        self.cmdline_args = cmdline_args
        self.lockfile_fd = lockfile_fd
        self.restart_after_time = restart_after_minute
        self.sys_config_path = self.cfg.get_config_dir()
        self.temp_dir = self.cfg.get('Application Paths', 'temp_dir')

        self.logger.info(
            u"Hello, this is FileRock client (version %s)"
            % CURRENT_CLIENT_VERSION)

        self.logger.debug(u"Initializing Metadata DB...")
        database_file = self.cfg.get('Application Paths', 'metadatadb')
        self._metadata_db = MetadataDB(database_file)
        self._metadata_db_exists_at_start = not self._metadata_db.recreated

        self.logger.debug(u"Initializing InternalFacade...")
        self._internal_facade = InternalFacade(
            self, command_queue, logging.getLogger('FR.InternalFacade'))

        self.logger.debug(u"Initializing UIController...")
        self._ui_controller = UIController(
            self._metadata_db, logging.getLogger('FR.UIController'))

        self.logger.debug(u"Initializing ClientFacade...")
        self._client_facade = ClientFacade(
            self, command_queue, logging.getLogger('FR.ClientFacade'))

        self.logger.debug(u"Initializing zombie ClientFacade...")
        import copy
        self._zombie_client_facade = copy.copy(self._client_facade)
        self._zombie_client_facade._set_zombie()

        self._scheduler = Scheduler()

        self.logger.debug(u"Initializing Hashes DB...")
        hashesdb_file = self.cfg.get('Application Paths', 'hashesdb')
        self.hashesDB = HashesDB(hashesdb_file)

        self.logger.debug(u"Initializing Storage Cache...")
        self.storage_cache = StorageCache(
                    self.cfg.get('Application Paths', 'storage_cache_db'))

        self.logger.debug(u"Initializing Linker...")
        self.linker = Linker(self.cfg, self._ui_controller)

        self.logger.debug(u"Initializing OS settings manager...")
        self.os_settings_manager = OsConfig(
                                        cmdline_args=self.cmdline_args
                                    )

        self._warebox = None
        self.queue = None
        self.connector = None
        self.FSWatcher = None
        self.startup_synchronization = None
        self._server_session = None

    def _get_ready_for_service(self):
        """Perform further initialization.

        Here are initialized those components that need any information
        available at runtime and thus couldn't be initialized by the
        constructor.
        """
        # TODO: do we need this at all?

        self.logger.debug(u"Initializing Warebox...")
        self._warebox = Warebox(self.cfg)

        self.logger.debug(u"Initializing Warebox Cache...")

        session_queue = MultiQueue([
            'servermessage',   # Messages sent by the server
            'operation',       # PathnameOperation objects to handle
            'usercommand',     # Commands sent by the user
            'sessioncommand',  # ServerSession internal use commands
            'systemcommand'    # Commands sent by other client components
        ])

        self.logger.debug(u"Initializing Event Queue...")
        self.queue = EventsQueue(self._internal_facade, session_queue)

        self.logger.debug(u"Initializing Storage Connector...")
        self.connector = StorageConnector(self._warebox, self.cfg)

        self.logger.debug(u"Initializing FileSystem Watcher...")
        self.FSWatcher = filesystemwatcher.watcher_class(self._warebox,
                                                         self.queue,
                                                         start_suspended=True)

        self.logger.debug(u"Initializing Startup Synchronization...")
        self.startup_synchronization = StartupSynchronization(
            self._warebox, self.storage_cache, self.queue)

        self.logger.debug(u"Initializing Server Session...")
        self._server_session = ServerSession(
            self.cfg, self._warebox,
            self.storage_cache, self.startup_synchronization,
            self.FSWatcher, self.linker,
            self._metadata_db, self.hashesDB, self._internal_facade,
            self._ui_controller, self.lockfile_fd, auto_start=self.auto_start,
            input_queue=session_queue, scheduler=self._scheduler)

        self.logger.debug(u"Initialization completed successfully")

    def _clean_env(self):
        """Delete all user meta-data on integrity checks.

        This reproduces the situation where the application has been
        just installed, so it won't bother with integrity checks on the
        user files at next connection. Moreover, local modifications
        will not be detected and every file in the warebox will result
        as "new", getting merged with the content of the storage.
        It's necessary when something has changed in such a way that
        checking integrity or real synchronization are not
        possible (e.g. the user has choosen a different directory to be
        the warebox, some data on integrity have been lost, etc).
        """
        self.logger.debug(u"Cleaning User environment")
        self.storage_cache.clear()
        self._metadata_db.delete_key('trusted_basis')
        self._metadata_db.delete_key('candidate_basis')
        self._metadata_db.delete_key(metadata.LASTACCEPTEDSTATEKEY)

    def _apply_os_config(self):
        """Apply OS-specific configurations.

        Any setup aimed at integrating FileRock with the OS should be
        done here.
        """
        
        # Start client on system startup (only for installed clients)
        if RUNNING_INSTALLED:
            value = self.cfg.get('User Defined Options', 'launch_on_startup')
            launch_on_startup = (value == u'True')
            self.os_settings_manager.set_autostart(enable=launch_on_startup)

        # Add FileRock to whitelisted tray icons if necessary
        if not self.os_settings_manager.is_systray_icon_whitelisted():
            self.os_settings_manager.whitelist_tray_icon()
            self._ui_controller.ask_for_user_input('logout_required')
            raise LogOutRequiredException()

    def _change_warebox_path(self, new_warebox):
        """Set a new folder as the warebox.

        @param new_warebox:
                    Absolute filesystem pathname of the directory that
                    will be the new warebox.
        """
        self.cfg.set('Application Paths', 'warebox_path', new_warebox)
        self.cfg.write_to_file()
        self._metadata_db.set('last_warebox_path',
                              self.cfg.get('Application Paths', 'warebox_path'))
        warebox = Warebox(self.cfg)
        CryptoUtils.recreate_encrypted_dir(warebox, self.logger)
        self._clean_env()

    def _ask_warebox_path(self, cfg):
        """Ask the user to select a directory on his filesystem to use
        as the warebox.

        @param cfg:
                    Instance of filerockclient.config.configManager.
        @return
                    Boolean telling whether the user has inserted the
                    requested input or has canceled the request.
        """
        last_warebox = self._metadata_db.try_get('last_warebox_path')

        if last_warebox is None and not self._metadata_db_exists_at_start:
            result = self._ui_controller.ask_for_user_input(
                                        'warebox_path',
                                        self.cfg.get('Application Paths',
                                                     'warebox_path')
                    )
            if result['result']:
                old_warebox = cfg.get('Application Paths', 'warebox_path')
                new_warebox = result['warebox_path']
                if self._warebox_need_merge(result['warebox_path']):
                    ret = self._ui_controller.ask_for_user_input(
                        'warebox_not_empty', old_warebox, new_warebox)
                    if ret != 'ok':
                        self._metadata_db.destroy()
                        return False
                self._change_warebox_path(new_warebox)
            else:
                self._metadata_db.destroy()
                return False

        return True

    def _warebox_need_merge(self, warebox_path):
        """Check whether a given directory would need to be merged with
        the content of the storage if used as the warebox.

        @param warebox_path:
                    Absolute filesystem pathname of the directory that
                    will be the new warebox.
        @return
                    True if the passed warebox will need a merge, False
                    otherwise.
        """
        old = self.cfg.get('Application Paths', 'warebox_path')
        self.cfg.set('Application Paths', 'warebox_path', warebox_path)
        tmp_warebox = Warebox(self.cfg)
        self.cfg.set('Application Paths', 'warebox_path', old)
        content = tmp_warebox.get_content(recursive=False)
        if CryptoUtils.ENCRYPTED_FOLDER_NAME in content:
            content.remove(CryptoUtils.ENCRYPTED_FOLDER_NAME)
        if content != []:
            return True
        return False

    def _check_warebox_or_username_changes(self):
        """Check whether the username or the warebox path have changed
        from the last time FileRock was running.

        If anything is changed than the user meta-data get deleted to
        restore a first-start condition.
        If the warebox has changed and it will be merged at next startup
        the user is asked to accept it.

        @return
                    False if the user refused to merge, True otherwise.
        """
        last_user = self._metadata_db.try_get('last_username')
        curr_user = self.cfg.get('User', 'username')
        last_warebox = self._metadata_db.try_get('last_warebox_path')
        curr_warebox = self.cfg.get('Application Paths', 'warebox_path')

        warebox_changed = last_warebox != curr_warebox
        user_changed = last_user is None or last_user != curr_user

        if user_changed:
            self.logger.debug('User changed from %s to %s',
                              last_user,
                              curr_user)

        if warebox_changed:
            self.logger.debug('Warebox path changed from %s to %s',
                              last_warebox,
                              curr_warebox)
            self.logger.debug('Maybe User has changed warebox path by hand')
            if self._warebox_need_merge(curr_warebox) \
            and not self._metadata_db_exists_at_start:
                ret = self._ui_controller.ask_for_user_input(
                    'warebox_not_empty', last_warebox, curr_warebox, True)
                if ret != 'ok':
                    return False
                self.logger.debug(
                    'User accepted the new warebox and content merge')

        if warebox_changed or user_changed:
            self._clean_env()

        self._metadata_db.set('last_warebox_path', curr_warebox)
        self._metadata_db.set('last_username', curr_user)
        return True

    def start_service(self):
        """Main entry point to run the application.

        Startup routine that makes initial checks and starts threads.
        All threads except those that run UIs are started from here.
        """
        self._check_for_updates()
        self._apply_os_config()
        self._show_welcome(self.cfg)

        self._patch_transition_from_release_0_4_0_no_null_basis()

        if self.storage_cache.recreated or not self.linker.is_linked():
            self.logger.debug('Storage cache was recreated or not linked')
            self._clean_env()

        if not self._ask_warebox_path(self.cfg):
            self._internal_facade.terminate()
            return

        self._get_ready_for_service()
        self._scheduler.start()
        link_result = self.linker.link()

        if not link_result:
            if link_result == False:
                self._internal_facade.terminate()
            return

        if not self._check_warebox_or_username_changes():
            self._internal_facade.terminate()
            return

        self._ui_controller.update_client_info({
            "username": self.cfg.get('User', 'username'),
            "client_id": self.cfg.get('User', 'client_id'),
            "client_hostname": platform.node(),
            "client_platform": platform.system(),
            "client_version": CURRENT_CLIENT_VERSION,
            "basis": self._metadata_db.try_get('trusted_basis')})

        self._ui_controller.update_config_info(self.cfg)

        if self.show_panel:
            self._ui_controller.show_panel()

        # Clear the storage cache if the blacklist has changed
        blacklist_currhash = self._warebox.get_blacklist_hash()
        blacklist_hash = self._metadata_db.try_get('blacklist_hash')
        if blacklist_hash is None or blacklist_hash != blacklist_currhash:
            self.logger.debug('Blacklist changed, cleaning cache')
            for pathname in map(lambda x: x[0], self.storage_cache.get_all_records()):
                if self._warebox.is_blacklisted(pathname):
                    self.storage_cache.delete_record(pathname)
            self._metadata_db.set('blacklist_hash', blacklist_currhash)

        self.FSWatcher.start()
        self._server_session.reload_config_info()
        self._server_session.start()
        if not self.auto_start:
            self._schedule_a_start()
        self._ui_controller.notify_core_ready()
        self._clean_os_label()

    def _patch_transition_from_release_0_4_0_no_null_basis(self):
        """Patch that handles an erroneous existence of an empty
        trusted basis in the metadata.

        The old FileRock releases used to automatically persist a None
        trusted basis if the 'trusted_basis' record didn't exist.
        However, now the metadata database has changed and would give an
        error on getting a None basis.

        To be removed as soon as all clients are updated.
        """
        trusted_basis = self._metadata_db.try_get('trusted_basis')
        exists = self._metadata_db.exist_record('trusted_basis')
        if exists and not trusted_basis:
            self.logger.info("Deleting a null trusted basis")
            self._metadata_db.delete_key('trusted_basis')

    def _clean_os_label(self):
        """Reset all labels of the OSX shell extension UI.
        """
        # TODO: move this method to the UI layer!!
        if sys.platform == 'darwin':
            try: enable_osx_label_shellext = self.cfg.get(config.USER_DEFINED_OPTIONS, 'osx_label_shellext') == u'True'
            except Exception: enable_osx_label_shellext = False
            if not enable_osx_label_shellext:
                if self._metadata_db.try_get('osx_label_shellext') != None:
                    from filerockclient.ui.shellextension.osx import label_based_ui
                    pathnames_list = map(self._warebox.absolute_pathname, self._warebox.get_content())
                    label_based_ui.clean_all_osx_labels(pathnames_list)
                    self._metadata_db.delete_key('osx_label_shellext')

    def _show_welcome(self, cfg):
        """Displays the presentation slides to the user, if needed.
        """
#        no_welcome = self._metadata_db.try_get('No welcome on startup')
#        no_welcome = no_welcome is not None and no_welcome != u'0'
        show = self.cfg.getboolean(config.USER_DEFINED_OPTIONS,'show_slideshow')

        if self.startupSlide and show:
            result = self._ui_controller.show_welcome(cfg, True)
            show = str(result['show welcome on startup'])
            self.cfg.set(config.USER_DEFINED_OPTIONS,'show_slideshow', show)
            self.cfg.write_to_file()

    def _check_for_updates(self):
        """Check if an update for FileRock is available.

        If a new a client version is found, user is prompted to download
        and install the upgrade.
        """

        # Never check for updates when running from source
        if RUNNING_FROM_SOURCE:
            self.logger.debug(u"Skipping update procedures (client is running from sources)")
            return
        
        # Get updater class for current platform (win32/darwin/linux2)
        try:
            updater = PlatformUpdater(
                                self.cfg.get('Application Paths', 'temp_dir'),
                                self.cfg.get('Application Paths', 'webserver_ca_chain'))
        # Note: this should never happen
        except UpdateRequestedFromTrunkClient as e:
            self.logger.debug(u"Skipping update procedures (running from trunk)")
            return
        except UnsupportedPlatformException as e:
            self.logger.warning(u"%s" % e)
            return
        # Updater failed to fetch latest version info (just log a warning)
        except ClientUpdateInfoRetrievingException as e:
            self.logger.warning(u"Error reading client update info: %s" % e)
            return

        # If client version is obsolete, prompt user to install updates
        if updater.is_client_version_obsolete():
            last_version = updater.get_latest_version_available()
            self.logger.info(
                u"Current client version (%s) is obsolete, latest is %s"
                % (CURRENT_CLIENT_VERSION, last_version))

            if self.cfg.get(u"User Defined Options", "auto_update") == u'True':
                user_choice = True
            else:
                # If cfg param 'auto_update' is off, prompt user to perform update
                user_choice = updater.prompt_user_for_update(self._ui_controller)

            if user_choice:
                raise UpdateRequestedException()
            elif updater.is_update_mandatory():
                raise MandatoryUpdateDeniedException()

        else:
            # Remove update data
            updater.flush_update_file()

    def _schedule_a_start(self):
        """Schedule a restart of the application.
        """
        # TODO: move this to the Application layer
        if self.restart_after_time > 0:
            self._scheduler.schedule_action(self.connect,
                                            name='START',
                                            minutes=self.restart_after_time)
            self.logger.info('Client schedules a connect in %d minutes' %
                             self.restart_after_time)

    def unschedule_start(self):
        """Unschedule a restart of the application, if there is any.
        """
        # TODO: move this to the Application layer
        self._scheduler.unschedule_action(self.connect)
        self.logger.debug('Removing scheduled connect')

    def terminate(self, terminate_ui=True):
        """Main termination routine.

        Makes all threads stop and releases all acquired resources.
        """
        self.logger.debug(u'Terminating Core...')
        self._client_facade._set_zombie()
        self._scheduler.terminate()
        if self.queue is not None:
            self.logger.debug(u"Aborting current operations...")
            self.queue.terminate()
            self.logger.debug(u"Current operations aborted.")
        if self._server_session is not None:
            self._server_session.terminate()
        if self.FSWatcher is not None:
            self.FSWatcher.terminate()
        self.logger.debug(u'Core terminated.')

    def connect(self):
        """Connect to the server.
        """
        self.unschedule_start()
        self._server_session.connect()

    def setup_ui(self, ui_class):
        """Factory method for creating user interface instances.

        Given a UI class, returns an instance of such class. Some basic
        setup is performed on the created object.
        It's needed only once.

        @param ui_class:
                    Any UI class in the filerockclient.ui package.
        """
        return ui_class.initUI(self._zombie_client_facade)

    def register_ui(self, ui):
        """Register a user interface object for interacting with the
        client.

        After a UI instance has been created by setup_ui(), it can be
        registered with this method. The UI gets linked to the client
        and can both receive messages and send queries/operations.

        @param ui:
                    Instance of any UI class in the filerockclient.ui
                    package.
        """
        ui.setClient(self._client_facade)
        self._ui_controller.register_ui(ui)
Beispiel #4
0
class Core(object):
    """The main class of FileRock client.

    This is the place where things begin and end. The application
    services are started and terminated through this interface.
    This class creates and registers all other domain components, except
    for user interfaces which are handled elsewhere.
    Performs initial checks at startup and offers a public interface
    for registering UI objects.
    """
    def __init__(self,
                 configManager,
                 startupSlide,
                 show_panel,
                 command_queue,
                 cmdline_args,
                 lockfile_fd,
                 auto_start=True,
                 restart_after_minute=-1):
        """FileRock client initialization.

        Loads and configures all application components.

        @param configManager:
                    Instance of filerockclient.config.configManager
        @param startupSlide:
                    Boolean telling whether the startup slides should
                    be shown.
        @param show_panel:
                    Boolean telling whether the UI should appear to the
                    user after the startup.
        @param command_queue:
                    Instance of Queue.Queue where to put commands for
                    filerockclient.application.Application.
        @param cmdline_args:
                    List of arguments which the client has been invoked with.
        @param lockfile_fd:
                    File descriptor of the lock file which ensures there
                    is only one instance of FileRock Client running.
                    Child processes have to close it to avoid stale locks.
        @param auto_start:
                    Boolean telling whether the client should automatically
                    connect to the server after initialization.
        @param restart_after_minute:
                    Number of minutes after which the application must
                    be restarted. There is no restart to do if it is
                    less than 0.
        """
        self.logger = logging.getLogger("FR")
        self.cfg = configManager
        self.startupSlide = startupSlide
        self.show_panel = show_panel
        self.auto_start = auto_start
        self.cmdline_args = cmdline_args
        self.lockfile_fd = lockfile_fd
        self.restart_after_time = restart_after_minute
        self.sys_config_path = self.cfg.get_config_dir()
        self.temp_dir = self.cfg.get('Application Paths', 'temp_dir')

        self.logger.info(u"Hello, this is FileRock client (version %s)" %
                         CURRENT_CLIENT_VERSION)
        self.logger.debug(u"python variable __debug__==%r" % __debug__)

        self.logger.debug(u"Initializing Metadata DB...")
        database_file = self.cfg.get('Application Paths', 'metadatadb')
        self._metadata_db = MetadataDB(database_file)
        self._metadata_db_exists_at_start = not self._metadata_db.recreated

        self.logger.debug(u"Initializing InternalFacade...")
        self._internal_facade = InternalFacade(
            self, command_queue, logging.getLogger('FR.InternalFacade'))

        self.logger.debug(u"Initializing UIController...")
        self._ui_controller = UIController(
            self._metadata_db, logging.getLogger('FR.UIController'))

        self.logger.debug(u"Initializing ClientFacade...")
        self._client_facade = ClientFacade(
            self, command_queue, logging.getLogger('FR.ClientFacade'))

        self.logger.debug(u"Initializing zombie ClientFacade...")
        import copy
        self._zombie_client_facade = copy.copy(self._client_facade)
        self._zombie_client_facade._set_zombie()

        self._scheduler = Scheduler()

        self.logger.debug(u"Initializing Hashes DB...")
        hashesdb_file = self.cfg.get('Application Paths', 'hashesdb')
        self.hashesDB = HashesDB(hashesdb_file)

        self.logger.debug(u"Initializing Storage Cache...")
        self.storage_cache = StorageCache(
            self.cfg.get('Application Paths', 'storage_cache_db'))

        self.logger.debug(u"Initializing Linker...")
        self.linker = Linker(self.cfg, self._ui_controller)

        self.logger.debug(u"Initializing OS settings manager...")
        self.os_settings_manager = OsConfig(cmdline_args=self.cmdline_args)

        self._warebox = None
        self.queue = None
        self.connector = None
        self.FSWatcher = None
        self.startup_synchronization = None
        self._server_session = None

    def _get_ready_for_service(self):
        """Perform further initialization.

        Here are initialized those components that need any information
        available at runtime and thus couldn't be initialized by the
        constructor.
        """
        # TODO: do we need this at all?

        self.logger.debug(u"Initializing Warebox...")
        self._warebox = Warebox(self.cfg)

        self.logger.debug(u"Initializing Warebox Cache...")

        session_queue = MultiQueue([
            'servermessage',  # Messages sent by the server
            'operation',  # PathnameOperation objects to handle
            'usercommand',  # Commands sent by the user
            'sessioncommand',  # ServerSession internal use commands
            'systemcommand'  # Commands sent by other client components
        ])

        self.logger.debug(u"Initializing Event Queue...")
        self.queue = EventsQueue(self._internal_facade, session_queue)

        self.logger.debug(u"Initializing Storage Connector...")
        self.connector = StorageConnector(self._warebox, self.cfg)

        self.logger.debug(u"Initializing FileSystem Watcher...")
        self.FSWatcher = filesystemwatcher.watcher_class(self._warebox,
                                                         self.queue,
                                                         start_suspended=True)

        self.logger.debug(u"Initializing Startup Synchronization...")
        self.startup_synchronization = StartupSynchronization(
            self._warebox, self.storage_cache, self.queue)

        self.logger.debug(u"Initializing Server Session...")
        self._server_session = ServerSession(self.cfg,
                                             self._warebox,
                                             self.storage_cache,
                                             self.startup_synchronization,
                                             self.FSWatcher,
                                             self.linker,
                                             self._metadata_db,
                                             self.hashesDB,
                                             self._internal_facade,
                                             self._ui_controller,
                                             self.lockfile_fd,
                                             auto_start=self.auto_start,
                                             input_queue=session_queue,
                                             scheduler=self._scheduler)

        self.logger.debug(u"Initialization completed successfully")

    def _clean_env(self):
        """Delete all user meta-data on integrity checks.

        This reproduces the situation where the application has been
        just installed, so it won't bother with integrity checks on the
        user files at next connection. Moreover, local modifications
        will not be detected and every file in the warebox will result
        as "new", getting merged with the content of the storage.
        It's necessary when something has changed in such a way that
        checking integrity or real synchronization are not
        possible (e.g. the user has choosen a different directory to be
        the warebox, some data on integrity have been lost, etc).
        """
        self.logger.debug(u"Cleaning User environment")
        self.storage_cache.clear()
        self._metadata_db.delete_key('trusted_basis')
        self._metadata_db.delete_key('candidate_basis')
        self._metadata_db.delete_key(metadata.LASTACCEPTEDSTATEKEY)

    def _apply_os_config(self):
        """Apply OS-specific configurations.

        Any setup aimed at integrating FileRock with the OS should be
        done here.
        """

        # Start client on system startup (only for installed clients)
        if RUNNING_INSTALLED:
            value = self.cfg.get('User Defined Options', 'launch_on_startup')
            launch_on_startup = (value == u'True')
            self.os_settings_manager.set_autostart(enable=launch_on_startup)

        # Add FileRock to whitelisted tray icons if necessary
        if not self.os_settings_manager.is_systray_icon_whitelisted():
            self.os_settings_manager.whitelist_tray_icon()
            self._ui_controller.ask_for_user_input('logout_required')
            raise LogOutRequiredException()

    def _change_warebox_path(self, new_warebox):
        """Set a new folder as the warebox.

        @param new_warebox:
                    Absolute filesystem pathname of the directory that
                    will be the new warebox.
        """
        self.cfg.set('Application Paths', 'warebox_path', new_warebox)
        self.cfg.write_to_file()
        self._metadata_db.set(
            'last_warebox_path',
            self.cfg.get('Application Paths', 'warebox_path'))
        warebox = Warebox(self.cfg)
        CryptoUtils.recreate_encrypted_dir(warebox, self.logger)
        self._clean_env()

    def _ask_warebox_path(self, cfg):
        """Ask the user to select a directory on his filesystem to use
        as the warebox.

        @param cfg:
                    Instance of filerockclient.config.configManager.
        @return
                    Boolean telling whether the user has inserted the
                    requested input or has canceled the request.
        """
        last_warebox = self._metadata_db.try_get('last_warebox_path')

        if last_warebox is None and not self._metadata_db_exists_at_start:
            result = self._ui_controller.ask_for_user_input(
                'warebox_path',
                self.cfg.get('Application Paths', 'warebox_path'))
            if result['result']:
                old_warebox = cfg.get('Application Paths', 'warebox_path')
                new_warebox = result['warebox_path']
                if self._warebox_need_merge(result['warebox_path']):
                    ret = self._ui_controller.ask_for_user_input(
                        'warebox_not_empty', old_warebox, new_warebox)
                    if ret != 'ok':
                        self._metadata_db.destroy()
                        return False
                self._change_warebox_path(new_warebox)
            else:
                self._metadata_db.destroy()
                return False

        return True

    def _warebox_need_merge(self, warebox_path):
        """Check whether a given directory would need to be merged with
        the content of the storage if used as the warebox.

        @param warebox_path:
                    Absolute filesystem pathname of the directory that
                    will be the new warebox.
        @return
                    True if the passed warebox will need a merge, False
                    otherwise.
        """
        old = self.cfg.get('Application Paths', 'warebox_path')
        self.cfg.set('Application Paths', 'warebox_path', warebox_path)
        tmp_warebox = Warebox(self.cfg)
        self.cfg.set('Application Paths', 'warebox_path', old)
        content = tmp_warebox.get_content(recursive=False)
        if CryptoUtils.ENCRYPTED_FOLDER_NAME in content:
            content.remove(CryptoUtils.ENCRYPTED_FOLDER_NAME)
        if content != []:
            return True
        return False

    def _check_warebox_or_username_changes(self):
        """Check whether the username or the warebox path have changed
        from the last time FileRock was running.

        If anything is changed than the user meta-data get deleted to
        restore a first-start condition.
        If the warebox has changed and it will be merged at next startup
        the user is asked to accept it.

        @return
                    False if the user refused to merge, True otherwise.
        """
        last_user = self._metadata_db.try_get('last_username')
        curr_user = self.cfg.get('User', 'username')
        last_warebox = self._metadata_db.try_get('last_warebox_path')
        curr_warebox = self.cfg.get('Application Paths', 'warebox_path')

        warebox_changed = last_warebox != curr_warebox
        user_changed = last_user is None or last_user != curr_user

        if user_changed:
            self.logger.debug('User changed from %s to %s', last_user,
                              curr_user)

        if warebox_changed:
            self.logger.debug('Warebox path changed from %s to %s',
                              last_warebox, curr_warebox)
            self.logger.debug('Maybe User has changed warebox path by hand')
            if self._warebox_need_merge(curr_warebox) \
            and not self._metadata_db_exists_at_start:
                ret = self._ui_controller.ask_for_user_input(
                    'warebox_not_empty', last_warebox, curr_warebox, True)
                if ret != 'ok':
                    return False
                self.logger.debug(
                    'User accepted the new warebox and content merge')

        if warebox_changed or user_changed:
            self._clean_env()

        self._metadata_db.set('last_warebox_path', curr_warebox)
        self._metadata_db.set('last_username', curr_user)
        return True

    def start_service(self):
        """Main entry point to run the application.

        Startup routine that makes initial checks and starts threads.
        All threads except those that run UIs are started from here.
        """
        self._check_for_updates()
        self._apply_os_config()
        self._show_welcome(self.cfg)

        self._patch_transition_from_release_0_4_0_no_null_basis()

        if self.storage_cache.recreated or not self.linker.is_linked():
            self.logger.debug('Storage cache was recreated or not linked')
            self._clean_env()

        if not self._ask_warebox_path(self.cfg):
            self._internal_facade.terminate()
            return

        self._get_ready_for_service()
        self._scheduler.start()
        link_result = self.linker.link()

        if not link_result:
            if link_result == False:
                self._internal_facade.terminate()
            return

        if not self._check_warebox_or_username_changes():
            self._internal_facade.terminate()
            return

        self._ui_controller.update_client_info({
            "username":
            self.cfg.get('User', 'username'),
            "client_id":
            self.cfg.get('User', 'client_id'),
            "client_hostname":
            platform.node(),
            "client_platform":
            platform.system(),
            "client_version":
            CURRENT_CLIENT_VERSION,
            "basis":
            self._metadata_db.try_get('trusted_basis')
        })

        self._ui_controller.update_config_info(self.cfg)

        if self.show_panel:
            self._ui_controller.show_panel()

        # Clear the storage cache if the blacklist has changed
        blacklist_currhash = self._warebox.get_blacklist_hash()
        blacklist_hash = self._metadata_db.try_get('blacklist_hash')
        if blacklist_hash is None or blacklist_hash != blacklist_currhash:
            self.logger.debug('Blacklist changed, cleaning cache')
            for pathname in map(lambda x: x[0],
                                self.storage_cache.get_all_records()):
                if self._warebox.is_blacklisted(pathname):
                    self.storage_cache.delete_record(pathname)
            self._metadata_db.set('blacklist_hash', blacklist_currhash)

        self.FSWatcher.start()
        self._server_session.reload_config_info()
        self._server_session.start()
        if not self.auto_start:
            self._schedule_a_start()
        self._ui_controller.notify_core_ready()
        self._clean_os_label()

    def _patch_transition_from_release_0_4_0_no_null_basis(self):
        """Patch that handles an erroneous existence of an empty
        trusted basis in the metadata.

        The old FileRock releases used to automatically persist a None
        trusted basis if the 'trusted_basis' record didn't exist.
        However, now the metadata database has changed and would give an
        error on getting a None basis.

        To be removed as soon as all clients are updated.
        """
        trusted_basis = self._metadata_db.try_get('trusted_basis')
        exists = self._metadata_db.exist_record('trusted_basis')
        if exists and not trusted_basis:
            self.logger.info("Deleting a null trusted basis")
            self._metadata_db.delete_key('trusted_basis')

    def _clean_os_label(self):
        """Reset all labels of the OSX shell extension UI.
        """
        # TODO: move this method to the UI layer!!
        if sys.platform == 'darwin':
            try:
                enable_osx_label_shellext = self.cfg.get(
                    config.USER_DEFINED_OPTIONS,
                    'osx_label_shellext') == u'True'
            except Exception:
                enable_osx_label_shellext = False
            if not enable_osx_label_shellext:
                if self._metadata_db.try_get('osx_label_shellext') != None:
                    from filerockclient.ui.shellextension.osx import label_based_ui
                    pathnames_list = map(self._warebox.absolute_pathname,
                                         self._warebox.get_content())
                    label_based_ui.clean_all_osx_labels(pathnames_list)
                    self._metadata_db.delete_key('osx_label_shellext')

    def _show_welcome(self, cfg):
        """Displays the presentation slides to the user, if needed.
        """
        #        no_welcome = self._metadata_db.try_get('No welcome on startup')
        #        no_welcome = no_welcome is not None and no_welcome != u'0'
        show = self.cfg.getboolean(config.USER_DEFINED_OPTIONS,
                                   'show_slideshow')

        if self.startupSlide and show:
            result = self._ui_controller.show_welcome(cfg, True)
            show = str(result['show welcome on startup'])
            self.cfg.set(config.USER_DEFINED_OPTIONS, 'show_slideshow', show)
            self.cfg.write_to_file()

    def _check_for_updates(self):
        """Check if an update for FileRock is available.

        If a new a client version is found, user is prompted to download
        and install the upgrade.
        """

        # Never check for updates when running from source
        if RUNNING_FROM_SOURCE:
            self.logger.debug(
                u"Skipping update procedures (client is running from sources)")
            return

        # Get updater class for current platform (win32/darwin/linux2)
        try:
            updater = PlatformUpdater(
                self.cfg.get('Application Paths', 'temp_dir'),
                self.cfg.get('Application Paths', 'webserver_ca_chain'))
        # Note: this should never happen
        except UpdateRequestedFromTrunkClient as e:
            self.logger.debug(
                u"Skipping update procedures (running from trunk)")
            return
        except UnsupportedPlatformException as e:
            self.logger.warning(u"%s" % e)
            return
        # Updater failed to fetch latest version info (just log a warning)
        except ClientUpdateInfoRetrievingException as e:
            self.logger.warning(u"Error reading client update info: %s" % e)
            return

        # If client version is obsolete, prompt user to install updates
        if updater.is_client_version_obsolete():
            last_version = updater.get_latest_version_available()
            self.logger.info(
                u"Current client version (%s) is obsolete, latest is %s" %
                (CURRENT_CLIENT_VERSION, last_version))

            if self.cfg.get(u"User Defined Options", "auto_update") == u'True':
                user_choice = True
            else:
                # If cfg param 'auto_update' is off, prompt user to perform update
                user_choice = updater.prompt_user_for_update(
                    self._ui_controller)

            if user_choice:
                raise UpdateRequestedException()
            elif updater.is_update_mandatory():
                raise MandatoryUpdateDeniedException()

        else:
            # Remove update data
            updater.flush_update_file()

    def _schedule_a_start(self):
        """Schedule a restart of the application.
        """
        # TODO: move this to the Application layer
        if self.restart_after_time > 0:
            self._scheduler.schedule_action(self.connect,
                                            name='START',
                                            minutes=self.restart_after_time)
            self.logger.info('Client schedules a connect in %d minutes' %
                             self.restart_after_time)

    def unschedule_start(self):
        """Unschedule a restart of the application, if there is any.
        """
        # TODO: move this to the Application layer
        self._scheduler.unschedule_action(self.connect)
        self.logger.debug('Removing scheduled connect')

    def terminate(self, terminate_ui=True):
        """Main termination routine.

        Makes all threads stop and releases all acquired resources.
        """
        self.logger.debug(u'Terminating Core...')
        self._client_facade._set_zombie()
        self._scheduler.terminate()
        if self.queue is not None:
            self.logger.debug(u"Aborting current operations...")
            self.queue.terminate()
            self.logger.debug(u"Current operations aborted.")
        if self._server_session is not None:
            self._server_session.terminate()
        if self.FSWatcher is not None:
            self.FSWatcher.terminate()
        self.logger.debug(u'Core terminated.')

    def connect(self):
        """Connect to the server.
        """
        self.unschedule_start()
        self._server_session.connect()

    def setup_ui(self, ui_class):
        """Factory method for creating user interface instances.

        Given a UI class, returns an instance of such class. Some basic
        setup is performed on the created object.
        It's needed only once.

        @param ui_class:
                    Any UI class in the filerockclient.ui package.
        """
        return ui_class.initUI(self._zombie_client_facade)

    def register_ui(self, ui):
        """Register a user interface object for interacting with the
        client.

        After a UI instance has been created by setup_ui(), it can be
        registered with this method. The UI gets linked to the client
        and can both receive messages and send queries/operations.

        @param ui:
                    Instance of any UI class in the filerockclient.ui
                    package.
        """
        ui.setClient(self._client_facade)
        self._ui_controller.register_ui(ui)