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()