#!/usr/bin/env python3 """Copy updated Supernote files to a local directory""" import os import sys import json import re import urllib.parse from datetime import datetime from pathlib import Path import requests # Configuration SUPERNOTE = "192.168.33.125:8089" HOMEDIR = "c:/users/charles/supernote" REQUEST_TIMEOUT = 10 def readdir(uri, base_url): """Recursively read a directory on the Supernote and yield file information""" url = f"{base_url}{uri}" with requests.get(url, timeout=REQUEST_TIMEOUT) as response: response.raise_for_status() # Look for the JSON in the response text # The JSON is stored as a JavaScript string: const json = '{"deviceName":...}' for line in response.text.split('\n'): if 'const json' in line: # Find where the JSON string starts idx = response.text.find(line) remaining = response.text[idx:] # Find the start of the JSON string (after the = and quote) start_match = re.search(r"const json\s*=\s*'", remaining) if not start_match: start_match = re.search(r'const json\s*=\s*"', remaining) if start_match: # Find the matching end quote by counting braces quote_char = remaining[start_match.end() - 1] json_start = start_match.end() # Find the closing quote - need to handle escaped quotes brace_count = 0 in_string = False escaped = False json_end = json_start for i in range(json_start, len(remaining)): ch = remaining[i] if escaped: escaped = False continue if ch == '\\': escaped = True continue if ch == quote_char and brace_count == 0: json_end = i break if ch == '{': brace_count += 1 elif ch == '}': brace_count -= 1 json_str = remaining[json_start:json_end] try: json_data = json.loads(json_str) if 'fileList' in json_data: for item in json_data['fileList']: name = item.get('name', '') date_str = item.get('date', '') size = item.get('size', 0) is_directory = item.get('isDirectory', False) item_uri = item.get('uri', '') # Skip spotlight and fseventsd directories if '/.Spotlight-V100/' in item_uri or '/.fseventsd/' in item_uri: continue if not is_directory: yield (date_str, size, item_uri) else: yield from readdir(item_uri, base_url) except json.JSONDecodeError as e: print(f"JSON parse error in {uri}: {e}", file=sys.stderr) print(f"JSON string (first 500 chars): {json_str[:500]}", file=sys.stderr) break def decode_uri(uri): """Decode URI by replacing + with spaces and URL decoding""" # Replace + with space decoded = uri.replace('+', ' ') # URL decode decoded = urllib.parse.unquote(decoded) return decoded def parse_date(date_str): """Parse date string in format 'YYYY-MM-DD HH:MM' to datetime""" try: return datetime.strptime(date_str, '%Y-%m-%d %H:%M') except ValueError: return None def get_file_mtime_str(filepath): """Get file modification time as a string in format 'YYYY-MM-DD HH:MM'""" if not os.path.exists(filepath): return None mtime = os.path.getmtime(filepath) return datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M') def main(): base_url = f"http://{SUPERNOTE}" home_path = Path(HOMEDIR) # Ensure home directory exists home_path.mkdir(parents=True, exist_ok=True) print(f"Syncing from {base_url} to {HOMEDIR}/", file=sys.stderr) # Walk the document tree and find all files try: for date_str, size, uri in readdir("/", base_url): # Decode the URI to get the actual filename fqfn = decode_uri(uri) local_path = home_path / fqfn.lstrip('/') # Check if file needs updating needs_update = False is_new_file = not local_path.exists() if is_new_file: needs_update = True else: local_date_str = get_file_mtime_str(local_path) if local_date_str != date_str: needs_update = True if needs_update: tag = "[new]" if is_new_file else "[update]" print(f"{tag} {local_path}") # Create parent directory if needed local_path.parent.mkdir(parents=True, exist_ok=True) # Download the file file_url = f"{base_url}{uri}" temp_path = local_path.with_suffix(local_path.suffix + '.tmp') if temp_path.exists(): temp_path.unlink() try: with requests.get(file_url, stream=True, timeout=REQUEST_TIMEOUT) as response: response.raise_for_status() # Write to temporary file first with open(temp_path, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): if chunk: f.write(chunk) # Move to final location (replace existing file on Windows) temp_path.replace(local_path) # Set modification time dt = parse_date(date_str) if dt: mtime = dt.timestamp() os.utime(local_path, (mtime, mtime)) except (requests.RequestException, OSError) as e: print(f"Error receiving {uri}: {e}", file=sys.stderr) if temp_path.exists(): try: temp_path.unlink() except OSError: pass except KeyboardInterrupt: print("\nInterrupted by user", file=sys.stderr) sys.exit(1) except Exception as e: print(f"Error: {e}", file=sys.stderr) sys.exit(1) if __name__ == '__main__': main()