Skip to content

Add revert buttons to key properties to overwrite action defaults - #54

Merged
Core447 merged 5 commits into
mainfrom
revert-buttons
Apr 19, 2024
Merged

Core447 merged 5 commits into
mainfrom
revert-buttons

Conversation

@Core447

@Core447 Core447 commented Apr 19, 2024 •

Copy link
Copy Markdown
Member

Add revert buttons to key properties to overwrite action defaults.

Implementation of #49

@Core447 Core447 changed the title Revert buttons Add revert buttons to key properties to overrite action defaults Apr 19, 2024
@Core447 Core447 changed the title Add revert buttons to key properties to overrite action defaults Add revert buttons to key properties to overwrite action defaults Apr 19, 2024
@Core447
Core447 merged commit 1752528 into main Apr 19, 2024
@Core447
Core447 deleted the revert-buttons branch April 26, 2024 13:32
nazbert added a commit to nazbert/Deckard that referenced this pull request Jul 12, 2026
remove_old_cache_files had three startup hazards:

- a legacy index entry with no "date" popped the entry and then still
  fell into `time.time() - None` -- TypeError out of StoreCache.__init__,
  taking the whole store backend down at startup
- an entry whose cache file was already gone was skipped but never
  dropped, so it survived every future pass (immortal index entry)
- os.remove was unguarded against the exists()/remove() race and EPERM

Legacy dateless entries now fall back to the content clocks ("fetched",
then file mtime -- the same order get_fetched_date uses), missing-path
entries are purged from the index, and a failed remove is logged and
retried on the next pass instead of raising.

Red-checked by tests/scenario_store_cache_eviction.py (the dateless case
reproduced the startup TypeError verbatim before the fix).

Refs StreamController#54
nazbert added a commit to nazbert/Deckard that referenced this pull request Jul 12, 2026
…out of get_web_image

get_remote_file never forwarded data_type to the StoreCache calls, so a
binary fetch (data_type="content", e.g. every thumbnail) was indexed
under the same "text" cache string as a text fetch of that path -- one
cache file shared by both, opened "r" or "rb" depending on who asked
first. The key now carries the data type end to end (is_cached /
open_cache_file / get_fetched_date / the write), including the
failed-fetch stale fallback.

get_web_image also wrapped its fetch and decode in bare `except:`
clauses, which swallow asyncio.CancelledError -- a cancelled store load's
image tasks completed with "no image" instead of cancelling. Both are
now `except Exception` with the failure logged; cancellation propagates.

Red-checked by tests/scenario_store_remote_datatype.py.

Refs StreamController#54
nazbert added a commit to nazbert/Deckard that referenced this pull request Jul 12, 2026
…four copies

check_required_version existed as four near-identical copies (StorePage,
PluginPage, StorePreview, PluginPreview), each comparing with a strict
`min_version < app_version`: an asset requiring EXACTLY the running app
version was flagged incompatible (red border). Only StorePreview's copy
had any callers -- the StorePage/PluginPage variants (is_min_app_version
gated, returning None otherwise) and PluginPreview's shadow were dead
weight waiting to diverge.

There is now one implementation, StoreData.is_min_app_version_satisfied
(inclusive `<=`, fails open with a warning on an unparseable remote
version string instead of raising out of the preview build).
StorePreview delegates to it; the other three copies are deleted, with
PluginPreview inheriting StorePreview's method.

Red-checked by tests/scenario_store_version_gate.py (equality case
returned False before the fix).

Refs StreamController#54
nazbert added a commit to nazbert/Deckard that referenced this pull request Jul 12, 2026
Dead code that could only crash if ever reached:

- make_api_call read/wrote self.api_cache, which no __init__ ever
  created -- its sole caller was the unreachable tail of get_stargazers
  (behind an unconditional `return 0`). Both the method and the dead
  tail are gone; the deactivated get_stargazers stub stays.
- remove_old_manifest_cache / remove_old_attribution_cache iterated
  self.manifest_cache / self.attribution_cache, attributes that have
  never existed, and had zero callers.
- the unused async_lru (alru_cache) import, the now-orphaned datetime
  import, and clone_repo's commented make_api_call block.

