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) txt = re.sub(reg, "", txt)
return txt return txt
def escapeImages(self, string): def escapeImages(self, string, unescape=False):
def repl(match): def repl(match):
tag = match.group(0) tag = match.group(0)
fname = match.group("fname") fname = match.group("fname")
if re.match("(https?|ftp)://", fname): if re.match("(https?|ftp)://", fname):
return tag return tag
return tag.replace( if unescape:
fname, urllib.quote(fname.encode("utf-8"))) txt = urllib.unquote(fname)
else:
txt = urllib.quote(fname.encode("utf-8"))
return tag.replace(fname, txt)
for reg in self.imgRegexps: for reg in self.imgRegexps:
string = re.sub(reg, repl, string) string = re.sub(reg, repl, string)
return string return string
@ -381,9 +384,13 @@ create table meta (dirMod int, lastUsn int); insert into meta values (0, 0);
if self.hasIllegal(f): if self.hasIllegal(f):
continue continue
# empty files are invalid; clean them up and 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) os.unlink(f)
continue continue
if sz > 100*1024*1024:
self.col.log("ignoring file over 100MB", f)
continue
# check encoding # check encoding
if not isMac: if not isMac:
normf = unicodedata.normalize("NFC", f) 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( return self.db.scalar(
"select count() from media where csum is not null") "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): def forceResync(self):
self.db.execute("delete from media") self.db.execute("delete from media")
self.db.execute("update meta set lastUsn=0,dirMod=0") 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 anki.consts import *
from hooks import runHook from hooks import runHook
import anki import anki
from lang import ngettext
# syncing vars # syncing vars
HTTP_TIMEOUT = 90 HTTP_TIMEOUT = 90
@ -740,6 +741,7 @@ class MediaSyncer(object):
# loop through and process changes from server # loop through and process changes from server
self.col.log("last local usn is %s"%lastUsn) self.col.log("last local usn is %s"%lastUsn)
self.downloadCount = 0
while True: while True:
data = self.server.mediaChanges(lastUsn=lastUsn) data = self.server.mediaChanges(lastUsn=lastUsn)
@ -789,11 +791,16 @@ class MediaSyncer(object):
# and we need to send our own # and we need to send our own
updateConflict = False updateConflict = False
toSend = self.col.media.dirtyCount()
while True: while True:
zip, fnames = self.col.media.mediaChangesZip() zip, fnames = self.col.media.mediaChangesZip()
if not fnames: if not fnames:
break break
runHook("syncMsg", ngettext(
"%d media change to upload", "%d media changes to upload", toSend)
% toSend)
processedCnt, serverLastUsn = self.server.uploadChanges(zip) processedCnt, serverLastUsn = self.server.uploadChanges(zip)
self.col.media.markClean(fnames[0:processedCnt]) self.col.media.markClean(fnames[0:processedCnt])
@ -811,6 +818,8 @@ class MediaSyncer(object):
self.col.media.db.commit() self.col.media.db.commit()
updateConflict = True updateConflict = True
toSend -= processedCnt
if updateConflict: if updateConflict:
self.col.log("restart sync due to concurrent update") self.col.log("restart sync due to concurrent update")
return self.sync() return self.sync()
@ -830,15 +839,14 @@ class MediaSyncer(object):
self.col.log("fetch %s"%top) self.col.log("fetch %s"%top)
zipData = self.server.downloadFiles(files=top) zipData = self.server.downloadFiles(files=top)
cnt = self.col.media.addFilesFromZip(zipData) cnt = self.col.media.addFilesFromZip(zipData)
self.downloadCount += cnt
self.col.log("received %d files"%cnt) self.col.log("received %d files"%cnt)
fnames = fnames[cnt:] fnames = fnames[cnt:]
def files(self): n = self.downloadCount
return self.col.media.addFilesToZip() runHook("syncMsg", ngettext(
"%d media file downloaded", "%d media files downloaded", n)
def addFiles(self, zip): % n)
"True if zip is the last in set. Server returns new usn instead."
return self.col.media.addFilesFromZip(zip)
# Remote media syncing # Remote media syncing
########################################################################## ##########################################################################

View file

@ -449,6 +449,8 @@ class Editor(object):
txt = self.mungeHTML(txt) txt = self.mungeHTML(txt)
# misbehaving apps may include a null byte in the text # misbehaving apps may include a null byte in the text
txt = txt.replace("\x00", "") 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 self.note.fields[self.currentField] = txt
if not self.addMode: if not self.addMode:
self.note.flush() self.note.flush()

View file

@ -65,6 +65,23 @@ Anki manual for more information.""")
"other programs are not using the audio device.")) "other programs are not using the audio device."))
if "invalidTempFolder" in error: if "invalidTempFolder" in error:
return showWarning(self.tempFolderMsg()) 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 = _("""\ stdText = _("""\
An error occurred. It may have been caused by a harmless bug, <br> An error occurred. It may have been caused by a harmless bug, <br>
or your deck may have a problem. or your deck may have a problem.

View file

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

View file

@ -71,9 +71,11 @@ def test_changes():
d = getEmptyCol() d = getEmptyCol()
assert d.media._changed() assert d.media._changed()
def added(): 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(added())
assert not list(d.media.removed()) assert not list(removed())
# add a file # add a file
dir = tempfile.mkdtemp(prefix="anki") dir = tempfile.mkdtemp(prefix="anki")
path = os.path.join(dir, u"foo.jpg") path = os.path.join(dir, u"foo.jpg")
@ -83,24 +85,24 @@ def test_changes():
# should have been logged # should have been logged
d.media.findChanges() d.media.findChanges()
assert list(added()) assert list(added())
assert not list(d.media.removed()) assert not list(removed())
# if we modify it, the cache won't notice # if we modify it, the cache won't notice
time.sleep(1) time.sleep(1)
open(path, "w").write("world") open(path, "w").write("world")
assert len(list(added())) == 1 assert len(list(added())) == 1
assert not list(d.media.removed()) assert not list(removed())
# but if we add another file, it will # but if we add another file, it will
time.sleep(1) time.sleep(1)
open(path+"2", "w").write("yo") open(path+"2", "w").write("yo")
d.media.findChanges() d.media.findChanges()
assert len(list(added())) == 2 assert len(list(added())) == 2
assert not list(d.media.removed()) assert not list(removed())
# deletions should get noticed too # deletions should get noticed too
time.sleep(1) time.sleep(1)
os.unlink(path+"2") os.unlink(path+"2")
d.media.findChanges() d.media.findChanges()
assert len(list(added())) == 1 assert len(list(added())) == 1
assert len(list(d.media.removed())) == 1 assert len(list(removed())) == 1
def test_illegal(): def test_illegal():
d = getEmptyCol() d = getEmptyCol()