55# Copyright (C) 2006-2007, 2011 Lukáš Lalinský
66# Copyright (C) 2011-2013 Michael Wiencek
77# Copyright (C) 2012 Chad Wilson
8- # Copyright (C) 2012-2013, 2018, 2021-2022, 2024 Philipp Wolfer
8+ # Copyright (C) 2012-2013, 2018, 2021-2022, 2024-2025 Philipp Wolfer
99# Copyright (C) 2013, 2018, 2020-2021, 2024 Laurent Monin
1010# Copyright (C) 2016 Suhas
1111# Copyright (C) 2016-2017 Sambhav Kothari
3838)
3939
4040from PyQt6 import QtCore
41+ from PyQt6 .QtGui import QDesktopServices
4142
4243from picard import (
4344 PICARD_APP_NAME ,
45+ PICARD_CUSTOM_PROTOCOL ,
4446 PICARD_ORG_NAME ,
4547 PICARD_VERSION_STR ,
4648 log ,
@@ -86,6 +88,8 @@ class BrowserIntegration(QtCore.QObject):
8688 def __init__ (self , parent = None ):
8789 super ().__init__ (parent )
8890 self .server = None
91+ self ._action_handler = RequestActionHandler ()
92+ QDesktopServices .setUrlHandler (PICARD_CUSTOM_PROTOCOL , self .url_handler )
8993
9094 @property
9195 def host_address (self ):
@@ -129,7 +133,7 @@ def start(self):
129133 except Exception :
130134 log .error ("Failed starting the browser integration on %s" , host_address , exc_info = True )
131135
132- def stop (self ):
136+ def stop (self , stop_url_handler = False ):
133137 if self .server :
134138 try :
135139 log .info ("Stopping the browser integration" )
@@ -142,9 +146,24 @@ def stop(self):
142146 else :
143147 log .debug ("Browser integration inactive, no need to stop" )
144148
149+ if stop_url_handler :
150+ QDesktopServices .unsetUrlHandler (PICARD_CUSTOM_PROTOCOL )
151+
152+ def url_handler (self , url : QtCore .QUrl ):
153+ """URL handler used for custom protocol handling."""
154+ if url .scheme () != PICARD_CUSTOM_PROTOCOL :
155+ log .error ("Invalid URL scheme: %s" , url .scheme ())
156+ return
157+
158+ self ._action_handler .handle_get (url .toString ())
159+
145160
146161class RequestHandler (BaseHTTPRequestHandler ):
147162
163+ def __init__ (self , * args , ** kwargs ):
164+ self ._action_handler = RequestActionHandler (self ._response )
165+ super ().__init__ (* args , ** kwargs )
166+
148167 def do_OPTIONS (self ):
149168 origin = self .headers ['origin' ]
150169 if _is_valid_origin (origin ):
@@ -161,7 +180,7 @@ def do_OPTIONS(self):
161180
162181 def do_GET (self ):
163182 try :
164- self ._handle_get ( )
183+ self ._action_handler . handle_get ( self . path )
165184 except Exception :
166185 log .error ('Browser integration failed handling request' , exc_info = True )
167186 self ._response (500 , 'Unexpected request error' )
@@ -172,10 +191,32 @@ def log_error(self, format, *args):
172191 def log_message (self , format , * args ):
173192 log .info (format , * args )
174193
175- def _handle_get (self ):
176- parsed = urlparse (self .path )
194+ def _response (self , code , content = '' , content_type = 'text/plain' ):
195+ self .server_version = SERVER_VERSION
196+ self .send_response (code )
197+ self .send_header ('Content-Type' , content_type )
198+ self .send_header ('Cache-Control' , 'max-age=0' )
199+ origin = self .headers ['origin' ]
200+ if _is_valid_origin (origin ):
201+ self .send_header ('Access-Control-Allow-Origin' , origin )
202+ self .send_header ('Vary' , 'Origin' )
203+ self .end_headers ()
204+ self .wfile .write (content .encode ())
205+
206+
207+ class RequestActionHandler :
208+ """
209+ Handles URL requests by executing the appropriate action based on URL path.
210+ """
211+ def __init__ (self , response_handler = None ):
212+ self ._response_handler = response_handler
213+
214+ def handle_get (self , path ):
215+ parsed = urlparse (path )
177216 args = parse_qs (parsed .query )
178217 action = parsed .path
218+ if not action .startswith ('/' ):
219+ action = '/' + action
179220
180221 if action == '/' :
181222 self ._response (200 , SERVER_VERSION )
@@ -188,6 +229,7 @@ def _handle_get(self):
188229 elif action == '/auth' :
189230 self ._auth (args )
190231 else :
232+ log .error ('Unknown browser action: %s' , action )
191233 self ._response (404 , 'Unknown action.' )
192234
193235 def _load_mbid (self , type , args ):
@@ -235,13 +277,8 @@ def _auth(self, args):
235277 self ._response (400 , 'Missing parameter "code".' )
236278
237279 def _response (self , code , content = '' , content_type = 'text/plain' ):
238- self .server_version = SERVER_VERSION
239- self .send_response (code )
240- self .send_header ('Content-Type' , content_type )
241- self .send_header ('Cache-Control' , 'max-age=0' )
242- origin = self .headers ['origin' ]
243- if _is_valid_origin (origin ):
244- self .send_header ('Access-Control-Allow-Origin' , origin )
245- self .send_header ('Vary' , 'Origin' )
246- self .end_headers ()
247- self .wfile .write (content .encode ())
280+ if not self ._response_handler :
281+ log .debug (f'Finished custom request with code { code } ' )
282+ return
283+
284+ self ._response_handler (code , content , content_type )
0 commit comments