211 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			211 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python3
 | |
| """
 | |
| WebDAV-based Markdown Editor Server
 | |
| Uses WsgiDAV for standards-compliant file operations
 | |
| """
 | |
| 
 | |
| import os
 | |
| import sys
 | |
| import yaml
 | |
| import json
 | |
| from pathlib import Path
 | |
| from wsgidav.wsgidav_app import WsgiDAVApp
 | |
| from wsgidav.fs_dav_provider import FilesystemProvider
 | |
| from cheroot import wsgi
 | |
| from cheroot.ssl.builtin import BuiltinSSLAdapter
 | |
| 
 | |
| 
 | |
| class MarkdownEditorApp:
 | |
|     """Main application that wraps WsgiDAV and adds custom endpoints"""
 | |
|     
 | |
|     def __init__(self, config_path="config.yaml"):
 | |
|         self.root_path = Path(__file__).parent.resolve()
 | |
|         os.chdir(self.root_path)
 | |
|         self.config = self.load_config(config_path)
 | |
|         self.collections = self.config.get('collections', {})
 | |
|         self.setup_collections()
 | |
|         self.webdav_app = self.create_webdav_app()
 | |
|     
 | |
|     def load_config(self, config_path):
 | |
|         """Load configuration from YAML file"""
 | |
|         with open(config_path, 'r') as f:
 | |
|             return yaml.safe_load(f)
 | |
|     
 | |
|     def setup_collections(self):
 | |
|         """Create collection directories if they don't exist"""
 | |
|         for name, config in self.collections.items():
 | |
|             path = Path(config['path'])
 | |
|             path.mkdir(parents=True, exist_ok=True)
 | |
|             
 | |
|             # Create images subdirectory
 | |
|             images_path = path / 'images'
 | |
|             images_path.mkdir(exist_ok=True)
 | |
|             
 | |
|             print(f"Collection '{name}' -> {path.absolute()}")
 | |
|     
 | |
|     def create_webdav_app(self):
 | |
|         """Create WsgiDAV application with configured collections"""
 | |
|         provider_mapping = {}
 | |
|         
 | |
|         for name, config in self.collections.items():
 | |
|             path = os.path.abspath(config['path'])
 | |
|             provider_mapping[f'/fs/{name}'] = FilesystemProvider(path)
 | |
|         
 | |
