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())
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()
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)
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")
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()
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)
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()
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"))
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()
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
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)
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
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)
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)
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()
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)
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)
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)
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()
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
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")
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.')
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