diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index efd4a4b..0f2f54f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -87,7 +87,7 @@ jobs: working-directory: app - name: Build executable - run: python build.py --skip-deps --version ${{ steps.get_version.outputs.version }} + run: python scripts/build.py --skip-deps --version ${{ steps.get_version.outputs.version }} - name: Upload artifact uses: actions/upload-artifact@v4 diff --git a/6.0 b/6.0 deleted file mode 100644 index e69de29..0000000 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7c0b424..793d94d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -427,10 +427,13 @@ PrintQue/ │ ├── pre-commit # Lint staged files │ └── commit-msg # Validate commit format │ +├── scripts/ # Build and run scripts +│ ├── build.py # Cross-platform build script (use this) +│ ├── run_app.py # Dev launcher +│ └── ... ├── package.json # Root package (commit tooling) ├── commitlint.config.js # Commit message rules ├── pyproject.toml # Python project config -├── build.py # Cross-platform build script └── CONTRIBUTING.md # This file ``` diff --git a/README.md b/README.md index f96d9a7..cd482eb 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,10 @@ The built files will be in the `app/dist` directory. ``` Printque/ +├── scripts/ # Build and run scripts +│ ├── build.py # Main PyInstaller build (use this) +│ ├── run_app.py # Dev launcher +│ └── ... ├── api/ # Python backend │ ├── app.py # Main Flask application │ ├── requirements.txt # Python dependencies @@ -421,7 +425,7 @@ The easiest way to build is using the cross-platform build script: ```bash # Build for your current platform -python build.py +python scripts/build.py ``` This will: @@ -445,13 +449,13 @@ Examples: ```bash # Clean build from scratch -python build.py --clean +python scripts/build.py --clean # Rebuild only the backend (frontend already built) -python build.py --skip-frontend +python scripts/build.py --skip-frontend # Quick rebuild (dependencies already installed) -python build.py --skip-deps --skip-frontend +python scripts/build.py --skip-deps --skip-frontend ``` ### Build Output @@ -476,7 +480,7 @@ dist/ #### Windows ```bash -python build.py +python scripts/build.py # Output: dist/PrintQue.exe # Launcher: dist/PrintQue_Windows_YYYYMMDD/Start_PrintQue.bat ``` @@ -484,7 +488,7 @@ python build.py #### macOS ```bash -python build.py +python scripts/build.py # Output: dist/PrintQue.app # Launcher: dist/PrintQue_macOS_YYYYMMDD/start_printque.sh ``` @@ -492,7 +496,7 @@ python build.py #### Linux ```bash -python build.py +python scripts/build.py # Output: dist/printque # Launcher: dist/PrintQue_Linux_YYYYMMDD/start_printque.sh ``` diff --git a/README.txt b/README.txt deleted file mode 100644 index 7c4803b..0000000 --- a/README.txt +++ /dev/null @@ -1,105 +0,0 @@ -# PrintQue - 3D Printer Management System - -PrintQue is a powerful and easy-to-use management system designed for 3D print farms. It provides centralized control, monitoring, and queue management for multiple 3D printers, helping you maximize efficiency and productivity. - -## Features - -- **Centralized Control**: Manage all your 3D printers from a single web interface -- **Queue Management**: Create, prioritize, and distribute print jobs automatically -- **Real-time Monitoring**: Track printer status, progress, and temperatures -- **Group Organization**: Organize printers into groups for specialized workloads -- **Automatic Ejection**: Configure custom end G-code for automated part removal -- **Statistics Tracking**: Monitor filament usage and printer performance -- **License Tiers**: Free, Standard, Professional, and Enterprise options to match your needs - -## Getting Started - -### System Requirements - -- Windows 10 or newer -- 4GB RAM (8GB recommended) -- 500MB free disk space -- Network connectivity to your 3D printers - -### Installation - -1. Run the PrintQue installer (PrintQue_Setup.exe) -2. Follow the on-screen instructions -3. Enter your license key when prompted (or use the free tier) -4. Launch PrintQue from the desktop or start menu shortcut - -### Accessing the Web Interface - -PrintQue provides several ways to access its web interface: - -- **Automatic Launch**: The web interface opens automatically when PrintQue starts -- **Desktop Shortcut**: Use the "PrintQue Web Interface" shortcut created during installation -- **Manual Access**: Open any web browser and navigate to http://localhost:5000 - -## Printer Setup - -1. From the web interface, click "Add Printer" -2. Enter printer details: - - Name: A descriptive name for the printer - - IP Address: The IP address of the printer (must be on the same network) - - API Key: The OctoPrint API key for the printer - - Group: Assign the printer to a group (optional) - -3. Click "Add" to connect the printer - -## Creating Print Jobs - -1. Click "New Print Job" on the main dashboard -2. Upload your G-code file -3. Select quantity and target printer group(s) -4. Enable ejection and configure end G-code if needed -5. Click "Create Job" to add it to the queue - -PrintQue will automatically distribute jobs to available printers based on group assignments and printer availability. - -## License Tiers - -### FREE -- Up to 3 printers -- Basic printing and job queue - -### STANDARD -- Up to 5 printers -- Advanced reporting -- Email notifications - -### PROFESSIONAL -- Up to 15 printers -- Priority support -- API access - -### ENTERPRISE -- Unlimited printers -- Custom branding -- Multi-tenant support - -To upgrade your license, visit the License page in the application and enter your new license key. - -## Troubleshooting - -### Common Issues - -- **Connection Issues**: Ensure printers are powered on and connected to the network -- **API Key Errors**: Verify your OctoPrint API key is correct -- **License Issues**: Check your license status on the License page - -### Logs - -Logs are stored in your user directory under PrintQueData/app.log and can help diagnose issues. - -### Support - -For assistance with PrintQue: -- Email: zhartley@hotmail.ca -- Visit: www.printque.ca -## Legal - -PrintQue © -All rights reserved. - -This software is licensed, not sold. Usage is subject to the terms and conditions specified in the End User License Agreement. \ No newline at end of file diff --git a/app/README.md b/app/README.md index 5ca2f7f..1444c08 100644 --- a/app/README.md +++ b/app/README.md @@ -1,290 +1,10 @@ -Welcome to your new TanStack app! +# PrintQue Frontend -# Getting Started +React frontend for PrintQue. See the [repository root README](../README.md) for full setup and development instructions. -To run this application: +**From this directory:** -```bash -npm install -npm run dev -``` - -# Building For Production - -To build this application for production: - -```bash -npm run build -``` - -## Testing - -This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with: - -```bash -npm run test -``` - -## Styling - -This project uses CSS for styling. - - - - -## Routing -This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a file based router. Which means that the routes are managed as files in `src/routes`. - -### Adding A Route - -To add a new route to your application just add another a new file in the `./src/routes` directory. - -TanStack will automatically generate the content of the route file for you. - -Now that you have two routes you can use a `Link` component to navigate between them. - -### Adding Links - -To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`. - -```tsx -import { Link } from "@tanstack/react-router"; -``` - -Then anywhere in your JSX you can use it like so: - -```tsx -About -``` - -This will create a link that will navigate to the `/about` route. - -More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent). - -### Using A Layout - -In the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you use the `` component. - -Here is an example layout that includes a header: - -```tsx -import { Outlet, createRootRoute } from '@tanstack/react-router' -import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' - -import { Link } from "@tanstack/react-router"; - -export const Route = createRootRoute({ - component: () => ( - <> -
- -
- - - - ), -}) -``` - -The `` component is not required so you can remove it if you don't want it in your layout. - -More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts). - - -## Data Fetching - -There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered. - -For example: - -```tsx -const peopleRoute = createRoute({ - getParentRoute: () => rootRoute, - path: "/people", - loader: async () => { - const response = await fetch("https://swapi.dev/api/people"); - return response.json() as Promise<{ - results: { - name: string; - }[]; - }>; - }, - component: () => { - const data = peopleRoute.useLoaderData(); - return ( -
    - {data.results.map((person) => ( -
  • {person.name}
  • - ))} -
- ); - }, -}); -``` - -Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters). - -### React-Query - -React-Query is an excellent addition or alternative to route loading and integrating it into you application is a breeze. - -First add your dependencies: - -```bash -npm install @tanstack/react-query @tanstack/react-query-devtools -``` - -Next we'll need to create a query client and provider. We recommend putting those in `main.tsx`. - -```tsx -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; - -// ... - -const queryClient = new QueryClient(); - -// ... - -if (!rootElement.innerHTML) { - const root = ReactDOM.createRoot(rootElement); - - root.render( - - - - ); -} -``` - -You can also add TanStack Query Devtools to the root route (optional). - -```tsx -import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; - -const rootRoute = createRootRoute({ - component: () => ( - <> - - - - - ), -}); -``` - -Now you can use `useQuery` to fetch your data. - -```tsx -import { useQuery } from "@tanstack/react-query"; - -import "./App.css"; - -function App() { - const { data } = useQuery({ - queryKey: ["people"], - queryFn: () => - fetch("https://swapi.dev/api/people") - .then((res) => res.json()) - .then((data) => data.results as { name: string }[]), - initialData: [], - }); - - return ( -
-
    - {data.map((person) => ( -
  • {person.name}
  • - ))} -
