sqlbench 0.1.1__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sqlbench-0.1.1 → sqlbench-0.1.2}/PKG-INFO +1 -1
- {sqlbench-0.1.1 → sqlbench-0.1.2}/pyproject.toml +2 -1
- sqlbench-0.1.2/sqlbench/__main__.py +40 -0
- {sqlbench-0.1.1 → sqlbench-0.1.2}/sqlbench/app.py +122 -5
- sqlbench-0.1.2/sqlbench/launcher.py +278 -0
- sqlbench-0.1.2/sqlbench/resources/sqlbench.png +0 -0
- sqlbench-0.1.2/sqlbench/version.py +57 -0
- sqlbench-0.1.1/sqlbench/__main__.py +0 -7
- {sqlbench-0.1.1 → sqlbench-0.1.2}/.github/workflows/pypi-publish.yml +0 -0
- {sqlbench-0.1.1 → sqlbench-0.1.2}/.gitignore +0 -0
- {sqlbench-0.1.1 → sqlbench-0.1.2}/Makefile +0 -0
- {sqlbench-0.1.1 → sqlbench-0.1.2}/README.md +0 -0
- {sqlbench-0.1.1 → sqlbench-0.1.2}/requirements.txt +0 -0
- {sqlbench-0.1.1 → sqlbench-0.1.2}/sqlbench/__init__.py +0 -0
- {sqlbench-0.1.1 → sqlbench-0.1.2}/sqlbench/adapters.py +0 -0
- {sqlbench-0.1.1 → sqlbench-0.1.2}/sqlbench/database.py +0 -0
- {sqlbench-0.1.1 → sqlbench-0.1.2}/sqlbench/dialogs/__init__.py +0 -0
- {sqlbench-0.1.1 → sqlbench-0.1.2}/sqlbench/dialogs/connection_dialog.py +0 -0
- {sqlbench-0.1.1 → sqlbench-0.1.2}/sqlbench/dialogs/regex_builder_dialog.py +0 -0
- {sqlbench-0.1.1 → sqlbench-0.1.2}/sqlbench/tabs/__init__.py +0 -0
- {sqlbench-0.1.1 → sqlbench-0.1.2}/sqlbench/tabs/spool_tab.py +0 -0
- {sqlbench-0.1.1 → sqlbench-0.1.2}/sqlbench/tabs/sql_tab.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlbench
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: A multi-database SQL workbench with support for IBM i, MySQL, and PostgreSQL
|
|
5
5
|
Project-URL: Homepage, https://github.com/jpsteil/sqlbench
|
|
6
6
|
Project-URL: Repository, https://github.com/jpsteil/sqlbench
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sqlbench"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "A multi-database SQL workbench with support for IBM i, MySQL, and PostgreSQL"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -60,6 +60,7 @@ Issues = "https://github.com/jpsteil/sqlbench/issues"
|
|
|
60
60
|
|
|
61
61
|
[tool.hatch.build.targets.wheel]
|
|
62
62
|
packages = ["sqlbench"]
|
|
63
|
+
include = ["sqlbench/resources/*"]
|
|
63
64
|
|
|
64
65
|
[tool.ruff]
|
|
65
66
|
line-length = 100
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Entry point for sqlbench."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
"""Main entry point with argument handling."""
|
|
8
|
+
if len(sys.argv) > 1:
|
|
9
|
+
arg = sys.argv[1].lower()
|
|
10
|
+
|
|
11
|
+
if arg in ("--install-launcher", "--create-launcher"):
|
|
12
|
+
from sqlbench.launcher import create_launcher
|
|
13
|
+
success = create_launcher()
|
|
14
|
+
sys.exit(0 if success else 1)
|
|
15
|
+
|
|
16
|
+
elif arg in ("--remove-launcher", "--uninstall-launcher"):
|
|
17
|
+
from sqlbench.launcher import remove_launcher
|
|
18
|
+
success = remove_launcher()
|
|
19
|
+
sys.exit(0 if success else 1)
|
|
20
|
+
|
|
21
|
+
elif arg in ("--help", "-h"):
|
|
22
|
+
print("SQLBench - Multi-database SQL Workbench")
|
|
23
|
+
print()
|
|
24
|
+
print("Usage: sqlbench [options]")
|
|
25
|
+
print()
|
|
26
|
+
print("Options:")
|
|
27
|
+
print(" --install-launcher Create a desktop launcher for this OS")
|
|
28
|
+
print(" --remove-launcher Remove the desktop launcher")
|
|
29
|
+
print(" --help, -h Show this help message")
|
|
30
|
+
print()
|
|
31
|
+
print("Run without arguments to start the application.")
|
|
32
|
+
sys.exit(0)
|
|
33
|
+
|
|
34
|
+
# Start the GUI application
|
|
35
|
+
from sqlbench.app import main as app_main
|
|
36
|
+
app_main()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
main()
|
|
@@ -41,6 +41,9 @@ class SQLBenchApp:
|
|
|
41
41
|
# Restore last connection and tabs after UI is ready
|
|
42
42
|
self.root.after(100, self._restore_session)
|
|
43
43
|
|
|
44
|
+
# Check for updates in background
|
|
45
|
+
self.root.after(500, self._check_for_updates)
|
|
46
|
+
|
|
44
47
|
def _create_menu(self):
|
|
45
48
|
menubar = tk.Menu(self.root)
|
|
46
49
|
self.root.config(menu=menubar)
|
|
@@ -939,8 +942,112 @@ class SQLBenchApp:
|
|
|
939
942
|
self.db.delete_connection(conn["id"])
|
|
940
943
|
self._refresh_connections()
|
|
941
944
|
|
|
945
|
+
def _install_launcher(self):
|
|
946
|
+
"""Install desktop launcher for the current OS."""
|
|
947
|
+
from sqlbench.launcher import create_launcher
|
|
948
|
+
try:
|
|
949
|
+
success = create_launcher()
|
|
950
|
+
if success:
|
|
951
|
+
tk.messagebox.showinfo(
|
|
952
|
+
"Launcher Installed",
|
|
953
|
+
"Desktop launcher installed successfully.\n\n"
|
|
954
|
+
"You may need to log out and back in for it to appear in your application menu."
|
|
955
|
+
)
|
|
956
|
+
else:
|
|
957
|
+
tk.messagebox.showwarning(
|
|
958
|
+
"Installation Failed",
|
|
959
|
+
"Could not install desktop launcher for this operating system."
|
|
960
|
+
)
|
|
961
|
+
except Exception as e:
|
|
962
|
+
tk.messagebox.showerror("Error", f"Failed to install launcher:\n{e}")
|
|
963
|
+
|
|
964
|
+
def _remove_launcher(self):
|
|
965
|
+
"""Remove desktop launcher for the current OS."""
|
|
966
|
+
from sqlbench.launcher import remove_launcher
|
|
967
|
+
try:
|
|
968
|
+
success = remove_launcher()
|
|
969
|
+
if success:
|
|
970
|
+
tk.messagebox.showinfo(
|
|
971
|
+
"Launcher Removed",
|
|
972
|
+
"Desktop launcher removed successfully."
|
|
973
|
+
)
|
|
974
|
+
else:
|
|
975
|
+
tk.messagebox.showinfo(
|
|
976
|
+
"No Launcher Found",
|
|
977
|
+
"No desktop launcher was found to remove."
|
|
978
|
+
)
|
|
979
|
+
except Exception as e:
|
|
980
|
+
tk.messagebox.showerror("Error", f"Failed to remove launcher:\n{e}")
|
|
981
|
+
|
|
982
|
+
def _check_for_updates(self):
|
|
983
|
+
"""Check for updates in background."""
|
|
984
|
+
from sqlbench.version import check_for_updates, __version__
|
|
985
|
+
|
|
986
|
+
def on_update_check(has_update, latest_version):
|
|
987
|
+
if has_update:
|
|
988
|
+
self.root.after(0, lambda: self._show_update_dialog(latest_version))
|
|
989
|
+
|
|
990
|
+
check_for_updates(on_update_check)
|
|
991
|
+
|
|
992
|
+
def _show_update_dialog(self, latest_version):
|
|
993
|
+
"""Show update available dialog."""
|
|
994
|
+
from sqlbench.version import __version__
|
|
995
|
+
|
|
996
|
+
result = tk.messagebox.askyesno(
|
|
997
|
+
"Update Available",
|
|
998
|
+
f"A new version of SQLBench is available.\n\n"
|
|
999
|
+
f"Installed: {__version__}\n"
|
|
1000
|
+
f"Latest: {latest_version}\n\n"
|
|
1001
|
+
f"Would you like to upgrade now?\n\n"
|
|
1002
|
+
f"(This will run: pipx upgrade sqlbench)"
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
if result:
|
|
1006
|
+
self._run_upgrade()
|
|
1007
|
+
|
|
1008
|
+
def _run_upgrade(self):
|
|
1009
|
+
"""Run pipx upgrade in background."""
|
|
1010
|
+
import subprocess
|
|
1011
|
+
import threading
|
|
1012
|
+
|
|
1013
|
+
def do_upgrade():
|
|
1014
|
+
try:
|
|
1015
|
+
result = subprocess.run(
|
|
1016
|
+
["pipx", "upgrade", "sqlbench"],
|
|
1017
|
+
capture_output=True,
|
|
1018
|
+
text=True
|
|
1019
|
+
)
|
|
1020
|
+
if result.returncode == 0:
|
|
1021
|
+
self.root.after(0, lambda: tk.messagebox.showinfo(
|
|
1022
|
+
"Upgrade Complete",
|
|
1023
|
+
"SQLBench has been upgraded.\n\n"
|
|
1024
|
+
"Please restart the application to use the new version."
|
|
1025
|
+
))
|
|
1026
|
+
else:
|
|
1027
|
+
error = result.stderr or result.stdout or "Unknown error"
|
|
1028
|
+
self.root.after(0, lambda: tk.messagebox.showerror(
|
|
1029
|
+
"Upgrade Failed",
|
|
1030
|
+
f"Failed to upgrade:\n{error}"
|
|
1031
|
+
))
|
|
1032
|
+
except FileNotFoundError:
|
|
1033
|
+
self.root.after(0, lambda: tk.messagebox.showerror(
|
|
1034
|
+
"Upgrade Failed",
|
|
1035
|
+
"pipx not found. Please upgrade manually:\n\n"
|
|
1036
|
+
"pipx upgrade sqlbench"
|
|
1037
|
+
))
|
|
1038
|
+
except Exception as e:
|
|
1039
|
+
self.root.after(0, lambda: tk.messagebox.showerror(
|
|
1040
|
+
"Upgrade Failed",
|
|
1041
|
+
f"Failed to upgrade:\n{e}"
|
|
1042
|
+
))
|
|
1043
|
+
|
|
1044
|
+
self.statusbar.config(text="Upgrading SQLBench...")
|
|
1045
|
+
thread = threading.Thread(target=do_upgrade, daemon=True)
|
|
1046
|
+
thread.start()
|
|
1047
|
+
|
|
942
1048
|
def _show_about(self):
|
|
943
|
-
|
|
1049
|
+
from sqlbench.version import __version__
|
|
1050
|
+
tk.messagebox.showinfo("About", f"SQLBench v{__version__}\nMulti-database SQL Workbench")
|
|
944
1051
|
|
|
945
1052
|
def _show_settings(self):
|
|
946
1053
|
"""Show settings dialog."""
|
|
@@ -950,10 +1057,10 @@ class SQLBenchApp:
|
|
|
950
1057
|
settings_win.grab_set()
|
|
951
1058
|
|
|
952
1059
|
# Center on parent
|
|
953
|
-
settings_win.geometry("
|
|
1060
|
+
settings_win.geometry("350x200")
|
|
954
1061
|
settings_win.update_idletasks()
|
|
955
|
-
x = self.root.winfo_x() + (self.root.winfo_width() -
|
|
956
|
-
y = self.root.winfo_y() + (self.root.winfo_height() -
|
|
1062
|
+
x = self.root.winfo_x() + (self.root.winfo_width() - 350) // 2
|
|
1063
|
+
y = self.root.winfo_y() + (self.root.winfo_height() - 200) // 2
|
|
957
1064
|
settings_win.geometry(f"+{x}+{y}")
|
|
958
1065
|
|
|
959
1066
|
# Apply dark mode
|
|
@@ -968,7 +1075,7 @@ class SQLBenchApp:
|
|
|
968
1075
|
|
|
969
1076
|
# Font size setting
|
|
970
1077
|
font_frame = ttk.Frame(settings_win)
|
|
971
|
-
font_frame.pack(fill=tk.X, padx=20, pady=20)
|
|
1078
|
+
font_frame.pack(fill=tk.X, padx=20, pady=(20, 10))
|
|
972
1079
|
|
|
973
1080
|
ttk.Label(font_frame, text="Font Size:").pack(side=tk.LEFT)
|
|
974
1081
|
|
|
@@ -977,6 +1084,16 @@ class SQLBenchApp:
|
|
|
977
1084
|
textvariable=font_size_var)
|
|
978
1085
|
font_spin.pack(side=tk.LEFT, padx=(10, 0))
|
|
979
1086
|
|
|
1087
|
+
# Desktop launcher
|
|
1088
|
+
launcher_frame = ttk.Frame(settings_win)
|
|
1089
|
+
launcher_frame.pack(fill=tk.X, padx=20, pady=10)
|
|
1090
|
+
|
|
1091
|
+
ttk.Label(launcher_frame, text="Desktop:").pack(side=tk.LEFT)
|
|
1092
|
+
ttk.Button(launcher_frame, text="Install Launcher",
|
|
1093
|
+
command=self._install_launcher).pack(side=tk.LEFT, padx=(10, 0))
|
|
1094
|
+
ttk.Button(launcher_frame, text="Remove Launcher",
|
|
1095
|
+
command=self._remove_launcher).pack(side=tk.LEFT, padx=(5, 0))
|
|
1096
|
+
|
|
980
1097
|
# Buttons
|
|
981
1098
|
btn_frame = ttk.Frame(settings_win)
|
|
982
1099
|
btn_frame.pack(fill=tk.X, padx=20, pady=10)
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""Desktop launcher creation for SQLBench."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_executable_path():
|
|
11
|
+
"""Get the path to the sqlbench executable."""
|
|
12
|
+
# When installed via pipx, the executable is in the PATH
|
|
13
|
+
return shutil.which("sqlbench") or sys.executable
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_icon_path():
|
|
17
|
+
"""Get the path to the bundled icon."""
|
|
18
|
+
# Icon is in the resources subdirectory of the package
|
|
19
|
+
package_dir = Path(__file__).parent
|
|
20
|
+
return package_dir / "resources" / "sqlbench.png"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def install_icon_linux():
|
|
24
|
+
"""Install the icon to the standard Linux icon location."""
|
|
25
|
+
icon_source = get_icon_path()
|
|
26
|
+
if not icon_source.exists():
|
|
27
|
+
print(f"Warning: Icon not found at {icon_source}")
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
# Install to user's icon directory
|
|
31
|
+
icon_dir = Path.home() / ".local" / "share" / "icons" / "hicolor" / "256x256" / "apps"
|
|
32
|
+
icon_dir.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
icon_dest = icon_dir / "sqlbench.png"
|
|
35
|
+
shutil.copy2(icon_source, icon_dest)
|
|
36
|
+
|
|
37
|
+
# Update icon cache (optional, may not work without root)
|
|
38
|
+
try:
|
|
39
|
+
os.system("gtk-update-icon-cache -f -t ~/.local/share/icons/hicolor 2>/dev/null")
|
|
40
|
+
except Exception:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
return "sqlbench" # Return icon name for .desktop file
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def create_linux_launcher():
|
|
47
|
+
"""Create a .desktop file for Linux."""
|
|
48
|
+
desktop_dir = os.path.expanduser("~/.local/share/applications")
|
|
49
|
+
os.makedirs(desktop_dir, exist_ok=True)
|
|
50
|
+
|
|
51
|
+
desktop_file = os.path.join(desktop_dir, "sqlbench.desktop")
|
|
52
|
+
|
|
53
|
+
# Get executable path
|
|
54
|
+
exec_path = shutil.which("sqlbench")
|
|
55
|
+
if not exec_path:
|
|
56
|
+
# Fallback to python -m sqlbench
|
|
57
|
+
exec_path = f"{sys.executable} -m sqlbench"
|
|
58
|
+
|
|
59
|
+
# Install icon and get icon name/path
|
|
60
|
+
icon_name = install_icon_linux()
|
|
61
|
+
if not icon_name:
|
|
62
|
+
# Fallback to bundled icon path or generic icon
|
|
63
|
+
icon_path = get_icon_path()
|
|
64
|
+
icon_name = str(icon_path) if icon_path.exists() else "utilities-terminal"
|
|
65
|
+
|
|
66
|
+
content = f"""[Desktop Entry]
|
|
67
|
+
Name=SQLBench
|
|
68
|
+
Comment=Multi-database SQL Workbench
|
|
69
|
+
Exec={exec_path}
|
|
70
|
+
Icon={icon_name}
|
|
71
|
+
Type=Application
|
|
72
|
+
Categories=Development;Database;
|
|
73
|
+
Terminal=false
|
|
74
|
+
StartupWMClass=sqlbench
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
with open(desktop_file, "w") as f:
|
|
78
|
+
f.write(content)
|
|
79
|
+
|
|
80
|
+
# Make executable
|
|
81
|
+
os.chmod(desktop_file, 0o755)
|
|
82
|
+
|
|
83
|
+
print(f"Created Linux desktop launcher: {desktop_file}")
|
|
84
|
+
print("Icon installed to ~/.local/share/icons/hicolor/256x256/apps/sqlbench.png")
|
|
85
|
+
print("You may need to log out and back in for it to appear in your application menu.")
|
|
86
|
+
return True
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def create_macos_launcher():
|
|
90
|
+
"""Create an application launcher for macOS."""
|
|
91
|
+
app_base = os.path.expanduser("~/Applications/SQLBench.app/Contents")
|
|
92
|
+
app_dir = os.path.join(app_base, "MacOS")
|
|
93
|
+
resources_dir = os.path.join(app_base, "Resources")
|
|
94
|
+
os.makedirs(app_dir, exist_ok=True)
|
|
95
|
+
os.makedirs(resources_dir, exist_ok=True)
|
|
96
|
+
|
|
97
|
+
# Get executable path
|
|
98
|
+
exec_path = shutil.which("sqlbench")
|
|
99
|
+
if not exec_path:
|
|
100
|
+
exec_path = f"{sys.executable} -m sqlbench"
|
|
101
|
+
|
|
102
|
+
# Create the launcher script
|
|
103
|
+
launcher_script = os.path.join(app_dir, "SQLBench")
|
|
104
|
+
content = f"""#!/bin/bash
|
|
105
|
+
exec {exec_path}
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
with open(launcher_script, "w") as f:
|
|
109
|
+
f.write(content)
|
|
110
|
+
os.chmod(launcher_script, 0o755)
|
|
111
|
+
|
|
112
|
+
# Copy icon to Resources
|
|
113
|
+
icon_source = get_icon_path()
|
|
114
|
+
icon_dest = os.path.join(resources_dir, "sqlbench.png")
|
|
115
|
+
if icon_source.exists():
|
|
116
|
+
shutil.copy2(icon_source, icon_dest)
|
|
117
|
+
# Also try to create .icns for better macOS integration
|
|
118
|
+
# (PNG works but .icns is preferred)
|
|
119
|
+
|
|
120
|
+
# Create Info.plist
|
|
121
|
+
plist_file = os.path.join(app_base, "Info.plist")
|
|
122
|
+
plist_content = """<?xml version="1.0" encoding="UTF-8"?>
|
|
123
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
124
|
+
<plist version="1.0">
|
|
125
|
+
<dict>
|
|
126
|
+
<key>CFBundleExecutable</key>
|
|
127
|
+
<string>SQLBench</string>
|
|
128
|
+
<key>CFBundleIdentifier</key>
|
|
129
|
+
<string>com.sqlbench.app</string>
|
|
130
|
+
<key>CFBundleName</key>
|
|
131
|
+
<string>SQLBench</string>
|
|
132
|
+
<key>CFBundleVersion</key>
|
|
133
|
+
<string>1.0</string>
|
|
134
|
+
<key>CFBundlePackageType</key>
|
|
135
|
+
<string>APPL</string>
|
|
136
|
+
<key>CFBundleIconFile</key>
|
|
137
|
+
<string>sqlbench.png</string>
|
|
138
|
+
</dict>
|
|
139
|
+
</plist>
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
with open(plist_file, "w") as f:
|
|
143
|
+
f.write(plist_content)
|
|
144
|
+
|
|
145
|
+
print(f"Created macOS application: ~/Applications/SQLBench.app")
|
|
146
|
+
print("You can drag it to your Dock or find it in ~/Applications.")
|
|
147
|
+
return True
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def create_windows_launcher():
|
|
151
|
+
"""Create a Start Menu shortcut for Windows."""
|
|
152
|
+
try:
|
|
153
|
+
import winreg
|
|
154
|
+
from pathlib import Path
|
|
155
|
+
|
|
156
|
+
# Get Start Menu path
|
|
157
|
+
start_menu = Path(os.environ.get("APPDATA", "")) / "Microsoft" / "Windows" / "Start Menu" / "Programs"
|
|
158
|
+
|
|
159
|
+
if not start_menu.exists():
|
|
160
|
+
print(f"Could not find Start Menu folder: {start_menu}")
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
# Get executable path
|
|
164
|
+
exec_path = shutil.which("sqlbench.exe") or shutil.which("sqlbench")
|
|
165
|
+
if not exec_path:
|
|
166
|
+
exec_path = f'"{sys.executable}" -m sqlbench'
|
|
167
|
+
|
|
168
|
+
# Create .bat launcher (simpler than .lnk without pywin32)
|
|
169
|
+
bat_file = start_menu / "SQLBench.bat"
|
|
170
|
+
content = f"""@echo off
|
|
171
|
+
start "" {exec_path}
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
with open(bat_file, "w") as f:
|
|
175
|
+
f.write(content)
|
|
176
|
+
|
|
177
|
+
print(f"Created Windows Start Menu launcher: {bat_file}")
|
|
178
|
+
print("You can find SQLBench in your Start Menu.")
|
|
179
|
+
return True
|
|
180
|
+
|
|
181
|
+
except Exception as e:
|
|
182
|
+
print(f"Failed to create Windows launcher: {e}")
|
|
183
|
+
|
|
184
|
+
# Fallback: create on Desktop
|
|
185
|
+
desktop = os.path.expanduser("~/Desktop")
|
|
186
|
+
if os.path.exists(desktop):
|
|
187
|
+
exec_path = shutil.which("sqlbench.exe") or shutil.which("sqlbench")
|
|
188
|
+
if not exec_path:
|
|
189
|
+
exec_path = f'"{sys.executable}" -m sqlbench'
|
|
190
|
+
|
|
191
|
+
bat_file = os.path.join(desktop, "SQLBench.bat")
|
|
192
|
+
content = f"""@echo off
|
|
193
|
+
start "" {exec_path}
|
|
194
|
+
"""
|
|
195
|
+
with open(bat_file, "w") as f:
|
|
196
|
+
f.write(content)
|
|
197
|
+
|
|
198
|
+
print(f"Created Desktop launcher instead: {bat_file}")
|
|
199
|
+
return True
|
|
200
|
+
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def create_launcher():
|
|
205
|
+
"""Create a desktop launcher appropriate for the current OS."""
|
|
206
|
+
system = platform.system().lower()
|
|
207
|
+
|
|
208
|
+
print(f"Detected OS: {platform.system()}")
|
|
209
|
+
|
|
210
|
+
if system == "linux":
|
|
211
|
+
return create_linux_launcher()
|
|
212
|
+
elif system == "darwin":
|
|
213
|
+
return create_macos_launcher()
|
|
214
|
+
elif system == "windows":
|
|
215
|
+
return create_windows_launcher()
|
|
216
|
+
else:
|
|
217
|
+
print(f"Unsupported operating system: {system}")
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def remove_launcher():
|
|
222
|
+
"""Remove the desktop launcher for the current OS."""
|
|
223
|
+
system = platform.system().lower()
|
|
224
|
+
|
|
225
|
+
if system == "linux":
|
|
226
|
+
removed = False
|
|
227
|
+
desktop_file = os.path.expanduser("~/.local/share/applications/sqlbench.desktop")
|
|
228
|
+
if os.path.exists(desktop_file):
|
|
229
|
+
os.remove(desktop_file)
|
|
230
|
+
print(f"Removed: {desktop_file}")
|
|
231
|
+
removed = True
|
|
232
|
+
|
|
233
|
+
# Also remove the icon
|
|
234
|
+
icon_file = os.path.expanduser("~/.local/share/icons/hicolor/256x256/apps/sqlbench.png")
|
|
235
|
+
if os.path.exists(icon_file):
|
|
236
|
+
os.remove(icon_file)
|
|
237
|
+
print(f"Removed: {icon_file}")
|
|
238
|
+
removed = True
|
|
239
|
+
|
|
240
|
+
if not removed:
|
|
241
|
+
print("No Linux launcher found.")
|
|
242
|
+
return False
|
|
243
|
+
return True
|
|
244
|
+
|
|
245
|
+
elif system == "darwin":
|
|
246
|
+
app_dir = os.path.expanduser("~/Applications/SQLBench.app")
|
|
247
|
+
if os.path.exists(app_dir):
|
|
248
|
+
shutil.rmtree(app_dir)
|
|
249
|
+
print(f"Removed: {app_dir}")
|
|
250
|
+
return True
|
|
251
|
+
else:
|
|
252
|
+
print("No macOS launcher found.")
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
elif system == "windows":
|
|
256
|
+
start_menu = os.path.join(
|
|
257
|
+
os.environ.get("APPDATA", ""),
|
|
258
|
+
"Microsoft", "Windows", "Start Menu", "Programs", "SQLBench.bat"
|
|
259
|
+
)
|
|
260
|
+
desktop = os.path.expanduser("~/Desktop/SQLBench.bat")
|
|
261
|
+
|
|
262
|
+
removed = False
|
|
263
|
+
if os.path.exists(start_menu):
|
|
264
|
+
os.remove(start_menu)
|
|
265
|
+
print(f"Removed: {start_menu}")
|
|
266
|
+
removed = True
|
|
267
|
+
if os.path.exists(desktop):
|
|
268
|
+
os.remove(desktop)
|
|
269
|
+
print(f"Removed: {desktop}")
|
|
270
|
+
removed = True
|
|
271
|
+
|
|
272
|
+
if not removed:
|
|
273
|
+
print("No Windows launcher found.")
|
|
274
|
+
return removed
|
|
275
|
+
|
|
276
|
+
else:
|
|
277
|
+
print(f"Unsupported operating system: {system}")
|
|
278
|
+
return False
|
|
Binary file
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Version checking for SQLBench."""
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
import urllib.request
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.2"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_installed_version():
|
|
11
|
+
"""Get the currently installed version."""
|
|
12
|
+
return __version__
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_pypi_version():
|
|
16
|
+
"""Fetch the latest version from PyPI."""
|
|
17
|
+
try:
|
|
18
|
+
url = "https://pypi.org/pypi/sqlbench/json"
|
|
19
|
+
with urllib.request.urlopen(url, timeout=5) as response:
|
|
20
|
+
data = json.loads(response.read().decode())
|
|
21
|
+
return data["info"]["version"]
|
|
22
|
+
except Exception:
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def parse_version(version_str):
|
|
27
|
+
"""Parse version string into tuple for comparison."""
|
|
28
|
+
try:
|
|
29
|
+
parts = version_str.split(".")
|
|
30
|
+
return tuple(int(p) for p in parts)
|
|
31
|
+
except (ValueError, AttributeError):
|
|
32
|
+
return (0, 0, 0)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_newer_version(pypi_version, installed_version):
|
|
36
|
+
"""Check if PyPI version is newer than installed version."""
|
|
37
|
+
return parse_version(pypi_version) > parse_version(installed_version)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def check_for_updates(callback):
|
|
41
|
+
"""Check for updates in background thread.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
callback: Function to call with (has_update, latest_version) or (False, None) on error
|
|
45
|
+
"""
|
|
46
|
+
def do_check():
|
|
47
|
+
try:
|
|
48
|
+
latest = get_pypi_version()
|
|
49
|
+
if latest and is_newer_version(latest, __version__):
|
|
50
|
+
callback(True, latest)
|
|
51
|
+
else:
|
|
52
|
+
callback(False, latest)
|
|
53
|
+
except Exception:
|
|
54
|
+
callback(False, None)
|
|
55
|
+
|
|
56
|
+
thread = threading.Thread(target=do_check, daemon=True)
|
|
57
|
+
thread.start()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|