Files
markdown_editor/server_webdav.py
2025-10-26 10:27:48 +04:00

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()