profile tweaks

- handle unpickling of anki 2.0 prefs
- copy the prefs on first load, as python2 is not capable of reading the
protocol 3 pickles we write for proper bytes support
- when there's an error unpickling, write a clean copy of the
preferences instead of forgetting all profiles and starting from scratch
This commit is contained in:
Damien Elmes 2017-08-28 18:35:24 +10:00
parent a96ddfc3fd
commit b0a62838b5

View File

@ -10,7 +10,7 @@ import os
import random
import pickle
import shutil
import io
import locale
import re
@ -132,25 +132,47 @@ a flash drive.""" % self.base)
self.db.list("select name from profiles")
if x != "_global")
def _unpickle(self, data):
class Unpickler(pickle.Unpickler):
def find_class(self, module, name):
fn = super().find_class(module, name)
if module == "sip" and name == "_unpickle_type":
def wrapper(mod, obj, args):
if mod.startswith("PyQt4") and obj == "QByteArray":
# can't trust str objects from python 2
return QByteArray()
return fn(mod, obj, args)
return wrapper
else:
return fn
up = Unpickler(io.BytesIO(data), errors="ignore")
return up.load()
def _pickle(self, obj):
return pickle.dumps(obj, protocol=0)
def load(self, name):
assert name != "_global"
data = self.db.scalar("select cast(data as blob) from profiles where name = ?", name)
# some profiles created in python2 may not decode properly
prof = pickle.loads(data, errors="ignore")
if name != "_global":
self.name = name
self.profile = prof
self.name = name
try:
self.profile = self._unpickle(data)
except:
print("resetting corrupt profile")
self.profile = profileConf.copy()
self.save()
return True
def save(self):
sql = "update profiles set data = ? where name = ?"
self.db.execute(sql, pickle.dumps(self.profile), self.name)
self.db.execute(sql, pickle.dumps(self.meta), "_global")
self.db.execute(sql, self._pickle(self.profile), self.name)
self.db.execute(sql, self._pickle(self.meta), "_global")
self.db.commit()
def create(self, name):
prof = profileConf.copy()
self.db.execute("insert into profiles values (?, ?)",
name, pickle.dumps(prof))
self.db.execute("insert or ignore into profiles values (?, ?)",
name, self._pickle(prof))
self.db.commit()
def remove(self, name):
@ -265,7 +287,11 @@ and no other programs are accessing your profile folders, then try again."""))
return os.path.join(dataDir, "Anki2")
def _loadMeta(self):
opath = os.path.join(self.base, "prefs.db")
path = os.path.join(self.base, "prefs21.db")
if os.path.exists(opath) and not os.path.exists(path):
shutil.copy(opath, path)
new = not os.path.exists(path)
def recover():
# if we can't load profile, start with a new one
@ -274,10 +300,7 @@ and no other programs are accessing your profile folders, then try again."""))
self.db.close()
except:
pass
broken = path+".broken"
if os.path.exists(broken):
os.unlink(broken)
os.rename(path, broken)
os.unlink(path)
QMessageBox.warning(
None, "Preferences Corrupt", """\
Anki's prefs21.db file was corrupt and has been recreated. If you were using multiple \
@ -287,23 +310,22 @@ profiles, please add them back using the same names to recover your cards.""")
self.db.execute("""
create table if not exists profiles
(name text primary key, data text not null);""")
data = self.db.scalar(
"select cast(data as blob) from profiles where name = '_global'")
except:
recover()
return self._loadMeta()
if not new:
# load previously created
# load previously created data
try:
self.meta = pickle.loads(
self.db.scalar(
"select cast(data as blob) from profiles where name = '_global'"))
self.meta = self._unpickle(data)
return
except:
recover()
return self._loadMeta()
print("resetting corrupt _global")
# create a default global profile
self.meta = metaConf.copy()
self.db.execute("insert or replace into profiles values ('_global', ?)",
pickle.dumps(metaConf))
self._pickle(metaConf))
self._setDefaultLang()
return True
@ -378,6 +400,6 @@ please see:
def setLang(self, code):
self.meta['defaultLang'] = code
sql = "update profiles set data = ? where name = ?"
self.db.execute(sql, pickle.dumps(self.meta), "_global")
self.db.execute(sql, self._pickle(self.meta), "_global")
self.db.commit()
anki.lang.setLang(code, local=False)