Пример #1
0
    def onOpenBackup(self):
        if not askUser(_("""\
Replace your collection with an earlier backup?"""),
                       msgfunc=QMessageBox.warning,
                       defaultno=True):
            return
        def doOpen(path):
            self._openBackup(path)
        getFile(self.profileDiag, _("Revert to backup"),
                cb=doOpen, filter="*.colpkg", dir=self.pm.backupFolder())
Пример #2
0
def import_from_json():
    path = getFile(mw, "Org file to import", cb=None, dir=expanduser("~"))
    if not path:
        return
    with open(path, 'r') as f:
        content = f.read().decode('utf-8')

    entries = json.loads(content)
    import itertools
    get_deck = lambda e: e['deck']
    entries = sorted(entries, key=get_deck)

    mw.checkpoint(_("Import"))
    logs = []
    for deck_name, entries in itertools.groupby(entries, get_deck):
        # FIXME: If required we could group by model name also!
        importer = JsonImporter(mw.col, path, MODEL_NAME, deck_name)
        importer.initMapping()
        importer.run(list(entries))
        if importer.log:
            logs.append('\n'.join(importer.log))

    txt = _("Importing complete.") + "\n"
    txt += '\n'.join(logs)
    showText(txt)
    mw.reset()
Пример #3
0
def onImport(mw):
    filt = ";;".join([x[0] for x in importing.Importers])
    file = getFile(mw, _("Import"), None, key="import",
                   filter=filt)
    if not file:
        return
    file = str(file)
    importFile(mw, file)
Пример #4
0
 def onAddMedia(self):
     key = (_("Media") +
            " (*.jpg *.png *.gif *.tiff *.svg *.tif *.jpeg "+
            "*.mp3 *.ogg *.wav *.avi *.ogv *.mpg *.mpeg *.mov *.mp4 " +
            "*.mkv *.ogx *.ogv *.oga *.flv *.swf *.flac)")
     def accept(file):
         self.addMedia(file, canDelete=True)
     file = getFile(self.widget, _("Add Media"), accept, key, key="media")
Пример #5
0
 def onAddMedia(self):
     key = (_("Media") +
            " (*.jpg *.png *.gif *.tiff *.svg *.tif *.jpeg "+
            "*.mp3 *.ogg *.wav *.avi *.ogv *.mpg *.mpeg *.mov *.mp4 " +
            "*.mkv *.ogx *.ogv *.oga *.flv *.swf *.flac)")
     def accept(file):
         self.addMedia(file)
     file = getFile(self.widget, _("Add Media"), accept, key, key="media")
     self.parentWindow.activateWindow()
Пример #6
0
 def onOpenFile(self):
     """
     Read a text file (in UTF-8 encoding) and replace the contents of the
     poem editor with the contents of the file.
     """
     if (self.form.textBox.toPlainText().strip()
             and not askUser("Importing a file will replace the current "
                             "contents of the poem editor. Continue?")):
         return
     filename = getFile(self, "Import file", None, key="import")
     if not filename: # canceled
         return
     with codecs.open(filename, 'rt', 'utf-8') as f:
         text = f.read()
     self.form.textBox.setPlainText(text)
Пример #7
0
    def onInstallFiles(self, paths=None):
        if not paths:
            key = (_("Packaged Anki Add-on") + " (*{})".format(self.mgr.ext))
            paths = getFile(self, _("Install Add-on(s)"), None, key,
                            key="addons", multi=True)
            if not paths:
                return False
        
        log, errs = self.mgr.processPackages(paths)

        if log:
            tooltip("<br>".join(log), parent=self)
        if errs:
            msg = _("Please report this to the respective add-on author(s).")
            showWarning("\n\n".join(errs + [msg]), parent=self, textFormat="plain")

        self.redrawAddons()
Пример #8
0
    def on_select_mdx_legacy(self, file_path, ignore_selection=False):
        self.mdx = ''
        if file_path and os.path.isfile(file_path):
            self.mdx = file_path
        else:
            if not ignore_selection:
                self.mdx = getFile(self, _trans("MDX TYPE"), lambda x: x, ("MDict (*.MDX)"),
                                   os.path.join(os.path.dirname(__file__),
                                                u"resource") if not self.mdx else os.path.dirname(self.mdx)
                                   )

        if self.mdx and os.path.isfile(self.mdx):
            self.btn_3select_mdx.setText(
                u'%s [%s]' % (_trans("MDX TYPE"),
                              six.ensure_text(os.path.splitext(os.path.basename(self.mdx))[0])))
            self.set_lang_config(mdx_path=six.ensure_text(self.mdx) if self.mdx else u'')
        else:
            self.btn_3select_mdx.setText(_trans("SELECT MDX"))
Пример #9
0
    def onAddMedia(self) -> None:
        extension_filter = " ".join(
            f"*.{extension}"
            for extension in sorted(itertools.chain(pics, audio)))
        filter = f"{tr.editing_media()} ({extension_filter})"

        def accept(file: str) -> None:
            self.addMedia(file)

        file = getFile(
            parent=self.widget,
            title=tr.editing_add_media(),
            cb=cast(Callable[[Any], None], accept),
            filter=filter,
            key="media",
        )

        self.parentWindow.activateWindow()
Пример #10
0
    def onInstallFiles(self,
                       paths: Optional[List[str]] = None) -> Optional[bool]:
        if not paths:
            key = tr(TR.ADDONS_PACKAGED_ANKI_ADDON) + " (*{})".format(
                self.mgr.ext)
            paths = getFile(self,
                            tr(TR.ADDONS_INSTALL_ADDONS),
                            None,
                            key,
                            key="addons",
                            multi=True)
            if not paths:
                return False

        installAddonPackages(self.mgr, paths, parent=self)

        self.redrawAddons()
        return None
