Example #1
0
 def onFocusLost(self, fact, field):
     if not self.sessionExistsFor(field):
         return
     
     # This is necessary because the QT controls screw with the HTML. If we don't normalise
     # away the changes before the comparison, we might report changes where none took place.
     def normaliseHtml(html):
         if html == None:
             return None
         
         te = QtGui.QTextEdit()
         te.setHtml(html)
         return te.toHtml()
     
     log.info("User moved focus from the field %s", field.name)
     
     # Check old field contents against remembered value to determine changed status..
     self.knownfieldcontents[field.name], fieldchanged = None, normaliseHtml(self.knownfieldcontents[field.name]) != normaliseHtml(field.value)
     
     # Changed fields have their "generated" tag stripped. NB: ALWAYS update the fact (even if the
     # field hasn't changed) because we might have changed ANOTHER field to e.g. blank it, and now
     # by moving focus from the expression field we indicate that we want to fill it out.
     #
     # NB: be careful with this ternary statement! It's perfectly OK for field.value to be "", and if
     # that happens we CAN'T let fieldvalue in updatefact be None, or autoblanking gets broken.
     self.updatefact(fact, field.name, (fieldchanged and [pinyin.factproxy.unmarkgeneratedfield(field.value)] or [None])[0])
    def hanziData(self):
        # If we have some data already, just give up
        if self.__hanzidatacache is not None:
            return self.__hanzidatacache

        log.info("Updating Hanzi graph data")

        # Retrieve information about the card contents that were first answered on each day
        #
        # NB: the KanjiGraph uses information from ANY field (i.e. does not look at the fieldModels.name
        # value at all). However, Nick was confused by this behaviour because he had some radicals in his
        # deck, so his graph looked like he had `learnt' thousands of characters based on the listing of
        # every character in the `examples' fields of his radical facts.
        #
        # NB: the first answered time can be 0 but repeats > 1 due to a bug in an Anki feature which will
        # have screwed up the data in old decks. We select the created date for use in such cases:
        # <http://github.com/batterseapower/pinyin-toolkit/issues/closed/#issue/48>
        self.__hanzidatacache = self.mw.deck.s.all("""
        select fields.value, cards.firstAnswered, cards.created from cards, fields, fieldModels, notes
        where
        cards.reps > 0 and
        cards.factId = fields.factId
        and cards.factId = notes.id
        and notes.modelId in %s
        and fields.fieldModelId = fieldModels.id
        and fieldModels.name in %s
        order by firstAnswered
        """ % (anki.utils.ids2str(self.suitableModelIds()),
               self.toSqlLiteral(
                   self.config.candidateFieldNamesByKey['expression'])))
        return self.__hanzidatacache
    def setupHanziGraph(self, graphwindow):
        log.info("Beginning setup of Hanzi graph on the graph window")

        # Don't add a graph if the deck doesn't have a Mandarin tag. This might be too conservative (the user
        # could add a Mandarin tag and then refresh) but in general it's going to work well to hide it from the
        # user on their non-Mandarin decks.
        if len(self.suitableModelIds()) == 0:
            return

        # NB: we used to preload the Hanzi data at this point, but that makes the ``refresh'' button not work,
        # because we have no means of clearing the preloaded data, so now it lives on the class. We also used to
        # avoid adding the graph if the current deck was not

        # Append our own graph at the end
        from ankiqt.ui.graphs import AdjustableFigure
        extragraph = AdjustableFigure(
            graphwindow.parent, 'hanzi',
            lambda days: self.calculateHanziData(graphwindow, days),
            graphwindow.range)
        extragraph.addWidget(
            QLabel("<h1>Unique Hanzi (Cumulative, By HSK Level)</h1>"))
        graphwindow.vbox.addWidget(extragraph)
        graphwindow.widgets.append(extragraph)

        # Add our graph to the name map - this is necessary to avoid exceptions when using show/hide
        graphwindow.nameMap[
            'hanzi'] = "Unique Hanzi (Cumulative, By HSK Level)"

        # To allow refreshing to work properly, we have to intercept the call to updateFigure() made by Ankis onRefresh code
        extragraph.updateFigure = wrap(extragraph.updateFigure,
                                       self.invalidateHanziData, "before")
    def calculateHanziData(self, graphwindow, days):
        log.info("Calculating %d days worth of Hanzi graph data", days)

        # NB: must lazy-load matplotlib to give Anki a chance to set up the paths
        # to the data files, or we get an error like "Could not find the matplotlib
        # data files" as documented at <http://www.py2exe.org/index.cgi/MatPlotLib>
        try:
            from matplotlib.figure import Figure
        except UnicodeEncodeError:
            # Haven't tracked down the cause of this yet, but reloading fixes it
            log.warn("UnicodeEncodeError loading matplotlib - trying again")
            from matplotlib.figure import Figure

        # Use the statistics engine to generate the data for graphing.
        # NB: should add one to the number of days because we want to
        # return e.g. 8 days of data for a 7 day plot (the extra day is "today").
        xs, _totaly, gradeys = pinyin.statistics.hanziDailyStats(
            self.hanziData(), days + 1)

        # Set up the figure into which we will add all our graphs
        figure = Figure(figsize=(graphwindow.dg.width, graphwindow.dg.height),
                        dpi=graphwindow.dg.dpi)
        self.addLegend(figure)
        self.addGraph(figure, graphwindow, days, xs, gradeys)

        return figure
Example #5
0
 def inner():
     source = dictionarydir(path)
     if os.path.exists(source):
         return path, os.path.getmtime(source), lambda target: shutil.copyfile(source, target)
     else:
         log.info("Missing ordinary file at %s", source)
         return None
Example #6
0
    def onFocusLost(self, flag, note, fldIdx):
        savedNoteValues = deepcopy(note.values())

        fieldNames = self.mw.col.models.fieldNames(note.model())
        currentFieldName = fieldNames[fldIdx]
        log.info("User moved focus from the field %s", currentFieldName)
        
        # Are we not in a Mandarin model?
        if not(pinyin.utils.ismandarinmodel(note.model()['name'])):
            return flag
        
        # Need a fact proxy because the updater works on dictionary-like objects
        factproxy = pinyin.factproxy.FactProxy(self.config.candidateFieldNamesByKey, note)
        
        # Find which kind of field we have just moved off
        updater = None
        for key, fieldname in factproxy.fieldnames.items():
            if currentFieldName == fieldname:
                updater = self.updaters.get(key)
                break

        # Update the card, ignoring any errors
        fieldValue = note.fields[fldIdx]
        if not updater:
            return flag

        pinyin.utils.suppressexceptions(
            lambda: updater.updatefact(factproxy, fieldValue))

        noteChanged = (savedNoteValues != note.values())

        return noteChanged
