169 lines
6.8 KiB
Python
169 lines
6.8 KiB
Python
|
|
from datetime import datetime, timezone, timedelta
|
||
|
|
import secrets
|
||
|
|
|
||
|
|
import db
|
||
|
|
|
||
|
|
|
||
|
|
def get_device(device_id: str):
|
||
|
|
conn = db.get_conn()
|
||
|
|
row = conn.execute("SELECT * FROM devices WHERE device_id=?", (device_id,)).fetchone()
|
||
|
|
conn.close()
|
||
|
|
return row
|
||
|
|
|
||
|
|
|
||
|
|
def get_license(license_id: str):
|
||
|
|
conn = db.get_conn()
|
||
|
|
row = conn.execute("SELECT * FROM licenses WHERE license_id=?", (license_id,)).fetchone()
|
||
|
|
conn.close()
|
||
|
|
return row
|
||
|
|
|
||
|
|
|
||
|
|
def touch_device(device_id: str):
|
||
|
|
conn = db.get_conn()
|
||
|
|
conn.execute("UPDATE devices SET last_seen_at=datetime('now') WHERE device_id=?", (device_id,))
|
||
|
|
conn.commit()
|
||
|
|
conn.close()
|
||
|
|
|
||
|
|
|
||
|
|
def issue_device(app_id: str, channel: str, license_key_hash: str, installation_id: str, machine_hash: str, ip: str):
|
||
|
|
now = datetime.now(timezone.utc)
|
||
|
|
conn = db.get_conn()
|
||
|
|
conn.execute("BEGIN IMMEDIATE")
|
||
|
|
try:
|
||
|
|
channel_row = conn.execute("SELECT * FROM channels WHERE app_id=? AND channel_code=?", (app_id, channel)).fetchone()
|
||
|
|
if not channel_row:
|
||
|
|
conn.rollback()
|
||
|
|
return {"status": "channel_not_found"}
|
||
|
|
if not channel_row["enabled"]:
|
||
|
|
conn.rollback()
|
||
|
|
return {"status": "channel_disabled"}
|
||
|
|
license_row = conn.execute("SELECT * FROM licenses WHERE license_key_hash=?", (license_key_hash,)).fetchone()
|
||
|
|
try:
|
||
|
|
expiry = datetime.fromisoformat((license_row["valid_until"] if license_row else "").replace("Z", "+00:00"))
|
||
|
|
except ValueError:
|
||
|
|
expiry = now - timedelta(seconds=1)
|
||
|
|
if (
|
||
|
|
not license_row
|
||
|
|
or license_row["status"] != "active"
|
||
|
|
or expiry <= now
|
||
|
|
or license_row["app_id"] != app_id
|
||
|
|
or license_row["channel_code"] != channel
|
||
|
|
):
|
||
|
|
conn.rollback()
|
||
|
|
return {"status": "license_invalid"}
|
||
|
|
|
||
|
|
device_row = conn.execute("SELECT * FROM devices WHERE app_id=? AND installation_id=?", (app_id, installation_id)).fetchone()
|
||
|
|
if device_row and device_row["disabled"]:
|
||
|
|
conn.rollback()
|
||
|
|
return {"status": "device_disabled", "reason": device_row["disabled_reason"] or "设备已被禁用"}
|
||
|
|
if device_row and device_row["license_id"] not in ("", license_row["license_id"]):
|
||
|
|
conn.rollback()
|
||
|
|
return {"status": "license_conflict"}
|
||
|
|
|
||
|
|
device_id = device_row["device_id"] if device_row else "dev_" + secrets.token_hex(16)
|
||
|
|
seq = int(device_row["credential_seq"]) if device_row else 1
|
||
|
|
bound = conn.execute("SELECT 1 FROM license_devices WHERE license_id=? AND device_id=?", (license_row["license_id"], device_id)).fetchone()
|
||
|
|
used = conn.execute("SELECT COUNT(*) FROM license_devices WHERE license_id=?", (license_row["license_id"],)).fetchone()[0]
|
||
|
|
if not bound and used >= int(license_row["max_devices"]):
|
||
|
|
conn.rollback()
|
||
|
|
return {"status": "license_device_limit"}
|
||
|
|
|
||
|
|
if device_row:
|
||
|
|
conn.execute(
|
||
|
|
"UPDATE devices SET machine_hash=?,license_id=?,channel=?,last_seen_at=datetime('now'),last_ip=? WHERE device_id=?",
|
||
|
|
(machine_hash, license_row["license_id"], channel, ip, device_id),
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
conn.execute(
|
||
|
|
"INSERT INTO devices(device_id,app_id,installation_id,machine_hash,credential_seq,last_ip,license_id,channel) VALUES(?,?,?,?,?,?,?,?)",
|
||
|
|
(device_id, app_id, installation_id, machine_hash, seq, ip, license_row["license_id"], channel),
|
||
|
|
)
|
||
|
|
conn.execute("INSERT OR IGNORE INTO license_devices(license_id,device_id) VALUES(?,?)", (license_row["license_id"], device_id))
|
||
|
|
conn.commit()
|
||
|
|
return {"status": "ok", "device_id": device_id, "license": license_row, "credential_seq": seq, "issued_at": now}
|
||
|
|
except Exception:
|
||
|
|
conn.rollback()
|
||
|
|
raise
|
||
|
|
finally:
|
||
|
|
conn.close()
|
||
|
|
|
||
|
|
|
||
|
|
def get_update_context(app_id: str, channel: str):
|
||
|
|
conn = db.get_conn()
|
||
|
|
try:
|
||
|
|
channel_row = conn.execute("SELECT * FROM channels WHERE app_id=? AND channel_code=?", (app_id, channel)).fetchone()
|
||
|
|
if not channel_row:
|
||
|
|
return {"status": "channel_not_found"}
|
||
|
|
if not channel_row["enabled"]:
|
||
|
|
return {"status": "channel_disabled"}
|
||
|
|
version = conn.execute(
|
||
|
|
"SELECT * FROM versions WHERE app_id=? AND channel=? AND latest=1",
|
||
|
|
(app_id, channel),
|
||
|
|
).fetchone()
|
||
|
|
policy = conn.execute(
|
||
|
|
"SELECT * FROM version_policies WHERE app_id=? AND channel=?",
|
||
|
|
(app_id, channel),
|
||
|
|
).fetchone()
|
||
|
|
return {"status": "ok", "version": version, "policy": policy}
|
||
|
|
finally:
|
||
|
|
conn.close()
|
||
|
|
|
||
|
|
|
||
|
|
def get_download_files(version_id: int, app_id: str, channel: str, version: str, identity: dict, ip: str, user_agent: str):
|
||
|
|
conn = db.get_conn()
|
||
|
|
version_row = conn.execute("SELECT app_id,channel,version FROM versions WHERE id=?", (version_id,)).fetchone()
|
||
|
|
if not version_row or version_row["app_id"] != app_id or version_row["channel"] != channel or version_row["version"] != version:
|
||
|
|
conn.close()
|
||
|
|
return {"status": "not_found"}
|
||
|
|
rows = conn.execute("SELECT path, sha256, size FROM version_files WHERE version_id=?", (version_id,)).fetchall()
|
||
|
|
conn.executemany(
|
||
|
|
"""INSERT INTO download_logs(device_id,license_id,app_id,version,channel_code,file_path,file_size,result,ip,user_agent)
|
||
|
|
VALUES(?,?,?,?,?,?,?,?,?,?)""",
|
||
|
|
[
|
||
|
|
(identity["device_id"], identity["license_id"], app_id, version, channel, row["path"], row["size"], "authorized", ip, user_agent)
|
||
|
|
for row in rows
|
||
|
|
],
|
||
|
|
)
|
||
|
|
conn.commit()
|
||
|
|
conn.close()
|
||
|
|
return {"status": "ok", "files": rows}
|
||
|
|
|
||
|
|
|
||
|
|
def insert_download_report_logs(values: list[tuple]) -> int:
|
||
|
|
conn = db.get_conn()
|
||
|
|
if values:
|
||
|
|
conn.executemany(
|
||
|
|
"INSERT INTO download_logs(device_id,license_id,app_id,version,channel_code,file_path,file_size,result,ip,user_agent) VALUES(?,?,?,?,?,?,?,?,?,?)",
|
||
|
|
values,
|
||
|
|
)
|
||
|
|
conn.commit()
|
||
|
|
conn.close()
|
||
|
|
return len(values)
|
||
|
|
|
||
|
|
|
||
|
|
def get_manifest_version_files(version_id: int):
|
||
|
|
conn = db.get_conn()
|
||
|
|
version = conn.execute(
|
||
|
|
"SELECT app_id, channel, version, create_time FROM versions WHERE id=?",
|
||
|
|
(version_id,),
|
||
|
|
).fetchone()
|
||
|
|
if not version:
|
||
|
|
conn.close()
|
||
|
|
return None, []
|
||
|
|
rows = conn.execute("SELECT path, sha256, size FROM version_files WHERE version_id=?", (version_id,)).fetchall()
|
||
|
|
conn.close()
|
||
|
|
return version, rows
|
||
|
|
|
||
|
|
|
||
|
|
def insert_upgrade_log(device_id: str, from_version: str, to_version: str, result: str):
|
||
|
|
conn = db.get_conn()
|
||
|
|
conn.execute(
|
||
|
|
"""
|
||
|
|
INSERT INTO upgrade_logs(device_id,from_ver,to_ver,result,create_time)
|
||
|
|
VALUES (?,?,?,?,datetime('now'))
|
||
|
|
""",
|
||
|
|
(device_id, from_version, to_version, result),
|
||
|
|
)
|
||
|
|
conn.commit()
|
||
|
|
conn.close()
|