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__':