Example #7
0
def runBulkFill(mw, config, notifier, updaters, field, updatehow, notification):
    if mw.web.key == "deckBrowser":
        return showInfo(u"No deck selected 同志!")

    log.info("User triggered missing information fill for %s" % field)
    
    queryStr = "deck:current "
    for tag in config.getmodeltagslist():
        queryStr += " or note:*" + tag + "* "
    notes = Finder(mw.col).findNotes(queryStr)

    for noteId in notes:
        note = mw.col.getNote(noteId)
        # Need a fact proxy because the updater works on dictionary-like objects
        factproxy = pinyin.factproxy.FactProxy(config.candidateFieldNamesByKey, note)
        if field not in factproxy:
            continue
        
        getattr(updaters[field], updatehow)(factproxy, factproxy[field])
        
        # NB: very important to mark the fact as modified (see #105) because otherwise
        # the HTML etc won't be regenerated by Anki, so users may not e.g. get working
        # sounds that have just been filled in by the updater.
        note.flush()
    
    # For good measure, mark the deck as modified as well (see #105)
    mw.col.setMod()

    # DEBUG consider future feature to add missing measure words cards after doing so (not now)
    notifier.info(notification)
Example #8
0
 def install(self):
     from anki.hooks import wrap
     import ankiqt.ui.facteditor
     
     log.info("Installing color shortcut keys hook")
     ankiqt.ui.facteditor.FactEditor.setupFields = wrap(ankiqt.ui.facteditor.FactEditor.setupFields, self.setupShortcuts, "after")
     self.setupShortcuts(self.mw.editor)
Example #9
0
 def install(self):
     from anki.hooks import wrap
     import ankiqt.ui.facteditor
     
     log.info("Installing color shortcut keys hook")
     ankiqt.ui.facteditor.FactEditor.setupFields = wrap(ankiqt.ui.facteditor.FactEditor.setupFields, self.setupShortcuts, "after")
     self.setupShortcuts(self.mw.editor)
 def setupHanziGraph(self, graphwindow):
     log.info("Beginning setup of Hanzi graph on the graph window")
     
     # Don't add a graph if the deck doesn't have a Mandarin tag. This might be too conservative (the user
     # could add a Mandarin tag and then refresh) but in general it's going to work well to hide it from the
     # user on their non-Mandarin decks.
     if len(self.suitableModelIds()) == 0:
         return
     
     # NB: we used to preload the Hanzi data at this point, but that makes the ``refresh'' button not work,
     # because we have no means of clearing the preloaded data, so now it lives on the class. We also used to
     # avoid adding the graph if the current deck was not 
     
     # Append our own graph at the end
     from ankiqt.ui.graphs import AdjustableFigure
     extragraph = AdjustableFigure(graphwindow.parent, 'hanzi', lambda days: self.calculateHanziData(graphwindow, days), graphwindow.range)
     extragraph.addWidget(QtGui.QLabel("<h1>Unique Hanzi (Cumulative, By HSK Level)</h1>"))
     graphwindow.vbox.addWidget(extragraph)
     graphwindow.widgets.append(extragraph)
     
     # Add our graph to the name map - this is necessary to avoid exceptions when using show/hide
     graphwindow.nameMap['hanzi'] = "Unique Hanzi (Cumulative, By HSK Level)"
     
     # To allow refreshing to work properly, we have to intercept the call to updateFigure() made by Ankis onRefresh code
     extragraph.updateFigure = wrap(extragraph.updateFigure, self.invalidateHanziData, "before")
 def hanziData(self):
     # If we have some data already, just give up
     if self.__hanzidatacache is not None:
         return self.__hanzidatacache
     
     log.info("Updating Hanzi graph data")
     
     # Retrieve information about the card contents that were first answered on each day
     #
     # NB: the KanjiGraph uses information from ANY field (i.e. does not look at the fieldModels.name
     # value at all). However, Nick was confused by this behaviour because he had some radicals in his
     # deck, so his graph looked like he had `learnt' thousands of characters based on the listing of
     # every character in the `examples' fields of his radical facts.
     #
     # NB: the first answered time can be 0 but repeats > 1 due to a bug in an Anki feature which will
     # have screwed up the data in old decks. We select the created date for use in such cases:
     # <http://github.com/batterseapower/pinyin-toolkit/issues/closed/#issue/48>
     self.__hanzidatacache = self.mw.deck.s.all("""
     select fields.value, cards.firstAnswered, cards.created from cards, fields, fieldModels, facts
     where
     cards.reps > 0 and
     cards.factId = fields.factId
     and cards.factId = facts.id
     and facts.modelId in %s
     and fields.fieldModelId = fieldModels.id
     and fieldModels.name in %s
     order by firstAnswered
     """ % (anki.utils.ids2str(self.suitableModelIds()), pinyin.utils.toSqlLiteral(self.config.candidateFieldNamesByKey['expression'])))
     return self.__hanzidatacache
Example #12
0
 def inner():
     path, timestamp = findtimestampedfile(pathpattern)
     
     if path is None:
         log.info("Missing archive matching the timestamped pattern %s", pathpattern)
         return None
     
     return plainArchiveSource(path, ["%s" in pizp and (pizp % timestamp) or pizp for pizp in pathinzippattern])()
Example #13
0
 def inner():
     path, _timestamp = findtimestampedfile(pathpattern)
     
     if path is None:
         log.info("Missing file matching the timestamped pattern %s", pathpattern)
         return None
     
     return fileSource(path)()
Example #14
0
 def onFocusGot(self, field):
     if not self.sessionExistsFor(field):
         return
     
     log.info("User put focus on the field %s", field.name)
     
     # Remember old field contents
     self.knownfieldcontents[field.name] = field.value
Example #15
0
 def setupShortcuts(self, editor):
     # Loop through the 8 F[x] keys, setting each one up
     # Note: Ctrl-F9 is the HTML editor. Don't do this as it causes a conflict
     log.info("Setting up shortcut keys on fact editor")
     for i in range(1, 9):
         for sandhify in [True, False]:
             keysequence = (sandhify and pinyin.anki.keys.sandhiModifier + "+" or "") + pinyin.anki.keys.shortcutKeyFor(i)
             QtGui.QShortcut(QtGui.QKeySequence(keysequence), editor.widget,
                             lambda i=i, sandhify=sandhify: self.setColor(editor, i, sandhify))
Example #16
0
 def install(self):
     from anki.hooks import addHook, remHook
     
     # Install hook into focus event of Anki: we regenerate the model information when
     # the cursor moves from the Expression/Reading/whatever field to another field
     log.info("Installing focus hook")
     
     # Unconditionally add our new hook to Anki
     addHook('editFocusLost', self.onFocusLost)
Example #17
0
 def setupShortcuts(self, editor):
     # Loop through the 8 F[x] keys, setting each one up
     # Note: Ctrl-F9 is the HTML editor. Don't do this as it causes a conflict
     log.info("Setting up shortcut keys on fact editor")
     for i in range(1, 9):
         for sandhify in [True, False]:
             keysequence = (sandhify and pinyin.anki.keys.sandhiModifier + "+" or "") + pinyin.anki.keys.shortcutKeyFor(i)
             QShortcut(QKeySequence(keysequence), editor.widget,
                             lambda i=i, sandhify=sandhify: self.setColor(editor, i, sandhify))
Example #18
0
def chooseField(candidateFieldNames, targetkeys):
    # Find the first field that is present in the fact
    for candidateField in candidateFieldNames:
        for factfieldname in [factfieldname for factfieldname in targetkeys if factfieldname.lower() == candidateField.lower()]:
            log.info("Choose %s as a field name from the fact for %s", factfieldname, candidateField)
            return factfieldname
    
    # No suitable field found!
    log.warn("No field matching %s in the fact", candidateFieldNames)
    return None
 def install(self):
     log.info("Installing Hanzi graph hook")
     
     # NB: must lazy-load ankiqt.ui.graphs because importing it will fail if the user doesn't
     # have python-matplotlib installed on Linux.
     try:
         from ankiqt.ui.graphs import GraphWindow
         GraphWindow.setupGraphs = wrap(GraphWindow.setupGraphs, self.setupHanziGraph, "after")
     except ImportError, e:
         self.notifier.exception("There was a problem setting up the Hanzi Graph! If you are using Linux, " +
                                 "you may need to install the package providing matplotlib to Python. On Ubuntu " +
                                 "you can do that by running 'sudo apt-get install python-matplotlib' in the Terminal.")
Example #20
0
 def __init__(self, candidateFieldNamesByKey, fact):
     self.fact = fact
     
     # NB: the fieldnames dictionary IS part of the interface of this class
     self.fieldnames = {}
     for key, candidateFieldNames in candidateFieldNamesByKey.items():
         # Don't add a key into the dictionary if we can't find a field, or we end
         # up reporting that we the contain the field but die during access
         fieldname = chooseField(candidateFieldNames, fact.keys())
         if fieldname is not None:
             self.fieldnames[key] = fieldname
     
     log.info("Choose field mapping %r", self.fieldnames)
    def __init__(self, candidateFieldNamesByKey, fact):
        self.fact = fact

        # NB: the fieldnames dictionary IS part of the interface of this class
        self.fieldnames = {}
        for key, candidateFieldNames in candidateFieldNamesByKey.items():
            # Don't add a key into the dictionary if we can't find a field, or we end
            # up reporting that we the contain the field but die during access
            fieldname = chooseField(candidateFieldNames, fact.keys())
            if fieldname is not None:
                self.fieldnames[key] = fieldname

        log.info("Choose field mapping %r", self.fieldnames)
Example #22
0
 def setColor(self, editor, i, sandhify):
     log.info("Got color change event for color %d, sandhify %s", i, sandhify)
     
     color = (self.config.tonecolors + self.config.extraquickaccesscolors)[i - 1]
     if sandhify:
         color = pinyin.transformations.sandhifycolor(color)
     
     focusededit = editor.focusedEdit()
     
     cursor = focusededit.textCursor()
     focusededit.setTextColor(QtGui.QColor(color))
     cursor.clearSelection()
     focusededit.setTextCursor(cursor)
Example #23
0
 def setColor(self, editor, i, sandhify):
     log.info("Got color change event for color %d, sandhify %s", i, sandhify)
     
     color = (self.config.tonecolors + self.config.extraquickaccesscolors)[i - 1]
     if sandhify:
         color = pinyin.transformations.sandhifycolor(color)
     
     focusededit = editor.focusedEdit()
     
     cursor = focusededit.textCursor()
     focusededit.setTextColor(QColor(color))
     cursor.clearSelection()
     focusededit.setTextCursor(cursor)
Example #24
0
    def initialize(self, mw):
        log.info("Pinyin Toolkit is initializing")

        # Build basic objects we use to interface with Anki
        thenotifier = notifier.AnkiNotifier()
        themediamanager = mediamanager.AnkiMediaManager(mw)

        # Open up the database
        if not self.tryCreateAndLoadDatabase(mw, thenotifier):
            # Eeek! Database building failed, so we better turn off the toolkit
            log.error("Database construction failed: disabling the Toolkit")
            return

        # Build the updaters
        updaters = {
            'expression':
            pinyin.updater.FieldUpdaterFromExpression(thenotifier,
                                                      themediamanager),
            'reading':
            pinyin.updater.FieldUpdaterFromReading(),
            'meaning':
            pinyin.updater.FieldUpdaterFromMeaning(),
            'audio':
            pinyin.updater.FieldUpdaterFromAudio(thenotifier, themediamanager)
        }

        # Finally, build the hooks.  Make sure you store a reference to these, because otherwise they
        # get garbage collected, causing garbage collection of the actions they contain
        self.hooks = [
            hookbuilder(mw, thenotifier, themediamanager, updaters)
            for hookbuilder in hookbuilders
        ]
        for hook in self.hooks:
            hook.install()

        # add hooks and menu items
        # use wrap() instead of addHook to ensure menu already created
        def ptkRebuildAddonsMenu(self):
            ptkMenu = None
            for menu in self._menus:
                if menu.title() == "Pinyin Toolkit":
                    ptkMenu = menu
                    break

            ptkMenu.addSeparator()
            config = getconfig()
            hooks.buildHooks(ptkMenu, mw, config, thenotifier, themediamanager,
                             updaters)

        aqt.addons.AddonManager.rebuildAddonsMenu = wrap(
            aqt.addons.AddonManager.rebuildAddonsMenu, ptkRebuildAddonsMenu)
Example #25
0
def chooseField(candidateFieldNames, fact):
    # Find the first field that is present in the fact
    for candidateField in candidateFieldNames:
        for factfieldname in [
                factfieldname for factfieldname in fact.keys()
                if factfieldname.lower() == candidateField.lower()
        ]:
            log.info("Choose %s as a field name from the fact for %s",
                     factfieldname, candidateField)
            return factfieldname

    # No suitable field found!
    log.warn("No field matching %s in the fact", candidateFieldNames)
    return None
