a4-printer-webserver 0.1.0__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.
- a4_printer_webserver-0.1.0/LICENSE +21 -0
- a4_printer_webserver-0.1.0/PKG-INFO +106 -0
- a4_printer_webserver-0.1.0/README.md +92 -0
- a4_printer_webserver-0.1.0/a4_printer_webserver/__init__.py +5 -0
- a4_printer_webserver-0.1.0/a4_printer_webserver/__main__.py +96 -0
- a4_printer_webserver-0.1.0/a4_printer_webserver/app.py +390 -0
- a4_printer_webserver-0.1.0/a4_printer_webserver/service.py +319 -0
- a4_printer_webserver-0.1.0/a4_printer_webserver/static/app.css +269 -0
- a4_printer_webserver-0.1.0/a4_printer_webserver/static/app.js +509 -0
- a4_printer_webserver-0.1.0/a4_printer_webserver/static/login.js +47 -0
- a4_printer_webserver-0.1.0/a4_printer_webserver/storage.py +222 -0
- a4_printer_webserver-0.1.0/a4_printer_webserver/templates/index.html +132 -0
- a4_printer_webserver-0.1.0/a4_printer_webserver/templates/login.html +40 -0
- a4_printer_webserver-0.1.0/a4_printer_webserver.egg-info/PKG-INFO +106 -0
- a4_printer_webserver-0.1.0/a4_printer_webserver.egg-info/SOURCES.txt +22 -0
- a4_printer_webserver-0.1.0/a4_printer_webserver.egg-info/dependency_links.txt +1 -0
- a4_printer_webserver-0.1.0/a4_printer_webserver.egg-info/requires.txt +3 -0
- a4_printer_webserver-0.1.0/a4_printer_webserver.egg-info/top_level.txt +1 -0
- a4_printer_webserver-0.1.0/pyproject.toml +26 -0
- a4_printer_webserver-0.1.0/setup.cfg +4 -0
- a4_printer_webserver-0.1.0/tests/test_app.py +199 -0
- a4_printer_webserver-0.1.0/tests/test_cli.py +45 -0
- a4_printer_webserver-0.1.0/tests/test_service.py +252 -0
- a4_printer_webserver-0.1.0/tests/test_storage.py +60 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 GGN_2015
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: a4-printer-webserver
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A password-protected Flask and WebSocket frontend for a4-printer-interface
|
|
5
|
+
Author-email: GGN_2015 <neko@jlulug.org>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: a4-printer-interface
|
|
11
|
+
Requires-Dist: Flask<4.0,>=3.1
|
|
12
|
+
Requires-Dist: flask-sock<1.0,>=0.7
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# a4_printer_webserver
|
|
16
|
+
|
|
17
|
+
A single-printer web interface built with Flask and native WebSockets. It uses
|
|
18
|
+
[`a4-printer-interface`](https://pypi.org/project/a4-printer-interface/) to
|
|
19
|
+
discover the printer, monitor its activity, and submit PDF documents or images.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
Python 3.10 or newer is required. The target printer must already be configured
|
|
24
|
+
in the operating system.
|
|
25
|
+
|
|
26
|
+
```powershell
|
|
27
|
+
python -m pip install a4_printer_webserver
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
First, list the available printers and their UUIDs:
|
|
31
|
+
|
|
32
|
+
```powershell
|
|
33
|
+
python -m a4_printer_interface
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Then start the website:
|
|
37
|
+
|
|
38
|
+
```powershell
|
|
39
|
+
python -m a4_printer_webserver `
|
|
40
|
+
--uuid "printer uuid" `
|
|
41
|
+
--password "your password"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The server listens on <http://127.0.0.1:5000> by default, and the default page
|
|
45
|
+
title is `Printer Website`. To use a custom title:
|
|
46
|
+
|
|
47
|
+
```powershell
|
|
48
|
+
python -m a4_printer_webserver `
|
|
49
|
+
--uuid "8fd0d51f-12c8-5b50-a2f1-4f64641ae77c" `
|
|
50
|
+
--password "your password" `
|
|
51
|
+
--title "New Title"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Both `--uuid` and `--password` are required. An empty password is rejected.
|
|
55
|
+
The following options are also available:
|
|
56
|
+
|
|
57
|
+
- `--host 127.0.0.1`: Address on which the server listens. Use `0.0.0.0` to
|
|
58
|
+
allow access from the local network.
|
|
59
|
+
- `--port 5000`: TCP port on which the server listens.
|
|
60
|
+
- `--data-dir printer_data`: Directory for uploaded files, the SQLite queue,
|
|
61
|
+
and print history.
|
|
62
|
+
- `--max-upload-mb 100`: Maximum size of one uploaded file, in MiB.
|
|
63
|
+
- `--auto-delete DAYS`: Automatically delete submitted document files after a
|
|
64
|
+
positive number of days. Automatic deletion is disabled when this option is
|
|
65
|
+
omitted.
|
|
66
|
+
|
|
67
|
+
When exposing the service to a local network or the internet, place it behind
|
|
68
|
+
an HTTPS-enabled reverse proxy. Passwords and documents should not cross an
|
|
69
|
+
untrusted network over plain HTTP.
|
|
70
|
+
|
|
71
|
+
## Behavior
|
|
72
|
+
|
|
73
|
+
- Unauthenticated users can only access the login page. They cannot view the
|
|
74
|
+
queue, download previously submitted files, or establish an authenticated
|
|
75
|
+
WebSocket connection.
|
|
76
|
+
- Uploading a document does not print it immediately. The user must click
|
|
77
|
+
`Print` before the document enters the queue.
|
|
78
|
+
- The `Print test page` button asks for confirmation before adding the built-in
|
|
79
|
+
test page to the queue. It can be canceled until printing starts.
|
|
80
|
+
- `Pause printing` prevents queued jobs from starting. It does not interrupt a
|
|
81
|
+
job that is already printing, and the paused state resets when the server
|
|
82
|
+
restarts.
|
|
83
|
+
- Documents that have not started printing can be canceled. A document in the
|
|
84
|
+
`printing` state cannot be canceled.
|
|
85
|
+
- After the printing library returns successfully, the job is shown as
|
|
86
|
+
`Submitted`. This means that the operating system accepted the job; it does
|
|
87
|
+
not guarantee that the physical pages have finished printing.
|
|
88
|
+
- A green light means the printer is idle, yellow means the printer or the
|
|
89
|
+
current submission is busy, and red means that the configured UUID cannot be
|
|
90
|
+
found or the print service status cannot be read.
|
|
91
|
+
- While the printer reports that it is busy, the most recently submitted job
|
|
92
|
+
remains in History but is displayed as `Printing`. It returns to `Submitted`
|
|
93
|
+
when the printer becomes idle.
|
|
94
|
+
- A submitted document can be deleted manually after confirmation. Manual and
|
|
95
|
+
automatic deletion remove only the stored file; the History entry remains.
|
|
96
|
+
Its Download button is then shown as disabled. Files for jobs currently shown
|
|
97
|
+
as `Printing`, and built-in test-page jobs, do not offer a Delete button.
|
|
98
|
+
- The queue and history are stored in SQLite. If the server stops unexpectedly,
|
|
99
|
+
a job that was being submitted is marked as failed and is not printed again
|
|
100
|
+
automatically.
|
|
101
|
+
|
|
102
|
+
## Tests
|
|
103
|
+
|
|
104
|
+
```powershell
|
|
105
|
+
python -m unittest discover -s tests -v
|
|
106
|
+
```
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# a4_printer_webserver
|
|
2
|
+
|
|
3
|
+
A single-printer web interface built with Flask and native WebSockets. It uses
|
|
4
|
+
[`a4-printer-interface`](https://pypi.org/project/a4-printer-interface/) to
|
|
5
|
+
discover the printer, monitor its activity, and submit PDF documents or images.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Python 3.10 or newer is required. The target printer must already be configured
|
|
10
|
+
in the operating system.
|
|
11
|
+
|
|
12
|
+
```powershell
|
|
13
|
+
python -m pip install a4_printer_webserver
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
First, list the available printers and their UUIDs:
|
|
17
|
+
|
|
18
|
+
```powershell
|
|
19
|
+
python -m a4_printer_interface
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then start the website:
|
|
23
|
+
|
|
24
|
+
```powershell
|
|
25
|
+
python -m a4_printer_webserver `
|
|
26
|
+
--uuid "printer uuid" `
|
|
27
|
+
--password "your password"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The server listens on <http://127.0.0.1:5000> by default, and the default page
|
|
31
|
+
title is `Printer Website`. To use a custom title:
|
|
32
|
+
|
|
33
|
+
```powershell
|
|
34
|
+
python -m a4_printer_webserver `
|
|
35
|
+
--uuid "8fd0d51f-12c8-5b50-a2f1-4f64641ae77c" `
|
|
36
|
+
--password "your password" `
|
|
37
|
+
--title "New Title"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Both `--uuid` and `--password` are required. An empty password is rejected.
|
|
41
|
+
The following options are also available:
|
|
42
|
+
|
|
43
|
+
- `--host 127.0.0.1`: Address on which the server listens. Use `0.0.0.0` to
|
|
44
|
+
allow access from the local network.
|
|
45
|
+
- `--port 5000`: TCP port on which the server listens.
|
|
46
|
+
- `--data-dir printer_data`: Directory for uploaded files, the SQLite queue,
|
|
47
|
+
and print history.
|
|
48
|
+
- `--max-upload-mb 100`: Maximum size of one uploaded file, in MiB.
|
|
49
|
+
- `--auto-delete DAYS`: Automatically delete submitted document files after a
|
|
50
|
+
positive number of days. Automatic deletion is disabled when this option is
|
|
51
|
+
omitted.
|
|
52
|
+
|
|
53
|
+
When exposing the service to a local network or the internet, place it behind
|
|
54
|
+
an HTTPS-enabled reverse proxy. Passwords and documents should not cross an
|
|
55
|
+
untrusted network over plain HTTP.
|
|
56
|
+
|
|
57
|
+
## Behavior
|
|
58
|
+
|
|
59
|
+
- Unauthenticated users can only access the login page. They cannot view the
|
|
60
|
+
queue, download previously submitted files, or establish an authenticated
|
|
61
|
+
WebSocket connection.
|
|
62
|
+
- Uploading a document does not print it immediately. The user must click
|
|
63
|
+
`Print` before the document enters the queue.
|
|
64
|
+
- The `Print test page` button asks for confirmation before adding the built-in
|
|
65
|
+
test page to the queue. It can be canceled until printing starts.
|
|
66
|
+
- `Pause printing` prevents queued jobs from starting. It does not interrupt a
|
|
67
|
+
job that is already printing, and the paused state resets when the server
|
|
68
|
+
restarts.
|
|
69
|
+
- Documents that have not started printing can be canceled. A document in the
|
|
70
|
+
`printing` state cannot be canceled.
|
|
71
|
+
- After the printing library returns successfully, the job is shown as
|
|
72
|
+
`Submitted`. This means that the operating system accepted the job; it does
|
|
73
|
+
not guarantee that the physical pages have finished printing.
|
|
74
|
+
- A green light means the printer is idle, yellow means the printer or the
|
|
75
|
+
current submission is busy, and red means that the configured UUID cannot be
|
|
76
|
+
found or the print service status cannot be read.
|
|
77
|
+
- While the printer reports that it is busy, the most recently submitted job
|
|
78
|
+
remains in History but is displayed as `Printing`. It returns to `Submitted`
|
|
79
|
+
when the printer becomes idle.
|
|
80
|
+
- A submitted document can be deleted manually after confirmation. Manual and
|
|
81
|
+
automatic deletion remove only the stored file; the History entry remains.
|
|
82
|
+
Its Download button is then shown as disabled. Files for jobs currently shown
|
|
83
|
+
as `Printing`, and built-in test-page jobs, do not offer a Delete button.
|
|
84
|
+
- The queue and history are stored in SQLite. If the server stops unexpectedly,
|
|
85
|
+
a job that was being submitted is marked as failed and is not printed again
|
|
86
|
+
automatically.
|
|
87
|
+
|
|
88
|
+
## Tests
|
|
89
|
+
|
|
90
|
+
```powershell
|
|
91
|
+
python -m unittest discover -s tests -v
|
|
92
|
+
```
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Run the web server with ``python -m a4_printer_webserver``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import logging
|
|
7
|
+
import uuid
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from .app import create_app
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
14
|
+
parser = argparse.ArgumentParser(
|
|
15
|
+
description="Serve a password-protected web interface for one A4 printer."
|
|
16
|
+
)
|
|
17
|
+
parser.add_argument(
|
|
18
|
+
"--uuid",
|
|
19
|
+
required=True,
|
|
20
|
+
type=_printer_uuid,
|
|
21
|
+
help="UUID reported by a4-printer-interface for the target printer",
|
|
22
|
+
)
|
|
23
|
+
parser.add_argument(
|
|
24
|
+
"--password",
|
|
25
|
+
required=True,
|
|
26
|
+
help="password required to access the website",
|
|
27
|
+
)
|
|
28
|
+
parser.add_argument("--title", default="Printer Website", help="website title")
|
|
29
|
+
parser.add_argument("--host", default="127.0.0.1", help="address to listen on")
|
|
30
|
+
parser.add_argument("--port", default=5000, type=int, help="TCP port to listen on")
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"--data-dir",
|
|
33
|
+
type=Path,
|
|
34
|
+
default=Path("printer_data"),
|
|
35
|
+
help="directory used for uploaded files and queue history",
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--max-upload-mb",
|
|
39
|
+
default=100,
|
|
40
|
+
type=_positive_integer,
|
|
41
|
+
help="maximum size of one uploaded file in MiB",
|
|
42
|
+
)
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
"--auto-delete",
|
|
45
|
+
metavar="DAYS",
|
|
46
|
+
type=_positive_integer,
|
|
47
|
+
help="delete submitted document files after this many days",
|
|
48
|
+
)
|
|
49
|
+
return parser
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def main() -> None:
|
|
53
|
+
args = build_parser().parse_args()
|
|
54
|
+
if not args.password:
|
|
55
|
+
raise SystemExit("--password cannot be empty")
|
|
56
|
+
if not 1 <= args.port <= 65535:
|
|
57
|
+
raise SystemExit("--port must be between 1 and 65535")
|
|
58
|
+
|
|
59
|
+
logging.basicConfig(
|
|
60
|
+
level=logging.INFO,
|
|
61
|
+
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
|
62
|
+
)
|
|
63
|
+
app = create_app(
|
|
64
|
+
printer_uuid=args.uuid,
|
|
65
|
+
password=args.password,
|
|
66
|
+
title=args.title,
|
|
67
|
+
data_dir=args.data_dir,
|
|
68
|
+
max_upload_mb=args.max_upload_mb,
|
|
69
|
+
auto_delete_days=args.auto_delete,
|
|
70
|
+
)
|
|
71
|
+
service = app.extensions["printer_service"]
|
|
72
|
+
try:
|
|
73
|
+
app.run(host=args.host, port=args.port, threaded=True, use_reloader=False)
|
|
74
|
+
finally:
|
|
75
|
+
service.stop()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _printer_uuid(value: str) -> str:
|
|
79
|
+
try:
|
|
80
|
+
return str(uuid.UUID(value))
|
|
81
|
+
except ValueError:
|
|
82
|
+
raise argparse.ArgumentTypeError("must be a valid UUID") from None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _positive_integer(value: str) -> int:
|
|
86
|
+
try:
|
|
87
|
+
result = int(value)
|
|
88
|
+
except ValueError:
|
|
89
|
+
raise argparse.ArgumentTypeError("must be an integer") from None
|
|
90
|
+
if result <= 0:
|
|
91
|
+
raise argparse.ArgumentTypeError("must be greater than zero")
|
|
92
|
+
return result
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
main()
|