mirror of
https://github.com/ankitects/anki.git
synced 2025-11-10 22:57:11 -05:00
Merge branch 'master' of https://github.com/dae/anki
This commit is contained in:
commit
684df05f51
15 changed files with 103 additions and 77 deletions
|
|
@ -30,6 +30,6 @@ if arch[1] == "ELF":
|
||||||
sys.path.insert(0, os.path.join(ext, "py2.%d-%s" % (
|
sys.path.insert(0, os.path.join(ext, "py2.%d-%s" % (
|
||||||
sys.version_info[1], arch[0][0:2])))
|
sys.version_info[1], arch[0][0:2])))
|
||||||
|
|
||||||
version="2.0.17" # build scripts grep this line, so preserve formatting
|
version="2.0.18" # build scripts grep this line, so preserve formatting
|
||||||
from anki.storage import Collection
|
from anki.storage import Collection
|
||||||
__all__ = ["Collection"]
|
__all__ = ["Collection"]
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,10 @@
|
||||||
import pprint
|
import pprint
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
from anki.hooks import runHook
|
||||||
from anki.utils import intTime, timestampID, joinFields
|
from anki.utils import intTime, timestampID, joinFields
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
|
|
||||||
# temporary
|
|
||||||
_warned = False
|
|
||||||
def warn():
|
|
||||||
global _warned
|
|
||||||
if _warned:
|
|
||||||
return
|
|
||||||
import sys
|
|
||||||
sys.stderr.write("Ignore the above, please download the fix assertion addon.")
|
|
||||||
_warned = True
|
|
||||||
|
|
||||||
# Cards
|
# Cards
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
|
@ -83,7 +74,7 @@ class Card(object):
|
||||||
self.usn = self.col.usn()
|
self.usn = self.col.usn()
|
||||||
# bug check
|
# bug check
|
||||||
if self.queue == 2 and self.odue and not self.col.decks.isDyn(self.did):
|
if self.queue == 2 and self.odue and not self.col.decks.isDyn(self.did):
|
||||||
warn()
|
runHook("odueInvalid")
|
||||||
assert self.due < 4294967296
|
assert self.due < 4294967296
|
||||||
self.col.db.execute(
|
self.col.db.execute(
|
||||||
"""
|
"""
|
||||||
|
|
@ -114,7 +105,7 @@ insert or replace into cards values
|
||||||
self.usn = self.col.usn()
|
self.usn = self.col.usn()
|
||||||
# bug checks
|
# bug checks
|
||||||
if self.queue == 2 and self.odue and not self.col.decks.isDyn(self.did):
|
if self.queue == 2 and self.odue and not self.col.decks.isDyn(self.did):
|
||||||
warn()
|
runHook("odueInvalid")
|
||||||
assert self.due < 4294967296
|
assert self.due < 4294967296
|
||||||
self.col.db.execute(
|
self.col.db.execute(
|
||||||
"""update cards set
|
"""update cards set
|
||||||
|
|
|
||||||
|
|
@ -741,6 +741,16 @@ select id from cards where nid not in (select id from notes)""")
|
||||||
ngettext("Deleted %d card with missing note.",
|
ngettext("Deleted %d card with missing note.",
|
||||||
"Deleted %d cards with missing note.", cnt) % cnt)
|
"Deleted %d cards with missing note.", cnt) % cnt)
|
||||||
self.remCards(ids)
|
self.remCards(ids)
|
||||||
|
# cards with odue set when it shouldn't be
|
||||||
|
ids = self.db.list("""
|
||||||
|
select id from cards where odue > 0 and (type=1 or queue=2) and not odid""")
|
||||||
|
if ids:
|
||||||
|
cnt = len(ids)
|
||||||
|
problems.append(
|
||||||
|
ngettext("Fixed %d card with invalid properties.",
|
||||||
|
"Fixed %d cards with invalid properties.", cnt) % cnt)
|
||||||
|
self.db.execute("update cards set odue=0 where id in "+
|
||||||
|
ids2str(ids))
|
||||||
# tags
|
# tags
|
||||||
self.tags.registerNotes()
|
self.tags.registerNotes()
|
||||||
# field cache
|
# field cache
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@ class MediaManager(object):
|
||||||
soundRegexps = ["(?i)(\[sound:(?P<fname>[^]]+)\])"]
|
soundRegexps = ["(?i)(\[sound:(?P<fname>[^]]+)\])"]
|
||||||
imgRegexps = [
|
imgRegexps = [
|
||||||
# src element quoted case
|
# src element quoted case
|
||||||
"(?i)(<img[^>]+src=(?P<str>[\"'])(?P<fname>[^>]+?)(?P=str)[^>]*>)",
|
"(?i)(<img[^>]* src=(?P<str>[\"'])(?P<fname>[^>]+?)(?P=str)[^>]*>)",
|
||||||
# unquoted case
|
# unquoted case
|
||||||
"(?i)(<img[^>]+src=(?!['\"])(?P<fname>[^ >]+)[^>]*?>)",
|
"(?i)(<img[^>]* src=(?!['\"])(?P<fname>[^ >]+)[^>]*?>)",
|
||||||
]
|
]
|
||||||
regexps = soundRegexps + imgRegexps
|
regexps = soundRegexps + imgRegexps
|
||||||
|
|
||||||
|
|
@ -342,7 +342,7 @@ class MediaManager(object):
|
||||||
def hasIllegal(self, str):
|
def hasIllegal(self, str):
|
||||||
# a file that couldn't be decoded to unicode is considered invalid
|
# a file that couldn't be decoded to unicode is considered invalid
|
||||||
if not isinstance(str, unicode):
|
if not isinstance(str, unicode):
|
||||||
return False
|
return True
|
||||||
return not not re.search(self._illegalCharReg, str)
|
return not not re.search(self._illegalCharReg, str)
|
||||||
|
|
||||||
# Media syncing - bundling zip files to send to server
|
# Media syncing - bundling zip files to send to server
|
||||||
|
|
|
||||||
|
|
@ -1333,9 +1333,10 @@ and (queue=0 or (queue=2 and due<=?))""",
|
||||||
|
|
||||||
def forgetCards(self, ids):
|
def forgetCards(self, ids):
|
||||||
"Put cards at the end of the new queue."
|
"Put cards at the end of the new queue."
|
||||||
|
self.remFromDyn(ids)
|
||||||
self.col.db.execute(
|
self.col.db.execute(
|
||||||
"update cards set type=0,queue=0,ivl=0,due=0,factor=? where odid=0 "
|
"update cards set type=0,queue=0,ivl=0,due=0,odue=0,factor=?"
|
||||||
"and queue >= 0 and id in "+ids2str(ids), 2500)
|
" where id in "+ids2str(ids), 2500)
|
||||||
pmax = self.col.db.scalar(
|
pmax = self.col.db.scalar(
|
||||||
"select max(due) from cards where type=0") or 0
|
"select max(due) from cards where type=0") or 0
|
||||||
# takes care of mod + usn
|
# takes care of mod + usn
|
||||||
|
|
@ -1351,10 +1352,10 @@ and (queue=0 or (queue=2 and due<=?))""",
|
||||||
r = random.randint(imin, imax)
|
r = random.randint(imin, imax)
|
||||||
d.append(dict(id=id, due=r+t, ivl=max(1, r), mod=mod,
|
d.append(dict(id=id, due=r+t, ivl=max(1, r), mod=mod,
|
||||||
usn=self.col.usn(), fact=2500))
|
usn=self.col.usn(), fact=2500))
|
||||||
self.removeLrn(ids)
|
self.remFromDyn(ids)
|
||||||
self.col.db.executemany("""
|
self.col.db.executemany("""
|
||||||
update cards set type=2,queue=2,ivl=:ivl,due=:due,
|
update cards set type=2,queue=2,ivl=:ivl,due=:due,odue=0,
|
||||||
usn=:usn, mod=:mod, factor=:fact where id=:id and odid=0 and queue >=0""",
|
usn=:usn,mod=:mod,factor=:fact where id=:id""",
|
||||||
d)
|
d)
|
||||||
self.col.log(ids)
|
self.col.log(ids)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -716,6 +716,8 @@ by clicking on one on the left."""))
|
||||||
hh.setResizeMode(i, QHeaderView.Stretch)
|
hh.setResizeMode(i, QHeaderView.Stretch)
|
||||||
else:
|
else:
|
||||||
hh.setResizeMode(i, QHeaderView.Interactive)
|
hh.setResizeMode(i, QHeaderView.Interactive)
|
||||||
|
# this must be set post-resize or it doesn't work
|
||||||
|
hh.setCascadingSectionResizes(False)
|
||||||
|
|
||||||
def onColumnMoved(self, a, b, c):
|
def onColumnMoved(self, a, b, c):
|
||||||
self.setColumnSizes()
|
self.setColumnSizes()
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,7 @@ Please create a new card type first."""))
|
||||||
if self.redrawing:
|
if self.redrawing:
|
||||||
return
|
return
|
||||||
self.card = self.cards[idx]
|
self.card = self.cards[idx]
|
||||||
|
self.ord = idx
|
||||||
self.tab = self.forms[idx]
|
self.tab = self.forms[idx]
|
||||||
self.tabs.setCurrentIndex(idx)
|
self.tabs.setCurrentIndex(idx)
|
||||||
self.playedAudio = {}
|
self.playedAudio = {}
|
||||||
|
|
|
||||||
|
|
@ -814,7 +814,7 @@ to a cloze type first, via Edit>Change Note Type."""))
|
||||||
for suffix in pics+audio:
|
for suffix in pics+audio:
|
||||||
if l.endswith(suffix):
|
if l.endswith(suffix):
|
||||||
return self._retrieveURL(url)
|
return self._retrieveURL(url)
|
||||||
# not a supported type; return link verbatim
|
# not a supported type
|
||||||
return
|
return
|
||||||
|
|
||||||
def isURL(self, s):
|
def isURL(self, s):
|
||||||
|
|
@ -1121,6 +1121,8 @@ class EditorWebView(AnkiWebView):
|
||||||
link = self.editor.urlToLink(url)
|
link = self.editor.urlToLink(url)
|
||||||
if link:
|
if link:
|
||||||
mime.setHtml(link)
|
mime.setHtml(link)
|
||||||
|
else:
|
||||||
|
mime.setText(url)
|
||||||
return mime
|
return mime
|
||||||
|
|
||||||
# if the user has used 'copy link location' in the browser, the clipboard
|
# if the user has used 'copy link location' in the browser, the clipboard
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ class ImportDialog(QDialog):
|
||||||
self.onDelimiter)
|
self.onDelimiter)
|
||||||
self.updateDelimiterButtonText()
|
self.updateDelimiterButtonText()
|
||||||
self.frm.allowHTML.setChecked(self.mw.pm.profile.get('allowHTML', True))
|
self.frm.allowHTML.setChecked(self.mw.pm.profile.get('allowHTML', True))
|
||||||
self.frm.importMode.setCurrentIndex(self.mw.pm.profile.get('importMode', 0))
|
self.frm.importMode.setCurrentIndex(self.mw.pm.profile.get('importMode', 1))
|
||||||
self.exec_()
|
self.exec_()
|
||||||
|
|
||||||
def setupOptions(self):
|
def setupOptions(self):
|
||||||
|
|
|
||||||
|
|
@ -863,6 +863,12 @@ Difference to correct time: %s.""") % diffText
|
||||||
def setupHooks(self):
|
def setupHooks(self):
|
||||||
addHook("modSchema", self.onSchemaMod)
|
addHook("modSchema", self.onSchemaMod)
|
||||||
addHook("remNotes", self.onRemNotes)
|
addHook("remNotes", self.onRemNotes)
|
||||||
|
addHook("odueInvalid", self.onOdueInvalid)
|
||||||
|
|
||||||
|
def onOdueInvalid(self):
|
||||||
|
showWarning(_("""\
|
||||||
|
Invalid property found on card. Please use Tools>Check Database, \
|
||||||
|
and if the problem comes up again, please ask on the support site."""))
|
||||||
|
|
||||||
# Log note deletion
|
# Log note deletion
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ profileConf = dict(
|
||||||
autoSync=True,
|
autoSync=True,
|
||||||
# importing
|
# importing
|
||||||
allowHTML=False,
|
allowHTML=False,
|
||||||
importMode=0,
|
importMode=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
class ProfileManager(object):
|
class ProfileManager(object):
|
||||||
|
|
|
||||||
22
aqt/sync.py
22
aqt/sync.py
|
|
@ -126,8 +126,8 @@ Please visit AnkiWeb, upgrade your deck, then try again."""))
|
||||||
self._checkFailed()
|
self._checkFailed()
|
||||||
elif evt == "mediaSanity":
|
elif evt == "mediaSanity":
|
||||||
showWarning(_("""\
|
showWarning(_("""\
|
||||||
A problem occurred while syncing media. Please sync again and Anki will \
|
A problem occurred while syncing media. Please use Tools>Check Media, then \
|
||||||
correct the issue."""))
|
sync again to correct the issue."""))
|
||||||
elif evt == "noChanges":
|
elif evt == "noChanges":
|
||||||
pass
|
pass
|
||||||
elif evt == "fullSync":
|
elif evt == "fullSync":
|
||||||
|
|
@ -167,7 +167,7 @@ AnkiWeb is too busy at the moment. Please try again in a few minutes.""")
|
||||||
return _("504 gateway timeout error received. Please try temporarily disabling your antivirus.")
|
return _("504 gateway timeout error received. Please try temporarily disabling your antivirus.")
|
||||||
elif "code: 409" in err:
|
elif "code: 409" in err:
|
||||||
return _("Only one client can access AnkiWeb at a time. If a previous sync failed, please try again in a few minutes.")
|
return _("Only one client can access AnkiWeb at a time. If a previous sync failed, please try again in a few minutes.")
|
||||||
elif "10061" in err or "10013" in err:
|
elif "10061" in err or "10013" in err or "10053" in err:
|
||||||
return _(
|
return _(
|
||||||
"Antivirus or firewall software is preventing Anki from connecting to the internet.")
|
"Antivirus or firewall software is preventing Anki from connecting to the internet.")
|
||||||
elif "Unable to find the server" in err:
|
elif "Unable to find the server" in err:
|
||||||
|
|
@ -337,12 +337,9 @@ class SyncThread(QThread):
|
||||||
ret = self.client.sync()
|
ret = self.client.sync()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log = traceback.format_exc()
|
log = traceback.format_exc()
|
||||||
try:
|
err = repr(str(e))
|
||||||
err = unicode(e[0], "utf8", "ignore")
|
if ("Unable to find the server" in err or
|
||||||
except:
|
"Errno 2" in err):
|
||||||
# number, exception with no args, etc
|
|
||||||
err = ""
|
|
||||||
if "Unable to find the server" in err:
|
|
||||||
self.fireEvent("offline")
|
self.fireEvent("offline")
|
||||||
else:
|
else:
|
||||||
if not err:
|
if not err:
|
||||||
|
|
@ -451,7 +448,9 @@ httplib.HTTPConnection.send = _incrementalSend
|
||||||
# this is an augmented version of httplib's request routine that:
|
# this is an augmented version of httplib's request routine that:
|
||||||
# - doesn't assume requests will be tried more than once
|
# - doesn't assume requests will be tried more than once
|
||||||
# - calls a hook for each chunk of data so we can update the gui
|
# - calls a hook for each chunk of data so we can update the gui
|
||||||
|
# - retries only when keep-alive connection is closed
|
||||||
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):
|
||||||
try:
|
try:
|
||||||
if conn.sock is None:
|
if conn.sock is None:
|
||||||
conn.connect()
|
conn.connect()
|
||||||
|
|
@ -473,6 +472,11 @@ def _conn_request(self, conn, request_uri, method, body, headers):
|
||||||
raise
|
raise
|
||||||
try:
|
try:
|
||||||
response = conn.getresponse()
|
response = conn.getresponse()
|
||||||
|
except httplib.BadStatusLine:
|
||||||
|
print "retry bad line"
|
||||||
|
conn.close()
|
||||||
|
conn.connect()
|
||||||
|
continue
|
||||||
except (socket.error, httplib.HTTPException):
|
except (socket.error, httplib.HTTPException):
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@
|
||||||
<enum>QAbstractItemView::SelectRows</enum>
|
<enum>QAbstractItemView::SelectRows</enum>
|
||||||
</property>
|
</property>
|
||||||
<attribute name="horizontalHeaderCascadingSectionResizes">
|
<attribute name="horizontalHeaderCascadingSectionResizes">
|
||||||
<bool>true</bool>
|
<bool>false</bool>
|
||||||
</attribute>
|
</attribute>
|
||||||
<attribute name="horizontalHeaderHighlightSections">
|
<attribute name="horizontalHeaderHighlightSections">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
import tempfile, os, time
|
import tempfile
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
from shared import getEmptyDeck, testDir
|
from shared import getEmptyDeck, testDir
|
||||||
|
|
||||||
|
|
||||||
# copying files to media folder
|
# copying files to media folder
|
||||||
def test_add():
|
def test_add():
|
||||||
d = getEmptyDeck()
|
d = getEmptyDeck()
|
||||||
|
|
@ -100,8 +104,8 @@ def test_changes():
|
||||||
|
|
||||||
def test_illegal():
|
def test_illegal():
|
||||||
d = getEmptyDeck()
|
d = getEmptyDeck()
|
||||||
aString = "a:b|cd\\e/f\0g*h"
|
aString = u"a:b|cd\\e/f\0g*h"
|
||||||
good = "abcdefgh"
|
good = u"abcdefgh"
|
||||||
assert d.media.stripIllegal(aString) == good
|
assert d.media.stripIllegal(aString) == good
|
||||||
for c in aString:
|
for c in aString:
|
||||||
bad = d.media.hasIllegal("somestring"+c+"morestring")
|
bad = d.media.hasIllegal("somestring"+c+"morestring")
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
import time, copy, sys
|
import time
|
||||||
|
import copy
|
||||||
|
|
||||||
from tests.shared import getEmptyDeck
|
from tests.shared import getEmptyDeck
|
||||||
from anki.utils import intTime
|
from anki.utils import intTime
|
||||||
from anki.hooks import addHook
|
from anki.hooks import addHook
|
||||||
|
|
||||||
|
|
||||||
def test_clock():
|
def test_clock():
|
||||||
d = getEmptyDeck()
|
d = getEmptyDeck()
|
||||||
if (d.sched.dayCutoff - intTime()) < 10*60:
|
if (d.sched.dayCutoff - intTime()) < 10*60:
|
||||||
|
|
@ -173,8 +176,10 @@ def test_learn():
|
||||||
c.queue = 1
|
c.queue = 1
|
||||||
c.odue = 321
|
c.odue = 321
|
||||||
c.flush()
|
c.flush()
|
||||||
|
print "----begin"
|
||||||
d.sched.removeLrn()
|
d.sched.removeLrn()
|
||||||
c.load()
|
c.load()
|
||||||
|
print c.__dict__
|
||||||
assert c.queue == 2
|
assert c.queue == 2
|
||||||
assert c.due == 321
|
assert c.due == 321
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue