]>
Commit | Line | Data |
---|---|---|
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 | ||
28 | from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler | |
29 | from SocketServer import ThreadingMixIn | |
30 | import getopt, threading, sys, urlparse, _winreg, os, subprocess, shutil, tempfile | |
31 | ||
32 | ||
33 | class BuildHTTPServer(ThreadingMixIn, HTTPServer): | |
34 | allow_reuse_address = True | |
35 | ||
36 | ||
37 | def usage(): | |
38 | print 'Usage: %s [options]' | |
39 | print 'Options:' | |
40 | ||
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 | ||
48 | def 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 | ||
91 | def 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 | ||
103 | class 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 | ||
112 | class HTTPError(BuildError): | |
113 | pass | |
114 | ||
115 | ||
116 | class 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 | ||
131 | class 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 | ||
150 | class 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 | ||
161 | class 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 | ||
182 | class 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 | ||
210 | class 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 | ||
220 | class 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 | ||
234 | class Builder(PythonBuilder, GITBuilder, YoutubeDLBuilder, DownloadBuilder, CleanupTempDir, Null): | |
235 | pass | |
236 | ||
237 | ||
238 | class 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 | ||
271 | if __name__ == '__main__': | |
272 | main(sys.argv[1:]) |