vappman 2.2.0__tar.gz → 2.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vappman-2.2.0 → vappman-2.2.2}/PKG-INFO +22 -4
- {vappman-2.2.0 → vappman-2.2.2}/README.md +10 -0
- vappman-2.2.2/pyproject.toml +88 -0
- vappman-2.2.2/vappman/Appearance.py +85 -0
- {vappman-2.2.0 → vappman-2.2.2}/vappman/AppmanLauncher.py +0 -1
- {vappman-2.2.0 → vappman-2.2.2}/vappman/AppmanVars.py +2 -1
- {vappman-2.2.0 → vappman-2.2.2}/vappman/PersistentState.py +30 -12
- {vappman-2.2.0 → vappman-2.2.2}/vappman/Prerequisites.py +5 -7
- {vappman-2.2.0 → vappman-2.2.2}/vappman/VappmanListCache.py +5 -3
- vappman-2.2.2/vappman/__init__.py +3 -0
- {vappman-2.2.0 → vappman-2.2.2}/vappman/main.py +135 -11
- vappman-2.2.0/.github/workflows/tests.yml +0 -25
- vappman-2.2.0/.gitignore +0 -7
- vappman-2.2.0/images/screen-2024-06-23-v0.7.mkv +0 -0
- vappman-2.2.0/images/vappman-2025-12-28.12-48.gif +0 -0
- vappman-2.2.0/images/vappman-with-filter.png +0 -0
- vappman-2.2.0/pyproject.toml +0 -42
- vappman-2.2.0/runner +0 -18
- vappman-2.2.0/tests/conftest.py +0 -13
- vappman-2.2.0/tests/example_init_order.py +0 -124
- vappman-2.2.0/tests/test_abut.py +0 -132
- vappman-2.2.0/tests/test_init_order.py +0 -99
- vappman-2.2.0/tests/test_install_confirmation.py +0 -393
- vappman-2.2.0/tests/test_scope_subtraction.py +0 -104
- vappman-2.2.0/tests/test_scoped_keys.py +0 -204
- vappman-2.2.0/tests/test_scoped_keys_unit.py +0 -209
- vappman-2.2.0/tests/test_selection_anchor.py +0 -157
- vappman-2.2.0/vappman/__init__.py +0 -0
- {vappman-2.2.0 → vappman-2.2.2}/LICENSE +0 -0
- {vappman-2.2.0 → vappman-2.2.2}/vappman/AppimageDoctor.py +0 -0
- {vappman-2.2.0 → vappman-2.2.2}/vappman/CommandRunner.py +0 -0
|
@@ -1,25 +1,33 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: vappman
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: A visual wrapper for appman
|
|
5
5
|
Keywords: app,installer,manager,appimages
|
|
6
6
|
Author-email: Joe Defen <joedef@google.com>
|
|
7
7
|
Requires-Python: >=3.10
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
|
+
License-Expression: MIT
|
|
9
10
|
Classifier: Programming Language :: Python :: 3
|
|
10
11
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
16
16
|
Classifier: Operating System :: POSIX :: Linux
|
|
17
17
|
License-File: LICENSE
|
|
18
|
-
Requires-Dist: console-window == 2.
|
|
18
|
+
Requires-Dist: console-window == 2.2.2
|
|
19
|
+
Requires-Dist: pytest>=7 ; extra == "dev"
|
|
20
|
+
Requires-Dist: pytest-cov>=5 ; extra == "dev"
|
|
21
|
+
Requires-Dist: ruff>=0.6 ; extra == "dev"
|
|
22
|
+
Requires-Dist: build>=1.2 ; extra == "dev"
|
|
23
|
+
Requires-Dist: twine>=7 ; extra == "dev"
|
|
24
|
+
Requires-Dist: packaging>=26 ; extra == "dev"
|
|
19
25
|
Requires-Dist: pytest>=7.0 ; extra == "test"
|
|
20
26
|
Project-URL: Bug Tracker, https://github.com/joedefen/vappman/issues
|
|
21
27
|
Project-URL: Homepage, https://github.com/joedefen/vappman
|
|
28
|
+
Provides-Extra: dev
|
|
22
29
|
Provides-Extra: test
|
|
30
|
+
Import-Name: vappman
|
|
23
31
|
|
|
24
32
|
## vappman
|
|
25
33
|
`vappman` presents a "visual" (or TUI) interface to `appman/am`.
|
|
@@ -117,6 +125,15 @@ Run `vappman` from the command line. You will see a screen similar to this:
|
|
|
117
125
|
* `﹫` - Database indicator (in multi-database view)
|
|
118
126
|
* `>` - Currently selected app
|
|
119
127
|
|
|
128
|
+
Colour reinforces these symbols, never replaces them: installed rows are
|
|
129
|
+
tinted with the `HOTSWAP` role and the inline install/remove prompts with
|
|
130
|
+
`DANGER`. On a terminal without colour the roles fall back to bold/reverse
|
|
131
|
+
attributes, so every state remains readable.
|
|
132
|
+
|
|
133
|
+
Press `T` to open the theme picker; `t` there cycles through the available
|
|
134
|
+
themes and `mono` (no colour at all). The choice is remembered for the next
|
|
135
|
+
run. `--theme NAME` and `--no-color` override it for a single run.
|
|
136
|
+
|
|
120
137
|
#### Global Keys (Top Line)
|
|
121
138
|
|
|
122
139
|
* `m` - Switch mode if using `am`, toggling between SYSTEM and USER mode
|
|
@@ -132,6 +149,7 @@ Run `vappman` from the command line. You will see a screen similar to this:
|
|
|
132
149
|
* `_` - Toggle fancy header mode (Underline/Reverse/Off)
|
|
133
150
|
* `*` - Toggle demo mode
|
|
134
151
|
* `#` - Change number of backups to keep (2, 1, or -1 for infinite)
|
|
152
|
+
* `T` - Open the colour-theme picker (`t` cycles there; `mono` disables colour)
|
|
135
153
|
|
|
136
154
|
#### Navigation Keys
|
|
137
155
|
|
|
@@ -94,6 +94,15 @@ Run `vappman` from the command line. You will see a screen similar to this:
|
|
|
94
94
|
* `﹫` - Database indicator (in multi-database view)
|
|
95
95
|
* `>` - Currently selected app
|
|
96
96
|
|
|
97
|
+
Colour reinforces these symbols, never replaces them: installed rows are
|
|
98
|
+
tinted with the `HOTSWAP` role and the inline install/remove prompts with
|
|
99
|
+
`DANGER`. On a terminal without colour the roles fall back to bold/reverse
|
|
100
|
+
attributes, so every state remains readable.
|
|
101
|
+
|
|
102
|
+
Press `T` to open the theme picker; `t` there cycles through the available
|
|
103
|
+
themes and `mono` (no colour at all). The choice is remembered for the next
|
|
104
|
+
run. `--theme NAME` and `--no-color` override it for a single run.
|
|
105
|
+
|
|
97
106
|
#### Global Keys (Top Line)
|
|
98
107
|
|
|
99
108
|
* `m` - Switch mode if using `am`, toggling between SYSTEM and USER mode
|
|
@@ -109,6 +118,7 @@ Run `vappman` from the command line. You will see a screen similar to this:
|
|
|
109
118
|
* `_` - Toggle fancy header mode (Underline/Reverse/Off)
|
|
110
119
|
* `*` - Toggle demo mode
|
|
111
120
|
* `#` - Change number of backups to keep (2, 1, or -1 for infinite)
|
|
121
|
+
* `T` - Open the colour-theme picker (`t` cycles there; `mono` disables colour)
|
|
112
122
|
|
|
113
123
|
#### Navigation Keys
|
|
114
124
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
# 3.12 is the first flit_core that understands the PEP 639 license expression
|
|
3
|
+
# and `license-files` declared below; older backends reject them.
|
|
4
|
+
requires = ["flit_core>=3.12"]
|
|
5
|
+
build-backend = "flit_core.buildapi"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "vappman"
|
|
9
|
+
# `vappman/__init__.py:__version__` is the single source of truth. Flit reads
|
|
10
|
+
# it at build time, so `bless` can rewrite a throwaway X.Y.Z.99.N in a copy
|
|
11
|
+
# without touching the tree.
|
|
12
|
+
dynamic = ["version"]
|
|
13
|
+
description = "A visual wrapper for appman"
|
|
14
|
+
authors = [
|
|
15
|
+
{ name = "Joe Defen", email = "joedef@google.com" }
|
|
16
|
+
]
|
|
17
|
+
readme = "README.md"
|
|
18
|
+
# SPDX expression (PEP 639) rather than the deprecated table form: this is what
|
|
19
|
+
# puts `License-Expression: MIT` in the core metadata, which is also why the
|
|
20
|
+
# legacy `License :: ...` classifier below must not come back.
|
|
21
|
+
license = "MIT"
|
|
22
|
+
license-files = ["LICENSE"]
|
|
23
|
+
requires-python = ">=3.10"
|
|
24
|
+
keywords = ["app", "installer", "manager", "appimages"]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Programming Language :: Python :: 3.13",
|
|
32
|
+
"Operating System :: POSIX :: Linux"
|
|
33
|
+
]
|
|
34
|
+
dependencies = [
|
|
35
|
+
'console-window == 2.2.2',
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
"Homepage" = "https://github.com/joedefen/vappman"
|
|
40
|
+
"Bug Tracker" = "https://github.com/joedefen/vappman/issues"
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
vappman = "vappman.main:main"
|
|
44
|
+
|
|
45
|
+
[project.optional-dependencies]
|
|
46
|
+
test = ["pytest>=7.0"]
|
|
47
|
+
# The release gate (`./bless`) runs ruff, compileall and pytest with coverage,
|
|
48
|
+
# then builds, inspects, `twine check`s and smoke-installs the artifacts. Those
|
|
49
|
+
# tools must live in the interpreter that runs bless, and twine/packaging must
|
|
50
|
+
# be new enough to validate Metadata 2.5 from flit_core 3.12.
|
|
51
|
+
dev = [
|
|
52
|
+
"pytest>=7",
|
|
53
|
+
"pytest-cov>=5",
|
|
54
|
+
"ruff>=0.6",
|
|
55
|
+
"build>=1.2",
|
|
56
|
+
"twine>=7",
|
|
57
|
+
"packaging>=26",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[tool.pytest.ini_options]
|
|
61
|
+
testpaths = ["tests"]
|
|
62
|
+
addopts = "-ra"
|
|
63
|
+
|
|
64
|
+
[tool.coverage.run]
|
|
65
|
+
source = ["vappman"]
|
|
66
|
+
|
|
67
|
+
[tool.coverage.report]
|
|
68
|
+
# Set just below the measured number when the shared gate was adopted. Raise
|
|
69
|
+
# it as the suite grows; never lower it to make a commit pass.
|
|
70
|
+
fail_under = 35
|
|
71
|
+
|
|
72
|
+
[tool.bless]
|
|
73
|
+
# The default is the pair, written out here so the branch model is visible
|
|
74
|
+
# where the rest of this project's identity lives. `publish` aligns `dev`
|
|
75
|
+
# with `main` before tagging, and pushes both.
|
|
76
|
+
branches = ["main", "dev"]
|
|
77
|
+
|
|
78
|
+
[tool.ruff]
|
|
79
|
+
# Keep the lint gate identical on every machine: no global/user config can
|
|
80
|
+
# change which rules the gate enforces. The selection below is deliberately
|
|
81
|
+
# the conservative, version-stable core; a stricter ratchet can be adopted
|
|
82
|
+
# later without changing bless.
|
|
83
|
+
target-version = "py310"
|
|
84
|
+
line-length = 100
|
|
85
|
+
extend-exclude = ["build", "dist", ".venv"]
|
|
86
|
+
|
|
87
|
+
[tool.ruff.lint]
|
|
88
|
+
select = ["E4", "E7", "E9", "F"]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""The appearance preferences for vappman.
|
|
4
|
+
|
|
5
|
+
Colour is decoration, never information. Every role degrades to an attribute
|
|
6
|
+
(or to plain text) when the terminal has no colour, so the installed markers
|
|
7
|
+
(``✔``/``🔒``), the ``S``/``U`` flags and the ``⚠️`` prompts always remain
|
|
8
|
+
readable on a monochrome terminal.
|
|
9
|
+
|
|
10
|
+
The role ids are ``console_window.Theme``'s semantic pairs rather than literal
|
|
11
|
+
colour numbers. console-window installs those pairs at startup and re-installs
|
|
12
|
+
them whenever a theme is applied, so changing the active theme recolours the
|
|
13
|
+
whole UI without an edit to any draw code.
|
|
14
|
+
|
|
15
|
+
The role-to-attribute mapping, the process-wide switch and the forgiving lookup
|
|
16
|
+
live in :mod:`console_window.appearance`, and are re-exported here. Keeping one
|
|
17
|
+
switch matters: the library's theme picker paints its swatches through that
|
|
18
|
+
layer, so a vappman-local copy would leave the picker in colour while the rest
|
|
19
|
+
of the UI went mono.
|
|
20
|
+
|
|
21
|
+
``mono`` is vappman's own no-colour pseudo-theme: console-window's
|
|
22
|
+
``dark-mono``/``light-mono`` are still colour themes, so the true monochrome
|
|
23
|
+
appearance is "colour off" rather than a library theme. It is part of the
|
|
24
|
+
picker's rotation but is not a library theme.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import os
|
|
28
|
+
|
|
29
|
+
from console_window import Theme
|
|
30
|
+
from console_window.appearance import (
|
|
31
|
+
MONO_ROLES, colors_enabled, semantic_attr, set_colors_enabled,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Explicit re-exports: the appearance API is read from this module by vappman.
|
|
35
|
+
__all__ = [
|
|
36
|
+
'MONO_ROLES', 'MONO_THEME', 'appearance_choices', 'appearance_label',
|
|
37
|
+
'colors_enabled', 'resolve_ui_prefs', 'semantic_attr',
|
|
38
|
+
'set_colors_enabled',
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
# vappman's pseudo-theme: no colour at all, only attributes.
|
|
42
|
+
MONO_THEME = 'mono'
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def appearance_choices():
|
|
46
|
+
"""Every appearance the picker cycles, in order.
|
|
47
|
+
|
|
48
|
+
console-window's themes come first (in ``Theme.THEMES`` order), then
|
|
49
|
+
vappman's no-colour pseudo-theme, so one cycle always reaches ``mono``.
|
|
50
|
+
"""
|
|
51
|
+
return list(Theme.list_all()) + [MONO_THEME]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def appearance_label(theme, colors):
|
|
55
|
+
"""The name to show for the live appearance."""
|
|
56
|
+
if not colors:
|
|
57
|
+
return MONO_THEME
|
|
58
|
+
return theme or 'default'
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def resolve_ui_prefs(cli_theme=None, no_color=False, saved_theme='',
|
|
62
|
+
saved_colors=True, environ=None):
|
|
63
|
+
"""Starting ``(theme, colors)`` from the flag, the environment and the prefs.
|
|
64
|
+
|
|
65
|
+
Precedence, highest first: an explicit ``--theme`` / ``--no-color``, the
|
|
66
|
+
``NO_COLOR`` convention (any non-empty value disables colour), then the
|
|
67
|
+
remembered choice. An explicit ``--theme`` is a statement of intent, so it
|
|
68
|
+
re-enables colour even when the saved choice was ``mono`` (unless
|
|
69
|
+
``--no-color`` says otherwise); an unknown theme name falls back to
|
|
70
|
+
console-window's ``default``.
|
|
71
|
+
|
|
72
|
+
``theme`` is ``''`` for the library default. The special value ``mono``
|
|
73
|
+
forces ``colors`` off and is reported as ``theme=''``: there is no library
|
|
74
|
+
theme to install, because mono is the attribute mapping in this module.
|
|
75
|
+
"""
|
|
76
|
+
env = os.environ if environ is None else environ
|
|
77
|
+
if cli_theme:
|
|
78
|
+
if cli_theme == MONO_THEME:
|
|
79
|
+
return '', False
|
|
80
|
+
return Theme.resolve(cli_theme), not no_color
|
|
81
|
+
theme = '' if saved_theme in (None, MONO_THEME) else Theme.resolve(saved_theme)
|
|
82
|
+
colors = bool(saved_colors) and saved_theme != MONO_THEME
|
|
83
|
+
if env.get('NO_COLOR') or no_color:
|
|
84
|
+
colors = False
|
|
85
|
+
return theme, colors
|
|
@@ -76,7 +76,8 @@ class AppmanVars:
|
|
|
76
76
|
"""Locates the user's appman directory via config file."""
|
|
77
77
|
try:
|
|
78
78
|
config_file = self._config_base / "appman" / "appman-config"
|
|
79
|
-
if not config_file.exists():
|
|
79
|
+
if not config_file.exists():
|
|
80
|
+
return None
|
|
80
81
|
raw_path = config_file.read_text(encoding='utf-8').strip()
|
|
81
82
|
path_obj = Path(raw_path)
|
|
82
83
|
if not path_obj.is_absolute():
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
import json
|
|
3
|
-
import os
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
|
|
6
5
|
class PersistentState:
|
|
@@ -20,7 +19,18 @@ class PersistentState:
|
|
|
20
19
|
self.load()
|
|
21
20
|
|
|
22
21
|
def load(self):
|
|
23
|
-
"""Loads state from JSON,
|
|
22
|
+
"""Loads state from JSON, merging it over the defaults.
|
|
23
|
+
|
|
24
|
+
A file written by an older version is missing keys the software has
|
|
25
|
+
since added, and one written by a newer version may carry keys this
|
|
26
|
+
version does not know. Both are normal, not corruption: known keys are
|
|
27
|
+
honoured after a type check, unknown keys are dropped, and missing keys
|
|
28
|
+
keep their defaults. The merged result is written back, so the next run
|
|
29
|
+
sees a complete file (a one-time, in-place migration).
|
|
30
|
+
|
|
31
|
+
Only unreadable JSON, a document that is not an object, or a type
|
|
32
|
+
mismatch for a known key still resets everything to the defaults.
|
|
33
|
+
"""
|
|
24
34
|
if not self._config_file.exists():
|
|
25
35
|
self.save()
|
|
26
36
|
return
|
|
@@ -29,24 +39,32 @@ class PersistentState:
|
|
|
29
39
|
with open(self._config_file, 'r', encoding='utf-8') as f:
|
|
30
40
|
data = json.load(f)
|
|
31
41
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if set(data.keys()) != set(self._defaults.keys()):
|
|
35
|
-
raise ValueError("Key sets do not match.")
|
|
42
|
+
if not isinstance(data, dict):
|
|
43
|
+
raise ValueError("State file is not an object.")
|
|
36
44
|
|
|
37
|
-
#
|
|
45
|
+
# Start from the defaults so keys an older file did not have keep
|
|
46
|
+
# their value; validate only the keys this version actually knows.
|
|
47
|
+
merged = dict(self._defaults)
|
|
38
48
|
for key, value in data.items():
|
|
39
|
-
if
|
|
49
|
+
if key not in self._defaults:
|
|
50
|
+
continue # a key from a newer version: drop it on save
|
|
51
|
+
if type(value) is not type(self._defaults[key]):
|
|
40
52
|
raise TypeError(f"Type mismatch for {key}.")
|
|
41
|
-
|
|
42
|
-
# If valid, update the object attributes
|
|
43
|
-
for key, value in data.items():
|
|
44
|
-
setattr(self, key, value)
|
|
53
|
+
merged[key] = value
|
|
45
54
|
|
|
46
55
|
except (json.JSONDecodeError, ValueError, TypeError) as e:
|
|
47
56
|
print(f"Invalid state file ({e}). Reverting to defaults.")
|
|
48
57
|
self._reset_to_defaults()
|
|
49
58
|
self.save()
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
for key, value in merged.items():
|
|
62
|
+
setattr(self, key, value)
|
|
63
|
+
|
|
64
|
+
# Persist the merge only when the file differed from it (old file
|
|
65
|
+
# missing keys, or a file with keys this version drops).
|
|
66
|
+
if merged != data:
|
|
67
|
+
self.save()
|
|
50
68
|
|
|
51
69
|
def _reset_to_defaults(self):
|
|
52
70
|
"""Internal helper to reset attributes to original defaults."""
|
|
@@ -10,10 +10,8 @@
|
|
|
10
10
|
# pylint: disable=line-too-long,protected-access,invalid-name
|
|
11
11
|
|
|
12
12
|
import sys
|
|
13
|
-
import re
|
|
14
13
|
import shutil
|
|
15
14
|
import subprocess
|
|
16
|
-
from types import SimpleNamespace
|
|
17
15
|
|
|
18
16
|
class Prerequisites:
|
|
19
17
|
""" Detect / install prereqs """
|
|
@@ -63,7 +61,7 @@ class Prerequisites:
|
|
|
63
61
|
pm_name, install_cmd_template = self.detect_package_manager()
|
|
64
62
|
|
|
65
63
|
if not pm_name:
|
|
66
|
-
print(
|
|
64
|
+
print('\nERROR: Cannot find supported package manager.')
|
|
67
65
|
print(f'Missing dependencies: {", ".join(sorted(missing))}')
|
|
68
66
|
print('Please install them manually using your system package manager.')
|
|
69
67
|
return False
|
|
@@ -74,7 +72,7 @@ class Prerequisites:
|
|
|
74
72
|
print(f'\n⚠️ Missing dependencies: {", ".join(packages)}')
|
|
75
73
|
print(f'\nDetected package manager: {pm_name}')
|
|
76
74
|
|
|
77
|
-
response = input(
|
|
75
|
+
response = input('\nInstall missing dependencies? [y/N]: ').strip().lower()
|
|
78
76
|
|
|
79
77
|
if response not in ('y', 'yes'):
|
|
80
78
|
print('Installation cancelled.')
|
|
@@ -96,7 +94,7 @@ class Prerequisites:
|
|
|
96
94
|
print(' - Check for problematic PPAs in /etc/apt/sources.list.d/')
|
|
97
95
|
print(' - Remove outdated PPAs that block apt operations')
|
|
98
96
|
return False
|
|
99
|
-
print(
|
|
97
|
+
print('\n✅ Dependencies installed successfully!')
|
|
100
98
|
return True
|
|
101
99
|
except Exception as exc:
|
|
102
100
|
print(f'\n❌ Installation failed: {exc}')
|
|
@@ -143,7 +141,7 @@ class Prerequisites:
|
|
|
143
141
|
if result.returncode != 0:
|
|
144
142
|
print(f'\n❌ AM installation failed with exit code {result.returncode}')
|
|
145
143
|
return False
|
|
146
|
-
print(
|
|
144
|
+
print('\n✅ AM/appman installed successfully!')
|
|
147
145
|
return True
|
|
148
146
|
except Exception as exc:
|
|
149
147
|
print(f'\n❌ AM installation failed: {exc}')
|
|
@@ -229,7 +227,7 @@ class Prerequisites:
|
|
|
229
227
|
print(f'\n⚠️ Optional dependencies missing: {", ".join(sorted(missing_optional))}')
|
|
230
228
|
print(' Some features may not work without these packages.')
|
|
231
229
|
|
|
232
|
-
response = input(
|
|
230
|
+
response = input('\nInstall optional dependencies? [y/N]: ').strip().lower()
|
|
233
231
|
if response in ('y', 'yes'):
|
|
234
232
|
if not self.install_dependencies(packages_needed, skip_prompt=True):
|
|
235
233
|
optional_install_failed = True
|
|
@@ -168,7 +168,8 @@ class AppCacheManager:
|
|
|
168
168
|
self.apps, self.apps_by_key, self.dbs = apps, apps_by_key, databases
|
|
169
169
|
|
|
170
170
|
def refresh_background(self):
|
|
171
|
-
if self.runner and self.runner.is_running():
|
|
171
|
+
if self.runner and self.runner.is_running():
|
|
172
|
+
return
|
|
172
173
|
os.makedirs(self.config_dir, exist_ok=True)
|
|
173
174
|
self.runner = CommandRunner([self.binary, "list", "--all"], self.list_file)
|
|
174
175
|
self.runner.start()
|
|
@@ -177,7 +178,8 @@ class AppCacheManager:
|
|
|
177
178
|
if not os.path.exists(self.list_file):
|
|
178
179
|
# print("[Cache] No file found. Fetching initial list...")
|
|
179
180
|
self.refresh_background()
|
|
180
|
-
while self.runner.is_running():
|
|
181
|
+
while self.runner.is_running():
|
|
182
|
+
time.sleep(0.1)
|
|
181
183
|
print('... got initial app list ... continuing up ...')
|
|
182
184
|
self._parse_now()
|
|
183
185
|
else:
|
|
@@ -210,7 +212,7 @@ if __name__ == "__main__":
|
|
|
210
212
|
dup_count = sum(1 for entries in apps.values() if len(entries) > 1)
|
|
211
213
|
total_entries = sum(len(entries) for entries in apps.values())
|
|
212
214
|
|
|
213
|
-
print(
|
|
215
|
+
print("Initial Load:")
|
|
214
216
|
print(f" - {len(apps)} unique app names")
|
|
215
217
|
print(f" - {total_entries} total entries")
|
|
216
218
|
print(f" - {dup_count} app names with duplicates")
|