mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 00:36:38 -04:00
Merge branch 'master' of https://github.com/dae/anki
This commit is contained in:
commit
0914c01706
6 changed files with 63 additions and 16 deletions
|
@ -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")
|
||||
|
|
20
anki/sync.py
20
anki/sync.py
|
@ -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
|
||||
##########################################################################
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue