Beispiel #1
0
    def __handle_renamed_removed_store(self):
        """
        searches the parents directory for renamed or removed stores
        """
        #print self.__parent_path
        #print self.__config_file_name
        #print ".."
        config_paths = self.__file_system.find_files(self.__parent_path, self.__config_file_name)
        #print "config paths: " + ",".join(config_paths)
        new_name = ""
        for path in config_paths:
            reader = ConfigWrapper(path)
            #print "found: " + path
            #print self.__id + ", " + reader.get_store_id()
            
            if self.__id == reader.get_store_id():
                new_name = path.split("/")[-3]
                #print "new name: " + new_name

        if new_name == "":      ## removed
            ## delete describing_nav directors
            #self.remove()
            self.emit(QtCore.SIGNAL("removed(PyQt_PyObject)"), self)
        else:                   ## renamed
            self.__path = self.__parent_path + "/" + new_name
            self.__navigation_path = self.__path + "/" + self.__navigation_dir_name
            self.__describing_nav_path = self.__path + "/" + self.__describing_nav_dir_name
            self.__categorising_nav_path = self.__path + "/" + self.__categorising_nav_dir_name
            ## update all links in windows: absolute links only
            #if self.__file_system.get_os() == EOS.Windows:
                #print "rebuild"
                #self.rebuild()
            #print "emit: " + self.__parent_path + "/" + new_name
            self.emit(QtCore.SIGNAL("renamed(PyQt_PyObject, QString)"), self, self.__parent_path + "/" + new_name)
Beispiel #2
0
 def __init__(self, application, path, retag_mode, verbose):
     QtCore.QObject.__init__(self)
     
     self.LOG_LEVEL = logging.INFO
     if verbose:
         self.LOG_LEVEL = logging.DEBUG
     
     self.__log = LogHelper.get_app_logger(self.LOG_LEVEL)
     
     ## create a config object to get the registered store paths
     self.__main_config = ConfigWrapper(TsConstants.CONFIG_PATH)
     
     if self.__main_config is None:
         self.__log.info("No config file found for the given path")
         self.__handle_retag_error()
     else:
         self.__store_path = PathHelper.resolve_store_path(path, self.__main_config.get_store_path_list())
     
     self.__item_name = PathHelper.get_item_name_from_path(path)
     
     ## create the object
     self.__retag_widget = ReTagController(application, self.__store_path, self.__item_name, True, verbose_mode)
     ## connect to the signal(s)
     self.connect(self.__retag_widget, QtCore.SIGNAL("retag_cancel"), self.__handle_retag_cancel)
     self.connect(self.__retag_widget, QtCore.SIGNAL("retag_error"), self.__handle_retag_error)
     self.connect(self.__retag_widget, QtCore.SIGNAL("retag_success"), self.__handle_retag_success)
     ## start the retagging
     self.__retag_widget.start()
Beispiel #3
0
    def __init_configuration(self):
        """
        initializes the configuration. This method is called every time the config file changes
        """
        self.__log.info("initialize configuration")
        if self.__main_config is None:
            self.__main_config = ConfigWrapper(TsConstants.CONFIG_PATH)
            #self.connect(self.__main_config, QtCore.SIGNAL("changed()"), self.__init_configuration)

        self.CURRENT_LANGUAGE = self.__main_config.get_current_language()

        if self.CURRENT_LANGUAGE is None or self.CURRENT_LANGUAGE == "":
            self.CURRENT_LANGUAGE = self.__get_locale_language()

        # switch back to the configured language
        self.change_language(self.CURRENT_LANGUAGE)

        ## connect to all the signals the admin gui is sending
        if self.__admin_dialog is None:
            self.__admin_dialog = StorePreferencesController()
            self.connect(self.__admin_dialog,
                         QtCore.SIGNAL("create_new_store"),
                         self.__handle_new_store)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("rename_desc_tag"),
                         self.__handle_tag_rename)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("rename_cat_tag"),
                         self.__handle_tag_rename)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("retag"),
                         self.__handle_retagging)

            self.connect(self.__admin_dialog, QtCore.SIGNAL("rebuild_store"),
                         self.__handle_store_rebuild)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("rename_store"),
                         self.__handle_store_rename)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("delete_store"),
                         self.__handle_store_delete)

            self.connect(self.__admin_dialog, QtCore.SIGNAL("synchronize"),
                         self.__handle_synchronization)

        self.__admin_dialog.set_main_config(self.__main_config)

        self.__prepare_store_params()
        self.__create_stores()

        ## create a temporary store list
        ## add the desc and cat tags which are needed in the admin-dialog
        tmp_store_list = []
        for current_store_item in self.__main_config.get_stores():
            store_name = current_store_item["path"].split("/").pop()
            current_store_item["desc_tags"] = self.__store_dict[
                store_name].get_tags()
            current_store_item["cat_tags"] = self.__store_dict[
                store_name].get_categorizing_tags()
            tmp_store_list.append(current_store_item)

        self.__admin_dialog.set_store_list(tmp_store_list)
        if self.__main_config.get_first_start():
            self.__admin_dialog.set_first_start(True)
Beispiel #4
0
    def remove_dir(self, store_path, store_id):
        
        filesystem = FileSystemWrapper()
        
        filesystem.delete_dir(store_path)
        
        config = ConfigWrapper("../tsresources/conf/tagstore.cfg")
        config.remove_store(store_id)

        assert(not filesystem.is_directory(store_path))
Beispiel #5
0
    def remove_dir(self, store_path, store_id):

        filesystem = FileSystemWrapper()

        filesystem.delete_dir(store_path)

        config = ConfigWrapper("../tsresources/conf/tagstore.cfg")
        config.remove_store(store_id)

        assert (not filesystem.is_directory(store_path))
Beispiel #6
0
    def move_store(self, store_path, store_id, new_store_path):
        store = self.create_new_store_object(store_path, "storage")

        store.move(new_store_path)
        ## rewrite the config with the new store dir
        config = ConfigWrapper("../tsresources/conf/tagstore.cfg")
        config.rename_store(store_id, new_store_path)

        filesystem = FileSystemWrapper()

        assert (filesystem.is_directory(new_store_path))
Beispiel #7
0
 def move_store(self, store_path, store_id, new_store_path):
     store = self.create_new_store_object(store_path, "storage")
     
     store.move(new_store_path)
     ## rewrite the config with the new store dir
     config = ConfigWrapper("../tsresources/conf/tagstore.cfg")
     config.rename_store(store_id, new_store_path)
     
     filesystem = FileSystemWrapper()
     
     assert(filesystem.is_directory(new_store_path))
Beispiel #8
0
    def __init__(self, application, path, retag_mode, verbose):
        QtCore.QObject.__init__(self)

        self.LOG_LEVEL = logging.INFO
        if verbose:
            self.LOG_LEVEL = logging.DEBUG

        self.__log = LogHelper.get_app_logger(self.LOG_LEVEL)

        ## create a config object to get the registered store paths
        self.__main_config = ConfigWrapper(TsConstants.CONFIG_PATH)

        if self.__main_config is None:
            self.__log.info("No config file found for the given path")
            self.__handle_retag_error()
        else:
            self.__store_path = PathHelper.resolve_store_path(path, self.__main_config.get_store_path_list())

        self.__item_name = PathHelper.get_item_name_from_path(path)

        ## create the object
        self.__retag_widget = ReTagController(application, self.__store_path, self.__item_name, True, verbose_mode)
        ## connect to the signal(s)
        self.connect(self.__retag_widget, QtCore.SIGNAL("retag_cancel"), self.__handle_retag_cancel)
        self.connect(self.__retag_widget, QtCore.SIGNAL("retag_error"), self.__handle_retag_error)
        self.connect(self.__retag_widget, QtCore.SIGNAL("retag_success"), self.__handle_retag_success)
        ## start the retagging
        self.__retag_widget.start()
Beispiel #9
0
    def __init__(self, application, source_store, target_store, auto_sync,
                 verbose):
        QtCore.QObject.__init__(self)

        self.LOG_LEVEL = logging.INFO
        if verbose:
            self.LOG_LEVEL = logging.DEBUG

        self.__log = LogHelper.get_app_logger(self.LOG_LEVEL)

        ## create a config object to get the registered store paths
        self.__main_config = ConfigWrapper(TsConstants.CONFIG_PATH)

        ## create the object
        self.__sync_widget = SyncController(application, source_store,
                                            target_store, auto_sync,
                                            verbose_mode)

        ## connect to the signal(s)
        self.connect(self.__sync_widget, QtCore.SIGNAL("sync_cancel"),
                     self.__handle_sync_cancel)
        self.connect(self.__sync_widget, QtCore.SIGNAL("sync_error"),
                     self.__handle_sync_error)
        self.connect(self.__sync_widget, QtCore.SIGNAL("sync_success"),
                     self.__handle_sync_success)

        ## start the sync controller
        self.__sync_widget.start()
Beispiel #10
0
    def test_remove_store(self):
        """
        create a store
        create an item with tags in the store
        place a "not allowed" item in the navigation hierarchy
        call the "remove" method of the store
        check if everything has been removed properly
        """

        STORE_PATH = "./test_store/"
        STORAGE_DIR = "storage"
        NEW_ITEM_NAME = "test_item"

        store = self.create_new_store_object(STORE_PATH, STORAGE_DIR)

        filesystem = FileSystemWrapper()

        ## place a new item in the store
        file_path = "%s%s/%s" % (STORE_PATH, STORAGE_DIR, NEW_ITEM_NAME)
        filesystem.create_file(file_path)
        config = ConfigWrapper("../tsresources/conf/tagstore.cfg")
        ## write the new store also to the config
        store_id = config.add_new_store(STORE_PATH)
        ## now tag it manually
        store.add_item_with_tags(NEW_ITEM_NAME, ["should", "not"],
                                 ["get", "this"])

        ## create a new file in a tag-dir to test if it is removed as well
        file_path = "%s%s/%s" % (STORE_PATH, "descriptions/should",
                                 "i_should_not_be_here.file")
        filesystem.create_file(file_path)

        ## now remove the store
        store.remove()
        ##check if this is done properly
        ##this one should not exist ...
        assert (not filesystem.path_exists(file_path))
        ## this path should still exist
        assert (filesystem.path_exists(STORE_PATH))

        self.remove_dir(STORE_PATH, store_id)
        assert (not filesystem.path_exists(STORE_PATH))

        config.remove_store(store_id)
Beispiel #11
0
    def __init_store(self):
        """
        initializes the store paths, config reader, file system watcher without instantiation of a new object
        """
        self.__name = self.__path.split("/")[-1]
        self.__parent_path = self.__path[:len(self.__path)-len(self.__name)]
        self.__tags_file_path = self.__path + "/" + self.__tags_file_name
        self.__sync_tags_file_path = self.__path + "/" + TsConstants.DEFAULT_STORE_CONFIG_DIR + "/" + self.__sync_tags_file_name
        self.__config_path = self.__path + "/" + self.__config_file_name
        self.__vocabulary_path = self.__path + "/" + self.__vocabulary_file_name #TsConstants.STORE_CONFIG_DIR + "/" + TsConstants.STORE_VOCABULARY_FILENAME
        self.__watcher_path = self.__path + "/" + self.__storage_dir_name
        self.__navigation_path = self.__path + "/" + self.__navigation_dir_name
        self.__describing_nav_path = self.__path + "/" + self.__describing_nav_dir_name
        self.__categorising_nav_path = self.__path + "/" + self.__categorising_nav_dir_name
        config_file_name = unicode(self.__config_path.split("/")[-1])
        self.__temp_progress_path = unicode(self.__config_path[:len(self.__config_path)-len(config_file_name)-1])
        
        self.__tag_wrapper = TagWrapper(self.__tags_file_path)
        self.__sync_tag_wrapper = TagWrapper(self.__sync_tags_file_path)
        
        ## update store id to avoid inconsistency
        config_wrapper = ConfigWrapper(self.__path + "/" + self.__config_file_name)#self.__tags_file_name)
        config_wrapper.set_store_id(self.__id)
        self.__vocabulary_wrapper = VocabularyWrapper(self.__vocabulary_path)
        self.connect(self.__vocabulary_wrapper, QtCore.SIGNAL("changed"), self.__handle_vocabulary_changed)
        self.__store_config_wrapper = ConfigWrapper(self.__config_path)
        self.connect(self.__store_config_wrapper, QtCore.SIGNAL("changed()"), self.__handle_store_config_changed)
        
        if len(self.__name) == 0:
            self.__name = self.__path[:self.__path.rfind("/")]
        
        if not self.__is_android_store():
            # no activity is required on android tag stores
            self.__watcher.addPath(self.__parent_path)
            self.__watcher.addPath(self.__watcher_path)
        
        ## all necessary files and dirs should have been created now - so init the logger
        self.__log = LogHelper.get_store_logger(self.__path, logging.INFO) 
        self.__log.info("parent_path: '%s'" % self.__name)

        ## handle offline changes
        self.__handle_unfinished_operation()
        self.__handle_file_expiry()
        self.__handle_file_changes(self.__watcher_path)
Beispiel #12
0
    def test_remove_store(self):
        """
        create a store
        create an item with tags in the store
        place a "not allowed" item in the navigation hierarchy
        call the "remove" method of the store
        check if everything has been removed properly
        """
        
        STORE_PATH = "./test_store/"
        STORAGE_DIR = "storage"
        NEW_ITEM_NAME = "test_item"
        
        store = self.create_new_store_object(STORE_PATH, STORAGE_DIR)
        
        filesystem = FileSystemWrapper()

         ## place a new item in the store
        file_path = "%s%s/%s" % (STORE_PATH, STORAGE_DIR, NEW_ITEM_NAME)
        filesystem.create_file(file_path)
        config = ConfigWrapper("../tsresources/conf/tagstore.cfg")
        ## write the new store also to the config
        store_id = config.add_new_store(STORE_PATH)
        ## now tag it manually
        store.add_item_with_tags(NEW_ITEM_NAME, ["should", "not"], ["get", "this"])
        
        ## create a new file in a tag-dir to test if it is removed as well
        file_path = "%s%s/%s" % (STORE_PATH, "descriptions/should", "i_should_not_be_here.file")
        filesystem.create_file(file_path)
        
        ## now remove the store 
        store.remove()
        ##check if this is done properly
        ##this one should not exist ...
        assert(not filesystem.path_exists(file_path))
        ## this path should still exist
        assert(filesystem.path_exists(STORE_PATH))

        self.remove_dir(STORE_PATH, store_id)
        assert(not filesystem.path_exists(STORE_PATH))

        config.remove_store(store_id)
Beispiel #13
0
class ApplicationController(QtCore.QObject):
    """
    a small helper class to launch the retag-dialog as a standalone application
    this helper connects to the signals emitted by the retag controller and does the handling
    """

    def __init__(self, application, path, retag_mode, verbose):
        QtCore.QObject.__init__(self)

        self.LOG_LEVEL = logging.INFO
        if verbose:
            self.LOG_LEVEL = logging.DEBUG

        self.__log = LogHelper.get_app_logger(self.LOG_LEVEL)

        ## create a config object to get the registered store paths
        self.__main_config = ConfigWrapper(TsConstants.CONFIG_PATH)

        if self.__main_config is None:
            self.__log.info("No config file found for the given path")
            self.__handle_retag_error()
        else:
            self.__store_path = PathHelper.resolve_store_path(path, self.__main_config.get_store_path_list())

        self.__item_name = PathHelper.get_item_name_from_path(path)

        ## create the object
        self.__retag_widget = ReTagController(application, self.__store_path, self.__item_name, True, verbose_mode)
        ## connect to the signal(s)
        self.connect(self.__retag_widget, QtCore.SIGNAL("retag_cancel"), self.__handle_retag_cancel)
        self.connect(self.__retag_widget, QtCore.SIGNAL("retag_error"), self.__handle_retag_error)
        self.connect(self.__retag_widget, QtCore.SIGNAL("retag_success"), self.__handle_retag_success)
        ## start the retagging
        self.__retag_widget.start()

    def __handle_retag_error(self):
        """
        exit the program if there is an error
        """
        sys.exit(-1)

    def __handle_retag_success(self):
        """
        exit the application gracefully
        """
        sys.exit(0)

    def __handle_retag_cancel(self):
        """
        exit the application gracefully
        """
        sys.exit(0)
Beispiel #14
0
    def __create_target_store(self, target_store):
        """
        create the target store object
        """

        # construct target store config object
        self.__target_store_config = ConfigWrapper(target_store)
        if self.__target_store_config is None:
            self.__emit_not_syncable(
                self.trUtf8("No target store found for the given path"))
            return

        # construct target store object
        self.__target_store = Store(
            self.__target_store_config.get_store_id(), target_store,
            self.STORE_CONFIG_DIR + "/" + self.STORE_CONFIG_FILE_NAME,
            self.STORE_CONFIG_DIR + "/" + self.STORE_TAGS_FILE_NAME,
            self.STORE_CONFIG_DIR + "/" + self.STORE_VOCABULARY_FILE_NAME,
            self.STORE_NAVIGATION_DIRS, self.STORE_STORAGE_DIRS,
            self.STORE_DESCRIBING_NAV_DIRS, self.STORE_CATEGORIZING_NAV_DIRS,
            self.STORE_EXPIRED_DIRS, self.__main_config.get_expiry_prefix())
        self.__target_store.init()
Beispiel #15
0
class ApplicationController(QtCore.QObject):
    """
    a small helper class to launch the retag-dialog as a standalone application
    this helper connects to the signals emitted by the retag controller and does the handling
    """
    def __init__(self, application, path, retag_mode, verbose):
        QtCore.QObject.__init__(self)
        
        self.LOG_LEVEL = logging.INFO
        if verbose:
            self.LOG_LEVEL = logging.DEBUG
        
        self.__log = LogHelper.get_app_logger(self.LOG_LEVEL)
        
        ## create a config object to get the registered store paths
        self.__main_config = ConfigWrapper(TsConstants.CONFIG_PATH)
        
        if self.__main_config is None:
            self.__log.info("No config file found for the given path")
            self.__handle_retag_error()
        else:
            self.__store_path = PathHelper.resolve_store_path(path, self.__main_config.get_store_path_list())
        
        self.__item_name = PathHelper.get_item_name_from_path(path)
        
        ## create the object
        self.__retag_widget = ReTagController(application, self.__store_path, self.__item_name, True, verbose_mode)
        ## connect to the signal(s)
        self.connect(self.__retag_widget, QtCore.SIGNAL("retag_cancel"), self.__handle_retag_cancel)
        self.connect(self.__retag_widget, QtCore.SIGNAL("retag_error"), self.__handle_retag_error)
        self.connect(self.__retag_widget, QtCore.SIGNAL("retag_success"), self.__handle_retag_success)
        ## start the retagging
        self.__retag_widget.start()
        
    def __handle_retag_error(self):
        """
        exit the program if there is an error
        """
        sys.exit(-1)
        
    def __handle_retag_success(self):
        """
        exit the application gracefully
        """
        sys.exit(0)

    def __handle_retag_cancel(self):
        """
        exit the application gracefully
        """
        sys.exit(0)
Beispiel #16
0
    def __init_configuration(self):
        """
        initializes the configuration. This method is called every time the config file changes
        """
        self.__log.info("initialize configuration")
        if self.__main_config is None:
            self.__main_config = ConfigWrapper(TsConstants.CONFIG_PATH)
            #self.connect(self.__main_config, QtCore.SIGNAL("changed()"), self.__init_configuration)
        
        self.CURRENT_LANGUAGE = self.__main_config.get_current_language();
        
        if self.CURRENT_LANGUAGE is None or self.CURRENT_LANGUAGE == "":
            self.CURRENT_LANGUAGE = self.__get_locale_language()
        
        # switch back to the configured language
        self.change_language(self.CURRENT_LANGUAGE)

        ## connect to all the signals the admin gui is sending 
        if self.__admin_dialog is None:
            self.__admin_dialog = StorePreferencesController()
            self.connect(self.__admin_dialog, QtCore.SIGNAL("create_new_store"), self.__handle_new_store)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("rename_desc_tag"), self.__handle_tag_rename)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("rename_cat_tag"), self.__handle_tag_rename)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("retag"), self.__handle_retagging)
            
            self.connect(self.__admin_dialog, QtCore.SIGNAL("rebuild_store"), self.__handle_store_rebuild)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("rename_store"), self.__handle_store_rename)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("delete_store"), self.__handle_store_delete)

            self.connect(self.__admin_dialog, QtCore.SIGNAL("synchronize"), self.__handle_synchronization)
            
        self.__admin_dialog.set_main_config(self.__main_config)
        
        
        self.__prepare_store_params()
        self.__create_stores()
        
        ## create a temporary store list
        ## add the desc and cat tags which are needed in the admin-dialog
        tmp_store_list = []
        for current_store_item in self.__main_config.get_stores():
            store_name = current_store_item["path"].split("/").pop()
            current_store_item["desc_tags"] = self.__store_dict[store_name].get_tags()
            current_store_item["cat_tags"] = self.__store_dict[store_name].get_categorizing_tags()
            tmp_store_list.append(current_store_item)
        
        self.__admin_dialog.set_store_list(tmp_store_list)
        if self.__main_config.get_first_start():
            self.__admin_dialog.set_first_start(True)
Beispiel #17
0
    def test_create_store_move_and_delete_dir(self):
        """
        1. create a new store
        2. store a new item in the store - manually
        3. move the store to a new location
        4. delete the whole directory
        """

        filesystem = FileSystemWrapper()

        NEW_ITEM_NAME = "test_item"

        STORE_PATH = "./test_store/"
        STORAGE_DIR = "storage"

        store = self.create_new_store_object(STORE_PATH, STORAGE_DIR)

        ## place a new item in the store
        file_path = "%s%s/%s" % (STORE_PATH, STORAGE_DIR, NEW_ITEM_NAME)
        filesystem.create_file(file_path)
        config = ConfigWrapper("../tsresources/conf/tagstore.cfg")
        ## write the new store also to the config
        store_id = config.add_new_store(STORE_PATH)

        ## now tag it manually
        store.add_item_with_tags(NEW_ITEM_NAME, ["should", "not"],
                                 ["get", "this"])

        ## now the tag "should" must be a folde in the describing dir
        #        path_to_check = "%sdescribing/should" % STORE_PATH
        path_to_check = "%sdescriptions/should" % STORE_PATH
        print path_to_check
        assert (filesystem.is_directory(path_to_check))

        self.move_store("./test_store/", store_id, "./new_test_store/")
        self.remove_dir("./new_test_store/", store_id)
Beispiel #18
0
    def test_create_store_move_and_delete_dir(self):
        
        """
        1. create a new store
        2. store a new item in the store - manually
        3. move the store to a new location
        4. delete the whole directory
        """
        
        filesystem = FileSystemWrapper()
        
        NEW_ITEM_NAME = "test_item"
        
        STORE_PATH = "./test_store/"
        STORAGE_DIR = "storage"
        
        store = self.create_new_store_object(STORE_PATH, STORAGE_DIR)
        
        ## place a new item in the store
        file_path = "%s%s/%s" % (STORE_PATH, STORAGE_DIR, NEW_ITEM_NAME)
        filesystem.create_file(file_path)
        config = ConfigWrapper("../tsresources/conf/tagstore.cfg")
        ## write the new store also to the config
        store_id = config.add_new_store(STORE_PATH)
        
        ## now tag it manually
        store.add_item_with_tags(NEW_ITEM_NAME, ["should", "not"], ["get", "this"])
        
        ## now the tag "should" must be a folde in the describing dir
#        path_to_check = "%sdescribing/should" % STORE_PATH
        path_to_check = "%sdescriptions/should" % STORE_PATH
        print path_to_check 
        assert(filesystem.is_directory(path_to_check))
        
        self.move_store("./test_store/", store_id, "./new_test_store/")
        self.remove_dir("./new_test_store/", store_id)
Beispiel #19
0
    def __create_target_store(self, target_store):
        """
        create the target store object
        """

        # construct target store config object
        self.__target_store_config = ConfigWrapper(target_store)
        if self.__target_store_config is None:    
            self.__emit_not_syncable(self.trUtf8("No target store found for the given path"))
            return
        
        # construct target store object
        self.__target_store = Store(self.__target_store_config.get_store_id(), target_store, 
              self.STORE_CONFIG_DIR + "/" + self.STORE_CONFIG_FILE_NAME,
              self.STORE_CONFIG_DIR + "/" + self.STORE_TAGS_FILE_NAME,
              self.STORE_CONFIG_DIR + "/" + self.STORE_VOCABULARY_FILE_NAME,
              self.STORE_NAVIGATION_DIRS,
              self.STORE_STORAGE_DIRS, 
              self.STORE_DESCRIBING_NAV_DIRS,
              self.STORE_CATEGORIZING_NAV_DIRS,
              self.STORE_EXPIRED_DIRS,
              self.__main_config.get_expiry_prefix())
        self.__target_store.init()        
Beispiel #20
0
class SyncController(QtCore.QObject):
    def __init__(self,
                 application,
                 source_store_path,
                 target_store_path,
                 auto_sync,
                 verbose=False):
        """
        initialize the controller
        """
        QtCore.QObject.__init__(self)

        # init components
        self.__application = application
        self.__source_store_path = source_store_path
        self.__target_store_path = target_store_path
        self.__auto_sync = auto_sync

        self.__main_config = None
        self.__store_config = None
        self.__source_store = None
        self.__target_store = None
        self.__sync_dialog = None
        self.__conflict_file_list = None
        self.__source_items = None
        self.__target_items = None
        self.__target_sync_items = None

        # default values
        self.STORE_CONFIG_DIR = TsConstants.DEFAULT_STORE_CONFIG_DIR
        self.STORE_CONFIG_FILE_NAME = TsConstants.DEFAULT_STORE_CONFIG_FILENAME
        self.STORE_TAGS_FILE_NAME = TsConstants.DEFAULT_STORE_TAGS_FILENAME
        self.STORE_VOCABULARY_FILE_NAME = TsConstants.DEFAULT_STORE_VOCABULARY_FILENAME
        self.STORE_SYNC_FILE_NAME = TsConstants.DEFAULT_STORE_SYNC_TAGS_FILENAME

        # load translators
        locale = unicode(QtCore.QLocale.system().name())[0:2]
        self.__translator = QtCore.QTranslator()
        if self.__translator.load("ts_" + locale + ".qm", "tsresources/"):
            self.__application.installTranslator(self.__translator)

        #get dir names for all available languages
        self.CURRENT_LANGUAGE = self.trUtf8("en")
        self.STORE_STORAGE_DIRS = []
        self.STORE_DESCRIBING_NAV_DIRS = []
        self.STORE_CATEGORIZING_NAV_DIRS = []
        self.STORE_EXPIRED_DIRS = []
        self.STORE_NAVIGATION_DIRS = []
        self.SUPPORTED_LANGUAGES = TsConstants.DEFAULT_SUPPORTED_LANGUAGES
        self.__store_dict = {}

        for lang in self.SUPPORTED_LANGUAGES:
            self.change_language(lang)
            self.STORE_NAVIGATION_DIRS.append(self.trUtf8("navigation"))
            self.STORE_STORAGE_DIRS.append(
                self.trUtf8("storage"))  #self.STORE_STORAGE_DIR_EN))
            self.STORE_DESCRIBING_NAV_DIRS.append(self.trUtf8(
                "descriptions"))  #self.STORE_DESCRIBING_NAVIGATION_DIR_EN))
            self.STORE_CATEGORIZING_NAV_DIRS.append(self.trUtf8(
                "categories"))  #self.STORE_CATEGORIZING_NAVIGATION_DIR_EN))
            self.STORE_EXPIRED_DIRS.append(
                self.trUtf8("expired_items"))  #STORE_EXPIRED_DIR_EN))

        ## reset language
        self.change_language(self.CURRENT_LANGUAGE)

        # init logger component
        self.LOG_LEVEL = logging.INFO
        if verbose:
            self.LOG_LEVEL = logging.DEBUG

        # get logger
        self.__log = LogHelper.get_app_logger(self.LOG_LEVEL)

    def start(self):
        """
        call this method to launch the sync dialog
        """
        self.__init_configuration()

    def __init_configuration(self):
        """
        initializes the configuration
        """

        # informal debug
        self.__log.info("__init_configuration")

        # construct config wrapper
        self.__main_config = ConfigWrapper(TsConstants.CONFIG_PATH)
        if self.__main_config is None:
            self.__emit_not_syncable(
                self.trUtf8("No config file found for the given path"))
            return

        search_source_path = False
        found_source_path = False
        if self.__source_store_path != None and self.__source_store_path != "":
            search_source_path = True

        search_target_path = False
        found_target_path = False
        if self.__target_store_path != None and self.__target_store_path != "":
            search_target_path = True

        ## create a temporary store list
        ## add the desc and cat tags which are needed in the admin-dialog
        tmp_store_list = []
        store_list = self.__main_config.get_stores()

        # add android store
        # when registered
        android_source_path = self.__main_config.get_android_store_path()
        if android_source_path != None and android_source_path != "":
            store_item = {}
            store_item["path"] = android_source_path
            store_item["name"] = "Android"
            store_list.append(store_item)

        # enumerate all stores and add their names and paths
        store_name = None
        for current_store_item in store_list:

            if current_store_item.has_key("name"):
                store_name = current_store_item["name"]
            else:
                store_name = current_store_item["path"].split("/").pop()

            store_path = current_store_item["path"]
            current_store_item["name"] = store_name
            current_store_item["path"] = store_path

            tmp_store_list.append(current_store_item)
            # find source target list
            if search_source_path:
                if store_path == self.__source_store_path:
                    found_source_path = True

            if search_target_path:
                if store_path == self.__target_store_path:
                    found_target_path = True

        if search_source_path and found_source_path == False:
            # source store is not registered
            self.__emit_not_syncable(
                self.trUtf8("Source tagstore not registered in main config"))
            return

        if search_target_path and found_target_path == False:
            # source store is not registered
            self.__emit_not_syncable(
                self.trUtf8("Target tagstore not registered in main config"))
            return

        if self.__sync_dialog is None:
            self.__sync_dialog = SyncDialogController(tmp_store_list,
                                                      self.__source_store_path,
                                                      self.__target_store_path,
                                                      self.__auto_sync)
            self.__sync_dialog.get_view().setModal(True)
            #self.__tag_dialog.set_parent(self.sender().get_view())
            self.__sync_dialog.connect(self.__sync_dialog,
                                       QtCore.SIGNAL("sync_store"),
                                       self.__sync_store_action)
            self.__sync_dialog.connect(self.__sync_dialog,
                                       QtCore.SIGNAL("sync_conflict"),
                                       self.__handle_resolve_conflict)
            self.__sync_dialog.connect(self.__sync_dialog,
                                       QtCore.SIGNAL("handle_cancel()"),
                                       self.__handle_sync_cancel)

        self.__sync_dialog.show_dialog()
        if self.__auto_sync:
            self.__sync_dialog.start_auto_sync()

    def __handle_resolve_conflict(self, file_item, action):

        # remove item from conflict list
        self.__conflict_file_list.remove(file_item)

        source_item = file_item["source_item"]
        target_item = file_item["target_item"]
        source_store = file_item["source_store"]
        target_store = file_item["target_store"]
        target_file_path = self.__create_target_file_path(
            target_store, target_item)

        self.__log.info(
            "handle_resolve_conclict: source_item %s target_item %s action % s"
            % (source_item, target_item, action))

        if action == "replace":
            # sync the file and their tags
            self.__sync_conflict_file(source_store, target_store, source_item,
                                      target_item, target_file_path)

        # launch conflict dialog
        self.__show_conflict_dialog()

    def __remove_lock_file(self):
        """
        removes the lock from the affected tagstores
        """

        self.__source_store.remove_sync_lock_file()
        self.__target_store.remove_sync_lock_file()

    def __create_lock_file(self):
        """
        creates the lock files for the affected tagstores
        """

        # check if the store is in use by another sync operation
        if self.__source_store.is_sync_active(
        ) or self.__target_store.is_sync_active():
            # sync is already active
            return False

        # create lock file in source tagstore
        result = self.__source_store.create_sync_lock_file()
        if not result:
            # failed to create lock file
            return result

        # create lock in target tagstore
        result = self.__target_store.create_sync_lock_file()
        if not result:
            # delete lock file from source tagstore
            self.__source_store.remove_sync_lock_file()

        # done
        return result

    def __show_conflict_dialog(self):
        """
        displays the conflict dialogs when there are one or more conflicts
        """

        while len(self.__conflict_file_list) > 0:

            # get first item
            current_item = self.__conflict_file_list[0]

            # extract paramters
            source_item = current_item["source_item"]
            target_item = current_item["target_item"]
            source_store = current_item["source_store"]
            target_store = current_item["target_store"]
            target_items = current_item["target_items"]
            target_sync_items = current_item["target_sync_items"]

            # do we need to sync
            sync_success = self.__sync_item(source_store, target_store,
                                            target_items, target_sync_items,
                                            source_item, False)
            if sync_success:
                # remove item
                # conflict has been solved by a previous conflict resolution
                self.__conflict_file_list.remove(current_item)
                continue

            # update status dialog
            message = ("Syncing %s" % source_item)
            self.__sync_dialog.set_status_msg(message)

            # replace dialog message
            message = ("Do you want to replace file %s with %s" %
                       (self.__get_full_file_path(target_store, target_item),
                        self.__get_full_file_path(source_store, source_item)))
            self.__sync_dialog.show_conflict_dialog("Conflict", message,
                                                    current_item)

            return
        # end while

        # conflict list empty
        msg = "Sync completed on " + datetime.datetime.utcnow().strftime(
            "%Y-%m-%d %H:%M:%S")

        # flush all changes
        self.__flush_changes()

        self.__sync_dialog.set_status_msg(msg)
        self.__sync_dialog.toggle_sync_button(True)
        self.__sync_dialog.set_close_button_text(self.trUtf8("Finish"))
        self.__remove_lock_file()

    def __create_source_store(self, source_store):
        """
        create the source store object
        """

        # construct config wrapper for the tagstore
        self.__store_config = ConfigWrapper(source_store)
        if self.__store_config is None:
            self.__emit_not_syncable(
                self.trUtf8("No source store found for the given path"))
            return

        # construct store object
        self.__source_store = Store(
            self.__store_config.get_store_id(), source_store,
            self.STORE_CONFIG_DIR + "/" + self.STORE_CONFIG_FILE_NAME,
            self.STORE_CONFIG_DIR + "/" + self.STORE_TAGS_FILE_NAME,
            self.STORE_CONFIG_DIR + "/" + self.STORE_VOCABULARY_FILE_NAME,
            self.STORE_NAVIGATION_DIRS, self.STORE_STORAGE_DIRS,
            self.STORE_DESCRIBING_NAV_DIRS, self.STORE_CATEGORIZING_NAV_DIRS,
            self.STORE_EXPIRED_DIRS, self.__main_config.get_expiry_prefix())
        self.__source_store.init()

    def __flush_changes(self):

        if self.__source_store != None:
            self.__source_store.finish_sync()

        if self.__target_store != None:
            self.__target_store.finish_sync()

    def __create_target_store(self, target_store):
        """
        create the target store object
        """

        # construct target store config object
        self.__target_store_config = ConfigWrapper(target_store)
        if self.__target_store_config is None:
            self.__emit_not_syncable(
                self.trUtf8("No target store found for the given path"))
            return

        # construct target store object
        self.__target_store = Store(
            self.__target_store_config.get_store_id(), target_store,
            self.STORE_CONFIG_DIR + "/" + self.STORE_CONFIG_FILE_NAME,
            self.STORE_CONFIG_DIR + "/" + self.STORE_TAGS_FILE_NAME,
            self.STORE_CONFIG_DIR + "/" + self.STORE_VOCABULARY_FILE_NAME,
            self.STORE_NAVIGATION_DIRS, self.STORE_STORAGE_DIRS,
            self.STORE_DESCRIBING_NAV_DIRS, self.STORE_CATEGORIZING_NAV_DIRS,
            self.STORE_EXPIRED_DIRS, self.__main_config.get_expiry_prefix())
        self.__target_store.init()

    def __get_file_items_with_sync_tag(self):
        """
        returns all files which have the associated sync tag
        """

        # get source items
        source_items = self.__source_store.get_items()

        # get current sync tag
        sync_tag = self.__main_config.get_sync_tag()

        # build empty result list
        source_sync_items = []

        # enumerate all items
        for source_item in source_items:

            if self.__has_sync_tag(self.__source_store, source_item, sync_tag):
                # item is tagged with sync tag
                source_sync_items.append(source_item)
                continue

        # done
        return source_sync_items

    def __prepare_sync(self, source_store, target_store):
        """
        prepares the sync
        """

        # initialize the store objects
        self.__init_stores(source_store, target_store)

        # get sync style
        android_sync = self.__source_store.is_android_store(
        ) or self.__target_store.is_android_store()

        # get source items
        if android_sync:
            self.__source_items = self.__get_file_items_with_sync_tag()
        else:
            self.__source_items = self.__source_store.get_items()

        # get target items
        self.__target_items = self.__target_store.get_items()

        # get target sync items
        self.__target_sync_items = self.__target_store.get_sync_items()

    def __sync_store_action(self, source_store, target_store):
        """
        initializes the sync
        """

        # conflict list
        self.__conflict_file_list = []

        # prepare the sync
        self.__prepare_sync(source_store, target_store)

        # now create the lock files
        lock_file = self.__create_lock_file()
        if not lock_file:
            self.__log.info(
                "another sync is in progress please wait until it is finished")
            self.__sync_dialog.set_status_msg(
                "Another sync is pending, please wait until it is finished")
            self.__sync_dialog.set_close_button_text(self.trUtf8("Finish"))
            return

        # start with source tagstore -> target tagstore
        self.__handle_sync()

        # switch stores
        self.__prepare_sync(target_store, source_store)

        # push changes from target store to source tagstore
        self.__handle_sync()

        self.__log.info("Number of conflicts %d" %
                        (len(self.__conflict_file_list)))

        # launch conflict dialog
        self.__show_conflict_dialog()

    def __handle_sync(self):
        """
        executes a sync
        """

        # start the sync
        self.__start_sync(self.__source_store, self.__target_store,
                          self.__source_items, self.__target_items,
                          self.__target_sync_items)

    def __sync_item(self,
                    source_store,
                    target_store,
                    target_items,
                    target_sync_items,
                    source_item,
                    add_conflict_list=True):

        # is there such an item in the target tagstore
        target_item = self.__find_item_in_store(target_items, source_item)

        # does the file exist in the target tagstore
        if target_item != None:
            return self.__sync_existing_item(source_store, target_store,
                                             target_items, target_sync_items,
                                             source_item, target_item,
                                             add_conflict_list)
        else:
            return self.__sync_new_item(source_store, target_store,
                                        target_items, target_sync_items,
                                        source_item, add_conflict_list)

    def __sync_new_item(self, source_store, target_store, target_items,
                        target_sync_items, source_item, add_conflict_list):

        # file does not exist
        # was it already synced once?
        target_sync_item = self.__find_item_in_store(target_sync_items,
                                                     source_item)
        if target_sync_item:
            #the file was already synced once, skipping
            self.__log.info("[SKIP] File '%s' was already synced once" %
                            target_sync_item)
            return True

        # file was not synced before, lets check if it exists in the target destination store
        # create target path
        target_file_path = self.__create_target_file_path(
            target_store, source_item)

        # check if file already exists
        if not os.path.exists(target_file_path):
            # file does not yet exist
            self.__log.info("[SYNC] New File: '%s' is synced" % source_item)
            self.__sync_new_file(source_store, target_store, source_item,
                                 target_file_path)
            return True

        # create target item
        target_item = self.__create_target_file_item(target_store, source_item)

        # the file already exists
        # is it the same file
        files_equal = self.__are_files_equal(source_store, target_store,
                                             source_item, target_item)
        if files_equal:
            # file is present, just sync the tags
            self.__log.info(
                "[SYNC] File '%s' already present in target, syncing tags only"
                % source_item)
            self.__sync_new_file(source_store,
                                 target_store,
                                 source_item,
                                 target_file_path,
                                 copy_file=False)
            return False

        # sync conflict
        self.__log.info("[Conflict] File: '%s' already exists" %
                        target_file_path)
        if add_conflict_list:
            self.__add_conflict_item(source_store, target_store, target_items,
                                     target_sync_items, source_item,
                                     target_item)
        return False

    def __sync_existing_item(self, source_store, target_store, target_items,
                             target_sync_items, source_item, target_item,
                             add_conflict_list):
        """
        syncs an existing item
        """

        # check if the source file is equal
        files_equal = self.__are_files_equal(source_store, target_store,
                                             source_item, target_item)
        if files_equal:
            # files are equal
            # sync tags
            self.__log.info("[SYNC] Tags of file '%s' are synced" %
                            source_item)
            self.__sync_new_tags(source_store, target_store, source_item,
                                 target_item)
            return True

        # okay files are not equal, lets get a sync date
        target_sync_item = (target_item in target_sync_items)
        if not target_sync_item:
            # there is no sync item for file
            self.__log.info(
                "[Conflict] File '%s' -> %s' was added in the tagstore simultaneously"
                % (source_item, target_item))
            if add_conflict_list:
                self.__add_conflict_item(source_store, target_store,
                                         target_items, target_sync_items,
                                         source_item, target_item)
            return False

        # get sync time
        str_sync_gm_time = target_store.get_sync_file_timestamp(target_item)
        sync_gm_time = time.strptime(str_sync_gm_time, "%Y-%m-%d %H:%M:%S")

        # get source modification time
        mod_time = os.path.getmtime(
            self.__get_full_file_path(source_store, source_item))
        source_gm_time = time.gmtime(mod_time)

        # get target modification time
        mod_time = os.path.getmtime(
            self.__get_full_file_path(target_store, target_item))
        target_gm_time = time.gmtime(mod_time)

        # was source file modified
        if source_gm_time <= sync_gm_time:
            # file was not modified since last sync
            # sync new tags
            self.__log.info(
                "[SYNC] No source modification, tags of file '%s' are synced" %
                source_item)
            self.__sync_new_tags(source_store, target_store, source_item,
                                 target_item)
            return True

        # source modified, lets check target file
        if target_gm_time <= sync_gm_time:
            # target file was not modified
            self.__log.info("[SYNC] Updating file '%s' and tags" % source_item)
            shutil.copy2(self.__get_full_file_path(source_store, source_item),
                         self.__get_full_file_path(target_store, target_item))
            self.__sync_new_tags(source_store, target_store, source_item,
                                 target_item)
            return True

        # source and target file have been modified, do their tags match
        if self.__are_all_tags_equal(source_store, target_store, source_item,
                                     target_item):
            # sync the file
            self.__log.info("[Conflict] Both files have been modified '%s'" %
                            target_item)
            if add_conflict_list:
                self.__add_conflict_item(source_store, target_store,
                                         target_items, target_sync_items,
                                         source_item, target_item)
            return False

        # both files and tags are modified
        self.__log.info("[Conflict] Both files and tags are modified '%s'" %
                        target_item)
        if add_conflict_list:
            self.__add_conflict_item(source_store, target_store, target_items,
                                     target_sync_items, source_item,
                                     target_item)
        return False

    def __start_sync(self, source_store, target_store, source_items,
                     target_items, target_sync_items):
        """
        starts the sync
        """

        for source_item in source_items:

            # sync item
            self.__log.info("[SYNC] Current Item: %s" % source_item)
            self.__sync_item(source_store, target_store, target_items,
                             target_sync_items, source_item)

    def __are_all_tags_equal(self, source_store, target_store, source_item,
                             target_item):
        """
        checks if all tags from the source item and target item are equal
        """

        source_describing_tags = source_store.get_describing_tags_for_item(
            source_item)
        target_describing_tags = target_store.get_describing_tags_for_item(
            target_item)

        if source_describing_tags != target_describing_tags:
            return False

        # get categorizing tags
        source_categorising_tags = source_store.get_categorizing_tags_for_item(
            source_item)
        target_categorising_tags = target_store.get_categorizing_tags_for_item(
            target_item)

        if source_categorising_tags != target_categorising_tags:
            return False

        # all equal
        return True

    def __sync_new_tags(self, source_store, target_store, source_item,
                        target_item):
        """
        syncs new tags
        """

        # get describing tags
        target_describing_sync_tags = set(
            target_store.get_describing_sync_tags_for_item(target_item))
        target_describing_tags = set(
            target_store.get_describing_tags_for_item(target_item))
        source_describing_tags = set(
            source_store.get_describing_tags_for_item(source_item))

        # get categorizing tags
        target_categorizing_sync_tags = set(
            target_store.get_categorizing_sync_tags_for_item(target_item))
        target_categorizing_tags = set(
            target_store.get_categorizing_tags_for_item(target_item))
        source_categorizing_tags = set(
            source_store.get_categorizing_tags_for_item(source_item))

        if target_describing_tags == source_describing_tags and\
            target_categorizing_tags == source_categorizing_tags:
            self.__log.info("no changes found")
            target_store.set_sync_tags(target_item, source_describing_tags,
                                       source_categorizing_tags)
            return

        new_describing_tags = (
            source_describing_tags -
            target_describing_sync_tags) | target_describing_tags

        # remove tag support
        #removed_describing_tags = target_describing_sync_tags - source_describing_tags
        #new_describing_tags -= removed_describing_tags

        #if len(removed_describing_tags) > 0:
        #    for item in removed_describing_tags:
        #        self.__log.info("removed tag: '%s'" %item)
        new_categorizing_tags = (
            source_categorizing_tags -
            target_categorizing_sync_tags) | target_categorizing_tags

        # now sync the tags
        target_store.add_item_with_tags(target_item, new_describing_tags,
                                        new_categorizing_tags)

        # update the sync tags
        target_store.set_sync_tags(target_item, source_describing_tags,
                                   source_categorizing_tags)

    def __sync_conflict_file(self, source_store, target_store, source_item,
                             target_item, target_file_path):
        """
        replaces the target file with the source file
        """

        # get describing tags from file
        describing_tag_list = source_store.get_describing_tags_for_item(
            source_item)

        # get categorizing tags from file
        categorizing_tag_list = source_store.get_categorizing_tags_for_item(
            source_item)

        # replace file
        shutil.copy2(self.__get_full_file_path(source_store, source_item),
                     target_file_path)

        # replace current entry
        target_store.add_item_with_tags(target_item, describing_tag_list,
                                        categorizing_tag_list)

        # set the sync tags
        target_store.set_sync_tags(target_item, describing_tag_list,
                                   categorizing_tag_list)

    def __sync_new_file(self,
                        source_store,
                        target_store,
                        source_item,
                        target_file_path,
                        copy_file=True):
        """
        copies the new file and its associated tags
        """

        # get describing tags from file
        describing_tag_list = source_store.get_describing_tags_for_item(
            source_item)

        # get categorizing tags from file
        categorizing_tag_list = source_store.get_categorizing_tags_for_item(
            source_item)

        if copy_file:
            # copy file
            shutil.copy2(self.__get_full_file_path(source_store, source_item),
                         target_file_path)

        # create target file item name
        target_item = self.__create_target_file_item(target_store, source_item)

        # add to tagstore
        target_store.add_item_with_tags(target_item, describing_tag_list,
                                        categorizing_tag_list)

        # set the sync tags
        target_store.set_sync_tags(target_item, describing_tag_list,
                                   categorizing_tag_list)

    def __create_target_file_item(self, target_store, source_item):
        """
        creates the target file name
        """

        position = source_item.rfind("\\")
        if position != -1:
            source_item = source_item[position + 1:len(source_item)]

        if target_store.is_android_store():
            # get directories
            storage_dir = target_store.get_android_root_directory()
            tagstore_dir = target_store.get_storage_directory()

            # extract storage directory name
            # replace path seperators with %5C which is required
            directory = tagstore_dir[len(storage_dir) +
                                     1:len(tagstore_dir)] + "\\" + source_item
            directory = directory.replace("/", "\\")
            return directory
        else:
            return source_item

    def __create_target_file_path(self, target_store, source_item):
        """
        creates the target file path
        """
        position = source_item.rfind("\\")
        if position != -1:
            source_item = source_item[position + 1:len(source_item)]

        return target_store.get_storage_directory() + "/" + source_item

    def __get_full_file_path(self, store, file_item):

        # check if it is an android store
        if store.is_android_store():
            # android store items have the full path encoded from the root directory
            return store.get_android_root_directory() + "/" + file_item
        else:
            # normal tagstores include their files in the storage directory
            return store.get_storage_directory() + "/" + file_item

    def __are_files_equal(self, source_store, target_store, source_file,
                          target_file):
        """
        compares both files if there are equal
        """

        # get file locations
        source_path = self.__get_full_file_path(source_store, source_file)
        target_path = self.__get_full_file_path(target_store, target_file)

        # check for equality
        return filecmp.cmp(source_path, target_path, 0)

    def __find_item_in_store(self, store_items, source_item):
        """
        finds an item which has the same name
        It is required to remove directory from the searched entries. The reasons is that 
        an Android tagstore has multiple virtual directories attached. 
        """

        position = source_item.rfind("\\")
        if position != -1:
            source_item = source_item[position + 1:len(source_item)]

        # look up all items in the target store
        for file_name in store_items:

            # is there a directory in the path
            position = file_name.rfind("\\")
            if position != -1:
                fname = file_name[position + 1:len(file_name)]
            else:
                fname = file_name

            # does the name now match
            if fname == source_item:
                return file_name

        # no item found
        return None

    def __emit_not_syncable(self, err_msg):
        self.__log.error(err_msg)
        self.emit(QtCore.SIGNAL("sync_error"))

    def set_application(self, application):
        """
        if the manager is called from another qt application (e.g. tagstore.py)
        you must set the calling application here for proper i18n
        """
        self.__application = application

    def __handle_sync_cancel(self):
        """
        the cancel button has been pressed
        """
        self.emit(QtCore.SIGNAL("sync_cancel"))
        #self.__tag_dialog.hide_dialog()

    def __prepare_store_params(self):
        """
        initializes all necessary parameters for creating a store object
        """

        for lang in self.SUPPORTED_LANGUAGES:
            #self.change_language(lang)
            self.STORE_STORAGE_DIRS.append(self.trUtf8("storage"))
            self.STORE_DESCRIBING_NAV_DIRS.append(self.trUtf8("navigation"))
            self.STORE_CATEGORIZING_NAV_DIRS.append(
                self.trUtf8("categorization"))
            self.STORE_EXPIRED_DIRS.append(self.trUtf8("expired_items"))
        ## reset language
        #self.change_language(store_current_language)

        config_dir = self.__main_config.get_store_config_directory()
        if config_dir != "":
            self.STORE_CONFIG_DIR = config_dir
        config_file_name = self.__main_config.get_store_configfile_name()
        if config_file_name != "":
            self.STORE_CONFIG_FILE_NAME = config_file_name
        tags_file_name = self.__main_config.get_store_tagsfile_name()
        if tags_file_name != "":
            self.STORE_TAGS_FILE_NAME = tags_file_name
        vocabulary_file_name = self.__main_config.get_store_vocabularyfile_name(
        )
        if vocabulary_file_name != "":
            self.STORE_VOCABULARY_FILE_NAME = vocabulary_file_name

    def change_language(self, locale):
        """
        changes the current application language
        please notice: this method is used to find all available storage/navigation directory names
        this is why it should not be extended to call any UI update methods directly
        """

        ## delete current translation to switch to default strings
        self.__application.removeTranslator(self.__translator)

        ## load new translation file
        self.__translator = QtCore.QTranslator()
        language = unicode(locale)
        if self.__translator.load("ts_" + language + ".qm", "tsresources/"):
            self.__application.installTranslator(self.__translator)

        ## update current language


#        self.CURRENT_LANGUAGE = self.trUtf8("en")
        self.CURRENT_LANGUAGE = self.trUtf8(locale)

    def __init_stores(self, source_store, target_store):
        """
        initializes the store objects
        """

        # get current language from main config file and apply it
        self.CURRENT_LANGUAGE = self.__main_config.get_current_language()
        self.change_language(self.CURRENT_LANGUAGE)

        # prepare all parameters for creating the store object
        self.__prepare_store_params()

        # create the source store
        self.__create_source_store(source_store)

        # create the target store
        self.__create_target_store(target_store)

        #init sync log for the source store
        self.__source_store.init_sync_log(self.__target_store.get_name())

        # init sync log for the target store
        self.__target_store.init_sync_log(self.__source_store.get_name())

    def __has_sync_tag(self, source_store, source_item, sync_tag):
        """
        checks if the file has the sync tag associated
        """

        # get describing tags
        source_item_describing_tags = source_store.get_describing_tags_for_item(
            source_item)

        if source_item_describing_tags != None:
            if sync_tag in source_item_describing_tags:
                return True

        # get categorising tags
        source_item_categorising_tags = source_store.get_categorizing_tags_for_item(
            source_item)
        if source_item_categorising_tags != None:
            if sync_tag in source_item_categorising_tags:
                return True

        # tag not found
        return False

    def __add_conflict_item(self, source_store, target_store, target_items,
                            target_sync_items, source_item, target_item):
        """
        adds a conflict item to the conflict list
        """

        current_item = {}
        current_item["source_item"] = source_item
        current_item["target_item"] = target_item
        current_item["source_store"] = source_store
        current_item["target_store"] = target_store
        current_item["target_items"] = target_items
        current_item["target_sync_items"] = target_sync_items

        self.__conflict_file_list.append(current_item)
Beispiel #21
0
 def __create_wrappers(self):
     if self.__file_system.path_exists(self.__path + "/" + self.__tags_file_name):
         self.__tag_wrapper = TagWrapper(self.__path + "/" + self.__tags_file_name)
     if self.__file_system.path_exists(self.__path + "/" + self.__config_file_name):
         self.__store_config_wrapper = ConfigWrapper(self.__path + "/" + self.__config_file_name)
Beispiel #22
0
    def test_configreader(self):
        config = ConfigWrapper("../tsresources/conf/tagstore.cfg")
        extensions = config.get_ignored_extension()

        assert (extensions == TsRestrictions.IGNORED_EXTENSIONS)
Beispiel #23
0
class SyncController(QtCore.QObject):
    
    def __init__(self, application, source_store_path, target_store_path, auto_sync, verbose = False):
        """
        initialize the controller
        """
        QtCore.QObject.__init__(self)
        
        # init components
        self.__application = application
        self.__source_store_path = source_store_path
        self.__target_store_path = target_store_path
        self.__auto_sync = auto_sync

        self.__main_config = None
        self.__store_config = None
        self.__source_store = None
        self.__target_store = None
        self.__sync_dialog = None
        self.__conflict_file_list = None
        self.__source_items = None
        self.__target_items = None
        self.__target_sync_items = None
        
        
        # default values
        self.STORE_CONFIG_DIR = TsConstants.DEFAULT_STORE_CONFIG_DIR
        self.STORE_CONFIG_FILE_NAME = TsConstants.DEFAULT_STORE_CONFIG_FILENAME
        self.STORE_TAGS_FILE_NAME = TsConstants.DEFAULT_STORE_TAGS_FILENAME
        self.STORE_VOCABULARY_FILE_NAME = TsConstants.DEFAULT_STORE_VOCABULARY_FILENAME
        self.STORE_SYNC_FILE_NAME = TsConstants.DEFAULT_STORE_SYNC_TAGS_FILENAME
        
        # load translators
        locale = unicode(QtCore.QLocale.system().name())[0:2]
        self.__translator = QtCore.QTranslator()
        if self.__translator.load("ts_" + locale + ".qm", "tsresources/"):
            self.__application.installTranslator(self.__translator)

        #get dir names for all available languages
        self.CURRENT_LANGUAGE = self.trUtf8("en")
        self.STORE_STORAGE_DIRS = []
        self.STORE_DESCRIBING_NAV_DIRS = []
        self.STORE_CATEGORIZING_NAV_DIRS = []
        self.STORE_EXPIRED_DIRS = []
        self.STORE_NAVIGATION_DIRS = []
        self.SUPPORTED_LANGUAGES = TsConstants.DEFAULT_SUPPORTED_LANGUAGES
        self.__store_dict = {}
        
        for lang in self.SUPPORTED_LANGUAGES: 
            self.change_language(lang)
            self.STORE_NAVIGATION_DIRS.append(self.trUtf8("navigation")) 
            self.STORE_STORAGE_DIRS.append(self.trUtf8("storage"))#self.STORE_STORAGE_DIR_EN))  
            self.STORE_DESCRIBING_NAV_DIRS.append(self.trUtf8("descriptions"))#self.STORE_DESCRIBING_NAVIGATION_DIR_EN))  
            self.STORE_CATEGORIZING_NAV_DIRS.append(self.trUtf8("categories"))#self.STORE_CATEGORIZING_NAVIGATION_DIR_EN)) 
            self.STORE_EXPIRED_DIRS.append(self.trUtf8("expired_items"))#STORE_EXPIRED_DIR_EN)) 
            
        ## reset language 
        self.change_language(self.CURRENT_LANGUAGE)
            
        # init logger component
        self.LOG_LEVEL = logging.INFO
        if verbose:
            self.LOG_LEVEL = logging.DEBUG 
        
        # get logger
        self.__log = LogHelper.get_app_logger(self.LOG_LEVEL)
        
    def start(self):
        """
        call this method to launch the sync dialog
        """
        self.__init_configuration()
        
    def __init_configuration(self):
        """
        initializes the configuration
        """
        
        # informal debug
        self.__log.info("__init_configuration")
        
        # construct config wrapper
        self.__main_config = ConfigWrapper(TsConstants.CONFIG_PATH)
        if self.__main_config is None:
            self.__emit_not_syncable(self.trUtf8("No config file found for the given path"))
            return


        search_source_path = False
        found_source_path = False
        if self.__source_store_path != None and self.__source_store_path != "":
            search_source_path = True

        search_target_path = False
        found_target_path = False
        if self.__target_store_path != None and self.__target_store_path != "":
            search_target_path = True

        ## create a temporary store list
        ## add the desc and cat tags which are needed in the admin-dialog
        tmp_store_list = []
        store_list = self.__main_config.get_stores()
        
        # add android store
        # when registered
        android_source_path = self.__main_config.get_android_store_path()
        if android_source_path != None and android_source_path != "":
            store_item = {}
            store_item["path"] = android_source_path
            store_item["name"] = "Android"
            store_list.append(store_item)
        
        # enumerate all stores and add their names and paths
        store_name = None
        for current_store_item in store_list:
            
            if current_store_item.has_key("name"):
                store_name = current_store_item["name"]
            else:
                store_name = current_store_item["path"].split("/").pop()
                
            store_path = current_store_item["path"]
            current_store_item["name"] = store_name
            current_store_item["path"] = store_path
            
            tmp_store_list.append(current_store_item)
            # find source target list
            if search_source_path:
                if store_path == self.__source_store_path:
                    found_source_path = True
                    
            if search_target_path:
                if store_path == self.__target_store_path:
                    found_target_path = True
        
        if search_source_path and found_source_path == False:
            # source store is not registered
            self.__emit_not_syncable(self.trUtf8("Source tagstore not registered in main config"))
            return
        
        if search_target_path and found_target_path == False:
            # source store is not registered
            self.__emit_not_syncable(self.trUtf8("Target tagstore not registered in main config"))
            return
        
        if self.__sync_dialog is None:
            self.__sync_dialog = SyncDialogController(tmp_store_list, self.__source_store_path, self.__target_store_path, self.__auto_sync)
            self.__sync_dialog.get_view().setModal(True)
            #self.__tag_dialog.set_parent(self.sender().get_view())
            self.__sync_dialog.connect(self.__sync_dialog, QtCore.SIGNAL("sync_store"), self.__sync_store_action)
            self.__sync_dialog.connect(self.__sync_dialog, QtCore.SIGNAL("sync_conflict"), self.__handle_resolve_conflict)
            self.__sync_dialog.connect(self.__sync_dialog, QtCore.SIGNAL("handle_cancel()"), self.__handle_sync_cancel)

        self.__sync_dialog.show_dialog()
        if self.__auto_sync:
            self.__sync_dialog.start_auto_sync()

    def __handle_resolve_conflict(self, file_item, action):

        # remove item from conflict list        
        self.__conflict_file_list.remove(file_item)
        
        source_item = file_item["source_item"]
        target_item = file_item["target_item"]
        source_store = file_item["source_store"]
        target_store = file_item["target_store"]
        target_file_path = self.__create_target_file_path(target_store, target_item)
        
        self.__log.info("handle_resolve_conclict: source_item %s target_item %s action % s" %(source_item, target_item, action))
        
        
        
        if action == "replace":
            # sync the file and their tags
            self.__sync_conflict_file(source_store, target_store, source_item, target_item, target_file_path)

        # launch conflict dialog
        self.__show_conflict_dialog() 
            
    def __remove_lock_file(self):
        """
        removes the lock from the affected tagstores
        """
        
        self.__source_store.remove_sync_lock_file()
        self.__target_store.remove_sync_lock_file()
    
    def __create_lock_file(self):
        """
        creates the lock files for the affected tagstores
        """
        
        # check if the store is in use by another sync operation
        if self.__source_store.is_sync_active() or self.__target_store.is_sync_active():
            # sync is already active
            return False
        
        # create lock file in source tagstore
        result = self.__source_store.create_sync_lock_file()
        if not result:
            # failed to create lock file
            return result
        
        # create lock in target tagstore
        result = self.__target_store.create_sync_lock_file()
        if not result:
            # delete lock file from source tagstore
            self.__source_store.remove_sync_lock_file()


        # done
        return result
                
    def __show_conflict_dialog(self):
        """
        displays the conflict dialogs when there are one or more conflicts
        """
        
        while len(self.__conflict_file_list) > 0:
        
            # get first item
            current_item = self.__conflict_file_list[0]
            
            # extract paramters
            source_item = current_item["source_item"]
            target_item = current_item["target_item"]
            source_store = current_item["source_store"]
            target_store = current_item["target_store"]
            target_items = current_item["target_items"]
            target_sync_items = current_item["target_sync_items"]
            
            # do we need to sync
            sync_success = self.__sync_item(source_store, target_store, target_items, target_sync_items, source_item, False)
            if sync_success:
                # remove item
                # conflict has been solved by a previous conflict resolution
                self.__conflict_file_list.remove(current_item)
                continue

            # update status dialog
            message = ("Syncing %s" % source_item)            
            self.__sync_dialog.set_status_msg(message)
            
            # replace dialog message
            message = ("Do you want to replace file %s with %s" % (self.__get_full_file_path(target_store, target_item), self.__get_full_file_path(source_store, source_item))) 
            self.__sync_dialog.show_conflict_dialog("Conflict", message, current_item)
            
            return
        # end while
        
        # conflict list empty
        msg = "Sync completed on " + datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")

        # flush all changes
        self.__flush_changes()


        self.__sync_dialog.set_status_msg(msg)
        self.__sync_dialog.toggle_sync_button(True)
        self.__sync_dialog.set_close_button_text(self.trUtf8("Finish"))
        self.__remove_lock_file()
            
    def __create_source_store(self, source_store):
        
        """
        create the source store object
        """
    
        # construct config wrapper for the tagstore
        self.__store_config = ConfigWrapper(source_store)
        if self.__store_config is None:    
            self.__emit_not_syncable(self.trUtf8("No source store found for the given path"))
            return    
    
        # construct store object
        self.__source_store = Store(self.__store_config.get_store_id(), source_store, 
              self.STORE_CONFIG_DIR + "/" + self.STORE_CONFIG_FILE_NAME,
              self.STORE_CONFIG_DIR + "/" + self.STORE_TAGS_FILE_NAME,
              self.STORE_CONFIG_DIR + "/" + self.STORE_VOCABULARY_FILE_NAME,
              self.STORE_NAVIGATION_DIRS,
              self.STORE_STORAGE_DIRS, 
              self.STORE_DESCRIBING_NAV_DIRS,
              self.STORE_CATEGORIZING_NAV_DIRS,
              self.STORE_EXPIRED_DIRS,
              self.__main_config.get_expiry_prefix())
        self.__source_store.init()
    
    def __flush_changes(self):
        
        if self.__source_store != None:
            self.__source_store.finish_sync()
        
        if self.__target_store != None:
            self.__target_store.finish_sync()
            
    def __create_target_store(self, target_store):
        """
        create the target store object
        """

        # construct target store config object
        self.__target_store_config = ConfigWrapper(target_store)
        if self.__target_store_config is None:    
            self.__emit_not_syncable(self.trUtf8("No target store found for the given path"))
            return
        
        # construct target store object
        self.__target_store = Store(self.__target_store_config.get_store_id(), target_store, 
              self.STORE_CONFIG_DIR + "/" + self.STORE_CONFIG_FILE_NAME,
              self.STORE_CONFIG_DIR + "/" + self.STORE_TAGS_FILE_NAME,
              self.STORE_CONFIG_DIR + "/" + self.STORE_VOCABULARY_FILE_NAME,
              self.STORE_NAVIGATION_DIRS,
              self.STORE_STORAGE_DIRS, 
              self.STORE_DESCRIBING_NAV_DIRS,
              self.STORE_CATEGORIZING_NAV_DIRS,
              self.STORE_EXPIRED_DIRS,
              self.__main_config.get_expiry_prefix())
        self.__target_store.init()        

    def __get_file_items_with_sync_tag(self):
        """
        returns all files which have the associated sync tag
        """
        
        # get source items
        source_items = self.__source_store.get_items()

        # get current sync tag
        sync_tag = self.__main_config.get_sync_tag()
       
        # build empty result list
        source_sync_items = []
       
        # enumerate all items
        for source_item in source_items:
            
            if self.__has_sync_tag(self.__source_store, source_item, sync_tag):
                # item is tagged with sync tag
                source_sync_items.append(source_item)
                continue
        
        # done
        return source_sync_items


    def __prepare_sync(self, source_store, target_store):
        """
        prepares the sync
        """
        
        # initialize the store objects
        self.__init_stores(source_store, target_store)
        
        # get sync style
        android_sync = self.__source_store.is_android_store() or self.__target_store.is_android_store()
        
        # get source items
        if android_sync:
            self.__source_items = self.__get_file_items_with_sync_tag()
        else:
            self.__source_items = self.__source_store.get_items()

        # get target items
        self.__target_items = self.__target_store.get_items()

        # get target sync items
        self.__target_sync_items = self.__target_store.get_sync_items()

    def __sync_store_action(self, source_store, target_store):
        """
        initializes the sync
        """

        # conflict list
        self.__conflict_file_list = []

        # prepare the sync
        self.__prepare_sync(source_store, target_store)

        # now create the lock files
        lock_file = self.__create_lock_file()
        if not lock_file:
            self.__log.info("another sync is in progress please wait until it is finished")
            self.__sync_dialog.set_status_msg("Another sync is pending, please wait until it is finished")
            self.__sync_dialog.set_close_button_text(self.trUtf8("Finish"))
            return

        # start with source tagstore -> target tagstore
        self.__handle_sync()

        # switch stores
        self.__prepare_sync(target_store, source_store)

        # push changes from target store to source tagstore
        self.__handle_sync()
        
        self.__log.info("Number of conflicts %d" %(len(self.__conflict_file_list)))
        
        # launch conflict dialog
        self.__show_conflict_dialog()
        
    def __handle_sync(self):
        """
        executes a sync
        """

        # start the sync
        self.__start_sync(self.__source_store, self.__target_store, self.__source_items, self.__target_items, self.__target_sync_items)

    def __sync_item(self, source_store, target_store, target_items, target_sync_items, source_item, add_conflict_list=True):
        
        # is there such an item in the target tagstore
        target_item = self.__find_item_in_store(target_items, source_item)
        
        # does the file exist in the target tagstore
        if target_item != None:
            return self.__sync_existing_item(source_store, target_store, target_items, target_sync_items, source_item, target_item, add_conflict_list)    
        else:
            return self.__sync_new_item(source_store, target_store, target_items, target_sync_items, source_item, add_conflict_list)

    def __sync_new_item(self, source_store, target_store, target_items, target_sync_items, source_item, add_conflict_list):   


        # file does not exist
        # was it already synced once?
        target_sync_item = self.__find_item_in_store(target_sync_items, source_item)
        if target_sync_item:
            #the file was already synced once, skipping
            self.__log.info("[SKIP] File '%s' was already synced once" % target_sync_item)
            return True

        # file was not synced before, lets check if it exists in the target destination store
        # create target path
        target_file_path = self.__create_target_file_path(target_store, source_item)
        
        # check if file already exists
        if not os.path.exists(target_file_path):
            # file does not yet exist
            self.__log.info("[SYNC] New File: '%s' is synced" % source_item)
            self.__sync_new_file(source_store, target_store, source_item, target_file_path)
            return True
        
        # create target item
        target_item = self.__create_target_file_item(target_store, source_item)
            
        # the file already exists
        # is it the same file
        files_equal = self.__are_files_equal(source_store, target_store, source_item, target_item)
        if files_equal:
            # file is present, just sync the tags
            self.__log.info("[SYNC] File '%s' already present in target, syncing tags only" % source_item)
            self.__sync_new_file(source_store, target_store, source_item, target_file_path, copy_file=False)
            return False
            
        # sync conflict
        self.__log.info("[Conflict] File: '%s' already exists" % target_file_path)
        if add_conflict_list:
            self.__add_conflict_item(source_store, target_store, target_items, target_sync_items, source_item, target_item)
        return False
        
        
    def __sync_existing_item(self, source_store, target_store, target_items, target_sync_items, source_item, target_item, add_conflict_list):
        """
        syncs an existing item
        """
        
        # check if the source file is equal
        files_equal = self.__are_files_equal(source_store, target_store, source_item, target_item)
        if files_equal:
            # files are equal
            # sync tags
            self.__log.info("[SYNC] Tags of file '%s' are synced" % source_item)
            self.__sync_new_tags(source_store, target_store, source_item, target_item)
            return True

        # okay files are not equal, lets get a sync date
        target_sync_item = (target_item in target_sync_items)
        if not target_sync_item:
            # there is no sync item for file
            self.__log.info("[Conflict] File '%s' -> %s' was added in the tagstore simultaneously" % (source_item, target_item))
            if add_conflict_list:
                self.__add_conflict_item(source_store, target_store, target_items, target_sync_items, source_item, target_item)
            return False

        # get sync time
        str_sync_gm_time = target_store.get_sync_file_timestamp(target_item)
        sync_gm_time = time.strptime(str_sync_gm_time, "%Y-%m-%d %H:%M:%S")

        # get source modification time
        mod_time = os.path.getmtime(self.__get_full_file_path(source_store, source_item))
        source_gm_time = time.gmtime(mod_time)
                    
        # get target modification time
        mod_time = os.path.getmtime(self.__get_full_file_path(target_store, target_item))
        target_gm_time = time.gmtime(mod_time)
               
        # was source file modified
        if source_gm_time <= sync_gm_time:
            # file was not modified since last sync 
            # sync new tags
            self.__log.info("[SYNC] No source modification, tags of file '%s' are synced" % source_item)
            self.__sync_new_tags(source_store, target_store, source_item, target_item)
            return True

        # source modified, lets check target file
        if target_gm_time <= sync_gm_time:
            # target file was not modified
            self.__log.info("[SYNC] Updating file '%s' and tags" % source_item)
            shutil.copy2(self.__get_full_file_path(source_store, source_item), self.__get_full_file_path(target_store, target_item))
            self.__sync_new_tags(source_store, target_store, source_item, target_item)            
            return True

        # source and target file have been modified, do their tags match
        if self.__are_all_tags_equal(source_store, target_store, source_item, target_item):
            # sync the file
            self.__log.info("[Conflict] Both files have been modified '%s'" % target_item)
            if add_conflict_list:
                self.__add_conflict_item(source_store, target_store, target_items, target_sync_items, source_item, target_item)
            return False

        # both files and tags are modified
        self.__log.info("[Conflict] Both files and tags are modified '%s'" % target_item)
        if add_conflict_list:
            self.__add_conflict_item(source_store, target_store, target_items, target_sync_items, source_item, target_item)
        return False

    def __start_sync(self, source_store, target_store, source_items, target_items, target_sync_items):
        """
        starts the sync
        """
        
        for source_item in source_items:
            
            # sync item
            self.__log.info("[SYNC] Current Item: %s" % source_item)
            self.__sync_item(source_store, target_store, target_items, target_sync_items, source_item)
        
    
    def __are_all_tags_equal(self, source_store, target_store, source_item, target_item):
        """
        checks if all tags from the source item and target item are equal
        """
        
        source_describing_tags = source_store.get_describing_tags_for_item(source_item)
        target_describing_tags = target_store.get_describing_tags_for_item(target_item)
        
        if source_describing_tags != target_describing_tags:
            return False
        
        # get categorizing tags
        source_categorising_tags = source_store.get_categorizing_tags_for_item(source_item)
        target_categorising_tags = target_store.get_categorizing_tags_for_item(target_item)

        if source_categorising_tags != target_categorising_tags:
            return False
        
        # all equal
        return True
    
    def __sync_new_tags(self, source_store, target_store, source_item, target_item):
        """
        syncs new tags
        """

        # get describing tags
        target_describing_sync_tags = set(target_store.get_describing_sync_tags_for_item(target_item))
        target_describing_tags = set(target_store.get_describing_tags_for_item(target_item))
        source_describing_tags = set(source_store.get_describing_tags_for_item(source_item))
        
        # get categorizing tags
        target_categorizing_sync_tags = set(target_store.get_categorizing_sync_tags_for_item(target_item))
        target_categorizing_tags = set(target_store.get_categorizing_tags_for_item(target_item))
        source_categorizing_tags = set(source_store.get_categorizing_tags_for_item(source_item))
    
        if target_describing_tags == source_describing_tags and\
            target_categorizing_tags == source_categorizing_tags:
            self.__log.info("no changes found")
            target_store.set_sync_tags(target_item, source_describing_tags, source_categorizing_tags)
            return

        new_describing_tags = (source_describing_tags - target_describing_sync_tags) | target_describing_tags
        
        # remove tag support
        #removed_describing_tags = target_describing_sync_tags - source_describing_tags
        #new_describing_tags -= removed_describing_tags
        
        #if len(removed_describing_tags) > 0:
        #    for item in removed_describing_tags:
        #        self.__log.info("removed tag: '%s'" %item)
        new_categorizing_tags = (source_categorizing_tags - target_categorizing_sync_tags) | target_categorizing_tags
    
        # now sync the tags
        target_store.add_item_with_tags(target_item, new_describing_tags, new_categorizing_tags)
        
        # update the sync tags
        target_store.set_sync_tags(target_item, source_describing_tags, source_categorizing_tags)
    
    def __sync_conflict_file(self, source_store, target_store, source_item, target_item, target_file_path):
        """
        replaces the target file with the source file
        """
        
        # get describing tags from file
        describing_tag_list = source_store.get_describing_tags_for_item(source_item)
        
        # get categorizing tags from file
        categorizing_tag_list = source_store.get_categorizing_tags_for_item(source_item)

        # replace file
        shutil.copy2(self.__get_full_file_path(source_store, source_item), target_file_path)

        # replace current entry
        target_store.add_item_with_tags(target_item, describing_tag_list, categorizing_tag_list)

        # set the sync tags
        target_store.set_sync_tags(target_item, describing_tag_list, categorizing_tag_list)
    
    def __sync_new_file(self, source_store, target_store, source_item, target_file_path, copy_file=True):
        """
        copies the new file and its associated tags
        """
        
        # get describing tags from file
        describing_tag_list = source_store.get_describing_tags_for_item(source_item)
        
        # get categorizing tags from file
        categorizing_tag_list = source_store.get_categorizing_tags_for_item(source_item)
        
        if copy_file:
            # copy file
            shutil.copy2(self.__get_full_file_path(source_store, source_item), target_file_path)
        
        # create target file item name
        target_item = self.__create_target_file_item(target_store, source_item)
        
        # add to tagstore
        target_store.add_item_with_tags(target_item, describing_tag_list, categorizing_tag_list)
        
        # set the sync tags
        target_store.set_sync_tags(target_item, describing_tag_list, categorizing_tag_list)
        
    def __create_target_file_item(self, target_store, source_item):
        """
        creates the target file name
        """
        
        position = source_item.rfind("\\")
        if position != -1:
            source_item = source_item[position+1:len(source_item)]

        if target_store.is_android_store():
            # get directories
            storage_dir = target_store.get_android_root_directory()
            tagstore_dir = target_store.get_storage_directory()
            
            # extract storage directory name
            # replace path seperators with %5C which is required
            directory = tagstore_dir[len(storage_dir)+1:len(tagstore_dir)] + "\\" + source_item
            directory = directory.replace("/", "\\")
            return directory
        else:
            return source_item


    def __create_target_file_path(self, target_store, source_item):
        """
        creates the target file path
        """
        position = source_item.rfind("\\")
        if position != -1:
            source_item = source_item[position+1:len(source_item)]
  
        return target_store.get_storage_directory() + "/" + source_item
    
    def __get_full_file_path(self, store, file_item):
    
        # check if it is an android store
        if store.is_android_store():
            # android store items have the full path encoded from the root directory
            return store.get_android_root_directory() + "/" + file_item
        else:
            # normal tagstores include their files in the storage directory
            return store.get_storage_directory() + "/" + file_item
            
    def __are_files_equal(self, source_store, target_store, source_file, target_file):
        """
        compares both files if there are equal
        """
        
        # get file locations
        source_path = self.__get_full_file_path(source_store, source_file)
        target_path = self.__get_full_file_path(target_store, target_file)
        
        # check for equality        
        return filecmp.cmp(source_path, target_path, 0)
        
    def __find_item_in_store(self, store_items, source_item):
        """
        finds an item which has the same name
        It is required to remove directory from the searched entries. The reasons is that 
        an Android tagstore has multiple virtual directories attached. 
        """
        
        position = source_item.rfind("\\")
        if position != -1:
            source_item = source_item[position+1:len(source_item)]
        

        
        # look up all items in the target store
        for file_name in store_items:

            # is there a directory in the path
            position  = file_name.rfind("\\")
            if position != -1:
                fname = file_name[position+1:len(file_name)]
            else:
                fname = file_name
            
            # does the name now match
            if fname == source_item:
                return file_name
        
        # no item found
        return None
    
    def __emit_not_syncable(self, err_msg):
        self.__log.error(err_msg)
        self.emit(QtCore.SIGNAL("sync_error"))

        
    def set_application(self, application):
        """
        if the manager is called from another qt application (e.g. tagstore.py)
        you must set the calling application here for proper i18n
        """
        self.__application = application
        
    def __handle_sync_cancel(self):
        """
        the cancel button has been pressed
        """
        self.emit(QtCore.SIGNAL("sync_cancel"))
        #self.__tag_dialog.hide_dialog()

    def __prepare_store_params(self):
        """
        initializes all necessary parameters for creating a store object
        """
    
        for lang in self.SUPPORTED_LANGUAGES: 
            #self.change_language(lang) 
            self.STORE_STORAGE_DIRS.append(self.trUtf8("storage"))  
            self.STORE_DESCRIBING_NAV_DIRS.append(self.trUtf8("navigation"))  
            self.STORE_CATEGORIZING_NAV_DIRS.append(self.trUtf8("categorization")) 
            self.STORE_EXPIRED_DIRS.append(self.trUtf8("expired_items")) 
        ## reset language 
        #self.change_language(store_current_language) 
        
        config_dir = self.__main_config.get_store_config_directory()
        if config_dir != "":
            self.STORE_CONFIG_DIR = config_dir
        config_file_name = self.__main_config.get_store_configfile_name()
        if config_file_name != "":
            self.STORE_CONFIG_FILE_NAME = config_file_name
        tags_file_name = self.__main_config.get_store_tagsfile_name()
        if tags_file_name != "":
            self.STORE_TAGS_FILE_NAME = tags_file_name
        vocabulary_file_name = self.__main_config.get_store_vocabularyfile_name()
        if vocabulary_file_name != "":
            self.STORE_VOCABULARY_FILE_NAME = vocabulary_file_name

    def change_language(self, locale):
        """
        changes the current application language
        please notice: this method is used to find all available storage/navigation directory names
        this is why it should not be extended to call any UI update methods directly
        """
        
        ## delete current translation to switch to default strings
        self.__application.removeTranslator(self.__translator)

        ## load new translation file        
        self.__translator = QtCore.QTranslator()
        language = unicode(locale)
        if self.__translator.load("ts_" + language + ".qm", "tsresources/"):
            self.__application.installTranslator(self.__translator)
            
        ## update current language
#        self.CURRENT_LANGUAGE = self.trUtf8("en")
        self.CURRENT_LANGUAGE = self.trUtf8(locale)

    def __init_stores(self, source_store, target_store):
        """
        initializes the store objects
        """
        
        # get current language from main config file and apply it
        self.CURRENT_LANGUAGE = self.__main_config.get_current_language();
        self.change_language(self.CURRENT_LANGUAGE)
        
        # prepare all parameters for creating the store object
        self.__prepare_store_params()
        
        # create the source store
        self.__create_source_store(source_store)
        
        # create the target store
        self.__create_target_store(target_store)

        #init sync log for the source store
        self.__source_store.init_sync_log(self.__target_store.get_name())
        
        # init sync log for the target store        
        self.__target_store.init_sync_log(self.__source_store.get_name())
        
    def __has_sync_tag(self, source_store, source_item, sync_tag):
        """
        checks if the file has the sync tag associated
        """
        
        # get describing tags
        source_item_describing_tags = source_store.get_describing_tags_for_item(source_item)
        
        if source_item_describing_tags != None:
            if sync_tag in source_item_describing_tags:
                return True
        
        # get categorising tags
        source_item_categorising_tags = source_store.get_categorizing_tags_for_item(source_item)
        if source_item_categorising_tags != None:
            if sync_tag in source_item_categorising_tags:
                return True
        
        # tag not found
        return False

    def __add_conflict_item(self, source_store, target_store, target_items, target_sync_items, source_item, target_item):
        """
        adds a conflict item to the conflict list
        """
        
        current_item = {}
        current_item["source_item"] = source_item
        current_item["target_item"] = target_item
        current_item["source_store"] = source_store
        current_item["target_store"] = target_store
        current_item["target_items"] = target_items
        current_item["target_sync_items"] = target_sync_items

        self.__conflict_file_list.append(current_item)
