This commit is contained in:
Soren I. Bjornstad 2013-11-13 10:34:51 -06:00
commit 294f177152
9 changed files with 73 additions and 41 deletions

View File

@ -51,9 +51,8 @@ defaultConf = {
# this is initialized by storage.Collection
class _Collection(object):
debugLog = False
def __init__(self, db, server=False):
def __init__(self, db, server=False, log=False):
self._debugLog = log
self.db = db
self.path = db._path
self._openLog()
@ -695,8 +694,12 @@ select id from notes where mid not in """ + ids2str(self.models.ids()))
self.remNotes(ids)
# for each model
for m in self.models.all():
# cards with invalid ordinal
if m['type'] == MODEL_STD:
# model with missing req specification
if 'req' not in m:
self.models._updateRequired(m)
problems.append(_("Fixed note type: %s") % m['name'])
# cards with invalid ordinal
ids = self.db.list("""
select id from cards where ord not in %s and nid in (
select id from notes where mid = ?)""" %
@ -779,7 +782,7 @@ and queue = 0""", intTime(), self.usn())
##########################################################################
def log(self, *args, **kwargs):
if not self.debugLog:
if not self._debugLog:
return
def customRepr(x):
if isinstance(x, basestring):
@ -794,9 +797,14 @@ and queue = 0""", intTime(), self.usn())
print buf
def _openLog(self):
if not self.debugLog:
if not self._debugLog:
return
lpath = re.sub("\.anki2$", ".log", self.path)
if os.path.exists(lpath) and os.path.getsize(lpath) > 10*1024*1024:
lpath2 = lpath + ".old"
if os.path.exists(lpath2):
os.unlink(lpath2)
os.rename(lpath, lpath2)
self._logHnd = open(lpath, "ab")
def _closeLog(self):

View File

@ -213,6 +213,7 @@ class MediaManager(object):
allRefs.update(noteRefs)
# loop through media folder
unused = []
invalid = []
if local is None:
files = os.listdir(mdir)
else:
@ -225,6 +226,9 @@ class MediaManager(object):
if file.startswith("_"):
# leading _ says to ignore file
continue
if not isinstance(file, unicode):
invalid.append(unicode(file, sys.getfilesystemencoding(), "replace"))
continue
nfcFile = unicodedata.normalize("NFC", file)
# we enforce NFC fs encoding on non-macs; on macs we'll have gotten
# NFD so we use the above variable for comparing references
@ -242,7 +246,7 @@ class MediaManager(object):
else:
allRefs.discard(nfcFile)
nohave = [x for x in allRefs if not x.startswith("_")]
return (nohave, unused)
return (nohave, unused, invalid)
def _normalizeNoteRefs(self, nid):
note = self.col.getNote(nid)
@ -336,6 +340,9 @@ class MediaManager(object):
return re.sub(self._illegalCharReg, "", str)
def hasIllegal(self, str):
# a file that couldn't be decoded to unicode is considered invalid
if not isinstance(str, unicode):
return False
return not not re.search(self._illegalCharReg, str)
# Media syncing - bundling zip files to send to server

View File

@ -1279,6 +1279,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
def buryCards(self, cids):
self.col.log(cids)
self.remFromDyn(cids)
self.removeLrn(cids)
self.col.db.execute("""
update cards set queue=-2,mod=?,usn=? where id in """+ids2str(cids),
@ -1414,7 +1415,6 @@ and due >= ? and queue = 0""" % scids, now, self.col.usn(), shiftby, low)
d.append(dict(now=now, due=due[nid], usn=self.col.usn(), cid=id))
self.col.db.executemany(
"update cards set due=:due,mod=:now,usn=:usn where id = :cid", d)
self.col.log(cids)
def randomizeCards(self, did):
cids = self.col.db.list("select id from cards where did = ?", did)

View File

@ -2,7 +2,10 @@
# Copyright: Damien Elmes <anki@ichi2.net>
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import os, copy, re
import os
import copy
import re
from anki.lang import _
from anki.utils import intTime, json
from anki.db import DB
@ -11,7 +14,8 @@ from anki.consts import *
from anki.stdmodels import addBasicModel, addClozeModel, addForwardReverse, \
addForwardOptionalReverse
def Collection(path, lock=True, server=False, sync=True):
def Collection(path, lock=True, server=False, sync=True, log=False):
"Open a new or existing collection. Path must be unicode."
assert path.endswith(".anki2")
path = os.path.abspath(path)
@ -33,7 +37,7 @@ def Collection(path, lock=True, server=False, sync=True):
else:
db.execute("pragma synchronous = off")
# add db to col and do any remaining upgrades
col = _Collection(db, server)
col = _Collection(db, server, log)
if ver < SCHEMA_VERSION:
_upgrade(col, ver)
elif create:

View File

@ -752,6 +752,7 @@ class MediaSyncer(object):
# back from sanity check to addFiles
s = self.server.mediaSanity()
c = self.mediaSanity()
self.col.log("mediaSanity", c, s)
if c != s:
# if the sanity check failed, force a resync
self.col.media.forceResync()

View File

@ -1,10 +1,14 @@
# -*- coding: utf-8 -*-
# Copyright: Damien Elmes <anki@ichi2.net>
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from anki.lang import _
import re
import os
import urllib2
import ctypes
import urllib
from anki.lang import _
from aqt.qt import *
import re, os, urllib2, ctypes
from anki.utils import stripHTML, isWin, isMac, namedtmp, json, stripHTMLMedia
import anki.sound
from anki.hooks import runHook, runFilter
@ -15,7 +19,6 @@ from aqt.utils import shortcut, showInfo, showWarning, getBase, getFile, \
import aqt
import anki.js
from BeautifulSoup import BeautifulSoup
import urllib
pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif", "svg")
audio = ("wav", "mp3", "ogg", "flac", "mp4", "swf", "mov", "mpeg", "mkv")
@ -313,6 +316,7 @@ class Editor(object):
b.setFixedHeight(20)
b.setFixedWidth(20)
if not native:
if self.plastiqueStyle:
b.setStyle(self.plastiqueStyle)
b.setFocusPolicy(Qt.NoFocus)
else:
@ -333,18 +337,22 @@ class Editor(object):
def setupButtons(self):
self._buttons = {}
# button styles for mac
if not isMac:
self.plastiqueStyle = QStyleFactory.create("plastique")
if not self.plastiqueStyle:
# plastique was removed in qt5
self.plastiqueStyle = QStyleFactory.create("fusion")
self.widget.setStyle(self.plastiqueStyle)
else:
self.plastiqueStyle = None
# icons
self.iconsBox = QHBoxLayout()
if not isMac:
self.iconsBox.setMargin(6)
self.iconsBox.setSpacing(0)
else:
self.iconsBox.setMargin(0)
self.iconsBox.setSpacing(0)
self.iconsBox.setSpacing(14)
self.outerLayout.addLayout(self.iconsBox)
b = self._addButton
b("fields", self.onFields, "",

View File

@ -31,8 +31,6 @@ class AnkiQt(QMainWindow):
self.state = "startup"
aqt.mw = self
self.app = app
from anki.collection import _Collection
_Collection.debugLog = True
if isWin:
self._xpstyle = QStyleFactory.create("WindowsXP")
self.app.setStyle(self._xpstyle)
@ -270,7 +268,7 @@ To import into a password protected profile, please open the profile before atte
def loadCollection(self):
self.hideSchemaMsg = True
try:
self.col = Collection(self.pm.collectionPath())
self.col = Collection(self.pm.collectionPath(), log=True)
except anki.db.Error:
# move back to profile manager
showWarning("""\
@ -915,11 +913,16 @@ will be lost. Continue?"""))
def onCheckMediaDB(self):
self.progress.start(immediate=True)
(nohave, unused) = self.col.media.check()
(nohave, unused, invalid) = self.col.media.check()
self.progress.finish()
# generate report
report = ""
if invalid:
report += _("Invalid encoding; please rename:")
report += "\n" + "\n".join(invalid)
if unused:
if report:
report += "\n\n\n"
report += _(
"In media folder but not used by any cards:")
report += "\n" + "\n".join(unused)

View File

@ -6,8 +6,13 @@
# - Saves in pickles rather than json to easily store Qt window state.
# - Saves in sqlite rather than a flat file so the config can't be corrupted
import os
import random
import cPickle
import locale
import re
from aqt.qt import *
import os, random, cPickle, shutil, locale, re
from anki.db import DB
from anki.utils import isMac, isWin, intTime, checksum
from anki.lang import langs
@ -16,6 +21,7 @@ from aqt import appHelpSite
import aqt.forms
from send2trash import send2trash
metaConf = dict(
ver=0,
updates=True,
@ -186,7 +192,6 @@ documentation for information on using a flash drive.""")
def _loadMeta(self):
path = os.path.join(self.base, "prefs.db")
new = not os.path.exists(path)
self.db = DB(path, text=str)
def recover():
# if we can't load profile, start with a new one
os.rename(path, path+".broken")
@ -195,6 +200,7 @@ documentation for information on using a flash drive.""")
Anki's prefs.db file was corrupt and has been recreated. If you were using multiple \
profiles, please add them back using the same names to recover your cards.""")
try:
self.db = DB(path, text=str)
self.db.execute("""
create table if not exists profiles
(name text primary key, data text not null);""")

View File

@ -280,7 +280,7 @@ class SyncThread(QThread):
self.syncMsg = ""
self.uname = ""
try:
self.col = Collection(self.path)
self.col = Collection(self.path, log=True)
except:
self.fireEvent("corrupt")
return
@ -421,7 +421,7 @@ class SyncThread(QThread):
######################################################################
CHUNK_SIZE = 65536
import httplib, httplib2, errno
import httplib, httplib2
from cStringIO import StringIO
from anki.hooks import runHook
@ -448,6 +448,9 @@ def _incrementalSend(self, data):
httplib.HTTPConnection.send = _incrementalSend
# receiving in httplib2
# this is an augmented version of httplib's request routine that:
# - doesn't assume requests will be tried more than once
# - calls a hook for each chunk of data so we can update the gui
def _conn_request(self, conn, request_uri, method, body, headers):
try:
if conn.sock is None:
@ -463,17 +466,9 @@ def _conn_request(self, conn, request_uri, method, body, headers):
conn.close()
raise
except socket.error, e:
err = 0
if hasattr(e, 'args'):
err = getattr(e, 'args')[0]
else:
err = e.errno
if err == errno.ECONNREFUSED: # Connection refused
conn.close()
raise
except httplib.HTTPException:
# Just because the server closed the connection doesn't apparently mean
# that the server didn't send a response.
if conn.sock is None:
conn.close()
raise
try: