add android app
This commit is contained in:
370
gui.py
370
gui.py
@@ -14,6 +14,7 @@ import shutil
|
||||
import socket
|
||||
import ssl
|
||||
import threading
|
||||
import signal
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
from PySide6.QtCore import Qt, QThread, QObject, Signal, QUrl, QFile, QIODevice, QTimer, QMimeData, QProcess
|
||||
@@ -37,6 +38,25 @@ def normalize_file_path(path_str: str) -> str:
|
||||
path_str = urllib.parse.unquote(path_str[7:])
|
||||
return os.path.abspath(path_str)
|
||||
|
||||
def normalize_api_url(url: str) -> str:
|
||||
if not url:
|
||||
return ""
|
||||
url = url.strip().strip("'\"")
|
||||
if not url:
|
||||
return ""
|
||||
if not (url.startswith("http://") or url.startswith("https://")):
|
||||
url = "http://" + url
|
||||
|
||||
parsed = urllib.parse.urlparse(url)
|
||||
path = parsed.path.rstrip('/')
|
||||
if not path or path == "":
|
||||
return f"{url.rstrip('/')}/api/v2/upload"
|
||||
elif path.endswith("/api/v2"):
|
||||
return f"{url.rstrip('/')}/upload"
|
||||
elif not path.endswith("/upload"):
|
||||
return f"{url.rstrip('/')}/api/v2/upload"
|
||||
return url
|
||||
|
||||
def parse_upload_target(arg: str) -> tuple[str, bool]:
|
||||
if not arg:
|
||||
return ("", False)
|
||||
@@ -206,6 +226,14 @@ def add_history_entry(entry):
|
||||
save_history(history)
|
||||
|
||||
DEFAULT_CONFIG = {
|
||||
"instances": [
|
||||
{
|
||||
"name": "Default Instance",
|
||||
"api_url": "",
|
||||
"api_key": ""
|
||||
}
|
||||
],
|
||||
"active_instance_index": 0,
|
||||
"api_url": "",
|
||||
"api_key": "",
|
||||
"default_rating": "",
|
||||
@@ -227,6 +255,27 @@ def load_config():
|
||||
data = json.load(f)
|
||||
config = DEFAULT_CONFIG.copy()
|
||||
config.update(data)
|
||||
|
||||
instances = config.get("instances")
|
||||
if not isinstance(instances, list) or not instances:
|
||||
url = data.get("api_url") or config.get("api_url", "")
|
||||
key = data.get("api_key") or config.get("api_key", "")
|
||||
instances = [{"name": "Default Instance", "api_url": normalize_api_url(url), "api_key": key}]
|
||||
config["instances"] = instances
|
||||
config["active_instance_index"] = 0
|
||||
else:
|
||||
for inst in instances:
|
||||
if "api_url" in inst:
|
||||
inst["api_url"] = normalize_api_url(inst["api_url"])
|
||||
|
||||
idx = config.get("active_instance_index", 0)
|
||||
if not isinstance(idx, int) or idx < 0 or idx >= len(config["instances"]):
|
||||
idx = 0
|
||||
config["active_instance_index"] = 0
|
||||
|
||||
active = config["instances"][idx]
|
||||
config["api_url"] = normalize_api_url(active.get("api_url", ""))
|
||||
config["api_key"] = active.get("api_key", "")
|
||||
return config
|
||||
except Exception:
|
||||
return DEFAULT_CONFIG.copy()
|
||||
@@ -241,6 +290,8 @@ def get_env_config():
|
||||
os.path.join(os.getcwd(), ".env")
|
||||
]
|
||||
dotenv_path = None
|
||||
env_url = ""
|
||||
env_key = ""
|
||||
for path in candidates:
|
||||
if os.path.exists(path):
|
||||
dotenv_path = path
|
||||
@@ -256,9 +307,11 @@ def get_env_config():
|
||||
key = k.strip()
|
||||
val = v.strip().strip("'\"")
|
||||
if key == "F0CKM_URL" and val:
|
||||
url = val if val.endswith("/api/v2/upload") else f"{val.rstrip('/')}/api/v2/upload"
|
||||
url = normalize_api_url(val)
|
||||
env_url = url
|
||||
config["api_url"] = url
|
||||
elif key == "API_KEY" and val:
|
||||
env_key = val
|
||||
config["api_key"] = val
|
||||
elif key == "RATING":
|
||||
config["default_rating"] = val
|
||||
@@ -277,11 +330,37 @@ def get_env_config():
|
||||
for k, v in user_cfg.items():
|
||||
config[k] = v
|
||||
|
||||
instances = config.get("instances")
|
||||
if not isinstance(instances, list) or not instances:
|
||||
url = config.get("api_url") or env_url
|
||||
key = config.get("api_key") or env_key
|
||||
instances = [{"name": "Default Instance", "api_url": normalize_api_url(url), "api_key": key}]
|
||||
config["instances"] = instances
|
||||
config["active_instance_index"] = 0
|
||||
|
||||
idx = config.get("active_instance_index", 0)
|
||||
if not isinstance(idx, int) or idx < 0 or idx >= len(instances):
|
||||
idx = 0
|
||||
config["active_instance_index"] = 0
|
||||
|
||||
active_inst = instances[idx]
|
||||
config["api_url"] = normalize_api_url(active_inst.get("api_url", ""))
|
||||
config["api_key"] = active_inst.get("api_key", "")
|
||||
config["active_instance_name"] = active_inst.get("name", "Default Instance")
|
||||
|
||||
return config
|
||||
|
||||
def save_config(config):
|
||||
try:
|
||||
os.makedirs(CONFIG_DIR, exist_ok=True)
|
||||
if "instances" in config and isinstance(config["instances"], list) and config["instances"]:
|
||||
for inst in config["instances"]:
|
||||
if "api_url" in inst:
|
||||
inst["api_url"] = normalize_api_url(inst["api_url"])
|
||||
idx = config.get("active_instance_index", 0)
|
||||
if 0 <= idx < len(config["instances"]):
|
||||
config["api_url"] = normalize_api_url(config["instances"][idx].get("api_url", ""))
|
||||
config["api_key"] = config["instances"][idx].get("api_key", "")
|
||||
with open(CONFIG_PATH, 'w') as f:
|
||||
json.dump(config, f, indent=4)
|
||||
return True
|
||||
@@ -334,7 +413,7 @@ class ConnectionTester(QObject):
|
||||
|
||||
def __init__(self, url, api_key):
|
||||
super().__init__()
|
||||
self.url = url
|
||||
self.url = normalize_api_url(url)
|
||||
self.api_key = api_key
|
||||
|
||||
def run(self):
|
||||
@@ -389,7 +468,7 @@ class SettingsDialog(QDialog):
|
||||
super().__init__(parent)
|
||||
self.tray_app = tray_app
|
||||
self.setWindowTitle("f0ckm Uploader Settings")
|
||||
self.resize(540, 680)
|
||||
self.resize(560, 700)
|
||||
|
||||
app_icon = get_app_icon()
|
||||
if not app_icon.isNull():
|
||||
@@ -397,6 +476,10 @@ class SettingsDialog(QDialog):
|
||||
|
||||
self.tester_thread = None
|
||||
self.tester = None
|
||||
|
||||
self.instances = []
|
||||
self.current_instance_index = 0
|
||||
self._ignore_instance_signals = False
|
||||
|
||||
self.init_ui()
|
||||
|
||||
@@ -414,7 +497,7 @@ class SettingsDialog(QDialog):
|
||||
header_text_layout = QVBoxLayout()
|
||||
header_title = QLabel("f0ckm Uploader")
|
||||
header_title.setStyleSheet("font-size: 16px; font-weight: bold;")
|
||||
header_subtitle = QLabel("Configure connection settings and default options")
|
||||
header_subtitle = QLabel("Configure f0ckm instances, connection settings and default options")
|
||||
header_subtitle.setStyleSheet("font-size: 11px; opacity: 0.7;")
|
||||
header_text_layout.addWidget(header_title)
|
||||
header_text_layout.addWidget(header_subtitle)
|
||||
@@ -424,20 +507,45 @@ class SettingsDialog(QDialog):
|
||||
header_layout.addStretch()
|
||||
main_layout.addLayout(header_layout)
|
||||
|
||||
# Section 1: API Connection Settings
|
||||
grp_api = QGroupBox("API Connection")
|
||||
# Section 1: API Connection & Instances Settings
|
||||
grp_api = QGroupBox("f0ckm Instances & Connection")
|
||||
api_layout = QFormLayout(grp_api)
|
||||
api_layout.setContentsMargins(15, 20, 15, 15)
|
||||
api_layout.setSpacing(10)
|
||||
|
||||
# Instance Selection & Management Row
|
||||
inst_row_layout = QHBoxLayout()
|
||||
self.cb_instance = QComboBox()
|
||||
self.cb_instance.currentIndexChanged.connect(self.on_instance_changed)
|
||||
inst_row_layout.addWidget(self.cb_instance, stretch=1)
|
||||
|
||||
self.btn_add_instance = QPushButton("+ Add")
|
||||
self.btn_add_instance.setToolTip("Add a new f0ckm instance")
|
||||
self.btn_add_instance.clicked.connect(self.on_add_instance)
|
||||
inst_row_layout.addWidget(self.btn_add_instance)
|
||||
|
||||
self.btn_rename_instance = QPushButton("Rename")
|
||||
self.btn_rename_instance.setToolTip("Rename currently selected instance")
|
||||
self.btn_rename_instance.clicked.connect(self.on_rename_instance)
|
||||
inst_row_layout.addWidget(self.btn_rename_instance)
|
||||
|
||||
self.btn_delete_instance = QPushButton("Delete")
|
||||
self.btn_delete_instance.setToolTip("Delete currently selected instance")
|
||||
self.btn_delete_instance.clicked.connect(self.on_delete_instance)
|
||||
inst_row_layout.addWidget(self.btn_delete_instance)
|
||||
|
||||
api_layout.addRow("Instance:", inst_row_layout)
|
||||
|
||||
self.txt_url = QLineEdit()
|
||||
self.txt_url.setPlaceholderText("https://example.com/api/v2/upload")
|
||||
self.txt_url.textChanged.connect(self.on_url_or_key_edited)
|
||||
api_layout.addRow("API URL:", self.txt_url)
|
||||
|
||||
key_layout = QHBoxLayout()
|
||||
self.txt_key = QLineEdit()
|
||||
self.txt_key.setEchoMode(QLineEdit.Password)
|
||||
self.txt_key.setPlaceholderText("your_api_key_here")
|
||||
self.txt_key.textChanged.connect(self.on_url_or_key_edited)
|
||||
key_layout.addWidget(self.txt_key)
|
||||
|
||||
self.btn_toggle_key = QPushButton("Show")
|
||||
@@ -578,11 +686,99 @@ class SettingsDialog(QDialog):
|
||||
else:
|
||||
self.txt_key.setEchoMode(QLineEdit.Password)
|
||||
self.btn_toggle_key.setText("Show")
|
||||
|
||||
def refresh_instance_combo(self):
|
||||
self._ignore_instance_signals = True
|
||||
self.cb_instance.clear()
|
||||
for inst in self.instances:
|
||||
self.cb_instance.addItem(inst.get("name", "Unnamed Instance"))
|
||||
|
||||
if 0 <= self.current_instance_index < len(self.instances):
|
||||
self.cb_instance.setCurrentIndex(self.current_instance_index)
|
||||
self._ignore_instance_signals = False
|
||||
self.load_instance_fields(self.current_instance_index)
|
||||
|
||||
def load_instance_fields(self, index):
|
||||
if 0 <= index < len(self.instances):
|
||||
inst = self.instances[index]
|
||||
self.txt_url.setText(inst.get("api_url", ""))
|
||||
self.txt_key.setText(inst.get("api_key", ""))
|
||||
|
||||
def on_instance_changed(self, new_index):
|
||||
if self._ignore_instance_signals or new_index < 0 or new_index >= len(self.instances):
|
||||
return
|
||||
|
||||
if 0 <= self.current_instance_index < len(self.instances):
|
||||
self.instances[self.current_instance_index]["api_url"] = self.txt_url.text().strip()
|
||||
self.instances[self.current_instance_index]["api_key"] = self.txt_key.text().strip()
|
||||
|
||||
self.current_instance_index = new_index
|
||||
self.load_instance_fields(new_index)
|
||||
|
||||
def on_url_or_key_edited(self):
|
||||
if self._ignore_instance_signals:
|
||||
return
|
||||
if 0 <= self.current_instance_index < len(self.instances):
|
||||
self.instances[self.current_instance_index]["api_url"] = self.txt_url.text().strip()
|
||||
self.instances[self.current_instance_index]["api_key"] = self.txt_key.text().strip()
|
||||
|
||||
def on_add_instance(self):
|
||||
name, ok = QInputDialog.getText(self, "Add Instance", "Enter name for new f0ckm instance:")
|
||||
if ok and name.strip():
|
||||
name = name.strip()
|
||||
self.on_url_or_key_edited()
|
||||
new_inst = {
|
||||
"name": name,
|
||||
"api_url": "",
|
||||
"api_key": ""
|
||||
}
|
||||
self.instances.append(new_inst)
|
||||
self.current_instance_index = len(self.instances) - 1
|
||||
self.refresh_instance_combo()
|
||||
|
||||
def on_rename_instance(self):
|
||||
if not (0 <= self.current_instance_index < len(self.instances)):
|
||||
return
|
||||
curr_name = self.instances[self.current_instance_index].get("name", "")
|
||||
name, ok = QInputDialog.getText(self, "Rename Instance", "Enter new instance name:", text=curr_name)
|
||||
if ok and name.strip():
|
||||
name = name.strip()
|
||||
self.instances[self.current_instance_index]["name"] = name
|
||||
self.refresh_instance_combo()
|
||||
|
||||
def on_delete_instance(self):
|
||||
if len(self.instances) <= 1:
|
||||
QMessageBox.information(self, "Cannot Delete", "You must keep at least one f0ckm instance.")
|
||||
return
|
||||
|
||||
curr_name = self.instances[self.current_instance_index].get("name", "")
|
||||
reply = QMessageBox.question(
|
||||
self,
|
||||
"Delete Instance",
|
||||
f"Are you sure you want to delete instance '{curr_name}'?",
|
||||
QMessageBox.Yes | QMessageBox.No
|
||||
)
|
||||
if reply == QMessageBox.Yes:
|
||||
self.instances.pop(self.current_instance_index)
|
||||
self.current_instance_index = max(0, self.current_instance_index - 1)
|
||||
self.refresh_instance_combo()
|
||||
|
||||
def load_current_settings(self):
|
||||
config = load_config()
|
||||
self.txt_url.setText(config.get("api_url", ""))
|
||||
self.txt_key.setText(config.get("api_key", ""))
|
||||
config = get_env_config()
|
||||
|
||||
self.instances = [dict(inst) for inst in config.get("instances", [])]
|
||||
if not self.instances:
|
||||
self.instances = [{
|
||||
"name": "Default Instance",
|
||||
"api_url": config.get("api_url", ""),
|
||||
"api_key": config.get("api_key", "")
|
||||
}]
|
||||
|
||||
self.current_instance_index = config.get("active_instance_index", 0)
|
||||
if self.current_instance_index < 0 or self.current_instance_index >= len(self.instances):
|
||||
self.current_instance_index = 0
|
||||
|
||||
self.refresh_instance_combo()
|
||||
|
||||
rating = config.get("default_rating", "").lower()
|
||||
rating_map = {"": 0, "s": 1, "sfw": 1, "safe": 1, "q": 2, "nsfw": 2, "questionable": 2, "e": 3, "nsfl": 3, "explicit": 3}
|
||||
@@ -620,7 +816,10 @@ class SettingsDialog(QDialog):
|
||||
self.btn_test.setEnabled(False)
|
||||
self.btn_test.setText("Testing...")
|
||||
|
||||
url = self.txt_url.text().strip()
|
||||
raw_url = self.txt_url.text().strip()
|
||||
url = normalize_api_url(raw_url)
|
||||
if url != raw_url:
|
||||
self.txt_url.setText(url)
|
||||
key = self.txt_key.text().strip()
|
||||
|
||||
self.tester_thread = QThread()
|
||||
@@ -671,9 +870,19 @@ class SettingsDialog(QDialog):
|
||||
play_success_sound(sound_path, volume=self.slider_volume.value())
|
||||
|
||||
def save_settings(self):
|
||||
self.on_url_or_key_edited()
|
||||
|
||||
active_idx = self.cb_instance.currentIndex()
|
||||
if active_idx < 0 or active_idx >= len(self.instances):
|
||||
active_idx = 0
|
||||
|
||||
active_inst = self.instances[active_idx] if self.instances else {"api_url": "", "api_key": ""}
|
||||
|
||||
config = {
|
||||
"api_url": self.txt_url.text().strip(),
|
||||
"api_key": self.txt_key.text().strip(),
|
||||
"instances": self.instances,
|
||||
"active_instance_index": active_idx,
|
||||
"api_url": active_inst.get("api_url", ""),
|
||||
"api_key": active_inst.get("api_key", ""),
|
||||
"default_rating": ["", "s", "q", "e"][self.cb_rating.currentIndex()],
|
||||
"default_visibility": ["0", "1", "2"][self.cb_visibility.currentIndex()],
|
||||
"default_tags": self.txt_tags.text().strip(),
|
||||
@@ -687,11 +896,14 @@ class SettingsDialog(QDialog):
|
||||
"success_audio_path": self.txt_sound_path.text().strip(),
|
||||
"success_audio_volume": self.slider_volume.value()
|
||||
}
|
||||
|
||||
|
||||
if save_config(config):
|
||||
set_autostart(config["autostart"])
|
||||
if self.tray_app and hasattr(self.tray_app, "load_tray_icon"):
|
||||
self.tray_app.load_tray_icon()
|
||||
if self.tray_app:
|
||||
if hasattr(self.tray_app, "load_tray_icon"):
|
||||
self.tray_app.load_tray_icon()
|
||||
if hasattr(self.tray_app, "rebuild_instance_menu"):
|
||||
self.tray_app.rebuild_instance_menu()
|
||||
self.accept()
|
||||
else:
|
||||
QMessageBox.critical(self, "Error", "Failed to save configuration file.")
|
||||
@@ -1203,7 +1415,7 @@ class URLPostThread(QThread):
|
||||
},
|
||||
method="POST"
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
with urllib.request.urlopen(req, timeout=300) as resp:
|
||||
resp_text = resp.read().decode("utf-8")
|
||||
print(f"[f0ckm-gui] [URL POST OK] Server Response: {resp_text}", flush=True)
|
||||
self.finished.emit(True, resp_text)
|
||||
@@ -1232,7 +1444,7 @@ class StreamingDownloadThread(QThread):
|
||||
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
}
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
with urllib.request.urlopen(req, timeout=120) as resp:
|
||||
total_size = int(resp.headers.get("Content-Length", 0))
|
||||
content_type = resp.headers.get("Content-Type", "")
|
||||
print(f"[f0ckm-gui] [DOWNLOAD HTTP OK] Content-Type: '{content_type}', Length: {total_size} bytes", flush=True)
|
||||
@@ -1253,7 +1465,8 @@ class StreamingDownloadThread(QThread):
|
||||
temp_path = os.path.join(temp_dir, f"f0ckm_remote_{int(time.time())}_{clean_name}")
|
||||
|
||||
received = 0
|
||||
chunk_size = 32768
|
||||
chunk_size = 131072
|
||||
last_emit = 0.0
|
||||
with open(temp_path, "wb") as f:
|
||||
while True:
|
||||
if self.aborted:
|
||||
@@ -1265,7 +1478,10 @@ class StreamingDownloadThread(QThread):
|
||||
break
|
||||
f.write(chunk)
|
||||
received += len(chunk)
|
||||
self.progress.emit(received, total_size)
|
||||
now = time.time()
|
||||
if now - last_emit >= 0.05 or (total_size > 0 and received == total_size):
|
||||
self.progress.emit(received, total_size)
|
||||
last_emit = now
|
||||
|
||||
print(f"[f0ckm-gui] [DOWNLOAD COMPLETE] Saved remote file to: {temp_path} ({received} bytes)", flush=True)
|
||||
self.finished.emit(True, temp_path)
|
||||
@@ -1320,6 +1536,8 @@ class StreamingUploadThread(QThread):
|
||||
path += "?" + parsed_url.query
|
||||
|
||||
raw_sock = socket.create_connection((host, port), timeout=30)
|
||||
raw_sock.settimeout(1800.0)
|
||||
|
||||
if parsed_url.scheme == "https":
|
||||
ctx = ssl.create_default_context()
|
||||
self.sock = ctx.wrap_socket(raw_sock, server_hostname=host)
|
||||
@@ -1343,7 +1561,9 @@ class StreamingUploadThread(QThread):
|
||||
total_sent += len(encoder.header_bytes)
|
||||
self.progress.emit(total_sent, encoder.total_size)
|
||||
|
||||
chunk_size = 32768
|
||||
chunk_size = 131072
|
||||
last_progress_time = 0.0
|
||||
|
||||
with open(self.file_path, "rb") as f:
|
||||
while True:
|
||||
if self.aborted:
|
||||
@@ -1356,10 +1576,12 @@ class StreamingUploadThread(QThread):
|
||||
self.sock.sendall(chunk)
|
||||
total_sent += len(chunk)
|
||||
|
||||
pct = min(99, int((total_sent / encoder.total_size) * 100))
|
||||
bytes_sent_calc = int(encoder.total_size * (pct / 100.0))
|
||||
self.progress.emit(bytes_sent_calc, encoder.total_size)
|
||||
time.sleep(0.005)
|
||||
now = time.time()
|
||||
if now - last_progress_time >= 0.05 or total_sent >= encoder.total_size:
|
||||
pct = min(99, int((total_sent / encoder.total_size) * 100))
|
||||
bytes_sent_calc = int(encoder.total_size * (pct / 100.0))
|
||||
self.progress.emit(bytes_sent_calc, encoder.total_size)
|
||||
last_progress_time = now
|
||||
|
||||
self.sock.sendall(encoder.footer_bytes)
|
||||
total_sent += len(encoder.footer_bytes)
|
||||
@@ -1423,7 +1645,10 @@ class StreamingUploadThread(QThread):
|
||||
if self.aborted:
|
||||
self.finished.emit(False, "ABORTED")
|
||||
else:
|
||||
self.finished.emit(False, str(e))
|
||||
err_msg = str(e)
|
||||
if isinstance(e, (socket.timeout, TimeoutError)) or "timed out" in err_msg.lower():
|
||||
err_msg = f"Upload timed out: socket read timed out ({err_msg})"
|
||||
self.finished.emit(False, err_msg)
|
||||
finally:
|
||||
if self.sock:
|
||||
try:
|
||||
@@ -1449,6 +1674,7 @@ class SystemTrayApp(QObject):
|
||||
self.upload_queue = [] # List of tuples: (target_str, is_url)
|
||||
self.is_uploading = False
|
||||
self.active_thread = None
|
||||
self._running_threads = set()
|
||||
self.current_target = None
|
||||
self.total_queue_count = 0
|
||||
self.current_item_index = 0
|
||||
@@ -1459,6 +1685,11 @@ class SystemTrayApp(QObject):
|
||||
title_action = QAction("f0ckm Uploader", self.menu)
|
||||
title_action.setEnabled(False)
|
||||
self.menu.addAction(title_action)
|
||||
|
||||
# Instance Submenu
|
||||
self.instance_menu = QMenu("Switch Instance", self.menu)
|
||||
self.menu.addMenu(self.instance_menu)
|
||||
|
||||
self.menu.addSeparator()
|
||||
|
||||
# Actions
|
||||
@@ -1505,6 +1736,59 @@ class SystemTrayApp(QObject):
|
||||
self.settings_dialog = None
|
||||
self.gallery_window = None
|
||||
|
||||
self.rebuild_instance_menu()
|
||||
|
||||
def _start_thread(self, thread):
|
||||
self._running_threads.add(thread)
|
||||
thread.finished.connect(thread.deleteLater)
|
||||
thread.finished.connect(lambda: self._running_threads.discard(thread))
|
||||
self.active_thread = thread
|
||||
thread.start()
|
||||
|
||||
def rebuild_instance_menu(self):
|
||||
self.instance_menu.clear()
|
||||
config = get_env_config()
|
||||
instances = config.get("instances", [])
|
||||
active_idx = config.get("active_instance_index", 0)
|
||||
|
||||
active_name = config.get("active_instance_name", "Default Instance")
|
||||
self.instance_menu.setTitle(f"Instance: {active_name}")
|
||||
|
||||
for idx, inst in enumerate(instances):
|
||||
name = inst.get("name", f"Instance {idx + 1}")
|
||||
url = inst.get("api_url", "")
|
||||
action = QAction(name, self.instance_menu)
|
||||
if url:
|
||||
action.setToolTip(url)
|
||||
action.setCheckable(True)
|
||||
if idx == active_idx:
|
||||
action.setChecked(True)
|
||||
|
||||
action.triggered.connect(lambda checked=False, i=idx: self.switch_active_instance(i))
|
||||
self.instance_menu.addAction(action)
|
||||
|
||||
self.instance_menu.addSeparator()
|
||||
manage_action = QAction("Manage Instances...", self.instance_menu)
|
||||
manage_action.triggered.connect(self.on_open_settings)
|
||||
self.instance_menu.addAction(manage_action)
|
||||
|
||||
def switch_active_instance(self, index):
|
||||
config = get_env_config()
|
||||
instances = config.get("instances", [])
|
||||
if 0 <= index < len(instances):
|
||||
config["active_instance_index"] = index
|
||||
active_inst = instances[index]
|
||||
config["api_url"] = active_inst.get("api_url", "")
|
||||
config["api_key"] = active_inst.get("api_key", "")
|
||||
save_config(config)
|
||||
|
||||
name = active_inst.get("name", "Instance")
|
||||
print(f"[f0ckm-gui] Switched active instance to '{name}' ({active_inst.get('api_url', '')})", flush=True)
|
||||
self.show_message("f0ckm Instance Switched", f"Active instance changed to: {name}", QSystemTrayIcon.Information)
|
||||
self.rebuild_instance_menu()
|
||||
if self.settings_dialog and self.settings_dialog.isVisible():
|
||||
self.settings_dialog.load_current_settings()
|
||||
|
||||
def enqueue_targets(self, targets):
|
||||
if isinstance(targets, str):
|
||||
targets = [targets]
|
||||
@@ -1554,12 +1838,7 @@ class SystemTrayApp(QObject):
|
||||
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
|
||||
self.active_thread = None
|
||||
QTimer.singleShot(50, self.process_next_in_queue)
|
||||
|
||||
def on_notification_clicked(self):
|
||||
@@ -1673,9 +1952,9 @@ class SystemTrayApp(QObject):
|
||||
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()
|
||||
thread = URLPostThread(api_url, api_key, url, rating, tags, visibility, is_oc)
|
||||
thread.finished.connect(lambda ok, res: self.on_url_post_finished(ok, res, url))
|
||||
self._start_thread(thread)
|
||||
|
||||
def on_url_post_finished(self, ok, result, url):
|
||||
if ok:
|
||||
@@ -1710,10 +1989,10 @@ class SystemTrayApp(QObject):
|
||||
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.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()
|
||||
thread = StreamingDownloadThread(url)
|
||||
thread.progress.connect(self.on_download_progress)
|
||||
thread.finished.connect(lambda ok_dl, res_dl: self.on_download_finished(ok_dl, res_dl, url))
|
||||
self._start_thread(thread)
|
||||
|
||||
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 ""
|
||||
@@ -1775,10 +2054,10 @@ class SystemTrayApp(QObject):
|
||||
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.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()
|
||||
thread = StreamingUploadThread(api_url, api_key, file_path, rating, tags, visibility, is_oc)
|
||||
thread.progress.connect(self.on_upload_progress)
|
||||
thread.finished.connect(lambda ok, resp: self.on_upload_finished(ok, resp, file_path))
|
||||
self._start_thread(thread)
|
||||
|
||||
def on_upload_progress(self, bytes_sent, bytes_total):
|
||||
if bytes_total > 0:
|
||||
@@ -2079,8 +2358,11 @@ def main():
|
||||
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)
|
||||
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
sig_timer = QTimer()
|
||||
sig_timer.timeout.connect(lambda: None)
|
||||
sig_timer.start(500)
|
||||
|
||||
sys.exit(app.exec())
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user