mirror of
https://github.com/ankitects/anki.git
synced 2025-11-10 14:47:12 -05:00
Merge branch 'master' of https://github.com/dae/anki
This commit is contained in:
commit
2339f4bafc
17 changed files with 137 additions and 56 deletions
|
|
@ -2,12 +2,16 @@
|
||||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import os, time
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pysqlite2 import dbapi2 as sqlite
|
from pysqlite2 import dbapi2 as sqlite
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from sqlite3 import dbapi2 as sqlite
|
from sqlite3 import dbapi2 as sqlite
|
||||||
|
|
||||||
|
Error = sqlite.Error
|
||||||
|
|
||||||
class DB(object):
|
class DB(object):
|
||||||
def __init__(self, path, text=None, timeout=0):
|
def __init__(self, path, text=None, timeout=0):
|
||||||
encpath = path
|
encpath = path
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,12 @@
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import sre_constants
|
||||||
|
|
||||||
from anki.utils import ids2str, splitFields, joinFields, intTime, fieldChecksum, stripHTMLMedia
|
from anki.utils import ids2str, splitFields, joinFields, intTime, fieldChecksum, stripHTMLMedia
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
from anki.hooks import *
|
from anki.hooks import *
|
||||||
import sre_constants
|
|
||||||
|
|
||||||
# Find
|
# Find
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
@ -257,7 +259,9 @@ select distinct(n.id) from cards c, notes n where c.nid=n.id and """+preds
|
||||||
return "queue in (1, 3)"
|
return "queue in (1, 3)"
|
||||||
return "type = %d" % n
|
return "type = %d" % n
|
||||||
elif val == "suspended":
|
elif val == "suspended":
|
||||||
return "c.queue in (-1, -2)"
|
return "c.queue = -1"
|
||||||
|
elif val == "buried":
|
||||||
|
return "c.queue = -2"
|
||||||
elif val == "due":
|
elif val == "due":
|
||||||
return """
|
return """
|
||||||
(c.queue in (2,3) and c.due <= %d) or
|
(c.queue in (2,3) and c.due <= %d) or
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,10 @@ class MediaManager(object):
|
||||||
except OSError:
|
except OSError:
|
||||||
# cwd doesn't exist
|
# cwd doesn't exist
|
||||||
self._oldcwd = None
|
self._oldcwd = None
|
||||||
|
try:
|
||||||
os.chdir(self._dir)
|
os.chdir(self._dir)
|
||||||
|
except OSError:
|
||||||
|
raise Exception("invalidTempFolder")
|
||||||
# change database
|
# change database
|
||||||
self.connect()
|
self.connect()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -945,7 +945,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
|
||||||
def _fillDyn(self, deck):
|
def _fillDyn(self, deck):
|
||||||
search, limit, order = deck['terms'][0]
|
search, limit, order = deck['terms'][0]
|
||||||
orderlimit = self._dynOrder(order, limit)
|
orderlimit = self._dynOrder(order, limit)
|
||||||
search += " -is:suspended -deck:filtered"
|
search += " -is:suspended -is:buried -deck:filtered"
|
||||||
try:
|
try:
|
||||||
ids = self.col.findCards(search, order=orderlimit)
|
ids = self.col.findCards(search, order=orderlimit)
|
||||||
except:
|
except:
|
||||||
|
|
@ -1266,16 +1266,19 @@ To study outside of the normal schedule, click the Custom Study button below."""
|
||||||
"where queue = -1 and id in "+ ids2str(ids),
|
"where queue = -1 and id in "+ ids2str(ids),
|
||||||
intTime(), self.col.usn())
|
intTime(), self.col.usn())
|
||||||
|
|
||||||
def buryNote(self, nid):
|
def buryCards(self, cids):
|
||||||
"Bury all cards for note until next session."
|
|
||||||
cids = self.col.db.list(
|
|
||||||
"select id from cards where nid = ? and queue >= 0", nid)
|
|
||||||
self.col.log(cids)
|
self.col.log(cids)
|
||||||
self.removeLrn(cids)
|
self.removeLrn(cids)
|
||||||
self.col.db.execute("""
|
self.col.db.execute("""
|
||||||
update cards set queue=-2,mod=?,usn=? where id in """+ids2str(cids),
|
update cards set queue=-2,mod=?,usn=? where id in """+ids2str(cids),
|
||||||
intTime(), self.col.usn())
|
intTime(), self.col.usn())
|
||||||
|
|
||||||
|
def buryNote(self, nid):
|
||||||
|
"Bury all cards for note until next session."
|
||||||
|
cids = self.col.db.list(
|
||||||
|
"select id from cards where nid = ? and queue >= 0", nid)
|
||||||
|
self.buryCards(cids)
|
||||||
|
|
||||||
# Sibling spacing
|
# Sibling spacing
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import anki
|
||||||
HTTP_TIMEOUT = 90
|
HTTP_TIMEOUT = 90
|
||||||
HTTP_PROXY = None
|
HTTP_PROXY = None
|
||||||
|
|
||||||
# badly named; means no retries, and doesn't affect ssl connections
|
# badly named; means no retries
|
||||||
httplib2.RETRIES = 1
|
httplib2.RETRIES = 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import getpass
|
import getpass
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import optparse
|
||||||
|
import tempfile
|
||||||
|
import __builtin__
|
||||||
|
import locale
|
||||||
|
import gettext
|
||||||
|
|
||||||
import os, sys, optparse, atexit, tempfile, __builtin__
|
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
import locale, gettext
|
|
||||||
import anki.lang
|
import anki.lang
|
||||||
from anki.consts import HELP_SITE
|
from anki.consts import HELP_SITE
|
||||||
from anki.lang import langDir
|
from anki.lang import langDir
|
||||||
|
|
@ -157,6 +162,7 @@ class AnkiApp(QApplication):
|
||||||
sys.stderr.write(sock.errorString())
|
sys.stderr.write(sock.errorString())
|
||||||
return
|
return
|
||||||
buf = sock.readAll()
|
buf = sock.readAll()
|
||||||
|
buf = unicode(buf, sys.getfilesystemencoding(), "ignore")
|
||||||
self.emit(SIGNAL("appMsg"), buf)
|
self.emit(SIGNAL("appMsg"), buf)
|
||||||
sock.disconnectFromServer()
|
sock.disconnectFromServer()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ Montague, Michael Penkov, Michal Čadil, Morteza Salehi, Nathanael Law, Nick Coo
|
||||||
Laxström, Nguyễn Hào Khôi, Norbert Nagold, Ole Guldberg,
|
Laxström, Nguyễn Hào Khôi, Norbert Nagold, Ole Guldberg,
|
||||||
Pcsl88, Petr Michalec, Piotr Kubowicz, Richard Colley, Roland Sieker,
|
Pcsl88, Petr Michalec, Piotr Kubowicz, Richard Colley, Roland Sieker,
|
||||||
Samson Melamed, Stefaan De Pooter, Silja Ijas, Snezana Lukic, Susanna Björverud, Sylvain Durand,
|
Samson Melamed, Stefaan De Pooter, Silja Ijas, Snezana Lukic, Susanna Björverud, Sylvain Durand,
|
||||||
Tacutu, Timm Preetz, Timo Paulssen, Ursus, Victor Suba, Xtru %s 黃文龍
|
Tacutu, Timm Preetz, Timo Paulssen, Ursus, Victor Suba, Volodymyr Goncharenko, Xtru %s 黃文龍
|
||||||
"""% _("<!--about diag--> and")}
|
"""% _("<!--about diag--> and")}
|
||||||
abouttext += '<p>' + _("""\
|
abouttext += '<p>' + _("""\
|
||||||
The icons were obtained from various sources; please see the Anki source
|
The icons were obtained from various sources; please see the Anki source
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,20 @@
|
||||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from aqt.qt import *
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from aqt.qt import *
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
import aqt
|
import aqt
|
||||||
from anki.sound import playFromText, clearAudioQueue
|
from anki.sound import playFromText, clearAudioQueue
|
||||||
from aqt.utils import saveGeom, restoreGeom, getBase, mungeQA,\
|
from aqt.utils import saveGeom, restoreGeom, getBase, mungeQA,\
|
||||||
showInfo, askUser, getOnlyText, \
|
showInfo, askUser, getOnlyText, \
|
||||||
showWarning, openHelp, openLink
|
showWarning, openHelp
|
||||||
from anki.utils import isMac, isWin, joinFields
|
from anki.utils import isMac, isWin, joinFields
|
||||||
from aqt.webview import AnkiWebView
|
from aqt.webview import AnkiWebView
|
||||||
import anki.js
|
import anki.js
|
||||||
|
|
||||||
|
|
||||||
class CardLayout(QDialog):
|
class CardLayout(QDialog):
|
||||||
|
|
||||||
def __init__(self, mw, note, ord=0, parent=None, addMode=False):
|
def __init__(self, mw, note, ord=0, parent=None, addMode=False):
|
||||||
|
|
@ -135,7 +137,7 @@ class CardLayout(QDialog):
|
||||||
return showInfo(_("At least one card type is required."))
|
return showInfo(_("At least one card type is required."))
|
||||||
cards = self.mm.tmplUseCount(self.model, idx)
|
cards = self.mm.tmplUseCount(self.model, idx)
|
||||||
cards = ngettext("%d card", "%d cards", cards) % cards
|
cards = ngettext("%d card", "%d cards", cards) % cards
|
||||||
msg = _("Delete the '%(a)s' card type, and its %(b)s?" %
|
msg = (_("Delete the '%(a)s' card type, and its %(b)s?") %
|
||||||
dict(a=self.model['tmpls'][idx]['name'], b=cards))
|
dict(a=self.model['tmpls'][idx]['name'], b=cards))
|
||||||
if not askUser(msg):
|
if not askUser(msg):
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -779,7 +779,7 @@ to a cloze type first, via Edit>Change Note Type."""))
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
showWarning(_(
|
showWarning(_(
|
||||||
"Couldn't record audio. Have you installed lame and sox?") +
|
"Couldn't record audio. Have you installed lame and sox?") +
|
||||||
"\n\n" + unicode(e))
|
"\n\n" + repr(str(e)))
|
||||||
return
|
return
|
||||||
self.addMedia(file)
|
self.addMedia(file)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from aqt.qt import *
|
|
||||||
import sys
|
import sys
|
||||||
import cgi
|
import cgi
|
||||||
|
|
||||||
|
from anki.lang import _
|
||||||
|
from aqt.qt import *
|
||||||
from aqt.utils import showText, showWarning
|
from aqt.utils import showText, showWarning
|
||||||
|
|
||||||
class ErrorHandler(QObject):
|
class ErrorHandler(QObject):
|
||||||
|
|
@ -43,6 +44,12 @@ class ErrorHandler(QObject):
|
||||||
self.timer.setSingleShot(True)
|
self.timer.setSingleShot(True)
|
||||||
self.timer.start()
|
self.timer.start()
|
||||||
|
|
||||||
|
def tempFolderMsg(self):
|
||||||
|
return _("""\
|
||||||
|
The permissions on your system's temporary folder are incorrect, and Anki is \
|
||||||
|
not able to correct them automatically. Please search for 'temp folder' in the \
|
||||||
|
Anki manual for more information.""")
|
||||||
|
|
||||||
def onTimeout(self):
|
def onTimeout(self):
|
||||||
error = cgi.escape(self.pool)
|
error = cgi.escape(self.pool)
|
||||||
self.pool = ""
|
self.pool = ""
|
||||||
|
|
@ -56,6 +63,8 @@ class ErrorHandler(QObject):
|
||||||
if "no default output" in error:
|
if "no default output" in error:
|
||||||
return showWarning(_("Please connect a microphone, and ensure "
|
return showWarning(_("Please connect a microphone, and ensure "
|
||||||
"other programs are not using the audio device."))
|
"other programs are not using the audio device."))
|
||||||
|
if "invalidTempFolder" in error:
|
||||||
|
return showWarning(self.tempFolderMsg())
|
||||||
stdText = _("""\
|
stdText = _("""\
|
||||||
An error occurred. It may have been caused by a harmless bug, <br>
|
An error occurred. It may have been caused by a harmless bug, <br>
|
||||||
or your deck may have a problem.
|
or your deck may have a problem.
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,22 @@
|
||||||
|
# coding=utf-8
|
||||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import os, re, traceback, zipfile, json
|
import os
|
||||||
|
import re
|
||||||
|
import traceback
|
||||||
|
import zipfile
|
||||||
|
import json
|
||||||
|
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
import anki.importing as importing
|
import anki.importing as importing
|
||||||
from aqt.utils import getOnlyText, getFile, showText, showWarning, openHelp,\
|
from aqt.utils import getOnlyText, getFile, showText, showWarning, openHelp,\
|
||||||
askUser, tooltip
|
askUser, tooltip
|
||||||
from anki.hooks import addHook, remHook
|
from anki.hooks import addHook, remHook
|
||||||
import aqt.forms, aqt.modelchooser, aqt.deckchooser
|
import aqt.forms
|
||||||
|
import aqt.modelchooser
|
||||||
|
import aqt.deckchooser
|
||||||
|
|
||||||
|
|
||||||
class ChangeMap(QDialog):
|
class ChangeMap(QDialog):
|
||||||
def __init__(self, mw, model, current):
|
def __init__(self, mw, model, current):
|
||||||
|
|
@ -155,7 +164,7 @@ you can enter it here. Use \\t to represent tab."""),
|
||||||
return
|
return
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
msg = _("Import failed.\n")
|
msg = _("Import failed.\n")
|
||||||
err = unicode(e)
|
err = repr(str(e))
|
||||||
if "1-character string" in err:
|
if "1-character string" in err:
|
||||||
msg += err
|
msg += err
|
||||||
else:
|
else:
|
||||||
|
|
@ -283,7 +292,7 @@ def importFile(mw, file):
|
||||||
showUnicodeWarning()
|
showUnicodeWarning()
|
||||||
return
|
return
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
msg = unicode(e)
|
msg = repr(str(e))
|
||||||
if msg == "unknownFormat":
|
if msg == "unknownFormat":
|
||||||
if file.endswith(".anki2"):
|
if file.endswith(".anki2"):
|
||||||
showWarning(_("""\
|
showWarning(_("""\
|
||||||
|
|
@ -322,11 +331,12 @@ failed. Please try again, and if the problem persists, please try again \
|
||||||
with a different browser.""")
|
with a different browser.""")
|
||||||
showWarning(msg)
|
showWarning(msg)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
if "invalidFile" in unicode(e):
|
err = repr(str(e))
|
||||||
|
if "invalidFile" in err:
|
||||||
msg = _("""\
|
msg = _("""\
|
||||||
Invalid file. Please restore from backup.""")
|
Invalid file. Please restore from backup.""")
|
||||||
showWarning(msg)
|
showWarning(msg)
|
||||||
elif "readonly" in unicode(e):
|
elif "readonly" in err:
|
||||||
showWarning(_("""\
|
showWarning(_("""\
|
||||||
Unable to import from a read-only file."""))
|
Unable to import from a read-only file."""))
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
53
aqt/main.py
53
aqt/main.py
|
|
@ -24,7 +24,7 @@ import aqt.stats
|
||||||
from aqt.utils import restoreGeom, showInfo, showWarning,\
|
from aqt.utils import restoreGeom, showInfo, showWarning,\
|
||||||
restoreState, getOnlyText, askUser, applyStyles, showText, tooltip, \
|
restoreState, getOnlyText, askUser, applyStyles, showText, tooltip, \
|
||||||
openHelp, openLink, checkInvalidFilename
|
openHelp, openLink, checkInvalidFilename
|
||||||
|
import anki.db
|
||||||
|
|
||||||
class AnkiQt(QMainWindow):
|
class AnkiQt(QMainWindow):
|
||||||
def __init__(self, app, profileManager, args):
|
def __init__(self, app, profileManager, args):
|
||||||
|
|
@ -64,7 +64,7 @@ class AnkiQt(QMainWindow):
|
||||||
"syncing and add-on loading."))
|
"syncing and add-on loading."))
|
||||||
# were we given a file to import?
|
# were we given a file to import?
|
||||||
if args and args[0]:
|
if args and args[0]:
|
||||||
self.onAppMsg(unicode(args[0], "utf8", "ignore"))
|
self.onAppMsg(unicode(args[0], sys.getfilesystemencoding(), "ignore"))
|
||||||
# Load profile in a timer so we can let the window finish init and not
|
# Load profile in a timer so we can let the window finish init and not
|
||||||
# close on profile load error.
|
# close on profile load error.
|
||||||
self.progress.timer(10, self.setupProfile, False)
|
self.progress.timer(10, self.setupProfile, False)
|
||||||
|
|
@ -229,6 +229,11 @@ Are you sure?""")):
|
||||||
self.activateWindow()
|
self.activateWindow()
|
||||||
self.raise_()
|
self.raise_()
|
||||||
# maybe sync (will load DB)
|
# maybe sync (will load DB)
|
||||||
|
if self.pendingImport and os.path.basename(
|
||||||
|
self.pendingImport).startswith("backup-"):
|
||||||
|
# skip sync when importing a backup
|
||||||
|
self.loadCollection()
|
||||||
|
else:
|
||||||
self.onSync(auto=True)
|
self.onSync(auto=True)
|
||||||
# import pending?
|
# import pending?
|
||||||
if self.pendingImport:
|
if self.pendingImport:
|
||||||
|
|
@ -265,13 +270,22 @@ To import into a password protected profile, please open the profile before atte
|
||||||
self.hideSchemaMsg = True
|
self.hideSchemaMsg = True
|
||||||
try:
|
try:
|
||||||
self.col = Collection(self.pm.collectionPath())
|
self.col = Collection(self.pm.collectionPath())
|
||||||
except:
|
except anki.db.Error:
|
||||||
# move back to profile manager
|
# move back to profile manager
|
||||||
showWarning("""\
|
showWarning("""\
|
||||||
Your collection is corrupt. Please see the manual for \
|
Your collection is corrupt. Please see the manual for \
|
||||||
how to restore from a backup.""")
|
how to restore from a backup.""")
|
||||||
self.unloadProfile()
|
self.unloadProfile()
|
||||||
raise
|
raise
|
||||||
|
except Exception, e:
|
||||||
|
# the custom exception handler won't catch this if we immediately
|
||||||
|
# unload, so we have to manually handle it
|
||||||
|
if "invalidTempFolder" in repr(str(e)):
|
||||||
|
showWarning(self.errorHandler.tempFolderMsg())
|
||||||
|
self.unloadProfile()
|
||||||
|
return
|
||||||
|
self.unloadProfile()
|
||||||
|
raise
|
||||||
self.hideSchemaMsg = False
|
self.hideSchemaMsg = False
|
||||||
self.progress.setupDB(self.col.db)
|
self.progress.setupDB(self.col.db)
|
||||||
self.maybeEnableUndo()
|
self.maybeEnableUndo()
|
||||||
|
|
@ -290,6 +304,9 @@ how to restore from a backup.""")
|
||||||
return
|
return
|
||||||
self.maybeOptimize()
|
self.maybeOptimize()
|
||||||
self.progress.start(immediate=True)
|
self.progress.start(immediate=True)
|
||||||
|
if os.getenv("ANKIDEV", 0):
|
||||||
|
corrupt = False
|
||||||
|
else:
|
||||||
corrupt = self.col.db.scalar("pragma integrity_check") != "ok"
|
corrupt = self.col.db.scalar("pragma integrity_check") != "ok"
|
||||||
if corrupt:
|
if corrupt:
|
||||||
showWarning(_("Your collection file appears to be corrupt. \
|
showWarning(_("Your collection file appears to be corrupt. \
|
||||||
|
|
@ -309,7 +326,7 @@ the manual for information on how to restore from an automatic backup."))
|
||||||
|
|
||||||
def backup(self):
|
def backup(self):
|
||||||
nbacks = self.pm.profile['numBackups']
|
nbacks = self.pm.profile['numBackups']
|
||||||
if not nbacks:
|
if not nbacks or os.getenv("ANKIDEV", 0):
|
||||||
return
|
return
|
||||||
dir = self.pm.backupFolder()
|
dir = self.pm.backupFolder()
|
||||||
path = self.pm.collectionPath()
|
path = self.pm.collectionPath()
|
||||||
|
|
@ -808,16 +825,24 @@ title="%s">%s</button>''' % (
|
||||||
def newMsg(self, data):
|
def newMsg(self, data):
|
||||||
aqt.update.showMessages(self, data)
|
aqt.update.showMessages(self, data)
|
||||||
|
|
||||||
def clockIsOff(self):
|
def clockIsOff(self, diff):
|
||||||
showWarning("""\
|
diffText = ngettext("%s second", "%s seconds", diff)
|
||||||
|
warn = _("""\
|
||||||
In order to ensure your collection works correctly when moved between \
|
In order to ensure your collection works correctly when moved between \
|
||||||
devices, Anki requires the system clock to be set correctly. Your system \
|
devices, Anki requires your computer's internal clock to be set correctly. \
|
||||||
clock appears to be wrong by more than 5 minutes.
|
The internal clock can be wrong even if your system is showing the correct \
|
||||||
|
local time.
|
||||||
|
|
||||||
This can be because the \
|
Please go to the time settings on your computer and check the following:
|
||||||
clock is slow or fast, because the date is set incorrectly, or because \
|
|
||||||
the timezone or daylight savings information is incorrect. Please correct \
|
- AM/PM
|
||||||
the problem and restart Anki.""")
|
- Clock drift
|
||||||
|
- Day, month and year
|
||||||
|
- Timezone
|
||||||
|
- Daylight savings
|
||||||
|
|
||||||
|
Difference to correct time: %s.""") % diffText
|
||||||
|
showWarning(warn)
|
||||||
self.app.closeAllWindows()
|
self.app.closeAllWindows()
|
||||||
|
|
||||||
# Count refreshing
|
# Count refreshing
|
||||||
|
|
@ -1106,10 +1131,6 @@ will be lost. Continue?"""))
|
||||||
self.connect(self.app, SIGNAL("appMsg"), self.onAppMsg)
|
self.connect(self.app, SIGNAL("appMsg"), self.onAppMsg)
|
||||||
|
|
||||||
def onAppMsg(self, buf):
|
def onAppMsg(self, buf):
|
||||||
if not isinstance(buf, unicode):
|
|
||||||
# even though we're sending this as unicode up above,
|
|
||||||
# a bug report still came in that we were receiving a qbytearray
|
|
||||||
buf = unicode(buf, "utf8", "ignore")
|
|
||||||
if self.state == "startup":
|
if self.state == "startup":
|
||||||
# try again in a second
|
# try again in a second
|
||||||
return self.progress.timer(1000, lambda: self.onAppMsg(buf), False)
|
return self.progress.timer(1000, lambda: self.onAppMsg(buf), False)
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,12 @@
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
import difflib, re, cgi
|
import difflib
|
||||||
|
import re
|
||||||
|
import cgi
|
||||||
import unicodedata as ucd
|
import unicodedata as ucd
|
||||||
import HTMLParser
|
import HTMLParser
|
||||||
|
|
||||||
from anki.lang import _, ngettext
|
from anki.lang import _, ngettext
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from anki.utils import stripHTML, isMac, json
|
from anki.utils import stripHTML, isMac, json
|
||||||
|
|
@ -15,6 +18,7 @@ from aqt.utils import mungeQA, getBase, openLink, tooltip, askUserDialog
|
||||||
from aqt.sound import getAudio
|
from aqt.sound import getAudio
|
||||||
import aqt
|
import aqt
|
||||||
|
|
||||||
|
|
||||||
class Reviewer(object):
|
class Reviewer(object):
|
||||||
"Manage reviews. Maintains a separate state."
|
"Manage reviews. Maintains a separate state."
|
||||||
|
|
||||||
|
|
@ -285,8 +289,10 @@ The front of this card is empty. Please run Tools>Empty Cards.""")
|
||||||
self.replayAudio()
|
self.replayAudio()
|
||||||
elif key == "*":
|
elif key == "*":
|
||||||
self.onMark()
|
self.onMark()
|
||||||
elif key == "-":
|
elif key == "=":
|
||||||
self.onBuryNote()
|
self.onBuryNote()
|
||||||
|
elif key == "-":
|
||||||
|
self.onBuryCard()
|
||||||
elif key == "!":
|
elif key == "!":
|
||||||
self.onSuspend()
|
self.onSuspend()
|
||||||
elif key == "@":
|
elif key == "@":
|
||||||
|
|
@ -678,7 +684,8 @@ function showAnswer(txt) {
|
||||||
def showContextMenu(self):
|
def showContextMenu(self):
|
||||||
opts = [
|
opts = [
|
||||||
[_("Mark Note"), "*", self.onMark],
|
[_("Mark Note"), "*", self.onMark],
|
||||||
[_("Bury Note"), "-", self.onBuryNote],
|
[_("Bury Card"), "-", self.onBuryCard],
|
||||||
|
[_("Bury Note"), "=", self.onBuryNote],
|
||||||
[_("Suspend Card"), "@", self.onSuspendCard],
|
[_("Suspend Card"), "@", self.onSuspendCard],
|
||||||
[_("Suspend Note"), "!", self.onSuspend],
|
[_("Suspend Note"), "!", self.onSuspend],
|
||||||
[_("Delete Note"), "Delete", self.onDelete],
|
[_("Delete Note"), "Delete", self.onDelete],
|
||||||
|
|
@ -740,6 +747,12 @@ function showAnswer(txt) {
|
||||||
"Note and its %d cards deleted.",
|
"Note and its %d cards deleted.",
|
||||||
cnt) % cnt)
|
cnt) % cnt)
|
||||||
|
|
||||||
|
def onBuryCard(self):
|
||||||
|
self.mw.checkpoint(_("Bury"))
|
||||||
|
self.mw.col.sched.buryCards([self.card.id])
|
||||||
|
self.mw.reset()
|
||||||
|
tooltip(_("Card buried."))
|
||||||
|
|
||||||
def onBuryNote(self):
|
def onBuryNote(self):
|
||||||
self.mw.checkpoint(_("Bury"))
|
self.mw.checkpoint(_("Bury"))
|
||||||
self.mw.col.sched.buryNote(self.card.nid)
|
self.mw.col.sched.buryNote(self.card.nid)
|
||||||
|
|
|
||||||
|
|
@ -275,6 +275,10 @@ class SyncThread(QThread):
|
||||||
self.media = media
|
self.media = media
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
# init this first so an early crash doesn't cause an error
|
||||||
|
# in the main thread
|
||||||
|
self.syncMsg = ""
|
||||||
|
self.uname = ""
|
||||||
try:
|
try:
|
||||||
self.col = Collection(self.path)
|
self.col = Collection(self.path)
|
||||||
except:
|
except:
|
||||||
|
|
@ -282,8 +286,6 @@ class SyncThread(QThread):
|
||||||
return
|
return
|
||||||
self.server = RemoteServer(self.hkey)
|
self.server = RemoteServer(self.hkey)
|
||||||
self.client = Syncer(self.col, self.server)
|
self.client = Syncer(self.col, self.server)
|
||||||
self.syncMsg = ""
|
|
||||||
self.uname = ""
|
|
||||||
self.sentTotal = 0
|
self.sentTotal = 0
|
||||||
self.recvTotal = 0
|
self.recvTotal = 0
|
||||||
# throttle updates; qt doesn't handle lots of posted events well
|
# throttle updates; qt doesn't handle lots of posted events well
|
||||||
|
|
@ -447,7 +449,7 @@ httplib.HTTPConnection.send = _incrementalSend
|
||||||
|
|
||||||
# receiving in httplib2
|
# receiving in httplib2
|
||||||
def _conn_request(self, conn, request_uri, method, body, headers):
|
def _conn_request(self, conn, request_uri, method, body, headers):
|
||||||
for i in range(2):
|
for i in range(httplib2.RETRIES):
|
||||||
try:
|
try:
|
||||||
if conn.sock is None:
|
if conn.sock is None:
|
||||||
conn.connect()
|
conn.connect()
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
import urllib
|
||||||
|
import urllib2
|
||||||
|
import time
|
||||||
|
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
import urllib, urllib2, time
|
|
||||||
import aqt
|
import aqt
|
||||||
import platform
|
|
||||||
from aqt.utils import openLink
|
from aqt.utils import openLink
|
||||||
from anki.utils import json, isWin, isMac, platDesc
|
from anki.utils import json, platDesc
|
||||||
from aqt.utils import showText
|
from aqt.utils import showText
|
||||||
|
|
||||||
|
|
||||||
class LatestVersionFinder(QThread):
|
class LatestVersionFinder(QThread):
|
||||||
|
|
||||||
def __init__(self, main):
|
def __init__(self, main):
|
||||||
|
|
@ -45,7 +48,7 @@ class LatestVersionFinder(QThread):
|
||||||
self.emit(SIGNAL("newVerAvail"), resp['ver'])
|
self.emit(SIGNAL("newVerAvail"), resp['ver'])
|
||||||
diff = resp['time'] - time.time()
|
diff = resp['time'] - time.time()
|
||||||
if abs(diff) > 300:
|
if abs(diff) > 300:
|
||||||
self.emit(SIGNAL("clockIsOff"))
|
self.emit(SIGNAL("clockIsOff"), diff)
|
||||||
|
|
||||||
def askAndUpdate(mw, ver):
|
def askAndUpdate(mw, ver):
|
||||||
baseStr = (
|
baseStr = (
|
||||||
|
|
|
||||||
|
|
@ -259,16 +259,16 @@ class UpgradeThread(QThread):
|
||||||
try:
|
try:
|
||||||
self.maybeCopyFromCustomFolder(path)
|
self.maybeCopyFromCustomFolder(path)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
imp.log.append(unicode(e))
|
imp.log.append(repr(str(e)))
|
||||||
# then run the import
|
# then run the import
|
||||||
try:
|
try:
|
||||||
imp.run()
|
imp.run()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
if unicode(e) == "invalidFile":
|
if repr(str(e)) == "invalidFile":
|
||||||
# already logged
|
# already logged
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
imp.log.append(unicode(e))
|
imp.log.append(repr(str(e)))
|
||||||
self.col.save()
|
self.col.save()
|
||||||
return imp.log
|
return imp.log
|
||||||
|
|
||||||
|
|
|
||||||
1
thirdparty/send2trash/plat_win.py
vendored
1
thirdparty/send2trash/plat_win.py
vendored
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
from ctypes import windll, Structure, byref, c_uint
|
from ctypes import windll, Structure, byref, c_uint
|
||||||
from ctypes.wintypes import HWND, UINT, LPCWSTR, BOOL
|
from ctypes.wintypes import HWND, UINT, LPCWSTR, BOOL
|
||||||
|
import os
|
||||||
import os.path as op
|
import os.path as op
|
||||||
|
|
||||||
shell32 = windll.shell32
|
shell32 = windll.shell32
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue