66import logging
77import subprocess
88import sys
9+ import time
910import traceback
1011from argparse import ArgumentParser
1112from collections .abc import Callable
1819from PySide6 .QtCore import Qt
1920
2021import usdb_syncer
21- from usdb_syncer import addons , db , errors , logger , settings , song_routines , utils
22+ from usdb_syncer import (
23+ addons ,
24+ db ,
25+ errors ,
26+ logger ,
27+ settings ,
28+ song_routines ,
29+ utils ,
30+ webserver ,
31+ )
2232from usdb_syncer import sync_meta as sync_meta
2333from usdb_syncer import usdb_song as usdb_song
2434from usdb_syncer .gui import events , hooks , theme
@@ -40,6 +50,7 @@ class CliArgs:
4050 """Command line arguments."""
4151
4252 reset_settings : bool = False
53+ subcommand : str = ""
4354
4455 # Settings
4556 songpath : Path | None = None
@@ -49,9 +60,14 @@ class CliArgs:
4960 skip_pyside : bool = not utils .IS_SOURCE
5061 trace_sql : bool = False
5162
52- # Subcommands
63+ # preview
5364 txt : Path | None = None
5465
66+ # webserver
67+ host : str | None = None
68+ port : int | None = None
69+ title : str | None = None
70+
5571 @classmethod
5672 def parse (cls ) -> CliArgs :
5773 parser = ArgumentParser (description = "USDB Syncer" )
@@ -86,11 +102,28 @@ def parse(cls) -> CliArgs:
86102 )
87103
88104 subcommands = parser .add_subparsers (
89- title = "subcommands" , description = "Subcommands."
105+ title = "subcommands" , description = "Subcommands." , dest = "subcommand"
90106 )
91107 preview = subcommands .add_parser ("preview" , help = "Show preview for song txt." )
92108 preview .add_argument ("txt" , type = Path , help = "Path to the song txt file." )
93109
110+ serve = subcommands .add_parser (
111+ "serve" , help = "Launch webserver with local songs."
112+ )
113+ serve .add_argument (
114+ "--host" ,
115+ type = int ,
116+ help = "Host for the webservice. Default is the device's public IP address. "
117+ "Use 127.0.0.1 (localhost) to not be accessible by other devies "
118+ "on the local network." ,
119+ )
120+ serve .add_argument (
121+ "--port" ,
122+ type = int ,
123+ help = "Port the webservice will bind to. Defaults to a random free port." ,
124+ )
125+ serve .add_argument ("--title" , help = "Title displayed at the top of the page." )
126+
94127 return parser .parse_args (namespace = cls ())
95128
96129 def apply (self ) -> None :
@@ -113,26 +146,36 @@ def main() -> None:
113146 utils .AppPaths .make_dirs ()
114147 app = _init_app ()
115148 app .setAttribute (Qt .ApplicationAttribute .AA_DontShowIconsInMenus , False )
116- if args .txt :
117- if not _run_preview (args .txt ):
149+ match args .subcommand :
150+ case "preview" :
151+ if not args .txt or not _run_preview (args .txt ):
152+ return
153+ case "serve" :
154+ _run_webserver (host = args .host , port = args .port , title = args .title )
118155 return
119- else :
120- if args .profile :
121- _with_profile (_run_main )
122- else :
123- _run_main ()
156+ case _ :
157+ if args .profile :
158+ _with_profile (_run_main )
159+ else :
160+ _run_main ()
124161 app .exec ()
125162
126163
164+ def configure_logging (mw : MainWindow | None = None ) -> None :
165+ handlers : list [logging .Handler ] = [
166+ logging .FileHandler (utils .AppPaths .log , encoding = "utf-8" ),
167+ logging .StreamHandler (sys .stdout ),
168+ ]
169+ if mw :
170+ handlers .append (_TextEditLogger (mw ))
171+ logger .configure_logging (* handlers )
172+
173+
127174def _run_main () -> None :
128175 from usdb_syncer .gui .mw import MainWindow
129176
130177 mw = MainWindow ()
131- logger .configure_logging (
132- logging .FileHandler (utils .AppPaths .log , encoding = "utf-8" ),
133- logging .StreamHandler (sys .stdout ),
134- _TextEditLogger (mw ),
135- )
178+ configure_logging (mw )
136179 mw .label_update_hint .setVisible (False )
137180 if not utils .IS_SOURCE :
138181 if version := utils .newer_version_available ():
@@ -153,12 +196,26 @@ def _run_main() -> None:
153196
154197
155198def _run_preview (txt : Path ) -> bool :
199+ configure_logging ()
156200 from usdb_syncer .gui .previewer import Previewer
157201
158202 theme .Theme .from_settings ().apply ()
159203 return Previewer .load_txt (txt )
160204
161205
206+ def _run_webserver (
207+ host : str | None = None , port : int | None = None , title : str | None = None
208+ ) -> None :
209+ configure_logging ()
210+ webserver .start (host = host , port = port , title = title )
211+ logger .logger .info ("Webserver is running in headless mode. Press Ctrl+C to stop." )
212+ try :
213+ while True :
214+ time .sleep (1 )
215+ except KeyboardInterrupt :
216+ webserver .stop ()
217+
218+
162219def _excepthook (
163220 error_type : type [BaseException ], error : BaseException , tb_type : TracebackType | None
164221) -> Any :
0 commit comments