16 sys
.path
.insert(0, os
.path
.dirname(os
.path
.dirname((os
.path
.abspath(__file__
)))))
17 from yt_dlp
.compat
import (
24 # These are not used outside of buildserver.py thus not in compat.py
27 import winreg
as compat_winreg
28 except ImportError: # Python 2
29 import _winreg
as compat_winreg
32 import socketserver
as compat_socketserver
33 except ImportError: # Python 2
34 import SocketServer
as compat_socketserver
37 class BuildHTTPServer(compat_socketserver
.ThreadingMixIn
, compat_http_server
.HTTPServer
):
38 allow_reuse_address
= True
41 advapi32
= ctypes
.windll
.advapi32
43 SC_MANAGER_ALL_ACCESS
= 0xf003f
44 SC_MANAGER_CREATE_SERVICE
= 0x02
45 SERVICE_WIN32_OWN_PROCESS
= 0x10
46 SERVICE_AUTO_START
= 0x2
47 SERVICE_ERROR_NORMAL
= 0x1
49 SERVICE_STATUS_START_PENDING
= 0x00000002
50 SERVICE_STATUS_RUNNING
= 0x00000004
51 SERVICE_ACCEPT_STOP
= 0x1
53 SVCNAME
= 'youtubedl_builder'
55 LPTSTR
= ctypes
.c_wchar_p
56 START_CALLBACK
= ctypes
.WINFUNCTYPE(None, ctypes
.c_int
, ctypes
.POINTER(LPTSTR
))
59 class SERVICE_TABLE_ENTRY(ctypes
.Structure
):
61 ('lpServiceName', LPTSTR
),
62 ('lpServiceProc', START_CALLBACK
)
66 HandlerEx
= ctypes
.WINFUNCTYPE(
67 ctypes
.c_int
, # return
68 ctypes
.c_int
, # dwControl
69 ctypes
.c_int
, # dwEventType
70 ctypes
.c_void_p
, # lpEventData,
71 ctypes
.c_void_p
, # lpContext,
75 def _ctypes_array(c_type
, py_array
):
76 ar
= (c_type
* len(py_array
))()
81 def win_OpenSCManager():
82 res
= advapi32
.OpenSCManagerW(None, None, SC_MANAGER_ALL_ACCESS
)
84 raise Exception('Opening service manager failed - '
85 'are you running this as administrator?')
89 def win_install_service(service_name
, cmdline
):
90 manager
= win_OpenSCManager()
92 h
= advapi32
.CreateServiceW(
93 manager
, service_name
, None,
94 SC_MANAGER_CREATE_SERVICE
, SERVICE_WIN32_OWN_PROCESS
,
95 SERVICE_AUTO_START
, SERVICE_ERROR_NORMAL
,
96 cmdline
, None, None, None, None, None)
98 raise OSError('Service creation failed: %s' % ctypes
.FormatError())
100 advapi32
.CloseServiceHandle(h
)
102 advapi32
.CloseServiceHandle(manager
)
105 def win_uninstall_service(service_name
):
106 manager
= win_OpenSCManager()
108 h
= advapi32
.OpenServiceW(manager
, service_name
, DELETE
)
110 raise OSError('Could not find service %s: %s' % (
111 service_name
, ctypes
.FormatError()))
114 if not advapi32
.DeleteService(h
):
115 raise OSError('Deletion failed: %s' % ctypes
.FormatError())
117 advapi32
.CloseServiceHandle(h
)
119 advapi32
.CloseServiceHandle(manager
)
122 def win_service_report_event(service_name
, msg
, is_error
=True):
123 with open('C:/sshkeys/log', 'a', encoding
='utf-8') as f
:
126 event_log
= advapi32
.RegisterEventSourceW(None, service_name
)
128 raise OSError('Could not report event: %s' % ctypes
.FormatError())
131 type_id
= 0x0001 if is_error
else 0x0004
132 event_id
= 0xc0000000 if is_error
else 0x40000000
133 lines
= _ctypes_array(LPTSTR
, [msg
])
135 if not advapi32
.ReportEventW(
136 event_log
, type_id
, 0, event_id
, None, len(lines
), 0,
138 raise OSError('Event reporting failed: %s' % ctypes
.FormatError())
140 advapi32
.DeregisterEventSource(event_log
)
143 def win_service_handler(stop_event
, *args
):
145 raise ValueError('Handler called with args ' + repr(args
))
147 except Exception as e
:
148 tb
= traceback
.format_exc()
149 msg
= str(e
) + '\n' + tb
150 win_service_report_event(service_name
, msg
, is_error
=True)
154 def win_service_set_status(handle
, status_code
):
155 svcStatus
= SERVICE_STATUS()
156 svcStatus
.dwServiceType
= SERVICE_WIN32_OWN_PROCESS
157 svcStatus
.dwCurrentState
= status_code
158 svcStatus
.dwControlsAccepted
= SERVICE_ACCEPT_STOP
160 svcStatus
.dwServiceSpecificExitCode
= 0
162 if not advapi32
.SetServiceStatus(handle
, ctypes
.byref(svcStatus
)):
163 raise OSError('SetServiceStatus failed: %r' % ctypes
.FormatError())
166 def win_service_main(service_name
, real_main
, argc
, argv_raw
):
168 # args = [argv_raw[i].value for i in range(argc)]
169 stop_event
= threading
.Event()
170 handler
= HandlerEx(functools
.partial(stop_event
, win_service_handler
))
171 h
= advapi32
.RegisterServiceCtrlHandlerExW(service_name
, handler
, None)
173 raise OSError('Handler registration failed: %s' %
174 ctypes
.FormatError())
177 except Exception as e
:
178 tb
= traceback
.format_exc()
179 msg
= str(e
) + '\n' + tb
180 win_service_report_event(service_name
, msg
, is_error
=True)
184 def win_service_start(service_name
, real_main
):
187 functools
.partial(win_service_main
, service_name
, real_main
))
188 dispatch_table
= _ctypes_array(SERVICE_TABLE_ENTRY
, [
193 SERVICE_TABLE_ENTRY(None, ctypes
.cast(None, START_CALLBACK
))
196 if not advapi32
.StartServiceCtrlDispatcherW(dispatch_table
):
197 raise OSError('ctypes start failed: %s' % ctypes
.FormatError())
198 except Exception as e
:
199 tb
= traceback
.format_exc()
200 msg
= str(e
) + '\n' + tb
201 win_service_report_event(service_name
, msg
, is_error
=True)
206 parser
= argparse
.ArgumentParser()
207 parser
.add_argument('-i', '--install',
208 action
='store_const', dest
='action', const
='install',
209 help='Launch at Windows startup')
210 parser
.add_argument('-u', '--uninstall',
211 action
='store_const', dest
='action', const
='uninstall',
212 help='Remove Windows service')
213 parser
.add_argument('-s', '--service',
214 action
='store_const', dest
='action', const
='service',
215 help='Run as a Windows service')
216 parser
.add_argument('-b', '--bind', metavar
='<host:port>',
217 action
='store', default
='0.0.0.0:8142',
218 help='Bind to host:port (default %default)')
219 options
= parser
.parse_args(args
=args
)
221 if options
.action
== 'install':
222 fn
= os
.path
.abspath(__file__
).replace('v:', '\\\\vboxsrv\\vbox')
223 cmdline
= '%s %s -s -b %s' % (sys
.executable
, fn
, options
.bind
)
224 win_install_service(SVCNAME
, cmdline
)
227 if options
.action
== 'uninstall':
228 win_uninstall_service(SVCNAME
)
231 if options
.action
== 'service':
232 win_service_start(SVCNAME
, main
)
235 host
, port_str
= options
.bind
.split(':')
238 print('Listening on %s:%d' % (host
, port
))
239 srv
= BuildHTTPServer((host
, port
), BuildHTTPRequestHandler
)
240 thr
= threading
.Thread(target
=srv
.serve_forever
)
242 compat_input('Press ENTER to shut down')
248 for name
in os
.listdir(path
):
249 fname
= os
.path
.join(path
, name
)
250 if os
.path
.isdir(fname
):
253 os
.chmod(fname
, 0o666)
258 class BuildError(Exception):
259 def __init__(self
, output
, code
=500):
267 class HTTPError(BuildError
):
271 class PythonBuilder(object):
272 def __init__(self
, **kwargs
):
273 python_version
= kwargs
.pop('python', '3.4')
275 for node
in ('Wow6432Node\\', ''):
277 key
= compat_winreg
.OpenKey(
278 compat_winreg
.HKEY_LOCAL_MACHINE
,
279 r
'SOFTWARE\%sPython\PythonCore\%s\InstallPath' % (node
, python_version
))
281 python_path
, _
= compat_winreg
.QueryValueEx(key
, '')
283 compat_winreg
.CloseKey(key
)
289 raise BuildError('No such Python version: %s' % python_version
)
291 self
.pythonPath
= python_path
293 super(PythonBuilder
, self
).__init
__(**kwargs
)
296 class GITInfoBuilder(object):
297 def __init__(self
, **kwargs
):
299 self
.user
, self
.repoName
= kwargs
['path'][:2]
300 self
.rev
= kwargs
.pop('rev')
302 raise BuildError('Invalid path')
303 except KeyError as e
:
304 raise BuildError('Missing mandatory parameter "%s"' % e
.args
[0])
306 path
= os
.path
.join(os
.environ
['APPDATA'], 'Build archive', self
.repoName
, self
.user
)
307 if not os
.path
.exists(path
):
309 self
.basePath
= tempfile
.mkdtemp(dir=path
)
310 self
.buildPath
= os
.path
.join(self
.basePath
, 'build')
312 super(GITInfoBuilder
, self
).__init
__(**kwargs
)
315 class GITBuilder(GITInfoBuilder
):
318 subprocess
.check_output(['git', 'clone', 'git://github.com/%s/%s.git' % (self
.user
, self
.repoName
), self
.buildPath
])
319 subprocess
.check_output(['git', 'checkout', self
.rev
], cwd
=self
.buildPath
)
320 except subprocess
.CalledProcessError
as e
:
321 raise BuildError(e
.output
)
323 super(GITBuilder
, self
).build()
326 class YoutubeDLBuilder(object):
327 authorizedUsers
= ['fraca7', 'phihag', 'rg3', 'FiloSottile', 'ytdl-org']
329 def __init__(self
, **kwargs
):
330 if self
.repoName
!= 'yt-dlp':
331 raise BuildError('Invalid repository "%s"' % self
.repoName
)
332 if self
.user
not in self
.authorizedUsers
:
333 raise HTTPError('Unauthorized user "%s"' % self
.user
, 401)
335 super(YoutubeDLBuilder
, self
).__init
__(**kwargs
)
339 proc
= subprocess
.Popen([os
.path
.join(self
.pythonPath
, 'python.exe'), 'setup.py', 'py2exe'], stdin
=subprocess
.PIPE
, cwd
=self
.buildPath
)
341 #subprocess.check_output([os.path.join(self.pythonPath, 'python.exe'), 'setup.py', 'py2exe'],
342 # cwd=self.buildPath)
343 except subprocess
.CalledProcessError
as e
:
344 raise BuildError(e
.output
)
346 super(YoutubeDLBuilder
, self
).build()
349 class DownloadBuilder(object):
350 def __init__(self
, **kwargs
):
351 self
.handler
= kwargs
.pop('handler')
352 self
.srcPath
= os
.path
.join(self
.buildPath
, *tuple(kwargs
['path'][2:]))
353 self
.srcPath
= os
.path
.abspath(os
.path
.normpath(self
.srcPath
))
354 if not self
.srcPath
.startswith(self
.buildPath
):
355 raise HTTPError(self
.srcPath
, 401)
357 super(DownloadBuilder
, self
).__init
__(**kwargs
)
360 if not os
.path
.exists(self
.srcPath
):
361 raise HTTPError('No such file', 404)
362 if os
.path
.isdir(self
.srcPath
):
363 raise HTTPError('Is a directory: %s' % self
.srcPath
, 401)
365 self
.handler
.send_response(200)
366 self
.handler
.send_header('Content-Type', 'application/octet-stream')
367 self
.handler
.send_header('Content-Disposition', 'attachment; filename=%s' % os
.path
.split(self
.srcPath
)[-1])
368 self
.handler
.send_header('Content-Length', str(os
.stat(self
.srcPath
).st_size
))
369 self
.handler
.end_headers()
371 with open(self
.srcPath
, 'rb') as src
:
372 shutil
.copyfileobj(src
, self
.handler
.wfile
)
374 super(DownloadBuilder
, self
).build()
377 class CleanupTempDir(object):
380 rmtree(self
.basePath
)
381 except Exception as e
:
382 print('WARNING deleting "%s": %s' % (self
.basePath
, e
))
384 super(CleanupTempDir
, self
).build()
388 def __init__(self
, **kwargs
):
401 class Builder(PythonBuilder
, GITBuilder
, YoutubeDLBuilder
, DownloadBuilder
, CleanupTempDir
, Null
):
405 class BuildHTTPRequestHandler(compat_http_server
.BaseHTTPRequestHandler
):
406 actionDict
= {'build': Builder, 'download': Builder}
# They're the same, no more caching.
409 path
= compat_urlparse
.urlparse(self
.path
)
410 paramDict
= dict([(key
, value
[0]) for key
, value
in compat_urlparse
.parse_qs(path
.query
).items()])
411 action
, _
, path
= path
.path
.strip('/').partition('/')
413 path
= path
.split('/')
414 if action
in self
.actionDict
:
416 builder
= self
.actionDict
[action
](path
=path
, handler
=self
, **paramDict
)
422 except BuildError
as e
:
423 self
.send_response(e
.code
)
424 msg
= compat_str(e
).encode('UTF-8')
425 self
.send_header('Content-Type', 'text/plain; charset=UTF-8')
426 self
.send_header('Content-Length', len(msg
))
428 self
.wfile
.write(msg
)
430 self
.send_response(500, 'Unknown build method "%s"' % action
)
432 self
.send_response(500, 'Malformed URL')
434 if __name__
== '__main__':