Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions BuildZip.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@ECHO OFF
REM A Windows 'build' script.

IF NOT EXIST "C:\Program Files/7-Zip/7z.exe" (
ECHO This script uses 7-Zip to build the .zip file, but 7-Zip does not seem to be installed, so the script will fail.
ECHO The script also assumes that 7-Zip is installed in its default location, C:\Program Files/7-Zip/7z.exe
ECHO So please do not change the default install location of 7-zip when you install it.
EXIT /B 1
)

REM Delete and recreate temporary folder so we don't end up copying old files
RMDIR /S /Q %TEMP%\FlashGrabBuild
MKDIR %TEMP%\FlashGrabBuild

REM Copy files into folder
xcopy /D /E /Y /exclude:ExcludedFilesWindows.txt xml\*.* %TEMP%\FlashGrabBuild\xml\
xcopy /D /E /Y /exclude:ExcludedFilesWindows.txt xpath\*.* %TEMP%\FlashGrabBuild\xpath\
REM No /E for syncxml since we don't want to include docsrc or samples directories
xcopy /D /Y /exclude:ExcludedFilesWindows.txt syncxml\*.* %TEMP%\FlashGrabBuild\syncxml\
REM We do want one file from samples, but it should be in a top-level folder in the zip file
xcopy /D /E /Y /exclude:ExcludedFilesWindows.txt syncxml\samples\lift-dictionary.apkg %TEMP%\FlashGrabBuild\samples\
REM The default config also needs to live in the top-level folder
move %TEMP%\FlashGrabBuild\syncxml\SyncFromXML_config_default.txt %TEMP%\FlashGrabBuild\
xcopy /D /Y /exclude:ExcludedFilesWindows.txt __init__.py %TEMP%\FlashGrabBuild\
xcopy /D /Y /exclude:ExcludedFilesWindows.txt manifest.json %TEMP%\FlashGrabBuild\

PUSHD %TEMP%\FlashGrabBuild
"C:\Program Files/7-Zip/7z.exe" a FlashGrab.zip syncxml samples xml xpath __init__.py manifest.json SyncFromXML_config_default.txt
POPD

MOVE %TEMP%\FlashGrabBuild\FlashGrab.zip .

ECHO FlashGrab.zip file created!
DIR FlashGrab.zip
PAUSE
35 changes: 35 additions & 0 deletions BuildZip.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# A Linux build script

which 7z || (
echo 7zip not found. Please install the 7zip package, e.g. sudo apt install 7zip or sudo pacman -S 7zip
exit 1
)

# Delete and recreate temporary folder so we don't end up copying old files
rm -rf /tmp/FlashGrabBuild
mkdir /tmp/FlashGrabBuild

# Copy files into folder
rsync -r -u -v --exclude-from ExcludedFilesLinux.txt xml /tmp/FlashGrabBuild
rsync -r -u -v --exclude-from ExcludedFilesLinux.txt xpath /tmp/FlashGrabBuild
rsync -r -u -v --exclude-from ExcludedFilesLinux.txt syncxml /tmp/FlashGrabBuild

cp -u -v __init__.py /tmp/FlashGrabBuild
cp -u -v manifest.json /tmp/FlashGrabBuild

# Some files under syncxml need to live in top-level directory
mv /tmp/FlashGrabBuild/syncxml/SyncFromXML_config_default.txt /tmp/FlashGrabBuild
mkdir -p /tmp/FlashGrabBuild/samples
mv /tmp/FlashGrabBuild/syncxml/samples/lift-dictionary.apkg /tmp/FlashGrabBuild/samples

# Rest of samples and docsrc from syncxml dir doesn't need to be in the addon, to save file size
rm -rf /tmp/FlashGrabBuild/syncxml/docsrc
rm -rf /tmp/FlashGrabBuild/syncxml/samples

pushd /tmp/FlashGrabBuild
7z a FlashGrab.zip syncxml samples xml xpath __init__.py manifest.json SyncFromXML_config_default.txt
popd

mv /tmp/FlashGrabBuild/FlashGrab.zip .
ls -l FlashGrab.zip
19 changes: 14 additions & 5 deletions CopyFilesToAddons.bat
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,24 @@ ECHO OFF
REM A Windows 'build' script. Run this after editing the addon's files. Then restart Anki.
REM Don't make edits over there in addons directly, as VCS won't see them, and an Anki or addon upgrade
REM might destroy them.

REM IMPORTANT: If you change the plugin package in manifest.json, make the same change to PLUGIN_NAME below
ECHO ON

SET PLUGIN_NAME=FlashGrab
SET ROOTFOLDER=%APPDATA%\Anki2\addons21\%PLUGIN_NAME%

