if this is wrong i blame suzubrah for playing entirely too hype music at 6am in the fkn morning

improve shares/session-db smoketests and error semantics
This commit is contained in:
ed 2025-04-08 05:42:21 +00:00
parent e3043004ba
commit 8e0364efad

View file

@ -73,6 +73,9 @@ from .util import (
ub64enc, ub64enc,
) )
if HAVE_SQLITE3:
import sqlite3
if TYPE_CHECKING: if TYPE_CHECKING:
try: try:
from .mdns import MDNS from .mdns import MDNS
@ -84,6 +87,10 @@ if PY2:
range = xrange # type: ignore range = xrange # type: ignore
VER_SESSION_DB = 1
VER_SHARES_DB = 2
class SvcHub(object): class SvcHub(object):
""" """
Hosts all services which cannot be parallelized due to reliance on monolithic resources. Hosts all services which cannot be parallelized due to reliance on monolithic resources.
@ -406,25 +413,44 @@ class SvcHub(object):
self.log("root", t, 3) self.log("root", t, 3)
return return
import sqlite3 assert sqlite3 # type: ignore # !rm
# policy:
# the sessions-db is whatever, if something looks broken then just nuke it
create = True
db_path = self.args.ses_db db_path = self.args.ses_db
self.log("root", "opening sessions-db %s" % (db_path,)) try:
for n in range(2): create = not os.path.getsize(db_path)
except:
create = True
zs = "creating new" if create else "opening"
self.log("root", "%s sessions-db %s" % (zs, db_path))
for tries in range(2):
sver = 0
try: try:
db = sqlite3.connect(db_path) db = sqlite3.connect(db_path)
cur = db.cursor() cur = db.cursor()
try: try:
zs = "select v from kv where k='sver'"
sver = cur.execute(zs).fetchall()[0][0]
if sver > VER_SESSION_DB:
zs = "this version of copyparty only understands session-db v%d and older; the db is v%d"
raise Exception(zs % (VER_SESSION_DB, sver))
cur.execute("select count(*) from us").fetchone() cur.execute("select count(*) from us").fetchone()
create = False
break
except: except:
pass if sver:
raise
sver = 1
self._create_session_db(cur)
self._verify_session_db(cur, sver)
break
except Exception as ex: except Exception as ex:
if n: if tries or sver > VER_SESSION_DB:
raise raise
t = "sessions-db corrupt; deleting and recreating: %r" t = "sessions-db is unusable; deleting and recreating: %r"
self.log("root", t % (ex,), 3) self.log("root", t % (ex,), 3)
try: try:
cur.close() # type: ignore cur.close() # type: ignore
@ -436,6 +462,7 @@ class SvcHub(object):
pass pass
os.unlink(db_path) os.unlink(db_path)
def _create_session_db(self, cur: "sqlite3.Cursor") -> None:
sch = [ sch = [
r"create table kv (k text, v int)", r"create table kv (k text, v int)",
r"create table us (un text, si text, t0 int)", r"create table us (un text, si text, t0 int)",
@ -445,15 +472,37 @@ class SvcHub(object):
r"create index us_t0 on us(t0)", r"create index us_t0 on us(t0)",
r"insert into kv values ('sver', 1)", r"insert into kv values ('sver', 1)",
] ]
for cmd in sch:
cur.execute(cmd)
self.log("root", "created new sessions-db")
assert db # type: ignore # !rm def _verify_session_db(self, cur: "sqlite3.Cursor", sver: int) -> None:
assert cur # type: ignore # !rm # ensure writable (maybe owned by other user)
if create: db = cur.connection
for cmd in sch:
cur.execute(cmd)
self.log("root", "created new sessions-db")
db.commit()
try:
zil = cur.execute("select v from kv where k='pid'").fetchall()
if len(zil) > 1:
raise Exception()
owner = zil[0][0]
except:
owner = 0
vars = (("pid", os.getpid()), ("ts", int(time.time() * 1000)))
if owner:
# wear-estimate: 2 cells; offsets 0x10, 0x50, 0x19720
for k, v in vars:
cur.execute("update kv set v=? where k=?", (v, k))
else:
# wear-estimate: 3~4 cells; offsets 0x10, 0x50, 0x19180, 0x19710, 0x36000, 0x360b0, 0x36b90
for k, v in vars:
cur.execute("insert into kv values(?, ?)", (k, v))
if sver < VER_SESSION_DB:
cur.execute("delete from kv where k='sver'")
cur.execute("insert into kv values('sver',?)", (VER_SESSION_DB,))
db.commit()
cur.close() cur.close()
db.close() db.close()
@ -475,34 +524,41 @@ class SvcHub(object):
al.shr = "/%s/" % (al.shr,) al.shr = "/%s/" % (al.shr,)
al.shr1 = al.shr[1:] al.shr1 = al.shr[1:]
create = True # policy:
modified = False # the shares-db is important, so panic if something is wrong
db_path = self.args.shr_db db_path = self.args.shr_db
self.log("root", "opening shares-db %s" % (db_path,)) try:
for n in range(2): create = not os.path.getsize(db_path)
try: except:
db = sqlite3.connect(db_path) create = True
cur = db.cursor() zs = "creating new" if create else "opening"
try: self.log("root", "%s shares-db %s" % (zs, db_path))
cur.execute("select count(*) from sh").fetchone()
create = False sver = 0
break try:
except: db = sqlite3.connect(db_path)
pass cur = db.cursor()
except Exception as ex: if not create:
if n: zs = "select v from kv where k='sver'"
raise sver = cur.execute(zs).fetchall()[0][0]
t = "shares-db corrupt; deleting and recreating: %r" if sver > VER_SHARES_DB:
self.log("root", t % (ex,), 3) zs = "this version of copyparty only understands shares-db v%d and older; the db is v%d"
try: raise Exception(zs % (VER_SHARES_DB, sver))
cur.close() # type: ignore
except: cur.execute("select count(*) from sh").fetchone()
pass except Exception as ex:
try: t = "could not open shares-db; will now panic...\nthe following database must be repaired or deleted before you can launch copyparty:\n%s\n\nERROR: %s\n\nadditional details:\n%s\n"
db.close() # type: ignore self.log("root", t % (db_path, ex, min_ex()), 1)
except: raise
pass
os.unlink(db_path) try:
zil = cur.execute("select v from kv where k='pid'").fetchall()
if len(zil) > 1:
raise Exception()
owner = zil[0][0]
except:
owner = 0
sch1 = [ sch1 = [
r"create table kv (k text, v int)", r"create table kv (k text, v int)",
@ -514,34 +570,37 @@ class SvcHub(object):
r"create index sf_k on sf(k)", r"create index sf_k on sf(k)",
r"create index sh_k on sh(k)", r"create index sh_k on sh(k)",
r"create index sh_t1 on sh(t1)", r"create index sh_t1 on sh(t1)",
r"insert into kv values ('sver', 2)",
] ]
assert db # type: ignore # !rm assert db # type: ignore # !rm
assert cur # type: ignore # !rm assert cur # type: ignore # !rm
if create: if not sver:
dver = 2 sver = VER_SHARES_DB
modified = True
for cmd in sch1 + sch2: for cmd in sch1 + sch2:
cur.execute(cmd) cur.execute(cmd)
self.log("root", "created new shares-db") self.log("root", "created new shares-db")
else:
(dver,) = cur.execute("select v from kv where k = 'sver'").fetchall()[0]
if dver == 1: if sver == 1:
modified = True
for cmd in sch2: for cmd in sch2:
cur.execute(cmd) cur.execute(cmd)
cur.execute("update sh set st = 0") cur.execute("update sh set st = 0")
self.log("root", "shares-db schema upgrade ok") self.log("root", "shares-db schema upgrade ok")
if modified: if sver < VER_SHARES_DB:
for cmd in [ cur.execute("delete from kv where k='sver'")
r"delete from kv where k = 'sver'", cur.execute("insert into kv values('sver',?)", (VER_SHARES_DB,))
r"insert into kv values ('sver', %d)" % (2,),
]:
cur.execute(cmd)
db.commit()
vars = (("pid", os.getpid()), ("ts", int(time.time() * 1000)))
if owner:
# wear-estimate: same as sessions-db
for k, v in vars:
cur.execute("update kv set v=? where k=?", (v, k))
else:
for k, v in vars:
cur.execute("insert into kv values(?, ?)", (k, v))
db.commit()
cur.close() cur.close()
db.close() db.close()