This commit is contained in:
Soren I. Bjornstad 2013-10-22 07:34:46 -05:00
commit 2339f4bafc
17 changed files with 137 additions and 56 deletions

View file

@ -2,12 +2,16 @@
# Copyright: Damien Elmes <anki@ichi2.net>
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import os, time
import os
import time
try:
from pysqlite2 import dbapi2 as sqlite
except ImportError:
from sqlite3 import dbapi2 as sqlite
Error = sqlite.Error
class DB(object):
def __init__(self, path, text=None, timeout=0):
encpath = path

View file

@ -3,10 +3,12 @@
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import re
import sre_constants
from anki.utils import ids2str, splitFields, joinFields, intTime, fieldChecksum, stripHTMLMedia
from anki.consts import *
from anki.hooks import *
import sre_constants
# 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 "type = %d" % n
elif val == "suspended":
return "c.queue in (-1, -2)"
return "c.queue = -1"
elif val == "buried":
return "c.queue = -2"
elif val == "due":
return """
(c.queue in (2,3) and c.due <= %d) or

View file

@ -45,7 +45,10 @@ class MediaManager(object):
except OSError:
# cwd doesn't exist
self._oldcwd = None
os.chdir(self._dir)
try:
os.chdir(self._dir)
except OSError:
raise Exception("invalidTempFolder")
# change database
self.connect()

View file

@ -945,7 +945,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
def _fillDyn(self, deck):
search, limit, order = deck['terms'][0]
orderlimit = self._dynOrder(order, limit)
search += " -is:suspended -deck:filtered"
search += " -is:suspended -is:buried -deck:filtered"
try:
ids = self.col.findCards(search, order=orderlimit)
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),
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)
def buryCards(self, cids):
self.col.log(cids)
self.removeLrn(cids)
self.col.db.execute("""
update cards set queue=-2,mod=?,usn=? where id in """+ids2str(cids),
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
##########################################################################

View file

@ -20,7 +20,7 @@ import anki
HTTP_TIMEOUT = 90
HTTP_PROXY = None
# badly named; means no retries, and doesn't affect ssl connections
# badly named; means no retries
httplib2.RETRIES = 1
try:

View file

@ -1,10 +1,15 @@
# Copyright: Damien Elmes <anki@ichi2.net>
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
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 *
import locale, gettext
import anki.lang
from anki.consts import HELP_SITE
from anki.lang import langDir
@ -157,6 +162,7 @@ class AnkiApp(QApplication):
sys.stderr.write(sock.errorString())
return
buf = sock.readAll()
buf = unicode(buf, sys.getfilesystemencoding(), "ignore")
self.emit(SIGNAL("appMsg"), buf)
sock.disconnectFromServer()

View file

@ -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,
Pcsl88, Petr Michalec, Piotr Kubowicz, Richard Colley, Roland Sieker,
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")}
abouttext += '<p>' + _("""\
The icons were obtained from various sources; please see the Anki source

View file

@ -2,18 +2,20 @@
# Copyright: Damien Elmes <anki@ichi2.net>
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from aqt.qt import *
import re
from aqt.qt import *
from anki.consts import *
import aqt
from anki.sound import playFromText, clearAudioQueue
from aqt.utils import saveGeom, restoreGeom, getBase, mungeQA,\
showInfo, askUser, getOnlyText, \
showWarning, openHelp, openLink
showWarning, openHelp
from anki.utils import isMac, isWin, joinFields
from aqt.webview import AnkiWebView
import anki.js
class CardLayout(QDialog):
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."))
cards = self.mm.tmplUseCount(self.model, idx)
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))
if not askUser(msg):
return

View file

@ -779,7 +779,7 @@ to a cloze type first, via Edit>Change Note Type."""))
except Exception, e:
showWarning(_(
"Couldn't record audio. Have you installed lame and sox?") +
"\n\n" + unicode(e))
"\n\n" + repr(str(e)))
return
self.addMedia(file)

View file

@ -1,10 +1,11 @@
# Copyright: Damien Elmes <anki@ichi2.net>
# -*- coding: utf-8 -*-
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from aqt.qt import *
import sys
import cgi
from anki.lang import _
from aqt.qt import *
from aqt.utils import showText, showWarning
class ErrorHandler(QObject):
@ -43,6 +44,12 @@ class ErrorHandler(QObject):
self.timer.setSingleShot(True)
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):
error = cgi.escape(self.pool)
self.pool = ""
@ -56,6 +63,8 @@ class ErrorHandler(QObject):
if "no default output" in error:
return showWarning(_("Please connect a microphone, and ensure "
"other programs are not using the audio device."))
if "invalidTempFolder" in error:
return showWarning(self.tempFolderMsg())
stdText = _("""\
An error occurred. It may have been caused by a harmless bug, <br>
or your deck may have a problem.

View file

@ -1,13 +1,22 @@
# coding=utf-8
# Copyright: Damien Elmes <anki@ichi2.net>
# 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 *
import anki.importing as importing
from aqt.utils import getOnlyText, getFile, showText, showWarning, openHelp,\
askUser, tooltip
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):
def __init__(self, mw, model, current):
@ -155,7 +164,7 @@ you can enter it here. Use \\t to represent tab."""),
return
except Exception, e:
msg = _("Import failed.\n")
err = unicode(e)
err = repr(str(e))
if "1-character string" in err:
msg += err
else:
@ -283,7 +292,7 @@ def importFile(mw, file):
showUnicodeWarning()
return
except Exception, e:
msg = unicode(e)
msg = repr(str(e))
if msg == "unknownFormat":
if file.endswith(".anki2"):
showWarning(_("""\
@ -322,11 +331,12 @@ failed. Please try again, and if the problem persists, please try again \
with a different browser.""")
showWarning(msg)
except Exception, e:
if "invalidFile" in unicode(e):
err = repr(str(e))
if "invalidFile" in err:
msg = _("""\
Invalid file. Please restore from backup.""")
showWarning(msg)
elif "readonly" in unicode(e):
elif "readonly" in err:
showWarning(_("""\
Unable to import from a read-only file."""))
else:

View file

@ -24,7 +24,7 @@ import aqt.stats
from aqt.utils import restoreGeom, showInfo, showWarning,\
restoreState, getOnlyText, askUser, applyStyles, showText, tooltip, \
openHelp, openLink, checkInvalidFilename
import anki.db
class AnkiQt(QMainWindow):
def __init__(self, app, profileManager, args):
@ -64,7 +64,7 @@ class AnkiQt(QMainWindow):
"syncing and add-on loading."))
# were we given a file to import?
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
# close on profile load error.
self.progress.timer(10, self.setupProfile, False)
@ -229,7 +229,12 @@ Are you sure?""")):
self.activateWindow()
self.raise_()
# maybe sync (will load DB)
self.onSync(auto=True)
if self.pendingImport and os.path.basename(
self.pendingImport).startswith("backup-"):
# skip sync when importing a backup
self.loadCollection()
else:
self.onSync(auto=True)
# import pending?
if self.pendingImport:
if self.pm.profile['key']:
@ -265,13 +270,22 @@ To import into a password protected profile, please open the profile before atte
self.hideSchemaMsg = True
try:
self.col = Collection(self.pm.collectionPath())
except:
except anki.db.Error:
# move back to profile manager
showWarning("""\
Your collection is corrupt. Please see the manual for \
how to restore from a backup.""")
self.unloadProfile()
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.progress.setupDB(self.col.db)
self.maybeEnableUndo()
@ -290,7 +304,10 @@ how to restore from a backup.""")
return
self.maybeOptimize()
self.progress.start(immediate=True)
corrupt = self.col.db.scalar("pragma integrity_check") != "ok"
if os.getenv("ANKIDEV", 0):
corrupt = False
else:
corrupt = self.col.db.scalar("pragma integrity_check") != "ok"
if corrupt:
showWarning(_("Your collection file appears to be corrupt. \
This can happen when the file is copied or moved while Anki is open, or \
@ -309,7 +326,7 @@ the manual for information on how to restore from an automatic backup."))
def backup(self):
nbacks = self.pm.profile['numBackups']
if not nbacks:
if not nbacks or os.getenv("ANKIDEV", 0):
return
dir = self.pm.backupFolder()
path = self.pm.collectionPath()
@ -808,16 +825,24 @@ title="%s">%s</button>''' % (
def newMsg(self, data):
aqt.update.showMessages(self, data)
def clockIsOff(self):
showWarning("""\
def clockIsOff(self, diff):
diffText = ngettext("%s second", "%s seconds", diff)
warn = _("""\
In order to ensure your collection works correctly when moved between \
devices, Anki requires the system clock to be set correctly. Your system \
clock appears to be wrong by more than 5 minutes.
devices, Anki requires your computer's internal clock to be set correctly. \
The internal clock can be wrong even if your system is showing the correct \
local time.
This can be because the \
clock is slow or fast, because the date is set incorrectly, or because \
the timezone or daylight savings information is incorrect. Please correct \
the problem and restart Anki.""")
Please go to the time settings on your computer and check the following:
- AM/PM
- Clock drift
- Day, month and year
- Timezone
- Daylight savings
Difference to correct time: %s.""") % diffText
showWarning(warn)
self.app.closeAllWindows()
# Count refreshing
@ -1106,10 +1131,6 @@ will be lost. Continue?"""))
self.connect(self.app, SIGNAL("appMsg"), self.onAppMsg)
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":
# try again in a second
return self.progress.timer(1000, lambda: self.onAppMsg(buf), False)

View file

@ -3,9 +3,12 @@
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from __future__ import division
import difflib, re, cgi
import difflib
import re
import cgi
import unicodedata as ucd
import HTMLParser
from anki.lang import _, ngettext
from aqt.qt import *
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
import aqt
class Reviewer(object):
"Manage reviews. Maintains a separate state."
@ -285,8 +289,10 @@ The front of this card is empty. Please run Tools>Empty Cards.""")
self.replayAudio()
elif key == "*":
self.onMark()
elif key == "-":
elif key == "=":
self.onBuryNote()
elif key == "-":
self.onBuryCard()
elif key == "!":
self.onSuspend()
elif key == "@":
@ -678,7 +684,8 @@ function showAnswer(txt) {
def showContextMenu(self):
opts = [
[_("Mark Note"), "*", self.onMark],
[_("Bury Note"), "-", self.onBuryNote],
[_("Bury Card"), "-", self.onBuryCard],
[_("Bury Note"), "=", self.onBuryNote],
[_("Suspend Card"), "@", self.onSuspendCard],
[_("Suspend Note"), "!", self.onSuspend],
[_("Delete Note"), "Delete", self.onDelete],
@ -740,6 +747,12 @@ function showAnswer(txt) {
"Note and its %d cards deleted.",
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):
self.mw.checkpoint(_("Bury"))
self.mw.col.sched.buryNote(self.card.nid)

View file

@ -275,6 +275,10 @@ class SyncThread(QThread):
self.media = media
def run(self):
# init this first so an early crash doesn't cause an error
# in the main thread
self.syncMsg = ""
self.uname = ""
try:
self.col = Collection(self.path)
except:
@ -282,8 +286,6 @@ class SyncThread(QThread):
return
self.server = RemoteServer(self.hkey)
self.client = Syncer(self.col, self.server)
self.syncMsg = ""
self.uname = ""
self.sentTotal = 0
self.recvTotal = 0
# throttle updates; qt doesn't handle lots of posted events well
@ -447,7 +449,7 @@ httplib.HTTPConnection.send = _incrementalSend
# receiving in httplib2
def _conn_request(self, conn, request_uri, method, body, headers):
for i in range(2):
for i in range(httplib2.RETRIES):
try:
if conn.sock is None:
conn.connect()

View file

@ -1,14 +1,17 @@
# Copyright: Damien Elmes <anki@ichi2.net>
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import urllib
import urllib2
import time
from aqt.qt import *
import urllib, urllib2, time
import aqt
import platform
from aqt.utils import openLink
from anki.utils import json, isWin, isMac, platDesc
from anki.utils import json, platDesc
from aqt.utils import showText
class LatestVersionFinder(QThread):
def __init__(self, main):
@ -45,7 +48,7 @@ class LatestVersionFinder(QThread):
self.emit(SIGNAL("newVerAvail"), resp['ver'])
diff = resp['time'] - time.time()
if abs(diff) > 300:
self.emit(SIGNAL("clockIsOff"))
self.emit(SIGNAL("clockIsOff"), diff)
def askAndUpdate(mw, ver):
baseStr = (

View file

@ -259,16 +259,16 @@ class UpgradeThread(QThread):
try:
self.maybeCopyFromCustomFolder(path)
except Exception, e:
imp.log.append(unicode(e))
imp.log.append(repr(str(e)))
# then run the import
try:
imp.run()
except Exception, e:
if unicode(e) == "invalidFile":
if repr(str(e)) == "invalidFile":
# already logged
pass
else:
imp.log.append(unicode(e))
imp.log.append(repr(str(e)))
self.col.save()
return imp.log

View file

@ -6,6 +6,7 @@
from ctypes import windll, Structure, byref, c_uint
from ctypes.wintypes import HWND, UINT, LPCWSTR, BOOL
import os
import os.path as op
shell32 = windll.shell32