diff --git a/API/data/alldebrid.json b/API/data/alldebrid.json index c2f1d35..fea52ee 100644 --- a/API/data/alldebrid.json +++ b/API/data/alldebrid.json @@ -353,7 +353,7 @@ "filedot\\.(xyz|to|top)/([0-9a-zA-Z]{12})" ], "regexp": "filedot\\.(xyz|to|top)/([0-9a-zA-Z]{12})", - "status": false + "status": true }, "filefactory": { "name": "filefactory", @@ -786,7 +786,7 @@ "(upl\\.wf/d/[0-9a-zA-Z]+)" ], "regexp": "((world\\-files\\.com/[0-9a-zA-Z]{12}))|((upl\\.wf/d/[0-9a-zA-Z]+))", - "status": false, + "status": true, "hardRedirect": [ "world\\-files\\.com/([0-9a-zA-Z]{12})" ] diff --git a/Store/ZeroTier.py b/Store/ZeroTier.py index d971152..6fa6221 100644 --- a/Store/ZeroTier.py +++ b/Store/ZeroTier.py @@ -277,15 +277,55 @@ class ZeroTier(Store): debug(f"Hydrus get_file failed: {exc}") return None - # remote storage: try metadata endpoint - res = self._request_remote("GET", f"/files/{file_hash}") - if isinstance(res, dict): - # remote server returns a 'path' to the file (server-local path) - p = res.get("path") or res.get("file") or None - if isinstance(p, str) and p.startswith("http"): - return p - return p - return None + # remote storage: return download URL + base = self._ensure_client() + if not base or not isinstance(base, str): + return None + + url = f"{base.rstrip('/')}/files/raw/{file_hash}" + if self._api_key: + sep = "&" if "?" in url else "?" + url += f"{sep}api_key={self._api_key}" + return url + + def download_to_temp(self, file_hash: str, temp_root: Optional[Path] = None) -> Optional[Path]: + """Download a file from the remote peer to a local temporary file.""" + import os + import httpx + import tempfile + + if self._service == "hydrus": + return None + + url = self.get_file(file_hash) + if not url or not isinstance(url, str) or not url.startswith("http"): + return None + + try: + # Use provided temp_root or system temp + if temp_root: + temp_root.mkdir(parents=True, exist_ok=True) + fd, tmp_path = tempfile.mkstemp(dir=str(temp_root), suffix=".tmp") + else: + fd, tmp_path = tempfile.mkstemp(suffix=".tmp") + + os_fd = os.fdopen(fd, 'wb') + + headers = {} + if self._api_key: + headers["X-API-Key"] = self._api_key + + with httpx.stream("GET", url, headers=headers, timeout=self._timeout) as r: + r.raise_for_status() + for chunk in r.iter_bytes(): + os_fd.write(chunk) + + os_fd.close() + return Path(tmp_path) + + except Exception as exc: + debug(f"ZeroTier download_to_temp failed for {file_hash}: {exc}") + return None def add_file(self, file_path: Path, **kwargs: Any) -> Optional[str]: """Upload a local file to the remote ZeroTier peer (supports 'remote' and 'hydrus' services). diff --git a/cmdlet/add_file.py b/cmdlet/add_file.py index c7ac0ee..6d764b7 100644 --- a/cmdlet/add_file.py +++ b/cmdlet/add_file.py @@ -1017,34 +1017,34 @@ class Add_File(Cmdlet): except Exception: pass - # PRIORITY 1b: Try hash+store from result dict (fetch from backend) - if isinstance(result, dict): - r_hash = result.get("hash") - r_store = result.get("store") - if r_hash and r_store: - try: - store = store_instance - if not store: - store = Store(config) + # PRIORITY 1b: Try hash+store from result (fetch from backend) + r_hash = get_field(result, "hash") or get_field(result, "file_hash") + r_store = get_field(result, "store") + + if r_hash and r_store: + try: + store = store_instance + if not store: + store = Store(config) + + if r_store in store.list_backends(): + backend = store[r_store] + # Try direct access (Path) + mp = backend.get_file(r_hash) + if isinstance(mp, Path) and mp.exists(): + pipe_obj.path = str(mp) + return mp, str(r_hash), None - if r_store in store.list_backends(): - backend = store[r_store] - # Try direct access (Path) - mp = backend.get_file(r_hash) - if isinstance(mp, Path) and mp.exists(): - pipe_obj.path = str(mp) - return mp, str(r_hash), None - - # Try download to temp - if isinstance(mp, str) and mp.strip(): - dl_path, tmp_dir = Add_File._maybe_download_backend_file( - backend, str(r_hash), pipe_obj - ) - if dl_path and dl_path.exists(): - pipe_obj.path = str(dl_path) - return dl_path, str(r_hash), tmp_dir - except Exception: - pass + # Try download to temp + if isinstance(mp, str) and mp.strip(): + dl_path, tmp_dir = Add_File._maybe_download_backend_file( + backend, str(r_hash), pipe_obj + ) + if dl_path and dl_path.exists(): + pipe_obj.path = str(dl_path) + return dl_path, str(r_hash), tmp_dir + except Exception: + pass # PRIORITY 2: Generic Coercion (Path arg > PipeObject > Result) candidate: Optional[Path] = None @@ -1130,7 +1130,6 @@ class Add_File(Cmdlet): return files_info - @staticmethod @staticmethod def _validate_source(media_path: Optional[Path], allow_all_extensions: bool = False) -> bool: """Validate that the source file exists and is supported. diff --git a/scripts/remote_storage_server.py b/scripts/remote_storage_server.py index 65ffcd1..c852c68 100644 --- a/scripts/remote_storage_server.py +++ b/scripts/remote_storage_server.py @@ -161,7 +161,7 @@ def create_app(): "Flask not installed. Install with: pip install flask flask-cors" ) - from flask import Flask, request, jsonify + from flask import Flask, request, jsonify, send_file from flask_cors import CORS app = Flask(__name__) @@ -325,12 +325,32 @@ def create_app(): logger.error(f"Get metadata error: {e}", exc_info=True) return jsonify({"error": f"Failed to get metadata: {str(e)}"}), 500 + @app.route("/files/raw/", methods=["GET"]) + @require_auth() + @require_storage() + def download_file(file_hash: str): + """Download a raw file by hash.""" + try: + search_db = get_db(STORAGE_PATH) + db = search_db.db + if not db: + return jsonify({"error": "Database unavailable"}), 500 + + file_path = db.search_hash(file_hash) + + if not file_path or not file_path.exists(): + return jsonify({"error": "File not found"}), 404 + + return send_file(file_path) + except Exception as e: + logger.error(f"Download error: {e}", exc_info=True) + return jsonify({"error": f"Download failed: {str(e)}"}), 500 + @app.route("/files/index", methods=["POST"]) @require_auth() @require_storage() def index_file(): """Index a new file in the storage.""" - from API.folder import API_folder_store from SYS.utils import sha256_file data = request.get_json() or {} @@ -347,28 +367,32 @@ def create_app(): if not file_path.exists(): return jsonify({"error": "File does not exist"}), 404 - with API_folder_store(STORAGE_PATH) as db: - db.get_or_create_file_entry(file_path) + search_db = get_db(STORAGE_PATH) + db = search_db.db + if not db: + return jsonify({"error": "Database unavailable"}), 500 + + db.get_or_create_file_entry(file_path) - if tags: - db.add_tags(file_path, tags) + if tags: + db.add_tags(file_path, tags) - if url: - db.add_url(file_path, url) + if url: + db.add_url(file_path, url) - file_hash = sha256_file(file_path) + file_hash = sha256_file(file_path) - return ( - jsonify( - { - "hash": file_hash, - "path": str(file_path), - "tags_added": len(tags), - "url_added": len(url), - } - ), - 201, - ) + return ( + jsonify( + { + "hash": file_hash, + "path": str(file_path), + "tags_added": len(tags), + "url_added": len(url), + } + ), + 201, + ) except Exception as e: logger.error(f"Index error: {e}", exc_info=True) return jsonify({"error": f"Indexing failed: {str(e)}"}), 500