Example #26
0
def openPreferences(mw, config, notifier, mediamanager):
    # NB: must import these lazily to break a loop between preferencescontroller and here
    import pinyin.forms.preferences
    import pinyin.forms.preferencescontroller
    log.info("User opened preferences dialog")
    
    # Instantiate and show the preferences dialog modally
    preferences = pinyin.forms.preferences.Preferences(mw)
    controller = pinyin.forms.preferencescontroller.PreferencesController(preferences, notifier, mediamanager, config)
    result = preferences.exec_()
    
    if result == QDialog.Accepted:
        config.settings = controller.model.settings
        saveconfig()
Example #27
0
    def install(self):
        log.info("Installing Hanzi graph hook")

        # NB: must lazy-load ankiqt.ui.graphs because importing it will fail if the user doesn't
        # have python-matplotlib installed on Linux.
        try:
            from ankiqt.ui.graphs import GraphWindow
            GraphWindow.setupGraphs = wrap(GraphWindow.setupGraphs,
                                           self.setupHanziGraph, "after")
        except ImportError, e:
            self.notifier.exception(
                "There was a problem setting up the Hanzi Graph! If you are using Linux, "
                +
                "you may need to install the package providing matplotlib to Python. On Ubuntu "
                +
                "you can do that by running 'sudo apt-get install python-matplotlib' in the Terminal."
            )
Example #28
0
 def discovermediapacks(self):
     packs = []
     themediadir = self.mediadir()
     for packname in os.listdir(themediadir):
         # Skip the download cache directory
         if packname.lower() == "downloads":
             continue
         
         # Only try and process directories as packs:
         packpath = os.path.join(themediadir, packname)
         if os.path.isdir(packpath):
             log.info("Considering %s as a media pack", packname)
             packs.append(pinyin.media.MediaPack.frompath(packpath))
         else:
             log.info("Ignoring the file %s in the media directory", packname)
     
     return packs
Example #29
0
    def install(self):
        from anki.hooks import addHook, removeHook

        # Install hook into focus event of Anki: we regenerate the model information when
        # the cursor moves from the Expression/Reading/whatever field to another field
        log.info("Installing focus hook")

        try:
            # On versions of Anki that still had Chinese support baked in, remove the
            # provided hook from this event before we replace it with our own:
            from anki.features.chinese import onFocusLost as oldHook
            removeHook('fact.focusLost', oldHook)
        except ImportError:
            pass

        # Unconditionally add our new hooks to Anki
        addHook('makeField', self.makeField)
        addHook('fact.focusLost', self.onFocusLost)
Example #30
0
 def initialize(self, mw):
     log.info("Pinyin Toolkit is initializing")
     
     # Build basic objects we use to interface with Anki
     thenotifier = notifier.AnkiNotifier()
     themediamanager = mediamanager.AnkiMediaManager(mw)
     
     # Open up the database
     if not self.tryCreateAndLoadDatabase(mw, thenotifier):
         # Eeek! Database building failed, so we better turn off the toolkit
         log.error("Database construction failed: disabling the Toolkit")
         return
     
     # Try and load the settings from the Anki config database
     settings = mw.config.get("pinyintoolkit")
     if settings is None:
         # Initialize the configuration with default settings
         config = pinyin.config.Config()
         utils.persistconfig(mw, config)
         
         # TODO: first-run activities:
         #  1) Guide user around the interface and what they can do
         #  2) Link to getting started guide
     else:
         # Initialize the configuration with the stored settings
         config = pinyin.config.Config(settings)
     
     # Build the updaters
     updaters = {
         'expression' : pinyin.updater.FieldUpdaterFromExpression,
         'reading'    : lambda *args: pinyin.updater.FieldUpdater("reading", *args),
         'meaning'    : lambda *args: pinyin.updater.FieldUpdater("meaning", *args),
         'audio'      : lambda *args: pinyin.updater.FieldUpdater("audio", *args)
       }
     
     # Finally, build the hooks.  Make sure you store a reference to these, because otherwise they
     # get garbage collected, causing garbage collection of the actions they contain
     self.hooks = [hookbuilder(mw, thenotifier, themediamanager, config, updaters) for hookbuilder in hookbuilders]
     for hook in self.hooks:
         hook.install()
 
     # Tell Anki about the plugin
     mw.registerPlugin("Mandarin Chinese Pinyin Toolkit", 4)
     self.registerStandardModels()
Example #31
0
 def initialize(self, mw):
     log.info("Pinyin Toolkit is initializing")
     
     # Build basic objects we use to interface with Anki
     thenotifier = notifier.AnkiNotifier()
     themediamanager = mediamanager.AnkiMediaManager(mw)
     
     # Open up the database
     if not self.tryCreateAndLoadDatabase(mw, thenotifier):
         # Eeek! Database building failed, so we better turn off the toolkit
         log.error("Database construction failed: disabling the Toolkit")
         return
     
     # Try and load the settings from the Anki config database
     settings = mw.config.get("pinyintoolkit")
     if settings is None:
         # Initialize the configuration with default settings
         config = pinyin.config.Config()
         utils.persistconfig(mw, config)
         
         # TODO: first-run activities:
         #  1) Guide user around the interface and what they can do
         #  2) Link to getting started guide
     else:
         # Initialize the configuration with the stored settings
         config = pinyin.config.Config(settings)
     
     # Build the updaters
     updaters = {
         'expression' : pinyin.updater.FieldUpdaterFromExpression,
         'reading'    : lambda *args: pinyin.updater.FieldUpdater("reading", *args),
         'meaning'    : lambda *args: pinyin.updater.FieldUpdater("meaning", *args),
         'audio'      : lambda *args: pinyin.updater.FieldUpdater("audio", *args)
       }
     
     # Finally, build the hooks.  Make sure you store a reference to these, because otherwise they
     # get garbage collected, causing garbage collection of the actions they contain
     self.hooks = [hookbuilder(mw, thenotifier, themediamanager, config, updaters) for hookbuilder in hookbuilders]
     for hook in self.hooks:
         hook.install()
 
     # Tell Anki about the plugin
     mw.registerPlugin("Mandarin Chinese Pinyin Toolkit", 4)
     self.registerStandardModels()
Example #32
0
    def install(self):
        from anki.hooks import addHook, removeHook

        # Install hook into focus event of Anki: we regenerate the model information when
        # the cursor moves from the Expression/Reading/whatever field to another field
        log.info("Installing focus hook")

        try:
            # On versions of Anki that still had Chinese support baked in, remove the
            # provided hook from this event before we replace it with our own:
            from anki.features.chinese import onFocusLost as oldHook

            removeHook("fact.focusLost", oldHook)
        except ImportError:
            pass

        # Unconditionally add our new hooks to Anki
        addHook("makeField", self.makeField)
        addHook("fact.focusLost", self.onFocusLost)