Пример #11
0
def onImport(mw):
    filt = ";;".join([x[0] for x in importing.Importers])
    file = getFile(mw, _("Import"), None, key="import",
                   filter=filt)
    if not file:
        return
    file = str(file)

    head, ext = os.path.splitext(file)
    ext = ext.lower()
    if ext == ".anki":
        showInfo(_(".anki files are from a very old version of Anki. You can import them with Anki 2.0, available on the Anki website."))
        return
    elif ext == ".anki2":
        showInfo(_(".anki2 files are not directly importable - please import the .apkg or .zip file you have received instead."))
        return

    importFile(mw, file)
Пример #12
0
def getDBPath():
    global mw
    vocab_path = getKindleVocabPath()
    if vocab_path == "":
        key = "Import"
        dir = None
    else:
        key = None
        dir = vocab_path
    db_path = getFile(mw,
                      _("Select db file"),
                      None,
                      dir=dir,
                      key=key,
                      filter="*.db")
    if not db_path:
        raise IOError
    db_path = str(db_path)
    return db_path
Пример #13
0
def ImportToAnki(model_name, import_to_deck, *args, **kwargs):
    # get file
    file = kwargs.get("file", None)
    if not file:
        file = getFile(mw,
                       _("Import"),
                       None,
                       key="import",
                       filter=Importers[0][0])
    if not file:
        return
    file = str(file)

    # check default model
    try:
        model = mw.col.models.byName(model_name)
        if not model:
            raise Exception("没有找到【{}】".format(model_name))
    except:
        importFile(mw, settings.deck_template_file)
        try:
            model = mw.col.models.byName(model_name)
        except:
            model = None

    importer = TextImporter(mw.col, file)
    importer.delimiter = "\t"
    importer.importMode = 0
    importer.allowHTML = True
    importer.model = model

    did = mw.col.decks.id(import_to_deck)
    mw.col.conf['curDeck'] = did
    importer.model['did'] = did
    mw.col.decks.select(did)
    importer.mapping = [kwargs.get("first")]
    importer.run()
    mw.reset()
    txt = _("Importing complete.") + "\n"
    if importer.log:
        txt += "\n".join(importer.log)
    showText(txt)
Пример #14
0
def onImport(mw):
    filt = ";;".join([x[0] for x in importing.Importers])
    file = getFile(mw, _("Import"), None, key="import", filter=filt)
    if not file:
        return
    file = str(file)

    head, ext = os.path.splitext(file)
    ext = ext.lower()
    if ext == ".anki":
        showInfo(
            _(".anki files are from a very old version of Anki. You can import them with Anki 2.0, available on the Anki website."
              ))
        return
    elif ext == ".anki2":
        showInfo(
            _(".anki2 files are not directly importable - please import the .apkg or .zip file you have received instead."
              ))
        return

    importFile(mw, file)
Пример #15
0
    def onInstallFiles(self, paths=None):
        if not paths:
            key = _("Packaged Anki Add-on") + " (*{})".format(self.mgr.ext)
            paths = getFile(
                self, _("Install Add-on(s)"), None, key, key="addons", multi=True
            )
            if not paths:
                return False

        log, errs = self.mgr.processPackages(paths)

        if log:
            log_html = "<br>".join(log)
            if len(log) == 1:
                tooltip(log_html, parent=self)
            else:
                showInfo(log_html, parent=self, textFormat="rich")
        if errs:
            msg = _("Please report this to the respective add-on author(s).")
            showWarning("<br><br>".join(errs + [msg]), parent=self, textFormat="rich")

        self.redrawAddons()
Пример #16
0
def importMHT():
    # Ask for the .mht file.
    file_path = getFile(mw, _("Import mht file"), None, key="import")
    if not file_path:
        return
    file_path = unicode(file_path)

    # Convert mht
    parser = Parser(file_path)
    output = parser.run()

    # Creates a temp dir instead of file since windows
    # won't allow subprocesses to access it otherwise.
    # https://stackoverflow.com/questions/15169101/how-to-create-a-temporary-file-that-can-be-read-by-a-subprocess
    try:
        temp_dir = mkdtemp()
        path = os.path.join(temp_dir, 'import.html')

        with open(path, 'w+') as html:
            html.write(output)
            # Move temp images to collection.media
            media_dir = os.path.join(mw.pm.profileFolder(), "collection.media")

            for meta in parser.file_map.values():
                temp_path = meta.get('path')
                new_path = os.path.join(media_dir, meta.get('filename'))
                shutil.move(temp_path, new_path)

        # import into the collection
        ti = TextImporter(mw.col, path)
        ti.delimiter = '\t'
        ti.allowHTML = True
        ti.initMapping()
        MHTImportDialog(mw, ti)

        # Remove file
        os.remove(path)
    finally:
        os.rmdir(temp_dir)
Пример #17
0
 def onImport(self):
     o = getFile(self,
                 "Anki - Select file for import ",
                 None,
                 "json",
                 key="json",
                 multi=False)
     if o:
         try:
             with open(o, 'r') as fp:
                 c = json.load(fp)
         except:
             showInfo("Aborting. Error while reading file.")
         try:
             self.config = c
         except:
             showInfo("Aborting. Error in file.")
         self.set_check_state_buttons()
         self.active = self.config["v3"]
         self.inactive = self.config["v3_inactive"]
         self.bo.tw_active.setRowCount(0)
         self.bo.tw_inactive.setRowCount(0)
         self.set_table(self.bo.tw_active, self.active)
         self.set_table(self.bo.tw_inactive, self.inactive)
Пример #18
0
def advimport():
    Log('-'*80)

    filename = getFile(mw, "Select file to import", None, key="import")
    
    if len(filename) == 0:
        showText("invalid filename", mw, type="text", run=True)
        return 
    
    lines = []
    n = 0
    
    with open(filename) as f:
        reader = unicode_csv_reader(f)
        
        for i in range(N_HEADER_LINES):
            n += 1
            reader.next()
            
        for row in reader:
            #print row
            n += 1
            lines.append((n, row))


    for n, line in lines:
        
        #Log("--"*5)

        data = []
        
        _chapt = line[0]
        _sect = line[1]
        _keywords = line[2]
        _question = line[3]
        _solution = line[4]
        
        _type = line[5]
        _subtype = line[6]
        _symb = SYMBOLS.get(_type, "")
        
        _rests = line[7:]
        
        print "L%03i:"%n,
        
        if not _type:
            print "!!! No type, skipping"
            continue


        elif _type == u"rule":
            print "   Rule        ",
            
            model = "Rule"
            key = _question
            
            data = [key, _question, _solution, _chapt, _sect, _type, _symb]


        elif _type == u"pron":
            print "   Pronoun     ",
            
            model = "Simple"
            key = _solution
            
            data = [key, _question, _solution, _chapt, _sect, _type, _symb]


        elif _type == u"wend":
            print "   Sentence    ",
            
            model = "Simple"
            key = _solution
            
            data = [key, _question, _solution, _chapt, _sect, _type, _symb]
            

        elif _type == u"prep":
            print "   Prepos      ",
            
            model = "Simple"
            key = _solution
            
            data = [key, _question, _solution, _chapt, _sect, _type, _symb]


        elif _type == u"adv":
            print "   Adverb      ",
            
            model = "Simple"
            key = _solution
            
            data = [key, _question, _solution, _chapt, _sect, _type, _symb]


        elif _type == u"nom": # Noun
            print "   Noun        ",
            
            model = "Noun"
            
            key = _solution
            
            lst = _solution.split(' ')
            
            art = lst.pop(0)
            noun = " ".join(lst)
            
            
            if not _subtype or _subtype == u"":
                if   art == "el":    _subtype = u"♂"
                elif art == "la":    _subtype = u"♀"
                elif art == "los":   _subtype = u"♂♂/♂♀"
                elif art == "las":   _subtype = u"♀♀"
                elif art == "el/la": _subtype = u"♂/♀"
            elif _subtype[0] in ["F", "f"]: _subtype = u"♀"
            elif _subtype[0] in ["M", "m"]: _subtype = u"♂"
            
            data = [key, _question, _solution, _chapt, _sect, _type, _subtype, _symb]

            
        elif _type == u"verb":
            print "   Verb        ", 
            
            modus = _rests[0]
            temp = _rests[1]
            forms = _rests[2:]
            
            for ii, f in enumerate(forms):
                _ = f.split('|')
                if len(_)==2:
                    for i, (c, e) in enumerate(zip(["stem", "ext"], _)):
                        _[i] = '<span class="%s">%s</span>' % (c, e)
                
                for i, x in enumerate(_):
                    _[i] = _[i].replace("[", '<span class="irr">')
                    _[i] = _[i].replace("]", '</span>')
                    
                forms[ii] = "".join(_)
            
            model = "Verb"
            key = "%s (%s; %s)" % (_solution, modus, temp)
            jsforms = '''{'sg1':'%s','sg2':'%s','sg3':'%s','pl1':'%s','pl2':'%s','pl3':'%s'}''' % tuple(forms)
            #Log("JSF", jsforms)

            _question = _question.replace("[", '<span class="prp">')
            _question = _question.replace("]", '</span>')
            _solution = _solution.replace("[", '<span class="prp">')
            _solution = _solution.replace("]", '</span>')
            
            #print _question

            data = [key, _question, _solution, _chapt, _sect, _type, _subtype, _symb, modus, temp, jsforms]
          
          
        elif _type == u"adj":
            print "   Adjective   ",
            
            s = _solution
            
            def decline(stem, exts=['_o', '_a', '_os', '_as'], wrap=('<b>', '</b>')):
                return [stem+wrap[0]+_+wrap[1] for _ in exts]
            
            if '[' in s:
                _subtype = 'IRR'
                i = s.find('[')
                stem = s[:i]
                exts = s[i+1:s.find(']')].split('|')
                #Log("ir1: ", i, stem, exts, len(exts))
                
                if len(exts)==4:
                    pass
                elif len(exts)==2:
                    exts = [exts[0], exts[0], exts[1], exts[1]]
                elif len(exts)==3:
                    exts = [exts[0], exts[1], exts[2], exts[2]]
                else:
                    #TODO
                    exts = ['???']*4
                    
            elif '|' in s:
                _subtype = 'IRR'
                stem = ''
                exts = s.split('|')

                if len(exts)==4:
                    pass
                elif len(exts)==2:
                    exts = [exts[0], exts[0], exts[1], exts[1]]
                elif len(exts)==3:
                    exts = [exts[0], exts[1], exts[2], exts[2]]
                else:
                    exts = ['???']*4
                
            elif s[-1]=='o':
                _subtype = '-o'
                stem = s[:-1]
                exts = ['_o', '_a', '_os', '_as']

            elif s[-1]=='e':
                _subtype = '-e'
                stem = s[:-1]
                exts = ['e', 'e', '_es', '_es']
                
            elif s[-4:]=='ista':
                _subtype = '-ista'
                stem = s[:-4]
                exts = ['ist_a', 'ist_a', 'ist_as', 'ist_as']
            
            elif s[-2:] == u'ón':
                _subtype = u'-ón'
                stem = s[:-2]
                exts = [u'*ón_', '*on_a', '*on_es', '*on_as']

            elif s[-5:] == 'erior':
                _subtype = '-erior'
                stem = s[:-5]
                exts = [u'erior', 'erior', 'erior_s', 'erior_s']

            elif s[-2:] == u'or':
                _subtype = '-or'
                stem = s[:-2]
                exts = [u'or', 'or_a', 'or_es', 'or_as']
                
            elif s[-1] == 'z':
                _subtype = '-z'
                stem = s[:-1]
                exts = [u'*z', '*z', '*c_es', '*c_es']
                
            else: # consonant at end:
                _subtype = '-CONS'
                stem = s
                exts = ['', '', '_es', '_es']
                print '!!!! >> check this:', stem, exts ,"\n                     ",
                
                
            #decl = decline(stem, exts, wrap=('<span class="ext">', '</span>'))
            decl = decline(stem, exts, wrap=('', ''))
            #decl = [_.replace('_', '') for _ in decl]
            
            for i, d in enumerate(decl):
                while d.find('*')>=0:
                    fi = d.find('*')
                    #print fi, d
                    d = d[:fi] + '<span class="irr">' + d[fi+1] + '</span>' + d[fi+2:]
                if '_' in d:
                    d = d.replace('_', '<span class="ext">') + '</span>'
                decl[i] = d
            #print decl
            
            #Log(stem, exts, decl)
                
            model = "Adjectiv"
            key = stem + exts[0] # use masculine form sg as key
            key = key.replace('*', '').replace('_','')
            jsforms = '''{'MSg':'%s','FSg':'%s','MPl':'%s','FPl':'%s'}''' % tuple(decl)
            
            data = [key, _question, key, _chapt, _sect, _type, _subtype, _symb, jsforms]
        
        
        else:
            print "!!! Unknown type, skipping"
            continue
        
        
        
        if len(data) > 0:
            print data[1], " | ", data[2]
            with codecs.open('multiimport.tsv', 'w', encoding='utf-8') as f:
                #data = [_.encode("utf8") for _ in data]
                s = "\t".join(data)
                #f.write(s.decode("utf8"))
                f.write(s)
                #print s
            
            did = mw.col.decks.byName(deck_name)['id']
            mw.col.decks.select(did)

            m = mw.col.models.byName(model)
            mw.col.conf['curModel'] = m['id']
            cdeck = mw.col.decks.current()
            cdeck['mid'] = m['id']
            mw.col.decks.save(cdeck)
            mw.col.models.setCurrent(m)
            m['did'] = did
            mw.col.models.save(m)
            mw.reset()
            
            ti = TextImporter(mw.col,'multiimport.tsv')
            ti.delimiter = '\t'
            ti.allowHTML = True
            ti.initMapping()
            ti.run()
            #os.remove('multiimport.tsv')

      
    print('-'*80)
