3 from http
.server
import HTTPServer
, BaseHTTPRequestHandler
4 from socketserver
import ThreadingMixIn
14 class BuildHTTPServer(ThreadingMixIn
, HTTPServer
):
15 allow_reuse_address
= True
18 advapi32
= ctypes
.windll
.advapi32
20 SC_MANAGER_ALL_ACCESS
= 0xf003f
21 SC_MANAGER_CREATE_SERVICE
= 0x02
22 SERVICE_WIN32_OWN_PROCESS
= 0x10
23 SERVICE_AUTO_START
= 0x2
24 SERVICE_ERROR_NORMAL
= 0x1
26 SERVICE_STATUS_START_PENDING
= 0x00000002
27 SERVICE_STATUS_RUNNING
= 0x00000004
28 SERVICE_ACCEPT_STOP
= 0x1
30 SVCNAME
= 'youtubedl_builder'
32 LPTSTR
= ctypes
.c_wchar_p
33 START_CALLBACK
= ctypes
.WINFUNCTYPE(None, ctypes
.c_int
, ctypes
.POINTER(LPTSTR
))
36 class SERVICE_TABLE_ENTRY(ctypes
.Structure
):
38 ('lpServiceName', LPTSTR
),
39 ('lpServiceProc', START_CALLBACK
)
43 HandlerEx
= ctypes
.WINFUNCTYPE(
44 ctypes
.c_int
, # return
45 ctypes
.c_int
, # dwControl
46 ctypes
.c_int
, # dwEventType
47 ctypes
.c_void_p
, # lpEventData,
48 ctypes
.c_void_p
, # lpContext,
52 def _ctypes_array(c_type
, py_array
):
53 ar
= (c_type
* len(py_array
))()
58 def win_OpenSCManager():
59 res
= advapi32
.OpenSCManagerW(None, None, SC_MANAGER_ALL_ACCESS
)
61 raise Exception('Opening service manager failed - '
62 'are you running this as administrator?')
66 def win_install_service(service_name
, cmdline
):
67 manager
= win_OpenSCManager()
69 h
= advapi32
.CreateServiceW(
70 manager
, service_name
, None,
71 SC_MANAGER_CREATE_SERVICE
, SERVICE_WIN32_OWN_PROCESS
,
72 SERVICE_AUTO_START
, SERVICE_ERROR_NORMAL
,
73 cmdline
, None, None, None, None, None)
75 raise OSError('Service creation failed: %s' % ctypes
.FormatError())
77 advapi32
.CloseServiceHandle(h
)
79 advapi32
.CloseServiceHandle(manager
)
82 def win_uninstall_service(service_name
):
83 manager
= win_OpenSCManager()
85 h
= advapi32
.OpenServiceW(manager
, service_name
, DELETE
)
87 raise OSError('Could not find service %s: %s' % (
88 service_name
, ctypes
.FormatError()))
91 if not advapi32
.DeleteService(h
):
92 raise OSError('Deletion failed: %s' % ctypes
.FormatError())
94 advapi32
.CloseServiceHandle(h
)
96 advapi32
.CloseServiceHandle(manager
)
99 def win_service_report_event(service_name
, msg
, is_error
=True):
100 with open('C:/sshkeys/log', 'a', encoding
='utf-8') as f
:
103 event_log
= advapi32
.RegisterEventSourceW(None, service_name
)
105 raise OSError('Could not report event: %s' % ctypes
.FormatError())
108 type_id
= 0x0001 if is_error
else 0x0004
109 event_id
= 0xc0000000 if is_error
else 0x40000000
110 lines
= _ctypes_array(LPTSTR
, [msg
])
112 if not advapi32
.ReportEventW(
113 event_log
, type_id
, 0, event_id
, None, len(lines
), 0,
115 raise OSError('Event reporting failed: %s' % ctypes
.FormatError())
117 advapi32
.DeregisterEventSource(event_log
)
120 def win_service_handler(stop_event
, *args
):
122 raise ValueError('Handler called with args ' + repr(args
))
124 except Exception as e
:
125 tb
= traceback
.format_exc()
126 msg
= str(e
) + '\n' + tb
127 win_service_report_event(service_name
, msg
, is_error
=True)
131 def win_service_set_status(handle
, status_code
):
132 svcStatus
= SERVICE_STATUS()
133 svcStatus
.dwServiceType
= SERVICE_WIN32_OWN_PROCESS
134 svcStatus
.dwCurrentState
= status_code
135 svcStatus
.dwControlsAccepted
= SERVICE_ACCEPT_STOP
137 svcStatus
.dwServiceSpecificExitCode
= 0
139 if not advapi32
.SetServiceStatus(handle
, ctypes
.byref(svcStatus
)):
140 raise OSError('SetServiceStatus failed: %r' % ctypes
.FormatError())
143 def win_service_main(service_name
, real_main
, argc
, argv_raw
):
145 # args = [argv_raw[i].value for i in range(argc)]
146 stop_event
= threading
.Event()
147 handler
= HandlerEx(functools
.partial(stop_event
, win_service_handler
))
148 h
= advapi32
.RegisterServiceCtrlHandlerExW(service_name
, handler
, None)
150 raise OSError('Handler registration failed: %s' %
151 ctypes
.FormatError())
154 except Exception as e
:
155 tb
= traceback
.format_exc()
156 msg
= str(e
) + '\n' + tb
157 win_service_report_event(service_name
, msg
, is_error
=True)
161 def win_service_start(service_name
, real_main
):
164 functools
.partial(win_service_main
, service_name
, real_main
))
165 dispatch_table
= _ctypes_array(SERVICE_TABLE_ENTRY
, [
170 SERVICE_TABLE_ENTRY(None, ctypes
.cast(None, START_CALLBACK
))
173 if not advapi32
.StartServiceCtrlDispatcherW(dispatch_table
):
174 raise OSError('ctypes start failed: %s' % ctypes
.FormatError())
175 except Exception as e
:
176 tb
= traceback
.format_exc()
177 msg
= str(e
) + '\n' + tb
178 win_service_report_event(service_name
, msg
, is_error
=True)
183 parser
= argparse
.ArgumentParser()
184 parser
.add_argument('-i', '--install',
185 action
='store_const', dest
='action', const
='install',
186 help='Launch at Windows startup')
187 parser
.add_argument('-u', '--uninstall',
188 action
='store_const', dest
='action', const
='uninstall',
189 help='Remove Windows service')
190 parser
.add_argument('-s', '--service',
191 action
='store_const', dest
='action', const
='service',
192 help='Run as a Windows service')
193 parser
.add_argument('-b', '--bind', metavar
='<host:port>',
194 action
='store', default
='localhost:8142',
195 help='Bind to host:port (default %default)')
196 options
= parser
.parse_args(args
=args
)
198 if options
.action
== 'install':
199 fn
= os
.path
.abspath(__file__
).replace('v:', '\\\\vboxsrv\\vbox')
200 cmdline
= '%s %s -s -b %s' % (sys
.executable
, fn
, options
.bind
)
201 win_install_service(SVCNAME
, cmdline
)
204 if options
.action
== 'uninstall':
205 win_uninstall_service(SVCNAME
)
208 if options
.action
== 'service':
209 win_service_start(SVCNAME
, main
)
212 host
, port_str
= options
.bind
.split(':')
215 print('Listening on %s:%d' % (host
, port
))
216 srv
= BuildHTTPServer((host
, port
), BuildHTTPRequestHandler
)
217 thr
= threading
.Thread(target
=srv
.serve_forever
)
219 input('Press ENTER to shut down')
225 for name
in os
.listdir(path
):
226 fname
= os
.path
.join(path
, name
)
227 if os
.path
.isdir(fname
):
230 os
.chmod(fname
, 0o666)
234 #==============================================================================
237 class BuildError(Exception):
238 def __init__(self
, output
, code
=500):
246 class HTTPError(BuildError
):
250 class PythonBuilder(object):
251 def __init__(self
, **kwargs
):
252 pythonVersion
= kwargs
.pop('python', '2.7')
254 key
= _winreg
.OpenKey(_winreg
.HKEY_LOCAL_MACHINE
, r
'SOFTWARE\Python\PythonCore\%s\InstallPath' % pythonVersion
)
256 self
.pythonPath
, _
= _winreg
.QueryValueEx(key
, '')
258 _winreg
.CloseKey(key
)
260 raise BuildError('No such Python version: %s' % pythonVersion
)
262 super(PythonBuilder
, self
).__init
__(**kwargs
)
265 class GITInfoBuilder(object):
266 def __init__(self
, **kwargs
):
268 self
.user
, self
.repoName
= kwargs
['path'][:2]
269 self
.rev
= kwargs
.pop('rev')
271 raise BuildError('Invalid path')
272 except KeyError as e
:
273 raise BuildError('Missing mandatory parameter "%s"' % e
.args
[0])
275 path
= os
.path
.join(os
.environ
['APPDATA'], 'Build archive', self
.repoName
, self
.user
)
276 if not os
.path
.exists(path
):
278 self
.basePath
= tempfile
.mkdtemp(dir=path
)
279 self
.buildPath
= os
.path
.join(self
.basePath
, 'build')
281 super(GITInfoBuilder
, self
).__init
__(**kwargs
)
284 class GITBuilder(GITInfoBuilder
):
287 subprocess
.check_output(['git', 'clone', 'git://github.com/%s/%s.git' % (self
.user
, self
.repoName
), self
.buildPath
])
288 subprocess
.check_output(['git', 'checkout', self
.rev
], cwd
=self
.buildPath
)
289 except subprocess
.CalledProcessError
as e
:
290 raise BuildError(e
.output
)
292 super(GITBuilder
, self
).build()
295 class YoutubeDLBuilder(object):
296 authorizedUsers
= ['fraca7', 'phihag', 'rg3', 'FiloSottile']
298 def __init__(self
, **kwargs
):
299 if self
.repoName
!= 'youtube-dl':
300 raise BuildError('Invalid repository "%s"' % self
.repoName
)
301 if self
.user
not in self
.authorizedUsers
:
302 raise HTTPError('Unauthorized user "%s"' % self
.user
, 401)
304 super(YoutubeDLBuilder
, self
).__init
__(**kwargs
)
308 subprocess
.check_output([os
.path
.join(self
.pythonPath
, 'python.exe'), 'setup.py', 'py2exe'],
310 except subprocess
.CalledProcessError
as e
:
311 raise BuildError(e
.output
)
313 super(YoutubeDLBuilder
, self
).build()
316 class DownloadBuilder(object):
317 def __init__(self
, **kwargs
):
318 self
.handler
= kwargs
.pop('handler')
319 self
.srcPath
= os
.path
.join(self
.buildPath
, *tuple(kwargs
['path'][2:]))
320 self
.srcPath
= os
.path
.abspath(os
.path
.normpath(self
.srcPath
))
321 if not self
.srcPath
.startswith(self
.buildPath
):
322 raise HTTPError(self
.srcPath
, 401)
324 super(DownloadBuilder
, self
).__init
__(**kwargs
)
327 if not os
.path
.exists(self
.srcPath
):
328 raise HTTPError('No such file', 404)
329 if os
.path
.isdir(self
.srcPath
):
330 raise HTTPError('Is a directory: %s' % self
.srcPath
, 401)
332 self
.handler
.send_response(200)
333 self
.handler
.send_header('Content-Type', 'application/octet-stream')
334 self
.handler
.send_header('Content-Disposition', 'attachment; filename=%s' % os
.path
.split(self
.srcPath
)[-1])
335 self
.handler
.send_header('Content-Length', str(os
.stat(self
.srcPath
).st_size
))
336 self
.handler
.end_headers()
338 with open(self
.srcPath
, 'rb') as src
:
339 shutil
.copyfileobj(src
, self
.handler
.wfile
)
341 super(DownloadBuilder
, self
).build()
344 class CleanupTempDir(object):
347 rmtree(self
.basePath
)
348 except Exception as e
:
349 print('WARNING deleting "%s": %s' % (self
.basePath
, e
))
351 super(CleanupTempDir
, self
).build()
355 def __init__(self
, **kwargs
):
368 class Builder(PythonBuilder
, GITBuilder
, YoutubeDLBuilder
, DownloadBuilder
, CleanupTempDir
, Null
):
372 class BuildHTTPRequestHandler(BaseHTTPRequestHandler
):
373 actionDict
= {'build': Builder
, 'download': Builder
} # They're the same, no more caching.
376 path
= urlparse
.urlparse(self
.path
)
377 paramDict
= dict([(key
, value
[0]) for key
, value
in urlparse
.parse_qs(path
.query
).items()])
378 action
, _
, path
= path
.path
.strip('/').partition('/')
380 path
= path
.split('/')
381 if action
in self
.actionDict
:
383 builder
= self
.actionDict
[action
](path
=path
, handler
=self
, **paramDict
)
389 except BuildError
as e
:
390 self
.send_response(e
.code
)
391 msg
= unicode(e
).encode('UTF-8')
392 self
.send_header('Content-Type', 'text/plain; charset=UTF-8')
393 self
.send_header('Content-Length', len(msg
))
395 self
.wfile
.write(msg
)
396 except HTTPError
as e
:
397 self
.send_response(e
.code
, str(e
))
399 self
.send_response(500, 'Unknown build method "%s"' % action
)
401 self
.send_response(500, 'Malformed URL')
403 #==============================================================================
405 if __name__
== '__main__':