-
- ); -} - -export default App; -``` - -You can find out everything you need to know on how to use React-Query in the [React-Query documentation](https://tanstack.com/query/latest/docs/framework/react/overview). - -## State Management - -Another common requirement for React applications is state management. There are many options for state management in React. TanStack Store provides a great starting point for your project. - -First you need to add TanStack Store as a dependency: - -```bash -npm install @tanstack/store -``` - -Now let's create a simple counter in the `src/App.tsx` file as a demonstration. - -```tsx -import { useStore } from "@tanstack/react-store"; -import { Store } from "@tanstack/store"; -import "./App.css"; - -const countStore = new Store(0); - -function App() { - const count = useStore(countStore); - return ( -
- -
- ); -} - -export default App; -``` - -One of the many nice features of TanStack Store is the ability to derive state from other state. That derived state will update when the base state updates. - -Let's check this out by doubling the count using derived state. - -```tsx -import { useStore } from "@tanstack/react-store"; -import { Store, Derived } from "@tanstack/store"; -import "./App.css"; - -const countStore = new Store(0); - -const doubledStore = new Derived({ - fn: () => countStore.state * 2, - deps: [countStore], -}); -doubledStore.mount(); - -function App() { - const count = useStore(countStore); - const doubledCount = useStore(doubledStore); - - return ( -
- -
Doubled - {doubledCount}
-
- ); -} - -export default App; -``` - -We use the `Derived` class to create a new store that is derived from another store. The `Derived` class has a `mount` method that will start the derived store updating. - -Once we've created the derived store we can use it in the `App` component just like we would any other store using the `useStore` hook. - -You can find out everything you need to know on how to use TanStack Store in the [TanStack Store documentation](https://tanstack.com/store/latest). - -# Demo files - -Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed. - -# Learn More - -You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com). +- `npm install` — Install dependencies +- `npm run dev` — Start development server (port 3000) +- `npm run build` — Production build +- `npm run test` — Run tests diff --git a/pyinstaller b/pyinstaller deleted file mode 100644 index e69de29..0000000 diff --git a/build.py b/scripts/build.py similarity index 97% rename from build.py rename to scripts/build.py index 1d19afd..7c5b15a 100644 --- a/build.py +++ b/scripts/build.py @@ -4,10 +4,10 @@ Creates portable executables for Windows, macOS, and Linux Usage: - python build.py # Build for current platform - python build.py --clean # Clean build artifacts first - python build.py --skip-frontend # Skip frontend build (use existing) - python build.py --version 1.2.3 # Override version (for CI) + python scripts/build.py # Build for current platform + python scripts/build.py --clean # Clean build artifacts first + python scripts/build.py --skip-frontend # Skip frontend build (use existing) + python scripts/build.py --version 1.2.3 # Override version (for CI) """ import os @@ -23,10 +23,13 @@ # Build configuration APP_NAME = "PrintQue" +# Repo root (script lives in scripts/) +ROOT_DIR = Path(__file__).resolve().parent.parent + def get_version_from_file() -> str: """Read version from api/__version__.py""" - version_file = Path(__file__).parent / "api" / "__version__.py" + version_file = ROOT_DIR / "api" / "__version__.py" if version_file.exists(): content = version_file.read_text() match = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', content) @@ -39,7 +42,6 @@ def get_version_from_file() -> str: VERSION = get_version_from_file() # Directories -ROOT_DIR = Path(__file__).parent.absolute() API_DIR = ROOT_DIR / "api" APP_DIR = ROOT_DIR / "app" BUILD_DIR = ROOT_DIR / "build" diff --git a/build_windows.py b/scripts/build_windows.py similarity index 62% rename from build_windows.py rename to scripts/build_windows.py index 1aef6d5..368d7dc 100644 --- a/build_windows.py +++ b/scripts/build_windows.py @@ -1,6 +1,10 @@ #!/usr/bin/env python """ -Fixed build script for PrintQue Windows executable +Legacy Windows-specific build script for PrintQue. +For current builds use: python scripts/build.py + +This script expects to be run from the repository root. It uses ROOT_DIR +so that build/ and dist/ are at repo root when run from scripts/. """ import os import sys @@ -8,53 +12,59 @@ import subprocess import zipfile from datetime import datetime +from pathlib import Path + +# Repo root (script lives in scripts/) +ROOT_DIR = Path(__file__).resolve().parent.parent + def clean_build(): """Clean previous build artifacts""" print("Cleaning previous builds...") - dirs_to_remove = ['build', 'dist', '__pycache__'] - for dir_name in dirs_to_remove: - if os.path.exists(dir_name): - shutil.rmtree(dir_name) - print(f"Removed {dir_name}") + dirs_to_remove = [ROOT_DIR / 'build', ROOT_DIR / 'dist', ROOT_DIR / 'api' / '__pycache__'] + for d in dirs_to_remove: + if d.exists(): + shutil.rmtree(d) + print(f"Removed {d}") + def check_files(): - """Verify all required files exist""" + """Verify all required files exist (under api/)""" print("\nChecking required files...") - required_files = [ - 'app.py', - 'run_app.py', - 'routes.py', - 'state.py', - 'printer_manager.py', - 'config.py', - 'requirements.txt' + api = ROOT_DIR / 'api' + required = [ + api / 'app.py', + api / 'state.py', + api / 'printer_manager.py', + api / 'config.py', + api / 'requirements.txt', ] - - missing_files = [] - for file in required_files: - if os.path.exists(file): - print(f"[OK] {file}") + required_dirs = [api / 'routes'] + missing = [] + for f in required: + if f.exists(): + print(f"[OK] {f.relative_to(ROOT_DIR)}") else: - print(f"[MISSING] {file}") - missing_files.append(file) - - if missing_files: + print(f"[MISSING] {f.relative_to(ROOT_DIR)}") + missing.append(f) + for d in required_dirs: + if d.is_dir(): + print(f"[OK] {d.relative_to(ROOT_DIR)}/") + else: + print(f"[MISSING] {d.relative_to(ROOT_DIR)}/") + missing.append(d) + if missing: print("\nError: Missing required files!") return False - - # Check templates - print("\nChecking templates...") - template_dir = 'templates' - if os.path.exists(template_dir): - templates = os.listdir(template_dir) - print(f"Found {len(templates)} templates") + template_dir = api / 'templates' + if template_dir.exists(): + print(f"Found {len(list(template_dir.iterdir()))} templates") else: - print("[MISSING] templates directory!") + print("[MISSING] api/templates directory!") return False - return True + def build_exe(): """Build the executable using PyInstaller""" print("\nBuilding PrintQue.exe...") @@ -152,15 +162,13 @@ def build_exe(): ) ''' - # Write spec file - with open('PrintQue.spec', 'w') as f: - f.write(spec_content) - - # Build using the spec file - cmd = ['pyinstaller', 'PrintQue.spec', '--clean', '-y'] + spec_file = ROOT_DIR / 'PrintQue.spec' + spec_file.write_text(spec_content) + # Build using the spec file (run from root so dist/build at root) + cmd = [sys.executable, '-m', 'PyInstaller', str(spec_file), '--clean', '-y'] try: - result = subprocess.run(cmd, check=True, capture_output=True, text=True) + result = subprocess.run(cmd, cwd=str(ROOT_DIR), check=True, capture_output=True, text=True) print("Build completed successfully!") print(result.stdout) return True @@ -174,44 +182,28 @@ def build_exe(): print("Please install it with: pip install pyinstaller") return False + def create_distribution(): """Create a distribution package""" print("\nCreating distribution package...") dist_name = f"PrintQue_Windows_{datetime.now().strftime('%Y%m%d')}" - dist_dir = os.path.join('dist', dist_name) + dist_dir = ROOT_DIR / 'dist' / dist_name - # Create distribution directory - os.makedirs(dist_dir, exist_ok=True) + dist_dir.mkdir(parents=True, exist_ok=True) - # Copy executable - if os.path.exists('dist/PrintQue.exe'): - shutil.copy('dist/PrintQue.exe', dist_dir) + src_exe = ROOT_DIR / 'dist' / 'PrintQue.exe' + if src_exe.exists(): + shutil.copy(src_exe, dist_dir) print(f"Copied PrintQue.exe to {dist_dir}") else: print("Error: PrintQue.exe not found!") return False - # Create data directories - data_dirs = ['data', 'uploads', 'certs'] - for dir_name in data_dirs: - target_dir = os.path.join(dist_dir, dir_name) - os.makedirs(target_dir, exist_ok=True) + for dir_name in ['data', 'uploads', 'certs']: + (dist_dir / dir_name).mkdir(exist_ok=True) print(f"Created directory: {dir_name}") - # Copy additional files - files_to_copy = [ - 'README.txt', - 'requirements.txt', - ] - - for file in files_to_copy: - if os.path.exists(file): - shutil.copy(file, dist_dir) - print(f"Copied {file}") - - - # Create batch file launcher batch_content = """@echo off title PrintQue Server echo ================================================ @@ -229,12 +221,9 @@ def create_distribution(): PrintQue.exe pause """ - - with open(os.path.join(dist_dir, 'Start_PrintQue.bat'), 'w') as f: - f.write(batch_content) + (dist_dir / 'Start_PrintQue.bat').write_text(batch_content) print("Created Start_PrintQue.bat") - # Create README readme_content = """PrintQue - Open Source Print Farm Manager ========================================== @@ -266,39 +255,30 @@ def create_distribution(): Version: 1.0 """ - - with open(os.path.join(dist_dir, 'README.txt'), 'w') as f: - f.write(readme_content) + (dist_dir / 'README.txt').write_text(readme_content) print("Created README.txt") - # Create a zip file - print(f"\nCreating zip archive: {dist_name}.zip") - zip_path = f'dist/{dist_name}.zip' - + zip_path = ROOT_DIR / 'dist' / f'{dist_name}.zip' with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: for root, dirs, files in os.walk(dist_dir): for file in files: file_path = os.path.join(root, file) - arcname = os.path.relpath(file_path, os.path.dirname(dist_dir)) + arcname = os.path.relpath(file_path, dist_dir.parent) zipf.write(file_path, arcname) - print(f"Distribution package created: {zip_path}") return True + def main(): print("=" * 60) - print(" PrintQue Windows Executable Builder") + print(" PrintQue Windows Executable Builder (legacy)") + print(" Prefer: python scripts/build.py") print("=" * 60) - # Check Python version if sys.version_info < (3, 8): print("Error: Python 3.8 or higher is required") - print(f"Current version: {sys.version}") return 1 - print(f"Python version: {sys.version.split()[0]}") - - # Check if PyInstaller is installed try: import PyInstaller print(f"PyInstaller version: {PyInstaller.__version__}") @@ -307,26 +287,13 @@ def main(): print("Install it with: pip install pyinstaller") return 1 - # Clean previous builds clean_build() - - # Check required files if not check_files(): print("\nBuild cannot continue - missing required files!") return 1 - - # Build executable - print("\n" + "=" * 60) - print("Building executable...") - print("=" * 60) if not build_exe(): print("\nBuild failed! Check the error messages above.") return 1 - - # Create distribution - print("\n" + "=" * 60) - print("Creating distribution package...") - print("=" * 60) if not create_distribution(): print("\nDistribution creation failed!") return 1 @@ -334,18 +301,9 @@ def main(): print("\n" + "=" * 60) print(" Build Completed Successfully!") print("=" * 60) - print("\nOutput files:") - print(f" - Executable: dist/PrintQue.exe") - print(f" - Distribution folder: dist/PrintQue_Windows_{datetime.now().strftime('%Y%m%d')}/") - print(f" - Zip package: dist/PrintQue_Windows_{datetime.now().strftime('%Y%m%d')}.zip") - print("\nTo test the executable:") - print(" 1. Navigate to: dist/PrintQue_Windows_{datetime.now().strftime('%Y%m%d')}/") - print(" 2. Double-click: Start_PrintQue.bat") - print(" 3. Open browser to: http://localhost:5000") - print("\nNote: First run may be slow as Windows Defender scans the file.") - print("=" * 60) - + print(f"\nOutput: {ROOT_DIR / 'dist'}") return 0 + if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file + sys.exit(main()) diff --git a/complete_build.py b/scripts/complete_build.py similarity index 71% rename from complete_build.py rename to scripts/complete_build.py index fa3075e..bf634cf 100644 --- a/complete_build.py +++ b/scripts/complete_build.py @@ -1,11 +1,20 @@ #!/usr/bin/env python """ -Complete build script with all dependencies for PrintQue +Legacy complete build script with all dependencies for PrintQue. +For current builds use: python scripts/build.py + +Run from repository root. Uses ROOT_DIR for build/dist; runs PyInstaller from api/. """ import os import sys import shutil import subprocess +from pathlib import Path + +# Repo root (script lives in scripts/) +ROOT_DIR = Path(__file__).resolve().parent.parent +API_DIR = ROOT_DIR / "api" + def create_spec_file(): """Create a comprehensive spec file with all dependencies""" @@ -94,11 +103,11 @@ def create_spec_file(): 'paho.mqtt.client', ] -# Add your project files and folders +# Add your project files and folders (paths relative to api_dir / cwd) datas += [ ('templates', 'templates'), ('static', 'static') if os.path.exists('static') else ('templates', '.'), - ('README.txt', '.') if os.path.exists('README.txt') else ('requirements.txt', '.'), + ('requirements.txt', '.'), ] # Add all your Python modules @@ -162,48 +171,44 @@ def create_spec_file(): ) ''' - with open('PrintQue_Complete.spec', 'w') as f: - f.write(spec_content) + spec_file = API_DIR / 'PrintQue_Complete.spec' + spec_file.write_text(spec_content) print("Created PrintQue_Complete.spec") + def build_exe(): """Build the executable""" print("\nBuilding PrintQue.exe with all dependencies...") - # First, make sure all packages are installed - print("Verifying package installation...") packages = [ - 'flask', 'flask-socketio', 'eventlet', 'python-socketio', + 'flask', 'flask-socketio', 'eventlet', 'python-socketio', 'python-engineio', 'werkzeug', 'jinja2', 'cryptography', 'aiohttp', 'requests', 'psutil', 'simple-websocket', 'dnspython', 'paho-mqtt' ] - for package in packages: - result = subprocess.run([sys.executable, '-m', 'pip', 'show', package], - capture_output=True, text=True) + result = subprocess.run([sys.executable, '-m', 'pip', 'show', package], + capture_output=True, text=True, cwd=str(ROOT_DIR)) if result.returncode != 0: print(f"Installing missing package: {package}") - subprocess.run([sys.executable, '-m', 'pip', 'install', package]) + subprocess.run([sys.executable, '-m', 'pip', 'install', package], cwd=str(ROOT_DIR)) - # Create the spec file create_spec_file() - # Build using PyInstaller - cmd = ['pyinstaller', 'PrintQue_Complete.spec', '--clean', '-y'] - + cmd = [ + sys.executable, '-m', 'PyInstaller', + str(API_DIR / 'PrintQue_Complete.spec'), + '--clean', '-y', + '--distpath', str(ROOT_DIR / 'dist'), + '--workpath', str(ROOT_DIR / 'build'), + ] try: print("\nRunning PyInstaller...") - result = subprocess.run(cmd, check=False) - + result = subprocess.run(cmd, cwd=str(API_DIR), check=False) if result.returncode == 0: print("\nBuild completed successfully!") - - # Create the distribution folder - if os.path.exists('dist/PrintQue.exe'): - print("\nCreating distribution package...") - - # Create Start_PrintQue.bat + exe_path = ROOT_DIR / 'dist' / 'PrintQue.exe' + if exe_path.exists(): batch_content = """@echo off title PrintQue Server echo Starting PrintQue Server... @@ -217,53 +222,39 @@ def build_exe(): PrintQue.exe pause """ - - with open('dist/Start_PrintQue.bat', 'w') as f: - f.write(batch_content) - - # Copy templates if they exist - if os.path.exists('templates'): - dest_templates = 'dist/templates' - if os.path.exists(dest_templates): - shutil.rmtree(dest_templates) - shutil.copytree('templates', dest_templates) + (ROOT_DIR / 'dist' / 'Start_PrintQue.bat').write_text(batch_content) + if (API_DIR / 'templates').exists(): + dest = ROOT_DIR / 'dist' / 'templates' + if dest.exists(): + shutil.rmtree(dest) + shutil.copytree(API_DIR / 'templates', dest) print("Copied templates folder") - print("\n" + "="*60) print("BUILD SUCCESSFUL!") print("="*60) - print("\nYour executable is ready in the 'dist' folder:") - print(" - PrintQue.exe (main executable)") - print(" - Start_PrintQue.bat (easy launcher)") - print(" - templates/ (HTML templates)") - print("\nPrintQue Open Source Edition - All features enabled!") - print("\nTo run PrintQue:") - print(" 1. Go to the dist folder") - print(" 2. Double-click Start_PrintQue.bat") - + print(f"\nOutput: {ROOT_DIR / 'dist'}") else: print("\nError: PrintQue.exe was not created!") - else: print(f"\nBuild failed with return code: {result.returncode}") - except Exception as e: print(f"\nBuild error: {str(e)}") + def main(): print("="*60) - print("PrintQue Complete Build Script") + print("PrintQue Complete Build Script (legacy)") + print("Prefer: python scripts/build.py") print("="*60) - # Clean old builds print("\nCleaning old builds...") - for folder in ['build', 'dist']: - if os.path.exists(folder): + for folder in [ROOT_DIR / 'build', ROOT_DIR / 'dist']: + if folder.exists(): shutil.rmtree(folder) print(f"Removed {folder}") - # Build the executable build_exe() + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/install-macos.sh b/scripts/install-macos.sh similarity index 98% rename from install-macos.sh rename to scripts/install-macos.sh index f117afe..04e55c3 100644 --- a/install-macos.sh +++ b/scripts/install-macos.sh @@ -126,4 +126,4 @@ echo "Default admin credentials: admin / printque" echo "Please change these credentials on first login!" echo echo "A shortcut has been created in your Applications folder." -echo "PrintQue will automatically start when you log in." \ No newline at end of file +echo "PrintQue will automatically start when you log in." diff --git a/run_app.py b/scripts/run_app.py similarity index 93% rename from run_app.py rename to scripts/run_app.py index 70317d1..da774b5 100644 --- a/run_app.py +++ b/scripts/run_app.py @@ -1,6 +1,9 @@ """ Enhanced error handling wrapper for PrintQue application. Modified to remove tkinter dependency and work with PyInstaller. + +When run from scripts/ (e.g. python scripts/run_app.py), adds api/ to path +and uses api/ for templates/static. Run from repo root. """ import os import sys @@ -10,8 +13,17 @@ import shutil import atexit import re +from pathlib import Path def main(): + # When run from scripts/, ensure api is on path and we use api dir for templates/static + script_dir = Path(__file__).resolve().parent + root_dir = script_dir.parent + api_dir = root_dir / "api" + if script_dir.name == "scripts": + sys.path.insert(0, str(api_dir)) + os.chdir(api_dir) + try: # Set up error logging error_log_path = os.path.join(os.path.expanduser("~"), "PrintQueData", "flask_error.log") @@ -82,7 +94,8 @@ def open_browser(): if getattr(sys, 'frozen', False): base_dir = sys._MEIPASS else: - base_dir = os.path.dirname(os.path.abspath(__file__)) + # When run from scripts/, use api dir for templates/static + base_dir = str(api_dir) if script_dir.name == "scripts" else os.path.dirname(os.path.abspath(__file__)) template_folder = os.path.join(base_dir, "templates") static_folder = os.path.join(base_dir, "static") @@ -228,4 +241,4 @@ def cleanup_on_exit(): time.sleep(2) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/test_build.py b/scripts/test_build.py similarity index 94% rename from test_build.py rename to scripts/test_build.py index 52e42b9..c77b5db 100644 --- a/test_build.py +++ b/scripts/test_build.py @@ -3,8 +3,8 @@ Quick test script to verify the PrintQue build works correctly. Usage: - python test_build.py # Test the source directly - python test_build.py --dist # Test the built executable + python scripts/test_build.py # Test the source directly + python scripts/test_build.py --dist # Test the built executable """ import os @@ -16,7 +16,8 @@ import requests from pathlib import Path -ROOT_DIR = Path(__file__).parent.absolute() +# Repo root (script lives in scripts/) +ROOT_DIR = Path(__file__).resolve().parent.parent DIST_DIR = ROOT_DIR / "dist" API_DIR = ROOT_DIR / "api" @@ -138,7 +139,7 @@ def run_dist_test(): if not exe_path.exists(): print(f"[ERROR] Executable not found at {exe_path}") - print("Run 'python build.py' first to create the executable.") + print("Run 'python scripts/build.py' first to create the executable.") return 1 print(f"Starting executable: {exe_path}")