Example #33
0
    def onFocusLost(self, fact, field):
        if not self.sessionExistsFor(field):
            return

        log.info("User moved focus from the field %s", field.name)

        # Determine whether the field was modified by checking the document modified status
        fieldwidget = self.knownfactedit.fields[field.name][1]
        fieldchanged = fieldwidget.document().isModified()

        # Changed fields have their "generated" tag stripped. NB: ALWAYS update the fact (even if the
        # field hasn't changed) because we might have changed ANOTHER field to e.g. blank it, and now
        # by moving focus from the expression field we indicate that we want to fill it out.
        #
        # NB: be careful with this ternary statement! It's perfectly OK for field.value to be "", and if
        # that happens we CAN'T let fieldvalue in updatefact be None, or autoblanking gets broken.
        self.updatefact(
            fact, field.name, (fieldchanged and [pinyin.factproxy.unmarkgeneratedfield(field.value)] or [None])[0]
        )
Example #34
0
    def initialize(self, mw):
        log.info("Pinyin Toolkit is initializing")
        
        # Build basic objects we use to interface with Anki
        thenotifier = notifier.AnkiNotifier()
        themediamanager = mediamanager.AnkiMediaManager(mw)
        
        # Open up the database
        if not self.tryCreateAndLoadDatabase(mw, thenotifier):
            # Eeek! Database building failed, so we better turn off the toolkit
            log.error("Database construction failed: disabling the Toolkit")
            return

        # Build the updaters
        updaters = {
            'expression' : pinyin.updater.FieldUpdaterFromExpression(thenotifier, themediamanager),
            'reading'    : pinyin.updater.FieldUpdaterFromReading(),
            'meaning'    : pinyin.updater.FieldUpdaterFromMeaning(),
            'audio'      : pinyin.updater.FieldUpdaterFromAudio(thenotifier, themediamanager)
          }
        
        # Finally, build the hooks.  Make sure you store a reference to these, because otherwise they
        # get garbage collected, causing garbage collection of the actions they contain
        self.hooks = [hookbuilder(mw, thenotifier, themediamanager, updaters) for hookbuilder in hookbuilders]
        for hook in self.hooks:
            hook.install()

        # add hooks and menu items
        # use wrap() instead of addHook to ensure menu already created 
        def ptkRebuildAddonsMenu(self):
            ptkMenu = None
            for menu in self._menus:
                if menu.title() == "Pinyin Toolkit":
                    ptkMenu = menu
                    break

            ptkMenu.addSeparator()
            config = getconfig()
            hooks.buildHooks(ptkMenu, mw, config, thenotifier, themediamanager,
                               updaters)

        aqt.addons.AddonManager.rebuildAddonsMenu = wrap(aqt.addons.AddonManager.rebuildAddonsMenu, ptkRebuildAddonsMenu) 
Example #35
0
 def install(self):
     # Install menu item
     log.info("Installing a menu hook (%s)", type(self))
     
     # Build and install the top level menu if it doesn't already exist
     if ToolMenuHook.pinyinToolkitMenu is None:
         ToolMenuHook.pinyinToolkitMenu = QtGui.QMenu("Pinyin Toolkit", self.mw.mainWin.menuTools)
         self.mw.mainWin.menuTools.addMenu(ToolMenuHook.pinyinToolkitMenu)
     
     # Store the action on the class.  Storing a reference to it is necessary to avoid it getting garbage collected.
     self.action = QtGui.QAction(self.__class__.menutext, self.mw)
     self.action.setStatusTip(self.__class__.menutooltip)
     self.action.setEnabled(True)
     
     # HACK ALERT: must use lambda here, or the signal never gets raised! I think this is due to garbage collection...
     # We try and make sure that we don't run the action if there is no deck presently, to at least suppress some errors
     # in situations where the users select the menu items (this is possible on e.g. OS X). It would be better to disable
     # the menu items entirely in these situations, but there is no suitable hook for that presently.
     self.mw.connect(self.action, QtCore.SIGNAL('triggered()'), lambda: self.mw.deck is not None and self.triggered())
     ToolMenuHook.pinyinToolkitMenu.addAction(self.action)
Example #36
0
    def triggered(self):
        # NB: must import these lazily to break a loop between preferencescontroller and here
        import pinyin.forms.preferences
        import pinyin.forms.preferencescontroller

        log.info("User opened preferences dialog")
        
        # Instantiate and show the preferences dialog modally
        preferences = pinyin.forms.preferences.Preferences(self.mw)
        controller = pinyin.forms.preferencescontroller.PreferencesController(preferences, self.notifier, self.mediamanager, self.config)
        result = preferences.exec_()
        
        # We only need to change the configuration if the user accepted the dialog
        if result == QtGui.QDialog.Accepted:
            # Update by the simple method of replacing the settings dictionaries: better make sure that no
            # other part of the code has cached parts of the configuration
            self.config.settings = controller.model.settings
            
            # Ensure this is saved in Anki's configuration
            utils.persistconfig(self.mw, self.config)
Example #37
0
    def triggered(self):
        # NB: must import these lazily to break a loop between preferencescontroller and here
        import pinyin.forms.preferences
        import pinyin.forms.preferencescontroller

        log.info("User opened preferences dialog")

        # Instantiate and show the preferences dialog modally
        preferences = pinyin.forms.preferences.Preferences(self.mw)
        controller = pinyin.forms.preferencescontroller.PreferencesController(
            preferences, self.notifier, self.mediamanager, self.config)
        result = preferences.exec_()

        # We only need to change the configuration if the user accepted the dialog
        if result == QtGui.QDialog.Accepted:
            # Update by the simple method of replacing the settings dictionaries: better make sure that no
            # other part of the code has cached parts of the configuration
            self.config.settings = controller.model.settings

            # Ensure this is saved in Anki's configuration
            utils.persistconfig(self.mw, self.config)
