def open_load_file_dialog(browser):
    nids = browser.selectedNotes()
    if nids:
        try:
            ext = ".csv"
            default_path = QStandardPaths.writableLocation(
                QStandardPaths.DocumentsLocation)
            path = os.path.join(default_path, f"changes{ext}")

            options = QFileDialog.Options()

            # native doesn't seem to works
            options |= QFileDialog.DontUseNativeDialog

            result = QFileDialog.getOpenFileName(browser,
                                                 "Import CSV for Batch Update",
                                                 path,
                                                 f"CSV (*{ext})",
                                                 options=options)

            if not isinstance(result, tuple):
                raise Exception("Expected a tuple from save dialog")
            file = result[0]
            if file:
                BatchUpdateDialog(browser, nids, file).exec_()

        except Exception as e:
            tooltip("Failed: {}".format(e))
    else:
        tooltip("You must select some cards first")
 def add_default_path(self):
     dir = QFileDialog.getExistingDirectory(None,
                                            'Anki Add-on Select a folder:',
                                            os.path.expanduser("~"),
                                            QFileDialog.ShowDirsOnly)
     if dir:
         self.dialog.lw_paths.addItems([dir])
Example #3
0
def getSaveFile(parent, title, dir_description, key, ext, fname=None):
    """Ask the user for a file to save. Use DIR_DESCRIPTION as config
    variable. The file dialog will default to open with FNAME."""
    config_key = dir_description + 'Directory'

    defaultPath = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation)
    base = aqt.mw.pm.profile.get(config_key, defaultPath)
    path = os.path.join(base, fname)
    file = QFileDialog.getSaveFileName(
        parent, title, path, "{0} (*{1})".format(key, ext),
        options=QFileDialog.DontConfirmOverwrite)[0]
    if file:
        # add extension
        if not file.lower().endswith(ext):
            file += ext
        # save new default
        dir = os.path.dirname(file)
        aqt.mw.pm.profile[config_key] = dir
        # check if it exists
        if os.path.exists(file):
            if not askUser(
                _("This file exists. Are you sure you want to overwrite it?"),
                parent):
                return None
    return file
Example #4
0
    def savepng(self):
        fileName = QFileDialog.getSaveFileName(self.win, "Save Page", QStandardPaths.standardLocations(QStandardPaths.DesktopLocation)[0], "Portable Network Graphics (*.png)")[0]
        if fileName != "":
            mw.progress.start(immediate=True)
            if ".png" not in fileName:
                fileName += ".png"

            oldsize = self.wv.size()
            self.wv.resize(self.wv.page().contentsSize().toSize())
            # the file will be saved after the page gets redrawn (KanjiGridWebView.eventFilter)
            self.wv.save_png = (fileName, oldsize)
Example #5
0
 def savehtml(self, config):
     fileName = QFileDialog.getSaveFileName(self.win, "Save Page", QStandardPaths.standardLocations(QStandardPaths.DesktopLocation)[0], "Web Page (*.html *.htm)")[0]
     if fileName != "":
         mw.progress.start(immediate=True)
         if ".htm" not in fileName:
             fileName += ".html"
         with open(fileName, 'w', encoding='utf-8') as fileOut:
             self.time = time.time()
             self.timepoint("HTML start")
             units = self.kanjigrid(config)
             self.generate(config, units, True)
             fileOut.write(self.html)
         mw.progress.finish()
         showInfo("Page saved to %s!" % os.path.abspath(fileOut.name))
Example #6
0
def promot_choose_css():
    for local_service in service_manager.local_services:
        try:
            missed_css = local_service.missed_css.pop()
            showInfo(
                u'MDX dictonary <b>{dict}</b> misses css file <b>{css}</b>. <br />Please choose the file.'.format(dict=local_service.title, css=missed_css))
            filepath = QFileDialog.getOpenFileName(
                caption=u'Choose css file', filter=u'CSS (*.css)')
            if filepath:
                shutil.copy(filepath, u'_' + missed_css)
                wrap_css(u'_' + missed_css)
                local_service.missed_css.clear()

        except KeyError as e:
            pass
Example #7
0
    def savepng(self):
        fileName = QFileDialog.getSaveFileName(
            self.win, "Save Page",
            QStandardPaths.standardLocations(
                QStandardPaths.DesktopLocation)[0],
            "Portable Network Graphics (*.png)")[0]
        if fileName != "":
            mw.progress.start(immediate=True)
            if ".png" not in fileName:
                fileName += ".png"

            oldsize = self.wv.size()
            self.wv.resize(self.wv.page().contentsSize().toSize())
            # the file will be saved after the page gets redrawn (KanjiGridWebView.eventFilter)
            self.wv.save_png = (fileName, oldsize)
Example #8
0
def promot_choose_css():
    for local_service in service_manager.local_services:
        try:
            missed_css = local_service.missed_css.pop()
            showInfo(Template.miss_css.format(
                dict=local_service.title, css=missed_css))
            filepath = QFileDialog.getOpenFileName(
                caption=u'Choose css file', filter=u'CSS (*.css)')[0]
            if filepath:
                shutil.copy(filepath, u'_' + missed_css)
                wrap_css(u'_' + missed_css)
                local_service.missed_css.clear()

        except KeyError as e:
            pass
Example #9
0
 def savehtml(self, config):
     fileName = QFileDialog.getSaveFileName(
         self.win, "Save Page",
         QStandardPaths.standardLocations(
             QStandardPaths.DesktopLocation)[0],
         "Web Page (*.html *.htm)")[0]
     if fileName != "":
         mw.progress.start(immediate=True)
         if ".htm" not in fileName:
             fileName += ".html"
         with open(fileName, 'w', encoding='utf-8') as fileOut:
             (units, timeNow) = self.kanjigrid(config)
             self.generate(config, units, timeNow, True)
             fileOut.write(self.html)
         mw.progress.finish()
         showInfo("Page saved to %s!" % os.path.abspath(fileOut.name))
Example #10
0
 def savejson(self, config, units):
     fileName = QFileDialog.getSaveFileName(
         self.win, "Save Page",
         QStandardPaths.standardLocations(
             QStandardPaths.DesktopLocation)[0], "JSON (*.json)")[0]
     if fileName != "":
         mw.progress.start(immediate=True)
         if ".json" not in fileName:
             fileName += ".json"
         with open(fileName, 'w', encoding='utf-8') as fileOut:
             self.time = time.time()
             self.timepoint("JSON start")
             self.generatejson(config, units)
             fileOut.write(self.json)
         mw.progress.finish()
         showInfo("JSON saved to %s!" % os.path.abspath(fileOut.name))
Example #11
0
    def browse_for_wiki(self):
        "Use a file browser dialog to replace the path to a wiki."
        dlg = QFileDialog(self,
                          caption="Browse for wiki",
                          filter="HTML files (*.html);;All files (*)")
        if self.form.type_.currentText().lower() == 'folder':
            mode = QFileDialog.Directory
        else:
            mode = QFileDialog.ExistingFile
        dlg.setFileMode(mode)

        retval = dlg.exec()
        if retval != 0:
            filename = dlg.selectedFiles()[0]
            self.form.path_.setText(filename)
Example #12
0
def on_save_selected_imgs(browser):
    mw = browser.mw
    nids = browser.selectedNotes()
    imgs = set()
    if not nids:
        tooltip("No cards selected.")
        return
    mw.progress.start(max=len(nids), min=0, immediate=True)
    for nid in nids:
        note = mw.col.getNote(nid)
        for field in note.values():
            image_filenames = re.findall(IMG_TAG_RE, field)
            imgs.update(image_filenames)
        mw.progress.update()
    mw.progress.finish()
    savefilename = QFileDialog.getSaveFileName(caption="Save image filenames",
                                               directory="image_filenames.txt")
    with open(savefilename[0], "w") as f:
        for imgname in imgs:
            f.write("%s\n" % imgname)
    tooltip("Saved {} images from {} notes".format(len(imgs), len(nids)))
Example #13
0
    def onDiff(self):
        """Produces HTML diff of the updates that would be made"""
        append_to_log = self.log.appendPlainText

        lines = []
        try:
            self.log.clear()
            nids = self.nids
            field_name = self.field_selection.currentText()

            cnt = 0
            need_clean = 0
            failed_notes = []
            for nid in nids:
                note = self.browser.mw.col.getNote(nid)
                if field_name in note:
                    content = note[field_name]
                    try:
                        cleaned_content = self.clean_content(
                            content, output_html_diff=True)
                        if content != cleaned_content:
                            lines.append((nid, cleaned_content))
                            need_clean += 1
                    except TextProcessingError as e:
                        failed_notes.append((nid, content, str(e)))
                    cnt += 1
            if failed_notes:
                append_to_log(
                    "Found {} notes that failed to be processed:".format(
                        len(failed_notes)))
                for nid, _, exc in failed_notes:
                    append_to_log("{}: {}\n".format(nid, exc))
            append_to_log(
                "Checked {} notes. Found {} notes need updating.".format(
                    cnt, need_clean))
            if failed_notes:
                append_to_log(
                    "Found {} notes that failed to be processed".format(
                        len(failed_notes)))

            if len(lines) > 0:
                ext = ".html"
                default_path = QStandardPaths.writableLocation(
                    QStandardPaths.DocumentsLocation)
                path = os.path.join(default_path, f"diff{ext}")

                options = QFileDialog.Options()

                # native doesn't seem to works
                options |= QFileDialog.DontUseNativeDialog

                # we'll confirm ourselves
                options |= QFileDialog.DontConfirmOverwrite

                result = QFileDialog.getSaveFileName(self,
                                                     "Save HTML diff",
                                                     path,
                                                     f"HTML (*{ext})",
                                                     options=options)

                if not isinstance(result, tuple):
                    raise Exception("Expected a tuple from save dialog")
                file = result[0]
                if file:
                    do_save = True
                    if not file.lower().endswith(ext):
                        file += ext
                    if os.path.exists(file):
                        if not askUser(
                                "{} already exists. Are you sure you want to overwrite it?"
                                .format(file),
                                parent=self):
                            do_save = False
                    if do_save:
                        append_to_log("Saving to {}".format(file))
                        with open(file, "w", encoding="utf-8") as outf:
                            outf.write(DIFF_PRE)
                            for nid, line in lines:
                                outf.write("<p>nid {}:</p>\n".format(nid))
                                outf.write("<p>{}</p>\n".format(line))
                            outf.write(DIFF_POST)
                        append_to_log("Done")

            # Ensure QPlainTextEdit refreshes (not clear why this is necessary)
            self.log.repaint()

        except Exception:
            append_to_log("Failed while checking notes:\n{}".format(
                traceback.format_exc()))
Example #14
0
    def onExport(self):
        append_to_log = self.log.appendPlainText

        if not self.has_records:
            tooltip("Log is empty")
            return

        try:
            ext = ".csv"
            default_path = QStandardPaths.writableLocation(
                QStandardPaths.DocumentsLocation)
            path = os.path.join(default_path, f"changes{ext}")

            options = QFileDialog.Options()

            # native doesn't seem to works
            options |= QFileDialog.DontUseNativeDialog

            # we'll confirm ourselves
            options |= QFileDialog.DontConfirmOverwrite

            result = QFileDialog.getSaveFileName(self,
                                                 "Save CSV",
                                                 path,
                                                 f"CSV (*{ext})",
                                                 options=options)

            if not isinstance(result, tuple):
                raise Exception("Expected a tuple from save dialog")
            file = result[0]
            if file:
                do_save = True
                if not file.lower().endswith(ext):
                    file += ext
                if os.path.exists(file):
                    if not askUser(
                            "{} already exists. Are you sure you want to overwrite it?"
                            .format(file),
                            parent=self):
                        do_save = False
                if do_save:
                    append_to_log("Saving to {}".format(file))
                    with open(file, "w", encoding="utf-8") as outf:
                        field_names = ["ts", "op", "nid", "fld", "old", "new"]
                        writer = csv.DictWriter(outf, fieldnames=field_names)
                        writer.writeheader()
                        for rec in self.changelog.db.all("""
                                select op, ts, nid, fld, old, new from changelog
                                order by ts asc
                                """):
                            op, ts, nid, fld, old, new = rec
                            writer.writerow({
                                "op": op,
                                "ts": ts,
                                "nid": nid,
                                "fld": fld,
                                "old": old,
                                "new": new
                            })

                    append_to_log("Done")
        except Exception:
            append_to_log("Failed while writing CSV:\n{}".format(
                traceback.format_exc()))
Example #15
0
def getFile(parent, title, cb, filter="*.*", dir=None, key=None, multi=False):
    "Ask the user for a file."
    assert not dir or not key
    if not dir:
        dirkey = key+"Directory"
        dir = aqt.mw.pm.profile.get(dirkey, "")
    else:
        dirkey = None
    d = QFileDialog(parent)
    mode = QFileDialog.ExistingFiles if multi else QFileDialog.ExistingFile
    d.setFileMode(mode)
    if os.path.exists(dir):
        d.setDirectory(dir)
    d.setWindowTitle(title)
    d.setNameFilter(filter)
    ret = []
    def accept():
        files = list(d.selectedFiles())
        if dirkey:
            dir = os.path.dirname(files[0])
            aqt.mw.pm.profile[dirkey] = dir
        result = files if multi else files[0]
        if cb:
            cb(result)
        ret.append(result)
    d.accepted.connect(accept)
    if key:
        restoreState(d, key)
    d.exec_()
    if key:
        saveState(d, key)
    return ret and ret[0]
 def pickcmdpath(self):
     # mod of aqt.utils getFile
     d = QFileDialog()
     mode = QFileDialog.ExistingFile
     d.setFileMode(mode)
     if isWin:
         d.setDirectory("C:\\")
     if isMac:
         d.setDirectory("/Applications")
     else:
         d.setDirectory(os.path.expanduser("~"))
     d.setWindowTitle("Anki Add-on: Select Executable")
     if d.exec():
         self.dialog.le_cmd.setText(d.selectedFiles()[0])
