3 from http
.server
import HTTPServer
, BaseHTTPRequestHandler
4 from socketserver
import ThreadingMixIn
12 class BuildHTTPServer(ThreadingMixIn
, HTTPServer
):
13 allow_reuse_address
= True
16 advapi32
= ctypes
.windll
.advapi32
18 SC_MANAGER_ALL_ACCESS
= 0xf003f
19 SC_MANAGER_CREATE_SERVICE
= 0x02
20 SERVICE_WIN32_OWN_PROCESS
= 0x10
21 SERVICE_AUTO_START
= 0x2
22 SERVICE_ERROR_NORMAL
= 0x1
26 def win_OpenSCManager():
27 res
= advapi32
.OpenSCManagerA(None, None, SC_MANAGER_ALL_ACCESS
)
29 raise Exception('Opening service manager failed - '
30 'are you running this as administrator?')
34 def win_install_service(service_name
, cmdline
):
35 manager
= win_OpenSCManager()
37 h
= advapi32
.CreateServiceA(
38 manager
, service_name
, None,
39 SC_MANAGER_CREATE_SERVICE
, SERVICE_WIN32_OWN_PROCESS
,
40 SERVICE_AUTO_START
, SERVICE_ERROR_NORMAL
,
41 cmdline
, None, None, None, None, None)
43 raise OSError('Service creation failed: %s' % ctypes
.FormatError())
45 advapi32
.CloseServiceHandle(h
)
47 advapi32
.CloseServiceHandle(manager
)
50 def win_uninstall_service(service_name
):
51 manager
= win_OpenSCManager()
53 h
= advapi32
.OpenServiceA(manager
, service_name
, DELETE
)
55 raise OSError('Could not find service %s: %s' % (
56 service_name
, ctypes
.FormatError()))
59 if not advapi32
.DeleteService(h
):
60 raise OSError('Deletion failed: %s' % ctypes
.FormatError())
62 advapi32
.CloseServiceHandle(h
)
64 advapi32
.CloseServiceHandle(manager
)
67 def install_service(bind
):
68 fn
= os
.path
.normpath(__file__
)
69 cmdline
= '"%s" "%s" -s -b "%s"' % (sys
.executable
, fn
, bind
)
70 win_install_service('youtubedl_builder', cmdline
)
73 def uninstall_service():
74 win_uninstall_service('youtubedl_builder')
78 parser
= argparse
.ArgumentParser()
79 parser
.add_argument('-i', '--install',
80 action
='store_const', dest
='action', const
='install',
81 help='Launch at Windows startup')
82 parser
.add_argument('-u', '--uninstall',
83 action
='store_const', dest
='action', const
='uninstall',
84 help='Remove Windows service')
85 parser
.add_argument('-s', '--service',
86 action
='store_const', dest
='action', const
='servce',
87 help='Run as a Windows service')
88 parser
.add_argument('-b', '--bind', metavar
='<host:port>',
89 action
='store', default
='localhost:8142',
90 help='Bind to host:port (default %default)')
91 options
= parser
.parse_args()
93 if options
.action
== 'install':
94 return install_service(options
.bind
)
96 if options
.action
== 'uninstall':
97 return uninstall_service()
99 host
, port_str
= options
.bind
.split(':')
102 print('Listening on %s:%d' % (host
, port
))
103 srv
= BuildHTTPServer((host
, port
), BuildHTTPRequestHandler
)
104 thr
= threading
.Thread(target
=srv
.serve_forever
)
106 input('Press ENTER to shut down')
112 for name
in os
.listdir(path
):
113 fname
= os
.path
.join(path
, name
)
114 if os
.path
.isdir(fname
):
117 os
.chmod(fname
, 0o666)
121 #==============================================================================
123 class BuildError(Exception):
124 def __init__(self
, output
, code
=500):
132 class HTTPError(BuildError
):
136 class PythonBuilder(object):
137 def __init__(self
, **kwargs
):
138 pythonVersion
= kwargs
.pop('python', '2.7')
140 key
= _winreg
.OpenKey(_winreg
.HKEY_LOCAL_MACHINE
, r
'SOFTWARE\Python\PythonCore\%s\InstallPath' % pythonVersion
)
142 self
.pythonPath
, _
= _winreg
.QueryValueEx(key
, '')
144 _winreg
.CloseKey(key
)
146 raise BuildError('No such Python version: %s' % pythonVersion
)
148 super(PythonBuilder
, self
).__init
__(**kwargs
)
151 class GITInfoBuilder(object):
152 def __init__(self
, **kwargs
):
154 self
.user
, self
.repoName
= kwargs
['path'][:2]
155 self
.rev
= kwargs
.pop('rev')
157 raise BuildError('Invalid path')
158 except KeyError as e
:
159 raise BuildError('Missing mandatory parameter "%s"' % e
.args
[0])
161 path
= os
.path
.join(os
.environ
['APPDATA'], 'Build archive', self
.repoName
, self
.user
)
162 if not os
.path
.exists(path
):
164 self
.basePath
= tempfile
.mkdtemp(dir=path
)
165 self
.buildPath
= os
.path
.join(self
.basePath
, 'build')
167 super(GITInfoBuilder
, self
).__init
__(**kwargs
)
170 class GITBuilder(GITInfoBuilder
):
173 subprocess
.check_output(['git', 'clone', 'git://github.com/%s/%s.git' % (self
.user
, self
.repoName
), self
.buildPath
])
174 subprocess
.check_output(['git', 'checkout', self
.rev
], cwd
=self
.buildPath
)
175 except subprocess
.CalledProcessError
as e
:
176 raise BuildError(e
.output
)
178 super(GITBuilder
, self
).build()
181 class YoutubeDLBuilder(object):
182 authorizedUsers
= ['fraca7', 'phihag', 'rg3', 'FiloSottile']
184 def __init__(self
, **kwargs
):
185 if self
.repoName
!= 'youtube-dl':
186 raise BuildError('Invalid repository "%s"' % self
.repoName
)
187 if self
.user
not in self
.authorizedUsers
:
188 raise HTTPError('Unauthorized user "%s"' % self
.user
, 401)
190 super(YoutubeDLBuilder
, self
).__init
__(**kwargs
)
194 subprocess
.check_output([os
.path
.join(self
.pythonPath
, 'python.exe'), 'setup.py', 'py2exe'],
196 except subprocess
.CalledProcessError
as e
:
197 raise BuildError(e
.output
)
199 super(YoutubeDLBuilder
, self
).build()
202 class DownloadBuilder(object):
203 def __init__(self
, **kwargs
):
204 self
.handler
= kwargs
.pop('handler')
205 self
.srcPath
= os
.path
.join(self
.buildPath
, *tuple(kwargs
['path'][2:]))
206 self
.srcPath
= os
.path
.abspath(os
.path
.normpath(self
.srcPath
))
207 if not self
.srcPath
.startswith(self
.buildPath
):
208 raise HTTPError(self
.srcPath
, 401)
210 super(DownloadBuilder
, self
).__init
__(**kwargs
)
213 if not os
.path
.exists(self
.srcPath
):
214 raise HTTPError('No such file', 404)
215 if os
.path
.isdir(self
.srcPath
):
216 raise HTTPError('Is a directory: %s' % self
.srcPath
, 401)
218 self
.handler
.send_response(200)
219 self
.handler
.send_header('Content-Type', 'application/octet-stream')
220 self
.handler
.send_header('Content-Disposition', 'attachment; filename=%s' % os
.path
.split(self
.srcPath
)[-1])
221 self
.handler
.send_header('Content-Length', str(os
.stat(self
.srcPath
).st_size
))
222 self
.handler
.end_headers()
224 with open(self
.srcPath
, 'rb') as src
:
225 shutil
.copyfileobj(src
, self
.handler
.wfile
)
227 super(DownloadBuilder
, self
).build()
230 class CleanupTempDir(object):
233 rmtree(self
.basePath
)
234 except Exception as e
:
235 print('WARNING deleting "%s": %s' % (self
.basePath
, e
))
237 super(CleanupTempDir
, self
).build()
241 def __init__(self
, **kwargs
):
254 class Builder(PythonBuilder
, GITBuilder
, YoutubeDLBuilder
, DownloadBuilder
, CleanupTempDir
, Null
):
258 class BuildHTTPRequestHandler(BaseHTTPRequestHandler
):
259 actionDict
= { 'build': Builder, 'download': Builder }
# They're the same, no more caching.
262 path
= urlparse
.urlparse(self
.path
)
263 paramDict
= dict([(key
, value
[0]) for key
, value
in urlparse
.parse_qs(path
.query
).items()])
264 action
, _
, path
= path
.path
.strip('/').partition('/')
266 path
= path
.split('/')
267 if action
in self
.actionDict
:
269 builder
= self
.actionDict
[action
](path
=path
, handler
=self
, **paramDict
)
275 except BuildError
as e
:
276 self
.send_response(e
.code
)
277 msg
= unicode(e
).encode('UTF-8')
278 self
.send_header('Content-Type', 'text/plain; charset=UTF-8')
279 self
.send_header('Content-Length', len(msg
))
281 self
.wfile
.write(msg
)
282 except HTTPError
as e
:
283 self
.send_response(e
.code
, str(e
))
285 self
.send_response(500, 'Unknown build method "%s"' % action
)
287 self
.send_response(500, 'Malformed URL')
289 #==============================================================================
291 if __name__
== '__main__':