Example #38
0
    def onFocusLost(self, fact, field):
        if not self.sessionExistsFor(field):
            return

        log.info("User moved focus from the field %s", field.name)

        # Determine whether the field was modified by checking the document modified status
        fieldwidget = self.knownfactedit.fields[field.name][1]
        fieldchanged = fieldwidget.document().isModified()

        # Changed fields have their "generated" tag stripped. NB: ALWAYS update the fact (even if the
        # field hasn't changed) because we might have changed ANOTHER field to e.g. blank it, and now
        # by moving focus from the expression field we indicate that we want to fill it out.
        #
        # NB: be careful with this ternary statement! It's perfectly OK for field.value to be "", and if
        # that happens we CAN'T let fieldvalue in updatefact be None, or autoblanking gets broken.
        self.updatefact(
            fact, field.name,
            (fieldchanged
             and [pinyin.factproxy.unmarkgeneratedfield(field.value)]
             or [None])[0])
    def install(self):
        log.info("Installing Hanzi statistics hook")

        # NB: must store reference to action on the class to prevent it being GCed
        self.action = QtGui.QAction("Hanzi Statistics by PyTK", self.mw)
        self.action.setStatusTip("Hanzi Statistics by PyTK")
        self.action.setEnabled(True)
        self.action.setIcon(QtGui.QIcon("../icons/hanzi.png"))

        def finish(x):
            html, python_actions = x
            self.mw.help.showText(
                html, py=dict([(k, lambda action=action: finish(action())) for k, action in python_actions])
            )

        self.mw.connect(
            self.action, QtCore.SIGNAL("triggered()"), lambda: finish(hanziStats(self.config, self.mw.deck.s))
        )
        self.mw.mainWin.menuTools.addAction(self.action)

        log.info("Hanzi statistics plugin loaded")
Example #40
0
    def tryCreateAndLoadDatabase(self, mw, notifier):
        datatimestamp, satisfiers = pinyin.db.builder.getSatisfiers()
        cjklibtimestamp = os.path.getmtime(
            pinyin.utils.toolkitdir("pinyin", "vendor", "cjklib", "cjklib",
                                    "build", "builder.py"))

        if not (os.path.exists(dbpath)):
            # MUST rebuild - DB doesn't exist
            log.info(
                "The database was missing entirely from %s. We had better build it!",
                dbpath)
            compulsory = True
        elif os.path.getmtime(dbpath) < cjklibtimestamp:
            # MUST rebuild - version upgrade might have changed DB format
            log.info(
                "The cjklib was upgraded at %d, which is since the database was built (at %d) - for safety we must rebuild",
                cjklibtimestamp, os.path.getmtime(dbpath))
            compulsory = True
        elif os.path.getmtime(dbpath) < datatimestamp:
            # SHOULD rebuild
            log.info(
                "The database had a timestamp of %d but we saw a data update at %d - let's rebuild",
                os.path.getmtime(dbpath), datatimestamp)
            compulsory = False
        else:
            # Do nothing
            log.info("Database up to date")
            compulsory = None

        if compulsory is not None:
            # We at least have the option to rebuild the DB: setup the builder
            dbbuilder = pinyin.db.builder.DBBuilder(satisfiers)

            # Show the form, which kicks off the builder and may give the user the option to cancel
            builddb = pinyin.forms.builddb.BuildDB(mw)
            # NB: VERY IMPORTANT to save the useless controller reference somewhere. This prevents the
            # QThread it spawns being garbage collected while the thread is still running! I hate PyQT4!
            _controller = pinyin.forms.builddbcontroller.BuildDBController(
                builddb, notifier, dbbuilder, compulsory)
            if builddb.exec_() == QDialog.Accepted:
                # Successful completion of the build process: replace the existing database, if any
                shutil.copyfile(dbbuilder.builtdatabasepath, dbpath)
            elif compulsory:
                # Eeek! The dialog was "rejected" despite being compulsory. This can only happen if there
                # was an error while building the database. Better give up now!
                return False

        # Finally, force the database connection to the (possibly fresh) DB to begin
        database()
        return True
Example #41
0
 def build(self):
     # [1/4]: copy and extract necessary files into a location cjklib can deal with
     log.info("Copying in dictionary data")
     for requirement, satisfier in self.satisfiers:
         satisfier(os.path.join(self.dictionarydatapath, requirement))
     
     # [2/4]: setup the database builder with a standard set of requirements
     log.info("Initializing builder")
     database = cjklib.dbconnector.getDBConnector({ "url" : sqlalchemy.engine.url.URL("sqlite", database=self.builtdatabasepath) })
     self.cjkdbbuilder = cjklib.build.DatabaseBuilder(
         dbConnectInst=database,
         # We need to turn quiet on, because Anki throws a hissy fit if you write to stderr
         # We turn disableFTS3 on because it makes my SELECTs 4 times faster on SQLite 3.4.0
         quiet=True, enableFTS3=False, rebuildExisting=False, noFail=False,
         dataPath=[self.dictionarydatapath, self.cjkdatapath],
         prefer=['CharacterVariantBMPBuilder', 'CombinedStrokeCountBuilder',
                 'CombinedCharacterResidualStrokeCountBuilder',
                 'HanDeDictFulltextSearchBuilder', 'UnihanBMPBuilder'])
     
     # [3/4]: build the database
     log.info("Building the cjklib database: the target file is %s", self.builtdatabasepath)
     self.cjkdbbuilder.build(DBBuilder.wantgroups)
     
     # [4/4]: clean up, so that we don't get errors if (when) the temporary database is deleted
     database.connection.close()
     del database.connection
     database.engine.dispose()
     del database.engine
Example #42
0
    def install(self):
        # Install menu item
        log.info("Installing a menu hook (%s)", type(self))

        # Build and install the top level menu if it doesn't already exist
        if ToolMenuHook.pinyinToolkitMenu is None:
            ToolMenuHook.pinyinToolkitMenu = QtGui.QMenu(
                "Pinyin Toolkit", self.mw.mainWin.menuTools)
            self.mw.mainWin.menuTools.addMenu(ToolMenuHook.pinyinToolkitMenu)

        # Store the action on the class.  Storing a reference to it is necessary to avoid it getting garbage collected.
        self.action = QtGui.QAction(self.__class__.menutext, self.mw)
        self.action.setStatusTip(self.__class__.menutooltip)
        self.action.setEnabled(True)

        # HACK ALERT: must use lambda here, or the signal never gets raised! I think this is due to garbage collection...
        # We try and make sure that we don't run the action if there is no deck presently, to at least suppress some errors
        # in situations where the users select the menu items (this is possible on e.g. OS X). It would be better to disable
        # the menu items entirely in these situations, but there is no suitable hook for that presently.
        self.mw.connect(self.action, QtCore.SIGNAL('triggered()'),
                        lambda: self.mw.deck is not None and self.triggered())
        ToolMenuHook.pinyinToolkitMenu.addAction(self.action)
