mirror of
https://github.com/Gathin23/cleanyourtv.git
synced 2026-10-27 19:54:45 +00:00
Merge PR #1813: tools: restore.sh turns back on what check.py --fix or a second disable.sh run disabled
This commit is contained in:
commit
eb29abe950
7 files changed
+293
-47
No files matched your search
@@ -724,7 +724,9 @@ Before `tools/disable.sh` changes anything it writes down what the TV looked lik
|
||||
in the list whether it was enabled, already disabled or not installed, which component owned Home,
|
||||
and the three animation scales. That record lives in `~/.cleanyourtv/`, one file per TV and list, and
|
||||
a second run of the same list keeps the first record instead of saving the cleaned-up state over it.
|
||||
If you added packages to the list since, that run adds them to the record before disabling them.
|
||||
If you added packages to the list since, that run adds them to the record before disabling them, and
|
||||
a listed package that was not installed then but is installed and enabled now is recorded as enabled.
|
||||
`check.py --fix` does the same before it disables anything.
|
||||
|
||||
Keep the list you applied. The restore script replays the record, so a package you had disabled
|
||||
yourself stays disabled and Home goes back to the component that owned it:
|
||||
@@ -756,7 +758,9 @@ TV_HOST=192.168.1.50 tools/venv/bin/python -I tools/check.py examples/<your tv>/
|
||||
|
||||
It exits with `0` when the TV still matches the list, `1` when something drifted, and `2` when it
|
||||
could not check at all: the TV did not answer, nobody accepted the "Allow USB debugging?" prompt in
|
||||
time, the list path is not there, or the list disables Android's core packages. Only 1 is drift, so
|
||||
time, the list path is not there, the list disables Android's core packages, or, under `--fix`, the
|
||||
recovery record for this TV and list cannot be read or updated (nothing is changed on the TV then).
|
||||
Only 1 is drift, so
|
||||
a job that mails on 1 alone stays quiet while the check never ran; mail on anything but 0, or on 2 as
|
||||
well, and a weekly job on a home server or a Pi is enough to hear about it:
|
||||
|
||||
@@ -770,9 +774,11 @@ your shell.
|
||||
|
||||
`--fix` reapplies the list to what drifted: it switches Home to the launcher the list does not
|
||||
disable (pass a package as the second argument if you run a fork), then disables the listed packages
|
||||
that came back, and reads the TV back to confirm both. It writes no recovery record, because the
|
||||
record already holds the state from before the first run: `tools/restore.sh` still returns the TV to
|
||||
stock afterwards. Home is fixed first; if no launcher is left to take it, `--fix` changes nothing
|
||||
that came back, and reads the TV back to confirm both. It never writes a recovery record of its own,
|
||||
because only the first run's record holds the state from before cleanup. When there is one, `--fix`
|
||||
first adds to it what a second `tools/disable.sh` run would (names the list gained, and a package that
|
||||
was not installed then and is installed and enabled now), so `tools/restore.sh` still returns the TV
|
||||
to stock afterwards. Home is fixed first; if no launcher is left to take it, `--fix` changes nothing
|
||||
and says so.
|
||||
|
||||
It runs the same safety checks as `tools/disable.sh` before it disables anything, since an update is
|
||||
|
||||
+17
-10
@@ -179,16 +179,23 @@ Things that bit us or contributors, so they don't bite you. Commands use `tools/
|
||||
"not installed any more". Bring it back with
|
||||
`tools/tv.py 'cmd package install-existing --user 0 <package>'` (AOSP's `cmd package` has it from
|
||||
Android 8.0; the Android 6 set in [#1185](https://github.com/Gathin23/cleanyourtv/issues/1185) had no
|
||||
such command; no TV here has run it yet). It comes back enabled, and without its data unless the
|
||||
uninstall used `-k`. To keep it, comment its line out of your list first: otherwise `check.py` reports
|
||||
it as back on, and `--fix` or the next `disable.sh` run disables it again. If you don't want it, let
|
||||
the list disable it afterwards, which the TV's own settings can undo. The record still calls it
|
||||
absent, so `restore.sh` keeps leaving it alone: `pm enable --user 0 <package>` is its undo. Two cases
|
||||
this does not cover. An app with no copy on the system partition that only user 0 had is deleted with
|
||||
its APK: the command answers `Package <package> doesn't exist`, and only reinstalling the APK brings
|
||||
it back (the Toshiba's `com.hisense.business.ad`). And a `User 0:` line with
|
||||
`installed=true hidden=true` was hidden, not uninstalled. install-existing changes nothing there even
|
||||
though it prints `Package <package> installed for user: 0`, and the shell cannot unhide it:
|
||||
such command; no TV here has run it yet). It comes back without its data unless the uninstall used
|
||||
`-k`, and disabled if a list that names it has gone through `disable.sh` since it was removed: that
|
||||
run sent it `pm disable-user --user 0`, which AOSP applies to a package removed for user 0 too (the
|
||||
[Shield list](../examples/nvidia-shield-tv-pro-2019/debloat.txt) notes `disable.sh` reporting
|
||||
"disabled-user" for such packages), and install-existing marks it installed without changing that
|
||||
state. `pm list packages -d --user 0 <package>` names it if it came back disabled. To keep it, comment
|
||||
its line out of your list, then run `tools/tv.py 'pm enable --user 0 <package>'`: with the line still
|
||||
in, `check.py` reports it as back on, and `--fix` or the next `disable.sh` run disables it again. If
|
||||
you don't want it, keep the line: the list disables it, or keeps it disabled, which the TV's own
|
||||
settings can undo. `--fix` or a `disable.sh` run that finds it enabled first changes it from absent to
|
||||
enabled in the record, so `restore.sh` turns it back on too. Until then, or in a record an older
|
||||
version updated, it stays absent, and `restore.sh` leaves it alone: `pm enable --user 0 <package>` is
|
||||
its undo. Two cases this does not cover. An app with no copy on the system partition that only user 0
|
||||
had is deleted with its APK: the command answers `Package <package> doesn't exist`, and only
|
||||
reinstalling the APK brings it back (the Toshiba's `com.hisense.business.ad`). And a `User 0:` line
|
||||
with `installed=true hidden=true` was hidden, not uninstalled. install-existing changes nothing there
|
||||
even though it prints `Package <package> installed for user: 0`, and the shell cannot unhide it:
|
||||
`pm unhide` needs `MANAGE_USERS`, which the shell does not hold from Android 7.0. Unhide it from the
|
||||
device-owner app that hid it, or leave it, as the
|
||||
[Chromecast with Google TV 4K list](../examples/google-chromecast-gtv-4k/debloat.txt) does with
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
"""check.py --fix and a second disable.sh run keep Undo able to turn back on what they disable.
|
||||
|
||||
restore.sh puts back only what the recovery record holds. check.py --fix never touched the record, so
|
||||
a name the list gained after the last disable.sh run was disabled by --fix and then left off by
|
||||
restore.sh for good ("not in the record, leaving alone"). A package the record calls absent, because
|
||||
the TV did not have it when disable.sh first ran, was left off the same way once an update or the Play
|
||||
Store installed it and --fix or disable.sh disabled it: save() only ever added missing names. Both now
|
||||
go through recovery.extend_record(), which adds the names the record does not hold and records an
|
||||
absent package that is installed and enabled now as enabled. check.py only extends a record that
|
||||
exists: a record of the TV after cleanup would make Undo leave the whole list off.
|
||||
|
||||
The TV is the drift check's fake tools/tv.py (tests/test_drift_check.py), which applies what --fix and
|
||||
a batch send, and the real disable.sh, restore.sh, check.py and recovery.py run against it.
|
||||
|
||||
Run from the repository root: python3 -m unittest discover -s tests -v
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import test_drift_check as drift # noqa: E402 (imported as a module so its tests do not run twice)
|
||||
import test_recovery_record as record_test # noqa: E402 (the same)
|
||||
|
||||
ROOT = drift.ROOT
|
||||
STOCK = drift.STOCK
|
||||
WEATHER = "com.example.weather"
|
||||
|
||||
|
||||
class CheckFixRecordTest(unittest.TestCase):
|
||||
# The drift check's harness: a temporary repo with the fake tv.py, HOME, the list and the TV state.
|
||||
write_list = drift.DriftCheckTest.write_list
|
||||
tv = drift.DriftCheckTest.tv
|
||||
run_check = drift.DriftCheckTest.run_check
|
||||
sent = drift.DriftCheckTest.sent
|
||||
# The recovery record test's runner for disable.sh and restore.sh, on the same repo and state.
|
||||
run_script = record_test.RecoveryRecordTest.run_script
|
||||
|
||||
def setUp(self):
|
||||
# The record's old name carries the port, so a TV_PORT left in the environment (by the user,
|
||||
# or by an earlier test in the same run) must not reach the scripts.
|
||||
environ = mock.patch.dict(os.environ)
|
||||
environ.start()
|
||||
self.addCleanup(environ.stop)
|
||||
os.environ.pop("TV_PORT", None)
|
||||
drift.DriftCheckTest.setUp(self)
|
||||
for script in ("disable.sh", "restore.sh"):
|
||||
shutil.copy2(ROOT / "tools" / script, self.repo / "tools" / script)
|
||||
# The scripts call tools/venv/bin/python: run the real interpreter, which imports the fake
|
||||
# tv.py for the transport and the real recovery.py for the logic.
|
||||
client = self.repo / "tools" / "venv" / "bin" / "python"
|
||||
client.parent.mkdir(parents=True)
|
||||
client.write_text(f'#!/bin/sh\nexec "{sys.executable}" "$@"\n')
|
||||
client.chmod(0o755)
|
||||
self.write_list(["com.example.music", "com.example.store"])
|
||||
|
||||
def state(self):
|
||||
return json.loads(self.state_path.read_text())
|
||||
|
||||
def set_state(self, **changes):
|
||||
state = self.state()
|
||||
state.update(changes)
|
||||
self.state_path.write_text(json.dumps(state))
|
||||
|
||||
def record_file(self):
|
||||
files = list((self.home / ".cleanyourtv").glob("recovery-*.json"))
|
||||
self.assertEqual(len(files), 1, files)
|
||||
return files[0]
|
||||
|
||||
def record(self):
|
||||
return json.loads(self.record_file().read_text())["packages"]
|
||||
|
||||
def apply_list(self):
|
||||
result = self.run_script("disable.sh", str(self.list_path))
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
return result
|
||||
|
||||
def undo(self):
|
||||
self.set_state(commands=[])
|
||||
result = self.run_script("restore.sh", str(self.list_path))
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
return self.sent(), result.stderr
|
||||
|
||||
def test_a_name_the_list_gained_is_recorded_before_fix_disables_it(self):
|
||||
self.tv(installed=drift.BASE + [WEATHER], disabled=[])
|
||||
self.apply_list()
|
||||
# A git pull adds a line; the next --fix disables it.
|
||||
self.write_list(["com.example.music", "com.example.store", WEATHER])
|
||||
result = self.run_check("--fix", str(self.list_path))
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
self.assertIn(f"pm disable-user --user 0 {WEATHER}", self.sent())
|
||||
self.assertIn("added 1 names the list gained since then", result.stdout)
|
||||
self.assertEqual(self.record()[WEATHER], "enabled")
|
||||
|
||||
sent, err = self.undo()
|
||||
self.assertIn(f"pm enable {WEATHER}", sent)
|
||||
self.assertNotIn("not in the record", err)
|
||||
|
||||
def test_a_package_installed_after_the_record_is_recorded_before_fix_disables_it(self):
|
||||
self.write_list(["com.example.music", "com.example.store", "com.example.later"])
|
||||
self.tv(installed=drift.BASE, disabled=[STOCK])
|
||||
self.apply_list()
|
||||
self.assertEqual(self.record()["com.example.later"], "absent")
|
||||
# A TV refuses pm disable-user for a package it has no copy of, which this fake does not; then
|
||||
# an update installs it, enabled.
|
||||
state = self.state()
|
||||
self.set_state(installed=state["installed"] + ["com.example.later"],
|
||||
disabled=[name for name in state["disabled"] if name != "com.example.later"])
|
||||
result = self.run_check("--fix", str(self.list_path))
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
self.assertIn("pm disable-user --user 0 com.example.later", self.sent())
|
||||
self.assertEqual(self.record()["com.example.later"], "enabled")
|
||||
|
||||
sent, err = self.undo()
|
||||
self.assertIn("pm enable com.example.later", sent)
|
||||
self.assertNotIn("leaving com.example.later alone", err)
|
||||
|
||||
def test_disable_run_again_records_a_package_installed_since(self):
|
||||
self.write_list(["com.example.music", "com.example.store", "com.example.later"])
|
||||
self.tv(installed=drift.BASE, disabled=[STOCK])
|
||||
self.apply_list()
|
||||
state = self.state()
|
||||
self.set_state(installed=state["installed"] + ["com.example.later"],
|
||||
disabled=[name for name in state["disabled"] if name != "com.example.later"])
|
||||
self.apply_list()
|
||||
self.assertEqual(self.record()["com.example.later"], "enabled")
|
||||
sent, _ = self.undo()
|
||||
self.assertIn("pm enable com.example.later", sent)
|
||||
|
||||
def test_entries_from_before_cleanup_are_not_overwritten(self):
|
||||
self.write_list(["com.example.music", "com.example.store", "com.example.later"])
|
||||
self.tv(installed=drift.BASE, disabled=["com.example.store", STOCK])
|
||||
self.apply_list()
|
||||
first = self.record()
|
||||
self.assertEqual(first["com.example.store"], "disabled")
|
||||
# Installed since, but disabled: nobody can say by whom, so it stays absent, which restore
|
||||
# leaves alone exactly as it would leave a disabled one.
|
||||
state = self.state()
|
||||
self.set_state(installed=state["installed"] + ["com.example.later"],
|
||||
disabled=[name for name in state["disabled"] if name != "com.example.music"])
|
||||
result = self.run_check("--fix", str(self.list_path))
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
self.assertEqual(self.record(), first)
|
||||
|
||||
def test_a_report_without_fix_leaves_the_record_alone(self):
|
||||
self.tv(installed=drift.BASE + [WEATHER], disabled=[])
|
||||
self.apply_list()
|
||||
before = self.record_file().read_bytes()
|
||||
self.write_list(["com.example.music", "com.example.store", WEATHER])
|
||||
result = self.run_check(str(self.list_path))
|
||||
self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
|
||||
self.assertEqual(self.record_file().read_bytes(), before)
|
||||
|
||||
def test_a_record_fix_cannot_use_stops_it_before_the_tv(self):
|
||||
for case in ("damaged", "two names"):
|
||||
with self.subTest(case=case):
|
||||
self.setUp()
|
||||
self.tv(disabled=[])
|
||||
self.apply_list()
|
||||
path = self.record_file()
|
||||
if case == "damaged":
|
||||
path.write_text("{not json")
|
||||
else:
|
||||
# The name older versions gave a record on the network, next to the one it gets now.
|
||||
shutil.copy2(path, path.with_name(path.name.replace("192.168.1.50-",
|
||||
"192.168.1.50_5555-")))
|
||||
before = path.read_bytes()
|
||||
# An update turned one listed package back on.
|
||||
self.set_state(commands=[], disabled=["com.example.store"])
|
||||
result = self.run_check("--fix", str(self.list_path))
|
||||
self.assertEqual(result.returncode, 2, result.stdout + result.stderr)
|
||||
self.assertIn(str(path), result.stderr)
|
||||
self.assertEqual(self.sent(), [], "nothing reached the TV")
|
||||
self.assertEqual(path.read_bytes(), before)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,8 +1,8 @@
|
||||
"""tools/check.py against a simulated TV (#359).
|
||||
|
||||
The property worth testing is the one no TV shows on demand: what the drift report says after a
|
||||
firmware update, and that --fix puts back only what drifted, without touching the recovery record
|
||||
undo depends on. tools/tv.py is replaced by a fake that answers the queries from a JSON file, so the
|
||||
firmware update, and that --fix puts back only what drifted, without writing a recovery record of
|
||||
its own. tools/tv.py is replaced by a fake that answers the queries from a JSON file, so the
|
||||
real check.py logic runs and only the ADB transport is simulated.
|
||||
|
||||
Run from the repository root: python3 -m unittest discover -s tests -v
|
||||
|
||||
@@ -9,8 +9,8 @@ replaced by a stand-in that keeps what `tools/tv.py --batch` was given.
|
||||
examples/anker-nebula-mars-3-air: the header and README said `tools/check.py --fix` re-disables
|
||||
Assistant after a boot, but the list keeps com.google.android.katniss commented out and check.py reads
|
||||
only active lines, so --fix never touched it. They now say to uncomment it and its LED service, then
|
||||
run Apply again: check.py --fix never writes the recovery record, and restore.sh leaves alone every
|
||||
listed name the record does not hold, so only disable.sh can make Undo bring them back. The owner's
|
||||
run Apply again: disable.sh adds them to the recovery record first, and restore.sh leaves alone every
|
||||
listed name the record does not hold, so that is what makes Undo bring them back. The owner's
|
||||
choice stays: the shipped list still leaves Assistant on. That half reuses the drift check's harness and
|
||||
fake tools/tv.py from tests/test_drift_check.py, and runs the real disable.sh and restore.sh against
|
||||
it with run_script from tests/test_recovery_record.py.
|
||||
@@ -167,12 +167,11 @@ class AnkerAssistantTest(unittest.TestCase):
|
||||
self.assertEqual(self.sent(), [f"pm disable-user --user 0 {name}" for name in ASSISTANT])
|
||||
|
||||
def test_undo_brings_assistant_back_after_the_header_steps(self):
|
||||
# check.py --fix applies the list without touching the recovery record, and restore.sh leaves
|
||||
# alone every listed name the record does not hold, so a package only check.py disabled stays
|
||||
# off after Undo. disable.sh adds the names a list gained to the record before disabling them.
|
||||
# restore.sh leaves alone every listed name the recovery record does not hold, and disable.sh
|
||||
# adds the names a list gained to the record before disabling them.
|
||||
names = self.told_to_uncomment()
|
||||
told = self.paragraph.split("uncomment", 1)[1].split("check.py", 1)[0]
|
||||
self.assertIn("run Apply again", told, "only disable.sh records the uncommented lines for Undo")
|
||||
self.assertIn("run Apply again", told, "Apply records the uncommented lines for Undo")
|
||||
lines = header(ANKER / "debloat.txt")
|
||||
apply = next(line for line in lines if line.startswith("# Apply:"))
|
||||
undo = next(line for line in lines if line.startswith("# Undo:"))
|
||||
|
||||
+27
-8
@@ -8,11 +8,13 @@
|
||||
A firmware update re-enables listed packages, hands Home back to the stock launcher and adds new
|
||||
preloads, and nothing in this repo notices: nobody runs disable.sh again, and restore.sh would undo
|
||||
the cleanup instead of redoing it. This command compares the TV with the list and exits 1 when the
|
||||
TV drifted, so it fits a cron job; --fix applies the list again without touching the recovery
|
||||
record (the packages were already recorded before the first run, so undo still means undo).
|
||||
TV drifted, so it fits a cron job; --fix applies the list again. It never writes a recovery record of
|
||||
its own (a record of the TV after cleanup would make undo leave the whole list disabled), but when
|
||||
this TV and list have one, it first adds what disable.sh would: the names the list gained since then,
|
||||
and a package the record calls absent that is installed and enabled now. Undo then still means undo.
|
||||
|
||||
It reads the list, not the record: the record holds what the TV looked like *before* cleanup, which
|
||||
is what restore needs, while the target here is the list itself.
|
||||
It compares the TV with the list, not the record: the record holds what the TV looked like *before*
|
||||
cleanup, which is what restore needs, while the target here is the list itself.
|
||||
|
||||
--fix runs the same safety checks as disable.sh before it disables anything (the Wi-Fi network
|
||||
setupwraith owns, the packages under the HDMI/AV inputs), because a firmware update is exactly the
|
||||
@@ -24,7 +26,8 @@ Exit codes: 0 in sync (or fixed), 1 drift, an incomplete fix, or packages a safe
|
||||
--fix may be partial, and the next check says what is left), or the command line asked for something this
|
||||
script does not do, which includes naming a launcher the TV does not have. That last one matters:
|
||||
README.md tells an owner to run this from a weekly job and hear about it on exit 1, so a mistyped flag
|
||||
must not come back as drift, nor a mistyped launcher as "nothing drifted".
|
||||
must not come back as drift, nor a mistyped launcher as "nothing drifted". A recovery record --fix
|
||||
cannot read or update is 2 as well, with nothing changed on the TV.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
@@ -347,7 +350,8 @@ def main():
|
||||
return 2
|
||||
fixing = False
|
||||
try:
|
||||
installed, _, home, back_on, gone, home_drifted, expected = inspect(dev, listed, preferred)
|
||||
installed, disabled, home, back_on, gone, home_drifted, expected = inspect(dev, listed,
|
||||
preferred)
|
||||
if preferred and preferred not in installed:
|
||||
# The other half of the launcher check before the list was read: only the TV can say
|
||||
# whether the package is there.
|
||||
@@ -357,12 +361,27 @@ def main():
|
||||
return 2
|
||||
code = report(list_path, listed, home, back_on, gone, home_drifted, expected)
|
||||
if code and wanted:
|
||||
# restore.sh puts back only what the recovery record holds, so before anything is disabled,
|
||||
# a record this TV and list already have gets the entries a second disable.sh run would add
|
||||
# (recovery.extend_record). Without a record nothing is written: one taken now would hold
|
||||
# the TV after cleanup. A record that cannot be used stops the run before anything reaches
|
||||
# the TV, as it stops disable.sh.
|
||||
path = recovery.find_record(list_path, os.environ.get("TV_HOST", ""))
|
||||
if path.exists():
|
||||
try:
|
||||
recovery.extend_record(path, recovery.load_record(path), listed, installed, disabled)
|
||||
except OSError as e:
|
||||
sys.stderr.write(f"could not update the recovery record ({e}), so nothing was "
|
||||
f"changed on the TV: without it, restore.sh would leave what "
|
||||
f"--fix disables off.\n")
|
||||
return 2
|
||||
fixing = True
|
||||
code = fix(dev, listed, back_on, home_drifted, preferred)
|
||||
except SystemExit as e:
|
||||
# recovery.query_packages stops with its own message when the TV answers no package at all:
|
||||
# the TV could not be read, so exit 2, not the 1 Python gives that message. Under --fix the
|
||||
# exits keep their code, as above.
|
||||
# the TV could not be read, so exit 2, not the 1 Python gives that message. find_record and
|
||||
# load_record do the same for a recovery record --fix cannot use. Under --fix the exits keep
|
||||
# their code, as above.
|
||||
if fixing:
|
||||
raise
|
||||
sys.stderr.write(f"{e}\n")
|
||||
|
||||
+49
-16
@@ -11,8 +11,9 @@ before. Both scripts now go through this record: `disable.sh` writes it before t
|
||||
reaches the TV, and `restore.sh` replays it.
|
||||
|
||||
Records live in ~/.cleanyourtv/, one per TV and list. The first one wins: a retry after a
|
||||
half-applied list must not record the broken state as the original. A later run only adds the names
|
||||
the list gained since then, with their state before this list disabled them.
|
||||
half-applied list must not record the broken state as the original. A later run, and check.py --fix,
|
||||
only add the names the list gained since then, with their state before this list disabled them, and
|
||||
record a package that was absent then and is installed and enabled now as enabled.
|
||||
"""
|
||||
import hashlib
|
||||
import ipaddress
|
||||
@@ -195,7 +196,8 @@ def load_record(path):
|
||||
# would lose that: restore would then enable every listed package, and a new disable.sh run would
|
||||
# record the TV after cleanup as the original. So the chooser and that one entry are dropped here,
|
||||
# which reads the record the way save() writes it now. Any other bad name is still refused below,
|
||||
# and save() writes the record back this way only when the list has gained names.
|
||||
# and extend_record() (from save() and check.py --fix) writes the record back this way only when the
|
||||
# list has gained names or a package recorded absent has since arrived.
|
||||
home = record.get("home")
|
||||
if isinstance(home, str) and HOME_RE.fullmatch(home) and is_chooser(home):
|
||||
record["packages"].pop(home.split("/", 1)[0], None)
|
||||
@@ -308,18 +310,50 @@ def package_state(name, installed, disabled):
|
||||
else "enabled")
|
||||
|
||||
|
||||
def extend_record(path, record, names, installed, disabled):
|
||||
"""Bring an existing record up to date before the list is applied to the TV again.
|
||||
|
||||
save() runs this for disable.sh, and check.py --fix runs it before it disables anything, because
|
||||
restore puts back only what the record holds. The list may have been edited since the record was
|
||||
taken. Entries already in the record are never overwritten (a retry must not record the broken
|
||||
state as the original), but names it does not hold yet have not been touched by this list, so
|
||||
their state now is their state before cleanup. So is the state of a package recorded as absent
|
||||
that is installed and enabled now: an update or the Play Store put it there after the record was
|
||||
taken. Without them, restore would leave those packages disabled for good. One that is installed
|
||||
but disabled stays absent, as nobody can say what disabled it, and restore leaves both alone.
|
||||
The file is written only when something changed. Returns the names added and the ones that
|
||||
arrived.
|
||||
"""
|
||||
states = record["packages"]
|
||||
arrived = [name for name in names if states.get(name) == "absent"
|
||||
and package_state(name, installed, disabled) == "enabled"]
|
||||
added = [name for name in names if name not in states]
|
||||
for name in added:
|
||||
states[name] = package_state(name, installed, disabled)
|
||||
for name in arrived:
|
||||
states[name] = "enabled"
|
||||
if not added and not arrived:
|
||||
return added, arrived
|
||||
write_record(path, record)
|
||||
if added:
|
||||
print(f"added {len(added)} names the list gained since then to the recovery record: "
|
||||
f"{', '.join(added)}")
|
||||
if arrived:
|
||||
print(f"recorded {len(arrived)} as enabled, absent when the record was taken and installed "
|
||||
f"since: {', '.join(arrived)}")
|
||||
print("every other entry keeps the state from before the first run.")
|
||||
return added, arrived
|
||||
|
||||
|
||||
def save(list_path, host):
|
||||
path = find_record(list_path, host)
|
||||
if path.exists():
|
||||
record = load_record(path)
|
||||
packages = read_list(list_path)
|
||||
# The list may have been edited since the record was taken. Entries already in the record are
|
||||
# never overwritten (a retry must not record the broken state as the original), but names it
|
||||
# does not hold yet have not been touched by this list, so their state now is their state
|
||||
# before cleanup. Without them, restore would leave those packages disabled for good.
|
||||
added = [name for name in packages if name not in record["packages"]]
|
||||
print(f"keeping the recovery record from {record.get('created')}: {path}")
|
||||
if not added:
|
||||
# Only a name the record does not hold, or holds as absent, can change it (extend_record), and
|
||||
# only then is the TV asked.
|
||||
if all(record["packages"].get(name, "absent") != "absent" for name in packages):
|
||||
print("nothing was recorded again: it holds the state from before the first run.")
|
||||
return 0
|
||||
dev = connect()
|
||||
@@ -327,11 +361,9 @@ def save(list_path, host):
|
||||
installed, disabled = query_packages(dev)
|
||||
finally:
|
||||
dev.close()
|
||||
for name in added:
|
||||
record["packages"][name] = package_state(name, installed, disabled)
|
||||
write_record(path, record)
|
||||
print(f"added {len(added)} names the list gained since then, entries already recorded "
|
||||
f"are unchanged: {', '.join(added)}")
|
||||
added, arrived = extend_record(path, record, packages, installed, disabled)
|
||||
if not added and not arrived:
|
||||
print("nothing was recorded again: it holds the state from before the first run.")
|
||||
return 0
|
||||
|
||||
packages = read_list(list_path)
|
||||
@@ -444,8 +476,9 @@ def restore_commands(list_path, stock, host):
|
||||
commands.append(f"settings put global {key} {value}")
|
||||
else:
|
||||
sys.stderr.write(f" skipping {key}: the record holds {value!r}, not a number\n")
|
||||
# disable.sh adds names the list gained to the record before disabling them, so a name still
|
||||
# missing here was added after the last disable run and never disabled by it: leave it alone.
|
||||
# disable.sh and check.py --fix add names the list gained to the record before disabling them, so
|
||||
# a name still missing here was added after the last of those runs and never disabled by it:
|
||||
# leave it alone.
|
||||
edited = [name for name in packages if name not in states]
|
||||
if edited:
|
||||
sys.stderr.write(f"not in the record, leaving alone: {', '.join(edited)}\n")
|
||||
|
||||
Reference in new issue
Block a user