Beispiel #24
0
    def __init_configuration(self):
        """
        initializes the configuration. This method is called every time the config file changes
        """
        self.__log.info("initialize configuration")

        self.__main_config = ConfigWrapper(TsConstants.CONFIG_PATH)

        if self.__main_config is None:
            self.__emit_not_retagable(self.trUtf8("No config file found for the given path"))
            return

        ## check if there has been found an appropriate store_path in the config
        if self.__store_path is None:
            self.__emit_not_retagable(self.trUtf8("No store found for the given path"))
            return
        else:
            self.__store_config = ConfigWrapper(self.__store_path)

        self.__prepare_store_params()

        self.CURRENT_LANGUAGE = self.__main_config.get_current_language()
        self.change_language(self.CURRENT_LANGUAGE)
        # self.__main_config.connect(self.__main_config, QtCore.SIGNAL("changed()"), self.__init_configuration)

        self.__store = Store(
            self.__store_config.get_store_id(),
            self.__store_path,
            self.STORE_CONFIG_DIR + "/" + self.STORE_CONFIG_FILE_NAME,
            self.STORE_CONFIG_DIR + "/" + self.STORE_TAGS_FILE_NAME,
            self.STORE_CONFIG_DIR + "/" + self.STORE_VOCABULARY_FILE_NAME,
            self.STORE_NAVIGATION_DIRS,
            self.STORE_STORAGE_DIRS,
            self.STORE_DESCRIBING_NAV_DIRS,
            self.STORE_CATEGORIZING_NAV_DIRS,
            self.STORE_EXPIRED_DIRS,
            self.__main_config.get_expiry_prefix(),
        )
        self.__store.init()

        if self.__tag_dialog is None:
            self.__tag_dialog = TagDialogController(
                self.__store.get_name(),
                self.__store.get_id(),
                self.__main_config.get_max_tags(),
                self.__main_config.get_tag_seperator(),
                self.__main_config.get_expiry_prefix(),
            )
            self.__tag_dialog.get_view().setModal(True)
            # self.__tag_dialog.set_parent(self.sender().get_view())
            self.__tag_dialog.connect(self.__tag_dialog, QtCore.SIGNAL("tag_item"), self.__tag_item_action)
            self.__tag_dialog.connect(self.__tag_dialog, QtCore.SIGNAL("handle_cancel()"), self.__handle_tag_cancel)

        ## configure the tag dialog with the according settings
        format_setting = self.__store.get_datestamp_format()
        datestamp_hidden = self.__store.get_datestamp_hidden()
        ## check if auto datestamp is enabled
        if format_setting != EDateStampFormat.DISABLED:
            self.__tag_dialog.show_datestamp(True)
            ## set the format
            format = None
            if format_setting == EDateStampFormat.DAY:
                format = TsConstants.DATESTAMP_FORMAT_DAY
            elif format_setting == EDateStampFormat.MONTH:
                format = TsConstants.DATESTAMP_FORMAT_MONTH

            self.__tag_dialog.set_datestamp_format(format, datestamp_hidden)

        self.__tag_dialog.show_category_line(self.__store.get_show_category_line())
        self.__tag_dialog.set_category_mandatory(self.__store.get_category_mandatory())

        ## check if the given item really exists in the store
        if not self.__store.item_exists(self.__item_name):
            self.__emit_not_retagable(self.trUtf8("%s: There is no such item recorded in the store" % self.__item_name))
            return

        self.__set_tag_information_to_dialog(self.__store)

        if self.__retag_mode:
            self.__handle_retag_mode()

        self.__tag_dialog.show_dialog()
Beispiel #25
0
class Administration(QtCore.QObject):

    def __init__(self, application, verbose):
        QtCore.QObject.__init__(self)
        
        self.__log = None
        self.__main_config = None
        self.__admin_dialog = None
        self.__retag_dialog = None
        self.__verbose_mode = verbose
        # the main application which has the translator installed
        self.__application = application

        self.LOG_LEVEL = logging.INFO
        if verbose:
            self.LOG_LEVEL = logging.DEBUG
            

        self.STORE_CONFIG_DIR = TsConstants.DEFAULT_STORE_CONFIG_DIR
        self.STORE_CONFIG_FILE_NAME = TsConstants.DEFAULT_STORE_CONFIG_FILENAME
        self.STORE_TAGS_FILE_NAME = TsConstants.DEFAULT_STORE_TAGS_FILENAME
        self.STORE_VOCABULARY_FILE_NAME = TsConstants.DEFAULT_STORE_VOCABULARY_FILENAME
        
        self.__system_locale = unicode(QtCore.QLocale.system().name())[0:2]
        self.__translator = QtCore.QTranslator()
        if self.__translator.load("ts_" + self.__system_locale + ".qm", "tsresources/"):
            self.__application.installTranslator(self.__translator)
        # "en" is automatically translated to the current language e.g. en -> de
        self.CURRENT_LANGUAGE = self.__get_locale_language()
        #dir names for all available languages
        self.STORE_STORAGE_DIRS = []
        self.STORE_DESCRIBING_NAV_DIRS = []
        self.STORE_CATEGORIZING_NAV_DIRS = []
        self.STORE_EXPIRED_DIRS = []
        self.STORE_NAVIGATION_DIRS = []
        self.SUPPORTED_LANGUAGES = TsConstants.DEFAULT_SUPPORTED_LANGUAGES
        self.__store_dict = {}
        
        # catch all "possible" dir-names
        for lang in self.SUPPORTED_LANGUAGES: 
            self.change_language(lang) 
            self.STORE_STORAGE_DIRS.append(self.trUtf8("storage"))#self.STORE_STORAGE_DIR_EN))  
            self.STORE_DESCRIBING_NAV_DIRS.append(self.trUtf8("descriptions"))#self.STORE_DESCRIBING_NAVIGATION_DIR_EN))  
            self.STORE_CATEGORIZING_NAV_DIRS.append(self.trUtf8("categories"))#self.STORE_CATEGORIZING_NAVIGATION_DIR_EN)) 
            self.STORE_EXPIRED_DIRS.append(self.trUtf8("expired_items"))#STORE_EXPIRED_DIR_EN))
            self.STORE_NAVIGATION_DIRS.append(self.trUtf8("navigation")) 
        
        self.__log = LogHelper.get_app_logger(self.LOG_LEVEL)
        self.__init_configuration()
        
    def __init_configuration(self):
        """
        initializes the configuration. This method is called every time the config file changes
        """
        self.__log.info("initialize configuration")
        if self.__main_config is None:
            self.__main_config = ConfigWrapper(TsConstants.CONFIG_PATH)
            #self.connect(self.__main_config, QtCore.SIGNAL("changed()"), self.__init_configuration)
        
        self.CURRENT_LANGUAGE = self.__main_config.get_current_language();
        
        if self.CURRENT_LANGUAGE is None or self.CURRENT_LANGUAGE == "":
            self.CURRENT_LANGUAGE = self.__get_locale_language()
        
        # switch back to the configured language
        self.change_language(self.CURRENT_LANGUAGE)

        ## connect to all the signals the admin gui is sending 
        if self.__admin_dialog is None:
            self.__admin_dialog = StorePreferencesController()
            self.connect(self.__admin_dialog, QtCore.SIGNAL("create_new_store"), self.__handle_new_store)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("rename_desc_tag"), self.__handle_tag_rename)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("rename_cat_tag"), self.__handle_tag_rename)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("retag"), self.__handle_retagging)
            
            self.connect(self.__admin_dialog, QtCore.SIGNAL("rebuild_store"), self.__handle_store_rebuild)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("rename_store"), self.__handle_store_rename)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("delete_store"), self.__handle_store_delete)

            self.connect(self.__admin_dialog, QtCore.SIGNAL("synchronize"), self.__handle_synchronization)
            
        self.__admin_dialog.set_main_config(self.__main_config)
        
        
        self.__prepare_store_params()
        self.__create_stores()
        
        ## create a temporary store list
        ## add the desc and cat tags which are needed in the admin-dialog
        tmp_store_list = []
        for current_store_item in self.__main_config.get_stores():
            store_name = current_store_item["path"].split("/").pop()
            current_store_item["desc_tags"] = self.__store_dict[store_name].get_tags()
            current_store_item["cat_tags"] = self.__store_dict[store_name].get_categorizing_tags()
            tmp_store_list.append(current_store_item)
        
        self.__admin_dialog.set_store_list(tmp_store_list)
        if self.__main_config.get_first_start():
            self.__admin_dialog.set_first_start(True)
    
    def __handle_synchronization(self, store_name):
        """
        do all the necessary synchronization stuff here ...
        """
        store_to_sync = self.__store_dict[str(store_name)]
        print "####################"
        print "synchronize " + store_name
        print "####################"
        
        store_to_sync.add_item_list_with_tags(["item_one", "item_two"], ["be", "tough"])
            
    def __handle_store_delete(self, store_name):
        self.__admin_dialog.start_progressbar(self.trUtf8("Deleting store ..."))
        store = self.__store_dict[str(store_name)]
        self.__store_to_be_deleted = store_name
        self.connect(store, QtCore.SIGNAL("store_delete_end"), self.__handle_store_deleted)
        ## remove the directories 
        store.remove()
        self.disconnect(store, QtCore.SIGNAL("store_delete_end"), self.__dummy)
        ## remove the config entry 
        self.__main_config.remove_store(store.get_id())
    
    def __dummy(self):
        return "dummy"
    
    def __handle_store_deleted(self, id):
        #second remove the item in the admin_dialog 
        self.__admin_dialog.remove_store_item(self.__store_to_be_deleted)
        
    def __handle_store_rename(self, store_name, new_store_name):
        """
        the whole store directory gets moved to the new directory
        the store will be rebuilt then to make sure all links are updated
        """
        ## show a progress bar at the admin dialog
        self.__admin_dialog.start_progressbar(self.trUtf8("Moving store ..."))
        store = self.__store_dict.pop(str(store_name))        

        self.__main_config.rename_store(store.get_id(), new_store_name)
        ## connect to the rebuild signal because after the moving there is a rebuild routine started
        self.connect(store, QtCore.SIGNAL("store_rebuild_end"), self.__handle_store_rebuild)

        store.move(new_store_name)
        
        self.disconnect(store, QtCore.SIGNAL("store_rebuild_end"))
        
        self.__init_configuration()
        
        
    def __handle_store_rebuild(self, store_name):
        """
        the whole store structure will be rebuild according to the records in store.tgs file
        """
        ## show a progress bar at the admin dialog
        self.__admin_dialog.start_progressbar(self.trUtf8("Rebuilding store ..."))
        store = self.__store_dict[str(store_name)]
        self.connect(store, QtCore.SIGNAL("store_rebuild_end"), self.__handle_store_rebuild)
        store.rebuild()
        self.disconnect(store, QtCore.SIGNAL("store_rebuild_end"), self.__get_locale_language)
    
    def __hide_progress_dialog(self, store_name):
        self.__admin_dialog.stop_progressbar()
        
    
    def __get_locale_language(self):
        """
        returns the translation of "en" in the system language
        """
        return self.trUtf8("en")
    
    def __handle_tag_rename(self, old_tag, new_tag, store_name):
        store = self.__store_dict[store_name]

        old_ba = old_tag.toUtf8()
        old_str = str(old_ba)
        
        new_ba = new_tag.toUtf8()
        new_str = str(new_ba)
        store.rename_tag(unicode(old_str, "utf-8"), unicode(new_str, "utf-8"))
    
    def set_application(self, application):
        """
        if the manager is called from another qt application (e.g. tagstore.py)
        you must set the calling application here for proper i18n
        """
        self.__application = application
    
    def __handle_retagging(self, store_name, item_name):
        """
        creates and configures a tag-dialog with all store-params and tags
        """
        store = self.__store_dict[store_name]
        
        ## make a string object of the QListWidgetItem, so other methods can use it
        item_name = item_name.text()                
        self.__log.info("retagging item %s at store %s..." % (item_name, store_name))
        #if(self.__retag_dialog is None):
            
        ## create the object
        self.__retag_dialog = ReTagController(self.__application, store.get_store_path(), item_name, True, self.__verbose_mode)
        ## connect to the signal(s)
        self.connect(self.__retag_dialog, QtCore.SIGNAL("retag_error"), self.__handle_retag_error)
        self.connect(self.__retag_dialog, QtCore.SIGNAL("retag_cancel"), self.__handle_retag_cancel)
        self.connect(self.__retag_dialog, QtCore.SIGNAL("retag_success"), self.__handle_retag_success)
        self.__retag_dialog.start()

    def __kill_tag_dialog(self):
        """
        hide the dialog and set it to None
        """
        self.__retag_dialog.hide_tag_dialog()
        self.__retag_dialog = None
    
    def __handle_retag_error(self):
        self.__kill_tag_dialog()
        self.__admin_dialog.show_tooltip(self.trUtf8("An error occurred while re-tagging"))
    
    def __handle_retag_success(self):
        self.__kill_tag_dialog()
        self.__admin_dialog.show_tooltip(self.trUtf8("Re-tagging successful!"))
    
    def __handle_retag_cancel(self):
        """
        the "postpone" button in the re-tag dialog has been clicked
        """
        self.__kill_tag_dialog()
    
    def __set_tag_information_to_dialog(self, store):
        """
        convenience method for setting the tag data at the gui-dialog
        """
        self.__retag_dialog.set_tag_list(store.get_tags())
        
        num_pop_tags = self.__main_config.get_num_popular_tags()
        
        tag_set = set(store.get_popular_tags(self.__main_config.get_max_tags()))
        tag_set = tag_set | set(store.get_recent_tags(num_pop_tags))

        cat_set = set(store.get_popular_categories(num_pop_tags))
        cat_set = cat_set | set(store.get_recent_categories(num_pop_tags))

        cat_list = list(cat_set)
        if store.is_controlled_vocabulary():
            allowed_set = set(store.get_controlled_vocabulary())
            self.__retag_dialog.set_category_list(list(allowed_set))

            ## just show allowed tags - so make the intersection of popular tags ant the allowed tags
            cat_list = list(cat_set.intersection(allowed_set)) 
        else:
            self.__retag_dialog.set_category_list(store.get_categorizing_tags())
            
        if len(cat_list) > num_pop_tags:
            cat_list = cat_list[:num_pop_tags]
        self.__retag_dialog.set_popular_categories(cat_list)
        
        ## make a list out of the set, to enable indexing, as not all tags cannot be used
        tag_list = list(tag_set)
        if len(tag_list) > num_pop_tags:
            tag_list = tag_list[:num_pop_tags]
        self.__retag_dialog.set_popular_tags(tag_list)


        self.__retag_dialog.set_store_name(store.get_name())
    
    def __retag_item_action(self, store_name, item_name, tag_list, category_list):
        """
        the "tag!" button in the re-tag dialog has been clicked
        """
        store = self.__store_dict[store_name]
        try:
            ## 1. write the data to the store-file
            store.add_item_with_tags(item_name, tag_list, category_list)
            self.__log.debug("added item %s to store-file", item_name)
        except NameInConflictException, e:
            c_type = e.get_conflict_type()
            c_name = e.get_conflicted_name()
            if c_type == EConflictType.FILE:
                self.__retag_dialog.show_message(self.trUtf8("The filename - %s - is in conflict with an already existing tag. Please rename!" % c_name))
            elif c_type == EConflictType.TAG:
                self.__retag_dialog.show_message(self.trUtf8("The tag - %s - is in conflict with an already existing file" % c_name))
            else:
                self.trUtf8("A tag or item is in conflict with an already existing tag/item")
            #raise
        except InodeShortageException, e:
            self.__retag_dialog.show_message(self.trUtf8("The Number of free inodes is below the threshold of %s%" % e.get_threshold()))
Beispiel #26
0
class ReTagController(QtCore.QObject):
    """
    object for calling the re-tag view.
    ************************
    MANDATORY parameters: 
    ************************
    * application -> the parent qt-application object ()for installing the translator properly
    * store_path -> absolute path to the store of the item to be retagged (TIP: use the PathHelper object to resolve a relative path.)
    * item_name -> the name of the item to be renamed (exactly how it is defined in the tagfile)

    ************************
    TIP: use the PathHelper object to resolve a relative path AND to extract the item name out of it. 
    ************************
    
    ************************
    OPTIONAL parameters:
    ************************
    * standalone_application -> default = False; set this to true if there
    * verbose -> set this to true for detailed output
    (DEVEL * retag_mode -> this application could even be used for a normal tagging procedure as well.)
    
    ************************
    IMPORTANT!!!
    ************************
    the start() method must be called in order to begin with the tagging procedure
    """

    def __init__(self, application, store_path, item_name, retag_mode = True, verbose = False):
        QtCore.QObject.__init__(self)
        
        self.__log = None
        self.__main_config = None
        self.__store_config = None
        self.__tag_dialog = None
        self.__store = None
        
        self.__retag_mode = retag_mode
        
        self.__no_store_found = False

        self.__item_name = unicode(item_name)
        self.__store_path = store_path

        # the main application which has the translator installed
        self.__application = application

        self.LOG_LEVEL = logging.INFO
        if verbose:
            self.LOG_LEVEL = logging.DEBUG
            

        self.STORE_CONFIG_DIR = TsConstants.DEFAULT_STORE_CONFIG_DIR
        self.STORE_CONFIG_FILE_NAME = TsConstants.DEFAULT_STORE_CONFIG_FILENAME
        self.STORE_TAGS_FILE_NAME = TsConstants.DEFAULT_STORE_TAGS_FILENAME
        self.STORE_VOCABULARY_FILE_NAME = TsConstants.DEFAULT_STORE_VOCABULARY_FILENAME
        
        locale = unicode(QtCore.QLocale.system().name())[0:2]
        self.__translator = QtCore.QTranslator()
        if self.__translator.load("ts_" + locale + ".qm", "tsresources/"):
            self.__application.installTranslator(self.__translator)

        #get dir names for all available languages
        self.CURRENT_LANGUAGE = self.trUtf8("en")
        self.STORE_STORAGE_DIRS = []
        self.STORE_DESCRIBING_NAV_DIRS = []
        self.STORE_CATEGORIZING_NAV_DIRS = []
        self.STORE_EXPIRED_DIRS = []
        self.STORE_NAVIGATION_DIRS = []
        self.SUPPORTED_LANGUAGES = TsConstants.DEFAULT_SUPPORTED_LANGUAGES
        self.MAX_CLOUD_TAGS = TsConstants.DEFAULT_MAX_CLOUD_TAGS
        self.__store_dict = {}
        
        for lang in self.SUPPORTED_LANGUAGES: 
            self.change_language(lang)
            self.STORE_NAVIGATION_DIRS.append(self.trUtf8("navigation")) 
            self.STORE_STORAGE_DIRS.append(self.trUtf8("storage"))#self.STORE_STORAGE_DIR_EN))  
            self.STORE_DESCRIBING_NAV_DIRS.append(self.trUtf8("descriptions"))#self.STORE_DESCRIBING_NAVIGATION_DIR_EN))  
            self.STORE_CATEGORIZING_NAV_DIRS.append(self.trUtf8("categories"))#self.STORE_CATEGORIZING_NAVIGATION_DIR_EN)) 
            self.STORE_EXPIRED_DIRS.append(self.trUtf8("expired_items"))#STORE_EXPIRED_DIR_EN)) 
        ## reset language 
        self.change_language(self.CURRENT_LANGUAGE)
        
        self.__log = LogHelper.get_app_logger(self.LOG_LEVEL)
        
    def start(self):
        """
        call this method to actually start the tagging procedure
        """
        self.__init_configuration()
        
    
    def __init_configuration(self):
        """
        initializes the configuration. This method is called every time the config file changes
        """
        self.__log.info("initialize configuration")
        
        
        self.__main_config = ConfigWrapper(TsConstants.CONFIG_PATH)
        
        if self.__main_config is None:
            self.__emit_not_retagable(self.trUtf8("No config file found for the given path"))
            return
        
        ## check if there has been found an appropriate store_path in the config 
        if self.__store_path is None:
            self.__emit_not_retagable(self.trUtf8("No store found for the given path"))
            return
        else:
            self.__store_config = ConfigWrapper(self.__store_path)
        
        self.__prepare_store_params()
        
        self.CURRENT_LANGUAGE = self.__main_config.get_current_language();
        self.change_language(self.CURRENT_LANGUAGE)
        #self.__main_config.connect(self.__main_config, QtCore.SIGNAL("changed()"), self.__init_configuration)

        self.__store = Store(self.__store_config.get_store_id(), self.__store_path, 
              self.STORE_CONFIG_DIR + "/" + self.STORE_CONFIG_FILE_NAME,
              self.STORE_CONFIG_DIR + "/" + self.STORE_TAGS_FILE_NAME,
              self.STORE_CONFIG_DIR + "/" + self.STORE_VOCABULARY_FILE_NAME,
              self.STORE_NAVIGATION_DIRS,
              self.STORE_STORAGE_DIRS, 
              self.STORE_DESCRIBING_NAV_DIRS,
              self.STORE_CATEGORIZING_NAV_DIRS,
              self.STORE_EXPIRED_DIRS,
              self.__main_config.get_expiry_prefix())
        self.__store.init()
        
        if self.__tag_dialog is None:
            self.__tag_dialog = TagDialogController(self.__store.get_name(), self.__store.get_id(), self.__main_config.get_max_tags(), self.__main_config.get_tag_seperator(), self.__main_config.get_expiry_prefix())
            self.__tag_dialog.get_view().setModal(True)
            #self.__tag_dialog.set_parent(self.sender().get_view())
            self.__tag_dialog.connect(self.__tag_dialog, QtCore.SIGNAL("tag_item"), self.__tag_item_action)
            self.__tag_dialog.connect(self.__tag_dialog, QtCore.SIGNAL("handle_cancel()"), self.__handle_tag_cancel)


        ## configure the tag dialog with the according settings
        format_setting = self.__store.get_datestamp_format()
        datestamp_hidden = self.__store.get_datestamp_hidden()
        ## check if auto datestamp is enabled
        if format_setting != EDateStampFormat.DISABLED:
            self.__tag_dialog.show_datestamp(True)
            ## set the format
            format = None
            if format_setting == EDateStampFormat.DAY:
                format = TsConstants.DATESTAMP_FORMAT_DAY
            elif format_setting == EDateStampFormat.MONTH:
                format = TsConstants.DATESTAMP_FORMAT_MONTH
            
            self.__tag_dialog.set_datestamp_format(format, datestamp_hidden)
        
        self.__tag_dialog.show_category_line(self.__store.get_show_category_line())
        self.__tag_dialog.set_category_mandatory(self.__store.get_category_mandatory()) 
        
        ## check if the given item really exists in the store
        if not self.__store.item_exists(self.__item_name):
            self.__emit_not_retagable(self.trUtf8("%s: There is no such item recorded in the store" % self.__item_name))
            return 

        self.__set_tag_information_to_dialog(self.__store)
        
        if self.__retag_mode:
            self.__handle_retag_mode()
        
        self.__tag_dialog.show_dialog()
    
    def __emit_not_retagable(self, err_msg):
        self.__log.error(err_msg)
        self.emit(QtCore.SIGNAL("retag_error"))
    
    def __handle_retag(self, store_name, file_name_list, new_describing_tags, new_categorizing_tags):
        
        for file_name in file_name_list:
            ## first of all remove the old references
            self.__store.remove_file(file_name)
            ## now create the new navigation structure
            try:
                self.__store.add_item_with_tags(file_name, new_describing_tags, new_categorizing_tags)
            except InodeShortageException, e:
                self.__tag_dialog.show_message(self.trUtf8("The Number of free inodes is below the threshold of %s%" % e.get_threshold()))
                #raise
            except Exception, e:
                self.__tag_dialog.show_message(self.trUtf8("An error occurred while tagging"))
                raise
            else:
Beispiel #27
0
 def test_configreader(self):
     config = ConfigWrapper("../tsresources/conf/tagstore.cfg")
     extensions = config.get_ignored_extension()
     
     assert(extensions == TsRestrictions.IGNORED_EXTENSIONS)
Beispiel #28
0
class Store(QtCore.QObject):

    __pyqtSignals__ = ("removed(PyQt_PyObject)",
                       "renamed(PyQt_PyObject, QString)",
                       "file_renamed(PyQt_PyObject, QString, QString)",
                       "file_removed(PyQt_PyObject, QString)",
                       "pending_operations_changed(PyQt_PyObject)")

    def __init__(self, id, path, config_file_name, tags_file, vocabulary_file, navigation_dir_list, storage_dir_list, describing_nav_dir_list, categorising_nav_dir_list, expiry_dir_list, expiry_prefix):
        """
        constructor
        """
        QtCore.QObject.__init__(self)

        #self.__log = logging.getLogger("TagStoreLogger")#None##FIXXME
        self.__log = None

        self.__file_system = FileSystemWrapper(self.__log)
        self.__watcher = QtCore.QFileSystemWatcher(self)
        self.__watcher.connect(self.__watcher,QtCore.SIGNAL("directoryChanged(QString)"), self.__directory_changed)
        self.__tag_wrapper = None
        self.__sync_tag_wrapper = None
        self.__store_config_wrapper = None
        self.__pending_changes = PendingChanges()
        
        self.__sync_tags_file_name = TsConstants.DEFAULT_STORE_SYNC_TAGS_FILENAME
        
        self.__tagline_config = None
        self.__paths_to_maintain = []
        
        self.__id = unicode(id)
        self.__path = unicode(path)
        self.__config_file_name = unicode(config_file_name)
        self.__tags_file_name = unicode(tags_file)
        self.__vocabulary_file_name = unicode(vocabulary_file)
        
        self.__storage_dir_list = storage_dir_list
        self.__describing_nav_dir_list = describing_nav_dir_list
        self.__categorising_nav_dir_list = categorising_nav_dir_list
        self.__expiry_dir_list = expiry_dir_list
        self.__expiry_prefix = unicode(expiry_prefix)
        
        self.__storage_dir_name = self.trUtf8("storage")
        self.__navigation_dir_name = self.trUtf8("navigation")
        self.__describing_nav_dir_name = self.trUtf8("descriptions")
        self.__categorising_nav_dir_name = self.trUtf8("categories")
        self.__expiry_dir_name = self.trUtf8("expired_items")
        #self.__parent_path = None
        #self.__name = None
        #self.__config_path = None
        #self.__watcher_path = None
        #self.__describing_nav_path = None
        #self.__config_path = self.__path + "/" + self.__config_file_name
        #self.__watcher_path = self.__path + "/" + self.__storage_dir_name
        #self.__describing_nav_path = self.__path + "/" + self.__describing_nav_dir_name

        self.__create_wrappers()
        
        if self.__path.find(":/") == -1:
            self.__path = self.__path.replace(":", ":/")
        self.__name = unicode(self.__path.split("/")[-1])
        self.__parent_path = unicode(self.__path[:len(self.__path)-len(self.__name)-1])
        
        self.__tagcloud = TagCloud()
        self.__recommender = Recommender(self.get_store_path())
        
    def __create_wrappers(self):
        if self.__file_system.path_exists(self.__path + "/" + self.__tags_file_name):
            self.__tag_wrapper = TagWrapper(self.__path + "/" + self.__tags_file_name)
        if self.__file_system.path_exists(self.__path + "/" + self.__config_file_name):
            self.__store_config_wrapper = ConfigWrapper(self.__path + "/" + self.__config_file_name)

    def init(self):
        """
        init is called after event listeners were added to the store instance
        """
        ## throw exception if store directory does not exist
        if not self.__file_system.path_exists(self.__path):
            ## look for renamed or removed store folder
            self.__handle_renamed_removed_store()
        if not self.__file_system.path_exists(self.__path):
            #print self.__path
            raise StoreInitError, self.trUtf8("The specified store directory does not exist! %s" % self.__path)
            return
        
        
        ## look for store/describing_nav/categorising_nav/expire directories names (all languages) if they do not exist
        if not self.__file_system.path_exists(self.__path + "/" + self.__storage_dir_name):
            for dir in self.__storage_dir_list:
                if self.__file_system.path_exists(self.__path + "/" + dir):
                    self.__storage_dir_name = unicode(dir)
        if not self.__file_system.path_exists(self.__path + "/" + self.__describing_nav_dir_name):
            for dir in self.__describing_nav_dir_list:
                if self.__file_system.path_exists(self.__path + "/" + dir):
                    self.__describing_nav_dir_name = unicode(dir)
        if not self.__file_system.path_exists(self.__path + "/" + self.__categorising_nav_dir_name):
            for dir in self.__categorising_nav_dir_list:
                if self.__file_system.path_exists(self.__path + "/" + dir):
                    self.__categorising_nav_dir_name = unicode(dir)
        if not self.__file_system.path_exists(self.__path + "/" + self.__expiry_dir_name):
            for dir in self.__expiry_dir_list:
                if self.__file_system.path_exists(self.__path + "/" + dir):
                    self.__expiry_dir_name = unicode(dir)
        if not self.__file_system.path_exists(self.__path + "/" + self.__navigation_dir_name):
            for dir in self.__expiry_dir_list:
                if self.__file_system.path_exists(self.__path + "/" + dir):
                    self.__navigation_dir_name = unicode(dir)
        
        
        
        ## built stores directories and config file if they currently not exist (new store)
        self.__file_system.create_dir(self.__path + "/" + self.__storage_dir_name)
        self.__file_system.create_dir(self.__path + "/" + self.__expiry_dir_name)
        self.__file_system.create_dir(self.__path + "/" + self.__config_file_name.split("/")[0])
        self.__file_system.create_dir(self.__path + "/" + self.__navigation_dir_name)
        ## create config/vocabulary files if they don't exist
        if not self.__file_system.path_exists(self.__path + "/" + self.__config_file_name):
            ConfigWrapper.create_store_config_file(self.__path + "/" + self.__config_file_name)
            ## now create a new config_wrapper instance
            self.__store_config_wrapper = ConfigWrapper(self.__path + "/" + self.__config_file_name)
        if not self.__file_system.path_exists(self.__path + "/" + self.__tags_file_name):
            TagWrapper.create_tags_file(self.__path + "/" + self.__tags_file_name)
            ## now create a new tag_wrapper instance
            self.__tag_wrapper = TagWrapper(self.__path + "/" + self.__tags_file_name)
        if not self.__file_system.path_exists(self.__path + "/" + self.__vocabulary_file_name):
            self.__vocabulary_wrapper = VocabularyWrapper.create_vocabulary_file(self.__path + "/" + self.__vocabulary_file_name)


        ## 0 ... show just the describing tagline -> create the NAVIGATION dir 
        ## 3 ... show just the categorizing tagline - only restricted vocabulary is allowed -> create the CATEGORIES dir
        ## ELSE: two taglines with dirs: CATEGORIES/DESCRIPTIONS
        self.__tagline_config = self.__store_config_wrapper.get_show_category_line()
        
        # clear the old list (if there is already one)
        self.__paths_to_maintain = []
        
        if self.__tagline_config == 0:
            #self.__file_system.create_dir(self.__path + "/" + self.__navigation_dir_name)
            self.__paths_to_maintain.append(self.__path + "/" + self.__navigation_dir_name)
        elif self.__tagline_config == 3:
            #self.__file_system.create_dir(self.__path + "/" + self.__categorising_nav_dir_name)
            self.__paths_to_maintain.append(self.__path + "/" + self.__categorising_nav_dir_name)
        else:
            #self.__file_system.create_dir(self.__path + "/" + self.__categorising_nav_dir_name)
            self.__paths_to_maintain.append(self.__path + "/" + self.__categorising_nav_dir_name)
            #self.__file_system.create_dir(self.__path + "/" + self.__describing_nav_dir_name)
            self.__paths_to_maintain.append(self.__path + "/" + self.__describing_nav_dir_name)

        for path in self.__paths_to_maintain:
            self.__file_system.create_dir(path)

        self.__init_store()

    def init_sync_log(self, target_store_name):
        """
        initializes the sync log
        """
        # construct sync tags file path
        target_store_name = target_store_name.replace(":", "")
        
        self.__sync_tags_file_path = self.__path + "/" + TsConstants.DEFAULT_STORE_CONFIG_DIR + "/" + target_store_name + self.__sync_tags_file_name
        
        self.__log.info("init sync log path:%s" % self.__sync_tags_file_path)
        if not self.__file_system.path_exists(self.__sync_tags_file_path):
            # create default sync tags file
            TagWrapper.create_tags_file(self.__sync_tags_file_path)
            
        ## now create a new sync tag_wrapper instance
        self.__sync_tag_wrapper = TagWrapper(self.__sync_tags_file_path)

    def sync_item_exists(self, item_name):
        """
        checks an item exists in the sync log
        """
        return self.__sync_tag_wrapper.file_exists(item_name)

    def get_sync_items(self):
        """
        returns a list of all item names in the sync log 
        """
        return self.__sync_tag_wrapper.get_files()

    def get_sync_file_timestamp(self, file_name):
        """
        returns the timestamp value in the sync log
        """
        return self.__sync_tag_wrapper.get_file_timestamp(file_name)
        
    def get_describing_sync_tags_for_item(self, item_name):
        """
        returns all describing tags associated with the given item in the sync log
        """
        return self.__sync_tag_wrapper.get_file_tags(item_name)
        
    def get_categorizing_sync_tags_for_item(self, item_name):
        """
        returns all categorizing tags associated with the given item in the sync log
        """
        return self.__sync_tag_wrapper.get_file_categories(item_name)

        
    def __init_store(self):
        """
        initializes the store paths, config reader, file system watcher without instantiation of a new object
        """
        self.__name = self.__path.split("/")[-1]
        self.__parent_path = self.__path[:len(self.__path)-len(self.__name)]
        self.__tags_file_path = self.__path + "/" + self.__tags_file_name
        self.__sync_tags_file_path = self.__path + "/" + TsConstants.DEFAULT_STORE_CONFIG_DIR + "/" + self.__sync_tags_file_name
        self.__config_path = self.__path + "/" + self.__config_file_name
        self.__vocabulary_path = self.__path + "/" + self.__vocabulary_file_name #TsConstants.STORE_CONFIG_DIR + "/" + TsConstants.STORE_VOCABULARY_FILENAME
        self.__watcher_path = self.__path + "/" + self.__storage_dir_name
        self.__navigation_path = self.__path + "/" + self.__navigation_dir_name
        self.__describing_nav_path = self.__path + "/" + self.__describing_nav_dir_name
        self.__categorising_nav_path = self.__path + "/" + self.__categorising_nav_dir_name
        config_file_name = unicode(self.__config_path.split("/")[-1])
        self.__temp_progress_path = unicode(self.__config_path[:len(self.__config_path)-len(config_file_name)-1])
        
        self.__tag_wrapper = TagWrapper(self.__tags_file_path)
        self.__sync_tag_wrapper = TagWrapper(self.__sync_tags_file_path)
        
        ## update store id to avoid inconsistency
        config_wrapper = ConfigWrapper(self.__path + "/" + self.__config_file_name)#self.__tags_file_name)
        config_wrapper.set_store_id(self.__id)
        self.__vocabulary_wrapper = VocabularyWrapper(self.__vocabulary_path)
        self.connect(self.__vocabulary_wrapper, QtCore.SIGNAL("changed"), self.__handle_vocabulary_changed)
        self.__store_config_wrapper = ConfigWrapper(self.__config_path)
        self.connect(self.__store_config_wrapper, QtCore.SIGNAL("changed()"), self.__handle_store_config_changed)
        
        if len(self.__name) == 0:
            self.__name = self.__path[:self.__path.rfind("/")]
        
        if not self.__is_android_store():
            # no activity is required on android tag stores
            self.__watcher.addPath(self.__parent_path)
            self.__watcher.addPath(self.__watcher_path)
        
        ## all necessary files and dirs should have been created now - so init the logger
        self.__log = LogHelper.get_store_logger(self.__path, logging.INFO) 
        self.__log.info("parent_path: '%s'" % self.__name)

        ## handle offline changes
        self.__handle_unfinished_operation()
        self.__handle_file_expiry()
        self.__handle_file_changes(self.__watcher_path)
        
    
    def __handle_store_config_changed(self):
        self.emit(QtCore.SIGNAL("store_config_changed"), self)

    def __handle_vocabulary_changed(self):
        self.emit(QtCore.SIGNAL("vocabulary_changed"), self)
        
#    def handle_offline_changes(self):
#        """
#        called after store and event-handler are created to handle (offline) modifications
#        """
#        self.__handle_file_changes(self.__watcher_path)

    def add_ignored_extensions(self, ignored_list):
        self.__file_system.add_ignored_extensions(ignored_list)

    def set_path(self, path, config_file=None, tags_file=None, vocabulary_file=None):
        """
        resets the stores path and config path (called if application config changes)
        """
        if self.__path == unicode(path) and (config_file is None or self.__config_file_name == unicode(config_file)):
            exit
        ## update changes
        self.__watcher.removePaths([self.__parent_path, self.__watcher_path])
        self.__path = unicode(path)
        if config_file is not None:
            self.__config_file_name = unicode(config_file)
        if tags_file is not None:
            self.__tags_file_name = unicode(tags_file)
        if vocabulary_file is not None:
            self.__vocabulary_file_name = unicode(vocabulary_file)
        self.__init_store()
        
    def __handle_renamed_removed_store(self):
        """
        searches the parents directory for renamed or removed stores
        """
        #print self.__parent_path
        #print self.__config_file_name
        #print ".."
        config_paths = self.__file_system.find_files(self.__parent_path, self.__config_file_name)
        #print "config paths: " + ",".join(config_paths)
        new_name = ""
        for path in config_paths:
            reader = ConfigWrapper(path)
            #print "found: " + path
            #print self.__id + ", " + reader.get_store_id()
            
            if self.__id == reader.get_store_id():
                new_name = path.split("/")[-3]
                #print "new name: " + new_name

        if new_name == "":      ## removed
            ## delete describing_nav directors
            #self.remove()
            self.emit(QtCore.SIGNAL("removed(PyQt_PyObject)"), self)
        else:                   ## renamed
            self.__path = self.__parent_path + "/" + new_name
            self.__navigation_path = self.__path + "/" + self.__navigation_dir_name
            self.__describing_nav_path = self.__path + "/" + self.__describing_nav_dir_name
            self.__categorising_nav_path = self.__path + "/" + self.__categorising_nav_dir_name
            ## update all links in windows: absolute links only
            #if self.__file_system.get_os() == EOS.Windows:
                #print "rebuild"
                #self.rebuild()
            #print "emit: " + self.__parent_path + "/" + new_name
            self.emit(QtCore.SIGNAL("renamed(PyQt_PyObject, QString)"), self, self.__parent_path + "/" + new_name)
    
    def __directory_changed(self, path):
        """
        handles directory changes of the stores directory and its parent directory 
        and finds out if the store itself was renamed/removed
        """
        if path == self.__parent_path:
            if not self.__file_system.path_exists(self.__path):
                ## store itself was changed: renamed, moved or deleted
                self.__watcher.removePath(self.__parent_path)
                self.__handle_renamed_removed_store()
        else:
            ## files or directories in the store directory have been changed
            self.__handle_file_changes(self.__watcher_path)

    def __handle_unfinished_operation(self):
        """
        looks for a opInProgress.tmp file to find out if the last operation was finished correctly
        this file is created before an operation starts and deleted afterwards
        """
        if self.__file_system.path_exists(self.__temp_progress_path + "/" + "opInProgress.tmp"):
            self.rebuild()
            
    def __create_inprogress_file(self):
        """
        creates a temporary file during an operation in progress to handle operation interruption
        """
        self.__file_system.create_file(self.__temp_progress_path + "/" + "opInProgress.tmp")

    def __remove_inprogress_file(self):
        """
        removes the temporary file after the operation succeeded
        """
        self.__file_system.remove_file(self.__temp_progress_path + "/" + "opInProgress.tmp")
        
    def __handle_file_expiry(self):
        """
        looks for expired items and moves & renames them to filename including tags in the expiry_directory
        """
        expiry_date_files = self.__tag_wrapper.get_files_with_expiry_tags(self.__expiry_prefix)
        now = datetime.datetime.now()
        for file in expiry_date_files:
            file_extension = "." + file["filename"].split(".")[-1]
            file_name = file["filename"]
            file_name = file_name[:len(file_name)-len(file_extension)]
            if int(file["exp_year"]) < now.year or (int(file["exp_year"]) == now.year and int(file["exp_month"]) < now.month):
                new_filename = file_name + " - " + "; ".join(file["category"]) + " - " + "; ".join(file["tags"]) + file_extension
                self.__file_system.rename_file(self.__watcher_path + "/" + file["filename"], self.__path + "/" + self.__expiry_dir_name + "/" + new_filename)
    
    def __get_lockfile_path(self):
        return self.__path + "/" + self.__storage_dir_name + "/" + TsConstants.DEFAULT_SYNCHRONIZATION_LOCKFILE_NAME
    
                    
    def __is_sync_active(self):
        """
        returns true when the sync is active
        """
  
        # get lock path
        path = self.__get_lockfile_path();
  
        # check if path exists        
        result = self.__file_system.path_exists(path)
        if result == False:
            return result
        
        
        # read pid from file
        old_pid = None 
        pid_file = open(path, "r")
        
        for line in pid_file.readlines():
            old_pid = line

        # close pid file
        pid_file.close()
        
        if old_pid is None or old_pid == "":
            # empty file remove
            self.__file_system.remove_file(path)
            return False
        
        # check if the pid still exists
        return PidHelper.pid_exists(old_pid)

    def __handle_file_changes(self, path):
        """
        handles the stores file and dir changes to find out if a file/directory was added, renamed, removed
        """
        if(path == self.__get_lockfile_path()):
            return
        
        ## if there is a synchronize procedure running - just do nothing
        #if(self.__is_synchronize_in_progress()):
        #    print "recognized a new file - but this must be from the sync-process"
        #    return
        
        if self.__is_sync_active():
            self.__log.info("__handle_file_changes: sync is active")
            return

        if self.__is_android_store():
            # no notifications on android stores
            self.__log.info("__handle_file_changes: no notifications on android stores")
            return
        
        # sync any changes if the´sync was active
        self.__tag_wrapper.sync_settings()
        
        ## this method does not handle the renaming or deletion of the store directory itself (only childs)
        existing_files = set(self.__file_system.get_files(path))
        existing_dirs = set(self.__file_system.get_directories(path))
        config_files = set(self.__tag_wrapper.get_files())
        captured_added_files = set(self.__pending_changes.get_added_names())
        captured_removed_files = set(self.__pending_changes.get_removed_names())

        data_files = (config_files | captured_added_files) - captured_removed_files 
        added = list((existing_files | existing_dirs) - data_files)
        removed = list(data_files - (existing_files | existing_dirs))
    
        #names = self.__pending_changes.get_added_names()
        #for name in names:
        #    self.__log.info(name)
    
        if len(added) == 1 and len(removed) == 1:
            self.__pending_changes.register(removed[0], self.__get_type(removed[0]), EFileEvent.REMOVED_OR_RENAMED)
            self.__pending_changes.register(added[0], self.__get_type(added[0]), EFileEvent.ADDED_OR_RENAMED)
            self.emit(QtCore.SIGNAL("file_renamed(PyQt_PyObject, QString, QString)"), self, removed[0], added[0])
        else:
            if len(removed) > 0:
                if len(added) == 0:
                    for item in removed:
                        self.__pending_changes.register(item, self.__get_type(item), EFileEvent.REMOVED)
                        self.emit(QtCore.SIGNAL("file_removed(PyQt_PyObject, QString)"), self, item)
                else:
                    for item in removed:
                        self.__pending_changes.register(item, self.__get_type(item), EFileEvent.REMOVED_OR_RENAMED)
                        self.emit(QtCore.SIGNAL("pending_operations_changed(PyQt_PyObject)"), self)
            if len(added) > 0:
                if len(removed) == 0:
                    for item in added:
                        self.__pending_changes.register(item, self.__get_type(item), EFileEvent.ADDED)
                        self.emit(QtCore.SIGNAL("pending_operations_changed(PyQt_PyObject)"), self)
                else:
                    for item in added:
                        self.__pending_changes.register(item, self.__get_type(item), EFileEvent.ADDED_OR_RENAMED)
                        self.emit(QtCore.SIGNAL("pending_operations_changed(PyQt_PyObject)"), self)                
        
    def __get_type(self, item):
        """
        returns the items type to be stored in pending_changes
        """
        if self.__file_system.is_directory(self.__watcher_path + "/" + unicode(item)):
            return EFileType.DIRECTORY
        return EFileType.FILE
    
    def get_name(self):
        """
        returns the stores name
        """
        return self.__name
    
    def get_id(self):
        """
        returns the stores id
        """
        return unicode(self.__id)
    def get_max_tags(self):
        return self.__store_config_wrapper.get_max_tags()
    def get_tag_separator(self):
        return self.__store_config_wrapper.get_tag_seperator()

    def get_store_path(self):
        """
        returns the root of the tagstore
        """
        return self.__path
    
    def get_pending_changes(self):
        """
        returns the stores unhandled changes 
        """
        return self.__pending_changes
    
    def move(self, new_path):
        """
        moves the whole path to the specified place 
        """
        ## first of all move the physical data to the new location 
        self.__file_system.move(self.__path, new_path)
        ## re-set the path variable
        self.__path = new_path
        ## initialize to update all necessary membervariables
        self.init()
        ## rebuild the whole store structure to make sure all links are updated
        self.rebuild()

    def remove(self):
        """
        removes all directories and links in the stores describing_nav path
        """
        for path in self.__paths_to_maintain:
            self.__file_system.delete_dir_content(path)
        
        self.emit(QtCore.SIGNAL("store_delete_end"), self.__id)
    
    def rebuild(self):
        """
        removes and rebuilds all links in the describing_nav path
        """
        self.__create_inprogress_file()
        self.__log.info("START rebuild progress")
        self.remove()
        for file in self.__tag_wrapper.get_files():
            describing_tag_list = self.__tag_wrapper.get_file_tags(file)
            categorising_tag_list = self.__tag_wrapper.get_file_categories(file)
            self.add_item_with_tags(file, describing_tag_list, categorising_tag_list)
        self.__remove_inprogress_file()
        self.emit(QtCore.SIGNAL("store_rebuild_end"), self.__name)
        self.__log.info("rebuild progress END")
    
    def rename_file(self, old_file_name, new_file_name):
        """
        renames an existing file: links and config settings 
        """
        self.__log.info("renaming: %s to %s" % (old_file_name, new_file_name))
        self.__create_inprogress_file()
        if self.__tag_wrapper.file_exists(old_file_name):
            describing_tag_list = self.__tag_wrapper.get_file_tags(old_file_name)
            categorising_tag_list = self.__tag_wrapper.get_file_categories(old_file_name)
            self.remove_file(old_file_name)
            self.add_item_with_tags(new_file_name, describing_tag_list, categorising_tag_list)

            self.__pending_changes.remove(old_file_name)
            self.__pending_changes.remove(new_file_name)
        else:
            self.__pending_changes.edit(old_file_name, new_file_name)
            self.emit(QtCore.SIGNAL("pending_operations_changed(PyQt_PyObject)"), self)
        self.__remove_inprogress_file()
        
    def remove_file(self, file_name):
        """
        removes a file: links and config settings 
        """
        self.__log.info("remove file: %s" % file_name)
        self.__create_inprogress_file()
        self.__pending_changes.remove(file_name)
        if self.__tag_wrapper.file_exists(file_name):
            describing_tag_list = self.__tag_wrapper.get_file_tags(file_name)
            categorising_tag_list = self.__tag_wrapper.get_file_categories(file_name)
            for path in self.__paths_to_maintain:
                if path == self.__describing_nav_path:
                    self.__delete_links(file_name, describing_tag_list, self.__describing_nav_path)
                if path == self.__categorising_nav_path:
                    self.__delete_links(file_name, categorising_tag_list, self.__categorising_nav_path)
                if path == self.__navigation_path:
                    #changed fron cat -> do desc tags because there are just descrbing tags when
                    #there is just the "navigation" folder
                    #self.__delete_links(file_name, categorising_tag_list, self.__navigation_path)
                    self.__delete_links(file_name, describing_tag_list, self.__navigation_path)
            self.__tag_wrapper.remove_file(file_name)
        else:
            self.emit(QtCore.SIGNAL("pending_operations_changed(PyQt_PyObject)"), self)
        self.__remove_inprogress_file()
        
    def __delete_links(self, file_name, tag_list, current_path):
        """
        deletes all links to the given file
        """
        self.__create_inprogress_file()
        for tag in tag_list:
            recursive_list = [] + tag_list
            recursive_list.remove(tag)
            self.__delete_links(file_name, recursive_list, current_path + "/" + tag)
            self.__file_system.remove_link(current_path + "/" + tag + "/" + file_name)
        self.__remove_inprogress_file()
    
    def change_expiry_prefix(self, new_prefix):
        """
        changes the expiry prefix and all existing expiry tags
        """
        ## handle changes only
        if self.__expiry_prefix == new_prefix:  
            return

        self.__log.info("changing the expiry prefix from %s to %s" % (self.__expiry_prefix, new_prefix))
        
        self.__create_inprogress_file()
        expiry_date_files = self.__tag_wrapper.get_files_with_expiry_tags(self.__expiry_prefix)
        
        for file in expiry_date_files:
            for tag in file["tags"]:
                match = re.match("^(" + self.__expiry_prefix + ")([0-9]{4})(-)([0-9]{2})", tag)
                if match:
                    self.rename_tag(tag, tag.replace(self.__expiry_prefix, new_prefix))
            
            for tag in file["category"]:
                match = re.match("^(" + self.__expiry_prefix + ")([0-9]{4})(-)([0-9]{2})", tag)
                if match:
                    self.rename_tag(tag, tag.replace(self.__expiry_prefix, new_prefix))

        ## set new prefix
        self.__expiry_prefix = new_prefix
        self.__remove_inprogress_file()
    
    def get_items(self):
        """
        returns a list of all item names in the store 
        """
        return self.__tag_wrapper.get_files()
    
    def get_tags(self):
        """
        returns a list of all describing  tags
        """
        return self.__tag_wrapper.get_all_tags()

    def get_categorizing_tags(self):
        """
        returns a list of categorizing all tags
        """
        return self.__tag_wrapper.get_all_categorizing_tags()

    def get_recent_tags(self, number):
        """
        returns a given number of recently entered tags
        """
        return self.__tag_wrapper.get_recent_tags(number)
    
    def get_popular_tags(self, number):
        """
        returns a given number of the most popular tags
        """
        return self.__tag_wrapper.get_popular_tags(number)

    def get_popular_categories(self, number):
        """
        returns a given number of the most popular tags
        """
        return self.__tag_wrapper.get_popular_categories(number)

    def get_recent_categories(self, number):
        """
        returns a given number of recently entered tags
        """
        return self.__tag_wrapper.get_recent_categories(number)

    def get_describing_tags_for_item(self, item_name):
        """
        returns all describing tags associated with the given item
        """
        return self.__tag_wrapper.get_file_tags(item_name)
        
    def get_categorizing_tags_for_item(self, item_name):
        """
        returns all categorizing tags associated with the given item
        """
        return self.__tag_wrapper.get_file_categories(item_name)
        

    def get_show_category_line(self):
        return self.__store_config_wrapper.get_show_category_line()

    def is_controlled_vocabulary(self):
        """
        return True if there is just controlled vocabulary allowed in the second tagline 
        """
        setting = self.__store_config_wrapper.get_show_category_line()
        if setting == ECategorySetting.ENABLED_ONLY_PERSONAL or setting == ECategorySetting.ENABLED_SINGLE_CONTROLLED_TAGLINE:
            return True
        return False
    
    def get_datestamp_format(self):
        return self.__store_config_wrapper.get_datestamp_format()

    def get_datestamp_hidden(self):
        return self.__store_config_wrapper.get_datestamp_hidden()
    
    def get_category_mandatory(self):
        return self.__store_config_wrapper.get_category_mandatory()

    def __name_in_conflict(self, file_name, describing_tag_list, categorising_tag_list):
        """
        checks for conflicts and returns the result as boolean
        """
        
        ## both lists could be none if there is just one tagline
        if(categorising_tag_list is None):
            categorising_tag_list = []
        if(describing_tag_list is None):
            describing_tag_list = []
        
        #TODO: extend functionality: have a look at #18 (Wiki)
        existing_files = self.__tag_wrapper.get_files()
        existing_tags = self.__tag_wrapper.get_all_tags()
        tag_list = list(set(describing_tag_list) | set(categorising_tag_list))

        file_name = unicode(file_name)        

        if file_name in existing_tags:
            return [file_name, EConflictType.FILE]
        for tag in tag_list:
            if tag in existing_files:
                return [tag, EConflictType.TAG]
        return ["", None]
    
    def add_item_list_with_tags(self, file_name_list, describing_tag_list, categorising_tag_list=None, silent=False):
        for item in file_name_list:
            self.add_item_with_tags(item, describing_tag_list, categorising_tag_list, silent)
        
    def add_item_with_tags(self, file_name, describing_tag_list, categorising_tag_list=None, silent=False):
        """
        adds tags to the given file, resets existing tags
        """
        #TODO: if file_name already in config, delete missing tags and recreate whole link structure
        #existing tags will not be recreated in windows-> linux, osx???
         
        #self.__log.info("add item with tags to navigation: itemname: %s" % file_name)
        #self.__log.info("describing tags: %s" % describing_tag_list)
        #self.__log.info("categorizing tags: %s" % categorising_tag_list)
        ## throw error if inodes run short
        if self.__file_system.inode_shortage(self.__config_path):
            self.__log.error("inode threshold has exceeded")
            raise InodeShortageException(TsRestrictions.INODE_THRESHOLD)
        ## throw error if item-names and tag-names (new and existing) are in conflict
        conflict = self.__name_in_conflict(file_name, describing_tag_list, categorising_tag_list)
        if conflict[0] != "":
            self.__log.error("name_in_conflict_error: %s, %s" % (conflict[0], conflict[1]))
            raise NameInConflictException(conflict[0], conflict[1])
        ## ignore multiple tags
        describing_tags = list(set(describing_tag_list))
        categorising_tags = []
        if categorising_tag_list is not None:
            categorising_tags = list(set(categorising_tag_list))


        #try:
        self.__create_inprogress_file()
        
        #print "-----"
        #print self.__describing_nav_path
        #print self.__categorising_nav_path
        #print self.__navigation_path
        # is it not an android store

        start = time()#time.clock() ## performance measure
        self.__log.info("starting to create TagTrees for item: %s" % file_name) ## FIXXME: remove this line if time measurement works

        if not self.__is_android_store():
            for path in self.__paths_to_maintain:
                if path == self.__describing_nav_path:
                    self.__build_store_navigation(file_name, describing_tags, self.__describing_nav_path)
                elif path == self.__categorising_nav_path:
                    self.__build_store_navigation(file_name, categorising_tags, self.__categorising_nav_path)
                elif path == self.__navigation_path:
                    self.__build_store_navigation(file_name, describing_tags, self.__navigation_path)
        #except:
        #    raise Exception, self.trUtf8("An error occurred during building the navigation path(s) and links!")
        #try:
        
        self.__tag_wrapper.set_file(file_name, describing_tags, categorising_tags)

        self.__pending_changes.remove(file_name)
        self.__remove_inprogress_file()
        #except:
        #    raise Exception, self.trUtf8("An error occurred during saving file and tags to configuration file!")
        ## scalability test
        ## print "number of tags: " + str(len(tags)) + ", time: " + str(time.clock()-start)
        self.__log.info("tagged item " + file_name + \
                            ", # descr tags: " + str(len(describing_tags)) + \
                            ", # categ tags: " + str(len(categorising_tags)) + \
                            "; %f" % (time()-start) )  ## performance measure
        ## CAUTION: time.clock() measures something weird, but not actual time
        
    def __build_store_navigation(self, link_name, tag_list, current_path):
        """
        builds the whole directory and link-structure (describing & categorising nav path) inside a stores filesystem
        """
        link_source = self.__watcher_path + "/" + link_name

        for tag in tag_list:
            self.__file_system.create_dir(current_path + "/" + tag)
            self.__file_system.create_link(link_source, current_path + "/" + tag + "/" + link_name)
            recursive_list = [] + tag_list
            recursive_list.remove(tag)
            self.__build_store_navigation(link_name, recursive_list, current_path + "/" + tag)
    
    def rename_tag(self, old_tag_name, new_tag_name):
        """
        renames a tag inside the store 
        """
        self.__create_inprogress_file()
        ##get all affected files per tag
        files = self.__tag_wrapper.get_files_with_tag(old_tag_name)
        self.delete_tags([old_tag_name])
        for file in files:
            if old_tag_name in file["tags"]:
                file["tags"].append(new_tag_name)
                file["tags"].remove(old_tag_name)
            if old_tag_name in file["category"]:
                file["category"].append(new_tag_name)
                file["category"].remove(old_tag_name)
            self.add_item_with_tags(file["filename"], file["tags"], file["category"]) 
        self.__remove_inprogress_file()
    
    def item_exists(self, item_name):
        """
        returns True or False if the item is entered in the store.tgs
        """
        return self.__tag_wrapper.file_exists(item_name)
    
    def delete_tags(self, tag_list):
        """
        delete tags inside the store
        """
        self.__create_inprogress_file()
        for tag_name in tag_list:
            ##get all affected files per tag
            files = self.__tag_wrapper.get_files_with_tag(tag_name)
            for file in files:
                for path in self.__paths_to_maintain:
                    if path == self.__describing_nav_path:
                        self.__delete_tag_folders(tag_name, file["tags"], self.__describing_nav_path)
                    if path == self.__categorising_nav_path:
                        self.__delete_tag_folders(tag_name, file["category"], self.__categorising_nav_path)
                    if path == self.__navigation_path:
                        self.__delete_tag_folders(tag_name, file["tags"], self.__navigation_path)
            ##remove tag from config file
            self.__tag_wrapper.remove_tag(tag_name)
        self.__remove_inprogress_file()
        
    def __delete_tag_folders(self, affected_tag, tag_list, current_path):
        """
        recursive function to delete the tag directories within the describing_nav structure
        """
        if affected_tag not in tag_list:
            return
        self.__file_system.delete_dir(current_path + "/" + affected_tag)
        diff_list = [] + tag_list
        diff_list.remove(affected_tag)
        for tag in diff_list:
            recursive_list = [] + tag_list
            recursive_list.remove(tag)
            self.__delete_tag_folders(affected_tag, recursive_list, current_path + "/" + tag)
        
    def get_controlled_vocabulary(self):
        """
        returns a predefined list of allowed strings (controlled vocabulary) to be used for categorizing
        """
        return self.__vocabulary_wrapper.get_vocabulary()
    
    def set_controlled_vocabulary(self, vocabulary_set):
        self.__vocabulary_wrapper.set_vocabulary(vocabulary_set)

    def get_storage_directory(self):
        """
        returns the path of the storage directory where the items are stored
        """        
        return self.__watcher_path
    
    def get_android_root_directory(self):
        """
        returns the root directory of the android removable storage drive
        """
        res = self.__path[:self.__path.find("/tagstore")] ###FIXME hardcoded constant
        return res
    
    def __is_android_store(self):
        """
        returns True if the store is an android store
        """
        
        # get 'android_store' store setting
        result = self.__store_config_wrapper.get_android_store()
        
        # is this setting active
        if result is None or result =="":
            return False
        
        if int(result) == 0:
            return False
        else:
            return True
        
    def is_android_store(self):
        return self.__is_android_store()
        
    def set_sync_tags(self, file_name, describing_tags, categorising_tags):
        """
        updates the sync tags
        """
        
        # is the and sync tag wrapper initialized
        if self.__sync_tag_wrapper != None:
            self.__sync_tag_wrapper.set_file(file_name, describing_tags, categorising_tags)

    
    def is_sync_active(self):
        """
        returns True when the sync is active
        """
        return self.__is_sync_active()
    
    def create_sync_lock_file(self):
        """
        creates the sync lock file
        """
        
        if self.__is_sync_active():
            # sync is already active
            return False
        
        # get sync lock file
        path = self.__get_lockfile_path()
        
        # get current pid
        pid = PidHelper.get_current_pid()
        
        # write new pid file
        pid_file = open(path, "w")
        pid_file.write(str(pid))
        pid_file.close()
        
        # done
        return True
    
    def remove_sync_lock_file(self):
        """
        removes the sync lock file
        """
        
        # sync lockfile
        path = self.__get_lockfile_path()
        
        # remove lock file
        self.__file_system.remove_file(path)

    def finish_sync(self):
        """
        writes all changes to the config file / sync file
        """
        
        if self.__tag_wrapper != None:
            self.__tag_wrapper.sync_settings()
        
        if self.__sync_tag_wrapper != None:
            self.__sync_tag_wrapper.sync_settings()


    def get_tag_recommendation(self, number, file_name):
        """
        Changes from Georg
        returns the recommendation
        """
        dictionary = self.__recommender.get_tag_recommendation(
                              self.__tag_wrapper,
                              file_name,
                              number,
                              self.__storage_dir_name)
        
        
        list_with_high_prio = []
        
        threshold = 0.9
        if len(dictionary) <= number:
            threshold = 0.5
        
        for tag_name, rating in dictionary.iteritems():
            if rating > threshold:
                list_with_high_prio.append(tag_name)
    
        
        
        list1 = sorted(dictionary.iteritems(), key=lambda (k,v): (v,k), reverse=True)
        return_list = []
        #for item in list[:number]:
        for item in list1[:15]:
            if item[0] in list_with_high_prio:
                return_list.append(item[0])  
        return return_list

        #list = sorted(dictionary.iteritems(), key=lambda (k,v): (v,k), reverse=True)
        #return_list = []
        #print list
        #for item in list[:number]:
        #for item in list:
        #    return_list.append(item[0])
        #print return_list
        #return return_list

        
    def get_cat_recommendation(self, number, file_name):
        """
        Changes from Georg
        returns the recommendation
        """
        allowed_dict = {}
        if self.is_controlled_vocabulary():
                allowed_dict = self.get_controlled_vocabulary()
        
        dictionary = self.__recommender.get_cat_recommendation(
                              self.__tag_wrapper,
                              file_name,
                              number,
                              self.__storage_dir_name,
                              allowed_dict)
        

        list_with_high_prio = []
        
        threshold = 0.9
        if len(dictionary) <= number:
            threshold = 0.5
        
        for tag_name, rating in dictionary.iteritems():
            if rating > threshold:
                list_with_high_prio.append(tag_name)
    
        
        
        list1 = sorted(dictionary.iteritems(), key=lambda (k,v): (v,k), reverse=True)
        return_list = []
        #for item in list[:number]:
        for item in list1[:15]:
            if item[0] in list_with_high_prio:
                return_list.append(item[0])  
        
        return return_list
        '''
        if len(dictionary) > number/2:
            for item in list:
                if dictionary[item] < 0.6:
                    return_list.append(item[0])
            print "if"
            print return_list
            
        else:
            for item in list:
                return_list.append(item[0])
            print return_list
        return return_list
        '''
        
        
    def get_tag_cloud(self, name):
        dict = self.__tag_wrapper.get_tag_dict(self.__tag_wrapper.KEY_TAGS)
        if len(dict) < 10:
            extension = self.__recommender.get_file_extension(name)
            self.__recommender.recommend_new_tags(dict, extension)
        return self.__tagcloud.create_tag_cloud(dict)
    
    def get_cat_cloud(self, name):
        tmp_dict = self.__tag_wrapper.get_tag_dict(self.__tag_wrapper.KEY_CATEGORIES)
        
        if len(tmp_dict) < 10:
            extension = self.__recommender.get_file_extension(name)
            self.__recommender.recommend_new_tags(tmp_dict, extension)
        
        
        dict = {}
        if self.is_controlled_vocabulary():
            allowed_list = self.get_controlled_vocabulary()
            for cat, size in tmp_dict.iteritems():
                if cat in allowed_list:
                    dict.setdefault(cat, size)
            for cat in allowed_list:
                if cat not in dict:
                    dict.setdefault(cat, 0)
            return self.__tagcloud.create_tag_cloud(dict)
        else:
            return self.__tagcloud.create_tag_cloud(tmp_dict)
    
    def get_tagline_config(self):
        return self.__tagline_config    
Beispiel #29
0
    def __init_configuration(self):
        """
        initializes the configuration. This method is called every time the config file changes
        """
        self.__log.info("initialize configuration")
        
        
        self.__main_config = ConfigWrapper(TsConstants.CONFIG_PATH)
        
        if self.__main_config is None:
            self.__emit_not_retagable(self.trUtf8("No config file found for the given path"))
            return
        
        ## check if there has been found an appropriate store_path in the config 
        if self.__store_path is None:
            self.__emit_not_retagable(self.trUtf8("No store found for the given path"))
            return
        else:
            self.__store_config = ConfigWrapper(self.__store_path)
        
        self.__prepare_store_params()
        
        self.CURRENT_LANGUAGE = self.__main_config.get_current_language();
        self.change_language(self.CURRENT_LANGUAGE)
        #self.__main_config.connect(self.__main_config, QtCore.SIGNAL("changed()"), self.__init_configuration)

        self.__store = Store(self.__store_config.get_store_id(), self.__store_path, 
              self.STORE_CONFIG_DIR + "/" + self.STORE_CONFIG_FILE_NAME,
              self.STORE_CONFIG_DIR + "/" + self.STORE_TAGS_FILE_NAME,
              self.STORE_CONFIG_DIR + "/" + self.STORE_VOCABULARY_FILE_NAME,
              self.STORE_NAVIGATION_DIRS,
              self.STORE_STORAGE_DIRS, 
              self.STORE_DESCRIBING_NAV_DIRS,
              self.STORE_CATEGORIZING_NAV_DIRS,
              self.STORE_EXPIRED_DIRS,
              self.__main_config.get_expiry_prefix())
        self.__store.init()
        
        if self.__tag_dialog is None:
            self.__tag_dialog = TagDialogController(self.__store.get_name(), self.__store.get_id(), self.__main_config.get_max_tags(), self.__main_config.get_tag_seperator(), self.__main_config.get_expiry_prefix())
            self.__tag_dialog.get_view().setModal(True)
            #self.__tag_dialog.set_parent(self.sender().get_view())
            self.__tag_dialog.connect(self.__tag_dialog, QtCore.SIGNAL("tag_item"), self.__tag_item_action)
            self.__tag_dialog.connect(self.__tag_dialog, QtCore.SIGNAL("handle_cancel()"), self.__handle_tag_cancel)


        ## configure the tag dialog with the according settings
        format_setting = self.__store.get_datestamp_format()
        datestamp_hidden = self.__store.get_datestamp_hidden()
        ## check if auto datestamp is enabled
        if format_setting != EDateStampFormat.DISABLED:
            self.__tag_dialog.show_datestamp(True)
            ## set the format
            format = None
            if format_setting == EDateStampFormat.DAY:
                format = TsConstants.DATESTAMP_FORMAT_DAY
            elif format_setting == EDateStampFormat.MONTH:
                format = TsConstants.DATESTAMP_FORMAT_MONTH
            
            self.__tag_dialog.set_datestamp_format(format, datestamp_hidden)
        
        self.__tag_dialog.show_category_line(self.__store.get_show_category_line())
        self.__tag_dialog.set_category_mandatory(self.__store.get_category_mandatory()) 
        
        ## check if the given item really exists in the store
        if not self.__store.item_exists(self.__item_name):
            self.__emit_not_retagable(self.trUtf8("%s: There is no such item recorded in the store" % self.__item_name))
            return 

        self.__set_tag_information_to_dialog(self.__store)
        
        if self.__retag_mode:
            self.__handle_retag_mode()
        
        self.__tag_dialog.show_dialog()
Beispiel #30
0
    def __init_configuration(self):
        """
        initializes the configuration
        """

        # informal debug
        self.__log.info("__init_configuration")

        # construct config wrapper
        self.__main_config = ConfigWrapper(TsConstants.CONFIG_PATH)
        if self.__main_config is None:
            self.__emit_not_syncable(
                self.trUtf8("No config file found for the given path"))
            return

        search_source_path = False
        found_source_path = False
        if self.__source_store_path != None and self.__source_store_path != "":
            search_source_path = True

        search_target_path = False
        found_target_path = False
        if self.__target_store_path != None and self.__target_store_path != "":
            search_target_path = True

        ## create a temporary store list
        ## add the desc and cat tags which are needed in the admin-dialog
        tmp_store_list = []
        store_list = self.__main_config.get_stores()

        # add android store
        # when registered
        android_source_path = self.__main_config.get_android_store_path()
        if android_source_path != None and android_source_path != "":
            store_item = {}
            store_item["path"] = android_source_path
            store_item["name"] = "Android"
            store_list.append(store_item)

        # enumerate all stores and add their names and paths
        store_name = None
        for current_store_item in store_list:

            if current_store_item.has_key("name"):
                store_name = current_store_item["name"]
            else:
                store_name = current_store_item["path"].split("/").pop()

            store_path = current_store_item["path"]
            current_store_item["name"] = store_name
            current_store_item["path"] = store_path

            tmp_store_list.append(current_store_item)
            # find source target list
            if search_source_path:
                if store_path == self.__source_store_path:
                    found_source_path = True

            if search_target_path:
                if store_path == self.__target_store_path:
                    found_target_path = True

        if search_source_path and found_source_path == False:
            # source store is not registered
            self.__emit_not_syncable(
                self.trUtf8("Source tagstore not registered in main config"))
            return

        if search_target_path and found_target_path == False:
            # source store is not registered
            self.__emit_not_syncable(
                self.trUtf8("Target tagstore not registered in main config"))
            return

        if self.__sync_dialog is None:
            self.__sync_dialog = SyncDialogController(tmp_store_list,
                                                      self.__source_store_path,
                                                      self.__target_store_path,
                                                      self.__auto_sync)
            self.__sync_dialog.get_view().setModal(True)
            #self.__tag_dialog.set_parent(self.sender().get_view())
            self.__sync_dialog.connect(self.__sync_dialog,
                                       QtCore.SIGNAL("sync_store"),
                                       self.__sync_store_action)
            self.__sync_dialog.connect(self.__sync_dialog,
                                       QtCore.SIGNAL("sync_conflict"),
                                       self.__handle_resolve_conflict)
            self.__sync_dialog.connect(self.__sync_dialog,
                                       QtCore.SIGNAL("handle_cancel()"),
                                       self.__handle_sync_cancel)

        self.__sync_dialog.show_dialog()
        if self.__auto_sync:
            self.__sync_dialog.start_auto_sync()
Beispiel #31
0
class Tagstore(QtCore.QObject):

    def __init__(self, application, parent=None, verbose=False, dryrun=False):
        """ 
        initializes the configuration. This method is called every time the config file changes
        """
        
        QtCore.QObject.__init__(self)
        
        self.__application = application
        
        self.__admin_widget = None
        
        self.DRY_RUN = dryrun        
        ## initialize localization
        self.__system_locale = unicode(QtCore.QLocale.system().name())[0:2]
        self.__translator = QtCore.QTranslator()
        if self.__translator.load("ts_" + self.__system_locale + ".qm", "tsresources/"):
            self.__application.installTranslator(self.__translator)
        # "en" is automatically translated to the current language e.g. en -> de
        self.CURRENT_LANGUAGE = self.trUtf8("en")
        self.SUPPORTED_LANGUAGES = TsConstants.DEFAULT_SUPPORTED_LANGUAGES
        
        ## global settings/defaults (only used if reading config file failed or invalid!)
        self.STORE_CONFIG_DIR = TsConstants.DEFAULT_STORE_CONFIG_DIR
        self.STORE_CONFIG_FILE_NAME = TsConstants.DEFAULT_STORE_CONFIG_FILENAME
        self.STORE_TAGS_FILE_NAME = TsConstants.DEFAULT_STORE_TAGS_FILENAME
        self.STORE_VOCABULARY_FILE_NAME = TsConstants.DEFAULT_STORE_VOCABULARY_FILENAME

        #get dir names for all available languages
        store_current_language = self.CURRENT_LANGUAGE 
        self.STORE_STORAGE_DIRS = []
        self.STORE_DESCRIBING_NAV_DIRS = []
        self.STORE_CATEGORIZING_NAV_DIRS = []
        self.STORE_EXPIRED_DIRS = []
        self.STORE_NAVIGATION_DIRS = []

        for lang in self.SUPPORTED_LANGUAGES: 
            self.change_language(lang)
            self.STORE_NAVIGATION_DIRS.append(self.trUtf8("navigation")) 
            self.STORE_STORAGE_DIRS.append(self.trUtf8("storage"))#self.STORE_STORAGE_DIR_EN))  
            self.STORE_DESCRIBING_NAV_DIRS.append(self.trUtf8("descriptions"))#self.STORE_DESCRIBING_NAVIGATION_DIR_EN))  
            self.STORE_CATEGORIZING_NAV_DIRS.append(self.trUtf8("categories"))#self.STORE_CATEGORIZING_NAVIGATION_DIR_EN)) 
            self.STORE_EXPIRED_DIRS.append(self.trUtf8("expired_items"))#STORE_EXPIRED_DIR_EN)) 
        ## reset language 
        self.change_language(store_current_language) 
     
        self.EXPIRY_PREFIX = TsConstants.DEFAULT_EXPIRY_PREFIX
        self.TAG_SEPERATOR = TsConstants.DEFAULT_TAG_SEPARATOR
        self.NUM_RECENT_TAGS = TsConstants.DEFAULT_RECENT_TAGS
        self.NUM_POPULAR_TAGS = TsConstants.DEFAULT_POPULAR_TAGS
        self.MAX_TAGS = TsConstants.DEFAULT_MAX_TAGS
        self.MAX_CLOUD_TAGS = TsConstants.DEFAULT_MAX_CLOUD_TAGS
        self.STORES = []
        ## dict for dialogs identified by their store id
        self.DIALOGS = {}
        
        ## init configurations
        self.__app_config_wrapper = None
        self.__log = None
        
        self.LOG_LEVEL = logging.INFO
        if verbose:
            self.LOG_LEVEL = logging.DEBUG
            
        self.__log = LogHelper.get_app_logger(self.LOG_LEVEL)
        self.__log.info("starting tagstore watcher")
        
        self.__init_configurations()
        
    def __init_configurations(self):
        """
        initializes the configuration. This method is called every time the config file changes
        """
        self.__log.info("initialize configuration")
        ## reload config file - overwrite default settings
        self.__app_config_wrapper = ConfigWrapper(TsConstants.CONFIG_PATH)
        self.__app_config_wrapper.connect(self.__app_config_wrapper, QtCore.SIGNAL("changed()"), self.__init_configurations)
        self.__app_config_wrapper.print_app_config_to_log()
        tag_seperator = self.__app_config_wrapper.get_tag_seperator()
        if tag_seperator.strip() != "":
            self.TAG_SEPERATOR = tag_seperator
        expiry_prefix = self.__app_config_wrapper.get_expiry_prefix()
        if expiry_prefix.strip() != "":
            self.EXPIRY_PREFIX = expiry_prefix
        
        self.NUM_RECENT_TAGS = self.__app_config_wrapper.get_num_popular_tags()
        self.NUM_POPULAR_TAGS = self.__app_config_wrapper.get_num_popular_tags()
        self.MAX_TAGS = self.__app_config_wrapper.get_max_tags()
        
        self.CURRENT_LANGUAGE = self.__app_config_wrapper.get_current_language();
        if self.CURRENT_LANGUAGE is None or self.CURRENT_LANGUAGE == "":
            self.CURRENT_LANGUAGE = self.trUtf8("en")
        self.change_language(self.CURRENT_LANGUAGE)
        
        config_dir = self.__app_config_wrapper.get_store_config_directory()
        if config_dir != "":
            self.STORE_CONFIG_DIR = config_dir
        config_file_name = self.__app_config_wrapper.get_store_configfile_name()
        if config_file_name != "":
            self.STORE_CONFIG_FILE_NAME = config_file_name
        tags_file_name = self.__app_config_wrapper.get_store_tagsfile_name()
        if tags_file_name != "":
            self.STORE_TAGS_FILE_NAME = tags_file_name
        vocabulary_file_name = self.__app_config_wrapper.get_store_vocabularyfile_name()
        if vocabulary_file_name != "":
            self.STORE_VOCABULARY_FILE_NAME = vocabulary_file_name
        
       
#        self.SUPPORTED_LANGUAGES = self.__app_config_wrapper.get_supported_languages()
#        current_language = self.CURRENT_LANGUAGE
#        self.STORE_STORAGE_DIRS = []
#        self.STORE_NAVIGATION_DIRS = [] 
#        for lang in self.SUPPORTED_LANGUAGES:
#            self.change_language(lang)
#            self.STORE_STORAGE_DIRS.append(self.trUtf8("storage")) 
#            self.STORE_NAVIGATION_DIRS.append(self.trUtf8("navigation")) 
#        ## reset language
#        self.change_language(current_language)

        ## get stores from config file         
        config_store_items = self.__app_config_wrapper.get_stores()
        config_store_ids = self.__app_config_wrapper.get_store_ids()

        deleted_stores = []
        for store in self.STORES:
            id = store.get_id()
            if id in config_store_ids:
            ## update changed stores
                store.set_path(self.__app_config_wrapper.get_store_path(id), 
                               self.STORE_CONFIG_DIR + "/" + self.STORE_CONFIG_FILE_NAME,
                               self.STORE_CONFIG_DIR + "/" + self.STORE_TAGS_FILE_NAME,
                               self.STORE_CONFIG_DIR + "/" + self.STORE_VOCABULARY_FILE_NAME)
                store.change_expiry_prefix(self.EXPIRY_PREFIX)               
                config_store_ids.remove(id)             ## remove already updated items
            else:
            ## remove deleted stores
                deleted_stores.append(store)

        ## update deleted stores from global list after iterating through it
        for store in deleted_stores:
            self.STORES.remove(store)
            self.__log.debug("removed store: %s", store.get_name())
        
        ## add new stores
        for store_item in config_store_items:
            if store_item["id"] in config_store_ids:    ## new
                store = Store(store_item["id"], store_item["path"], 
                              self.STORE_CONFIG_DIR + "/" + self.STORE_CONFIG_FILE_NAME,
                              self.STORE_CONFIG_DIR + "/" + self.STORE_TAGS_FILE_NAME,
                              self.STORE_CONFIG_DIR + "/" + self.STORE_VOCABULARY_FILE_NAME,
                              self.STORE_NAVIGATION_DIRS,
                              self.STORE_STORAGE_DIRS, 
                              self.STORE_DESCRIBING_NAV_DIRS,
                              self.STORE_CATEGORIZING_NAV_DIRS,
                              self.STORE_EXPIRED_DIRS,
							  self.EXPIRY_PREFIX)

                store.connect(store, QtCore.SIGNAL("removed(PyQt_PyObject)"), self.store_removed)
                store.connect(store, QtCore.SIGNAL("renamed(PyQt_PyObject, QString)"), self.store_renamed)
                store.connect(store, QtCore.SIGNAL("file_renamed(PyQt_PyObject, QString, QString)"), self.file_renamed)
                store.connect(store, QtCore.SIGNAL("file_removed(PyQt_PyObject, QString)"), self.file_removed)
                store.connect(store, QtCore.SIGNAL("pending_operations_changed(PyQt_PyObject)"), self.pending_file_operations)
                store.connect(store, QtCore.SIGNAL("vocabulary_changed"), self.__handle_vocabulary_changed)
                store.connect(store, QtCore.SIGNAL("store_config_changed"), self.__handle_store_config_changed)

                extensions = self.__app_config_wrapper.get_additional_ignored_extension()
                ##if there comes a value from the config -> set it
                if len(extensions) > 0 and extensions[0] != "":
                    store.add_ignored_extensions(extensions)

                self.STORES.append(store)

                self.__log.debug("init store: %s", store.get_name())
                
                ## create a dialogcontroller for each store ...
                tmp_dialog = TagDialogController(store.get_name(), store.get_id(), self.MAX_TAGS, self.TAG_SEPERATOR, self.EXPIRY_PREFIX)
                
                tmp_dialog.connect(tmp_dialog, QtCore.SIGNAL("tag_item"), self.tag_item_action)
                tmp_dialog.connect(tmp_dialog, QtCore.SIGNAL("handle_cancel()"), self.handle_cancel)
                tmp_dialog.connect(tmp_dialog, QtCore.SIGNAL("open_store_admin_dialog()"), self.show_admin_dialog)
                
                self.DIALOGS[store.get_id()] = tmp_dialog
                ## call init to initialize new store instance (after adding the event handler)
                ## necessary if store was renamed during tagstore was not running (to write config)
                store.init()
                self.connect(tmp_dialog, QtCore.SIGNAL("item_selected"), self.__set_tag_information_to_dialog_wrapper)
                self.__configure_tag_dialog(store, tmp_dialog)
                

    
    def __configure_tag_dialog(self, store, tmp_dialog):
        """
        given a store and a tag dialog - promote all settings to the dialog 
        """
        
        format_setting = store.get_datestamp_format()
        is_hidden = store.get_datestamp_hidden()

        ##check if auto datestamp is enabled
        if format_setting != EDateStampFormat.DISABLED:
            tmp_dialog.show_datestamp(True)
            ## set the format
            format = None
            if format_setting == EDateStampFormat.DAY:
                format = TsConstants.DATESTAMP_FORMAT_DAY
            elif format_setting == EDateStampFormat.MONTH:
                format = TsConstants.DATESTAMP_FORMAT_MONTH
            tmp_dialog.set_datestamp_format(format, is_hidden)
                
        tmp_dialog.show_category_line(store.get_show_category_line())
        tmp_dialog.set_category_mandatory(store.get_category_mandatory())

    def __handle_vocabulary_changed(self, store):
        self.__set_tag_information_to_dialog(store)
    
    def __handle_store_config_changed(self, store):
        ## at first get the store-corresponding dialog controller
        dialog_controller = self.DIALOGS[store.get_id()]
        self.__configure_tag_dialog(store, dialog_controller)
    
    def show_admin_dialog(self):
        self.__admin_widget = Administration(self.__application, verbose=verbose_mode)
        self.__admin_widget.set_modal(True)
        self.__admin_widget.set_parent(self.sender().get_view())
        #admin_widget.set_parent(self.__tag_dialog)
        self.__admin_widget.show_admin_dialog(True)
    
    def store_removed(self, store):
        """
        event handler of the stores remove event
        """
        self.__app_config_wrapper.remove_store(store.get_id())
        ## __init_configuration is called due to config file changes
        
    def store_renamed(self, store, new_path):
        """
        event handler of the stores rename event
        """
        self.__app_config_wrapper.rename_store(store.get_id(), new_path)
        ## __init_configuration is called due to config file changes
        
    def file_renamed(self, store, old_file_name, new_file_name):
        """
        event handler for: file renamed
        """
        self.__log.debug("..........file renamed %s, %s" % (old_file_name, new_file_name))
        store.rename_file(old_file_name, new_file_name)
        
    def file_removed(self, store, file_name):
        """
        event handler for: file renamed
        """
        self.__log.debug("...........file removed %s" % file_name)
        store.remove_file(file_name)
        
    def pending_file_operations(self, store):
        """
        event handler: handles all operations with user interaction
        """        
        self.__log.info("new pending file operation added")

        dialog_controller = self.DIALOGS[store.get_id()]
        
        #dialog_controller.clear_store_children(store.get_name())
#        dialog_controller.clear_all_items()
        dialog_controller.clear_tagdialog()
        
        added_list = set(store.get_pending_changes().get_items_by_event(EFileEvent.ADDED))
        added_renamed_list = set(store.get_pending_changes().get_items_by_event(EFileEvent.ADDED_OR_RENAMED))
        
        whole_list = added_list | added_renamed_list
        
        if whole_list is None or len(whole_list) == 0:
            dialog_controller.hide_dialog()
            return
        self.__log.debug("store: %s, item: %s " % (store.get_id(), store.get_pending_changes().to_string()))
        
        for item in whole_list:
            dialog_controller.add_pending_item(item)

        self.__set_tag_information_to_dialog(store)

        
        dialog_controller.show_dialog()
     
    def handle_cancel(self):
        dialog_controller = self.sender()
        if dialog_controller is None or not isinstance(dialog_controller, TagDialogController):
            return
        dialog_controller.hide_dialog()
        
    def __set_tag_information_to_dialog_wrapper(self, store_id):
        for store in self.STORES:
            if store.get_id() == store_id:
                self.__set_tag_information_to_dialog(store)

    def __set_tag_information_to_dialog(self, store):
        """
        convenience method for refreshing the tag data at the gui-dialog
        """
        self.__log.debug("refresh tag information on dialog")
        dialog_controller = self.DIALOGS[store.get_id()]
        dialog_controller.set_tag_list(store.get_tags())
        
        item_list = dialog_controller.get_selected_item_list_public()

        if item_list is not None:
            if len(item_list) > 0 and item_list[0] is not None:
    
                if store.get_tagline_config() == 1 or store.get_tagline_config() == 2 or store.get_tagline_config() == 3:
                    tmp_cat_list = store.get_cat_recommendation(self.NUM_POPULAR_TAGS, str(item_list[0].text()))
                    cat_list = []
                    if store.is_controlled_vocabulary():
                        allowed_set = set(store.get_controlled_vocabulary())
                        dialog_controller.set_category_list(list(allowed_set))
            
                        ## just show allowed tags - so make the intersection of popular tags ant the allowed tags
                        for cat in tmp_cat_list:
                            if cat in list(allowed_set):
                                cat_list.append(cat)
                        #for cat in list(allowed_set):
                        #    if cat not in cat_list:
                        #        cat_list.append(cat)
                        #cat_list = list(cat_set.intersection(allowed_set)) 
                    else:
                        dialog_controller.set_category_list(store.get_categorizing_tags())
                        cat_list = tmp_cat_list
                    #print cat_list
                    #if len(cat_list) > self.NUM_POPULAR_TAGS:
                    #    cat_list = cat_list[:self.NUM_POPULAR_TAGS]
                    #dialog_controller.set_popular_categories(cat_list)
                    dict = store.get_cat_cloud(str(item_list[0].text())) 
                    dialog_controller.set_cat_cloud(dict, cat_list, self.MAX_CLOUD_TAGS)
                    
                ## make a list out of the set, to enable indexing, as not all tags cannot be used
                #tag_list = list(tag_set)
                if store.get_tagline_config() == 1 or store.get_tagline_config() == 2 or store.get_tagline_config() == 0:
                    tag_list = store.get_tag_recommendation(self.NUM_POPULAR_TAGS, str(item_list[0].text()))
                    #if len(tag_list) > self.NUM_POPULAR_TAGS:
                    #    tag_list = tag_list[:self.NUM_POPULAR_TAGS]
                    #dialog_controller.set_popular_tags(tag_list)
                    dict = store.get_tag_cloud(str(item_list[0].text()))
                    dialog_controller.set_tag_cloud(dict, tag_list, self.MAX_CLOUD_TAGS)
    
                
        
                #if len(self.DIALOGS) > 1:
                dialog_controller.set_store_name(store.get_name())
    
    def tag_item_action(self, store_name, item_name_list, tag_list, category_list):
        """
        write the tags for the given item to the store
        """
        
        store = None
        ## find the store where the item should be saved    
        for loop_store in self.STORES:
            if store_name == loop_store.get_name():
                store = loop_store
                break
            
        if store is not None:
            dialog_controller = self.DIALOGS[store.get_id()]
            try:
                ## 1. write the data to the store-file
                store.add_item_list_with_tags(item_name_list, tag_list, category_list)
                self.__log.debug("added items %s to store-file", item_name_list)
            except NameInConflictException, e:
                c_type = e.get_conflict_type()
                c_name = e.get_conflicted_name()
                if c_type == EConflictType.FILE:
                    dialog_controller.show_message(self.trUtf8("The filename - %s - is in conflict with an already existing tag. Please rename!" % c_name))
                elif c_type == EConflictType.TAG:
                    dialog_controller.show_message(self.trUtf8("The tag - %s - is in conflict with an already existing file" % c_name))
                else:
                    self.trUtf8("A tag or item is in conflict with an already existing tag/item")
                #raise
            except InodeShortageException, e:
                dialog_controller.show_message(self.trUtf8("The Number of free inodes is below the threshold of %s%" % e.get_threshold()))
                #raise
            except Exception, e:
                dialog_controller.show_message(self.trUtf8("An error occurred while tagging"))
                raise
Beispiel #32
0
    def __init__(self, tab, parent=None):
        '''
        Constructor
        '''
        QtGui.QDialog.__init__(self, parent)

        self.__tab = tab

        self.setWindowFlags(QtCore.Qt.WindowTitleHint)

        self.__config_wrapper = ConfigWrapper(TsConstants.CONFIG_PATH)

        self.__wizard = Wizard()

        self.setWindowTitle("tagstore Manager Help")

        self.setWindowIcon(QtGui.QIcon('./tsresources/images/help.png'))

        self.setWindowModality(QtCore.Qt.WindowModal)
        self.__base_layout = QtGui.QVBoxLayout()
        self.__descr_layout = QtGui.QHBoxLayout()
        self.__bb_layout = QtGui.QHBoxLayout()
        self.setLayout(self.__base_layout)

        self.__description_label = QtGui.QLabel()
        self.__description_label.setTextInteractionFlags(
            QtCore.Qt.TextSelectableByMouse)
        self.__cancel_button = QtGui.QPushButton(self.trUtf8("Close"))
        self.__wizard_button = QtGui.QPushButton(self.trUtf8("Show Wizard"))
        self.__visible_checkbox = QtGui.QCheckBox(
            self.trUtf8("Show this automatically"))

        self.__descr_layout.addWidget(self.__description_label)

        self.__visible_checkbox.setChecked(True)

        if self.__tab == "tagdialog":
            self.__description_label.setText(
                self.trUtf8(
                    "In diesem Fenster werden die Items, die sich in der Ablage befinden getaggt. <br>"
                    "Auf der rechten Seite Oben befindet sich eine Liste der noch nicht getagten Items. Um ein solches zu taggen wird zuerst eines ausgewaehlt, danach werden die Tags in die darunterliegende/n \"Tag-Zeile/n\" (je nach Einstellung) geschrieben und jeweils mit einem \",\" getrennt.<br>"
                    "Mit einem abschliessenden Klick auf \"Tag\" wird das ausgewaehlte Item getaggt.<br>"
                    "Auf der linken Seite befindet sich eine sogenannte \"TagCloud\" (fuer jede \"Tag-Zeile\" eine) in der sich Vorschlaege fuer Tags befinden, wobei die Groesse des Tags angibt, wie oft dieser verwendet wurde (je groesser desto haeufiger).<br>"
                    "Die in Orange dargestellten Tags wurden mit Hilfe eines Algorithmus, speziell fuer das ausgewaehlte Item und ihren Taggingverhalten ausgesucht."
                ))
            self.__tagging_label = QtGui.QLabel()
            self.__tagging_label.setPixmap(
                QtGui.QPixmap(
                    self.trUtf8("./tsresources/images/tagging_en.png")))
            self.__descr_layout.addWidget(self.__tagging_label)
            self.setWindowTitle(self.trUtf8("Help tag-dialog"))
            if not self.__config_wrapper.get_show_tag_help():
                self.__visible_checkbox.setChecked(False)
        elif self.__tab == "My Tags":
            self.__description_label.setText(
                self.trUtf8(
                    "Unter dem Reiter \"Meine Tags\" kann fuer jeden Store nachgeschaut werden, ob eine oder zwei Tag-Zeilen verwendet werden. In der erste Tag-Zeile werden beschreibende und in der zweiten kategorisierende Tags verwendet.<br>"
                    "Wird nur eine Zeile verwendet, kann eingestellt werden, ob in dieser nur vom Benutzter vordefinierte Tags (\"Meine Tags\") erlaubt werden oder nicht. Dies soll die Verwendung von aehnlichen Tags verhindern(z.B: Uni, Universitaet). <br>"
                    "Solche Tags koennen mit einem klick auf \"Hinzufuegen\" der Liste von Tags hinzugefuegt werden oder mit \"Loeschen\" von ihr geloescht werden. <br>"
                    "Werden zwei Zeilen verwendet, kann nur noch fuer die zweite Tag-Zeile eingestellt werden, ob diese nur \"meine Tags\" verwenden soll."
                ))
            if not self.__config_wrapper.get_show_my_tags_help():
                self.__visible_checkbox.setChecked(False)
        elif self.__tab == "Datestamps":
            self.__description_label.setText(
                self.trUtf8(
                    "Unter diesem Reiter kann eingestellt werden, ob beim taggen, das Datum automatisch in die \"Tagline\" eingetragen wird."
                ))
            if not self.__config_wrapper.get_show_datestamps_help():
                self.__visible_checkbox.setChecked(False)
        elif self.__tab == "Expiry Date":
            self.__description_label.setText(
                self.trUtf8(
                    "Unter diesem Reiter kann eingestellt werden, wann ein bestimmtes Objekt, mit Hilfe eines Praefixes, ablaeuft und in den Ordner \"abgelaufene_Daten\" verschoben wird.<br>"
                    "Diesr Praefix kann in der Zeile am unteren Ende des Tabs definiert werden."
                ))
            if not self.__config_wrapper.get_show_expiry_date_help():
                self.__visible_checkbox.setChecked(False)
        elif self.__tab == "Re-Tagging":
            self.__description_label.setText(
                self.trUtf8(
                    "Unter diesem Reiter koennen bereits getaggte Objekte neu getaggt werden. Dazu wird ein Objekt ausgewaehlt und anschliessend auf \"Re-Tag\" geklickt."
                ))
            if not self.__config_wrapper.get_show_retagging_help():
                self.__visible_checkbox.setChecked(False)
        elif self.__tab == "Rename Tags":
            self.__description_label.setText(
                self.trUtf8(
                    "Unter diesem Reiter koennen Tags, die zum taggen verwendet wurden, umbenannt werden, womit sich auch die Ordnerstruktur aendert.<br>"
                    "Dazu wird ein Tag ausgewaehlt und anschliessend auf \"Umbenennen\" geklickt."
                ))
            if not self.__config_wrapper.get_show_rename_tags_help():
                self.__visible_checkbox.setChecked(False)
        elif self.__tab == "Store Management":
            self.__description_label.setText(
                self.trUtf8(
                    "Unter diesem Reiter kann ein neuer \"Store\" angelegt oder ein bereits vorhandener geloescht oder umbenannet werden. Dies geschiet durch einen Klick auf \"Neuer tagstore\", \"Loeschen ...\" oder \"Umbenennen ...\".<br>"
                    "Zum loeschen oder umbenennen muss der gewuenschte \"Store\" ausgewaehlt werden.<br>"
                    "Wenn erwuenscht, kann auch die Ordnerstruktur des ausgewaehlten \"Stores\", mit einem klick auf \"Struktur neu erstellen ...\" neu erstellt werden"
                ))
            if not self.__config_wrapper.get_show_store_management_help():
                self.__visible_checkbox.setChecked(False)
        elif self.__tab == "Sync Settings":
            self.__description_label.setText(
                self.trUtf8(
                    "Unter diesem Reiter kann ein Tag zum synchronisieren mit Android definiert werden. Alle Items, welche mit diesem Tag getaggt werden, werden automatisch synchronisiert."
                ))
            if not self.__config_wrapper.get_show_sync_settings_help():
                self.__visible_checkbox.setChecked(False)

        self.__description_label.setWordWrap(True)

        self.__bb_layout.addWidget(self.__visible_checkbox)
        self.__bb_layout.addWidget(self.__wizard_button)
        self.__bb_layout.addWidget(self.__cancel_button)
        self.__base_layout.addLayout(self.__descr_layout)
        self.__base_layout.addLayout(self.__bb_layout)

        self.__visible_checkbox.stateChanged.connect(self.__handle_checkbox)

        self.connect(self.__cancel_button, QtCore.SIGNAL('clicked()'),
                     self.__handle_close)
        self.connect(self.__wizard_button, QtCore.SIGNAL('clicked()'),
                     self.__handle_show_wizard)
Beispiel #33
0
    def __init__(self, tab, parent = None):
        '''
        Constructor
        '''
        QtGui.QDialog.__init__(self, parent)
        
        self.__tab = tab
              
        self.setWindowFlags(QtCore.Qt.WindowTitleHint)
        
        self.__config_wrapper = ConfigWrapper(TsConstants.CONFIG_PATH)
        
        self.__wizard = Wizard()
        
        self.setWindowTitle("tagstore Manager Help")
        
        self.setWindowIcon(QtGui.QIcon('./tsresources/images/help.png'))
           
        self.setWindowModality(QtCore.Qt.WindowModal)
        self.__base_layout = QtGui.QVBoxLayout()
        self.__descr_layout = QtGui.QHBoxLayout()
        self.__bb_layout = QtGui.QHBoxLayout()
        self.setLayout(self.__base_layout)
        
        self.__description_label = QtGui.QLabel()
        self.__description_label.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
        self.__cancel_button = QtGui.QPushButton(self.trUtf8("Close"))
        self.__wizard_button = QtGui.QPushButton(self.trUtf8("Show Wizard"))
        self.__visible_checkbox = QtGui.QCheckBox(self.trUtf8("Show this automatically"))
        
        self.__descr_layout.addWidget(self.__description_label)      
        
        self.__visible_checkbox.setChecked(True)
        
        if self.__tab == "tagdialog":
            self.__description_label.setText(self.trUtf8("In diesem Fenster werden die Items, die sich in der Ablage befinden getaggt. <br>"
                                                         "Auf der rechten Seite Oben befindet sich eine Liste der noch nicht getagten Items. Um ein solches zu taggen wird zuerst eines ausgewaehlt, danach werden die Tags in die darunterliegende/n \"Tag-Zeile/n\" (je nach Einstellung) geschrieben und jeweils mit einem \",\" getrennt.<br>"
                                                         "Mit einem abschliessenden Klick auf \"Tag\" wird das ausgewaehlte Item getaggt.<br>"
                                                         "Auf der linken Seite befindet sich eine sogenannte \"TagCloud\" (fuer jede \"Tag-Zeile\" eine) in der sich Vorschlaege fuer Tags befinden, wobei die Groesse des Tags angibt, wie oft dieser verwendet wurde (je groesser desto haeufiger).<br>"
                                                         "Die in Orange dargestellten Tags wurden mit Hilfe eines Algorithmus, speziell fuer das ausgewaehlte Item und ihren Taggingverhalten ausgesucht."))
            self.__tagging_label = QtGui.QLabel()
            self.__tagging_label.setPixmap(QtGui.QPixmap(self.trUtf8("./tsresources/images/tagging_en.png")))
            self.__descr_layout.addWidget(self.__tagging_label)   
            self.setWindowTitle(self.trUtf8("Help tag-dialog"))
            if not self.__config_wrapper.get_show_tag_help():
                self.__visible_checkbox.setChecked(False)
        elif self.__tab == "My Tags":
            self.__description_label.setText(self.trUtf8("Unter dem Reiter \"Meine Tags\" kann fuer jeden Store nachgeschaut werden, ob eine oder zwei Tag-Zeilen verwendet werden. In der erste Tag-Zeile werden beschreibende und in der zweiten kategorisierende Tags verwendet.<br>"
                                                         "Wird nur eine Zeile verwendet, kann eingestellt werden, ob in dieser nur vom Benutzter vordefinierte Tags (\"Meine Tags\") erlaubt werden oder nicht. Dies soll die Verwendung von aehnlichen Tags verhindern(z.B: Uni, Universitaet). <br>"
                                                         "Solche Tags koennen mit einem klick auf \"Hinzufuegen\" der Liste von Tags hinzugefuegt werden oder mit \"Loeschen\" von ihr geloescht werden. <br>"
                                                         "Werden zwei Zeilen verwendet, kann nur noch fuer die zweite Tag-Zeile eingestellt werden, ob diese nur \"meine Tags\" verwenden soll."))
            if not self.__config_wrapper.get_show_my_tags_help():
                self.__visible_checkbox.setChecked(False)
        elif self.__tab == "Datestamps":
            self.__description_label.setText(self.trUtf8("Unter diesem Reiter kann eingestellt werden, ob beim taggen, das Datum automatisch in die \"Tagline\" eingetragen wird."))
            if not self.__config_wrapper.get_show_datestamps_help():
                self.__visible_checkbox.setChecked(False)
        elif self.__tab == "Expiry Date":
            self.__description_label.setText(self.trUtf8("Unter diesem Reiter kann eingestellt werden, wann ein bestimmtes Objekt, mit Hilfe eines Praefixes, ablaeuft und in den Ordner \"abgelaufene_Daten\" verschoben wird.<br>"
                                                         "Diesr Praefix kann in der Zeile am unteren Ende des Tabs definiert werden."))
            if not self.__config_wrapper.get_show_expiry_date_help():
                self.__visible_checkbox.setChecked(False)
        elif self.__tab == "Re-Tagging":
            self.__description_label.setText(self.trUtf8("Unter diesem Reiter koennen bereits getaggte Objekte neu getaggt werden. Dazu wird ein Objekt ausgewaehlt und anschliessend auf \"Re-Tag\" geklickt."))
            if not self.__config_wrapper.get_show_retagging_help():
                self.__visible_checkbox.setChecked(False)
        elif self.__tab == "Rename Tags":
            self.__description_label.setText(self.trUtf8("Unter diesem Reiter koennen Tags, die zum taggen verwendet wurden, umbenannt werden, womit sich auch die Ordnerstruktur aendert.<br>"
                                                         "Dazu wird ein Tag ausgewaehlt und anschliessend auf \"Umbenennen\" geklickt."))
            if not self.__config_wrapper.get_show_rename_tags_help():
                self.__visible_checkbox.setChecked(False)
        elif self.__tab == "Store Management":
            self.__description_label.setText(self.trUtf8("Unter diesem Reiter kann ein neuer \"Store\" angelegt oder ein bereits vorhandener geloescht oder umbenannet werden. Dies geschiet durch einen Klick auf \"Neuer tagstore\", \"Loeschen ...\" oder \"Umbenennen ...\".<br>"
                                                         "Zum loeschen oder umbenennen muss der gewuenschte \"Store\" ausgewaehlt werden.<br>"
                                                         "Wenn erwuenscht, kann auch die Ordnerstruktur des ausgewaehlten \"Stores\", mit einem klick auf \"Struktur neu erstellen ...\" neu erstellt werden"))
            if not self.__config_wrapper.get_show_store_management_help():
                self.__visible_checkbox.setChecked(False)
        elif self.__tab == "Sync Settings":
            self.__description_label.setText(self.trUtf8("Unter diesem Reiter kann ein Tag zum synchronisieren mit Android definiert werden. Alle Items, welche mit diesem Tag getaggt werden, werden automatisch synchronisiert."))
            if not self.__config_wrapper.get_show_sync_settings_help():
                self.__visible_checkbox.setChecked(False)
                
        self.__description_label.setWordWrap(True)
        
        self.__bb_layout.addWidget(self.__visible_checkbox)
        self.__bb_layout.addWidget(self.__wizard_button)
        self.__bb_layout.addWidget(self.__cancel_button)
        self.__base_layout.addLayout(self.__descr_layout)
        self.__base_layout.addLayout(self.__bb_layout)
        
        self.__visible_checkbox.stateChanged.connect(self.__handle_checkbox)
 
        self.connect(self.__cancel_button, QtCore.SIGNAL('clicked()'), self.__handle_close)
        self.connect(self.__wizard_button, QtCore.SIGNAL('clicked()'), self.__handle_show_wizard)
Beispiel #34
0
class Administration(QtCore.QObject):
    def __init__(self, application, verbose):
        QtCore.QObject.__init__(self)

        self.__log = None
        self.__main_config = None
        self.__admin_dialog = None
        self.__retag_dialog = None
        self.__verbose_mode = verbose
        # the main application which has the translator installed
        self.__application = application

        self.LOG_LEVEL = logging.INFO
        if verbose:
            self.LOG_LEVEL = logging.DEBUG

        self.STORE_CONFIG_DIR = TsConstants.DEFAULT_STORE_CONFIG_DIR
        self.STORE_CONFIG_FILE_NAME = TsConstants.DEFAULT_STORE_CONFIG_FILENAME
        self.STORE_TAGS_FILE_NAME = TsConstants.DEFAULT_STORE_TAGS_FILENAME
        self.STORE_VOCABULARY_FILE_NAME = TsConstants.DEFAULT_STORE_VOCABULARY_FILENAME

        self.__system_locale = unicode(QtCore.QLocale.system().name())[0:2]
        self.__translator = QtCore.QTranslator()
        if self.__translator.load("ts_" + self.__system_locale + ".qm",
                                  "tsresources/"):
            self.__application.installTranslator(self.__translator)
        # "en" is automatically translated to the current language e.g. en -> de
        self.CURRENT_LANGUAGE = self.__get_locale_language()
        #dir names for all available languages
        self.STORE_STORAGE_DIRS = []
        self.STORE_DESCRIBING_NAV_DIRS = []
        self.STORE_CATEGORIZING_NAV_DIRS = []
        self.STORE_EXPIRED_DIRS = []
        self.STORE_NAVIGATION_DIRS = []
        self.SUPPORTED_LANGUAGES = TsConstants.DEFAULT_SUPPORTED_LANGUAGES
        self.__store_dict = {}

        # catch all "possible" dir-names
        for lang in self.SUPPORTED_LANGUAGES:
            self.change_language(lang)
            self.STORE_STORAGE_DIRS.append(
                self.trUtf8("storage"))  #self.STORE_STORAGE_DIR_EN))
            self.STORE_DESCRIBING_NAV_DIRS.append(self.trUtf8(
                "descriptions"))  #self.STORE_DESCRIBING_NAVIGATION_DIR_EN))
            self.STORE_CATEGORIZING_NAV_DIRS.append(self.trUtf8(
                "categories"))  #self.STORE_CATEGORIZING_NAVIGATION_DIR_EN))
            self.STORE_EXPIRED_DIRS.append(
                self.trUtf8("expired_items"))  #STORE_EXPIRED_DIR_EN))
            self.STORE_NAVIGATION_DIRS.append(self.trUtf8("navigation"))

        self.__log = LogHelper.get_app_logger(self.LOG_LEVEL)
        self.__init_configuration()

    def __init_configuration(self):
        """
        initializes the configuration. This method is called every time the config file changes
        """
        self.__log.info("initialize configuration")
        if self.__main_config is None:
            self.__main_config = ConfigWrapper(TsConstants.CONFIG_PATH)
            #self.connect(self.__main_config, QtCore.SIGNAL("changed()"), self.__init_configuration)

        self.CURRENT_LANGUAGE = self.__main_config.get_current_language()

        if self.CURRENT_LANGUAGE is None or self.CURRENT_LANGUAGE == "":
            self.CURRENT_LANGUAGE = self.__get_locale_language()

        # switch back to the configured language
        self.change_language(self.CURRENT_LANGUAGE)

        ## connect to all the signals the admin gui is sending
        if self.__admin_dialog is None:
            self.__admin_dialog = StorePreferencesController()
            self.connect(self.__admin_dialog,
                         QtCore.SIGNAL("create_new_store"),
                         self.__handle_new_store)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("rename_desc_tag"),
                         self.__handle_tag_rename)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("rename_cat_tag"),
                         self.__handle_tag_rename)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("retag"),
                         self.__handle_retagging)

            self.connect(self.__admin_dialog, QtCore.SIGNAL("rebuild_store"),
                         self.__handle_store_rebuild)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("rename_store"),
                         self.__handle_store_rename)
            self.connect(self.__admin_dialog, QtCore.SIGNAL("delete_store"),
                         self.__handle_store_delete)

            self.connect(self.__admin_dialog, QtCore.SIGNAL("synchronize"),
                         self.__handle_synchronization)

        self.__admin_dialog.set_main_config(self.__main_config)

        self.__prepare_store_params()
        self.__create_stores()

        ## create a temporary store list
        ## add the desc and cat tags which are needed in the admin-dialog
        tmp_store_list = []
        for current_store_item in self.__main_config.get_stores():
            store_name = current_store_item["path"].split("/").pop()
            current_store_item["desc_tags"] = self.__store_dict[
                store_name].get_tags()
            current_store_item["cat_tags"] = self.__store_dict[
                store_name].get_categorizing_tags()
            tmp_store_list.append(current_store_item)

        self.__admin_dialog.set_store_list(tmp_store_list)
        if self.__main_config.get_first_start():
            self.__admin_dialog.set_first_start(True)

    def __handle_synchronization(self, store_name):
        """
        do all the necessary synchronization stuff here ...
        """
        store_to_sync = self.__store_dict[str(store_name)]
        print "####################"
        print "synchronize " + store_name
        print "####################"

        store_to_sync.add_item_list_with_tags(["item_one", "item_two"],
                                              ["be", "tough"])

    def __handle_store_delete(self, store_name):
        self.__admin_dialog.start_progressbar(
            self.trUtf8("Deleting store ..."))
        store = self.__store_dict[str(store_name)]
        self.__store_to_be_deleted = store_name
        self.connect(store, QtCore.SIGNAL("store_delete_end"),
                     self.__handle_store_deleted)
        ## remove the directories
        store.remove()
        self.disconnect(store, QtCore.SIGNAL("store_delete_end"), self.__dummy)
        ## remove the config entry
        self.__main_config.remove_store(store.get_id())

    def __dummy(self):
        return "dummy"

    def __handle_store_deleted(self, id):
        #second remove the item in the admin_dialog
        self.__admin_dialog.remove_store_item(self.__store_to_be_deleted)

    def __handle_store_rename(self, store_name, new_store_name):
        """
        the whole store directory gets moved to the new directory
        the store will be rebuilt then to make sure all links are updated
        """
        ## show a progress bar at the admin dialog
        self.__admin_dialog.start_progressbar(self.trUtf8("Moving store ..."))
        store = self.__store_dict.pop(str(store_name))

        self.__main_config.rename_store(store.get_id(), new_store_name)
        ## connect to the rebuild signal because after the moving there is a rebuild routine started
        self.connect(store, QtCore.SIGNAL("store_rebuild_end"),
                     self.__handle_store_rebuild)

        store.move(new_store_name)

        self.disconnect(store, QtCore.SIGNAL("store_rebuild_end"))

        self.__init_configuration()

    def __handle_store_rebuild(self, store_name):
        """
        the whole store structure will be rebuild according to the records in store.tgs file
        """
        ## show a progress bar at the admin dialog
        self.__admin_dialog.start_progressbar(
            self.trUtf8("Rebuilding store ..."))
        store = self.__store_dict[str(store_name)]
        self.connect(store, QtCore.SIGNAL("store_rebuild_end"),
                     self.__handle_store_rebuild)
        store.rebuild()
        self.disconnect(store, QtCore.SIGNAL("store_rebuild_end"),
                        self.__get_locale_language)

    def __hide_progress_dialog(self, store_name):
        self.__admin_dialog.stop_progressbar()

    def __get_locale_language(self):
        """
        returns the translation of "en" in the system language
        """
        return self.trUtf8("en")

    def __handle_tag_rename(self, old_tag, new_tag, store_name):
        store = self.__store_dict[store_name]

        old_ba = old_tag.toUtf8()
        old_str = str(old_ba)

        new_ba = new_tag.toUtf8()
        new_str = str(new_ba)
        store.rename_tag(unicode(old_str, "utf-8"), unicode(new_str, "utf-8"))

    def set_application(self, application):
        """
        if the manager is called from another qt application (e.g. tagstore.py)
        you must set the calling application here for proper i18n
        """
        self.__application = application

    def __handle_retagging(self, store_name, item_name):
        """
        creates and configures a tag-dialog with all store-params and tags
        """
        store = self.__store_dict[store_name]

        ## make a string object of the QListWidgetItem, so other methods can use it
        item_name = item_name.text()
        self.__log.info("retagging item %s at store %s..." %
                        (item_name, store_name))
        #if(self.__retag_dialog is None):

        ## create the object
        self.__retag_dialog = ReTagController(self.__application,
                                              store.get_store_path(),
                                              item_name, True,
                                              self.__verbose_mode)
        ## connect to the signal(s)
        self.connect(self.__retag_dialog, QtCore.SIGNAL("retag_error"),
                     self.__handle_retag_error)
        self.connect(self.__retag_dialog, QtCore.SIGNAL("retag_cancel"),
                     self.__handle_retag_cancel)
        self.connect(self.__retag_dialog, QtCore.SIGNAL("retag_success"),
                     self.__handle_retag_success)
        self.__retag_dialog.start()

    def __kill_tag_dialog(self):
        """
        hide the dialog and set it to None
        """
        self.__retag_dialog.hide_tag_dialog()
        self.__retag_dialog = None

    def __handle_retag_error(self):
        self.__kill_tag_dialog()
        self.__admin_dialog.show_tooltip(
            self.trUtf8("An error occurred while re-tagging"))

    def __handle_retag_success(self):
        self.__kill_tag_dialog()
        self.__admin_dialog.show_tooltip(self.trUtf8("Re-tagging successful!"))

    def __handle_retag_cancel(self):
        """
        the "postpone" button in the re-tag dialog has been clicked
        """
        self.__kill_tag_dialog()

    def __set_tag_information_to_dialog(self, store):
        """
        convenience method for setting the tag data at the gui-dialog
        """
        self.__retag_dialog.set_tag_list(store.get_tags())

        num_pop_tags = self.__main_config.get_num_popular_tags()

        tag_set = set(store.get_popular_tags(
            self.__main_config.get_max_tags()))
        tag_set = tag_set | set(store.get_recent_tags(num_pop_tags))

        cat_set = set(store.get_popular_categories(num_pop_tags))
        cat_set = cat_set | set(store.get_recent_categories(num_pop_tags))

        cat_list = list(cat_set)
        if store.is_controlled_vocabulary():
            allowed_set = set(store.get_controlled_vocabulary())
            self.__retag_dialog.set_category_list(list(allowed_set))

            ## just show allowed tags - so make the intersection of popular tags ant the allowed tags
            cat_list = list(cat_set.intersection(allowed_set))
        else:
            self.__retag_dialog.set_category_list(
                store.get_categorizing_tags())

        if len(cat_list) > num_pop_tags:
            cat_list = cat_list[:num_pop_tags]
        self.__retag_dialog.set_popular_categories(cat_list)

        ## make a list out of the set, to enable indexing, as not all tags cannot be used
        tag_list = list(tag_set)
        if len(tag_list) > num_pop_tags:
            tag_list = tag_list[:num_pop_tags]
        self.__retag_dialog.set_popular_tags(tag_list)

        self.__retag_dialog.set_store_name(store.get_name())

    def __retag_item_action(self, store_name, item_name, tag_list,
                            category_list):
        """
        the "tag!" button in the re-tag dialog has been clicked
        """
        store = self.__store_dict[store_name]
        try:
            ## 1. write the data to the store-file
            store.add_item_with_tags(item_name, tag_list, category_list)
            self.__log.debug("added item %s to store-file", item_name)
        except NameInConflictException, e:
            c_type = e.get_conflict_type()
            c_name = e.get_conflicted_name()
            if c_type == EConflictType.FILE:
                self.__retag_dialog.show_message(
                    self.trUtf8(
                        "The filename - %s - is in conflict with an already existing tag. Please rename!"
                        % c_name))
            elif c_type == EConflictType.TAG:
                self.__retag_dialog.show_message(
                    self.trUtf8(
                        "The tag - %s - is in conflict with an already existing file"
                        % c_name))
            else:
                self.trUtf8(
                    "A tag or item is in conflict with an already existing tag/item"
                )
            #raise
        except InodeShortageException, e:
            self.__retag_dialog.show_message(
                self.trUtf8(
                    "The Number of free inodes is below the threshold of %s%" %
                    e.get_threshold()))
Beispiel #35
0
class ReTagController(QtCore.QObject):
    """
    object for calling the re-tag view.
    ************************
    MANDATORY parameters: 
    ************************
    * application -> the parent qt-application object ()for installing the translator properly
    * store_path -> absolute path to the store of the item to be retagged (TIP: use the PathHelper object to resolve a relative path.)
    * item_name -> the name of the item to be renamed (exactly how it is defined in the tagfile)

    ************************
    TIP: use the PathHelper object to resolve a relative path AND to extract the item name out of it. 
    ************************
    
    ************************
    OPTIONAL parameters:
    ************************
    * standalone_application -> default = False; set this to true if there
    * verbose -> set this to true for detailed output
    (DEVEL * retag_mode -> this application could even be used for a normal tagging procedure as well.)
    
    ************************
    IMPORTANT!!!
    ************************
    the start() method must be called in order to begin with the tagging procedure
    """

    def __init__(self, application, store_path, item_name, retag_mode=True, verbose=False):
        QtCore.QObject.__init__(self)

        self.__log = None
        self.__main_config = None
        self.__store_config = None
        self.__tag_dialog = None
        self.__store = None

        self.__retag_mode = retag_mode

        self.__no_store_found = False

        self.__item_name = unicode(item_name)
        self.__store_path = store_path

        # the main application which has the translator installed
        self.__application = application

        self.LOG_LEVEL = logging.INFO
        if verbose:
            self.LOG_LEVEL = logging.DEBUG

        self.STORE_CONFIG_DIR = TsConstants.DEFAULT_STORE_CONFIG_DIR
        self.STORE_CONFIG_FILE_NAME = TsConstants.DEFAULT_STORE_CONFIG_FILENAME
        self.STORE_TAGS_FILE_NAME = TsConstants.DEFAULT_STORE_TAGS_FILENAME
        self.STORE_VOCABULARY_FILE_NAME = TsConstants.DEFAULT_STORE_VOCABULARY_FILENAME

        locale = unicode(QtCore.QLocale.system().name())[0:2]
        self.__translator = QtCore.QTranslator()
        if self.__translator.load("ts_" + locale + ".qm", "tsresources/"):
            self.__application.installTranslator(self.__translator)

        # get dir names for all available languages
        self.CURRENT_LANGUAGE = self.trUtf8("en")
        self.STORE_STORAGE_DIRS = []
        self.STORE_DESCRIBING_NAV_DIRS = []
        self.STORE_CATEGORIZING_NAV_DIRS = []
        self.STORE_EXPIRED_DIRS = []
        self.STORE_NAVIGATION_DIRS = []
        self.SUPPORTED_LANGUAGES = TsConstants.DEFAULT_SUPPORTED_LANGUAGES
        self.MAX_CLOUD_TAGS = TsConstants.DEFAULT_MAX_CLOUD_TAGS
        self.__store_dict = {}

        for lang in self.SUPPORTED_LANGUAGES:
            self.change_language(lang)
            self.STORE_NAVIGATION_DIRS.append(self.trUtf8("navigation"))
            self.STORE_STORAGE_DIRS.append(self.trUtf8("storage"))  # self.STORE_STORAGE_DIR_EN))
            self.STORE_DESCRIBING_NAV_DIRS.append(
                self.trUtf8("descriptions")
            )  # self.STORE_DESCRIBING_NAVIGATION_DIR_EN))
            self.STORE_CATEGORIZING_NAV_DIRS.append(
                self.trUtf8("categories")
            )  # self.STORE_CATEGORIZING_NAVIGATION_DIR_EN))
            self.STORE_EXPIRED_DIRS.append(self.trUtf8("expired_items"))  # STORE_EXPIRED_DIR_EN))
        ## reset language
        self.change_language(self.CURRENT_LANGUAGE)

        self.__log = LogHelper.get_app_logger(self.LOG_LEVEL)

    def start(self):
        """
        call this method to actually start the tagging procedure
        """
        self.__init_configuration()

    def __init_configuration(self):
        """
        initializes the configuration. This method is called every time the config file changes
        """
        self.__log.info("initialize configuration")

        self.__main_config = ConfigWrapper(TsConstants.CONFIG_PATH)

        if self.__main_config is None:
            self.__emit_not_retagable(self.trUtf8("No config file found for the given path"))
            return

        ## check if there has been found an appropriate store_path in the config
        if self.__store_path is None:
            self.__emit_not_retagable(self.trUtf8("No store found for the given path"))
            return
        else:
            self.__store_config = ConfigWrapper(self.__store_path)

        self.__prepare_store_params()

        self.CURRENT_LANGUAGE = self.__main_config.get_current_language()
        self.change_language(self.CURRENT_LANGUAGE)
        # self.__main_config.connect(self.__main_config, QtCore.SIGNAL("changed()"), self.__init_configuration)

        self.__store = Store(
            self.__store_config.get_store_id(),
            self.__store_path,
            self.STORE_CONFIG_DIR + "/" + self.STORE_CONFIG_FILE_NAME,
            self.STORE_CONFIG_DIR + "/" + self.STORE_TAGS_FILE_NAME,
            self.STORE_CONFIG_DIR + "/" + self.STORE_VOCABULARY_FILE_NAME,
            self.STORE_NAVIGATION_DIRS,
            self.STORE_STORAGE_DIRS,
            self.STORE_DESCRIBING_NAV_DIRS,
            self.STORE_CATEGORIZING_NAV_DIRS,
            self.STORE_EXPIRED_DIRS,
            self.__main_config.get_expiry_prefix(),
        )
        self.__store.init()

        if self.__tag_dialog is None:
            self.__tag_dialog = TagDialogController(
                self.__store.get_name(),
                self.__store.get_id(),
                self.__main_config.get_max_tags(),
                self.__main_config.get_tag_seperator(),
                self.__main_config.get_expiry_prefix(),
            )
            self.__tag_dialog.get_view().setModal(True)
            # self.__tag_dialog.set_parent(self.sender().get_view())
            self.__tag_dialog.connect(self.__tag_dialog, QtCore.SIGNAL("tag_item"), self.__tag_item_action)
            self.__tag_dialog.connect(self.__tag_dialog, QtCore.SIGNAL("handle_cancel()"), self.__handle_tag_cancel)

        ## configure the tag dialog with the according settings
        format_setting = self.__store.get_datestamp_format()
        datestamp_hidden = self.__store.get_datestamp_hidden()
        ## check if auto datestamp is enabled
        if format_setting != EDateStampFormat.DISABLED:
            self.__tag_dialog.show_datestamp(True)
            ## set the format
            format = None
            if format_setting == EDateStampFormat.DAY:
                format = TsConstants.DATESTAMP_FORMAT_DAY
            elif format_setting == EDateStampFormat.MONTH:
                format = TsConstants.DATESTAMP_FORMAT_MONTH

            self.__tag_dialog.set_datestamp_format(format, datestamp_hidden)

        self.__tag_dialog.show_category_line(self.__store.get_show_category_line())
        self.__tag_dialog.set_category_mandatory(self.__store.get_category_mandatory())

        ## check if the given item really exists in the store
        if not self.__store.item_exists(self.__item_name):
            self.__emit_not_retagable(self.trUtf8("%s: There is no such item recorded in the store" % self.__item_name))
            return

        self.__set_tag_information_to_dialog(self.__store)

        if self.__retag_mode:
            self.__handle_retag_mode()

        self.__tag_dialog.show_dialog()

    def __emit_not_retagable(self, err_msg):
        self.__log.error(err_msg)
        self.emit(QtCore.SIGNAL("retag_error"))

    def __handle_retag(self, store_name, file_name_list, new_describing_tags, new_categorizing_tags):

        for file_name in file_name_list:
            ## first of all remove the old references
            self.__store.remove_file(file_name)
            ## now create the new navigation structure
            try:
                self.__store.add_item_with_tags(file_name, new_describing_tags, new_categorizing_tags)
            except InodeShortageException, e:
                self.__tag_dialog.show_message(
                    self.trUtf8("The Number of free inodes is below the threshold of %s%" % e.get_threshold())
                )
                # raise
            except Exception, e:
                self.__tag_dialog.show_message(self.trUtf8("An error occurred while tagging"))
                raise
            else:
Beispiel #36
0
    def init(self):
        """
        init is called after event listeners were added to the store instance
        """
        ## throw exception if store directory does not exist
        if not self.__file_system.path_exists(self.__path):
            ## look for renamed or removed store folder
            self.__handle_renamed_removed_store()
        if not self.__file_system.path_exists(self.__path):
            #print self.__path
            raise StoreInitError, self.trUtf8("The specified store directory does not exist! %s" % self.__path)
            return
        
        
        ## look for store/describing_nav/categorising_nav/expire directories names (all languages) if they do not exist
        if not self.__file_system.path_exists(self.__path + "/" + self.__storage_dir_name):
            for dir in self.__storage_dir_list:
                if self.__file_system.path_exists(self.__path + "/" + dir):
                    self.__storage_dir_name = unicode(dir)
        if not self.__file_system.path_exists(self.__path + "/" + self.__describing_nav_dir_name):
            for dir in self.__describing_nav_dir_list:
                if self.__file_system.path_exists(self.__path + "/" + dir):
                    self.__describing_nav_dir_name = unicode(dir)
        if not self.__file_system.path_exists(self.__path + "/" + self.__categorising_nav_dir_name):
            for dir in self.__categorising_nav_dir_list:
                if self.__file_system.path_exists(self.__path + "/" + dir):
                    self.__categorising_nav_dir_name = unicode(dir)
        if not self.__file_system.path_exists(self.__path + "/" + self.__expiry_dir_name):
            for dir in self.__expiry_dir_list:
                if self.__file_system.path_exists(self.__path + "/" + dir):
                    self.__expiry_dir_name = unicode(dir)
        if not self.__file_system.path_exists(self.__path + "/" + self.__navigation_dir_name):
            for dir in self.__expiry_dir_list:
                if self.__file_system.path_exists(self.__path + "/" + dir):
                    self.__navigation_dir_name = unicode(dir)
        
        
        
        ## built stores directories and config file if they currently not exist (new store)
        self.__file_system.create_dir(self.__path + "/" + self.__storage_dir_name)
        self.__file_system.create_dir(self.__path + "/" + self.__expiry_dir_name)
        self.__file_system.create_dir(self.__path + "/" + self.__config_file_name.split("/")[0])
        self.__file_system.create_dir(self.__path + "/" + self.__navigation_dir_name)
        ## create config/vocabulary files if they don't exist
        if not self.__file_system.path_exists(self.__path + "/" + self.__config_file_name):
            ConfigWrapper.create_store_config_file(self.__path + "/" + self.__config_file_name)
            ## now create a new config_wrapper instance
            self.__store_config_wrapper = ConfigWrapper(self.__path + "/" + self.__config_file_name)
        if not self.__file_system.path_exists(self.__path + "/" + self.__tags_file_name):
            TagWrapper.create_tags_file(self.__path + "/" + self.__tags_file_name)
            ## now create a new tag_wrapper instance
            self.__tag_wrapper = TagWrapper(self.__path + "/" + self.__tags_file_name)
        if not self.__file_system.path_exists(self.__path + "/" + self.__vocabulary_file_name):
            self.__vocabulary_wrapper = VocabularyWrapper.create_vocabulary_file(self.__path + "/" + self.__vocabulary_file_name)


        ## 0 ... show just the describing tagline -> create the NAVIGATION dir 
        ## 3 ... show just the categorizing tagline - only restricted vocabulary is allowed -> create the CATEGORIES dir
        ## ELSE: two taglines with dirs: CATEGORIES/DESCRIPTIONS
        self.__tagline_config = self.__store_config_wrapper.get_show_category_line()
        
        # clear the old list (if there is already one)
        self.__paths_to_maintain = []
        
        if self.__tagline_config == 0:
            #self.__file_system.create_dir(self.__path + "/" + self.__navigation_dir_name)
            self.__paths_to_maintain.append(self.__path + "/" + self.__navigation_dir_name)
        elif self.__tagline_config == 3:
            #self.__file_system.create_dir(self.__path + "/" + self.__categorising_nav_dir_name)
            self.__paths_to_maintain.append(self.__path + "/" + self.__categorising_nav_dir_name)
        else:
            #self.__file_system.create_dir(self.__path + "/" + self.__categorising_nav_dir_name)
            self.__paths_to_maintain.append(self.__path + "/" + self.__categorising_nav_dir_name)
            #self.__file_system.create_dir(self.__path + "/" + self.__describing_nav_dir_name)
            self.__paths_to_maintain.append(self.__path + "/" + self.__describing_nav_dir_name)

        for path in self.__paths_to_maintain:
            self.__file_system.create_dir(path)

        self.__init_store()
Beispiel #37
0
    def __init_configurations(self):
        """
        initializes the configuration. This method is called every time the config file changes
        """
        self.__log.info("initialize configuration")
        ## reload config file - overwrite default settings
        self.__app_config_wrapper = ConfigWrapper(TsConstants.CONFIG_PATH)
        self.__app_config_wrapper.connect(self.__app_config_wrapper, QtCore.SIGNAL("changed()"), self.__init_configurations)
        self.__app_config_wrapper.print_app_config_to_log()
        tag_seperator = self.__app_config_wrapper.get_tag_seperator()
        if tag_seperator.strip() != "":
            self.TAG_SEPERATOR = tag_seperator
        expiry_prefix = self.__app_config_wrapper.get_expiry_prefix()
        if expiry_prefix.strip() != "":
            self.EXPIRY_PREFIX = expiry_prefix
        
        self.NUM_RECENT_TAGS = self.__app_config_wrapper.get_num_popular_tags()
        self.NUM_POPULAR_TAGS = self.__app_config_wrapper.get_num_popular_tags()
        self.MAX_TAGS = self.__app_config_wrapper.get_max_tags()
        
        self.CURRENT_LANGUAGE = self.__app_config_wrapper.get_current_language();
        if self.CURRENT_LANGUAGE is None or self.CURRENT_LANGUAGE == "":
            self.CURRENT_LANGUAGE = self.trUtf8("en")
        self.change_language(self.CURRENT_LANGUAGE)
        
        config_dir = self.__app_config_wrapper.get_store_config_directory()
        if config_dir != "":
            self.STORE_CONFIG_DIR = config_dir
        config_file_name = self.__app_config_wrapper.get_store_configfile_name()
        if config_file_name != "":
            self.STORE_CONFIG_FILE_NAME = config_file_name
        tags_file_name = self.__app_config_wrapper.get_store_tagsfile_name()
        if tags_file_name != "":
            self.STORE_TAGS_FILE_NAME = tags_file_name
        vocabulary_file_name = self.__app_config_wrapper.get_store_vocabularyfile_name()
        if vocabulary_file_name != "":
            self.STORE_VOCABULARY_FILE_NAME = vocabulary_file_name
        
       
#        self.SUPPORTED_LANGUAGES = self.__app_config_wrapper.get_supported_languages()
#        current_language = self.CURRENT_LANGUAGE
#        self.STORE_STORAGE_DIRS = []
#        self.STORE_NAVIGATION_DIRS = [] 
#        for lang in self.SUPPORTED_LANGUAGES:
#            self.change_language(lang)
#            self.STORE_STORAGE_DIRS.append(self.trUtf8("storage")) 
#            self.STORE_NAVIGATION_DIRS.append(self.trUtf8("navigation")) 
#        ## reset language
#        self.change_language(current_language)

        ## get stores from config file         
        config_store_items = self.__app_config_wrapper.get_stores()
        config_store_ids = self.__app_config_wrapper.get_store_ids()

        deleted_stores = []
        for store in self.STORES:
            id = store.get_id()
            if id in config_store_ids:
            ## update changed stores
                store.set_path(self.__app_config_wrapper.get_store_path(id), 
                               self.STORE_CONFIG_DIR + "/" + self.STORE_CONFIG_FILE_NAME,
                               self.STORE_CONFIG_DIR + "/" + self.STORE_TAGS_FILE_NAME,
                               self.STORE_CONFIG_DIR + "/" + self.STORE_VOCABULARY_FILE_NAME)
                store.change_expiry_prefix(self.EXPIRY_PREFIX)               
                config_store_ids.remove(id)             ## remove already updated items
            else:
            ## remove deleted stores
                deleted_stores.append(store)

        ## update deleted stores from global list after iterating through it
        for store in deleted_stores:
            self.STORES.remove(store)
            self.__log.debug("removed store: %s", store.get_name())
        
        ## add new stores
        for store_item in config_store_items:
            if store_item["id"] in config_store_ids:    ## new
                store = Store(store_item["id"], store_item["path"], 
                              self.STORE_CONFIG_DIR + "/" + self.STORE_CONFIG_FILE_NAME,
                              self.STORE_CONFIG_DIR + "/" + self.STORE_TAGS_FILE_NAME,
                              self.STORE_CONFIG_DIR + "/" + self.STORE_VOCABULARY_FILE_NAME,
                              self.STORE_NAVIGATION_DIRS,
                              self.STORE_STORAGE_DIRS, 
                              self.STORE_DESCRIBING_NAV_DIRS,
                              self.STORE_CATEGORIZING_NAV_DIRS,
                              self.STORE_EXPIRED_DIRS,
							  self.EXPIRY_PREFIX)

                store.connect(store, QtCore.SIGNAL("removed(PyQt_PyObject)"), self.store_removed)
                store.connect(store, QtCore.SIGNAL("renamed(PyQt_PyObject, QString)"), self.store_renamed)
                store.connect(store, QtCore.SIGNAL("file_renamed(PyQt_PyObject, QString, QString)"), self.file_renamed)
                store.connect(store, QtCore.SIGNAL("file_removed(PyQt_PyObject, QString)"), self.file_removed)
                store.connect(store, QtCore.SIGNAL("pending_operations_changed(PyQt_PyObject)"), self.pending_file_operations)
                store.connect(store, QtCore.SIGNAL("vocabulary_changed"), self.__handle_vocabulary_changed)
                store.connect(store, QtCore.SIGNAL("store_config_changed"), self.__handle_store_config_changed)

                extensions = self.__app_config_wrapper.get_additional_ignored_extension()
                ##if there comes a value from the config -> set it
                if len(extensions) > 0 and extensions[0] != "":
                    store.add_ignored_extensions(extensions)

                self.STORES.append(store)

                self.__log.debug("init store: %s", store.get_name())
                
                ## create a dialogcontroller for each store ...
                tmp_dialog = TagDialogController(store.get_name(), store.get_id(), self.MAX_TAGS, self.TAG_SEPERATOR, self.EXPIRY_PREFIX)
                
                tmp_dialog.connect(tmp_dialog, QtCore.SIGNAL("tag_item"), self.tag_item_action)
                tmp_dialog.connect(tmp_dialog, QtCore.SIGNAL("handle_cancel()"), self.handle_cancel)
                tmp_dialog.connect(tmp_dialog, QtCore.SIGNAL("open_store_admin_dialog()"), self.show_admin_dialog)
                
                self.DIALOGS[store.get_id()] = tmp_dialog
                ## call init to initialize new store instance (after adding the event handler)
                ## necessary if store was renamed during tagstore was not running (to write config)
                store.init()
                self.connect(tmp_dialog, QtCore.SIGNAL("item_selected"), self.__set_tag_information_to_dialog_wrapper)
                self.__configure_tag_dialog(store, tmp_dialog)
Beispiel #38
0
    def __init_configuration(self):
        """
        initializes the configuration
        """
        
        # informal debug
        self.__log.info("__init_configuration")
        
        # construct config wrapper
        self.__main_config = ConfigWrapper(TsConstants.CONFIG_PATH)
        if self.__main_config is None:
            self.__emit_not_syncable(self.trUtf8("No config file found for the given path"))
            return


        search_source_path = False
        found_source_path = False
        if self.__source_store_path != None and self.__source_store_path != "":
            search_source_path = True

        search_target_path = False
        found_target_path = False
        if self.__target_store_path != None and self.__target_store_path != "":
            search_target_path = True

        ## create a temporary store list
        ## add the desc and cat tags which are needed in the admin-dialog
        tmp_store_list = []
        store_list = self.__main_config.get_stores()
        
        # add android store
        # when registered
        android_source_path = self.__main_config.get_android_store_path()
        if android_source_path != None and android_source_path != "":
            store_item = {}
            store_item["path"] = android_source_path
            store_item["name"] = "Android"
            store_list.append(store_item)
        
        # enumerate all stores and add their names and paths
        store_name = None
        for current_store_item in store_list:
            
            if current_store_item.has_key("name"):
                store_name = current_store_item["name"]
            else:
                store_name = current_store_item["path"].split("/").pop()
                
            store_path = current_store_item["path"]
            current_store_item["name"] = store_name
            current_store_item["path"] = store_path
            
            tmp_store_list.append(current_store_item)
            # find source target list
            if search_source_path:
                if store_path == self.__source_store_path:
                    found_source_path = True
                    
            if search_target_path:
                if store_path == self.__target_store_path:
                    found_target_path = True
        
        if search_source_path and found_source_path == False:
            # source store is not registered
            self.__emit_not_syncable(self.trUtf8("Source tagstore not registered in main config"))
            return
        
        if search_target_path and found_target_path == False:
            # source store is not registered
            self.__emit_not_syncable(self.trUtf8("Target tagstore not registered in main config"))
            return
        
        if self.__sync_dialog is None:
            self.__sync_dialog = SyncDialogController(tmp_store_list, self.__source_store_path, self.__target_store_path, self.__auto_sync)
            self.__sync_dialog.get_view().setModal(True)
            #self.__tag_dialog.set_parent(self.sender().get_view())
            self.__sync_dialog.connect(self.__sync_dialog, QtCore.SIGNAL("sync_store"), self.__sync_store_action)
            self.__sync_dialog.connect(self.__sync_dialog, QtCore.SIGNAL("sync_conflict"), self.__handle_resolve_conflict)
            self.__sync_dialog.connect(self.__sync_dialog, QtCore.SIGNAL("handle_cancel()"), self.__handle_sync_cancel)

        self.__sync_dialog.show_dialog()
        if self.__auto_sync:
            self.__sync_dialog.start_auto_sync()