pendify 0.1.3__tar.gz → 0.1.5__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.
Files changed (28) hide show
  1. {pendify-0.1.3/src/pendify.egg-info → pendify-0.1.5}/PKG-INFO +9 -3
  2. {pendify-0.1.3 → pendify-0.1.5}/README.md +8 -2
  3. {pendify-0.1.3 → pendify-0.1.5}/pyproject.toml +1 -1
  4. {pendify-0.1.3 → pendify-0.1.5}/src/pendify/__main__.py +72 -8
  5. {pendify-0.1.3 → pendify-0.1.5}/src/pendify/config.py +18 -6
  6. {pendify-0.1.3 → pendify-0.1.5}/src/pendify/page.py +132 -25
  7. {pendify-0.1.3 → pendify-0.1.5}/src/pendify/qr.py +61 -10
  8. pendify-0.1.5/src/pendify/tray.py +316 -0
  9. pendify-0.1.5/src/pendify/update.py +252 -0
  10. {pendify-0.1.3 → pendify-0.1.5/src/pendify.egg-info}/PKG-INFO +9 -3
  11. {pendify-0.1.3 → pendify-0.1.5}/src/pendify.egg-info/SOURCES.txt +2 -0
  12. {pendify-0.1.3 → pendify-0.1.5}/LICENSE +0 -0
  13. {pendify-0.1.3 → pendify-0.1.5}/MANIFEST.in +0 -0
  14. {pendify-0.1.3 → pendify-0.1.5}/setup.cfg +0 -0
  15. {pendify-0.1.3 → pendify-0.1.5}/src/pendify/__init__.py +0 -0
  16. {pendify-0.1.3 → pendify-0.1.5}/src/pendify/alert.py +0 -0
  17. {pendify-0.1.3 → pendify-0.1.5}/src/pendify/autostart.py +0 -0
  18. {pendify-0.1.3 → pendify-0.1.5}/src/pendify/client.py +0 -0
  19. {pendify-0.1.3 → pendify-0.1.5}/src/pendify/codes.py +0 -0
  20. {pendify-0.1.3 → pendify-0.1.5}/src/pendify/icon.py +0 -0
  21. {pendify-0.1.3 → pendify-0.1.5}/src/pendify/pairing.py +0 -0
  22. {pendify-0.1.3 → pendify-0.1.5}/src/pendify/pendify.ico +0 -0
  23. {pendify-0.1.3 → pendify-0.1.5}/src/pendify/plate_almena.py +0 -0
  24. {pendify-0.1.3 → pendify-0.1.5}/src/pendify/runfile.py +0 -0
  25. {pendify-0.1.3 → pendify-0.1.5}/src/pendify/watcher.py +0 -0
  26. {pendify-0.1.3 → pendify-0.1.5}/src/pendify/worker.py +0 -0
  27. {pendify-0.1.3 → pendify-0.1.5}/src/pendify.egg-info/dependency_links.txt +0 -0
  28. {pendify-0.1.3 → pendify-0.1.5}/src/pendify.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pendify
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: Pairs this PC with a phone account by a QR on a loopback page and sends it two kinds of alerts.
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/ElkinDev/PendiFy
@@ -49,11 +49,13 @@ Si Windows responde que no encuentra `python`, usa `py` en su lugar: `py -m pip
49
49
 
50
50
  Así no se crean los accesos directos en el Escritorio ni en el menú Inicio, y el inicio con Windows queda en el interruptor de la página. Para actualizar, pulsa «Salir» en la página, ejecuta otra vez la misma línea de pip y vuelve a iniciarlo. Para quitarlo, apaga primero el interruptor «Iniciar con Windows» en la página, pulsa «Salir» y después ejecuta `python -m pip uninstall pendify`. La configuración en `%APPDATA%\pendify`, con el enlace con el teléfono, se queda; puedes borrar esa carpeta a mano.
51
51
 
52
+ El programa revisa PyPI cada seis horas y, cuando hay una versión más nueva, la instala en segundo plano. La página ofrece entonces «Reiniciar ahora» para empezar a usarla. Para apagarlo, inícialo con `--no-update` o escribe `"update": "off"` en `%APPDATA%\pendify\config.json`. Las actualizaciones automáticas se instalan para tu usuario (--user).
53
+
52
54
  ### Enlazar
53
55
 
54
56
  Al iniciarse, el navegador abre una página local con un código QR. Escanéalo con la cámara del teléfono: se abre la app Pendi y te pide confirmar el enlace. La página muestra cuando el enlace quedó hecho.
55
57
 
56
- El código QR y la clave se muestran durante un minuto al pulsar «Mostrar el código» y luego se ocultan de nuevo.
58
+ El código QR y la clave están ocultos; se muestran durante un minuto al pulsar «Mostrar el código», y la página dice para qué sirve el código.
57
59
 
58
60
  El botón de arriba a la derecha cambia entre el tema claro y el oscuro, y el programa guarda la elección para la próxima vez.
59
61
 
@@ -61,6 +63,7 @@ El botón de arriba a la derecha cambia entre el tema claro y el oscuro, y el pr
61
63
 
62
64
  - Iniciar: el acceso directo `PendiFy`, o `pythonw -m pendify`, que es lo que ejecuta el acceso directo; sin consola, si ya está abierto o no puede iniciarse te lo dice en una ventana. Para verlo en una consola: `python -m pendify`.
63
65
  - Detener: pulsa «Salir» en la página del programa; si no la tienes abierta, iniciarlo otra vez la abre. Como último recurso, cierra el proceso `pythonw.exe` en el Administrador de tareas.
66
+ - Icono junto al reloj: un clic en el icono de PendiFy junto al reloj abre un menú para abrir la página, pausar o reanudar los avisos y salir del programa, también con la página y el navegador cerrados.
64
67
  - Pausar: «Pausar avisos», en la tarjeta «Este PC» de la página, deja de leer el cliente del juego: no acepta partidas ni avisa a tu teléfono, y la página sigue abierta. «Reanudar avisos» vuelve a leerlo. La pausa no se guarda: cada vez que el programa se inicia, empieza activo.
65
68
  - Iniciar con Windows: el interruptor «Iniciar con Windows», en la misma tarjeta, está apagado hasta que lo enciendas. Encendido, el programa empieza solo al iniciar sesión en Windows, solo para tu usuario y sin abrir el navegador ni la página.
66
69
  - Actividad: la tarjeta «Actividad» de la página lista lo que el programa hizo desde que empezó: el cliente del juego encontrado y perdido, cada fase, la partida aceptada y el aviso enviado. Guarda las últimas 50 líneas, solo en memoria.
@@ -111,11 +114,13 @@ If Windows answers that `python` was not found, use `py` in its place: `py -m pi
111
114
 
112
115
  This way leaves no shortcut on the Desktop or in the Start menu, and the start with Windows is the switch on the page. To update, press «Quit» on the page, run the same pip line again and start it again. To remove it, first turn off the «Start with Windows» switch on the page, press «Quit», then run `python -m pip uninstall pendify`. The configuration in `%APPDATA%\pendify`, with the link to the phone, stays; you can delete that folder by hand.
113
116
 
117
+ The program checks PyPI every six hours and, when a newer version exists, installs it in the background. The page then offers «Restart now» to start using it. To turn this off, start it with `--no-update` or write `"update": "off"` in `%APPDATA%\pendify\config.json`. Automatic updates install for your user (--user).
118
+
114
119
  ### Link
115
120
 
116
121
  When it starts, the browser opens a local page with a QR code. Scan it with the phone's camera: the Pendi app opens and asks you to confirm the link. The page shows when the link is done.
117
122
 
118
- The QR code and the key show for a minute when «Show the code» is pressed, and then hide again.
123
+ The QR code and the key are hidden; they show for a minute when «Show the code» is pressed, and the page says what the code is for.
119
124
 
120
125
  The button at the top right switches between the light and the dark theme, and the program keeps the choice for the next time.
121
126
 
@@ -123,6 +128,7 @@ The button at the top right switches between the light and the dark theme, and t
123
128
 
124
129
  - Start: the `PendiFy` shortcut, or `pythonw -m pendify`, which is what the shortcut runs; with no console, it tells you in a window when it is already running or cannot start. To see it in a console: `python -m pendify`.
125
130
  - Stop: press «Quit» on the program's page; if it is not open, starting the program again opens it. As a last resort, end the `pythonw.exe` process in Task Manager.
131
+ - Icon by the clock: a click on the PendiFy icon by the clock opens a menu that opens the page, pauses or resumes the alerts and quits the program, also with the page and the browser closed.
126
132
  - Pause: «Pause alerts», on the page's «This PC» card, stops reading the game client: it accepts no match and sends no alert to your phone, and the page stays open. «Resume alerts» reads it again. The pause is not kept: every start of the program is active again.
