mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 08:46:37 -04: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.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
|
||||
__all__ = ["Collection"]
|
||||
|
|
|
@ -4,19 +4,10 @@
|
|||
import pprint
|
||||
|
||||
import time
|
||||
from anki.hooks import runHook
|
||||
from anki.utils import intTime, timestampID, joinFields
|
||||
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
|
||||
##########################################################################
|
||||
|
||||
|
@ -83,7 +74,7 @@ class Card(object):
|
|||
self.usn = self.col.usn()
|
||||
# bug check
|
||||
if self.queue == 2 and self.odue and not self.col.decks.isDyn(self.did):
|
||||
warn()
|
||||
runHook("odueInvalid")
|
||||
assert self.due < 4294967296
|
||||
self.col.db.execute(
|
||||
"""
|
||||
|
@ -114,7 +105,7 @@ insert or replace into cards values
|
|||
self.usn = self.col.usn()
|
||||
# bug checks
|
||||
if self.queue == 2 and self.odue and not self.col.decks.isDyn(self.did):
|
||||
warn()
|
||||
runHook("odueInvalid")
|
||||
assert self.due < 4294967296
|
||||
self.col.db.execute(
|
||||
"""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.",
|
||||
"Deleted %d cards with missing note.", cnt) % cnt)
|
||||
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
|
||||
self.tags.registerNotes()
|
||||
# field cache
|
||||
|
|
|
@ -22,9 +22,9 @@ class MediaManager(object):
|
|||
soundRegexps = ["(?i)(\[sound:(?P<fname>[^]]+)\])"]
|
||||
imgRegexps = [
|
||||
# src element quoted case
|
||||
"(?i)(<img[^>]+src=(?P<str>[\"'])(?P<fname>[^>]+?)(?P=str)[^>]*>)",
|
||||
"(?i)(<img[^>]* src=(?P<str>[\"'])(?P<fname>[^>]+?)(?P=str)[^>]*>)",
|
||||
# unquoted case
|
||||
"(?i)(<img[^>]+src=(?!['\"])(?P<fname>[^ >]+)[^>]*?>)",
|
||||
"(?i)(<img[^>]* src=(?!['\"])(?P<fname>[^ >]+)[^>]*?>)",
|
||||
]
|
||||
regexps = soundRegexps + imgRegexps
|
||||
|
||||
|
@ -342,7 +342,7 @@ class MediaManager(object):
|
|||
def hasIllegal(self, str):
|
||||
# a file that couldn't be decoded to unicode is considered invalid
|
||||
if not isinstance(str, unicode):
|
||||
return False
|
||||
return True
|
||||
return not not re.search(self._illegalCharReg, str)
|
||||
|
||||
# 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):
|
||||
"Put cards at the end of the new queue."
|
||||
self.remFromDyn(ids)
|
||||
self.col.db.execute(
|
||||
"update cards set type=0,queue=0,ivl=0,due=0,factor=? where odid=0 "
|
||||
"and queue >= 0 and id in "+ids2str(ids), 2500)
|
||||
"update cards set type=0,queue=0,ivl=0,due=0,odue=0,factor=?"
|
||||
" where id in "+ids2str(ids), 2500)
|
||||
pmax = self.col.db.scalar(
|
||||
"select max(due) from cards where type=0") or 0
|
||||
# takes care of mod + usn
|
||||
|
@ -1351,10 +1352,10 @@ and (queue=0 or (queue=2 and due<=?))""",
|
|||
r = random.randint(imin, imax)
|
||||
d.append(dict(id=id, due=r+t, ivl=max(1, r), mod=mod,
|
||||
usn=self.col.usn(), fact=2500))
|
||||
self.removeLrn(ids)
|
||||
self.remFromDyn(ids)
|
||||
self.col.db.executemany("""
|
||||
update cards set type=2,queue=2,ivl=:ivl,due=:due,
|
||||
usn=:usn, mod=:mod, factor=:fact where id=:id and odid=0 and queue >=0""",
|
||||
update cards set type=2,queue=2,ivl=:ivl,due=:due,odue=0,
|
||||
usn=:usn,mod=:mod,factor=:fact where id=:id""",
|
||||
d)
|
||||
self.col.log(ids)
|
||||
|
||||
|
|
|
@ -716,6 +716,8 @@ by clicking on one on the left."""))
|
|||
hh.setResizeMode(i, QHeaderView.Stretch)
|
||||
else:
|
||||
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):
|
||||
self.setColumnSizes()
|
||||
|
|
|
@ -191,6 +191,7 @@ Please create a new card type first."""))
|
|||
if self.redrawing:
|
||||
return
|
||||
self.card = self.cards[idx]
|
||||
self.ord = idx
|
||||
self.tab = self.forms[idx]
|
||||
self.tabs.setCurrentIndex(idx)
|
||||
self.playedAudio = {}
|
||||
|
|
|
@ -814,7 +814,7 @@ to a cloze type first, via Edit>Change Note Type."""))
|
|||
for suffix in pics+audio:
|
||||
if l.endswith(suffix):
|
||||
return self._retrieveURL(url)
|
||||
# not a supported type; return link verbatim
|
||||
# not a supported type
|
||||
return
|
||||
|
||||
def isURL(self, s):
|
||||
|
@ -1121,6 +1121,8 @@ class EditorWebView(AnkiWebView):
|
|||
link = self.editor.urlToLink(url)
|
||||
if link:
|
||||
mime.setHtml(link)
|
||||
else:
|
||||
mime.setText(url)
|
||||
return mime
|
||||
|
||||
# if the user has used 'copy link location' in the browser, the clipboard
|
||||
|
|
|
@ -79,7 +79,7 @@ class ImportDialog(QDialog):
|
|||
self.onDelimiter)
|
||||
self.updateDelimiterButtonText()
|
||||
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_()
|
||||
|
||||
def setupOptions(self):
|
||||
|
|
|
@ -863,6 +863,12 @@ Difference to correct time: %s.""") % diffText
|
|||
def setupHooks(self):
|
||||
addHook("modSchema", self.onSchemaMod)
|
||||
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
|
||||
##########################################################################
|
||||
|
|
|
@ -56,7 +56,7 @@ profileConf = dict(
|
|||
autoSync=True,
|
||||
# importing
|
||||
allowHTML=False,
|
||||
importMode=0,
|
||||
importMode=1,
|
||||
)
|
||||
|
||||
class ProfileManager(object):
|
||||
|
|
100
aqt/sync.py
100
aqt/sync.py
|
@ -126,8 +126,8 @@ Please visit AnkiWeb, upgrade your deck, then try again."""))
|
|||
self._checkFailed()
|
||||
elif evt == "mediaSanity":
|
||||
showWarning(_("""\
|
||||
A problem occurred while syncing media. Please sync again and Anki will \
|
||||
correct the issue."""))
|
||||
A problem occurred while syncing media. Please use Tools>Check Media, then \
|
||||
sync again to correct the issue."""))
|
||||
elif evt == "noChanges":
|
||||
pass
|
||||
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.")
|
||||
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.")
|
||||
elif "10061" in err or "10013" in err:
|
||||
elif "10061" in err or "10013" in err or "10053" in err:
|
||||
return _(
|
||||
"Antivirus or firewall software is preventing Anki from connecting to the internet.")
|
||||
elif "Unable to find the server" in err:
|
||||
|
@ -337,12 +337,9 @@ class SyncThread(QThread):
|
|||
ret = self.client.sync()
|
||||
except Exception, e:
|
||||
log = traceback.format_exc()
|
||||
try:
|
||||
err = unicode(e[0], "utf8", "ignore")
|
||||
except:
|
||||
# number, exception with no args, etc
|
||||
err = ""
|
||||
if "Unable to find the server" in err:
|
||||
err = repr(str(e))
|
||||
if ("Unable to find the server" in err or
|
||||
"Errno 2" in err):
|
||||
self.fireEvent("offline")
|
||||
else:
|
||||
if not err:
|
||||
|
@ -451,46 +448,53 @@ httplib.HTTPConnection.send = _incrementalSend
|
|||
# this is an augmented version of httplib's request routine that:
|
||||
# - doesn't assume requests will be tried more than once
|
||||
# - 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):
|
||||
try:
|
||||
if conn.sock is None:
|
||||
conn.connect()
|
||||
conn.request(method, request_uri, body, headers)
|
||||
except socket.timeout:
|
||||
raise
|
||||
except socket.gaierror:
|
||||
conn.close()
|
||||
raise httplib2.ServerNotFoundError(
|
||||
"Unable to find the server at %s" % conn.host)
|
||||
except httplib2.ssl_SSLError:
|
||||
conn.close()
|
||||
raise
|
||||
except socket.error, e:
|
||||
conn.close()
|
||||
raise
|
||||
except httplib.HTTPException:
|
||||
conn.close()
|
||||
raise
|
||||
try:
|
||||
response = conn.getresponse()
|
||||
except (socket.error, httplib.HTTPException):
|
||||
raise
|
||||
else:
|
||||
content = ""
|
||||
if method == "HEAD":
|
||||
response.close()
|
||||
for i in range(2):
|
||||
try:
|
||||
if conn.sock is None:
|
||||
conn.connect()
|
||||
conn.request(method, request_uri, body, headers)
|
||||
except socket.timeout:
|
||||
raise
|
||||
except socket.gaierror:
|
||||
conn.close()
|
||||
raise httplib2.ServerNotFoundError(
|
||||
"Unable to find the server at %s" % conn.host)
|
||||
except httplib2.ssl_SSLError:
|
||||
conn.close()
|
||||
raise
|
||||
except socket.error, e:
|
||||
conn.close()
|
||||
raise
|
||||
except httplib.HTTPException:
|
||||
conn.close()
|
||||
raise
|
||||
try:
|
||||
response = conn.getresponse()
|
||||
except httplib.BadStatusLine:
|
||||
print "retry bad line"
|
||||
conn.close()
|
||||
conn.connect()
|
||||
continue
|
||||
except (socket.error, httplib.HTTPException):
|
||||
raise
|
||||
else:
|
||||
buf = StringIO()
|
||||
while 1:
|
||||
data = response.read(CHUNK_SIZE)
|
||||
if not data:
|
||||
break
|
||||
buf.write(data)
|
||||
runHook("httpRecv", len(data))
|
||||
content = buf.getvalue()
|
||||
response = httplib2.Response(response)
|
||||
if method != "HEAD":
|
||||
content = httplib2._decompressContent(response, content)
|
||||
return (response, content)
|
||||
content = ""
|
||||
if method == "HEAD":
|
||||
response.close()
|
||||
else:
|
||||
buf = StringIO()
|
||||
while 1:
|
||||
data = response.read(CHUNK_SIZE)
|
||||
if not data:
|
||||
break
|
||||
buf.write(data)
|
||||
runHook("httpRecv", len(data))
|
||||
content = buf.getvalue()
|
||||
response = httplib2.Response(response)
|
||||
if method != "HEAD":
|
||||
content = httplib2._decompressContent(response, content)
|
||||
return (response, content)
|
||||
|
||||
httplib2.Http._conn_request = _conn_request
|
||||
|
|
|
@ -173,7 +173,7 @@
|
|||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderCascadingSectionResizes">
|
||||
<bool>true</bool>
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderHighlightSections">
|
||||
<bool>false</bool>
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
# coding: utf-8
|
||||
|
||||
import tempfile, os, time
|
||||
import tempfile
|
||||
import os
|
||||
import time
|
||||
|
||||
from shared import getEmptyDeck, testDir
|
||||
|
||||
|
||||
# copying files to media folder
|
||||
def test_add():
|
||||
d = getEmptyDeck()
|
||||
|
@ -100,8 +104,8 @@ def test_changes():
|
|||
|
||||
def test_illegal():
|
||||
d = getEmptyDeck()
|
||||
aString = "a:b|cd\\e/f\0g*h"
|
||||
good = "abcdefgh"
|
||||
aString = u"a:b|cd\\e/f\0g*h"
|
||||
good = u"abcdefgh"
|
||||
assert d.media.stripIllegal(aString) == good
|
||||
for c in aString:
|
||||
bad = d.media.hasIllegal("somestring"+c+"morestring")
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
# coding: utf-8
|
||||
|
||||
import time, copy, sys
|
||||
import time
|
||||
import copy
|
||||
|
||||
from tests.shared import getEmptyDeck
|
||||
from anki.utils import intTime
|
||||
from anki.hooks import addHook
|
||||
|
||||
|
||||
def test_clock():
|
||||
d = getEmptyDeck()
|
||||
if (d.sched.dayCutoff - intTime()) < 10*60:
|
||||
|
@ -173,8 +176,10 @@ def test_learn():
|
|||
c.queue = 1
|
||||
c.odue = 321
|
||||
c.flush()
|
||||
print "----begin"
|
||||
d.sched.removeLrn()
|
||||
c.load()
|
||||
print c.__dict__
|
||||
assert c.queue == 2
|
||||
assert c.due == 321
|
||||
|
||||
|
|
Loading…
Reference in a new issue