@echo off setlocal EnableExtensions pushd "%~dp0" title VimpulseHub v5 - Update and AppStore Server color 0A REM ============================================================ REM VimpulseHub v5 Combined Server REM Update API: 8765 REM Download / AppStore: 8766 REM REM Windows software lives in: apps\ REM Android apps live in: android\ REM ============================================================ set "HOST=10.0.0.30" rem Set this to your DDNS hostname, for example hub.example-ddns.com set "DDNS_HOST=" if not "%DDNS_HOST%"=="" (set "PUBLIC_HOST=%DDNS_HOST%") else (set "PUBLIC_HOST=%HOST%") set "BIND_HOST=0.0.0.0" set "API_PORT=8765" set "WEB_PORT=8766" set "SOFTWARE_ROOT=%~dp0apps" set "ANDROID_ROOT=%~dp0android" set "PLUGIN_ROOT=%~dp0plugins" set "RUNTIME_DIR=%~dp0_vimpulse_runtime" set "PY_FILE=%RUNTIME_DIR%\vimpulse_web.py" set "HTML_FILE=%RUNTIME_DIR%\index.html" cls echo ============================================================ echo VimpulseHub v5 Server echo ============================================================ echo. echo Windows Software: apps\ echo Android Apps: android\ echo. REM ------------------------------------------------------------ REM Create Windows software folders. REM ------------------------------------------------------------ if not exist "apps" mkdir "apps" if not exist "plugins" mkdir "plugins" if not exist "logs" mkdir "logs" call :MakeSoftware "StoryCut" call :MakeSoftware "StoryRealm Studio" call :MakeSoftware "Vault" call :MakeSoftware "GobhaInk" call :MakeSoftware "cybervault" call :MakeSoftware "vimpulsetechtoolkit" call :MakeToolkitEdition "Pro" call :MakeToolkitEdition "Business" call :MakeSoftware "vimvnc" call :MakeSoftware "vimpulsescamshield" call :MakeSoftware "privatevaultnas" REM ------------------------------------------------------------ REM Create Android app folders. REM ------------------------------------------------------------ if not exist "android" mkdir "android" if not exist "androidlogs" mkdir "androidlogs" call :MakeAndroid "BroomMap" call :MakeAndroid "EarnClearOntario" call :MakeAndroid "FastLife" call :MakeAndroid "GasCalculator" call :MakeAndroid "EVCalculator" call :MakeAndroid "InfiniteDispatch" call :MakeAndroid "PersonalWheelOfFortune" call :MakeAndroid "SideQuest" echo Folder structure ready. echo. where python >nul 2>nul if errorlevel 1 ( where py >nul 2>nul if errorlevel 1 ( echo ERROR: Python 3 was not found in PATH. echo Install Python 3 and enable "Add Python to PATH". echo. pause exit /b 1 ) ) if not exist "%RUNTIME_DIR%" mkdir "%RUNTIME_DIR%" REM ------------------------------------------------------------ REM Extract embedded Python and HTML from this BAT. REM ------------------------------------------------------------ powershell -NoProfile -ExecutionPolicy Bypass -Command ^ "$bat=[IO.File]::ReadAllText('%~f0');" ^ "$pyStart=$bat.LastIndexOf(':__VIMPULSE_PY__');" ^ "$htmlStart=$bat.LastIndexOf(':__VIMPULSE_HTML__');" ^ "if($pyStart -lt 0 -or $htmlStart -lt 0 -or $htmlStart -le $pyStart){throw 'Embedded markers not found.'};" ^ "$py=$bat.Substring($pyStart+':__VIMPULSE_PY__'.Length,$htmlStart-($pyStart+':__VIMPULSE_PY__'.Length)).Trim([char]13,[char]10);" ^ "$html=$bat.Substring($htmlStart+':__VIMPULSE_HTML__'.Length).TrimStart([char]13,[char]10);" ^ "[IO.File]::WriteAllText('%PY_FILE%',$py,(New-Object Text.UTF8Encoding($false)));" ^ "[IO.File]::WriteAllText('%HTML_FILE%',$html,(New-Object Text.UTF8Encoding($false)))" if errorlevel 1 ( echo ERROR: Could not create runtime server files. pause exit /b 1 ) REM ------------------------------------------------------------ REM Check whether the GUI port is already in use. REM ------------------------------------------------------------ netstat -ano | findstr /R /C:":%WEB_PORT% .*LISTENING" >nul 2>&1 if %ERRORLEVEL% EQU 0 ( echo ERROR: Port %WEB_PORT% is already in use. echo Close the old VimpulseHub download server first. echo. netstat -ano | findstr /R /C:":%WEB_PORT% .*LISTENING" pause exit /b 1 ) REM ------------------------------------------------------------ REM Start the existing update API server once, if server.py exists. REM This keeps the app/software update API on port 8765. REM ------------------------------------------------------------ if exist "%~dp0server.py" ( netstat -ano | findstr /R /C:":%API_PORT% .*LISTENING" >nul 2>&1 if not %ERRORLEVEL% EQU 0 ( echo Starting Update API on %HOST%:%API_PORT% ... start "Vimpulse Update API" /min cmd /c "pushd ""%~dp0"" && python server.py" ) else ( echo Update API already running on port %API_PORT%. ) ) else ( echo WARNING: server.py was not found beside this BAT. echo The AppStore/download server will still work, but port %API_PORT% will not be started by this BAT. ) echo. echo ============================================================ echo VimpulseHub is starting echo ============================================================ echo Internal AppStore: echo http://%PUBLIC_HOST%:%WEB_PORT% echo. echo Update API: echo http://%HOST%:%API_PORT% echo. echo Windows downloads map to: echo /software/ ^> apps\ echo. echo Android downloads map to: echo /android/ ^> android\ echo. echo Press CTRL+C to stop the AppStore/download server. echo ============================================================ echo. start "" powershell -NoProfile -WindowStyle Hidden -Command "Start-Sleep -Milliseconds 900; Start-Process 'http://%PUBLIC_HOST%:%WEB_PORT%/'" where py >nul 2>&1 if %ERRORLEVEL% EQU 0 ( py "%PY_FILE%" --bind-host "%BIND_HOST%" --public-host "%PUBLIC_HOST%" --port %WEB_PORT% --apps "%SOFTWARE_ROOT%" --android "%ANDROID_ROOT%" --plugins "%PLUGIN_ROOT%" --html "%HTML_FILE%" --api-port %API_PORT% ) else ( python "%PY_FILE%" --bind-host "%BIND_HOST%" --public-host "%PUBLIC_HOST%" --port %WEB_PORT% --apps "%SOFTWARE_ROOT%" --android "%ANDROID_ROOT%" --plugins "%PLUGIN_ROOT%" --html "%HTML_FILE%" --api-port %API_PORT% ) echo. echo VimpulseHub server stopped. pause exit /b :MakeToolkitEdition if not exist "apps\vimpulsetechtoolkit\%~1" mkdir "apps\vimpulsetechtoolkit\%~1" if not exist "apps\vimpulsetechtoolkit\%~1\updates" mkdir "apps\vimpulsetechtoolkit\%~1\updates" if not exist "apps\vimpulsetechtoolkit\%~1\icon" mkdir "apps\vimpulsetechtoolkit\%~1\icon" if not exist "apps\vimpulsetechtoolkit\%~1\screenshots" mkdir "apps\vimpulsetechtoolkit\%~1\screenshots" exit /b :MakeSoftware if not exist "apps\%~1" mkdir "apps\%~1" if not exist "apps\%~1\updates" mkdir "apps\%~1\updates" if not exist "apps\%~1\icon" mkdir "apps\%~1\icon" if not exist "apps\%~1\screenshots" mkdir "apps\%~1\screenshots" exit /b :MakeAndroid if not exist "android\%~1" mkdir "android\%~1" if not exist "android\%~1\updates" mkdir "android\%~1\updates" if not exist "android\%~1\icon" mkdir "android\%~1\icon" if not exist "android\%~1\screenshots" mkdir "android\%~1\screenshots" exit /b goto :eof :__VIMPULSE_PY__ import argparse import hashlib import html as html_lib import json import secrets import mimetypes import os import re import subprocess import tempfile import time import urllib.parse import urllib.request import zipfile from datetime import datetime from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from http.cookies import SimpleCookie from pathlib import Path parser = argparse.ArgumentParser() parser.add_argument('--bind-host', default='0.0.0.0') parser.add_argument('--public-host', default='10.0.0.30') parser.add_argument('--port', type=int, default=8766) parser.add_argument('--apps', required=True) parser.add_argument('--android', required=True) parser.add_argument('--plugins', required=True) parser.add_argument('--html', required=True) parser.add_argument('--api-port', type=int, default=8765) parser.add_argument('--seo-base-url', default='https://vimpulsehub.ca') args = parser.parse_args() SOFTWARE_ROOT = Path(args.apps).resolve() ANDROID_ROOT = Path(args.android).resolve() PLUGINS_ROOT = Path(args.plugins).resolve() PLUGINS_ROOT.mkdir(parents=True, exist_ok=True) HTML_FILE = Path(args.html).resolve() ICON_CACHE = HTML_FILE.parent / 'icon_cache' ICON_CACHE.mkdir(parents=True, exist_ok=True) SITE_URL = f'http://{args.public_host}:{args.port}' SEO_BASE_URL = args.seo_base_url.rstrip('/') STATS_FILE = HTML_FILE.parent / 'download_stats.json' USERS_FILE = HTML_FILE.parent / 'admin_users.json' CACHE_FILE = HTML_FILE.parent / 'catalog_cache.json' HASH_CACHE_FILE = HTML_FILE.parent / 'hash_cache.json' SERVER_STARTED = time.time() CATALOG_CACHE = {} CATALOG_CACHE_SIGNATURE = {} HASH_CACHE = {} try: HASH_CACHE = json.loads(HASH_CACHE_FILE.read_text(encoding='utf-8')) if HASH_CACHE_FILE.exists() else {} if not isinstance(HASH_CACHE, dict): HASH_CACHE = {} except Exception: HASH_CACHE = {} DOWNLOAD_STATS = None ADMIN_SESSIONS = {} SESSION_TTL = 60 * 60 * 12 PASSWORD_ITERATIONS = 260000 PRETTY_SOFTWARE = { 'storycut':'StoryCut', 'storyrealm studio':'StoryRealm Studio', 'storyrealmstudio':'StoryRealm Studio', 'vault':'Vault', 'vimpulseautoclicker':'Vimpulse AutoClicker', 'vimpulse game auto clicker':'Vimpulse Game Auto Clicker', 'gobhaink':'GobhaInk', 'vitaltrack':'VitalTrack', 'cybervault':'CyberVault', 'freshcore':'FreshCore', 'rigcore':'RigCore', 'storagemedic':'StorageMedic', 'vimpulsetechtoolkit':'Vimpulse Tech Toolkit', 'vimvnc':'VimVNC', 'vimpulsescamshield':'Vimpulse ScamShield' } DESC_SOFTWARE = { 'storycut':'Video editing made simple.', 'storyrealm studio':'Creative story and media tools.', 'storyrealmstudio':'Creative story and media tools.', 'vault':'Secure local file storage.', 'gobhaink':'Document editing and writing tools.', 'vitaltrack':'Personal vital tracking tools.', 'cybervault':'Portable encrypted USB vault.', 'freshcore':'System cleanup and maintenance.', 'rigcore':'PC diagnostics and system information.', 'storagemedic':'Storage health and repair utilities.', 'vimpulsetechtoolkit':'Advanced technician utilities.', 'vimvnc':'Simple, secure remote access.', 'vimpulsescamshield':'Scam and remote-access protection tools.' } ICON_SOFTWARE = { 'storycut':'▶', 'storyrealm studio':'✦', 'storyrealmstudio':'✦', 'vault':'◆', 'gobhaink':'✎', 'vitaltrack':'♥', 'cybervault':'🔒', 'freshcore':'✦', 'rigcore':'⚙', 'storagemedic':'▣', 'vimpulsetechtoolkit':'🔧', 'vimvnc':'▰', 'vimpulsescamshield':'🛡' } PRETTY_ANDROID = { 'broommap':'BroomMap', 'earnclearontario':'EarnClear Ontario', 'fastlife':'FastLife', 'gascalculator':'Gas Calculator', 'evcalculator':'EV Calculator', 'infinitedispatch':'Infinite Dispatch', 'personalwheeloffortune':'Personal Wheel of Fortune', 'sidequest':'SideQuest' } DESC_ANDROID = { 'broommap':'Android mapping and utility app.', 'earnclearontario':'ODSP earnings and benefit companion for Ontario.', 'fastlife':'Fasting, meal and weight tracking.', 'gascalculator':'Fuel cost and trip calculator.', 'evcalculator':'EV charging and trip calculator.', 'infinitedispatch':'Android dispatch utility.', 'personalwheeloffortune':'Personal wheel and choice helper.', 'sidequest':'Android side-project and activity app.' } ICON_ANDROID = { 'broommap':'⌖', 'earnclearontario':'$', 'fastlife':'◷', 'gascalculator':'⛽', 'evcalculator':'⚡', 'infinitedispatch':'↗', 'personalwheeloffortune':'◉', 'sidequest':'★' } SOFTWARE_EXTS = {'.exe', '.msi', '.zip'} ANDROID_EXTS = {'.apk', '.aab', '.zip'} def is_plugin_file(path): return path.is_file() and path.name.lower() != 'info.txt' and not path.name.startswith('.') def safe_inside(root: Path, relative: str): relative = urllib.parse.unquote(relative).replace('\\', '/') candidate = (root / relative.lstrip('/')).resolve() try: candidate.relative_to(root) return candidate except ValueError: return None def newest_file(folder: Path, extensions): files = [] for p in folder.rglob('*'): if p.is_file() and p.suffix.lower() in extensions: files.append(p) return max(files, key=lambda p: p.stat().st_mtime, default=None) def version_from_name(path: Path): m = re.search(r'(?i)(?:^|[^0-9])v?(\d+(?:\.\d+){1,3})(?:[^0-9]|$)', path.stem) return m.group(1) if m else 'Latest' def size_text(n): if n >= 1024**3: return f'{n/1024**3:.2f} GB' if n >= 1024**2: return f'{n/1024**2:.1f} MB' return f'{max(1, round(n/1024))} KB' def ensure_info_file(info_folder: Path, display_name: str, default_desc: str, platform: str): info_path = info_folder / 'info.txt' if not info_path.exists(): template = ( f'Name: {display_name}\n' f'Description: {default_desc}\n' f'Platform: {platform}\n' 'Cost: Free\n\n' 'About:\n' 'Edit this info.txt file to add more information about this program or app.\n' 'Set Cost to Free, $4.99, $9.99/month, etc.\n' 'Everything in this file will be shown in VimpulseHub when View is clicked.\n' ) try: info_path.write_text(template, encoding='utf-8') except OSError: pass try: text = info_path.read_text(encoding='utf-8', errors='replace') if not any(line.lower().startswith('cost:') for line in text.splitlines()): lines = text.splitlines() insert_at = next((i + 1 for i, line in enumerate(lines) if line.lower().startswith('platform:')), min(3, len(lines))) lines.insert(insert_at, 'Cost: Free') text = '\n'.join(lines).rstrip() + '\n' try: info_path.write_text(text, encoding='utf-8') except OSError: pass return text.strip() except OSError: return '' def value_from_info(info_text: str, field: str, fallback: str = ''): prefix = field.lower() + ':' for line in info_text.splitlines(): if line.lower().startswith(prefix): value = line.split(':', 1)[1].strip() if value: return value return fallback def description_from_info(info_text: str, fallback: str): for line in info_text.splitlines(): if line.lower().startswith('description:'): value = line.split(':', 1)[1].strip() if value: return value for line in info_text.splitlines(): value = line.strip() if value and not value.lower().startswith(('name:', 'platform:', 'version:', 'about:')): return value return fallback def about_from_info(info_text: str): lines = info_text.splitlines() collecting = False about_lines = [] for line in lines: stripped = line.strip() if stripped.lower() == 'about:': collecting = True continue if collecting: about_lines.append(line) return '\n'.join(about_lines).strip() def field_values_from_info(info_text: str, field: str): value = value_from_info(info_text, field, '') if not value: return [] return [x.strip() for x in re.split(r'[,;|]', value) if x.strip()] def requirements_from_info(info_text: str, kind: str): fields = [ ('Minimum OS', value_from_info(info_text, 'Minimum OS', '')), ('Architecture', value_from_info(info_text, 'Architecture', '')), ('RAM', value_from_info(info_text, 'RAM', '')), ('Disk Space', value_from_info(info_text, 'Disk Space', '')), ('Requires Admin', value_from_info(info_text, 'Requires Admin', '')), ('Portable', value_from_info(info_text, 'Portable', '')), ] if kind == 'android': fields.append(('Minimum Android', value_from_info(info_text, 'Minimum Android', ''))) return [{'label': label, 'value': value} for label, value in fields if value] def read_optional_text(folder: Path, names): for name in names: p = folder / name if p.is_file(): try: return p.read_text(encoding='utf-8', errors='replace').strip() except OSError: pass return '' def screenshots_for(info_folder: Path, kind: str): root = SOFTWARE_ROOT if kind == 'software' else ANDROID_ROOT shot_dir = info_folder / 'screenshots' if not shot_dir.is_dir(): return [] out = [] try: files = sorted( (p for p in shot_dir.iterdir() if p.is_file() and p.suffix.lower() in {'.png','.jpg','.jpeg','.webp','.gif'}), key=lambda p: p.name.lower() ) for p in files[:12]: rel = p.relative_to(root).as_posix() out.append(f'/screenshot/{kind}/{urllib.parse.quote(rel, safe="/")}') except OSError: pass return out def previous_releases(info_folder: Path, current_file: Path, kind: str, extensions): root = SOFTWARE_ROOT if kind == 'software' else ANDROID_ROOT releases = [] try: files = [p for p in info_folder.rglob('*') if p.is_file() and p.suffix.lower() in extensions and p.resolve() != current_file.resolve()] files.sort(key=lambda p: p.stat().st_mtime, reverse=True) for p in files[:10]: st = p.stat() rel = p.relative_to(root).as_posix() releases.append({ 'version': version_from_name(p), 'filename': p.name, 'size': size_text(st.st_size), 'updated': datetime.fromtimestamp(st.st_mtime).strftime('%b %d, %Y'), 'download': f'/{kind}/{urllib.parse.quote(rel, safe="/")}' }) except OSError: pass return releases def sha256_file(path: Path): key = str(path.resolve()) try: st = path.stat() cached = HASH_CACHE.get(key) if cached and cached.get('size') == st.st_size and cached.get('mtime_ns') == st.st_mtime_ns: return cached.get('sha256', '') except OSError: return '' h=hashlib.sha256() try: with path.open('rb') as f: for chunk in iter(lambda:f.read(1024*1024),b''): h.update(chunk) digest = h.hexdigest() try: st = path.stat() HASH_CACHE[key] = {'size': st.st_size, 'mtime_ns': st.st_mtime_ns, 'sha256': digest} HASH_CACHE_FILE.write_text(json.dumps(HASH_CACHE, indent=2), encoding='utf-8') except OSError: pass return digest except OSError: return '' def load_download_stats(): global DOWNLOAD_STATS if DOWNLOAD_STATS is not None: return DOWNLOAD_STATS try: if STATS_FILE.exists(): d=json.loads(STATS_FILE.read_text(encoding='utf-8')) DOWNLOAD_STATS = d if isinstance(d,dict) else {} return DOWNLOAD_STATS except Exception: pass DOWNLOAD_STATS = {} return DOWNLOAD_STATS def save_download_stats(d): global DOWNLOAD_STATS DOWNLOAD_STATS = d try: STATS_FILE.write_text(json.dumps(d,indent=2),encoding='utf-8') except OSError: pass def stat_key(kind,path): root=SOFTWARE_ROOT if kind=='software' else ANDROID_ROOT try: return kind+':'+path.relative_to(root).as_posix() except ValueError: return '' def download_count_for(kind,path): return int(load_download_stats().get(stat_key(kind,path),{}).get('count',0)) def record_download(kind,path): key=stat_key(kind,path) if not key:return d=load_download_stats(); row=d.get(key,{}) row.update({'count':int(row.get('count',0))+1,'filename':path.name,'kind':kind,'last_download':datetime.now().isoformat(timespec='seconds')}) d[key]=row;save_download_stats(d) def invalidate_catalog(kind=None): if kind: CATALOG_CACHE.pop(kind, None) CATALOG_CACHE_SIGNATURE.pop(kind, None) else: CATALOG_CACHE.clear() CATALOG_CACHE_SIGNATURE.clear() def catalog_signature(root: Path): try: newest = max((p.stat().st_mtime_ns for p in root.rglob('*') if p.is_file()), default=0) count = sum(1 for p in root.rglob('*') if p.is_file()) return (newest, count) except OSError: return (0, 0) def load_users(): try: if USERS_FILE.exists(): data = json.loads(USERS_FILE.read_text(encoding='utf-8')) if isinstance(data, dict): return data except Exception: pass return {} def save_users(users): try: tmp = USERS_FILE.with_suffix('.tmp') tmp.write_text(json.dumps(users, indent=2, ensure_ascii=False), encoding='utf-8') tmp.replace(USERS_FILE) return True except OSError: return False def hash_password(password, salt_hex=None): salt = bytes.fromhex(salt_hex) if salt_hex else secrets.token_bytes(16) digest = hashlib.pbkdf2_hmac( 'sha256', password.encode('utf-8'), salt, PASSWORD_ITERATIONS ) return f'pbkdf2_sha256${PASSWORD_ITERATIONS}${salt.hex()}${digest.hex()}' def verify_password(password, stored): try: scheme, iterations, salt_hex, expected_hex = stored.split('$', 3) if scheme != 'pbkdf2_sha256': return False digest = hashlib.pbkdf2_hmac( 'sha256', password.encode('utf-8'), bytes.fromhex(salt_hex), int(iterations) ).hex() return secrets.compare_digest(digest, expected_hex) except Exception: return False def clean_sessions(): now = time.time() dead = [sid for sid, data in ADMIN_SESSIONS.items() if data.get('expires', 0) <= now] for sid in dead: ADMIN_SESSIONS.pop(sid, None) def session_user(handler): clean_sessions() raw = handler.headers.get('Cookie', '') if not raw: return None cookie = SimpleCookie() try: cookie.load(raw) except Exception: return None morsel = cookie.get('vimpulse_admin') if not morsel: return None session = ADMIN_SESSIONS.get(morsel.value) if not session or session.get('expires', 0) <= time.time(): return None users = load_users() user = users.get(session.get('username', '')) if not user or not user.get('enabled', True): return None session['expires'] = time.time() + SESSION_TTL return { 'username': session.get('username', ''), 'role': user.get('role', 'editor'), 'enabled': True } def create_session(username): sid = secrets.token_urlsafe(32) ADMIN_SESSIONS[sid] = { 'username': username, 'expires': time.time() + SESSION_TTL } return sid def destroy_session(handler): raw = handler.headers.get('Cookie', '') cookie = SimpleCookie() try: cookie.load(raw) morsel = cookie.get('vimpulse_admin') if morsel: ADMIN_SESSIONS.pop(morsel.value, None) except Exception: pass def session_cookie(sid): return f'vimpulse_admin={sid}; Path=/admin; HttpOnly; SameSite=Strict; Max-Age={SESSION_TTL}' def clear_session_cookie(): return 'vimpulse_admin=; Path=/admin; HttpOnly; SameSite=Strict; Max-Age=0' def role_allowed(user, *roles): return bool(user and user.get('role') in roles) def admin_folder(kind, name): return safe_inside( SOFTWARE_ROOT if kind == 'software' else ANDROID_ROOT if kind == 'android' else PLUGINS_ROOT, name ) if kind in ('software', 'android', 'plugins') else None def esc_html(value): return html_lib.escape(str(value), quote=True) def setup_html(): return """ VimpulseHub Admin Setup

VimpulseHub Admin Setup

Create the first administrator account. This only appears when no admin accounts exist yet.

""" def login_html(): return """ VimpulseHub Admin Login

VimpulseHub Admin

Sign in to manage VimpulseHub.

""" def admin_html(user): is_admin = user.get('role') == 'admin' accounts = """

Accounts

""" if is_admin else "" role_json = json.dumps(user.get('role', 'editor')) username_json = json.dumps(user.get('username', '')) return """ VimpulseHub Admin

VimpulseHub Admin

Signed in as ·

Product Editor

Multiple categories supported, for example: Category: Editor, Word Processor

Create Product

Upload New Release

Product Screenshot

Download Statistics

Server Dashboard

Loading…

Download Statistics

""" + accounts + """
""" CUSTOM_ICON_EXTS = {'.png', '.jpg', '.jpeg', '.webp', '.gif', '.ico', '.bmp'} def ensure_icon_folder(folder: Path): icon_dir = folder / 'icon' try: icon_dir.mkdir(parents=True, exist_ok=True) except OSError: pass return icon_dir def custom_icon_file(folder: Path): icon_dir = ensure_icon_folder(folder) try: images = [p for p in icon_dir.iterdir() if p.is_file() and p.suffix.lower() in CUSTOM_ICON_EXTS] except OSError: return None if not images: return None # Only one image is needed. If more than one exists, use the newest one. return max(images, key=lambda p: p.stat().st_mtime) def icon_url_for(file: Path, kind: str, icon_folder: Path = None): root = SOFTWARE_ROOT if kind == 'software' else ANDROID_ROOT custom = custom_icon_file(icon_folder or file.parent) if custom: rel = custom.relative_to(root).as_posix() return f'/custom-icon/{kind}/{urllib.parse.quote(rel, safe="/")}' suffix = file.suffix.lower() if kind == 'software' and suffix == '.exe': rel = file.relative_to(root).as_posix() return f'/asset-icon/software/{urllib.parse.quote(rel, safe="/")}' if kind == 'android' and suffix == '.apk': rel = file.relative_to(root).as_posix() return f'/asset-icon/android/{urllib.parse.quote(rel, safe="/")}' return '' def windows_exe_icon_png(exe_path: Path): try: key = re.sub(r'[^A-Za-z0-9_.-]+', '_', str(exe_path.relative_to(SOFTWARE_ROOT))) except Exception: key = re.sub(r'[^A-Za-z0-9_.-]+', '_', exe_path.name) cache = ICON_CACHE / f'{key}.png' try: if cache.exists() and cache.stat().st_mtime >= exe_path.stat().st_mtime: return cache.read_bytes() except OSError: pass ps = ( "Add-Type -AssemblyName System.Drawing;" "$src=$args[0];$out=$args[1];" "$ico=[System.Drawing.Icon]::ExtractAssociatedIcon($src);" "if($null -eq $ico){exit 2};" "$bmp=$ico.ToBitmap();" "$bmp.Save($out,[System.Drawing.Imaging.ImageFormat]::Png);" "$bmp.Dispose();$ico.Dispose();" ) try: r = subprocess.run( ['powershell', '-NoProfile', '-NonInteractive', '-Command', ps, str(exe_path), str(cache)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=12 ) if r.returncode == 0 and cache.exists(): return cache.read_bytes() except Exception: pass return None def android_apk_icon(apk_path: Path): try: with zipfile.ZipFile(apk_path, 'r') as z: names = z.namelist() candidates = [] density_score = { 'xxxhdpi': 700, 'xxhdpi': 600, 'xhdpi': 500, 'hdpi': 400, 'mdpi': 300, 'nodpi': 250, 'anydpi': 100 } for name in names: low = name.lower() if not low.startswith('res/'): continue if not any(part in low for part in ('mipmap', 'drawable')): continue if not low.endswith(('.png', '.webp', '.jpg', '.jpeg')): continue base = Path(low).stem score = 0 if 'ic_launcher' in base: score += 1000 elif base in ('icon', 'app_icon', 'launcher_icon') or base.endswith('_icon'): score += 800 elif 'launcher' in base or 'icon' in base: score += 500 else: continue for d, pts in density_score.items(): if d in low: score += pts break try: score += min(z.getinfo(name).file_size // 1024, 200) except Exception: pass candidates.append((score, name)) if not candidates: return None, None candidates.sort(reverse=True) name = candidates[0][1] data = z.read(name) ctype = mimetypes.guess_type(name)[0] or ('image/webp' if name.lower().endswith('.webp') else 'image/png') return data, ctype except Exception: return None, None def item_for(folder: Path, file: Path, kind: str, display=None, desc=None, icon=None, info_folder=None): root = SOFTWARE_ROOT if kind == 'software' else ANDROID_ROOT rel = file.relative_to(root).as_posix() st = file.stat() display_name = display or folder.name.replace('_', ' ').replace('-', ' ') fallback_desc = desc or ('Vimpulse Windows software and utilities.' if kind == 'software' else 'Vimpulse Android application.') platform = 'Windows' if kind == 'software' else 'Android' info_dir = info_folder or folder info_text = ensure_info_file(info_dir, display_name, fallback_desc, platform) return { 'name': display_name, 'folder': folder.name, 'description': description_from_info(info_text, fallback_desc), 'cost': value_from_info(info_text, 'Cost', 'Free'), 'purchase_url': value_from_info(info_text, 'PurchaseURL', ''), 'about': about_from_info(info_text), 'categories': field_values_from_info(info_text, 'Category') or ['Other'], 'category': ', '.join(field_values_from_info(info_text, 'Category') or ['Other']), 'tags': field_values_from_info(info_text, 'Tags'), 'channel': value_from_info(info_text, 'Channel', 'Stable'), 'status': value_from_info(info_text, 'Status', 'Available'), 'featured': value_from_info(info_text, 'Featured', 'No').lower() in ('yes', 'true', '1'), 'requirements': requirements_from_info(info_text, kind), 'screenshots': screenshots_for(info_dir, kind), 'changelog': read_optional_text(info_dir, ('changelog.txt', 'CHANGELOG.txt', 'Changelog.txt')), 'previous_releases': previous_releases(info_dir, file, kind, SOFTWARE_EXTS if kind == 'software' else ANDROID_EXTS), 'sha256': sha256_file(file), 'downloads': download_count_for(kind, file), 'info': info_text, 'icon': icon or ('◆' if kind == 'software' else '⬢'), 'icon_url': icon_url_for(file, kind, info_dir), 'version': version_from_name(file), 'download': f'/{kind}/{urllib.parse.quote(rel, safe="/")}', 'share_url': f'/{"apps" if kind == "android" else "software"}/{urllib.parse.quote(folder.name, safe="")}', 'size': size_text(st.st_size), 'updated': datetime.fromtimestamp(st.st_mtime).strftime('%b %d, %Y'), 'updated_iso': datetime.fromtimestamp(st.st_mtime).isoformat(timespec='seconds'), 'filename': file.name, 'platform': platform } def build_catalog(kind): root = SOFTWARE_ROOT if kind == 'software' else ANDROID_ROOT signature = catalog_signature(root) if CATALOG_CACHE_SIGNATURE.get(kind) == signature and kind in CATALOG_CACHE: return CATALOG_CACHE[kind] exts = SOFTWARE_EXTS if kind == 'software' else ANDROID_EXTS pretty = PRETTY_SOFTWARE if kind == 'software' else PRETTY_ANDROID descs = DESC_SOFTWARE if kind == 'software' else DESC_ANDROID icons = ICON_SOFTWARE if kind == 'software' else ICON_ANDROID items = [] if not root.exists(): return items for folder in sorted((p for p in root.iterdir() if p.is_dir() and not p.name.startswith('_')), key=lambda p: p.name.lower()): key = folder.name.lower() ensure_icon_folder(folder) if kind == 'software' and key in ('vimpulsetechtoolkit', 'vimpulse tech toolkit'): for edition in ('Pro', 'Business'): ed = folder / edition if ed.exists(): ensure_icon_folder(ed) f = newest_file(ed, exts) if f: items.append(item_for(folder, f, kind, f'Vimpulse Tech Toolkit {edition}', 'Advanced tools for professionals.' if edition == 'Pro' else 'Business-ready technician tools.', '🔧' if edition == 'Pro' else '▣', info_folder=ed)) if any(i['folder'] == folder.name for i in items): continue f = newest_file(folder, exts) if f: items.append(item_for(folder, f, kind, pretty.get(key), descs.get(key), icons.get(key))) items.sort(key=lambda item: (not item.get('featured', False), item.get('name', '').lower())) CATALOG_CACHE[kind] = items CATALOG_CACHE_SIGNATURE[kind] = signature return items def build_plugin_catalog(): groups = [] if not PLUGINS_ROOT.exists(): return groups for folder in sorted((p for p in PLUGINS_ROOT.iterdir() if p.is_dir() and not p.name.startswith('_')), key=lambda p: p.name.lower()): files = [p for p in folder.rglob('*') if is_plugin_file(p)] info = ensure_info_file(folder, folder.name.replace('_', ' ').replace('-', ' '), 'A folder of Vimpulse plugins.', 'Plugins') groups.append({'name': value_from_info(info, 'Name', folder.name), 'folder': folder.name, 'description': description_from_info(info, 'A folder of Vimpulse plugins.'), 'plugins': [{'name': p.name, 'size': size_text(p.stat().st_size), 'download': '/plugin/' + urllib.parse.quote(folder.name, safe='') + '/' + urllib.parse.quote(p.relative_to(folder).as_posix(), safe='/')} for p in sorted(files, key=lambda p: p.name.lower())]}) return groups def analytics_summary(): now = datetime.now() today = now.date() week_start = today.fromordinal(today.toordinal() - today.weekday()) month_start = today.replace(day=1) summary = {'today': 0, 'week': 0, 'month': 0, 'all_time': 0, 'top': []} rows = [] for key, row in load_download_stats().items(): count = int(row.get('count', 0) or 0) summary['all_time'] += count stamp = str(row.get('last_download', '')) try: when = datetime.fromisoformat(stamp).date() except ValueError: when = None if when == today: summary['today'] += count if when and when >= week_start: summary['week'] += count if when and when >= month_start: summary['month'] += count rows.append({'key': key, 'filename': row.get('filename', ''), 'kind': row.get('kind', ''), 'count': count, 'last_download': stamp}) summary['top'] = sorted(rows, key=lambda row: (-row['count'], row['filename'].lower()))[:10] return summary def dashboard_data(): try: disk = os.statvfs(str(SOFTWARE_ROOT)) disk_total = disk.f_frsize * disk.f_blocks disk_free = disk.f_frsize * disk.f_bavail except (AttributeError, OSError): disk_total = disk_free = 0 return { 'uptime_seconds': max(0, int(time.time() - SERVER_STARTED)), 'cpu_threads': os.cpu_count() or 1, 'disk_total_bytes': disk_total, 'disk_free_bytes': disk_free, 'software_count': len(build_catalog('software')), 'android_count': len(build_catalog('android')), 'analytics': analytics_summary(), } class Handler(BaseHTTPRequestHandler): server_version = 'VimpulseHub/2.6' def log_message(self, fmt, *vals): print(f'[{self.log_date_time_string()}] {self.address_string()} - {fmt % vals}') def send_bytes(self, data: bytes, ctype='application/octet-stream', status=200, filename=None, extra_headers=None): self.send_response(status) self.send_header('Content-Type', ctype) self.send_header('Content-Length', str(len(data))) self.send_header('Cache-Control', 'no-store') if filename: safe = filename.replace('"', '') self.send_header('Content-Disposition', f'attachment; filename="{safe}"') if extra_headers: for key, value in extra_headers.items(): self.send_header(key, value) self.end_headers() if self.command != 'HEAD': self.wfile.write(data) def send_json(self, obj, status=200, extra_headers=None): self.send_bytes( json.dumps(obj, ensure_ascii=False).encode('utf-8'), 'application/json; charset=utf-8', status, extra_headers=extra_headers ) def serve_file(self, path: Path, download=False): if not path or not path.is_file(): self.send_error(404, 'File not found') return ctype = mimetypes.guess_type(path.name)[0] or 'application/octet-stream' try: stat = path.stat() self.send_response(200) self.send_header('Content-Type', ctype) self.send_header('Content-Length', str(stat.st_size)) self.send_header('Cache-Control', 'public, max-age=86400' if not download else 'private, max-age=3600') self.send_header('Last-Modified', datetime.fromtimestamp(stat.st_mtime).strftime('%a, %d %b %Y %H:%M:%S GMT')) if download: safe = path.name.replace('"', '') self.send_header('Content-Disposition', f'attachment; filename="{safe}"') self.end_headers() if self.command != 'HEAD': with path.open('rb') as handle: for chunk in iter(lambda: handle.read(1024 * 1024), b''): self.wfile.write(chunk) except (OSError, BrokenPipeError): return def serve_share_page(self, kind='home', item=None, share_mode=False): titles = { 'home': 'VimpulseHub AppStore', 'software': 'VimpulseHub Windows Software', 'android': 'VimpulseHub Android Apps', 'plugins': 'VimpulseHub Plugins', 'status': 'VimpulseHub Server Status', 'about': 'About VimpulseHub', } descriptions = { 'home': 'Download the latest Vimpulse Windows software and Android apps from one hub.', 'software': 'Browse and download the latest Vimpulse Windows software.', 'android': 'Browse and download the latest Vimpulse Android apps.', 'plugins': 'Browse plugin folders and download the plugins inside them.', 'status': 'Current availability and service status for VimpulseHub.', 'about': 'One front end with separate Windows software and Android app libraries.', } title = titles.get(kind, titles['home']) description = descriptions.get(kind, descriptions['home']) url_path = {'home': '/', 'software': '/software', 'android': '/apps', 'plugins': '/plugins', 'status': '/status', 'about': '/about'}.get(kind, '/') image_url = '' if item: title = f"{item['name']} | VimpulseHub" description = item['description'] url_path = f"/apps/{urllib.parse.quote(item['folder'], safe='')}" if kind == 'android' else f"/software/{urllib.parse.quote(item['folder'], safe='')}" screenshot_choices = item.get('screenshots') or [] if screenshot_choices: image_url = SEO_BASE_URL + secrets.choice(screenshot_choices) elif item.get('icon_url'): image_url = SEO_BASE_URL + item['icon_url'] esc = lambda value: html_lib.escape(str(value), quote=True) canonical_url = SEO_BASE_URL + url_path structured = None if item: structured = { '@context': 'https://schema.org', '@type': 'MobileApplication' if kind == 'android' else 'SoftwareApplication', 'name': item.get('name', ''), 'description': item.get('description', ''), 'applicationCategory': ', '.join(item.get('categories', [])), 'operatingSystem': item.get('platform', ''), 'softwareVersion': item.get('version', ''), 'dateModified': item.get('updated_iso', ''), 'url': canonical_url, 'downloadUrl': SEO_BASE_URL + item.get('download', ''), } if image_url: structured['image'] = image_url meta = [ f'', f'', f'', f'', f'', f'', f'', f'', f'', f'', f'', f'', f'', f'', f'', f'', f'', ] if item: meta.extend([ f'', f'', f'', f'', ]) if image_url: meta.append(f'') meta.append(f'') meta.append(f'') meta.append(f'') if structured: meta.append(f'') breadcrumb = { '@context': 'https://schema.org', '@type': 'BreadcrumbList', 'itemListElement': [ {'@type': 'ListItem', 'position': 1, 'name': 'VimpulseHub', 'item': SEO_BASE_URL + '/'}, {'@type': 'ListItem', 'position': 2, 'name': 'Android Apps' if kind == 'android' else 'Software', 'item': SEO_BASE_URL + ('/apps' if kind == 'android' else '/software')}, {'@type': 'ListItem', 'position': 3, 'name': item.get('name', ''), 'item': canonical_url}, ], } meta.append(f'') data = HTML_FILE.read_text(encoding='utf-8') data = data.replace('VimpulseHub AppStore', f'{esc(title)}\n' + '\n'.join(meta), 1) self.send_bytes(data.encode('utf-8'), 'text/html; charset=utf-8') def do_HEAD(self): self.do_GET() def do_POST(self): parsed = urllib.parse.urlparse(self.path) path = parsed.path try: length = int(self.headers.get('Content-Length', '0')) except ValueError: length = 0 def read_json(): try: raw = self.rfile.read(length) if length else b'{}' return json.loads(raw.decode('utf-8')) except Exception: return None # First-run setup is only available while there are no accounts. if path == '/admin/api/setup': users = load_users() if users: self.send_json({'success': False, 'message': 'Setup is already complete.'}, 403) return data = read_json() if data is None: self.send_json({'success': False, 'message': 'Invalid request.'}, 400) return username = str(data.get('username', '')).strip() password = str(data.get('password', '')) if not re.fullmatch(r'[A-Za-z0-9_.-]{3,40}', username): self.send_json({'success': False, 'message': 'Username must be 3-40 letters, numbers, dots, dashes, or underscores.'}, 400) return if len(password) < 8: self.send_json({'success': False, 'message': 'Password must be at least 8 characters.'}, 400) return users[username] = { 'password_hash': hash_password(password), 'role': 'admin', 'enabled': True, 'created': datetime.now().isoformat(timespec='seconds') } if not save_users(users): self.send_json({'success': False, 'message': 'Could not save account database.'}, 500) return sid = create_session(username) self.send_json( {'success': True, 'message': 'Administrator created.'}, extra_headers={'Set-Cookie': session_cookie(sid)} ) return if path == '/admin/api/login': data = read_json() if data is None: self.send_json({'success': False, 'message': 'Invalid request.'}, 400) return username = str(data.get('username', '')).strip() password = str(data.get('password', '')) users = load_users() account = users.get(username) if not account or not account.get('enabled', True) or not verify_password(password, account.get('password_hash', '')): time.sleep(0.25) self.send_json({'success': False, 'message': 'Invalid username or password.'}, 401) return sid = create_session(username) self.send_json( {'success': True, 'message': 'Signed in.'}, extra_headers={'Set-Cookie': session_cookie(sid)} ) return if path == '/admin/api/logout': destroy_session(self) self.send_json( {'success': True, 'message': 'Signed out.'}, extra_headers={'Set-Cookie': clear_session_cookie()} ) return user = session_user(self) if not user: self.send_json({'success': False, 'message': 'Please sign in.'}, 401) return if path == '/admin/api/upload': if not role_allowed(user, 'admin', 'editor'): self.send_json({'success': False, 'message': 'Permission denied.'}, 403) return query = urllib.parse.parse_qs(parsed.query) kind = query.get('kind', [''])[0] folder = admin_folder(kind, query.get('folder', [''])[0]) filename = Path(urllib.parse.unquote(self.headers.get('X-Filename', ''))).name asset = urllib.parse.parse_qs(parsed.query).get('asset', [''])[0].lower() if asset == 'screenshot': allowed = CUSTOM_ICON_EXTS valid_upload = kind in ('software', 'android') and bool(filename) and Path(filename).suffix.lower() in allowed else: allowed = SOFTWARE_EXTS if kind == 'software' else ANDROID_EXTS if kind == 'android' else None if kind == 'plugins' else set() plugin_upload = kind == 'plugins' and bool(filename) and filename.lower() != 'info.txt' and not filename.startswith('.') valid_upload = plugin_upload if kind == 'plugins' else bool(filename) and Path(filename).suffix.lower() in allowed if not folder or not folder.is_dir() or not valid_upload: self.send_json({'success': False, 'message': 'Invalid upload target or file type.'}, 400) return target_dir = folder / 'screenshots' if asset == 'screenshot' else folder if kind == 'plugins' else folder / 'updates' target_dir.mkdir(parents=True, exist_ok=True) target = target_dir / filename temp_path = None try: remaining = int(self.headers.get('Content-Length', '0')) with tempfile.NamedTemporaryFile(delete=False, dir=str(target_dir), prefix='.upload-', suffix='.tmp') as temp: temp_path = Path(temp.name) while remaining > 0: chunk = self.rfile.read(min(1024 * 1024, remaining)) if not chunk: break temp.write(chunk) remaining -= len(chunk) temp_path.replace(target) invalidate_catalog(kind) self.send_json({'success': True, 'message': f'{filename} uploaded.'}) except OSError: try: if temp_path: temp_path.unlink(missing_ok=True) except Exception: pass self.send_json({'success': False, 'message': 'Upload could not be saved.'}, 500) return if path == '/admin/api/change-password': data = read_json() if data is None: self.send_json({'success': False, 'message': 'Invalid request.'}, 400) return password = str(data.get('password', '')) if len(password) < 8: self.send_json({'success': False, 'message': 'Password must be at least 8 characters.'}, 400) return users = load_users() account = users.get(user['username']) if not account: self.send_json({'success': False, 'message': 'Account not found.'}, 404) return account['password_hash'] = hash_password(password) users[user['username']] = account save_users(users) self.send_json({'success': True, 'message': 'Password changed.'}) return if path in ('/admin/api/info', '/admin/api/create'): if not role_allowed(user, 'admin', 'editor'): self.send_json({'success': False, 'message': 'Permission denied.'}, 403) return data = read_json() if data is None: self.send_json({'success': False, 'message': 'Invalid request.'}, 400) return kind = data.get('kind', '') folder_name = str(data.get('folder', '')).strip() if path == '/admin/api/create': if kind not in ('software', 'android', 'plugins'): self.send_json({'success': False, 'message': 'Invalid library.'}, 400) return if not re.fullmatch(r'[A-Za-z0-9 _.-]{1,80}', folder_name): self.send_json({'success': False, 'message': 'Invalid folder name.'}, 400) return root = SOFTWARE_ROOT if kind == 'software' else ANDROID_ROOT if kind == 'android' else PLUGINS_ROOT folder = (root / folder_name).resolve() try: folder.relative_to(root) except ValueError: self.send_json({'success': False, 'message': 'Invalid folder.'}, 400) return folder.mkdir(parents=True, exist_ok=True) for sub in (() if kind == 'plugins' else ('updates', 'icon', 'screenshots')): (folder / sub).mkdir(exist_ok=True) platform = 'Plugins' if kind == 'plugins' else 'Windows' if kind == 'software' else 'Android' display = str(data.get('name', '')).strip() or folder_name desc = str(data.get('description', '')).strip() or 'Vimpulse application.' info = ( f'Name: {display}\n' f'Description: {desc}\n' f'Platform: {platform}\n' 'Cost: Free\n' f'Category: {"Plugins" if kind == "plugins" else "Other"}\n' 'Tags:\n' 'Channel: Stable\n' 'Status: Available\n' 'Featured: No\n' 'PurchaseURL:\n\n' 'About:\n' ) (folder / 'info.txt').write_text(info, encoding='utf-8') self.send_json({'success': True, 'message': 'Product created.'}) return folder = admin_folder(kind, folder_name) if not folder or not folder.is_dir(): self.send_json({'success': False, 'message': 'Product not found.'}, 404) return (folder / 'info.txt').write_text(str(data.get('info', '')), encoding='utf-8') self.send_json({'success': True, 'message': 'info.txt saved.'}) return # Account management: administrators only. if path.startswith('/admin/api/users/'): if not role_allowed(user, 'admin'): self.send_json({'success': False, 'message': 'Administrator permission required.'}, 403) return data = read_json() if data is None: self.send_json({'success': False, 'message': 'Invalid request.'}, 400) return users = load_users() if path == '/admin/api/users/create': username = str(data.get('username', '')).strip() password = str(data.get('password', '')) role = str(data.get('role', 'editor')).strip().lower() if not re.fullmatch(r'[A-Za-z0-9_.-]{3,40}', username): self.send_json({'success': False, 'message': 'Invalid username.'}, 400) return if username in users: self.send_json({'success': False, 'message': 'That username already exists.'}, 409) return if len(password) < 8: self.send_json({'success': False, 'message': 'Password must be at least 8 characters.'}, 400) return if role not in ('admin', 'editor'): role = 'editor' users[username] = { 'password_hash': hash_password(password), 'role': role, 'enabled': True, 'created': datetime.now().isoformat(timespec='seconds') } save_users(users) self.send_json({'success': True, 'message': f'Account {username} created.'}) return target = str(data.get('username', '')).strip() account = users.get(target) if not account: self.send_json({'success': False, 'message': 'Account not found.'}, 404) return if path == '/admin/api/users/reset-password': password = str(data.get('password', '')) if len(password) < 8: self.send_json({'success': False, 'message': 'Password must be at least 8 characters.'}, 400) return account['password_hash'] = hash_password(password) users[target] = account save_users(users) self.send_json({'success': True, 'message': f'Password reset for {target}.'}) return if target == user['username']: self.send_json({'success': False, 'message': 'You cannot disable or delete the account you are currently using.'}, 400) return if path == '/admin/api/users/toggle': enabled = bool(data.get('enabled', True)) if account.get('role') == 'admin' and not enabled: enabled_admins = sum( 1 for u in users.values() if u.get('role') == 'admin' and u.get('enabled', True) ) if enabled_admins <= 1: self.send_json({'success': False, 'message': 'You cannot disable the last enabled administrator.'}, 400) return account['enabled'] = enabled users[target] = account save_users(users) self.send_json({'success': True, 'message': f'Account {target} updated.'}) return if path == '/admin/api/users/delete': if account.get('role') == 'admin': admins = sum(1 for u in users.values() if u.get('role') == 'admin') if admins <= 1: self.send_json({'success': False, 'message': 'You cannot delete the last administrator.'}, 400) return users.pop(target, None) save_users(users) for sid, sess in list(ADMIN_SESSIONS.items()): if sess.get('username') == target: ADMIN_SESSIONS.pop(sid, None) self.send_json({'success': True, 'message': f'Account {target} deleted.'}) return self.send_json({'success': False, 'message': 'Endpoint not found.'}, 404) def do_GET(self): parsed = urllib.parse.urlparse(self.path) path = parsed.path if path in ('/admin', '/admin/'): users = load_users() if not users: self.send_bytes(setup_html().encode('utf-8'), 'text/html; charset=utf-8') return user = session_user(self) if not user: self.send_bytes(login_html().encode('utf-8'), 'text/html; charset=utf-8') return self.send_bytes(admin_html(user).encode('utf-8'), 'text/html; charset=utf-8') return if path == '/admin/api/me': user = session_user(self) if not user: self.send_json({'success': False, 'message': 'Please sign in.'}, 401) return self.send_json({'success': True, 'user': user}) return if path == '/admin/api/stats': user = session_user(self) if not role_allowed(user, 'admin', 'editor'): self.send_json({'success': False, 'message': 'Please sign in.'}, 401) return self.send_json({'success': True, 'stats': load_download_stats()}) return if path == '/admin/api/analytics': user = session_user(self) if not role_allowed(user, 'admin', 'editor'): self.send_json({'success': False, 'message': 'Please sign in.'}, 401) return self.send_json({'success': True, **analytics_summary()}) return if path == '/admin/api/dashboard': user = session_user(self) if not role_allowed(user, 'admin', 'editor'): self.send_json({'success': False, 'message': 'Please sign in.'}, 401) return self.send_json({'success': True, **dashboard_data()}) return if path == '/admin/api/info': user = session_user(self) if not role_allowed(user, 'admin', 'editor'): self.send_json({'success': False, 'message': 'Please sign in.'}, 401) return q = urllib.parse.parse_qs(parsed.query) kind = q.get('kind', [''])[0] folder = admin_folder(kind, q.get('folder', [''])[0]) if not folder or not folder.is_dir(): self.send_json({'success': False, 'message': 'Product not found.'}, 404) return p = folder / 'info.txt' self.send_json({ 'success': True, 'message': 'Loaded.', 'info': p.read_text(encoding='utf-8', errors='replace') if p.exists() else '' }) return if path == '/admin/api/users': user = session_user(self) if not role_allowed(user, 'admin'): self.send_json({'success': False, 'message': 'Administrator permission required.'}, 403) return users = load_users() result = [] for username, account in sorted(users.items(), key=lambda x: x[0].lower()): result.append({ 'username': username, 'role': account.get('role', 'editor'), 'enabled': account.get('enabled', True), 'created': account.get('created', '') }) self.send_json({'success': True, 'users': result}) return if path == '/robots.txt': body = ( 'User-agent: *\n' 'Allow: /\n' 'Disallow: /admin\n' 'Disallow: /catalog/\n' 'Disallow: /metadata/\n' f'Sitemap: {SEO_BASE_URL}/sitemap.xml\n' ) self.send_bytes(body.encode('utf-8'), 'text/plain; charset=utf-8') return if path == '/sitemap.xml': urls = [('/', None), ('/software', None), ('/apps', None), ('/status', None), ('/about', None)] for kind in ('software', 'android'): route = 'apps' if kind == 'android' else 'software' for row in build_catalog(kind): urls.append((f'/{route}/{urllib.parse.quote(row.get("folder", ""), safe="")}', row.get('updated_iso', ''))) entries = [] for route, modified in urls: lastmod = f'{html_lib.escape(modified)}' if modified else '' entries.append(f'{html_lib.escape(SEO_BASE_URL + route)}{lastmod}') xml = '' + '' + ''.join(entries) + '' self.send_bytes(xml.encode('utf-8'), 'application/xml; charset=utf-8') return # The single-page UI uses real, bookmarkable routes. Keep the # download paths (/software/ and /android/) below. if path in ('/', '/index.html', '/home'): self.serve_share_page('home') return if path in ('/software', '/software/'): self.serve_share_page('software') return if path in ('/apps', '/apps/', '/app', '/app/'): self.serve_share_page('android') return if path in ('/plugins', '/plugins/'): self.serve_share_page('plugins') return if path in ('/status', '/status/'): self.serve_share_page('status') return if path in ('/about', '/about/'): self.serve_share_page('about') return if path.startswith('/share/software/') or path.startswith('/share/apps/'): share_kind = 'software' if path.startswith('/share/software/') else 'android' prefix = '/share/software/' if share_kind == 'software' else '/share/apps/' folder_name = urllib.parse.unquote(path[len(prefix):]).strip('/') catalog_kind = share_kind item = next((a for a in build_catalog(catalog_kind) if a['folder'].casefold() == folder_name.casefold()), None) if item: self.serve_share_page(catalog_kind, item, share_mode=True) return self.send_error(404, 'Share page item not found') return if path.startswith('/app/') or path.startswith('/apps/'): folder_name = urllib.parse.unquote(path.split('/', 2)[2]).strip('/') item = next((a for a in build_catalog('android') if a['folder'].lower() == folder_name.lower()), None) if item: self.serve_share_page('android', item) return self.send_error(404, 'App not found') return if path.startswith('/software/'): folder_name = urllib.parse.unquote(path[len('/software/'):]).strip('/') item = next((a for a in build_catalog('software') if a['folder'].casefold() == folder_name.casefold()), None) if item: self.serve_share_page('software', item) return if path.startswith('/software/item/'): folder_name = urllib.parse.unquote(path[len('/software/item/'):]).strip('/') item = next((a for a in build_catalog('software') if a['folder'].lower() == folder_name.lower()), None) if item: self.serve_share_page('software', item) return self.send_error(404, 'Software item not found') return if path == '/catalog/software': self.send_json(build_catalog('software')) return if path == '/catalog/android': self.send_json(build_catalog('android')) return if path == '/catalog/plugins': self.send_json(build_plugin_catalog()) return if path.startswith('/metadata/'): requested = urllib.parse.unquote(path[len('/metadata/'):]).strip('/') for kind in ('software', 'android'): item = next((row for row in build_catalog(kind) if row.get('folder', '').casefold() == requested.casefold()), None) if item: self.send_json({key: item.get(key) for key in ('name', 'version', 'sha256', 'size', 'updated', 'download', 'filename', 'platform', 'channel', 'status')}) return self.send_error(404, 'Metadata not found') return if path == '/health': self.send_json({'success': True, 'service': 'VimpulseHub Download Server', 'status': 'online', 'port': args.port}) return if path == '/api-status': target_port = 8765 probes = [('99.232.137.208', 'External'), ('10.0.0.30', 'LAN')] for target_host, source in probes: started = time.perf_counter() try: with urllib.request.urlopen(f'http://{target_host}:{target_port}/health', timeout=3) as response: reachable = 200 <= response.status < 500 if reachable: self.send_json({'success': True, 'reachable': True, 'source': source, 'port': target_port, 'latency_ms': round((time.perf_counter() - started) * 1000)}) return except Exception: continue self.send_json({'success': False, 'reachable': False, 'source': None, 'port': target_port, 'latency_ms': None}) return if path.startswith('/screenshot/software/'): target = safe_inside(SOFTWARE_ROOT, path[len('/screenshot/software/'):]) if target and target.is_file() and target.suffix.lower() in CUSTOM_ICON_EXTS and 'screenshots' in [p.lower() for p in target.parts]: self.serve_file(target) return self.send_error(404, 'Screenshot not found') return if path.startswith('/screenshot/android/'): target = safe_inside(ANDROID_ROOT, path[len('/screenshot/android/'):]) if target and target.is_file() and target.suffix.lower() in CUSTOM_ICON_EXTS and 'screenshots' in [p.lower() for p in target.parts]: self.serve_file(target) return self.send_error(404, 'Screenshot not found') return if path.startswith('/custom-icon/software/'): target = safe_inside(SOFTWARE_ROOT, path[len('/custom-icon/software/'):]) if target and target.is_file() and target.suffix.lower() in CUSTOM_ICON_EXTS and target.parent.name.lower() == 'icon': self.serve_file(target) return self.send_error(404, 'Custom icon not found') return if path.startswith('/custom-icon/android/'): target = safe_inside(ANDROID_ROOT, path[len('/custom-icon/android/'):]) if target and target.is_file() and target.suffix.lower() in CUSTOM_ICON_EXTS and target.parent.name.lower() == 'icon': self.serve_file(target) return self.send_error(404, 'Custom icon not found') return if path.startswith('/asset-icon/software/'): target = safe_inside(SOFTWARE_ROOT, path[len('/asset-icon/software/'):]) if target and target.is_file() and target.suffix.lower() == '.exe': data = windows_exe_icon_png(target) if data: self.send_bytes(data, 'image/png') return self.send_error(404, 'Icon not found') return if path.startswith('/asset-icon/android/'): target = safe_inside(ANDROID_ROOT, path[len('/asset-icon/android/'):]) if target and target.is_file() and target.suffix.lower() == '.apk': data, ctype = android_apk_icon(target) if data: self.send_bytes(data, ctype or 'image/png') return self.send_error(404, 'Icon not found') return if path.startswith('/software/'): target = safe_inside(SOFTWARE_ROOT, path[len('/software/'):]) if target and target.is_file(): record_download('software',target) self.serve_file(target, download=True) return if path.startswith('/android/'): target = safe_inside(ANDROID_ROOT, path[len('/android/'):]) if target and target.is_file(): record_download('android',target) self.serve_file(target, download=True) return if path.startswith('/plugin/'): relative = path[len('/plugin/'):] parts = relative.split('/', 1) if len(parts) == 2: target = safe_inside(PLUGINS_ROOT, urllib.parse.unquote(parts[0]) + '/' + urllib.parse.unquote(parts[1])) if target and is_plugin_file(target): self.serve_file(target, download=True) return self.send_error(404, 'Plugin not found') return self.send_error(404, 'Not found') server = ThreadingHTTPServer((args.bind_host, args.port), Handler) print(f'VimpulseHub AppStore running at http://{args.public_host}:{args.port}/') print(f'Windows software: {SOFTWARE_ROOT}') print(f'Android apps: {ANDROID_ROOT}') print(f'ADMIN URL: {SITE_URL}/admin') try: server.serve_forever() except KeyboardInterrupt: pass finally: server.server_close() :__VIMPULSE_HTML__ VimpulseHub AppStore
VimpulseAPPSTORE & UPDATE SERVER

VimpulseHub
AppStore

Download the latest Vimpulse Windows software and Android apps from one server while keeping both libraries separate.

SOFTWARE + APPS.
ONE HUB.
▣
Windows Software
⬢
Android Apps
♢
Separate LibrariesNo folder mixing
⚙
Update API

Featured Products

New & Updated

All Vimpulse Software

.

Vimpulse Android Apps

Vimpulse Plugins

Plugin folders can contain multiple downloadable plugins.

Server Status

Current status for VimpulseHub.

Download Server OnlineThis page is being served from port 8766.
Update API (Port )Checking…Waiting for response
AppStore / Download ServerOnlinePort 8766
Windows SoftwareOnlineLoading
Android AppsOnlineLoading

About VimpulseHub

One front end, two separate download libraries.

A growing library of applications designed to make everyday tasks simpler, faster, and more efficient. Our apps are built with a focus on practical solutions, clean design, and reliability. Whether you're looking for productivity tools, utilities, calculators, or community-focused software, our goal is to create software that people actually enjoy using. Every application is developed with performance, ease of use, and continuous improvement in mind. As we grow, you'll find new apps, feature updates, and innovative projects added regularly.

What You'll Find Productivity tools, Utility applications, Smart calculators, Community-focused apps.

Why Choose Vimpulse Applications/Apps? Modern and intuitive interfaces, Fast and lightweight performance, Regular updates and new features, Some Cross-platform projects (Android, IOS, EXE, MacOS, Linux), Built by people who use the software they create We're committed to building software that solves real problems while remaining accessible to everyone. Whether you're trying one of our newest beta releases or using one of our established tools, we appreciate your support and feedback as we continue to expand the Vimpulse ecosystem. Thank you for being part of the Vimpulse INC community.