From 43e95c9d3e83833843fbf49750dab717ec435150 Mon Sep 17 00:00:00 2001 From: Kibi Kelburton Date: Wed, 12 Aug 2026 17:14:59 +0200 Subject: [PATCH] add queue functionality to upload many items and update readme to include basic installation guide --- README.md | 59 ++---- f0ckm-uploader-servicemenu.desktop | 2 +- f0ckm-uploader.desktop | 4 +- gui.py | 317 ++++++++++++++++++----------- install.sh | 61 +++++- uploader.sh | 3 +- 6 files changed, 277 insertions(+), 169 deletions(-) diff --git a/README.md b/README.md index de75983..536aae6 100644 --- a/README.md +++ b/README.md @@ -34,55 +34,34 @@ Ensure the following system dependencies are installed: --- -## Quick Setup & Installation +## ⚡ Quick Start (3-Step Setup) -### 1. Clone the Repository +Setting up f0ckm Uploader takes less than a minute: + +### 1. Run the Automated Installer ```bash git clone https://git.lat/kibi/f0ckm-uploader cd f0ckm-uploader -``` - -### 2. Install Python Dependencies - -```bash -pip install PySide6 -``` - -_(Or install via system package manager: `sudo dnf install python3-pyside6` / `sudo apt install python3-pyside6`)_ - -### 3. Environment Configuration _(Optional)_ - -You can set up a `.env` file prior to installation, or configure settings later via the GUI: - -```bash -cp .env.example .env -``` - -Edit `.env`: - -```env -API_URL=https://your-f0ckm-instance.com/api/v2/upload -API_KEY=your_secret_api_key -DEFAULT_RATING=s -DEFAULT_TAGS= -DEFAULT_VISIBILITY=0 -DEFAULT_IS_OC=false -``` - -### 4. Run the Installer - -```bash -chmod +x install.sh ./install.sh ``` -The installer will automatically: +### 2. Enter Your API Credentials -1. Copy binaries to `~/.local/bin/f0ckm-uploader-gui` and `~/.local/bin/f0ckm-uploader`. -2. Generate multi-resolution icons (`32x32`, `48x48`, `64x64`, `128x128`, `256x256`) for KRunner & Start Menu compatibility. -3. Register the Dolphin context menu action in `~/.local/share/kio/servicemenus/`. -4. Register the `.desktop` application launchers and rebuild the KDE Plasma cache (`kbuildsycoca6`). +When `./install.sh` finishes, the **f0ckm Uploader Settings** GUI window will open automatically. +- Fill in your **API URL** (e.g. `https://your-f0ckm-site.com/api/v2/upload`) +- Paste your **API Key** +- Click **Test Connection** -> **Save Settings** + +### 3. Start Uploading! + +- **System Tray**: Click the tray icon to take a screenshot and upload instantly. +- **Dolphin**: Right-click any file -> **Actions** -> **Upload to f0ckm**. +- **Browser**: Install the extension in `browser-extension/` to right-click web media and upload directly. + +--- + +The installer handles dependency checking (PySide6), desktop entry creation, Dolphin context menus, multi-resolution application icons, and system tray startup automatically. --- diff --git a/f0ckm-uploader-servicemenu.desktop b/f0ckm-uploader-servicemenu.desktop index 3a42cdc..84f9b29 100644 --- a/f0ckm-uploader-servicemenu.desktop +++ b/f0ckm-uploader-servicemenu.desktop @@ -9,4 +9,4 @@ X-KDE-Submenu=Upload [Desktop Action upload_custom_api] Name=Upload to f0ckm Icon=cloud-upload-symbolic -Exec=__EXEC_PATH__ "%f" +Exec=__EXEC_PATH__ %F diff --git a/f0ckm-uploader.desktop b/f0ckm-uploader.desktop index 187624e..035088b 100644 --- a/f0ckm-uploader.desktop +++ b/f0ckm-uploader.desktop @@ -2,7 +2,7 @@ Type=Application Name=f0ckm Uploader (Internal) Comment=Upload to f0ckm (Backend registration) -Exec=__EXEC_PATH__ +Exec=__EXEC_PATH__ %F Icon=f0ckm-uploader Terminal=false Categories=Utility;Network; @@ -12,5 +12,5 @@ InitialPreference=0 [Desktop Action upload] Name=Upload to Custom API -Exec=__EXEC_PATH__ +Exec=__EXEC_PATH__ %F Icon=f0ckm-uploader diff --git a/gui.py b/gui.py index 1d53be5..dfb1f44 100644 --- a/gui.py +++ b/gui.py @@ -73,6 +73,34 @@ def parse_upload_target(arg: str) -> tuple[str, bool]: print(f"[f0ckm-gui] [PARSE] File path parsed: '{arg}' -> norm='{norm}' (is_url=False)", flush=True) return (norm, False) +def parse_cli_args(args: list[str]) -> tuple[str, list[str]]: + if not args: + return ("show", []) + + targets = [] + action = "show" + + for arg in args: + arg_str = arg.strip() + if not arg_str: + continue + if arg_str in ("--spectacle", "-s"): + action = "spectacle" + elif arg_str.startswith("--upload="): + action = "upload" + t, _ = parse_upload_target(arg_str[9:]) + if t: + targets.append(t) + elif arg_str == "--upload": + action = "upload" + else: + t, _ = parse_upload_target(arg_str) + if t: + action = "upload" + targets.append(t) + + return (action, targets) + class LocalHTTPBridge(QObject): request_upload = Signal(str) @@ -1417,6 +1445,14 @@ class SystemTrayApp(QObject): self.network_manager = QNetworkAccessManager(self) self.current_reply = None + # Queue system state + self.upload_queue = [] # List of tuples: (target_str, is_url) + self.is_uploading = False + self.active_thread = None + self.current_target = None + self.total_queue_count = 0 + self.current_item_index = 0 + self.menu = QMenu() # Header entry @@ -1469,6 +1505,63 @@ class SystemTrayApp(QObject): self.settings_dialog = None self.gallery_window = None + def enqueue_targets(self, targets): + if isinstance(targets, str): + targets = [targets] + valid_targets = [] + for t in targets: + target_str, is_url = parse_upload_target(t) + if target_str: + valid_targets.append((target_str, is_url)) + + if not valid_targets: + return + + if not self.is_uploading and len(self.upload_queue) == 0: + self.total_queue_count = len(valid_targets) + self.current_item_index = 0 + self.upload_queue.extend(valid_targets) + self.process_next_in_queue() + else: + self.upload_queue.extend(valid_targets) + self.total_queue_count += len(valid_targets) + print(f"[f0ckm-gui] [QUEUE] Enqueued {len(valid_targets)} items. Total in queue: {len(self.upload_queue)} (Batch total: {self.total_queue_count})", flush=True) + + def process_next_in_queue(self): + if len(self.upload_queue) == 0: + self.is_uploading = False + self.current_target = None + self.active_thread = None + self.total_queue_count = 0 + self.current_item_index = 0 + self.abort_action.setEnabled(False) + self.tray.setIcon(self.default_icon) + self.tray.setToolTip("f0ckm Uploader (Click to capture & upload)") + return + + target_tuple = self.upload_queue.pop(0) + target_str, is_url = target_tuple + self.is_uploading = True + self.current_target = target_str + self.current_item_index += 1 + self.abort_action.setEnabled(True) + + print(f"[f0ckm-gui] [QUEUE ITEM {self.current_item_index}/{self.total_queue_count}] Processing target: '{target_str}' (is_url={is_url})", flush=True) + + if is_url: + self.download_and_upload_url(target_str) + else: + self.upload_file_direct(target_str) + + def _on_single_task_complete(self): + if self.active_thread: + try: + self.active_thread.deleteLater() + except Exception: + pass + self.active_thread = None + QTimer.singleShot(50, self.process_next_in_queue) + def on_notification_clicked(self): url = getattr(self, "last_uploaded_url", "") if url: @@ -1527,36 +1620,34 @@ class SystemTrayApp(QObject): def on_capture_and_upload(self): screenshot_dir = os.path.expanduser("~/Pictures/Screenshots") os.makedirs(screenshot_dir, exist_ok=True) - save_path = os.path.join(screenshot_dir, f"screenshot_{time.strftime('%Y%m%d_%H%M%S')}.png") + ts = time.strftime('%Y%m%d_%H%M%S') + ms = int(time.time() * 1000) % 1000 + save_path = os.path.join(screenshot_dir, f"screenshot_{ts}_{ms:03d}.png") try: - self.spectacle_proc = QProcess(self) - self.spectacle_proc.finished.connect(lambda exit_code, exit_status: self._on_spectacle_finished(save_path, exit_code)) - self.spectacle_proc.start("spectacle", ["-r", "-b", "-n", "-o", save_path]) + proc = QProcess(self) + proc.finished.connect(lambda exit_code, exit_status, p=proc, sp=save_path: self._on_spectacle_finished(p, sp, exit_code)) + proc.start("spectacle", ["-r", "-b", "-n", "-o", save_path]) except Exception as e: self.show_message("Upload Error", f"Failed to capture screenshot: {e}", QSystemTrayIcon.Critical) - def _on_spectacle_finished(self, save_path, exit_code): + def _on_spectacle_finished(self, proc, save_path, exit_code): + proc.deleteLater() if os.path.exists(save_path) and os.path.getsize(save_path) > 0: - self.upload_file_direct(save_path) + self.enqueue_targets([save_path]) def on_abort_upload(self): - if hasattr(self, "download_thread") and self.download_thread and self.download_thread.isRunning(): - self.download_thread.abort() + print(f"[f0ckm-gui] [QUEUE] Abort requested. Clearing queue ({len(self.upload_queue)} remaining items) and stopping active upload.", flush=True) + self.upload_queue.clear() + self.total_queue_count = 0 + self.current_item_index = 0 + if self.active_thread and hasattr(self.active_thread, "abort"): + self.active_thread.abort() if self.current_reply and self.current_reply.isRunning(): self.current_reply.abort() - elif hasattr(self, "upload_thread") and self.upload_thread and self.upload_thread.isRunning(): - self.upload_thread.abort() def upload_target_direct(self, target): - target_str, is_url = parse_upload_target(target) - print(f"[f0ckm-gui] [TRAY ACTION] Received target: '{target}' -> target_str='{target_str}', is_url={is_url}", flush=True) - if not target_str: - return - if is_url: - self.download_and_upload_url(target_str) - else: - self.upload_file_direct(target_str) + self.enqueue_targets([target]) def download_and_upload_url(self, url): config = get_env_config() @@ -1571,17 +1662,20 @@ class SystemTrayApp(QObject): if not api_url or not api_key: print(f"[f0ckm-gui] [CONFIG ERROR] API URL or API Key missing in configuration!", flush=True) self.show_message("Upload Error", "API URL and API Key must be set in Settings.", QSystemTrayIcon.Critical) + QTimer.singleShot(100, self._on_single_task_complete) return self.download_url = url self.download_start_time = time.time() self.tray.setIcon(create_digit_icon(50)) - self.tray.setToolTip(f"Uploading URL to f0ckm...\n{url[:45]}") - self.url_post_thread = URLPostThread(api_url, api_key, url, rating, tags, visibility, is_oc) - self.url_post_thread.finished.connect(lambda ok, res: self.on_url_post_finished(ok, res, url)) - self.url_post_thread.start() - self.abort_action.setEnabled(True) + batch_prefix = f"[{self.current_item_index}/{self.total_queue_count}] " if self.total_queue_count > 1 else "" + rem_str = f"\n({len(self.upload_queue)} waiting in queue)" if self.upload_queue else "" + self.tray.setToolTip(f"Uploading URL {batch_prefix}to f0ckm...\n{url[:45]}{rem_str}") + + self.active_thread = URLPostThread(api_url, api_key, url, rating, tags, visibility, is_oc) + self.active_thread.finished.connect(lambda ok, res: self.on_url_post_finished(ok, res, url)) + self.active_thread.start() def on_url_post_finished(self, ok, result, url): if ok: @@ -1608,22 +1702,22 @@ class SystemTrayApp(QObject): } add_history_entry(entry) - self.abort_action.setEnabled(False) - self.tray.setIcon(self.default_icon) - self.tray.setToolTip("f0ckm Uploader (Click to capture & upload)") self.last_uploaded_url = final_url self.show_success_notification(url, final_url) + self._on_single_task_complete() return except Exception as e: print(f"[f0ckm-gui] JSON parse notice: {e}", flush=True) print(f"[f0ckm-gui] Direct URL API notice: Falling back to local download of '{url}'", flush=True) - self.download_thread = StreamingDownloadThread(url) - self.download_thread.progress.connect(self.on_download_progress) - self.download_thread.finished.connect(lambda ok_dl, res_dl: self.on_download_finished(ok_dl, res_dl, url)) - self.download_thread.start() + self.active_thread = StreamingDownloadThread(url) + self.active_thread.progress.connect(self.on_download_progress) + self.active_thread.finished.connect(lambda ok_dl, res_dl: self.on_download_finished(ok_dl, res_dl, url)) + self.active_thread.start() def on_download_progress(self, bytes_received, bytes_total): + batch_prefix = f"[{self.current_item_index}/{self.total_queue_count}] " if self.total_queue_count > 1 else "" + rem_str = f"\n({len(self.upload_queue)} waiting in queue)" if self.upload_queue else "" if bytes_total > 0: pct = min(99, max(0, int((bytes_received / bytes_total) * 100))) self.tray.setIcon(create_digit_icon(pct)) @@ -1631,20 +1725,18 @@ class SystemTrayApp(QObject): tot_str = format_size(bytes_total) url_str = getattr(self, "download_url", "") short_url = (url_str[:35] + "...") if len(url_str) > 35 else url_str - self.tray.setToolTip(f"Downloading: {short_url}\n{pct}% ({rec_str} / {tot_str})") + self.tray.setToolTip(f"Downloading {batch_prefix}: {short_url}\n{pct}% ({rec_str} / {tot_str}){rem_str}") else: rec_str = format_size(bytes_received) - self.tray.setToolTip(f"Downloading remote file...\n{rec_str}") + self.tray.setToolTip(f"Downloading remote file {batch_prefix}...\n{rec_str}{rem_str}") def on_download_finished(self, success, result, url): if not success: - self.abort_action.setEnabled(False) - self.tray.setIcon(self.default_icon) - self.tray.setToolTip("f0ckm Uploader (Click to capture & upload)") if result == "ABORTED": self.show_message("Download Aborted", "Remote URL download was canceled.", QSystemTrayIcon.Information) else: self.show_message("Download Failed", f"Failed to download remote URL: {result}", QSystemTrayIcon.Critical) + self._on_single_task_complete() return self.upload_file_direct(result) @@ -1653,6 +1745,7 @@ class SystemTrayApp(QObject): file_path = normalize_file_path(file_path) if not file_path or not os.path.exists(file_path): self.show_message("Upload Error", f"File not found: {file_path}", QSystemTrayIcon.Critical) + QTimer.singleShot(100, self._on_single_task_complete) return config = get_env_config() @@ -1660,6 +1753,7 @@ class SystemTrayApp(QObject): api_key = config.get("api_key", "") if not api_url or not api_key: self.show_message("Upload Error", "API URL and API Key must be set in Settings.", QSystemTrayIcon.Critical) + QTimer.singleShot(100, self._on_single_task_complete) return rating = config.get("default_rating", "") @@ -1676,29 +1770,15 @@ class SystemTrayApp(QObject): self.upload_start_time = time.time() self.tray.setIcon(create_digit_icon(0)) - self.tray.setToolTip(f"Uploading {filename}...\n0% (0 B)") - self.upload_thread = StreamingUploadThread(api_url, api_key, file_path, rating, tags, visibility, is_oc) - self.upload_thread.progress.connect(self.on_upload_progress) - self.upload_thread.finished.connect(lambda ok, resp: self.on_upload_finished(ok, resp, file_path)) - self.upload_thread.start() + batch_prefix = f"[{self.current_item_index}/{self.total_queue_count}] " if self.total_queue_count > 1 else "" + rem_str = f"\n({len(self.upload_queue)} waiting in queue)" if self.upload_queue else "" + self.tray.setToolTip(f"Uploading {batch_prefix}{filename}...\n0% (0 B){rem_str}") - self.abort_action.setEnabled(True) - - def _on_qt_reply_finished(self, reply, file_path): - self.current_reply = None - err = reply.error() - if err == QNetworkReply.NetworkError.NoError: - resp_bytes = reply.readAll().data() - resp_str = resp_bytes.decode('utf-8', errors='replace') - self.on_upload_finished(True, resp_str, file_path) - else: - if err == QNetworkReply.NetworkError.OperationCanceledError: - self.on_upload_finished(False, "ABORTED", file_path) - else: - resp_bytes = reply.readAll().data() - resp_str = resp_bytes.decode('utf-8', errors='replace') if resp_bytes else reply.errorString() - self.on_upload_finished(False, resp_str, file_path) + self.active_thread = StreamingUploadThread(api_url, api_key, file_path, rating, tags, visibility, is_oc) + self.active_thread.progress.connect(self.on_upload_progress) + self.active_thread.finished.connect(lambda ok, resp: self.on_upload_finished(ok, resp, file_path)) + self.active_thread.start() def on_upload_progress(self, bytes_sent, bytes_total): if bytes_total > 0: @@ -1712,8 +1792,12 @@ class SystemTrayApp(QObject): total_str = format_size(bytes_total) filename = getattr(self, "upload_filename", "file") + batch_prefix = f"[{self.current_item_index}/{self.total_queue_count}] " if self.total_queue_count > 1 else "" + remaining = len(self.upload_queue) + rem_str = f"\n({remaining} waiting in queue)" if remaining > 0 else "" + if pct >= 99: - tooltip = f"Uploading: {filename}\n99% ({total_str})\nProcessing upload on server..." + tooltip = f"Uploading {batch_prefix}: {filename}\n99% ({total_str})\nProcessing upload on server...{rem_str}" else: speed_str = format_speed(speed) remaining_bytes = bytes_total - bytes_sent @@ -1725,24 +1809,17 @@ class SystemTrayApp(QObject): else: eta_str = f"{eta_sec // 3600}h {(eta_sec % 3600) // 60}m" - tooltip = f"Uploading: {filename}\n{pct}% ({sent_str} / {total_str})\nSpeed: {speed_str} | ETA: {eta_str}" + tooltip = f"Uploading {batch_prefix}: {filename}\n{pct}% ({sent_str} / {total_str})\nSpeed: {speed_str} | ETA: {eta_str}{rem_str}" self.tray.setToolTip(tooltip) - def on_abort_upload(self): - if hasattr(self, "upload_thread") and self.upload_thread: - self.upload_thread.abort() - def on_upload_finished(self, success, response_text, file_path): - self.abort_action.setEnabled(False) - self.tray.setIcon(self.default_icon) - self.tray.setToolTip("f0ckm Uploader (Click to capture & upload)") - if not success: if response_text == "ABORTED": self.show_message("Upload Aborted", "The active upload was canceled.", QSystemTrayIcon.Information) else: self.show_message("Upload Failed", response_text, QSystemTrayIcon.Critical) + self._on_single_task_complete() return try: @@ -1779,6 +1856,8 @@ class SystemTrayApp(QObject): self.show_message("Upload Failed", msg, QSystemTrayIcon.Critical) except Exception as e: self.show_message("Upload Error", f"Error parsing response: {e}\nResponse: {response_text}", QSystemTrayIcon.Critical) + finally: + self._on_single_task_complete() def show_success_notification(self, file_path, item_url): self.last_uploaded_url = item_url @@ -1838,16 +1917,16 @@ class SystemTrayApp(QObject): print(f"Notification error: {e}") def on_upload_file(self): - file_path, _ = QFileDialog.getOpenFileName( - None, "Select File to Upload", "", "All Files (*)" + file_paths, _ = QFileDialog.getOpenFileNames( + None, "Select Files to Upload", "", "All Files (*)" ) - if file_path: - self.upload_file_direct(file_path) + if file_paths: + self.enqueue_targets(file_paths) def on_upload_url(self): url, ok = QInputDialog.getText(None, "Upload URL", "Enter image or file URL:") if ok and url.strip(): - self.upload_target_direct(url.strip()) + self.enqueue_targets([url.strip()]) def on_upload_clipboard(self): clipboard = QApplication.clipboard() @@ -1856,36 +1935,41 @@ class SystemTrayApp(QObject): if mime_data.hasImage(): image = clipboard.image() temp_dir = tempfile.gettempdir() - temp_path = os.path.join(temp_dir, f"f0ckm_clipboard_{int(time.time())}.png") + ts = time.strftime('%Y%m%d_%H%M%S') + ms = int(time.time() * 1000) % 1000 + temp_path = os.path.join(temp_dir, f"f0ckm_clipboard_{ts}_{ms:03d}.png") if image.save(temp_path, "PNG"): - self.upload_file_direct(temp_path) + self.enqueue_targets([temp_path]) else: self.show_message("Upload Error", "Failed to save clipboard image to temporary file.", QSystemTrayIcon.Critical) elif mime_data.hasUrls(): urls = mime_data.urls() - uploaded_any = False + valid_paths = [] for url in urls: if url.isLocalFile(): path = url.toLocalFile() if os.path.exists(path): - self.upload_file_direct(path) - uploaded_any = True - if not uploaded_any: + valid_paths.append(path) + if valid_paths: + self.enqueue_targets(valid_paths) + else: self.show_message("Upload Error", "No valid local files in clipboard.", QSystemTrayIcon.Warning) elif mime_data.hasText(): text = mime_data.text().strip() lines = [l.strip() for l in text.split('\n') if l.strip()] - uploaded_any = False + valid_targets = [] for line in lines: if line.startswith("file://"): line = line[7:] - if os.path.exists(line): - self.upload_file_direct(line) - uploaded_any = True - if not uploaded_any: - self.show_message("Upload Error", "Clipboard text is not a valid local file path.", QSystemTrayIcon.Warning) + t, _ = parse_upload_target(line) + if t: + valid_targets.append(t) + if valid_targets: + self.enqueue_targets(valid_targets) + else: + self.show_message("Upload Error", "Clipboard text does not contain valid file paths or URLs.", QSystemTrayIcon.Warning) else: - self.show_message("Upload Error", "Clipboard does not contain an image or file paths.", QSystemTrayIcon.Warning) + self.show_message("Upload Error", "Clipboard does not contain an image, files, or URLs.", QSystemTrayIcon.Warning) def show_message(self, title, message, icon=QSystemTrayIcon.Information): self.tray.showMessage(title, message, icon, 5000) @@ -1903,26 +1987,10 @@ class SingleInstanceApp: socket = QLocalSocket() socket.connectToServer(self.name) if socket.waitForConnected(500): - msg = "show" - if len(sys.argv) > 1: - arg = sys.argv[1] - if arg in ("--spectacle", "-s"): - msg = "spectacle" - elif arg.startswith("--upload="): - target, _ = parse_upload_target(arg[9:]) - msg = f"upload:{target}" - elif arg.startswith("--upload"): - if len(sys.argv) > 2: - target, _ = parse_upload_target(sys.argv[2]) - msg = f"upload:{target}" - else: - msg = "show" - else: - target, _ = parse_upload_target(arg) - if target: - msg = f"upload:{target}" - print(f"[f0ckm-gui] [IPC CLIENT] Daemon running. Forwarding message via socket: '{msg}'", flush=True) - socket.write(msg.encode("utf-8")) + action, targets = parse_cli_args(sys.argv[1:]) + payload = json.dumps({"action": action, "targets": targets}) + print(f"[f0ckm-gui] [IPC CLIENT] Daemon running. Forwarding payload via socket: '{payload}'", flush=True) + socket.write(payload.encode("utf-8")) socket.waitForBytesWritten(500) socket.disconnectFromServer() return False @@ -1964,11 +2032,26 @@ def main(): def on_activate(msg_str): nonlocal tray_app if tray_app: + if msg_str.startswith("{"): + try: + data = json.loads(msg_str) + act = data.get("action", "show") + targets = data.get("targets", []) + if act == "spectacle": + tray_app.on_capture_and_upload() + elif act == "upload" and targets: + tray_app.enqueue_targets(targets) + else: + tray_app.on_open_settings() + return + except Exception as e: + print(f"[f0ckm-gui] IPC JSON parse notice: {e}", flush=True) + if msg_str == "spectacle": tray_app.on_capture_and_upload() elif msg_str.startswith("upload:"): target = msg_str[7:] - tray_app.upload_target_direct(target) + tray_app.enqueue_targets([target]) else: tray_app.on_open_settings() @@ -1982,28 +2065,24 @@ def main(): set_autostart(cfg.get("autostart", False)) tray_app = SystemTrayApp() - local_http_bridge.request_upload.connect(tray_app.upload_target_direct) + local_http_bridge.request_upload.connect(lambda url: tray_app.enqueue_targets([url])) tray_app.show() start_local_http_server(tray_app) # Check if started with action argument if len(sys.argv) > 1: - arg = sys.argv[1] - if arg in ("--spectacle", "-s"): + action, targets = parse_cli_args(sys.argv[1:]) + if action == "spectacle": tray_app.on_capture_and_upload() - elif arg.startswith("--upload="): - target, _ = parse_upload_target(arg[9:]) - tray_app.upload_target_direct(target) - elif arg.startswith("--upload"): - if len(sys.argv) > 2: - target, _ = parse_upload_target(sys.argv[2]) - tray_app.upload_target_direct(target) - else: - target, _ = parse_upload_target(arg) - if target: - tray_app.upload_target_direct(target) + elif action == "upload" and targets: + tray_app.enqueue_targets(targets) + else: + if not cfg.get("api_url") or not cfg.get("api_key"): + print("[f0ckm-gui] First launch detected (missing API credentials). Opening settings dialog...", flush=True) + QTimer.singleShot(400, tray_app.on_open_settings) sys.exit(app.exec()) if __name__ == "__main__": main() + diff --git a/install.sh b/install.sh index fe006b9..90a7380 100755 --- a/install.sh +++ b/install.sh @@ -6,6 +6,38 @@ APP_DIR="$HOME/.local/share/applications" SERVICE_MENU_DIR="$HOME/.local/share/kio/servicemenus" LEGACY_SERVICE_MENU_DIR="$HOME/.local/share/kservices5/ServiceMenus" +# Check Python 3 and PySide6 dependency +echo "Checking dependencies..." +if ! command -v python3 &>/dev/null; then + echo "Error: Python 3 is required but not found on your system." + exit 1 +fi + +if ! python3 -c "import PySide6" &>/dev/null; then + echo "PySide6 is not installed. Attempting automatic installation..." + if command -v pip3 &>/dev/null; then + pip3 install PySide6 || pip3 install --break-system-packages PySide6 || true + elif command -v pip &>/dev/null; then + pip install PySide6 || pip install --break-system-packages PySide6 || true + elif command -v dnf &>/dev/null; then + echo "Installing python3-pyside6 via dnf..." + sudo dnf install -y python3-pyside6 || true + elif command -v apt-get &>/dev/null; then + echo "Installing python3-pyside6 via apt..." + sudo apt-get update && sudo apt-get install -y python3-pyside6 || true + elif command -v pacman &>/dev/null; then + echo "Installing python-pyside6 via pacman..." + sudo pacman -S --noconfirm python-pyside6 || true + elif command -v zypper &>/dev/null; then + echo "Installing python3-pyside6 via zypper..." + sudo zypper install -y python3-pyside6 || true + fi +fi + +if ! python3 -c "import PySide6" &>/dev/null; then + echo "Warning: Could not automatically install PySide6. Please install 'PySide6' via pip or your distro package manager (e.g. python3-pyside6)." +fi + # Create necessary directories mkdir -p "$INSTALL_DIR" "$APP_DIR" "$SERVICE_MENU_DIR" "$LEGACY_SERVICE_MENU_DIR" @@ -154,9 +186,26 @@ except Exception as e: print(f"Warning: could not sync autostart entry: {e}") ' -echo "Installation complete!" -echo "The script has been installed to $INSTALL_DIR/f0ckm-uploader" -echo "The GUI has been installed to $INSTALL_DIR/f0ckm-uploader-gui" -echo "You can launch the GUI uploader from your applications menu (f0ckm Uploader GUI)." -echo "You can now right click files in Dolphin and find 'Upload to f0ckm' in the Actions menu." -echo "In Spectacle, you should find 'Upload to f0ckm' in the Export or Share menu." +echo "" +echo "========================================================" +echo " f0ckm Uploader Installation Complete!" +echo "========================================================" +echo " -> Executable installed: $INSTALL_DIR/f0ckm-uploader-gui" +echo " -> Dolphin context menu registered (Right-click file -> Actions -> Upload to f0ckm)" +echo " -> Spectacle screenshot export integration enabled" +echo " -> Browser extension helper API ready on http://127.0.0.1:18739" +echo "========================================================" +echo "" + +# Launch GUI to prompt for API settings if API key or URL is not configured yet +python3 -c ' +import json, os +cfg_path = os.path.expanduser("~/.config/f0ckm-uploader/config.json") +if os.path.exists(cfg_path): + with open(cfg_path) as f: + cfg = json.load(f) + if not cfg.get("api_url") or not cfg.get("api_key"): + print("[!] API Key or URL not configured yet.") + print(" Launching f0ckm Uploader GUI Settings for initial setup...") + os.system("'$INSTALL_DIR'/f0ckm-uploader-gui &") +' || true diff --git a/uploader.sh b/uploader.sh index 102a8d3..d8719a1 100755 --- a/uploader.sh +++ b/uploader.sh @@ -1,3 +1,4 @@ #!/usr/bin/env bash # Pass execution directly to the Python GUI engine -exec f0ckm-uploader-gui --upload="$1" +exec f0ckm-uploader-gui --upload "$@" +