Refs StreamController#54
nazbert added a commit to nazbert/Deckard that referenced this pull request Jul 12, 2026
uninstall_plugin iterated gl.page_manager.pages directly (transferred
from StreamController#56): the list() copies didn't make it safe -- load_page,
discard_controller and clear_old_cached_pages mutate that dict under
_pages_lock from other threads, so the unlocked iteration could race a
concurrent insert/evict mid-teardown.

PageManagerBackend grows all_cached_pages(), the all-controllers sibling
of pages_for_controller(): a snapshot of every cached Page taken under
_pages_lock, iterated lock-free by the caller (page teardown may invoke
plugin hooks, so the lock must not be held across it). uninstall_plugin
now uses it.

Refs StreamController#54
nazbert added a commit to nazbert/Deckard that referenced this pull request Jul 12, 2026
DynamicFlowBox.show_range set recycled children VISIBLE synchronously
(and it is also called from chooser build threads) while binding each
child's new asset via a separate GLib.idle_add. A click in that gap
activated the PREVIOUS page's asset -- or a fresh placeholder's None
asset, a TypeError in on_child_activated -- and a child selected on the
old page kept its GTK selection while already showing a different asset
(phantom selection across pages/filters).

The whole rebind now runs as ONE main-loop callback (_apply_range):
unselect_all, then bind-THEN-show per child, then hide leftovers and set
nav sensitivity. Input events are dispatched by the same main loop, so a
half-rebound pool is never clickable. get_items_to_show moved inside the
callback too -- filter/sort funcs read GTK state (search entry, toggles)
and now always do so on the main thread.

With the factory running on the main loop, the preview setters
(AssetPreview.set_asset and the wallpaper/SD+/icon pack equivalents)
bind text/image directly instead of deferring through idle_add, which
left a just-shown child displaying the previous item's name/thumbnail
for a frame.

Red-checked by tests/scenario_assetui_recycler.py (lands with the
build_finished fix in the next commit; its gap assertions reproduced the
visible-with-None-asset state verbatim against this code).

Refs StreamController#54
nazbert added a commit to nazbert/Deckard that referenced this pull request Jul 12, 2026
CustomAssetChooser.build() flipped build_finished=True BEFORE draining
build_task_finished_tasks, with no lock against show_for_path: a caller
that read the flag as False could append its deferred task AFTER the
(only) drain had already snapshotted the queue. The task sat in the list
forever -- the Asset Manager opened without preselecting the requested
path.

Flag flip + queue snapshot now happen atomically under _build_tasks_lock
(_finish_build); show_for_path checks the flag and appends under the same
lock, so a task either makes the snapshot or sees build_finished True and
dispatches directly -- never stranded in between. Tasks still run outside
the lock.

tests/scenario_assetui_recycler.py deterministically replays the loser
interleaving (append gated mid-flight while the build finishes) plus an
enqueue-storm stress, and carries the red-checked assertions for the
previous commit's recycler bind gap.

Refs StreamController#54
nazbert added a commit to nazbert/Deckard that referenced this pull request Jul 12, 2026
…e runtime loader

is_min_app_version_satisfied compared raw parsed versions, while the
runtime plugin loader (PluginBase.is_minimum_version_ok via
_get_parsed_base_version) strips to base_version. On a pre-release build
these diverged: running 1.5.0-beta.15, a store asset requiring the
release 1.5.0 displayed as incompatible (1.5.0b15 < 1.5.0) while the
loader would load it fine (base 1.5.0 == base 1.5.0) -- the badge lied
about what installing would actually do.

Strip both sides to base_version before the inclusive compare, matching
the runtime gate. Replicated locally (two-line base_version strip) rather
than importing PluginBase: StoreData is a leaf module that already defers
`import globals` to stay cycle-free, and pulling in the plugin-manager
import graph would reintroduce that risk.

scenario_store_version_gate gains an assertion that the store verdict
equals the runtime gate's across suffixed (pre/post) and plain-release
min versions; red-checked against the raw compare (diverged on min=1.5.0
under the 1.5.0-beta.15 build).

Refs StreamController#54
nazbert added a commit to nazbert/Deckard that referenced this pull request Jul 12, 2026
…ueue race

IconPackChooserStack carried the same unlocked flag-vs-queue race that was
just fixed for the custom-asset chooser (!30 / _finish_build), in its subtler
two-flag form: the pack chooser and the icon chooser each build on their own
worker thread, set their own build_finished, and call on_load_finished.
show_for_path checked get_is_build_finished() (both flags) then appended
unlocked -- a caller that read a flag as False could append its deferred task
after the draining thread snapshotted the queue, stranding it (the Asset
Manager opened without preselecting the requested icon). And because
on_load_finished runs from BOTH threads, the old copy()-and-remove() drain
could double-run or double-remove a task across a concurrent entry.

Apply the same pattern: flag-check + append and snapshot + clear are now
serialized under one lock (_loads_lock); tasks run OUTSIDE the lock (they
recurse into show_for_path, which takes the same lock).

Extends tests/scenario_assetui_recycler.py with the icon-pack race (Refs StreamController#54):
the raced-defer interleave (gate-based, no sleep) and a deterministic
two-thread concurrent-drain that forces the double-run. Both red-checked
against the pre-fix Stack.py; full harness 35/35.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant