This commit is contained in:
Soren I. Bjornstad 2013-12-01 09:58:34 -06:00
commit 684df05f51
15 changed files with 103 additions and 77 deletions

View file

@ -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"]

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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()

View file

@ -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 = {}

View file

@ -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

View file

@ -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):

View file

@ -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
########################################################################## ##########################################################################

View file

@ -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):

View file

@ -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,46 +448,53 @@ 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):
try: for i in range(2):
if conn.sock is None: try:
conn.connect() if conn.sock is None:
conn.request(method, request_uri, body, headers) conn.connect()
except socket.timeout: conn.request(method, request_uri, body, headers)
raise except socket.timeout:
except socket.gaierror: raise
conn.close() except socket.gaierror:
raise httplib2.ServerNotFoundError( conn.close()
"Unable to find the server at %s" % conn.host) raise httplib2.ServerNotFoundError(
except httplib2.ssl_SSLError: "Unable to find the server at %s" % conn.host)
conn.close() except httplib2.ssl_SSLError:
raise conn.close()
except socket.error, e: raise
conn.close() except socket.error, e:
raise conn.close()
except httplib.HTTPException: raise
conn.close() except httplib.HTTPException:
raise conn.close()
try: raise
response = conn.getresponse() try:
except (socket.error, httplib.HTTPException): response = conn.getresponse()
raise except httplib.BadStatusLine:
else: print "retry bad line"
content = "" conn.close()
if method == "HEAD": conn.connect()
response.close() continue
except (socket.error, httplib.HTTPException):
raise
else: else:
buf = StringIO() content = ""
while 1: if method == "HEAD":
data = response.read(CHUNK_SIZE) response.close()
if not data: else:
break buf = StringIO()
buf.write(data) while 1:
runHook("httpRecv", len(data)) data = response.read(CHUNK_SIZE)
content = buf.getvalue() if not data:
response = httplib2.Response(response) break
if method != "HEAD": buf.write(data)
content = httplib2._decompressContent(response, content) runHook("httpRecv", len(data))
return (response, content) content = buf.getvalue()
response = httplib2.Response(response)
if method != "HEAD":
content = httplib2._decompressContent(response, content)
return (response, content)
httplib2.Http._conn_request = _conn_request httplib2.Http._conn_request = _conn_request

View file

@ -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>

View file

@ -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")

View file

@ -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