This commit is contained in:
Soren I. Bjornstad 2014-08-04 10:44:43 -05:00
commit 0914c01706
6 changed files with 63 additions and 16 deletions

View file

@ -221,14 +221,17 @@ create table meta (dirMod int, lastUsn int); insert into meta values (0, 0);
txt = re.sub(reg, "", txt)
return txt
def escapeImages(self, string):
def escapeImages(self, string, unescape=False):
def repl(match):
tag = match.group(0)
fname = match.group("fname")
if re.match("(https?|ftp)://", fname):
return tag
return tag.replace(
fname, urllib.quote(fname.encode("utf-8")))
if unescape:
txt = urllib.unquote(fname)
else:
txt = urllib.quote(fname.encode("utf-8"))
return tag.replace(fname, txt)
for reg in self.imgRegexps:
string = re.sub(reg, repl, string)
return string
@ -381,9 +384,13 @@ create table meta (dirMod int, lastUsn int); insert into meta values (0, 0);
if self.hasIllegal(f):
continue
# empty files are invalid; clean them up and continue
if not os.path.getsize(f):
sz = os.path.getsize(f)
if not sz:
os.unlink(f)
continue
if sz > 100*1024*1024:
self.col.log("ignoring file over 100MB", f)
continue
# check encoding
if not isMac:
normf = unicodedata.normalize("NFC", f)
@ -439,6 +446,10 @@ create table meta (dirMod int, lastUsn int); insert into meta values (0, 0);
return self.db.scalar(
"select count() from media where csum is not null")
def dirtyCount(self):
return self.db.scalar(
"select count() from media where dirty=1")
def forceResync(self):
self.db.execute("delete from media")
self.db.execute("update meta set lastUsn=0,dirMod=0")

View file

@ -14,6 +14,7 @@ from anki.utils import ids2str, intTime, json, isWin, isMac, platDesc, checksum
from anki.consts import *
from hooks import runHook
import anki
from lang import ngettext
# syncing vars
HTTP_TIMEOUT = 90
@ -740,6 +741,7 @@ class MediaSyncer(object):
# loop through and process changes from server
self.col.log("last local usn is %s"%lastUsn)
self.downloadCount = 0
while True:
data = self.server.mediaChanges(lastUsn=lastUsn)
@ -789,11 +791,16 @@ class MediaSyncer(object):
# and we need to send our own
updateConflict = False
toSend = self.col.media.dirtyCount()
while True:
zip, fnames = self.col.media.mediaChangesZip()
if not fnames:
break
runHook("syncMsg", ngettext(
"%d media change to upload", "%d media changes to upload", toSend)
% toSend)
processedCnt, serverLastUsn = self.server.uploadChanges(zip)
self.col.media.markClean(fnames[0:processedCnt])
@ -811,6 +818,8 @@ class MediaSyncer(object):
self.col.media.db.commit()
updateConflict = True
toSend -= processedCnt
if updateConflict:
self.col.log("restart sync due to concurrent update")
return self.sync()
@ -830,15 +839,14 @@ class MediaSyncer(object):
self.col.log("fetch %s"%top)
zipData = self.server.downloadFiles(files=top)
cnt = self.col.media.addFilesFromZip(zipData)
self.downloadCount += cnt
self.col.log("received %d files"%cnt)
fnames = fnames[cnt:]
def files(self):
return self.col.media.addFilesToZip()
def addFiles(self, zip):
"True if zip is the last in set. Server returns new usn instead."
return self.col.media.addFilesFromZip(zip)
n = self.downloadCount
runHook("syncMsg", ngettext(
"%d media file downloaded", "%d media files downloaded", n)
% n)
# Remote media syncing
##########################################################################

View file

@ -449,6 +449,8 @@ class Editor(object):
txt = self.mungeHTML(txt)
# misbehaving apps may include a null byte in the text
txt = txt.replace("\x00", "")
# reverse the url quoting we added to get images to display
txt = self.mw.col.media.escapeImages(txt, unescape=True)
self.note.fields[self.currentField] = txt
if not self.addMode:
self.note.flush()

View file

@ -65,6 +65,23 @@ Anki manual for more information.""")
"other programs are not using the audio device."))
if "invalidTempFolder" in error:
return showWarning(self.tempFolderMsg())
if "disk I/O error" in error:
return showWarning(_("""\
An error occurred while accessing the database.
Possible causes:
- Antivirus, firewall, backup, or synchronization software may be \
interfering with Anki. Try disabling such software and see if the \
problem goes away.
- Your disk may be full.
- The Documents/Anki folder may be on a network drive.
- Files in the Documents/Anki folder may not be writeable.
- Your hard disk may have errors.
It's a good idea to run Tools>Check Database to ensure your collection \
is not corrupt.
"""))
stdText = _("""\
An error occurred. It may have been caused by a harmless bug, <br>
or your deck may have a problem.

View file

@ -116,6 +116,9 @@ Please visit AnkiWeb, upgrade your deck, then try again."""))
if m:
self.label = m
self._updateLabel()
elif evt == "syncMsg":
self.label = args[0]
self._updateLabel()
elif evt == "error":
self._didError = True
showText(_("Syncing failed:\n%s")%
@ -296,6 +299,8 @@ class SyncThread(QThread):
self.byteUpdate = time.time()
def syncEvent(type):
self.fireEvent("sync", type)
def syncMsg(msg):
self.fireEvent("syncMsg", msg)
def canPost():
if (time.time() - self.byteUpdate) > 0.1:
self.byteUpdate = time.time()
@ -309,6 +314,7 @@ class SyncThread(QThread):
if canPost():
self.fireEvent("recv", self.recvTotal)
addHook("sync", syncEvent)
addHook("syncMsg", syncMsg)
addHook("httpSend", sendEvent)
addHook("httpRecv", recvEvent)
# run sync and catch any errors
@ -323,6 +329,7 @@ class SyncThread(QThread):
# don't bump mod time unless we explicitly save
self.col.close(save=False)
remHook("sync", syncEvent)
remHook("syncMsg", syncMsg)
remHook("httpSend", sendEvent)
remHook("httpRecv", recvEvent)

View file

@ -71,9 +71,11 @@ def test_changes():
d = getEmptyCol()
assert d.media._changed()
def added():
return d.media.db.execute("select fname from log where type = 0")
return d.media.db.execute("select fname from media where csum is not null")
def removed():
return d.media.db.execute("select fname from media where csum is null")
assert not list(added())
assert not list(d.media.removed())
assert not list(removed())
# add a file
dir = tempfile.mkdtemp(prefix="anki")
path = os.path.join(dir, u"foo.jpg")
@ -83,24 +85,24 @@ def test_changes():
# should have been logged
d.media.findChanges()
assert list(added())
assert not list(d.media.removed())
assert not list(removed())
# if we modify it, the cache won't notice
time.sleep(1)
open(path, "w").write("world")
assert len(list(added())) == 1
assert not list(d.media.removed())
assert not list(removed())
# but if we add another file, it will
time.sleep(1)
open(path+"2", "w").write("yo")
d.media.findChanges()
assert len(list(added())) == 2
assert not list(d.media.removed())
assert not list(removed())
# deletions should get noticed too
time.sleep(1)
os.unlink(path+"2")
d.media.findChanges()
assert len(list(added())) == 1
assert len(list(d.media.removed())) == 1
assert len(list(removed())) == 1
def test_illegal():
d = getEmptyCol()