def show_insert_img_dialog(self, filter): """Show a file dialog filtered on the supported filetypes, get a filename, massage it, and return it to the widget to be inserted. There is more media file logic inside the database code too, as the user could also just type in the html tags as opposed to passing through the file selector here. The reason we don't do all the operations in the database code, is that we want to display a nice short relative path back in the edit field. """ from mnemosyne.libmnemosyne.utils import copy_file_to_dir data_dir, media_dir = \ self.config().data_dir, self.database().media_dir() path = expand_path(self.config()["import_img_dir"], data_dir) filter = _("Image files") + " " + filter filename = self.main_widget().get_filename_to_open(\ path, filter, _("Insert image")) if not filename: return "" else: self.config()["import_img_dir"] = contract_path(\ os.path.dirname(filename), data_dir) filename = copy_file_to_dir(filename, media_dir) return contract_path(filename, media_dir)
def new(self, path): if self.is_loaded(): self.unload() path = expand_path(path, self.config().basedir) self.load_failed = False self.save(contract_path(path, self.config().basedir)) self.config()["path"] = contract_path(path, self.config().basedir) self.log().new_database()
def insert_sound(self): path = expand_path(config()["import_sound_dir"]) fname = unicode(QFileDialog.getOpenFileName(self, _("Insert sound"), path, _("Sound files") + \ " (*.wav *.mp3 *.ogg *.WAV *.MP3 *.OGG)")) if fname: self.insertPlainText("<sound src=\""+contract_path(fname)+"\">") config()["import_sound_dir"] = \ contract_path(os.path.dirname(fname))
def insert_img(self): path = expand_path(config()["import_img_dir"]) fname = unicode(QFileDialog.getOpenFileName(self, _("Insert image"), path, _("Image files") + \ " (*.png *.gif *.jpg *.bmp *.jpeg" + \ " *.PNG *.GIF *.jpg *.BMP *.JPEG)")) if fname: self.insertPlainText("<img src=\""+contract_path(fname)+"\">") config()["import_img_dir"] = \ contract_path(os.path.dirname(fname))
def load(self, path): path = expand_path(path, config().basedir) if self.is_loaded(): unload_database() if not os.path.exists(path): self.load_failed = True raise LoadError try: infile = file(path, 'rb') db = cPickle.load(infile) self.start_date = db[0] self.categories = db[1] self.facts = db[2] self.fact_views = db[3] self.cards = db[4] infile.close() self.load_failed = False except: self.load_failed = True raise InvalidFormatError(stack_trace=True) # Work around a sip bug: don't store card types, but their ids. for f in self.facts: f.card_type = card_type_by_id(f.card_type) # TODO: This was to remove database inconsistencies. Still needed? #for c in self.categories: # self.remove_category_if_unused(c) config()["path"] = contract_path(path, config().basedir) log().loaded_database() for f in component_manager.get_all("function_hook", "after_load"): f.run()
def unused_media_files(self): """Returns a set of media files which are in the media directory but which are not referenced in the cards. """ case_insensitive = is_filesystem_case_insensitive() # Files referenced in the database. files_in_db = set() for result in self.con.execute(\ "select value from data_for_fact where value like '%src=%'"): for match in re_src.finditer(result[0]): filename = match.group(2) if case_insensitive: filename = filename.lower() files_in_db.add(filename) # Files in the media dir. files_in_media_dir = set() for root, dirnames, filenames in os.walk(self.media_dir()): root = contract_path(root, self.media_dir()) if root.startswith("_"): continue for filename in filenames: # Paths are stored using unix convention. if root: filename = root + "/" + filename if case_insensitive: filename = filename.lower() files_in_media_dir.add(filename) return files_in_media_dir - files_in_db
def new(self, path): if self.is_loaded(): self.unload() self._path = expand_path(path, self.config().basedir) if os.path.exists(self._path): os.remove(self._path) self.load_failed = False # Create tables. self.con.executescript(SCHEMA) self.con.execute("insert into global_variables(key, value) values(?,?)", ("version", self.version)) self.con.execute("""insert into partnerships(partner, _last_log_id) values(?,?)""", ("log.txt", 0)) self.con.commit() self.config()["path"] = contract_path(self._path, self.config().basedir) # Create default criterion. from mnemosyne.libmnemosyne.activity_criteria.default_criterion import \ DefaultCriterion self._current_criterion = DefaultCriterion(self.component_manager) self.add_activity_criterion(self._current_criterion) # Create media directory. mediadir = self.mediadir() if not os.path.exists(mediadir): os.mkdir(mediadir) os.mkdir(os.path.join(mediadir, "latex"))
def unused_media_files(self): """Returns a set of media files which are in the media directory but which are not referenced in the cards. """ case_insensitive = is_filesystem_case_insensitive() # Files referenced in the database. files_in_db = set() for result in self.con.execute("select value from data_for_fact where value like '%src=%'"): for match in re_src.finditer(result[0]): filename = match.group(2) if case_insensitive: filename = filename.lower() files_in_db.add(filename) # Files in the media dir. files_in_media_dir = set() for root, dirnames, filenames in os.walk(self.media_dir()): root = contract_path(root, self.media_dir()) if root.startswith("_"): continue for filename in filenames: # Paths are stored using unix convention. if root: filename = root + "/" + filename if case_insensitive: filename = filename.lower() files_in_media_dir.add(filename) return files_in_media_dir - files_in_db
def accept(self): filename = self.filename_box.text().replace("\\", "/") if not filename: return QtWidgets.QDialog.accept(self) if os.path.isabs(filename): if not filename.startswith(\ self.database().media_dir().replace("\\", "/")): self.main_widget().show_error(\ _("Please select a filename inside the media directory.")) self.set_default_filename() return else: filename = contract_path(filename, self.database().media_dir()) # By now, filename is relative to the media dir. # Save subdirectory for this card type. local_dir = os.path.dirname(filename) if local_dir: self.config()["tts_dir_for_card_type_id"]\ [self.card_type.id] = local_dir full_local_dir = expand_path(local_dir, self.database().media_dir()) if not os.path.exists(full_local_dir): os.makedirs(full_local_dir) shutil.copyfile(self.tmp_filename, os.path.join(self.database().media_dir(), filename)) self.text_to_insert = "<audio src=\"" + filename + "\">" QtWidgets.QDialog.accept(self)
def initialise(self, data_dir=None, config_dir=None, filename=None, automatic_upgrades=True, debug_file=None, server_only=False): """The automatic upgrades of the database can be turned off by setting 'automatic_upgrade' to False. This is mainly useful for the testsuite. """ if debug_file: self.component_manager.debug_file = open(debug_file, "w", 0) self.register_components() # Upgrade from 1.x if needed. if automatic_upgrades: from mnemosyne.libmnemosyne.upgrades.upgrade1 import Upgrade1 Upgrade1(self.component_manager).backup_old_dir() if data_dir: self.config().data_dir = data_dir self.config().config_dir = data_dir if config_dir: self.config().config_dir = config_dir # Upgrade config if needed. if automatic_upgrades: from mnemosyne.libmnemosyne.upgrades.upgrade3 import Upgrade3 Upgrade3(self.component_manager).run() self.activate_components() register_component_manager(self.component_manager, self.config()["user_id"]) self.execute_user_plugin_dir() self.activate_saved_plugins() # If we are only running a sync or a review server, do not yet load # the database to prevent threading access issues. if server_only: if filename: self.config()["last_database"] = \ contract_path(filename, self.config().data_dir) return # Loading the database should come after all user plugins have been # loaded, since these could be needed e.g. for a card type in the # database. if filename and not filename.endswith(".db"): from mnemosyne.libmnemosyne.translator import _ self.main_widget().show_error(\ _("Command line argument is not a *.db file.")) sys.exit() self.load_database(filename) # Only now that the database is loaded, we can start writing log # events to it. This is why we log started_scheduler and # loaded_database manually. try: self.log().started_program() except Exception, e: if "lock" in str(e): from mnemosyne.libmnemosyne.translator import _ self.main_widget().show_error(\ _("Another copy of Mnemosyne is still running.") + "\n" + \ _("Continuing is impossible and will lead to data loss!")) sys.exit() else: raise e
def save(self, path=None): # Don't erase a database which failed to load. if self.load_failed == True: return -1 if not path: path = self.config()["path"] path = expand_path(path, self.config().basedir) # Update version. self.global_variables["version"] = self.version # Work around a sip bug: don't store card types, but their ids. for f in self.facts: f.card_type = f.card_type.id try: # Write to a backup file first, as shutting down Windows can # interrupt the dump command and corrupt the database. outfile = file(path + "~", 'wb') db = [self.tags, self.facts, self.cards, self.global_variables] cPickle.dump(db, outfile) outfile.close() shutil.move(path + "~", path) # Should be atomic. except: raise RuntimeError, _("Unable to save file.") \ + "\n" + traceback_string() self.config()["path"] = contract_path(path, self.config().basedir) # Work around sip bug again. for f in self.facts: f.card_type = self.card_type_by_id(f.card_type)
def new(self, path): if self.is_loaded(): self.unload() self.load_failed = False self.start_date = StartDate() config()["path"] = path log().new_database() self.save(contract_path(path, config().basedir))
def default_filename(self, card_type, foreign_text): if foreign_text.count(" ") <= 1: filename = foreign_text.replace("?", "").replace("/", "")\ .replace("\\", "") + ".mp3" else: filename = datetime.datetime.today().strftime("%Y%m%d.mp3") local_dir = self.config()["tts_dir_for_card_type_id"]\ .get(card_type.id, "") filename = os.path.join(local_dir, filename) full_path = expand_path(filename, self.database().media_dir()) full_path = make_filename_unique(full_path) filename = contract_path(full_path, self.database().media_dir()) return filename
def insert_video(self, filter): from mnemosyne.libmnemosyne.utils import copy_file_to_dir basedir, mediadir = self.config().basedir, self.database().mediadir() path = expand_path(self.config()["import_video_dir"], basedir) filter = _("Video files") + " " + filter filename = self.main_widget().open_file_dialog(path, filter, _("Insert video")) if not filename: return "" else: self.config()["import_video_dir"] = contract_path(os.path.dirname(filename), basedir) filename = copy_file_to_dir(filename, mediadir) return filename
def insert_img(self, filter): """Show a file dialog filtered on the supported filetypes, get a filename, massage it, and return it to the widget to be inserted. There is more media file logic inside the database code too, as the user could also just type in the html tags as opposed to passing through the fileselector here. """ from mnemosyne.libmnemosyne.utils import copy_file_to_dir basedir, mediadir = self.config().basedir, self.database().mediadir() path = expand_path(self.config()["import_img_dir"], basedir) filter = _("Image files") + " " + filter filename = self.main_widget().open_file_dialog(path, filter, _("Insert image")) if not filename: return "" else: self.config()["import_img_dir"] = contract_path(os.path.dirname(filename), basedir) filename = copy_file_to_dir(filename, mediadir) return contract_path(filename, mediadir)
def show_insert_flash_dialog(self, filter): from mnemosyne.libmnemosyne.utils import copy_file_to_dir data_dir, media_dir = self.config().data_dir, self.database().media_dir() path = expand_path(self.config()["import_flash_dir"], data_dir) filter = _("Flash files") + " " + filter filename = self.main_widget().get_filename_to_open(\ path, filter, _("Insert Flash")) if not filename: return "" else: self.config()["import_flash_dir"] = contract_path(\ os.path.dirname(filename), data_dir) filename = copy_file_to_dir(filename, media_dir) return filename
def save(self, path=None): # Don't erase a database which failed to load. if self.load_failed == True: return -1 # Update format. self.con.execute("update global_variables set value=? where key=?", (self.version, "version" )) # Save database and copy it to different location if needed. self.con.commit() if not path: return dest_path = expand_path(path, self.config().basedir) if dest_path != self._path: shutil.copy(self._path, dest_path) self._path = dest_path self.config()["path"] = contract_path(path, self.config().basedir)
def load(self, path): if self.is_loaded(): self.unload() self._path = expand_path(path, self.config().basedir) # Check database version. try: sql_res = self.con.execute("""select value from global_variables where key=?""", ("version", )).fetchone() self.load_failed = False except sqlite3.OperationalError: self.main_widget().error_box( _("Another copy of Mnemosyne is still running.") + "\n" + _("Continuing is impossible and will lead to data loss!")) sys.exit() except: self.load_failed = True raise RuntimeError, _("Unable to load file.") if sql_res["value"] != self.version: self.load_failed = True raise RuntimeError, \ _("Unable to load file: database version mismatch.") # Instantiate card types stored in this database. for cursor in self.con.execute("select id from card_types"): id = cursor[0] card_type = self.get_card_type(id, id_is_internal=-1) self.component_manager.register(card_type) # Identify missing plugins for card types and their parents. plugin_needed = set() active_ids = set(card_type.id for card_type in self.card_types()) for cursor in self.con.execute("""select distinct card_type_id from facts"""): id = cursor[0] while "::" in id: # Move up one level of the hierarchy. id, child_name = id.rsplit("::", 1) if id not in active_ids: plugin_needed.add(id) if id not in active_ids: plugin_needed.add(id) for card_type_id in plugin_needed: self._find_plugin_for_card_type(card_type_id) self.config()["path"] = contract_path(path, self.config().basedir) for f in self.component_manager.get_all("hook", "after_load"): f.run()
def new(self, path): if self.is_loaded(): self.unload() self._path = expand_path(path, self.config().basedir) if os.path.exists(self._path): os.remove(self._path) self.load_failed = False # Create tables. self.con.executescript(SCHEMA) self.con.execute("insert into global_variables(key, value) values(?,?)", ("version", self.version)) self.con.execute("""insert into partnerships(partner, _last_log_id) values(?,?)""", ("log.txt", 0)) self.con.commit() self.config()["path"] = contract_path(self._path, self.config().basedir) # Create media directory. mediadir = self.config().mediadir() if not os.path.exists(mediadir): os.mkdir(mediadir)
def add_media_file(self, filename): """ Adds a new media file to the media directory. """ from mnemosyne.libmnemosyne.utils import copy_file_to_dir, contract_path media_dir = self.mnemo.database().media_dir() # Make sure the path is proper absolute filesystem path filename_expanded = os.path.expanduser(filename) if os.path.isabs(filename_expanded): filename_abs = filename_expanded else: filename_abs = os.path.join( os.path.dirname(utils.get_absolute_filepath()), filename_expanded) copy_file_to_dir(filename_abs, media_dir) return contract_path(filename_abs, media_dir)
def delete_unused_media_files(self, unused_files): """Delete media files which are no longer in use. 'unused_files' should be a subset of 'self.unused_media_files', because here we no longer check if these media files are used or not. """ for filename in unused_files: os.remove(expand_path(filename, self.media_dir())) self.log().deleted_media_file(filename) # Purge empty dirs. for root, dirnames, filenames in os.walk(self.media_dir(), topdown=False): contracted_root = contract_path(root, self.media_dir()) if not contracted_root or contracted_root.startswith("_"): continue if len(filenames) == 0 and len(dirnames) == 0: os.rmdir(root) # Other media files, e.g. latex. for f in self.component_manager.all("hook", "delete_unused_media_files"): f.run() remove_empty_dirs_in(self.media_dir())
def delete_unused_media_files(self, unused_files): """Delete media files which are no longer in use. 'unused_files' should be a subset of 'self.unused_media_files', because here we no longer check if these media files are used or not. """ for filename in unused_files: os.remove(expand_path(filename, self.media_dir())) self.log().deleted_media_file(filename) # Purge empty dirs. for root, dirnames, filenames in \ os.walk(self.media_dir(), topdown=False): contracted_root = contract_path(root, self.media_dir()) if not contracted_root or contracted_root.startswith("_"): continue if len(filenames) == 0 and len(dirnames) == 0: os.rmdir(root) # Other media files, e.g. latex. for f in self.component_manager.all("hook", "delete_unused_media_files"): f.run() remove_empty_dirs_in(self.media_dir())
def save(self, path): path = expand_path(path, config().basedir) # Work around a sip bug: don't store card types, but their ids. for f in self.facts: f.card_type = f.card_type.id # Don't erase a database which failed to load. if self.load_failed == True: return try: # Write to a backup file first, as shutting down Windows can # interrupt the dump command and corrupt the database. outfile = file(path + "~", 'wb') db = [self.start_date, self.categories, self.facts, self.fact_views, self.cards] cPickle.dump(db, outfile) outfile.close() shutil.move(path + "~", path) # Should be atomic. except: print traceback_string() raise SaveError() config()["path"] = contract_path(path, config().basedir) # Work around sip bug again. for f in self.facts: f.card_type = card_type_by_id(f.card_type)
def load(self, path): if self.is_loaded(): self.unload() path = expand_path(path, self.config().basedir) if not os.path.exists(path): self.load_failed = True raise RuntimeError, _("File does not exist.") try: infile = file(path, 'rb') db = cPickle.load(infile) self.tags = db[0] self.facts = db[1] self.cards = db[2] self.global_variables = db[3] infile.close() self.load_failed = False except: self.load_failed = True raise RuntimeError, _("Invalid file format.") \ + "\n" + traceback_string() # Check database version. if self.global_variables["version"] != self.version: self.load_failed = True raise RuntimeError, _("Unable to load file: database version mismatch.") # Deal with clones and plugins, also plugins for parent classes. # Because of the sip bugs, card types here are actually still card # type ids. plugin_needed = set() clone_needed = [] active_id = set(card_type.id for card_type in self.card_types()) for id in set(card.fact.card_type for card in self.cards): while "." in id: # Move up one level of the hierarchy. id, child_name = id.rsplit(".", 1) if id.endswith("_CLONED"): id = id.replace("_CLONED", "") clone_needed.append((id, child_name)) if id not in active_id: plugin_needed.add(id) if id not in active_id: plugin_needed.add(id) # Activate necessary plugins. for card_type_id in plugin_needed: found = False for plugin in self.plugins(): for component in plugin.components: if component.component_type == "card_type" and \ component.id == card_type_id: found = True try: plugin.activate() except: self.load_failed = True raise RuntimeError, \ _("Error when running plugin:") \ + "\n" + traceback_string() if not found: self.load_failed = True raise RuntimeError, \ _("Missing plugin for card type with id:") \ + " " + card_type_id # Create necessary clones. for parent_type_id, clone_name in clone_needed: parent_instance = self.card_type_by_id(parent_type_id) try: parent_instance.clone(clone_name) except NameError: # In this case the clone was already created by loading the # database earlier. pass # Work around a sip bug: don't store card types, but their ids. for f in self.facts: f.card_type = self.card_type_by_id(f.card_type) self.config()["path"] = contract_path(path, self.config().basedir) for f in self.component_manager.get_all("hook", "after_load"): f.run()