]> jfr.im git - yt-dlp.git/blame - devscripts/buildserver.py
Add original buildserver from @fraca7
[yt-dlp.git] / devscripts / buildserver.py
CommitLineData
83de7942
PH
1#!/usr/bin/python
2
3## This is free and unencumbered software released into the public domain.
4
5## Anyone is free to copy, modify, publish, use, compile, sell, or
6## distribute this software, either in source code form or as a compiled
7## binary, for any purpose, commercial or non-commercial, and by any
8## means.
9
10## In jurisdictions that recognize copyright laws, the author or authors
11## of this software dedicate any and all copyright interest in the
12## software to the public domain. We make this dedication for the benefit
13## of the public at large and to the detriment of our heirs and
14## successors. We intend this dedication to be an overt act of
15## relinquishment in perpetuity of all present and future rights to this
16## software under copyright law.
17
18## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21## IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
22## OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
23## ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24## OTHER DEALINGS IN THE SOFTWARE.
25
26## For more information, please refer to <http://unlicense.org/>
27
28from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
29from SocketServer import ThreadingMixIn
30import getopt, threading, sys, urlparse, _winreg, os, subprocess, shutil, tempfile
31
32
33class BuildHTTPServer(ThreadingMixIn, HTTPServer):
34 allow_reuse_address = True
35
36
37def usage():
38 print 'Usage: %s [options]'
39 print 'Options:'
40 print
41 print ' -h, --help Display this help'
42 print ' -i, --install Launch at session startup'
43 print ' -u, --uninstall Do not launch at session startup'
44 print ' -b, --bind <host[:port]> Bind to host:port (default localhost:8142)'
45 sys.exit(0)
46
47
48def main(argv):
49 opts, args = getopt.getopt(argv, 'hb:iu', ['help', 'bind=', 'install', 'uninstall'])
50 host = 'localhost'
51 port = 8142
52
53 for opt, val in opts:
54 if opt in ['-h', '--help']:
55 usage()
56 elif opt in ['-b', '--bind']:
57 try:
58 host, port = val.split(':')
59 except ValueError:
60 host = val
61 else:
62 port = int(port)
63 elif opt in ['-i', '--install']:
64 key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Run', 0, _winreg.KEY_WRITE)
65 try:
66 _winreg.SetValueEx(key, 'Youtube-dl builder', 0, _winreg.REG_SZ,
67 '"%s" "%s" -b %s:%d' % (sys.executable, os.path.normpath(os.path.abspath(sys.argv[0])),
68 host, port))
69 finally:
70 _winreg.CloseKey(key)
71 print 'Installed.'
72 sys.exit(0)
73 elif opt in ['-u', '--uninstall']:
74 key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Run', 0, _winreg.KEY_WRITE)
75 try:
76 _winreg.DeleteValue(key, 'Youtube-dl builder')
77 finally:
78 _winreg.CloseKey(key)
79 print 'Uninstalled.'
80 sys.exit(0)
81
82 print 'Listening on %s:%d' % (host, port)
83 srv = BuildHTTPServer((host, port), BuildHTTPRequestHandler)
84 thr = threading.Thread(target=srv.serve_forever)
85 thr.start()
86 raw_input('Hit <ENTER> to stop...\n')
87 srv.shutdown()
88 thr.join()
89
90
91def rmtree(path):
92 for name in os.listdir(path):
93 fname = os.path.join(path, name)
94 if os.path.isdir(fname):
95 rmtree(fname)
96 else:
97 os.chmod(fname, 0666)
98 os.remove(fname)
99 os.rmdir(path)
100
101#==============================================================================
102
103class BuildError(Exception):
104 def __init__(self, output, code=500):
105 self.output = output
106 self.code = code
107
108 def __str__(self):
109 return self.output
110
111
112class HTTPError(BuildError):
113 pass
114
115
116class PythonBuilder(object):
117 def __init__(self, **kwargs):
118 pythonVersion = kwargs.pop('python', '2.7')
119 try:
120 key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\Python\PythonCore\%s\InstallPath' % pythonVersion)
121 try:
122 self.pythonPath, _ = _winreg.QueryValueEx(key, '')
123 finally:
124 _winreg.CloseKey(key)
125 except Exception:
126 raise BuildError('No such Python version: %s' % pythonVersion)
127
128 super(PythonBuilder, self).__init__(**kwargs)
129
130
131class GITInfoBuilder(object):
132 def __init__(self, **kwargs):
133 try:
134 self.user, self.repoName = kwargs['path'][:2]
135 self.rev = kwargs.pop('rev')
136 except ValueError:
137 raise BuildError('Invalid path')
138 except KeyError as e:
139 raise BuildError('Missing mandatory parameter "%s"' % e.args[0])
140
141 path = os.path.join(os.environ['APPDATA'], 'Build archive', self.repoName, self.user)
142 if not os.path.exists(path):
143 os.makedirs(path)
144 self.basePath = tempfile.mkdtemp(dir=path)
145 self.buildPath = os.path.join(self.basePath, 'build')
146
147 super(GITInfoBuilder, self).__init__(**kwargs)
148
149
150class GITBuilder(GITInfoBuilder):
151 def build(self):
152 try:
153 subprocess.check_output(['git', 'clone', 'git://github.com/%s/%s.git' % (self.user, self.repoName), self.buildPath])
154 subprocess.check_output(['git', 'checkout', self.rev], cwd=self.buildPath)
155 except subprocess.CalledProcessError as e:
156 raise BuildError(e.output)
157
158 super(GITBuilder, self).build()
159
160
161class YoutubeDLBuilder(object):
162 authorizedUsers = ['fraca7', 'phihag', 'rg3', 'FiloSottile']
163
164 def __init__(self, **kwargs):
165 if self.repoName != 'youtube-dl':
166 raise BuildError('Invalid repository "%s"' % self.repoName)
167 if self.user not in self.authorizedUsers:
168 raise HTTPError('Unauthorized user "%s"' % self.user, 401)
169
170 super(YoutubeDLBuilder, self).__init__(**kwargs)
171
172 def build(self):
173 try:
174 subprocess.check_output([os.path.join(self.pythonPath, 'python.exe'), 'setup.py', 'py2exe'],
175 cwd=self.buildPath)
176 except subprocess.CalledProcessError as e:
177 raise BuildError(e.output)
178
179 super(YoutubeDLBuilder, self).build()
180
181
182class DownloadBuilder(object):
183 def __init__(self, **kwargs):
184 self.handler = kwargs.pop('handler')
185 self.srcPath = os.path.join(self.buildPath, *tuple(kwargs['path'][2:]))
186 self.srcPath = os.path.abspath(os.path.normpath(self.srcPath))
187 if not self.srcPath.startswith(self.buildPath):
188 raise HTTPError(self.srcPath, 401)
189
190 super(DownloadBuilder, self).__init__(**kwargs)
191
192 def build(self):
193 if not os.path.exists(self.srcPath):
194 raise HTTPError('No such file', 404)
195 if os.path.isdir(self.srcPath):
196 raise HTTPError('Is a directory: %s' % self.srcPath, 401)
197
198 self.handler.send_response(200)
199 self.handler.send_header('Content-Type', 'application/octet-stream')
200 self.handler.send_header('Content-Disposition', 'attachment; filename=%s' % os.path.split(self.srcPath)[-1])
201 self.handler.send_header('Content-Length', str(os.stat(self.srcPath).st_size))
202 self.handler.end_headers()
203
204 with open(self.srcPath, 'rb') as src:
205 shutil.copyfileobj(src, self.handler.wfile)
206
207 super(DownloadBuilder, self).build()
208
209
210class CleanupTempDir(object):
211 def build(self):
212 try:
213 rmtree(self.basePath)
214 except Exception as e:
215 print 'WARNING deleting "%s": %s' % (self.basePath, e)
216
217 super(CleanupTempDir, self).build()
218
219
220class Null(object):
221 def __init__(self, **kwargs):
222 pass
223
224 def start(self):
225 pass
226
227 def close(self):
228 pass
229
230 def build(self):
231 pass
232
233
234class Builder(PythonBuilder, GITBuilder, YoutubeDLBuilder, DownloadBuilder, CleanupTempDir, Null):
235 pass
236
237
238class BuildHTTPRequestHandler(BaseHTTPRequestHandler):
239 actionDict = { 'build': Builder, 'download': Builder } # They're the same, no more caching.
240
241 def do_GET(self):
242 path = urlparse.urlparse(self.path)
243 paramDict = dict([(key, value[0]) for key, value in urlparse.parse_qs(path.query).items()])
244 action, _, path = path.path.strip('/').partition('/')
245 if path:
246 path = path.split('/')
247 if action in self.actionDict:
248 try:
249 builder = self.actionDict[action](path=path, handler=self, **paramDict)
250 builder.start()
251 try:
252 builder.build()
253 finally:
254 builder.close()
255 except BuildError as e:
256 self.send_response(e.code)
257 msg = unicode(e).encode('UTF-8')
258 self.send_header('Content-Type', 'text/plain; charset=UTF-8')
259 self.send_header('Content-Length', len(msg))
260 self.end_headers()
261 self.wfile.write(msg)
262 except HTTPError as e:
263 self.send_response(e.code, str(e))
264 else:
265 self.send_response(500, 'Unknown build method "%s"' % action)
266 else:
267 self.send_response(500, 'Malformed URL')
268
269#==============================================================================
270
271if __name__ == '__main__':
272 main(sys.argv[1:])