|         config = {
 | |
|             'host': self.config['server']['host'],
 | |
|             'port': int(os.environ.get('PORT', self.config['server']['port'])),
 | |
|             'provider_mapping': provider_mapping,
 | |
|             'verbose': self.config['webdav'].get('verbose', 1),
 | |
|             'logging': {
 | |
|                 'enable_loggers': []
 | |
|             },
 | |
|             'property_manager': True,
 | |
|             'lock_storage': True,
 | |
|             'simple_dc': {
 | |
|                 'user_mapping': {
 | |
|                     '*': True  # Allow anonymous access for development
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         return WsgiDAVApp(config)
 | |
|     
 | |
|     def __call__(self, environ, start_response):
 | |
|         """WSGI application entry point"""
 | |
|         path = environ.get('PATH_INFO', '')
 | |
|         method = environ.get('REQUEST_METHOD', '')
 | |
| 
 | |
|         # Root and index.html
 | |
|         if path == '/' or path == '/index.html':
 | |
|             return self.handle_index(environ, start_response)
 | |
|         
 | |
|         # Static files
 | |
|         if path.startswith('/static/'):
 | |
|             return self.handle_static(environ, start_response)
 | |
|         
 | |
|         # Health check
 | |
|         if path == '/health' and method == 'GET':
 | |
|             start_response('200 OK', [('Content-Type', 'text/plain')])
 | |
|             return [b'OK']
 | |
|             
 | |
|         # API for collections
 | |
|         if path == '/fs/' and method == 'GET':
 | |
|             return self.handle_collections_list(environ, start_response)
 | |
| 
 | |
|         # All other /fs/ requests go to WebDAV
 | |
|         if path.startswith('/fs/'):
 | |
|             return self.webdav_app(environ, start_response)
 | |
| 
 | |
|         # Fallback for anything else (shouldn't happen with correct linking)
 | |
|         start_response('404 Not Found', [('Content-Type', 'text/plain')])
 | |
|         return [b'Not Found']
 | |
|     
 | |
|     def handle_collections_list(self, environ, start_response):
 | |
|         """Return list of available collections"""
 | |
|         collections = list(self.collections.keys())
 | |
|         response_body = json.dumps(collections).encode('utf-8')
 | |
|         
 | |
|         start_response('200 OK', [
 | |
|             ('Content-Type', 'application/json'),
 | |
|             ('Content-Length', str(len(response_body))),
 | |
|             ('Access-Control-Allow-Origin', '*')
 | |
|         ])
 | |
|         
 | |
|         return [response_body]
 | |
|     
 | |
|     def handle_static(self, environ, start_response):
 | |
|         """Serve static files"""
 | |
|         path = environ.get('PATH_INFO', '')[1:]  # Remove leading /
 | |
|         file_path = self.root_path / path
 | |
|         
 | |
|         if not file_path.is_file():
 | |
|             start_response('404 Not Found', [('Content-Type', 'text/plain')])
 | |
|             return [b'File not found']
 | |
|         
 | |
|         # Determine content type
 | |
|         content_types = {
 | |
|             '.html': 'text/html',
 | |
|             '.css': 'text/css',
 | |
|             '.js': 'application/javascript',
 | |
|             '.json': 'application/json',
 | |
|             '.png': 'image/png',
 | |
|             '.jpg': 'image/jpeg',
 | |
|             '.jpeg': 'image/jpeg',
 | |
|             '.gif': 'image/gif',
 | |
|             '.svg': 'image/svg+xml',
 | |
|             '.ico': 'image/x-icon'
 | |
|         }
 | |
|         
 | |
|         ext = file_path.suffix.lower()
 | |
|         content_type = content_types.get(ext, 'application/octet-stream')
 | |
|         
 | |
|         with open(file_path, 'rb') as f:
 | |
|             content = f.read()
 | |
|         
 | |
|         start_response('200 OK', [
 | |
|             ('Content-Type', content_type),
 | |
|             ('Content-Length', str(len(content)))
 | |
|         ])
 | |
|         
 | |
|         return [content]
 | |
|     
 | |
|     def handle_index(self, environ, start_response):
 | |
|         """Serve index.html"""
 | |
|         index_path = self.root_path / 'templates' / 'index.html'
 | |
|         
 | |
|         if not index_path.is_file():
 | |
|             start_response('404 Not Found', [('Content-Type', 'text/plain')])
 | |
|             return [b'index.html not found']
 | |
|         
 | |
|         with open(index_path, 'r', encoding='utf-8') as f:
 | |
|             content = f.read().encode('utf-8')
 | |
|         
 | |
|         start_response('200 OK', [
 | |
|             ('Content-Type', 'text/html; charset=utf-8'),
 | |
|             ('Content-Length', str(len(content)))
 | |
|         ])
 | |
|         
 | |
|         return [content]
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     """Start the server"""
 | |
|     print("=" * 60)
 | |
|     print("Markdown Editor with WebDAV Backend")
 | |
|     print("=" * 60)
 | |
|     
 | |
|     # Create application
 | |
|     app = MarkdownEditorApp()
 | |
|     
 | |
|     # Get server config
 | |
|     host = app.config['server']['host']
 | |
|     port = int(os.environ.get('PORT', app.config['server']['port']))
 | |
|     
 | |
|     print(f"\nServer starting on http://{host}:{port}")
 | |
|     print(f"\nAvailable collections:")
 | |
|     for name, config in app.collections.items():
 | |
|         print(f"  - {name}: {config['description']}")
 | |
|         print(f"    WebDAV: http://{host}:{port}/fs/{name}/")
 | |
|     
 | |
|     print(f"\nWeb UI: http://{host}:{port}/")
 | |
|     print("\nPress Ctrl+C to stop the server")
 | |
|     print("=" * 60)
 | |
|     
 | |
|     # Create and start server
 | |
|     server = wsgi.Server(
 | |
|         bind_addr=(host, port),
 | |
|         wsgi_app=app
 | |
|     )
 | |
|     
 | |
|     try:
 | |
|         server.start()
 | |
|         server.wait()
 | |
|     except KeyboardInterrupt:
 | |
|         print("\n\nShutting down...")
 | |
|         server.stop()
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main()
 | |
| 
 |