Example #43
0
 def install(self):
     from anki.hooks import addHook, removeHook
     
     # Install hook into focus event of Anki: we regenerate the model information when
     # the cursor moves from the Expression/Reading/whatever field to another field
     log.info("Installing focus hook")
     
     try:
         # On versions of Anki that still had Chinese support baked in, remove the
         # provided hook from this event before we replace it with our own:
         from anki.features.chinese import onFocusLost as oldHook
         removeHook('fact.focusLost', oldHook)
     except ImportError:
         pass
     
     # Unconditionally add our new hooks to Anki
     addHook('makeField', self.makeField)
     addHook('fact.focusLost', self.onFocusLost)
     
     # Global hook
     app = QtGui.QApplication.instance()
     app.connect(app, QtCore.SIGNAL("focusChanged(QWidget*, QWidget*)"), self.onFocusChanged)
Example #44
0
 def triggered(self):
     field = self.__class__.field
     log.info("User triggered missing information fill for %s" % field)
     
     for fact in utils.suitableFacts(self.config.modelTag, self.mw.deck):
         # Need a fact proxy because the updater works on dictionary-like objects
         factproxy = pinyin.factproxy.FactProxy(self.config.candidateFieldNamesByKey, fact)
         if field not in factproxy:
             continue
         
         self.buildupdater(field).updatefact(factproxy, None, **self.__class__.updatefactkwargs)
         
         # NB: very important to mark the fact as modified (see #105) because otherwise
         # the HTML etc won't be regenerated by Anki, so users may not e.g. get working
         # sounds that have just been filled in by the updater.
         fact.setModified(textChanged=True)
     
     # For good measure, mark the deck as modified as well (see #105)
     self.mw.deck.setModified()
 
     # DEBUG consider future feature to add missing measure words cards after doing so (not now)
     self.notifier.info(self.__class__.notification)
Example #45
0
    def triggered(self):
        field = self.__class__.field
        log.info("User triggered missing information fill for %s" % field)

        for fact in utils.suitableFacts(self.config.modelTag, self.mw.deck):
            # Need a fact proxy because the updater works on dictionary-like objects
            factproxy = pinyin.factproxy.FactProxy(
                self.config.candidateFieldNamesByKey, fact)
            if field not in factproxy:
                continue

            self.buildupdater(field).updatefact(
                factproxy, None, **self.__class__.updatefactkwargs)

            # NB: very important to mark the fact as modified (see #105) because otherwise
            # the HTML etc won't be regenerated by Anki, so users may not e.g. get working
            # sounds that have just been filled in by the updater.
            fact.setModified(textChanged=True)

        # For good measure, mark the deck as modified as well (see #105)
        self.mw.deck.setModified()

        # DEBUG consider future feature to add missing measure words cards after doing so (not now)
        self.notifier.info(self.__class__.notification)
 def calculateHanziData(self, graphwindow, days):
     log.info("Calculating %d days worth of Hanzi graph data", days)
     
     # NB: must lazy-load matplotlib to give Anki a chance to set up the paths
     # to the data files, or we get an error like "Could not find the matplotlib
     # data files" as documented at <http://www.py2exe.org/index.cgi/MatPlotLib>
     try:
         from matplotlib.figure import Figure
     except UnicodeEncodeError:
         # Haven't tracked down the cause of this yet, but reloading fixes it
         log.warn("UnicodeEncodeError loading matplotlib - trying again")
         from matplotlib.figure import Figure
     
     # Use the statistics engine to generate the data for graphing.
     # NB: should add one to the number of days because we want to
     # return e.g. 8 days of data for a 7 day plot (the extra day is "today").
     xs, _totaly, gradeys = pinyin.statistics.hanziDailyStats(self.hanziData(), days + 1)
     
     # Set up the figure into which we will add all our graphs
     figure = Figure(figsize=(graphwindow.dg.width, graphwindow.dg.height), dpi=graphwindow.dg.dpi)
     self.addLegend(figure)
     self.addGraph(figure, graphwindow, days, xs, gradeys)
     
     return figure
Example #47
0
    def install(self):
        log.info("Installing Hanzi statistics hook")

        # NB: must store reference to action on the class to prevent it being GCed
        self.action = QtGui.QAction('Hanzi Statistics by PyTK', self.mw)
        self.action.setStatusTip('Hanzi Statistics by PyTK')
        self.action.setEnabled(True)
        self.action.setIcon(QtGui.QIcon("../icons/hanzi.png"))

        def finish(x):
            html, python_actions = x
            self.mw.help.showText(html,
                                  py=dict([
                                      (k,
                                       lambda action=action: finish(action()))
                                      for k, action in python_actions
                                  ]))

        self.mw.connect(
            self.action, QtCore.SIGNAL('triggered()'),
            lambda: finish(hanziStats(self.config, self.mw.deck.s)))
        self.mw.mainWin.menuTools.addAction(self.action)

        log.info('Hanzi statistics plugin loaded')
Example #48
0
 def tryCreateAndLoadDatabase(self, mw, notifier):
     datatimestamp, satisfiers = pinyin.db.builder.getSatisfiers()
     cjklibtimestamp = os.path.getmtime(pinyin.utils.toolkitdir("pinyin", "vendor", "cjklib", "cjklib", "build", "builder.py"))
     
     if not(os.path.exists(dbpath)):
         # MUST rebuild - DB doesn't exist
         log.info("The database was missing entirely from %s. We had better build it!", dbpath)
         compulsory = True
     elif os.path.getmtime(dbpath) < cjklibtimestamp:
         # MUST rebuild - version upgrade might have changed DB format
         log.info("The cjklib was upgraded at %d, which is since the database was built (at %d) - for safety we must rebuild", cjklibtimestamp, os.path.getmtime(dbpath))
         compulsory = True
     elif os.path.getmtime(dbpath) < datatimestamp:
         # SHOULD rebuild
         log.info("The database had a timestamp of %d but we saw a data update at %d - let's rebuild", os.path.getmtime(dbpath), datatimestamp)
         compulsory = False
     else:
         # Do nothing
         log.info("Database up to date")
         compulsory = None
     
     if compulsory is not None:
         # We at least have the option to rebuild the DB: setup the builder
         dbbuilder = pinyin.db.builder.DBBuilder(satisfiers)
         
         # Show the form, which kicks off the builder and may give the user the option to cancel
         builddb = pinyin.forms.builddb.BuildDB(mw)
         # NB: VERY IMPORTANT to save the useless controller reference somewhere. This prevents the
         # QThread it spawns being garbage collected while the thread is still running! I hate PyQT4!
         _controller = pinyin.forms.builddbcontroller.BuildDBController(builddb, notifier, dbbuilder, compulsory)
         if builddb.exec_() == QDialog.Accepted:
             # Successful completion of the build process: replace the existing database, if any
             shutil.copyfile(dbbuilder.builtdatabasepath, dbpath)
         elif compulsory:
             # Eeek! The dialog was "rejected" despite being compulsory. This can only happen if there
             # was an error while building the database. Better give up now!
             return False
     
     # Finally, force the database connection to the (possibly fresh) DB to begin
     database()
     return True