127
133
  - Start with Windows: the «Start with Windows» switch, on the same card, is off until you turn it on. When it is on, the program starts by itself when you sign in to Windows, for your user only, without opening the browser or the page.
128
134
  - Activity: the page's «Activity» card lists what the program did since it started: the game client found and lost, each phase, the match accepted and the alert sent. It keeps the last 50 lines, in memory only.
@@ -33,11 +33,13 @@ Si Windows responde que no encuentra `python`, usa `py` en su lugar: `py -m pip
33
33
 
34
34
  Así no se crean los accesos directos en el Escritorio ni en el menú Inicio, y el inicio con Windows queda en el interruptor de la página. Para actualizar, pulsa «Salir» en la página, ejecuta otra vez la misma línea de pip y vuelve a iniciarlo. Para quitarlo, apaga primero el interruptor «Iniciar con Windows» en la página, pulsa «Salir» y después ejecuta `python -m pip uninstall pendify`. La configuración en `%APPDATA%\pendify`, con el enlace con el teléfono, se queda; puedes borrar esa carpeta a mano.
35
35
 
36
+ El programa revisa PyPI cada seis horas y, cuando hay una versión más nueva, la instala en segundo plano. La página ofrece entonces «Reiniciar ahora» para empezar a usarla. Para apagarlo, inícialo con `--no-update` o escribe `"update": "off"` en `%APPDATA%\pendify\config.json`. Las actualizaciones automáticas se instalan para tu usuario (--user).
37
+
36
38
  ### Enlazar
37
39
 
38
40
  Al iniciarse, el navegador abre una página local con un código QR. Escanéalo con la cámara del teléfono: se abre la app Pendi y te pide confirmar el enlace. La página muestra cuando el enlace quedó hecho.
39
41
 
40
- El código QR y la clave se muestran durante un minuto al pulsar «Mostrar el código» y luego se ocultan de nuevo.
42
+ El código QR y la clave están ocultos; se muestran durante un minuto al pulsar «Mostrar el código», y la página dice para qué sirve el código.
41
43
 
42
44
  El botón de arriba a la derecha cambia entre el tema claro y el oscuro, y el programa guarda la elección para la próxima vez.
43
45
 
@@ -45,6 +47,7 @@ El botón de arriba a la derecha cambia entre el tema claro y el oscuro, y el pr
45
47
 
46
48
  - Iniciar: el acceso directo `PendiFy`, o `pythonw -m pendify`, que es lo que ejecuta el acceso directo; sin consola, si ya está abierto o no puede iniciarse te lo dice en una ventana. Para verlo en una consola: `python -m pendify`.
47
49
  - Detener: pulsa «Salir» en la página del programa; si no la tienes abierta, iniciarlo otra vez la abre. Como último recurso, cierra el proceso `pythonw.exe` en el Administrador de tareas.
50
+ - Icono junto al reloj: un clic en el icono de PendiFy junto al reloj abre un menú para abrir la página, pausar o reanudar los avisos y salir del programa, también con la página y el navegador cerrados.
48
51
  - Pausar: «Pausar avisos», en la tarjeta «Este PC» de la página, deja de leer el cliente del juego: no acepta partidas ni avisa a tu teléfono, y la página sigue abierta. «Reanudar avisos» vuelve a leerlo. La pausa no se guarda: cada vez que el programa se inicia, empieza activo.
49
52
  - Iniciar con Windows: el interruptor «Iniciar con Windows», en la misma tarjeta, está apagado hasta que lo enciendas. Encendido, el programa empieza solo al iniciar sesión en Windows, solo para tu usuario y sin abrir el navegador ni la página.
50
53
  - Actividad: la tarjeta «Actividad» de la página lista lo que el programa hizo desde que empezó: el cliente del juego encontrado y perdido, cada fase, la partida aceptada y el aviso enviado. Guarda las últimas 50 líneas, solo en memoria.
@@ -95,11 +98,13 @@ If Windows answers that `python` was not found, use `py` in its place: `py -m pi
95
98
 
96
99
  This way leaves no shortcut on the Desktop or in the Start menu, and the start with Windows is the switch on the page. To update, press «Quit» on the page, run the same pip line again and start it again. To remove it, first turn off the «Start with Windows» switch on the page, press «Quit», then run `python -m pip uninstall pendify`. The configuration in `%APPDATA%\pendify`, with the link to the phone, stays; you can delete that folder by hand.
97
100
 
101
+ The program checks PyPI every six hours and, when a newer version exists, installs it in the background. The page then offers «Restart now» to start using it. To turn this off, start it with `--no-update` or write `"update": "off"` in `%APPDATA%\pendify\config.json`. Automatic updates install for your user (--user).
102
+
98
103
  ### Link
99
104
 
100
105
  When it starts, the browser opens a local page with a QR code. Scan it with the phone's camera: the Pendi app opens and asks you to confirm the link. The page shows when the link is done.
101
106
 
102
- The QR code and the key show for a minute when «Show the code» is pressed, and then hide again.
107
+ The QR code and the key are hidden; they show for a minute when «Show the code» is pressed, and the page says what the code is for.
103
108
 
104
109
  The button at the top right switches between the light and the dark theme, and the program keeps the choice for the next time.
105
110
 
@@ -107,6 +112,7 @@ The button at the top right switches between the light and the dark theme, and t
107
112
 
108
113
  - Start: the `PendiFy` shortcut, or `pythonw -m pendify`, which is what the shortcut runs; with no console, it tells you in a window when it is already running or cannot start. To see it in a console: `python -m pendify`.
109
114
  - Stop: press «Quit» on the program's page; if it is not open, starting the program again opens it. As a last resort, end the `pythonw.exe` process in Task Manager.
115
+ - Icon by the clock: a click on the PendiFy icon by the clock opens a menu that opens the page, pauses or resumes the alerts and quits the program, also with the page and the browser closed.
110
116
  - Pause: «Pause alerts», on the page's «This PC» card, stops reading the game client: it accepts no match and sends no alert to your phone, and the page stays open. «Resume alerts» reads it again. The pause is not kept: every start of the program is active again.
111
117
  - Start with Windows: the «Start with Windows» switch, on the same card, is off until you turn it on. When it is on, the program starts by itself when you sign in to Windows, for your user only, without opening the browser or the page.
112
118
  - Activity: the page's «Activity» card lists what the program did since it started: the game client found and lost, each phase, the match accepted and the alert sent. It keeps the last 50 lines, in memory only.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pendify"
7
- version = "0.1.3"
7
+ version = "0.1.5"
8
8
  description = "Pairs this PC with a phone account by a QR on a loopback page and sends it two kinds of alerts."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -4,11 +4,15 @@
4
4
  python -m <package> --dry the same, but the watcher never accepts: it alerts at the queue pop
5
5
  python -m <package> --quiet the same, started by the system at logon: no browser, and no message box
6
6
  unless the start fails
7
+ python -m <package> --no-update the same, with no check of PyPI for a newer version (update.py)
7
8
  python -m <package> ping <kind> one alert with the stored pair, one fixed line per answer
8
9
 
9
- The page and the watcher stop together on Ctrl+C, on Ctrl+Break and on the page's quit button, which
10
- removes the run file and exits 0. One instance per config folder: a second start opens the running page
11
- and exits 0, and one made while that program is still closing after its quit waits for it to end and starts.
10
+ The page and the watcher stop together on Ctrl+C, on Ctrl+Break, on the page's quit button and on the quit of
11
+ the icon by the clock, which remove the run file and exit 0. One instance per config folder: a second start
12
+ opens the running page and exits 0, and one made while that program is still closing after its quit waits for
13
+ it to end and starts. A restart the page asks for («Reiniciar ahora», once a newer version is installed, or the
14
+ icon's «Reiniciar para actualizar») starts a new copy once the run file is released: without --quiet, so its page
15
+ opens, and with the --dry, --data-dir, --worker and --client-lockfile of this one.
12
16
  Where no console is attached (a pythonw start), the line a start ends on is also shown in a
13
17
  message box. `--data-dir`, `--worker` and `--client-lockfile` are the
14
18
  test overrides, loopback only, so a test run reads neither the profile, the Worker nor the client's files.
@@ -16,14 +20,16 @@ test overrides, loopback only, so a test run reads neither the profile, the Work
16
20
  import argparse
17
21
  import os
18
22
  import signal
23
+ import subprocess
19
24
  import sys
20
25
  import threading
21
26
  import time
22
27
  import webbrowser
23
28
  from pathlib import Path
24
29
 
25
- from . import alert, client, config, page, pairing, runfile, watcher, worker
30
+ from . import alert, client, config, page, pairing, runfile, update, watcher, worker
26
31
  from .autostart import Autostart
32
+ from .tray import Tray
27
33
 
28
34
  TICK_SECONDS = 1.0
29
35
  STOP_SECONDS = 5.0
@@ -39,8 +45,14 @@ PAGE_NOT_KNOWN_LINE = ("already running: the page of the program that runs is no
39
45
  "moment to open it")
40
46
  CLAIM_FAILED_LINE = "cannot start: the run file cannot be replaced: {path}"
41
47
  FOLDER_FAILED_LINE = "cannot start: the config folder cannot be written: {path}"
48
+ RESTART_FAILED_LINE = "cannot restart: the new copy did not start; start the program again"
42
49
  _MB_ICONINFORMATION = 0x40
43
50
  _MB_SETFOREGROUND = 0x10000
51
+ # The restart's new copy: no console, its own process group, out of this one's.
52
+ _DETACHED_PROCESS = 0x00000008
53
+ _CREATE_NEW_PROCESS_GROUP = 0x00000200
54
+ # The icon by the clock's picture: pendify.ico beside the modules, the file install.ps1 points the shortcuts at.
55
+ ICON_PATH = Path(__file__).with_name("pendify.ico")
44
56
 
45
57
 
46
58
  def _data_dir(value):
@@ -78,6 +90,8 @@ def _arguments(argv):
78
90
  parser.add_argument("--dry", action="store_true", help="watch and alert, but never accept")
79
91
  parser.add_argument("--quiet", action="store_true",
80
92
  help="a start by the system: no browser, and no message box for a start that ends well")
93
+ parser.add_argument("--no-update", action="store_true",
94
+ help="no check of PyPI for a newer version in this run")
81
95
  commands = parser.add_subparsers(dest="command")
82
96
  ping = commands.add_parser("ping", parents=[common], help="send one alert with the stored pair")
83
97
  ping.add_argument("kind", choices=worker.KINDS)
@@ -150,7 +164,31 @@ def _ends(line, code, show):
150
164
  return code
151
165
 
152
166
 
153
- def _serve(args, store, base, timeout, opener, stop, delay, beep, show, clock, sleep, autostart):
167
+ def _restart(args, spawn, show, opens_page):
168
+ """The restart asked for, once the run file is free: a new copy of this program, detached and with no console,
169
+ which claims the run file as a first start does. One that opens its page, as every restart asked today does,
170
+ goes without --quiet; one that does not keeps this run's --quiet. This run's --dry, --data-dir, --worker and
171
+ --client-lockfile go with it either way."""
172
+ command = [sys.executable, "-m", __package__] + (["--quiet"] if args.quiet and not opens_page else [])
173
+ command += ["--dry"] if args.dry else []
174
+ for option in ("data_dir", "worker", "client_lockfile"):
175
+ if hasattr(args, option): # the parser's SUPPRESS: present only when this run was given it
176
+ command += ["--" + option.replace("_", "-"), str(getattr(args, option))]
177
+ try:
178
+ spawn(command, creationflags=_DETACHED_PROCESS | _CREATE_NEW_PROCESS_GROUP, close_fds=True)
179
+ except OSError:
180
+ return _ends(RESTART_FAILED_LINE, 1, show)
181
+ return 0
182
+
183
+
184
+ def _ready_version(updater):
185
+ """The version an install made ready, for the icon's item; None in any other state."""
186
+ seen = updater.snapshot()
187
+ return seen["version"] if seen["state"] == update.READY else None
188
+
189
+
190
+ def _serve(args, store, base, timeout, opener, stop, delay, beep, show, clock, sleep, autostart, tray, updater,
191
+ spawn):
154
192
  # A quiet start is the system's, at logon: it never opens the browser, and a start that ends well shows no box.
155
193
  calm = (lambda line: None) if args.quiet else show
156
194
  run = runfile.RunFile(store.path.parent, clock=clock, sleep=sleep)
@@ -175,7 +213,9 @@ def _serve(args, store, base, timeout, opener, stop, delay, beep, show, clock, s
175
213
  opener(f"http://{page.ADDRESS}:{port}/")
176
214
  show(ALREADY_RUNNING_LINE)
177
215
  return 0
216
+ updating = None
178
217
  try:
218
+ mode = update.OFF if args.no_update else store.read_update()
179
219
  state = pairing.PairingState(store, lambda secret: worker.check(secret, base=base, timeout=timeout))
180
220
  watch, alerter = _watcher(args, store, state, base, timeout, stop, delay, beep)
181
221
 
@@ -192,8 +232,22 @@ def _serve(args, store, base, timeout, opener, stop, delay, beep, show, clock, s
192
232
  pairing_page = page.PairingPage(state, watch=watch.snapshot, on_quit=quit_page, on_pause=watch.pause,
193
233
  on_resume=watch.resume, autostart=autostart, events=watch.events)
194
234
  url = pairing_page.start()
235
+ # The update's rounds on their own thread (update.py), built once the page serves; its restart is the
236
+ # page's quit, after the flag read below once the run file is free; pip's output goes to the config folder.
237
+ updating = updater(update.RUNNING_VERSION, mode, restart=quit_page, folder=store.path.parent)
238
+ pairing_page.updater = updating
239
+ # The icon by the clock, under --quiet as well: the way to stop a copy the Run key started. Its words are
240
+ # the page's, in the language the page resolves with no browser to ask: the kept choice, else Spanish.
241
+ icon = tray(url, on_open=lambda: opener(url), on_pause=watch.pause, on_resume=watch.resume,
242
+ on_quit=quit_page, paused=lambda: watch.paused,
243
+ words=lambda: page.WORDS[pairing_page.language_of(None)], icon_path=ICON_PATH,
244
+ # «Reiniciar para actualizar a X» once an install is ready, the page's own restart
245
+ update_ready=lambda: _ready_version(updating), on_restart=updating.restart)
195
246
  watching = threading.Thread(target=watch.run, daemon=True)
196
247
  try:
248
+ icon.start()
249
+ if mode != update.OFF:
250
+ updating.start()
197
251
  run.publish(pairing_page.port)
198
252
  print(f"page: {url}", flush=True)
199
253
  if not args.quiet and not opener(url): # a start by a person opens the page, linked or not
@@ -207,6 +261,7 @@ def _serve(args, store, base, timeout, opener, stop, delay, beep, show, clock, s
207
261
  pass
208
262
  finally:
209
263
  stop.set()
264
+ updating.close() # the restart gate shuts at the quit
210
265
  # First, so a start made while this one stops waits for it instead of opening a closing page: Ctrl+C
211
266
  # and Ctrl+Break are marked only here, and the page's quit, marked already, is marked again.
212
267
  try:
@@ -217,8 +272,14 @@ def _serve(args, store, base, timeout, opener, stop, delay, beep, show, clock, s
217
272
  watching.join(STOP_SECONDS)
218
273
  alerter.flush(STOP_SECONDS)
219
274
  pairing_page.close()
275
+ icon.close()
276
+ # An install under way is waited for before the run file goes, so a quit does not end this copy while
277
+ # pip swaps the files; past the bound pip goes on alone, its output in a file.
278
+ updating.wait(update.INSTALL_JOIN_SECONDS)
220
279
  finally:
221
280
  run.release()
281
+ if updating is not None and updating.restart_requested.is_set():
282
+ return _restart(args, spawn, show, updating.restart_opens_page)
222
283
  return 0
223
284
 
224
285
 
@@ -231,10 +292,13 @@ def _break_as_interrupt():
231
292
 
232
293
  def main(argv=None, *, opener=webbrowser.open, stop=None, timeout=worker.TIMEOUT_SECONDS, delay=None,
233
294
  beep=alert.beep, box=_message_box, autostart=None, console=_console_attached, clock=time.monotonic,
234
- sleep=time.sleep):
295
+ sleep=time.sleep, tray=Tray, updater=update.Updater, spawn=subprocess.Popen):
235
296
  """`beep`, `box` and `autostart` are the seams of the sound, of the message box and of the start with Windows
236
297
  (the per-user Run value when None): a test run passes silent ones and a fake. `console` says whether a printed
237
- line reaches anybody; `clock` and `sleep` time the run file's re-reads."""
298
+ line reaches anybody; `clock` and `sleep` time the run file's re-reads. `tray` makes the icon by the clock, the
299
+ seam a test run fills with a fake, so no test shows a real icon. `updater` makes the update and `spawn` starts
300
+ the restart's new copy, the seams a test run fills with fakes, so no test reaches PyPI, runs pip or starts a
301
+ copy."""
238
302
  args = _arguments(sys.argv[1:] if argv is None else argv)
239
303
  store = config.ConfigStore(getattr(args, "data_dir", None) or config.default_base_dir())
240
304
  base = getattr(args, "worker", worker.BASE_URL)
@@ -250,7 +314,7 @@ def main(argv=None, *, opener=webbrowser.open, stop=None, timeout=worker.TIMEOUT
250
314
  try:
251
315
  return _serve(args, store, base, timeout, opener, stop or threading.Event(),
252
316
  delay or watcher.accept_delay, beep, show, clock, sleep,
253
- autostart if autostart is not None else Autostart())
317
+ autostart if autostart is not None else Autostart(), tray, updater, spawn)
254
318
  finally:
255
319
  if previous is not None:
256
320
  signal.signal(signal.SIGBREAK, previous)
@@ -1,6 +1,7 @@
1
1
  """The config file: the secret and the link id, under the user's profile (design P1, P6), and the page's theme
2
2
  choice when there is one (light or dark; the system's choice is no key), and its language choice when there is one
3
- (es or en; with none the page follows the browser).
3
+ (es or en; with none the page follows the browser), and the update's mode when one is written there by hand
4
+ (auto, notify or off; with none, auto).
4
5
 
5
6
  The file is `<base>/<folder>/config.json`, where the base is %APPDATA% in a real run and is injected
6
7
  everywhere else, so no test touches the real profile. A write goes to a temp file in the same folder
@@ -30,6 +31,10 @@ THEMES = ("light", "dark")
30
31
  SYSTEM_THEME = "system"
31
32
  # The language switch's choices; with no key the page follows the browser's language.
32
33
  LANGS = ("es", "en")
34
+ # The update's modes (update.py), written by hand as the key "update": auto checks PyPI and installs in the
35
+ # background, notify checks only, off never checks. With no key, or a foreign value, auto.
36
+ UPDATE_MODES = ("auto", "notify", "off")
37
+ DEFAULT_UPDATE = "auto"
33
38
  # _write's default: the stored theme and language choices are kept.
34
39
  _KEEP = object()
35
40
 
@@ -92,6 +97,11 @@ class ConfigStore:
92
97
  and cannot be read raises ConfigError, as read_theme() does; a missing, corrupt or foreign value is None."""
93
98
  return _choice(self._stored(), "lang", LANGS)
94
99
 
100
+ def read_update(self):
101
+ """The update's mode, auto, notify or off; auto when the file holds none or a foreign one. A file that is
102
+ there and cannot be read raises ConfigError, as read_theme() does."""
103
+ return _choice(self._stored(), "update", UPDATE_MODES) or DEFAULT_UPDATE
104
+
95
105
  def set_theme(self, choice):
96
106
  """The theme button's choice written beside the pair: light or dark kept, system kept as no key. Any
97
107
  other value is refused and nothing is written. Answers the stored choice, None for the system's."""
@@ -176,17 +186,19 @@ class ConfigStore:
176
186
 
177
187
  def _write(self, pairing, theme=_KEEP, lang=_KEEP):
178
188
  """The pair, and the theme and language choices: each stored one kept unless set_theme or set_lang passes
179
- its own."""
189
+ its own. The update's mode, written by hand, is kept as it is stored."""
180
190
  folder = self.path.parent
181
- if theme is _KEEP or lang is _KEEP:
182
- stored = self._stored()
183
- theme = _choice(stored, "theme", THEMES) if theme is _KEEP else theme
184
- lang = _choice(stored, "lang", LANGS) if lang is _KEEP else lang
191
+ stored = self._stored()
192
+ theme = _choice(stored, "theme", THEMES) if theme is _KEEP else theme
193
+ lang = _choice(stored, "lang", LANGS) if lang is _KEEP else lang
194
+ update = _choice(stored, "update", UPDATE_MODES)
185
195
  values = {"secret": pairing.secret, "linkId": pairing.link_id}
186
196
  if theme is not None:
187
197
  values["theme"] = theme
188
198
  if lang is not None:
189
199
  values["lang"] = lang
200
+ if update is not None:
201
+ values["update"] = update
190
202
  data = json.dumps(values).encode("utf-8")
191
203
  try:
192
204
  folder.mkdir(parents=True, exist_ok=True)