From 519a9c11613ca0fbfd113fdcc26a95309195d44e Mon Sep 17 00:00:00 2001 From: AJ Siegel Date: Sat, 13 Dec 2025 13:09:40 -0500 Subject: [PATCH] initial add of to-print app --- .gitignore | 25 ++++ README.md | 159 +++++++++++++++++++++++++ print_queue.py | 296 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 6 + 4 files changed, 486 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 print_queue.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..052b80f --- /dev/null +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..ddf2145 --- /dev/null +++ b/README.md @@ -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` diff --git a/print_queue.py b/print_queue.py new file mode 100755 index 0000000..e7ec252 --- /dev/null +++ b/print_queue.py @@ -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() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..84fc7e2 --- /dev/null +++ b/requirements.txt @@ -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)