Example #49
0
 def inner():
     # Ensure that the zip exists before we open it
     zipsource = dictionarydir(path)
     if not(os.path.exists(zipsource)):
         log.info("Missing zip file at %s", zipsource)
         return None
     
     # Load up the zip
     sourcezip = zipfile.ZipFile(zipsource, "r")
     log.info("Available zip file contents %s", sourcezip.namelist())
     
     # Find the correct file in the zip: we should try both sorts of
     # slashes because the zip may have been created on Windows or Unix
     pathinzip = None
     for possiblepath in ["/".join(pathinzipcomponents), "\\".join(pathinzipcomponents)]:
         try:
             sourcezip.getinfo(possiblepath)
             pathinzip = possiblepath
             break
         except KeyError:
             pass
     
     # Maybe the file didn't exist at all?
     if pathinzip is None:
         log.info("Zip file at %s lacked a file called %s", path, os.path.join(*pathinzipcomponents))
         return None
     
     def go(target):
         # Extract the selected file from the zip
         targetfile = open(target, 'w')
         try:
             targetfile.write(sourcezip.read(pathinzip))
         finally:
             targetfile.close()
     
     return path + ":" + pathinzip, os.path.getmtime(zipsource), go
Example #50
0
def getSatisfiers():
    dictionarydir = lambda *components: pinyin.utils.toolkitdir("pinyin", "dictionaries", *components)
    
    def fileSource(path):
        def inner():
            source = dictionarydir(path)
            if os.path.exists(source):
                return path, os.path.getmtime(source), lambda target: shutil.copyfile(source, target)
            else:
                log.info("Missing ordinary file at %s", source)
                return None
        
        return inner
    
    def plainArchiveSource(path, pathinzipcomponents):
        def inner():
            # Ensure that the zip exists before we open it
            zipsource = dictionarydir(path)
            if not(os.path.exists(zipsource)):
                log.info("Missing zip file at %s", zipsource)
                return None
            
            # Load up the zip
            sourcezip = zipfile.ZipFile(zipsource, "r")
            log.info("Available zip file contents %s", sourcezip.namelist())
            
            # Find the correct file in the zip: we should try both sorts of
            # slashes because the zip may have been created on Windows or Unix
            pathinzip = None
            for possiblepath in ["/".join(pathinzipcomponents), "\\".join(pathinzipcomponents)]:
                try:
                    sourcezip.getinfo(possiblepath)
                    pathinzip = possiblepath
                    break
                except KeyError:
                    pass
            
            # Maybe the file didn't exist at all?
            if pathinzip is None:
                log.info("Zip file at %s lacked a file called %s", path, os.path.join(*pathinzipcomponents))
                return None
            
            def go(target):
                # Extract the selected file from the zip
                targetfile = open(target, 'w')
                try:
                    targetfile.write(sourcezip.read(pathinzip))
                finally:
                    targetfile.close()
            
            return path + ":" + pathinzip, os.path.getmtime(zipsource), go
        
        return inner
    
    def findtimestampedfile(pathpattern):
        path, timestamp = None, None
        for file in os.listdir(dictionarydir()):
            # We want to find the file with the maximal timestamp.  Luckily, I have carefully
            # constructed the filenames so that this is just the ordering on the strings
            match = re.match(pathpattern % "(.+)", file)
            if match and match.group(1) > timestamp:
                path, timestamp = match.group(0), match.group(1)
        
        return path, timestamp
    
    def timestampedFileSource(pathpattern):
        def inner():
            path, _timestamp = findtimestampedfile(pathpattern)
            
            if path is None:
                log.info("Missing file matching the timestamped pattern %s", pathpattern)
                return None
            
            return fileSource(path)()
        
        return inner
    
    def timestampedArchiveSource(pathpattern, pathinzippattern):
        def inner():
            path, timestamp = findtimestampedfile(pathpattern)
            
            if path is None:
                log.info("Missing archive matching the timestamped pattern %s", pathpattern)
                return None
            
            return plainArchiveSource(path, ["%s" in pizp and (pizp % timestamp) or pizp for pizp in pathinzippattern])()
        
        return inner
    
    # NB: because we currently use the first matching source, I've put the timestamped .txt files that
    # come with the Toolkit at the end of the list. This ensures that if we ever implement dictionary
    # download, the resulting .zip files will be used in preference to the .txt files.
    requirements = {
        "cedict_ts.u8" : [fileSource("cedict_ts.u8"),
                          plainArchiveSource("cedict_1_0_ts_utf-8_mdbg.zip", ["cedict_ts.u8"]),
                          timestampedArchiveSource("cedict-%s.zip", ["cedict_ts.u8"]),
                          timestampedFileSource("cedict-%s.txt")],
        "handedict.u8" : [fileSource("handedict_nb.u8"),
                          timestampedArchiveSource("handedict-%s.zip", ["handedict-%s", "handedict_nb.u8"]),
                          timestampedFileSource("handedict-%s.txt")],
        "cfdict.u8"    : [fileSource("cfdict_nb.u8"),
                          timestampedArchiveSource("cfdict-%s.zip", ["cfdict-%s", "cfdict_nb.u8"]),
                          plainArchiveSource("shipped.zip", ["cfdict_nb.u8"]),
                          timestampedFileSource("cfdict-%s.txt")],
        "Unihan.txt"   : [fileSource("Unihan.txt"),
                          plainArchiveSource("Unihan.zip", ["Unihan.txt"])]
      }
    
    maxtimestamp = 0
    satisfiers = []
    for requirement, sources in requirements.items():
        success = False
        for source in sources:
            timestampsatisfier = source()
            if timestampsatisfier:
                satisfiedby, timestamp, satisfier = timestampsatisfier
                log.info("The requirement for %s was satisified by %s (with a timestamp of %d)", requirement, satisfiedby, timestamp)
                
                maxtimestamp = max(maxtimestamp, timestamp)
                satisfiers.append((requirement, satisfier))
                
                success = True
                break
        
        if not(success):
            raise IOError("Couldn't satisfy our need for '%s' during dictionary generation" % requirement)
    
    return maxtimestamp, satisfiers
Example #51
0
 def __del__(self):
     try:
         log.info("Cleaning up temporary dictionary builder directory")
         shutil.rmtree(self.dictionarydatapath)
     except IOError:
         pass