Example #17
0
def select_adhoc_script():
    dlg = QFileDialog()
    dlg.setAcceptMode(QFileDialog.AcceptOpen)
    dlg.setFileMode(QFileDialog.Directory)
    if dlg.exec():
        Parser.parse(dlg.selectedFiles()[0], AdHocExecution())
    def onCheck(self, *, mode):
        self.log.clear()
        try:
            # Mapping from field name in file to field combo boxes for notes.
            # We need to check each of the selections for the combo boxes.
            zipped_fields = zip(
                [fn for fn in self.file_field_names if fn != "nid"],
                self.mapping_field_selections)

            # mapping from file join key name to note join key name
            file_join_key_name = self.file_join_key_selection.currentText()
            note_join_key_name = self.note_join_key_selection.currentText()

            self.log.appendPlainText(
                "Join key: File field '{}' -> Note field '{}'".format(
                    file_join_key_name, note_join_key_name))

            # Check which of the field combo boxes having a non-nothing selection and
            # build the mapping from fields in file to fields in notes.
            file_to_note_mappings = {}
            for file_field_name, note_field_cb in zipped_fields:
                note_field_name = note_field_cb.currentText()
                if note_field_name != NOTHING_VALUE:
                    file_to_note_mappings[file_field_name] = note_field_name
                    self.log.appendPlainText(
                        "File field '{}' -> Note field '{}'".format(
                            file_field_name, note_field_name))
            if not file_to_note_mappings:
                self.log.appendPlainText("ERROR: No mappings selected")
                return

            # Check which key values exist and to make sure there are no duplicate values.
            # Build a mapping form these key values to the row which contains the field values.
            file_key_to_values = {}
            duplicate_file_key_values = set()
            with open(self.file, encoding="utf-8") as inf:
                reader = csv.DictReader(inf)
                for row in reader:
                    join_key_val = row[file_join_key_name]
                    if join_key_val in file_key_to_values:
                        duplicate_file_key_values.add(join_key_val)
                    else:
                        file_key_to_values[join_key_val] = row
            if duplicate_file_key_values:
                self.log.appendPlainText(
                    "ERROR: Found {} key values for '{}' that appear more than once:"
                    .format(len(duplicate_file_key_values),
                            file_join_key_name))
                for val in duplicate_file_key_values:
                    self.log.appendPlainText(val)
                return
            self.log.appendPlainText("Found {} records for '{}' in {}".format(
                len(file_key_to_values), file_join_key_name, self.file))

            # When we aren't joining by nid, we need to create an additional mapping from the join
            # key to the nid value, because we can only look up by nid.
            note_join_key_to_nid = {}
            if note_join_key_name != "nid":
                self.log.appendPlainText(
                    "Joining to notes by '{}', so finding all values.".format(
                        note_join_key_name))
                for nid in self.nids:
                    note = self.browser.mw.col.getNote(nid)
                    if note.mid != self.model_id:
                        self.log.appendPlainText(
                            "ERROR: Note {} has different model ID {} than expected {} based on first note. "
                            .format(nid, note.mid, self.model_id) +
                            "Please only select notes of the same model.")
                        return
                    if note_join_key_name in note:
                        if note[note_join_key_name] in note_join_key_to_nid:
                            self.log.appendPlainText(
                                "ERROR: Value '{}' already exists in notes".
                                format(note[note_join_key_name]))
                            return
                        else:
                            note_join_key_to_nid[
                                note[note_join_key_name]] = nid
                    else:
                        self.log.appendPlainText(
                            "ERROR: Field '{}' not found in note {}".format(
                                note_join_key_name, nid))
                        return

            # these store the changes we will propose to make (grouped by nid)
            note_changes = defaultdict(list)

            # track join keys that were not found in notes
            missing_note_keys = set()

            # how many fields being updated are empty
            empty_note_field_count = 0
            notes_with_empty_fields = set()

            for file_key, file_values in file_key_to_values.items():

                if note_join_key_name == "nid":
                    nid = file_key
                else:
                    if file_key in note_join_key_to_nid:
                        nid = note_join_key_to_nid[file_key]
                        self.log.appendPlainText(
                            "Found note {} with value {} for '{}'".format(
                                nid, file_key, note_join_key_name))
                    else:
                        self.log.appendPlainText(
                            "Could not find note with value {} for '{}'".
                            format(file_key, note_join_key_name))
                        missing_note_keys.add(file_key)
                        continue

                self.log.appendPlainText("Checking note {}".format(nid))

                try:
                    note = self.browser.mw.col.getNote(nid)
                except TypeError:
                    self.log.appendPlainText(
                        "ERROR: Note {} was not found".format(nid))
                    return

                # Get the current values for fields we're updating in the note.
                note_values = {}
                for note_field_name in file_to_note_mappings.values():
                    if note_field_name in note:
                        note_values[note_field_name] = note[note_field_name]
                    else:
                        self.log.appendPlainText(
                            "ERROR: Field '{}' not found in note {}".format(
                                note_field_name, nid))
                        return

                # Compare the file field values to the note field values and see if anything is different
                # and therefore needs to be updated.
                for file_field_name, note_field_name in file_to_note_mappings.items(
                ):
                    file_value = file_values[file_field_name]
                    note_value = note_values[note_field_name]
                    if file_value != note_value:
                        self.log.appendPlainText(
                            "Need to update note field '{}':".format(
                                note_field_name))
                        self.log.appendPlainText("{}\n=>\n{}".format(
                            note_value or "<empty>", file_value))
                        note_changes[nid].append(
                            NoteChange(nid=nid,
                                       fld=note_field_name,
                                       old=note_value,
                                       new=file_value))
                        if not note_value:
                            empty_note_field_count += 1
                            notes_with_empty_fields.add(nid)

            if missing_note_keys:
                self.log.appendPlainText(
                    "ERROR: {} values were not found in notes for field '{}'".
                    format(len(missing_note_keys), note_join_key_name))
                return

            if note_changes:
                self.log.appendPlainText(
                    "Need to make changes to {} notes".format(
                        len(note_changes)))
                if empty_note_field_count:
                    self.log.appendPlainText(
                        "{} fields across {} notes are empty".format(
                            empty_note_field_count,
                            len(notes_with_empty_fields)))

                if mode == "dryrun":
                    # nothing to do
                    pass
                elif mode == "diff":
                    ext = ".html"
                    default_path = QStandardPaths.writableLocation(
                        QStandardPaths.DocumentsLocation)
                    path = os.path.join(default_path, f"diff{ext}")

                    options = QFileDialog.Options()

                    # native doesn't seem to works
                    options |= QFileDialog.DontUseNativeDialog

                    # we'll confirm ourselves
                    options |= QFileDialog.DontConfirmOverwrite

                    result = QFileDialog.getSaveFileName(self,
                                                         "Save HTML diff",
                                                         path,
                                                         f"HTML (*{ext})",
                                                         options=options)

                    if not isinstance(result, tuple):
                        raise Exception("Expected a tuple from save dialog")
                    file = result[0]
                    if file:
                        do_save = True
                        if not file.lower().endswith(ext):
                            file += ext
                        if os.path.exists(file):
                            if not askUser(
                                    "{} already exists. Are you sure you want to overwrite it?"
                                    .format(file),
                                    parent=self):
                                do_save = False
                        if do_save:
                            self.log.appendPlainText(
                                "Saving to {}".format(file))
                            with open(file, "w", encoding="utf-8") as outf:
                                outf.write(DIFF_PRE)
                                for nid, changes in note_changes.items():
                                    outf.write("<p>nid {}:</p>\n".format(nid))
                                    for change in changes:
                                        outf.write("<p>{}: {}</p>\n".format(
                                            change.fld,
                                            html_diff(html.escape(change.old),
                                                      html.escape(
                                                          change.new))))
                                outf.write(DIFF_POST)
                            self.log.appendPlainText("Done")
                elif mode == "update":
                    if askUser(
                            "{} notes will be updated.  Are you sure you want to do this?"
                            .format(len(note_changes)),
                            parent=self):
                        self.log.appendPlainText("Beginning update")

                        self.browser.mw.checkpoint("{} ({} {})".format(
                            self.checkpoint_name, len(note_changes),
                            "notes" if len(note_changes) > 1 else "note"))
                        self.browser.model.beginReset()
                        updated_count = 0
                        try:
                            init_ts = int(time.time() * 1000)

                            for nid, changes in note_changes.items():
                                note = self.browser.mw.col.getNote(nid)
                                for change in changes:
                                    ts = int(time.time() * 1000)
                                    note[change.fld] = change.new
                                    self.changelog.record_change(
                                        "batch_update", init_ts,
                                        ChangeLogEntry(ts=ts,
                                                       nid=nid,
                                                       fld=change.fld,
                                                       old=change.old,
                                                       new=change.new))
                                note.flush()
                                updated_count += 1
                            self.log.appendPlainText(
                                "Updated {} notes".format(updated_count))
                        finally:
                            if updated_count:
                                self.changelog.commit_changes()
                                self.browser.mw.requireReset()
                            self.browser.model.endReset()
                else:
                    self.log.appendPlainText(
                        "ERROR: Unexpected mode: {}".format(mode))
                    return
            else:
                self.log.appendPlainText("No changes need to be made")

        except Exception:
            self.log.appendPlainText("Failed during dry run:\n{}".format(
                traceback.format_exc()))

        finally:
            # Ensure QPlainTextEdit refreshes (not clear why this is necessary)
            self.log.repaint()