diff --git a/scripts/installer/install.py b/scripts/installer/install.py index 3d2a183..99a444c 100644 --- a/scripts/installer/install.py +++ b/scripts/installer/install.py @@ -125,7 +125,7 @@ def record(rule, name=None): for key, value in rule.items() if key != 'source'} -def validate_bundle(bundle): +def validate_bundle(bundle, *, installed=False): if bundle.is_symlink() or not bundle.is_dir(): fail(f'Bundle must be a directory, not a symlink: {bundle}') @@ -162,7 +162,14 @@ def validate_bundle(bundle): seen.add(target) actual = {str(path.relative_to(bundle)) for path in bundle.rglob('*') if path.is_file()} - if not expected.keys() <= seen or actual != seen | {'manifest.json'}: + required = expected.keys() + if installed: + # Installed releases may predate the appearance-specific wallpapers. + # New bundles must include them; upgrades and rollback verify the older + # release's recorded files without requiring these later additions. + required = required - {'wallpapers/ferese-wallpaper-dark.jpg', 'wallpapers/ferese-wallpaper-light.png'} + + if not required <= seen or actual != seen | {'manifest.json'}: fail('Bundle is incomplete or contains unlisted files') return manifest @@ -277,7 +284,7 @@ class Installation: if not (directory / 'manifest.json').is_file(): fail(f'Release {release} predates verified bundles; select a bundled release for rollback') - return validate_bundle(directory) + return validate_bundle(directory, installed=True) def current_publication(self, current): if not current: diff --git a/scripts/tests/test_portal_install.py b/scripts/tests/test_portal_install.py index 1b6a528..4a51324 100644 --- a/scripts/tests/test_portal_install.py +++ b/scripts/tests/test_portal_install.py @@ -112,6 +112,36 @@ m.main() for name in ('ferese-session', 'ferese-session-shell', 'ferese-screenshot'): self.assertEqual((self.bundles[0] / name).stat().st_mode & 0o777, 0o755) + def without_appearance_wallpapers(self, bundle): + manifest_path = bundle / 'manifest.json' + manifest = json.loads(manifest_path.read_text()) + removed = {'wallpapers/ferese-wallpaper-dark.jpg', 'wallpapers/ferese-wallpaper-light.png'} + for target in removed: + (bundle / target).unlink() + + manifest['files'] = [item for item in manifest['files'] if item['target'] not in removed] + manifest_path.write_text(json.dumps(manifest)) + + def test_upgrade_and_rollback_accept_installed_release_before_appearance_wallpapers(self): + self.install() + legacy = self.base / 'releases/first' + self.without_appearance_wallpapers(legacy) + self.install(1) + self.run_command('rollback') + self.assertEqual(os.readlink(self.base / 'current'), 'releases/first') + self.assertEqual(installer.Installation(self.root).manifest('first')['release'], 'first') + (legacy / 'ferese').write_bytes(b'corrupt') + with self.assertRaisesRegex(ValueError, 'Checksum mismatch'): + installer.Installation(self.root).manifest('first') + + def test_new_bundle_requires_appearance_wallpapers(self): + incomplete = Path(self.temporary.name) / 'incomplete' + shutil.copytree(self.bundles[0], incomplete) + self.without_appearance_wallpapers(incomplete) + result = self.run_command('install', incomplete, success=False) + self.assertIn('Bundle is incomplete', result.stderr) + self.assertFalse((self.base / 'current').exists()) + def test_fresh_upgrade_and_rollback_restore_all_integration(self): self.install() first = [(self.root / path).read_bytes() for path in (PORTAL, UNIT, DESKTOP)] diff --git a/shell/ferese-settings/src/visuals.rs b/shell/ferese-settings/src/visuals.rs index 829486e..63c7bbc 100644 --- a/shell/ferese-settings/src/visuals.rs +++ b/shell/ferese-settings/src/visuals.rs @@ -34,6 +34,7 @@ pub fn brand_icon() -> svg_icon::Icon { .symbolic(false) .icon() .size(32) + .content_fit(cosmic::iced::ContentFit::Contain) } pub fn action_icon(path: &str, tint: Color) -> svg_icon::Icon {