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)
|
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")
|
||||||
|
|
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 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
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue