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 contract_path(p, prefix=None): """Make absolute path relative and normalise slashes.""" # By default, make paths relative to the database location. if prefix == None: prefix = os.path.dirname(config()["path"]) # If there was no dirname in the last statement, it was a relative # path and we set the prefix to the basedir. if prefix == '': prefix = config().basedir # Normalise paths and convert everything to lowercase on Windows. p = os.path.normpath(p) prefix = os.path.normpath(prefix) if ( (len(p) > 2) and p[1] == ":"): p = p.lower() prefix = prefix.lower() # Do the actual detection. if ( ( (len(p) > 1) and p[0] == "/") \ or ( (len(p) > 2) and p[1] == ":") ): # Unix or Windows absolute path. try: return p.split(prefix)[1][1:] except: return p else: return p
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 closeEvent(self, event): try: config().save() database().backup() database().unload() except MnemosyneError, e: self.error_box(e) event.ignore()
def update_dialog(self): # Update question and answer font. if config()["QA_font"] != None: font = QFont() font.fromString(config()["QA_font"]) else: font = self.show_button.font()
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 initialise(basedir): """Note: running user plugins is best done after the GUI has been created, in order to be able to provide feedback about errors to the user.""" initialise_system_components() config().initialise(basedir) initialise_logging() initialise_lockfile() initialise_new_empty_database() initialise_error_handling()
def rebuild_queue(self, learn_ahead=False): self.queue = [] datab = database() if not datab.is_loaded(): return queue = CardQueue(limit=20) if learn_ahead: queue.mkgroup("learn ahead", limit=5) queue.add(datab.cards_learn_ahead(sort_key="next_rep")) else: queue.mkgroup("scheduled", limit=10, allow_dup=None) # limit amount of grade0 cards for the current group queue.setlimit(limit=config()["grade_0_items_at_once"], group="scheduled", grade=0) # add due_for_ret_rep generator queue.add(datab.cards_due_for_ret_rep(sort_key="interval")) # add final review generator, grade0 queue.add(datab.cards_due_for_final_review(grade=0)) # add final review generator, grade1 queue.add(datab.cards_due_for_final_review(grade=1)) # add new_memorizing generator, grade0 new_mem = datab.cards_new_memorising(grade=0) queue.add(new_mem) # FIXME: HOW? # queue.add(iter(2*grade_0_selected)) # queue.add(new_mem) # queue.add(new_mem) # The same for grade1 new_mem = datab.cards_new_memorising(grade=1) queue.add(new_mem) # add cards_unseen generator queue.add(datab.cards_unseen(randomise=config()["randomise_new_cards"])) # FIXME: HOW??? # grade_0_in_queue = sum(1 for i in self.queue if i.grade == 0)/2 self.queue = [card for card in queue()]
def ui_factory(interface=None): """ Create UI(View in terms of MVC) """ if interface == 'cmd': from pomni.cmd_ui import CmdUiControllerReview, CommandlineUI component_manager.register("ui_controller_review", CmdUiControllerReview()) component_manager.register("renderer", TextRenderer()) return CommandlineUI() if not interface or interface == "hildon": try: theme = config()["theme_path"].split("/")[-1] except KeyError: theme = "eternal" from pomni import hildon_ui from hildon_ui import HildonUI review_class = getattr(hildon_ui, theme.capitalize() + 'ControllerReview') input_class = getattr(hildon_ui, theme.capitalize() + 'ControllerInput') main_class = getattr(hildon_ui, theme.capitalize() + 'ControllerMain') component_manager.register("ui_controller_main", main_class()) component_manager.register("ui_controller_review", review_class()) component_manager.register("ui_controller_input", input_class()) component_manager.register("renderer", TextRenderer()) return HildonUI() # add next gui here raise ValueError("No idea how to create %s UI" % interface)
def run(self): basedir = config().basedir join = os.path.join # Find out which files haven't been uploaded yet. dir = os.listdir(unicode(join(basedir, "history"))) history_files = [x for x in dir if x[-4:] == ".bz2"] uploaded = None try: upload_log = file(join(basedir, "history", "uploaded")) uploaded = [x.strip() for x in upload_log] upload_log.close() except: uploaded = [] to_upload = sets.Set(history_files) - sets.Set(uploaded) if len(to_upload) == 0: return # Upload them to our server. upload_log = file(join(basedir, "history", "uploaded"), 'a') try: for f in to_upload: print "Uploading", f, "...", filename = join(basedir, "history", f) self.upload(filename) print >> upload_log, f log().uploaded(filename) print "done!" except: log().uploading_failed() traceback.print_exc() upload_log.close()
def expand_path(p, prefix=None): """Make relative path absolute and normalise slashes.""" # By default, make paths relative to the database location. if prefix == None: prefix = os.path.dirname(config()["path"]) # If there was no dirname in the last statement, it was a relative # path and we set the prefix to the basedir. if prefix == '': prefix = config().basedir if ( ( (len(p) > 1) and p[0] == "/") \ or ( (len(p) > 2) and p[1] == ":") ): # Unix or Windows absolute path. return os.path.normpath(p) else: return os.path.normpath(os.path.join(prefix, p))
def __init__(self): """ Initialization items of review window """ HildonBaseUi.__init__(self, signals=[]) self.title = _("Mnemosyne") + " - " + \ splitext(basename(config()["path"]))[0]
def __init__(self, parent=None): QDialog.__init__(self, parent) # TODO: modal, Qt.WStyle_MinMax | Qt.WStyle_SysMenu))? self.setupUi(self) # We calculate card_type_by_name here rather than in the component # manager, because these names can change if the user chooses another # translation. TODO: test. self.card_type_by_name = {} for card_type in card_types(): self.card_types.addItem(card_type.name) self.card_type_by_name[card_type.name] = card_type # TODO: sort card types by id. # TODO: remember last type. self.card_widget = None self.update_card_widget() self.update_combobox(config()["last_add_category"]) self.grades = QButtonGroup() self.grades.addButton(self.grade_0_button, 0) self.grades.addButton(self.grade_1_button, 1) self.grades.addButton(self.grade_2_button, 2) self.grades.addButton(self.grade_3_button, 3) self.grades.addButton(self.grade_4_button, 4) self.grades.addButton(self.grade_5_button, 5) self.connect(self.grades, SIGNAL("buttonClicked(int)"), self.new_cards)
def __init__(self): UiControllerReview.__init__(self, name="Command line UI Controller") self.title = _("Mnemosyne") + " - " + \ os.path.basename(config()["path"])[:-4] self.grade = 0 self.card = None
def do_input(line): """ Input mode """ print("=== Input mode ===") card = ui_controller_main() card_type_by_id = dict([(card_type.id, card_type) \ for card_type in card_types()]) category_names_by_id = dict([(i, name) for (i, name) in \ enumerate(database().category_names())]) while True: # Select Card Type by user: print "Select Card Type:" print '\n'.join(["%s %s" % \ (type_id, card_type_by_id[type_id].name)\ for type_id in sorted(card_type_by_id.keys())]) while True: inp = \ raw_input(_("Enter number of Card Type or 'q' to quit ... ")) if inp in ("q", "Q"): return if inp in card_type_by_id: card_type = card_type_by_id[inp] break print(_("Input error, try again")) # Select the exist or Add the new Category print '\n'.join(["%s %s" % (cat_id, category_names_by_id[cat_id]) \ for cat_id in sorted(category_names_by_id.keys())]) inp = raw_input(_("Enter number of Category or "\ "enter new category or 'q' to quit ... ")) if inp in ("q", "Q"): return category_name = inp if inp in category_names_by_id: category_name = category_names_by_id[inp] # Enter all fields for the current type fact = {} for key, name in card_type.fields: print _("Enter field"), name inp = raw_input() if inp: fact[key] = inp # Check necesary fields and create new card for required in card_type.required_fields(): if required not in fact: print(_("Error.This card is not saved in a database !!!")) print(_("You didn't enter all necesary field(s) !!!")) break else: # Create new card card.create_new_cards(fact, card_type, 0, [category_name]) database().save(config()['path']) if raw_input(_("Do you want to add a new record? y/n ")) != "y": break
def file_save(self): stopwatch.pause() path = config()["path"] try: database().save(path) except MnemosyneError, e: self.widget.error_box(e)
def file_new(self): stopwatch.pause() out = self.widget.save_file_dialog(path=config().basedir, filter=_("Mnemosyne databases (*.mem)"), caption=_("New")) if not out: stopwatch.unpause() return if not out.endswith(".mem"): out += ".mem" db = database() db.unload() db.new(out) db.load(config()["path"]) ui_controller_review().clear() ui_controller_review().update_dialog() stopwatch.unpause()
def start_logging(self): basedir = config().basedir log_name = os.path.join(basedir, "log.txt") self.logger.setLevel(logging.INFO) fh = logging.FileHandler(log_name) formatter = logging.Formatter("%(asctime)s %(message)s", "%Y-%m-%d %H:%M:%S :") fh.setFormatter(formatter) self.logger.addHandler(fh)
def initialise_logging(): global upload_thread from mnemosyne.libmnemosyne.log_uploader import LogUploader log().archive_old_log() log().start_logging() log().program_started() if config()["upload_logs"]: upload_thread = LogUploader() upload_thread.start()
def unload(self): self.save(config()["path"]) log().saved_database() self.start_date = None self.categories = [] self.facts = [] self.fact_views = [] self.cards = [] scheduler().clear_queue() return True
def initialise_user_plugins(): basedir = config().basedir plugindir = unicode(os.path.join(basedir, "plugins")) sys.path.insert(0, plugindir) for plugin in os.listdir(plugindir): if plugin.endswith(".py"): try: __import__(plugin[:-3]) except: raise PluginError(stack_trace=True)
def __init__(self): """ Load theme's glade file """ theme_path = config()["theme_path"] gtk.rc_parse(os.path.join(theme_path, "rcfile")) self.w_tree = gtk.glade.XML(os.path.join(theme_path, "window.glade")) # Set unvisible tabs of switcher switcher = self.w_tree.get_widget("switcher") switcher.set_property('show_tabs', False)
def archive_old_log(self): """Archive log to history folder if it's large enough.""" basedir = config().basedir log_name = os.path.join(basedir, "log.txt") try: log_size = os.stat(log_name).st_size except: log_size = 0 if log_size > 64000: user = config()["user_id"] index = config()["log_index"] archive_name = "%s_%05d.bz2" % (user, index) f = bz2.BZ2File(os.path.join(basedir, "history", archive_name), 'w') for l in file(log_name): f.write(l) f.close() os.remove(log_name) config()["log_index"] = index + 1
def days_since_start(self): """Note that this should be cached as much as possible for efficiency reasons. """ h = config()["day_starts_at"] adjusted_start = self.start.replace(hour=h, minute=0, second=0) dt = datetime.datetime.now() - adjusted_start return dt.days
def __init__(self): """ Initialization items of review window """ HildonBaseUi.__init__(self, signals=["get_answer", "grade"]) UiControllerReview.__init__(self, name="Hildon UI Review Controller") self.title = _("Mnemosyne") + " - " + \ splitext(basename(config()["path"]))[0] self.grade = 0 self.card = None
def finalise(): global upload_thread if upload_thread: print "Waiting for uploader thread to stop..." upload_thread.join() print "done!" log().program_stopped() try: os.remove(os.path.join(config().basedir,"MNEMOSYNE_LOCK")) except OSError: print "Failed to remove lock file." print traceback_string()
def file_open(self): stopwatch.pause() old_path = expand_path(config()["path"]) out = self.widget.open_file_dialog(path=old_path, filter=_("Mnemosyne databases (*.mem)")) if not out: stopwatch.unpause() return try: database().unload() except MnemosyneError, e: self.widget.error_box(e) stopwatch.unpause() return
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, fname): """Load database from file.""" # Unload opened database if exists self.unload() self.path = expand_path(fname, config().basedir) try: res = self.conn.execute("select value from meta where key=?", ("start_date", )).fetchone() self.load_failed = False self.set_start_date(StartDate(datetime.strptime(res["value"], "%Y-%m-%d %H:%M:%S"))) self.load_failed = False except sqlite.OperationalError: self.load_failed = True