REM copy all modified (D) files and folders (even E, empty ones), and overwrite (Y) without prompting
REM (Like Anki's own deployment, this does NOT remove any files. The simplest way to do so manually here is to delete the addons folder, then re-run.)

xcopy /D /E /Y /exclude:ExcludedFilesWindows.txt xml\*.* %USERPROFILE%\documents\Anki\addons\xml\
xcopy /D /E /Y /exclude:ExcludedFilesWindows.txt xpath\*.* %USERPROFILE%\documents\Anki\addons\xpath\
xcopy /D /E /Y /exclude:ExcludedFilesWindows.txt distutils\*.* %USERPROFILE%\documents\Anki\addons\distutils\
xcopy /D /E /Y /exclude:ExcludedFilesWindows.txt syncxml\*.* %USERPROFILE%\documents\Anki\addons\syncxml\
xcopy /D /Y syncx.py %USERPROFILE%\documents\Anki\addons\
xcopy /D /E /Y /exclude:ExcludedFilesWindows.txt xml\*.* "%ROOTFOLDER%\xml\"
xcopy /D /E /Y /exclude:ExcludedFilesWindows.txt xpath\*.* "%ROOTFOLDER%\xpath\"
REM No /E for syncxml since we don't want to include docsrc or samples directories
xcopy /D /Y /exclude:ExcludedFilesWindows.txt syncxml\*.* "%ROOTFOLDER%\syncxml\"
REM We do want one file from samples, but it should be in a top-level "samples" folder in the addons directory
xcopy /D /E /Y /exclude:ExcludedFilesWindows.txt syncxml\samples\lift-dictionary.apkg "%ROOTFOLDER%\samples\"
xcopy /D /Y __init__.py "%ROOTFOLDER%\"
xcopy /D /Y manifest.json "%ROOTFOLDER%\"
xcopy /D /Y syncxml\SyncFromXML_config_default.txt "%ROOTFOLDER%\"

PAUSE
30 changes: 23 additions & 7 deletions CopyFilesToAddons.sh
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
#!/usr/bin/env bash

# To run this script from the terminal command line: ./CopyFilesToAddons.sh
# A Linux 'build' script. Run this after editing the addon's files. Then restart Anki.
# A Linux 'build' script. Run this after editing the addon's files. Then restart Anki.
# Don't make edits over there in addons directly, as VCS won't see them, and an Anki or addon upgrade might destroy them.

# IMPORTANT: If you change the plugin package in manifest.json, make the same change to PLUGIN_NAME below
PLUGIN_NAME=FlashGrab

# Copy recursively (-r) all modified files and folders, except the excluded ones
# (Like Anki's own deployment, this does NOT remove any files. The simplest way to do so manually here is to delete the addons folder, then re-run.)

mkdir -p ~/Anki/addons
rsync -r -u -v --exclude-from ExcludedFilesLinux.txt xml ~/Anki/addons/
rsync -r -u -v --exclude-from ExcludedFilesLinux.txt xpath ~/Anki/addons/
rsync -r -u -v --exclude-from ExcludedFilesLinux.txt distutils ~/Anki/addons/
rsync -r -u -v --exclude-from ExcludedFilesLinux.txt syncxml ~/Anki/addons/
cp -r -u -v syncx.py ~/Anki/addons/
# Use XDG_DATA_HOME if present, otherwise default to ~/.local/share
DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
PLUGIN_HOME="${DATA_HOME}/Anki2/addons21/${PLUGIN_NAME}"

mkdir -p "${PLUGIN_HOME}"
rsync -r -u -v --exclude-from ExcludedFilesLinux.txt xml "${PLUGIN_HOME}/"
rsync -r -u -v --exclude-from ExcludedFilesLinux.txt xpath "${PLUGIN_HOME}/"
rsync -r -u -v --exclude-from ExcludedFilesLinux.txt syncxml "${PLUGIN_HOME}/"
# No -r for syncxml since we don't want to include docsrc or samples directories
# syncxml samples dir should be at the top level of the plugin directory and only contain one file
mkdir -p "${PLUGIN_HOME}/samples"
mv "${PLUGIN_HOME}/syncxml/samples/lift-dictionary.apkg" "${PLUGIN_HOME}/samples/"
# Default config should also be in plugin root
mv "${PLUGIN_HOME}/syncxml/SyncFromXML_config_default.txt" "${PLUGIN_HOME}/"
# Rest of samples, as well as docsrc folder, not needed
rm -rf "${PLUGIN_HOME}/syncxml/samples"
rm -rf "${PLUGIN_HOME}/syncxml/docsrc"
cp -u -v __init__.py "${PLUGIN_HOME}/"
cp -u -v manifest.json "${PLUGIN_HOME}/"
3 changes: 2 additions & 1 deletion ExcludedFilesLinux.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
- *.pyc
- __pycache__/
- local_launch.py
- syncx_local.py
- SyncFromXML_config.txt
- SyncFromXML_log.txt
- SyncFromXML_configB*
- SyncFromXML_configB*
3 changes: 2 additions & 1 deletion ExcludedFilesWindows.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.pyc\
\__pycache__\
\local_launch.py\
\syncx_local.py\
\SyncFromXML_config.txt\
\SyncFromXML_log.txt\
\SyncFromXML_configB
\SyncFromXML_configB
4 changes: 4 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@
from the source XML file(s) as specified in the config file.
"""

# Make package-local imports work in Python 3.6 and later
import os, sys
sys.path.append(os.path.dirname(os.path.realpath(__file__)))

from syncxml import SyncFromXML

4 changes: 4 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"package": "FlashGrab",
"name": "FlashGrab: import LIFT XML from FieldWorks or WeSay"
}
16 changes: 13 additions & 3 deletions syncxml/SyncFromXML.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,14 @@
from anki.notes import Note
# from anki.utils import isMac #obsolete now, I think
# from PyQt4.QtGui import QMessageBox #done for us already?
QUESTION = QMessageBox.Question
CRITICAL = QMessageBox.Critical
try:
QUESTION = QMessageBox.Icon.Question
except AttributeError:
QUESTION = QMessageBox.Question
try:
CRITICAL = QMessageBox.Icon.Critical
except AttributeError:
CRITICAL = QMessageBox.Critical
else:
# crash-prevention dummies
QUESTION = 0
Expand All @@ -59,7 +65,11 @@ def dialogbox(text, buttons, icon=4, log=True):

def hourglass():
if A.IN_ANKI:
mw.app.setOverrideCursor(QCursor(Qt.WaitCursor)) # display an hourglass cursor
try:
mw.app.setOverrideCursor(QCursor(Qt.WaitCursor)) # display an hourglass cursor
except AttributeError:
# Qt6 has moved this to CursorShape
mw.app.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor)) # display an hourglass cursor
def no_hourglass():
if A.IN_ANKI:
mw.app.restoreOverrideCursor()
Expand Down
10 changes: 7 additions & 3 deletions syncxml/anki_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@

ANKI_MEDIA_FOLDER = 'collection.media'

ADDON_FOLDER = 'syncxml'
ADDON_FOLDER = 'FlashGrab'
APKG_PATH = os.path.join("samples", "lift-dictionary.apkg")
NO_MODEL_INSTR = "\nSteps: Restart Anki and make sure the target deck exists. Use Tools, Manage Note Types, or go to File Import, and import Anki/addons/syncxml/{}. Or, reconfigure.)".format(APKG_PATH)
NO_MODEL_INSTR = "\nSteps: Restart Anki and make sure the target deck exists. Use Tools, Manage Note Types, or go to File Import, and import Anki/addons/{}/{}. Or, reconfigure.)".format(ADDON_FOLDER, APKG_PATH)
NO_MODEL_MSG = "The target model with the fields we needed was missing; have attempted to re-import the default APKG but an error occurred. \nPlease try again, or create it manually. \n{}".format(NO_MODEL_INSTR)
TARGET_DECK = "lift-dictionary"

Expand Down Expand Up @@ -90,7 +90,11 @@ def import_apkg_model(path, delete=False):
# assert deck.cardCount() > 0
# assert deck.noteCount() > 0
ids = mw.col.findCards("deck:{}".format(TARGET_DECK))
mw.col.remCards(ids, True)
try:
mw.col.remCards(ids, True)
except TypeError:
# Newer versions of Anki renamed remCards and removed the second argument
mw.col.remove_cards_and_orphaned_notes(ids)
# assert deck.cardCount() == 0
# assert deck.noteCount() == 0
except:
Expand Down
57 changes: 27 additions & 30 deletions syncxml/file_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,29 @@

__revision__ = "$Id$"

import os
from distutils.errors import DistutilsFileError
from distutils import log
import os, sys

# for generating verbose output in 'copy_file()'
_copy_action = {None: 'copying',
'hard': 'hard linking',
'sym': 'symbolically linking'}


# from distutils.dep_util import newer
# distutils deprecated in Python 3.12 and later, so reimplement newer() function
def newer(source, target):
if not os.path.exists(source):
raise OSError("file %s does not exist" % (os.path.abspath(source)))
return not os.path.exists(target) or (
os.path.getmtime(source) > os.path.getmtime(target)
)


def _copy_file_contents(src, dst, buffer_size=16*1024):
"""Copy the file 'src' to 'dst'.

Both must be filenames. Any error opening either file, reading from
'src', or writing to 'dst', raises DistutilsFileError. Data is
'src', or writing to 'dst', raises OSError. Data is
read/written in chunks of 'buffer_size' bytes (default 16k). No attempt
is made to handle anything apart from regular files.
"""
Expand All @@ -39,30 +47,27 @@ def _copy_file_contents(src, dst, buffer_size=16*1024):
fsrc = open(src, 'rb')
except os.error as xxx_todo_changeme3:
(errno, errstr) = xxx_todo_changeme3.args
raise DistutilsFileError("could not open '%s': %s" % (src, errstr))
raise OSError("could not open '%s': %s" % (src, errstr))

if os.path.exists(dst):
try:
os.unlink(dst)
except os.error as xxx_todo_changeme:
(errno, errstr) = xxx_todo_changeme.args
raise DistutilsFileError(
"could not delete '%s': %s" % (dst, errstr))
raise OSError("could not delete '%s': %s" % (dst, errstr))

try:
fdst = open(dst, 'wb')
except os.error as xxx_todo_changeme4:
(errno, errstr) = xxx_todo_changeme4.args
raise DistutilsFileError(
"could not create '%s': %s" % (dst, errstr))
raise OSError("could not create '%s': %s" % (dst, errstr))

while 1:
try:
buf = fsrc.read(buffer_size)
except os.error as xxx_todo_changeme1:
(errno, errstr) = xxx_todo_changeme1.args
raise DistutilsFileError(
"could not read from '%s': %s" % (src, errstr))
raise OSError("could not read from '%s': %s" % (src, errstr))

if not buf:
break
Expand All @@ -71,8 +76,7 @@ def _copy_file_contents(src, dst, buffer_size=16*1024):
fdst.write(buf)
except os.error as xxx_todo_changeme2:
(errno, errstr) = xxx_todo_changeme2.args
raise DistutilsFileError(
"could not write to '%s': %s" % (dst, errstr))
raise OSError("could not write to '%s': %s" % (dst, errstr))

finally:
if fdst:
Expand Down Expand Up @@ -113,12 +117,10 @@ def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0,
# changing it (ie. it's not already a hard/soft link to src OR
# (not update) and (src newer than dst).

from distutils.dep_util import newer
from stat import ST_ATIME, ST_MTIME, ST_MODE, S_IMODE

if not os.path.isfile(src):
raise DistutilsFileError(
"can't copy '%s': doesn't exist or not a regular file" % src)
raise OSError("can't copy '%s': doesn't exist or not a regular file" % src)

if os.path.isdir(dst):
dir = dst
Expand All @@ -128,7 +130,7 @@ def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0,

if update and not newer(src, dst):
if verbose >= 1:
log.debug("not copying %s (output up-to-date)", src)
print("not copying %s (output up-to-date)".format(src), file=sys.stderr)
return dst, 0

try:
Expand All @@ -138,9 +140,9 @@ def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0,

if verbose >= 1:
if os.path.basename(dst) == os.path.basename(src):
log.info("%s %s -> %s", action, src, dir)
print("%s %s -> %s".format(action, src, dir))
else:
log.info("%s %s -> %s", action, src, dst)
print("%s %s -> %s".format(action, src, dst))

if dry_run:
return (dst, 1)
Expand Down Expand Up @@ -185,25 +187,21 @@ def move_file (src, dst, verbose=1, dry_run=0):
import errno

if verbose >= 1:
log.info("moving %s -> %s", src, dst)
print("moving %s -> %s".format(src, dst))

if dry_run:
return dst

if not isfile(src):
raise DistutilsFileError("can't move '%s': not a regular file" % src)
raise OSError("can't move '%s': not a regular file" % src)

if isdir(dst):
dst = os.path.join(dst, basename(src))
elif exists(dst):
raise DistutilsFileError(
"can't move '%s': destination '%s' already exists" %
(src, dst))
raise OSError("can't move '%s': destination '%s' already exists" % (src, dst))

if not isdir(dirname(dst)):
raise DistutilsFileError(
"can't move '%s': destination '%s' not a valid path" % \
(src, dst))
raise OSError("can't move '%s': destination '%s' not a valid path" % (src, dst))

copy_it = 0
try:
Expand All @@ -213,8 +211,7 @@ def move_file (src, dst, verbose=1, dry_run=0):
if num == errno.EXDEV:
copy_it = 1
else:
raise DistutilsFileError(
"couldn't move '%s' to '%s': %s" % (src, dst, msg))
raise OSError("couldn't move '%s' to '%s': %s" % (src, dst, msg))

if copy_it:
copy_file(src, dst, verbose=verbose)
Expand All @@ -226,7 +223,7 @@ def move_file (src, dst, verbose=1, dry_run=0):
os.unlink(dst)
except os.error:
pass
raise DistutilsFileError(
raise OSError(
("couldn't move '%s' to '%s' by copy/delete: " +
"delete '%s' failed: %s") %
(src, dst, src, msg))
Expand Down
Loading