add queue functionality to upload many items and update readme to include basic installation guide

This commit is contained in:
2026-08-12 17:14:59 +02:00
parent 35ff9bae5e
commit 43e95c9d3e
6 changed files with 277 additions and 169 deletions

View File

@@ -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.
---

View File

@@ -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

View File

@@ -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

317
gui.py
View File

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

View File

@@ -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

View File

@@ -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 "$@"