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