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 __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 __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 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))
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))
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))
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))
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()
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)
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 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)
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)
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 __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 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)
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)
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()
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)
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 test_configreader(self): config = ConfigWrapper("../tsresources/conf/tagstore.cfg") extensions = config.get_ignored_extension() assert (extensions == TsRestrictions.IGNORED_EXTENSIONS)
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)
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()
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()))
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:
def test_configreader(self): config = ConfigWrapper("../tsresources/conf/tagstore.cfg") extensions = config.get_ignored_extension() assert(extensions == TsRestrictions.IGNORED_EXTENSIONS)
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
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 __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()
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
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)
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)
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()))
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:
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_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 __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()