initial add of to-print app

This commit is contained in:
AJ Siegel
2025-12-13 13:09:40 -05:00
commit 519a9c1161
4 changed files with 486 additions and 0 deletions

25
.gitignore vendored Normal file
View File

@@ -0,0 +1,25 @@
# Print queue data (personal)
queue.json
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
# Virtual environments
venv/
env/
ENV/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db

159
README.md Normal file
View File

@@ -0,0 +1,159 @@
# Print Queue Manager
A simple command-line tool to store things you want to print when you're away from your home network, then print them all at once when you're back.
## Features
- Store file paths and URLs in a print queue
- **Watch folder**: Drop files into `~/to-print` and they'll be printed automatically
- Print all pending items with a single command
- Track what's been printed and what's still pending
- Works with your default macOS printer
## Requirements
- Python 3.6+
- macOS (uses `lp` command for printing)
- `curl` (for downloading URLs)
## Usage
### Two ways to queue items
**Method 1: Manually add items to the queue**
Add a local file:
```bash
./print_queue.py add /path/to/document.pdf
./print_queue.py add ~/Downloads/invoice.pdf --name "Monthly Invoice"
```
Add a URL:
```bash
./print_queue.py add https://example.com/article.html
./print_queue.py add https://example.com/page.html --name "Important Article"
```
**Method 2: Use the watch folder (simpler!)**
Just drop files into `~/to-print`:
```bash
cp ~/Downloads/document.pdf ~/to-print/
mv ~/Desktop/receipt.pdf ~/to-print/
```
The watch folder accepts: PDF, TXT, JPG, PNG, GIF, DOC, DOCX, HTML, and PS files.
### List items in the queue
Show pending items:
```bash
./print_queue.py list
```
Show all items (including already printed):
```bash
./print_queue.py list --all
```
### Print all pending items
Print everything in the queue:
```bash
./print_queue.py print
```
Preview what would be printed (dry run):
```bash
./print_queue.py print --dry-run
```
### Remove items from queue
Remove a specific item by ID:
```bash
./print_queue.py remove 3
```
Clear all printed items from the queue:
```bash
./print_queue.py clear
```
## How it works
**Manual queue:**
1. When you're away from your home network, use `add` to store items in the queue
2. The queue is stored in `queue.json` in this directory
3. When you're back on your home network, run `print` to print everything
4. Items are marked as printed but stay in the queue
5. Use `clear` to remove printed items when you're done
**Watch folder:**
1. Drop any files into `~/to-print` whenever you want
2. When you run `print`, all files in the folder are printed
3. After printing, files are moved to `~/to-print/printed/` to avoid reprinting
4. You can delete or keep the printed files - they won't be reprinted
## Examples
**Using the watch folder (easiest):**
```bash
# Away from home - just drop files in the folder
cp ~/Downloads/receipt.pdf ~/to-print/
cp ~/Desktop/ticket.pdf ~/to-print/
# Back home - check what's ready
./print_queue.py list
# Print everything
./print_queue.py print
```
**Using manual queue (for URLs or tracking):**
```bash
# Away from home - add items to queue
./print_queue.py add ~/Downloads/receipt.pdf
./print_queue.py add https://github.com/user/repo/blob/main/README.md --name "GitHub README"
# Back home - check what's queued
./print_queue.py list
# Print everything
./print_queue.py print
# Later, clean up printed items
./print_queue.py clear
```
**Mix both methods:**
```bash
# Add URLs to queue
./print_queue.py add https://example.com/article.html
# Drop files in folder
cp ~/Downloads/*.pdf ~/to-print/
# Print everything at once (queue + folder)
./print_queue.py print
```
## Tips
- **Watch folder is the easiest**: Just drop files into `~/to-print` - no commands needed until you're ready to print
- File paths are converted to absolute paths, so they'll work even if you change directories
- URLs are downloaded and sent directly to the printer
- For better web page printing, consider saving the page as PDF first, then dropping it in the watch folder
- Use `--name` to give items friendly names instead of long file paths or URLs
- Printed files from the folder are moved to `~/to-print/printed/` - check there if you need to reprint something
## Troubleshooting
**"File not found" error**: Make sure the file path is correct and the file exists
**Printing fails**:
- Check that your printer is set up and `lpstat -p -d` shows your default printer
- Make sure you're connected to the network where your printer is available
- For URLs, ensure you have internet connectivity
**Permission denied**: Make sure the script is executable with `chmod +x print_queue.py`

296
print_queue.py Executable file
View File

@@ -0,0 +1,296 @@
#!/usr/bin/env python3
"""
Print Queue Manager - Store items to print when you're not on your home network
"""
import argparse
import json
import os
import subprocess
import sys
from datetime import datetime
from pathlib import Path
import tempfile
QUEUE_FILE = "queue.json"
WATCH_FOLDER = Path.home() / "to-print"
PRINTED_FOLDER = WATCH_FOLDER / "printed"
class PrintQueue:
def __init__(self):
self.queue_file = Path(__file__).parent / QUEUE_FILE
self.queue = self.load_queue()
self.watch_folder = WATCH_FOLDER
self.printed_folder = PRINTED_FOLDER
# Create watch folder if it doesn't exist
self.watch_folder.mkdir(exist_ok=True)
self.printed_folder.mkdir(exist_ok=True)
def load_queue(self):
"""Load the print queue from JSON file"""
if self.queue_file.exists():
with open(self.queue_file, 'r') as f:
return json.load(f)
return []
def save_queue(self):
"""Save the print queue to JSON file"""
with open(self.queue_file, 'w') as f:
json.dump(self.queue, f, indent=2)
def add(self, item, item_type, name=None):
"""Add an item to the print queue"""
entry = {
"id": len(self.queue) + 1,
"type": item_type,
"item": item,
"name": name or item,
"added": datetime.now().isoformat(),
"printed": False
}
self.queue.append(entry)
self.save_queue()
print(f"✓ Added to queue: {entry['name']}")
return entry
def list_items(self, show_all=False):
"""List items in the queue"""
items = self.queue if show_all else [i for i in self.queue if not i['printed']]
folder_files = self.get_folder_files()
if not items and not folder_files:
print("Nothing to print!")
return
if items:
print("\nManually Added Queue:")
print("-" * 60)
for item in items:
status = "✓ PRINTED" if item['printed'] else "⏳ PENDING"
print(f"[{item['id']}] {status}")
print(f" Name: {item['name']}")
print(f" Type: {item['type']}")
print(f" Added: {item['added'][:10]}")
if item['printed']:
print(f" Printed: {item.get('printed_at', 'N/A')[:10]}")
print()
if folder_files:
print(f"\nFiles in Watch Folder ({self.watch_folder}):")
print("-" * 60)
for file in folder_files:
print(f"{file.name}")
print(f"\nTotal: {len(folder_files)} file(s)")
print()
def print_all(self, dry_run=False):
"""Print all pending items in the queue and folder"""
pending = [i for i in self.queue if not i['printed']]
folder_files = self.get_folder_files()
total = len(pending) + len(folder_files)
if total == 0:
print("Nothing to print!")
return
print(f"\n{'Would print' if dry_run else 'Printing'} {total} item(s)...\n")
# Print queue items
if pending:
print("From manually added queue:")
for item in pending:
try:
if dry_run:
print(f" Would print: {item['name']} ({item['type']})")
else:
print(f" Printing: {item['name']}...")
self.print_item(item)
item['printed'] = True
item['printed_at'] = datetime.now().isoformat()
print(f" ✓ Success")
except Exception as e:
print(f" ✗ Failed: {e}")
if not dry_run:
self.save_queue()
print()
# Print folder files
if folder_files:
print(f"From watch folder ({self.watch_folder}):")
for file in folder_files:
try:
if dry_run:
print(f" Would print: {file.name}")
else:
print(f" Printing: {file.name}...")
self.print_folder_file(file)
print(f" ✓ Success (moved to {self.printed_folder.name}/)")
except Exception as e:
print(f" ✗ Failed: {e}")
print()
print("Done! Use 'list --all' to see all items or 'clear' to remove printed items.")
def print_item(self, item):
"""Print a single item"""
if item['type'] == 'file':
# Expand user home directory and make path absolute
file_path = Path(item['item']).expanduser().resolve()
if not file_path.exists():
raise FileNotFoundError(f"File not found: {file_path}")
subprocess.run(['lp', str(file_path)], check=True, capture_output=True)
elif item['type'] == 'url':
# Download URL and print
# For web pages, you might want to use wkhtmltopdf for better formatting
with tempfile.NamedTemporaryFile(suffix='.html', delete=False) as tmp:
try:
print(f" Downloading...")
result = subprocess.run(
['curl', '-L', '-s', item['item'], '-o', tmp.name],
check=True,
capture_output=True,
text=True
)
# Check if file has content
if os.path.getsize(tmp.name) == 0:
raise ValueError("Downloaded file is empty")
print(f" Sending to printer...")
subprocess.run(['lp', tmp.name], check=True, capture_output=True)
finally:
# Clean up temp file
if os.path.exists(tmp.name):
os.unlink(tmp.name)
def clear_printed(self):
"""Remove printed items from the queue"""
original_count = len(self.queue)
self.queue = [i for i in self.queue if not i['printed']]
removed = original_count - len(self.queue)
self.save_queue()
print(f"✓ Removed {removed} printed item(s) from queue")
def remove_item(self, item_id):
"""Remove a specific item from the queue by ID"""
original_queue = self.queue.copy()
self.queue = [i for i in self.queue if i['id'] != item_id]
if len(self.queue) == len(original_queue):
print(f"✗ Item {item_id} not found")
return False
self.save_queue()
print(f"✓ Removed item {item_id} from queue")
return True
def get_folder_files(self):
"""Get all printable files from the watch folder"""
# Common printable file extensions
printable_extensions = {'.pdf', '.txt', '.jpg', '.jpeg', '.png', '.gif',
'.doc', '.docx', '.html', '.htm', '.ps'}
files = []
if self.watch_folder.exists():
for file in self.watch_folder.iterdir():
if file.is_file() and file.suffix.lower() in printable_extensions:
files.append(file)
return sorted(files, key=lambda f: f.stat().st_mtime)
def print_folder_file(self, file_path):
"""Print a file from the watch folder and move it to printed folder"""
subprocess.run(['lp', str(file_path)], check=True, capture_output=True)
# Move to printed folder
dest = self.printed_folder / file_path.name
# Handle name conflicts
counter = 1
while dest.exists():
stem = file_path.stem
suffix = file_path.suffix
dest = self.printed_folder / f"{stem}_{counter}{suffix}"
counter += 1
file_path.rename(dest)
def main():
parser = argparse.ArgumentParser(
description='Print Queue Manager - Store and print files when on your home network',
epilog='Examples:\n'
' %(prog)s add document.pdf\n'
' %(prog)s add https://example.com/page.html --name "Example Page"\n'
' %(prog)s list\n'
' %(prog)s print\n',
formatter_class=argparse.RawDescriptionHelpFormatter
)
subparsers = parser.add_subparsers(dest='command', help='Available commands')
# Add command
add_parser = subparsers.add_parser('add', help='Add item to print queue')
add_parser.add_argument('item', help='File path or URL to print')
add_parser.add_argument('--name', help='Custom name for the item')
# List command
list_parser = subparsers.add_parser('list', help='List items in queue')
list_parser.add_argument('--all', action='store_true',
help='Show all items including printed ones')
# Print command
print_parser = subparsers.add_parser('print', help='Print all pending items')
print_parser.add_argument('--dry-run', action='store_true',
help='Show what would be printed without actually printing')
# Clear command
clear_parser = subparsers.add_parser('clear',
help='Remove printed items from queue')
# Remove command
remove_parser = subparsers.add_parser('remove',
help='Remove a specific item by ID')
remove_parser.add_argument('id', type=int, help='Item ID to remove')
args = parser.parse_args()
if not args.command:
parser.print_help()
return
queue = PrintQueue()
if args.command == 'add':
# Detect if it's a URL or file
item_type = 'url' if args.item.startswith(('http://', 'https://')) else 'file'
# Validate file exists if it's a file path
if item_type == 'file':
file_path = Path(args.item).expanduser().resolve()
if not file_path.exists():
print(f"✗ Error: File not found: {file_path}")
sys.exit(1)
# Store the absolute path
args.item = str(file_path)
queue.add(args.item, item_type, args.name)
elif args.command == 'list':
queue.list_items(show_all=args.all)
elif args.command == 'print':
queue.print_all(dry_run=args.dry_run)
elif args.command == 'clear':
queue.clear_printed()
elif args.command == 'remove':
queue.remove_item(args.id)
if __name__ == '__main__':
main()

6
requirements.txt Normal file
View File

@@ -0,0 +1,6 @@
# No external dependencies required
# This project uses only Python 3 standard library
# System requirements:
# - Python 3.6+
# - curl (for downloading URLs)
# - lp (macOS/Linux printing command)