Пример #19
0
def _importDeck():
    logs = []
    now = intTime()
    importedN = 0

    deckId = chooseDeck(prompt="Choose deck to import scheduling into")
    if deckId == 0:
        return
    file = getFile(mw, "", None, filter="*.json", key="anki-times")
    if not file:
        return

    data = {}
    with open(file, "r") as f:
        data = json.load(f)

    crt = data["meta"]["crt"]
    cards = data["cards"]

    for key in cards:
        src = cards[key]
        destCids = mw.col.db.list(
            "select distinct(c.id) from cards as c, notes as n "
            "where c.nid=n.id and c.did=? and n.sfld=?", deckId, key)

        # If there are no destination cards, skip
        if not destCids:
            continue
        if len(destCids) > 1:
            logs.append(
                f"Multiple destination cards not supported. Matched field={key}"
            )
            continue
        logs.append(f"Matched card {key}")

        destCid, = destCids

        mw.col.db.execute(
            "update cards set "
            "due=:due, mod=:now, usn=:usn, queue=:queue, lapses=:lapses, "
            "reps=:reps, flags=:flags, "
            "ivl=:ivl, factor=:factor, left=:left, type=:type "
            "where id=:cid",
            cid=destCid,
            now=now,
            usn=mw.col.usn(),
            **src)

        for i in range(0, MAX_RETRIES + 1):
            try:
                if i == 0:
                    importRevlogs(0, destCid, src["revlogs"])
                else:
                    importRevlogs(1 << (i - 1), destCid, src["revlogs"])
                break
            except:
                if i == MAX_RETRIES:
                    raise
        importedN += 1

    logs.append(f"Copied {importedN} cards")

    showText("\n".join(logs), title="Import scheduling info log")
    mw.reset()
Пример #20
0
def wizard(target=""):
    """Assumption: The main config file does not exist. Auto-configure. 
    Returns True if successful and ready to sync.
    target: this parameter is for testing; it overrides the logic for finding a LIFT file."""
    L.debug('Launching the auto-config wizard')

    cfmainpath = SX.get_config_path()
    cfdefpath = SX.get_config_path(SX.CONFIG_DEFAULT_FILE)

    if not os.path.exists(cfdefpath):  # if no default config exists either...
        msg = "Cannot find and copy the default config file:\n  {}\nCannot continue.".format(
            cfdefpath)
        x = dialogbox(msg, ['ok'], CRITICAL)
        return False

    src_dir = SX.get_home_dir_plus(
        os.path.join(SX.get_docs_dir_name(), SX.SRC_DIR_LIFT),
        False)  # use True if creating dict4anki

    flex_dir = F.flex_dir()
    flex_msg = ''
    if flex_dir:
        flex_msg = "  For quickest setup, give it the same name as one of the projects here:\n  {}\n".format(
            flex_dir)


    msg = "Would you like to bring in your own* LIFT data? If so, either...\n" \
    "A) FLEx users, export a LIFT file here (or to a direct subfolder of it):\n" \
    "  {} \n{}" \
    "B) WeSay (or FLEx) users can just click LIFT and choose a LIFT file.\n\n" \
    "A copy of the default configuration file will be auto-configured for you,\n" \
    "  which may take a few seconds. After configuration, the LIFT file to be synced\n" \
    "  must always be located in that same place.\n\n" \
    "Or, click Sample to sync from the sample file instead.\n\n" \
    "*Audio will only be auto-detected if your main writing systems are 2- or 3-letter codes."
    msg = msg.format(src_dir, flex_msg)
    # L.debug("Dialog: {}\n".format(msg))
    x = dialogbox(msg, ['LIFT', 'Sample', 'Cancel'], QUESTION)
    if (x == 'Cancel'): return False

    hourglass()

    # Make sure Anki has the default deck and models already there; else import the APKG file.
    if not ensure_models([X.MODEL1, X.MODEL2]):
        return False
    if (x == 'Sample'):
        # TODO idea: preprocess = (('vern', 'klw'),('other', 'id')) , hard-coding here.
        # After that, the default config file wouldn't need to be hard-coded to specific languages anymore.
        # Note that klw should cover klw-Zxxx-x-audio too, etc.
        try:
            msg = SX.sync(SX.CONFIG_DEFAULT_FILE)  # , preprocess)
            msgbox(msg)
            # launch_paths_maybe()
            return False
        except:
            # launch_paths(suppressExceptions=True)
            raise
        finally:
            no_hourglass()

    hourglass()

    # prepare to copy default config to make a new config (via a temp file first)
    shutil.copy(cfdefpath,
                cfmainpath + '.tmp')  # will overwrite silently if need be

    lift = ''  # was: lift = SX.get_first_lift_file(src_dir) # check the dict4anki folder
    if not lift:  # fall back to anything in a direct subfolder of Documents\WeSay (Linux: ~/WeSay)
        tmp = SX.get_home_dir_plus(
            os.path.join(SX.get_docs_dir_name(), SX.SRC_DIR_WESAY))
        lift = SX.get_first_lift_file(tmp)
    if not lift:  # fall back to anything in a direct subfolder of Documents (Windows: %USERPROFILE%\My Documents; Linux: ~/)
        # src_dir = os.path.split(src_dir)[0]  # remove "/dict4anki/"
        lift = SX.get_first_lift_file(
            SX.get_home_dir_plus(SX.get_docs_dir_name()))
    if lift:
        src_dir = os.path.split(lift)[0]
    if A.IN_ANKI:
        no_hourglass()
        # pop up a File Open dialog using ankilib's convenience method
        lift = getFile(mw,
                       "Open LIFT file",
                       None,
                       filter="*.lift",
                       dir=src_dir,
                       key="")  # "*.lift"
        L.debug("User chose this LIFT file: {}".format(lift))
    elif (not lift) and os.path.exists(TEST_PATH):
        lift = TEST_PATH  # hard-coded test
    if target:
        lift = target  # for testing, a passed parameter overrides all of the above

    L.debug("Using this LIFT file: {}".format(lift))
    if not lift:
        # Still no LIFT file. Fail.
        msg = "No file chosen. Auto-configuration cancelled for now."
        x = dialogbox(msg, ['ok'], CRITICAL)
        return False

    m = "LIFT file: \n  {}\n".format(lift)
    flex_audio, flex_image = None, None

    # Check for WeSay. E.g. if Catalan.lift has a Catalan.WeSayConfig next to it, assume it's a WeSay project
    # Would it be better to make sure it's in the official WeSay directory?
    p, f = os.path.split(lift)
    f = os.path.splitext(f)[0]
    is_wesay = os.path.exists(os.path.join(p, f + ".WeSayConfig"))

    if (not is_wesay) and flex_dir:
        L.debug("Checking for projects in this flex_dir: {}".format(flex_dir))
        tmp = F.flex_media(lift, flex_dir)
        L.debug("Found tmp: {}".format(tmp))
        if tmp:
            flex_audio, flex_image = tmp

    if flex_audio:
        msg = "{}Also found a FLEx project with the same name as your LIFT file and it probably has these media folders:\n" \
          "  {}\n  {}\n" \
          "Shall we sync media files directly from there, so that before each \n" \
          "sync the only thing you'll have to export from FLEx will be the LIFT data?\n" \
          "(If No, the 'audio' and 'pictures' folders in the LIFT file's location will be used.)".format(m, flex_audio, flex_image)
        answer = dialogbox(msg, ['Yes', 'No'], QUESTION)
        if not A.IN_ANKI:
            # answer = 'No'  #Or, put the No button first, then delete this
            pass
        if answer != 'Yes':
            flex_audio, flex_image = None, None  # dump them
    elif not is_wesay:
        msg = "{}Could not find a FLEx project with the same name as your LIFT file.\n" \
          "Do you wish to select a FLEx project that does/will contain your media files? \n" \
          "WeSay users: choose No. FLEx users: choose Yes unless you want to export \n" \
          " the media files along with the LIFT before each sync.".format(m)
        answer = dialogbox(msg, ['Yes', 'No', 'Cancel'], QUESTION)
        if (answer == 'Cancel'): return False
        fwdata = TEST_FWDATA
        if (answer == 'Yes') and (A.IN_ANKI):
            # pop up a File Open dialog using ankilib's convenience method
            fwdata = getFile(mw,
                             "Select FLEx project",
                             None,
                             filter="*.fwdata",
                             key="",
                             dir=flex_dir)
        if fwdata:
            tmp = F.flex_media(fwdata)
            if tmp: flex_audio, flex_image = tmp

    # Note: working with a temp copy of config, so as to not create an official config until we're sure we've succeeded.
    try:
        xset = X.XmlSettings(cfmainpath + '.tmp', lift, flex_audio, flex_image)
    except:
        launch_paths(suppressExceptions=True)
        raise

    if os.path.getsize(lift) > 1000000:
        msg = m + "Your file is large, so analyzing it may take a while. Please click Ok and then wait."
        answer = dialogbox(msg, ['Ok', 'Cancel'], QUESTION)
        if answer == 'Cancel': return

    # status bar (no longer supported by Anki?)


#    from aqt.main import setStatus as set_status
#    mw.setStatus("Analyzing the LIFT file...", timeout=3000)

# Find and Replace WS's in the new config file
    hourglass()
    to_replace = xset.find_vern_nat()
    L.w("For LIFT file\n  ({}) we will now find/replace writing systems in our settings as follows: \n{}"
        .format(lift, X.lang_table(to_replace)))

    # Using regex (on cfmainpath + '.tmp') to replace WSes in our new config file ...
    xset.save()
    tmp = xset.file_path
    X.replace_all(tmp, to_replace)
    xset = None  # since it is now outdated
    # TODO: xset.dispose() # this would help with safety if implemented

    # do a dry run: use the new config file to load the LIFT file...
    try:
        xset = X.XmlSettings(
            tmp, lift, flex_audio, flex_image
        )  # we need those last two parameters or we'll lose any FLEx path we had
    except:
        launch_paths(suppressExceptions=True)
        raise

    xdl = X.XmlDataLoader(
    )  # No try block, since presumably the user knows where the data file is.
    _recs, empties = xdl.load_src_file(xset.get_attr(),
                                       xset.entry,
                                       sync_media=False,
                                       dry_run=True)
    _recs, empties2 = xdl.load_src_file(xset.get_attr(),
                                        xset.example,
                                        sync_media=False,
                                        dry_run=True)
    # empties.append(empties2)

    # ... so we can disable any xpaths that don't match any data.
    if empties:
        L.w("The following entry fields yielded no data and will now be disabled so as to not generate warnings: {}"
            .format(empties))
        xset.entry.disable_fields(empties)
    if empties2:
        L.w("The following example fields yielded no data and will now be disabled so as to not generate warnings: {}"
            .format(empties))
        xset.example.disable_fields(empties2)

    # If no example sentences, we already disabled auto-disabled that section.
    if xset.example.get_attr()['enabled'] == 'true':
        # It's still enabled, which means it has data.
        msg = m + "Found dictionary Examples containing data; when imported, each will show up on the main entry's flashcard.\n" \
              "Will you also need a separate flashcard for each Example?"
        x = dialogbox(msg, ['No', 'Yes', 'Cancel'], QUESTION)
        if (x == "Cancel"):
            return False
        if (x != 'Yes'):
            xset.example.disable()

    hourglass()
    xset.save()

    # rename the default config file (remove the .tmp)
    shutil.move(
        cfmainpath + '.tmp', cfmainpath
    )  # will overwrite silently if need be, but see our initial assumption
    L.debug("Configuration file saved.")
    m2 = "\nReplaced writing systems in our new configuration as follows: \n{}".format(
        X.lang_table(to_replace))
    m3, m5 = '', ''
    if flex_audio:
        m3 = "\nConfigured to copy media files from these locations: \n  {}\n  {}\n".format(
            flex_audio, flex_image)
    m4 = "\nConfiguration file saved. Click Yes to sync now, or No if you wish to review/tweak the configuration first.\n"
    if L.error_count() or L.warn_count():
        m5 = "\nThere were errors or warnings during auto-config. Please review the log."
    msg = m + m2 + m3 + m4 + m5
    # TODO: " or want to run a Sync Preview."
    L.w(msg)
    # msgbox(msg)
    x = dialogbox(msg, ['Yes', 'No'], QUESTION)
    if (x == 'No'):
        return False  # successful config, but don't sync right now
    return True
Пример #21
0
 def _chooseFile(self):
     key = (_("Media") + " (*.jpg *.png *.gif *.tiff *.svg *.tif *.jpeg " +
            "*.mp3 *.ogg *.wav *.avi *.ogv *.mpg *.mpeg *.mov *.mp4 " +
            "*.mkv *.ogx *.ogv *.oga *.flv *.swf *.flac)")
     return getFile(self, _("Add Media"), None, key, key="media")
Пример #22
0
def import_highlights():
    path = getFile(mw,
                   'Open Kindle clippings',
                   cb=None,
                   filter='Clippings file (*.txt *.html)',
                   key='KindleHighlights')

    if not path:
        return

    with open(path, encoding='utf-8') as file:
        lower_path = path.lower()
        if lower_path.endswith('txt'):
            clippings, bad_clippings = parse_text_clippings(file)
        elif lower_path.endswith('html'):
            clippings, bad_clippings = parse_html_clippings(file)
        else:
            raise RuntimeError(f'Unknown extension in path: {path!r}')

    if bad_clippings:
        showText(
            f'The following {len(bad_clippings)} clippings could not be parsed:\n\n'
            + '\n==========\n'.join(bad_clippings))

    config = mw.addonManager.getConfig(__name__)

    highlight_clippings = list(highlights_only(clippings))
    clippings_to_add = after_last_added(highlight_clippings,
                                        last_added_datetime(config))

    num_added = 0
    last_added = None

    user_files_path = os.path.join(mw.addonManager.addonsFolder(__name__),
                                   'user_files')
    os.makedirs(user_files_path, exist_ok=True)

    added_highlights_path = os.path.join(user_files_path,
                                         'added_highlights.json')

    if os.path.isfile(added_highlights_path):
        with open(added_highlights_path,
                  encoding='utf-8') as added_highlights_file:
            added_highlights = json.load(added_highlights_file)
    else:
        added_highlights = []

    note_adder = NoteAdder(mw.col, config, added_highlights)
    for clipping in clippings_to_add:
        note_was_added = note_adder.try_add(clipping)
        if note_was_added:
            num_added += 1
            if clipping.added:
                last_added = clipping.added

    with open(added_highlights_path, encoding='utf-8',
              mode='w') as added_highlights_file:
        json.dump(list(note_adder.added_normalized_contents),
                  added_highlights_file)

    if last_added:
        config['last_added'] = parse_clipping_added(last_added).isoformat()
        mw.addonManager.writeConfig(__name__, config)

    def info():
        if num_added:
            yield f'{num_added} new highlights imported'

        num_duplicates = len(clippings_to_add) - num_added
        if num_duplicates:
            yield f'{num_duplicates} duplicate highlights ignored'

        num_old_highlights = len(highlight_clippings) - len(clippings_to_add)
        if num_old_highlights:
            yield f'{num_old_highlights} old highlights ignored'

        num_not_highlights = len(clippings) - len(highlight_clippings)
        if num_not_highlights:
            yield f'{num_not_highlights} non-highlight clippings ignored'

    info_strings = list(info())
    if info_strings:
        showInfo(', '.join(info_strings) + '.')
    elif bad_clippings:
        showInfo('No other clippings found.')
    else:
        showInfo('No clippings found.')
Пример #23
0
def wizard(target=""):
    """Assumption: The main config file does not exist. Auto-configure. 
    Returns True if successful and ready to sync.
    target: this parameter is for testing; it overrides the logic for finding a LIFT file."""
    L.debug('Launching the auto-config wizard')

    cfmainpath = SX.get_config_path()
    cfdefpath = SX.get_config_path(SX.CONFIG_DEFAULT_FILE)

    if not os.path.exists(cfdefpath):  # if no default config exists either...
        msg = "Cannot find and copy the default config file:\n  {}\nCannot continue.".format(cfdefpath)
        x = dialogbox(msg, ['ok'], CRITICAL)
        return False
    
    src_dir = SX.get_home_dir_plus(os.path.join(SX.get_docs_dir_name(), SX.SRC_DIR_LIFT), False)  # use True if creating dict4anki

    flex_dir = F.flex_dir()
    flex_msg = ''
    if flex_dir:
        flex_msg = "  For quickest setup, give it the same name as one of the projects here:\n  {}\n".format(flex_dir)

     
    msg = "Would you like to bring in your own* LIFT data? If so, either...\n" \
    "A) FLEx users, export a LIFT file here (or to a direct subfolder of it):\n" \
    "  {} \n{}" \
    "B) WeSay (or FLEx) users can just click LIFT and choose a LIFT file.\n\n" \
    "A copy of the default configuration file will be auto-configured for you,\n" \
    "  which may take a few seconds. After configuration, the LIFT file to be synced\n" \
    "  must always be located in that same place.\n\n" \
    "Or, click Sample to sync from the sample file instead.\n\n" \
    "*Audio will only be auto-detected if your main writing systems are 2- or 3-letter codes."
    msg = msg.format(src_dir, flex_msg)
    # L.debug("Dialog: {}\n".format(msg))
    x = dialogbox(msg, ['LIFT', 'Sample', 'Cancel'], QUESTION)
    if (x == 'Cancel'): return False

    hourglass()
    
    # Make sure Anki has the default deck and models already there; else import the APKG file.
    if not ensure_models([X.MODEL1, X.MODEL2]):
        return False
    if (x == 'Sample'):
        # TODO idea: preprocess = (('vern', 'klw'),('other', 'id')) , hard-coding here.
        # After that, the default config file wouldn't need to be hard-coded to specific languages anymore.
        # Note that klw should cover klw-Zxxx-x-audio too, etc.
        try:
            msg = SX.sync(SX.CONFIG_DEFAULT_FILE)  # , preprocess)
            msgbox(msg)
            # launch_paths_maybe()
            return False
        except:
            # launch_paths(suppressExceptions=True)
            raise
        finally:       
            no_hourglass()

    hourglass()

    # prepare to copy default config to make a new config (via a temp file first)
    shutil.copy(cfdefpath, cfmainpath + '.tmp')  # will overwrite silently if need be
    
    lift = ''  # was: lift = SX.get_first_lift_file(src_dir) # check the dict4anki folder
    if not lift:  # fall back to anything in a direct subfolder of Documents\WeSay (Linux: ~/WeSay)
        tmp = SX.get_home_dir_plus(os.path.join(SX.get_docs_dir_name(), SX.SRC_DIR_WESAY))
        lift = SX.get_first_lift_file(tmp)
    if not lift:  # fall back to anything in a direct subfolder of Documents (Windows: %USERPROFILE%\My Documents; Linux: ~/)
        # src_dir = os.path.split(src_dir)[0]  # remove "/dict4anki/"
        lift = SX.get_first_lift_file(SX.get_home_dir_plus(SX.get_docs_dir_name()))
    if lift:
        src_dir = os.path.split(lift)[0]
    if A.IN_ANKI:
        no_hourglass()
        # pop up a File Open dialog using ankilib's convenience method
        lift = getFile(mw, "Open LIFT file", None, filter="*.lift", dir=src_dir, key="")  # "*.lift"
        L.debug("User chose this LIFT file: {}".format(lift))
    elif (not lift) and os.path.exists(TEST_PATH): 
        lift = TEST_PATH  # hard-coded test
    if target:
        lift = target  # for testing, a passed parameter overrides all of the above

    L.debug("Using this LIFT file: {}".format(lift))
    if not lift:   
        # Still no LIFT file. Fail.
        msg = "No file chosen. Auto-configuration cancelled for now." 
        x = dialogbox(msg, ['ok'], CRITICAL)
        return False
    
    m = "LIFT file: \n  {}\n".format(lift)
    flex_audio, flex_image = None, None

    # Check for WeSay. E.g. if Catalan.lift has a Catalan.WeSayConfig next to it, assume it's a WeSay project
    # Would it be better to make sure it's in the official WeSay directory?
    p, f = os.path.split(lift)
    f = os.path.splitext(f)[0]
    is_wesay = os.path.exists(os.path.join (p, f + ".WeSayConfig"))  

    if (not is_wesay) and flex_dir:
        L.debug("Checking for projects in this flex_dir: {}".format(flex_dir))
        tmp = F.flex_media(lift, flex_dir)
        L.debug("Found tmp: {}".format(tmp))
        if tmp: 
            flex_audio, flex_image = tmp

    if flex_audio:
        msg = "{}Also found a FLEx project with the same name as your LIFT file and it probably has these media folders:\n" \
          "  {}\n  {}\n" \
          "Shall we sync media files directly from there, so that before each \n" \
          "sync the only thing you'll have to export from FLEx will be the LIFT data?\n" \
          "(If No, the 'audio' and 'pictures' folders in the LIFT file's location will be used.)".format(m, flex_audio, flex_image)
        answer = dialogbox(msg, ['Yes', 'No'], QUESTION)
        if not A.IN_ANKI:
            # answer = 'No'  #Or, put the No button first, then delete this
            pass
        if answer != 'Yes':
            flex_audio, flex_image = None, None  # dump them
    elif not is_wesay:
        msg = "{}Could not find a FLEx project with the same name as your LIFT file.\n" \
          "Do you wish to select a FLEx project that does/will contain your media files? \n" \
          "WeSay users: choose No. FLEx users: choose Yes unless you want to export \n" \
          " the media files along with the LIFT before each sync.".format(m)
        answer = dialogbox(msg, ['Yes', 'No', 'Cancel'], QUESTION)
        if (answer == 'Cancel'): return False
        fwdata = TEST_FWDATA
        if (answer == 'Yes') and (A.IN_ANKI):
            # pop up a File Open dialog using ankilib's convenience method
            fwdata = getFile(mw, "Select FLEx project", None, filter="*.fwdata", key="", dir=flex_dir)
        if fwdata:
            tmp = F.flex_media(fwdata)
            if tmp: flex_audio, flex_image = tmp
        
    # Note: working with a temp copy of config, so as to not create an official config until we're sure we've succeeded.
    try:        
        xset = X.XmlSettings(cfmainpath + '.tmp', lift, flex_audio, flex_image)
    except:
        launch_paths(suppressExceptions=True)
        raise

    if os.path.getsize(lift) > 1000000:
        msg = m + "Your file is large, so analyzing it may take a while. Please click Ok and then wait."
        answer = dialogbox(msg, ['Ok', 'Cancel'], QUESTION)
        if answer == 'Cancel' : return

    # status bar (no longer supported by Anki?)
#    from aqt.main import setStatus as set_status
#    mw.setStatus("Analyzing the LIFT file...", timeout=3000)  

    # Find and Replace WS's in the new config file
    hourglass()
    to_replace = xset.find_vern_nat()
    L.w("For LIFT file\n  ({}) we will now find/replace writing systems in our settings as follows: \n{}".format(lift, X.lang_table(to_replace)))

    # Using regex (on cfmainpath + '.tmp') to replace WSes in our new config file ...
    xset.save() 
    tmp = xset.file_path
    X.replace_all(tmp, to_replace)
    xset = None  # since it is now outdated 
    # TODO: xset.dispose() # this would help with safety if implemented

    # do a dry run: use the new config file to load the LIFT file...
    try:
        xset = X.XmlSettings(tmp, lift, flex_audio, flex_image)  # we need those last two parameters or we'll lose any FLEx path we had
    except:
        launch_paths(suppressExceptions=True)
        raise
        
    xdl = X.XmlDataLoader()  # No try block, since presumably the user knows where the data file is.
    _recs, empties = xdl.load_src_file(xset.get_attr(), xset.entry, sync_media=False, dry_run=True)
    _recs, empties2 = xdl.load_src_file(xset.get_attr(), xset.example, sync_media=False, dry_run=True)
    # empties.append(empties2)

    # ... so we can disable any xpaths that don't match any data.
    if empties:
        L.w("The following entry fields yielded no data and will now be disabled so as to not generate warnings: {}".format(empties))
        xset.entry.disable_fields(empties)
    if empties2:
        L.w("The following example fields yielded no data and will now be disabled so as to not generate warnings: {}".format(empties))
        xset.example.disable_fields(empties2)

    # If no example sentences, we already disabled auto-disabled that section.
    if xset.example.get_attr()['enabled'] == 'true':
        # It's still enabled, which means it has data. 
        msg = m + "Found dictionary Examples containing data; when imported, each will show up on the main entry's flashcard.\n" \
              "Will you also need a separate flashcard for each Example?"
        x = dialogbox(msg, ['No', 'Yes', 'Cancel'], QUESTION)
        if (x == "Cancel"):
            return False
        if (x != 'Yes'): 
            xset.example.disable()

    hourglass()
    xset.save()

    # rename the default config file (remove the .tmp)
    shutil.move(cfmainpath + '.tmp', cfmainpath)  # will overwrite silently if need be, but see our initial assumption
    L.debug("Configuration file saved.")
    m2 = "\nReplaced writing systems in our new configuration as follows: \n{}".format(X.lang_table(to_replace))
    m3, m5 = '', ''
    if flex_audio:
        m3 = "\nConfigured to copy media files from these locations: \n  {}\n  {}\n".format(flex_audio, flex_image)
    m4 = "\nConfiguration file saved. Click Yes to sync now, or No if you wish to review/tweak the configuration first.\n"
    if L.error_count() or L.warn_count():
        m5 = "\nThere were errors or warnings during auto-config. Please review the log."
    msg = m + m2 + m3 + m4 + m5
    # TODO: " or want to run a Sync Preview."
    L.w(msg)
    # msgbox(msg)
    x = dialogbox(msg, ['Yes', 'No'], QUESTION)
    if (x == 'No'): 
        return False  # successful config, but don't sync right now
    return True