Files
simonandCursor 1e1111c90f Qortino AI QDN assembly, share-to-Quitter, categorized saves, File shim fixes
Safety snapshot of mobile work before merging upstream chat-v2:
- Qortino AI: QDN pack discovery/download (Q-Share ids), external app
  storage, install validation, teach/report Q-Mail, PDF thumbs+zoom
- Android share target "Quitter" with native ShareReceiver plugin
- Categorized device saves (Images/Videos/Audio/Documents/Apps/GO state)
- createNamedFile helper: cordova-plugin-file clobbers global File

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-08 12:47:44 +00:00

102 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""Local QDN stand-in for Qortino Assemble QA (adb reverse tcp:18765 tcp:18765)."""
from __future__ import annotations
import mimetypes
import os
import urllib.parse
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
ROOT = os.environ.get(
"QORTINO_PUBLISH_DIR",
os.path.expanduser("~/Desktop/Qortino-QShare-publish"),
)
PORT = int(os.environ.get("QORTINO_MOCK_PORT", "18765"))
ROUTES = {
"/arbitrary/FILE/Qortino%20AI/qortino_pack_standard": "Qortino-Standard.zip",
"/arbitrary/FILE/Qortino AI/qortino_pack_standard": "Qortino-Standard.zip",
"/arbitrary/FILE/Qortino%20AI/qortino_pack_lite": "Qortino-Lite.zip",
"/arbitrary/FILE/Qortino AI/qortino_pack_lite": "Qortino-Lite.zip",
"/arbitrary/DOCUMENT/Qortino%20AI/qortino_agent_manifest": "qortino_agent_manifest.json",
"/arbitrary/DOCUMENT/Qortino AI/qortino_agent_manifest": "qortino_agent_manifest.json",
}
class Handler(BaseHTTPRequestHandler):
def _cors(self):
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "*")
self.send_header("Access-Control-Expose-Headers", "Content-Length, Content-Type")
def _resolve(self):
parsed = urllib.parse.urlparse(self.path)
path = parsed.path
candidates = [path, urllib.parse.unquote(path)]
for key in candidates:
filename = ROUTES.get(key)
if filename:
full = os.path.join(ROOT, filename)
if os.path.isfile(full):
return full
return None
return False
def do_OPTIONS(self): # noqa: N802
self.send_response(204)
self._cors()
self.end_headers()
def do_HEAD(self): # noqa: N802
full = self._resolve()
if full is False:
self.send_error(404, f"No mock resource for {self.path}")
return
if full is None:
self.send_error(404, "Missing file")
return
size = os.path.getsize(full)
ctype = mimetypes.guess_type(full)[0] or "application/octet-stream"
self.send_response(200)
self._cors()
self.send_header("Content-Type", ctype)
self.send_header("Content-Length", str(size))
self.send_header("Accept-Ranges", "bytes")
self.end_headers()
def do_GET(self): # noqa: N802
full = self._resolve()
if full is False:
self.send_error(404, f"No mock resource for {self.path}")
return
if full is None:
self.send_error(404, "Missing file")
return
size = os.path.getsize(full)
ctype = mimetypes.guess_type(full)[0] or "application/octet-stream"
self.send_response(200)
self._cors()
self.send_header("Content-Type", ctype)
self.send_header("Content-Length", str(size))
self.send_header("Accept-Ranges", "bytes")
self.end_headers()
with open(full, "rb") as fh:
while True:
chunk = fh.read(1024 * 1024)
if not chunk:
break
self.wfile.write(chunk)
def log_message(self, fmt, *args):
print(f"[qortino-mock] {self.address_string()} {fmt % args}")
def main():
print(f"Serving Qortino QDN mock from {ROOT} on 0.0.0.0:{PORT}")
ThreadingHTTPServer(("0.0.0.0", PORT), Handler).serve_forever()
if __name__ == "__main__":
main()