diff options
Diffstat (limited to 'devscripts/buildserver.py')
| -rw-r--r-- | devscripts/buildserver.py | 272 | 
1 files changed, 272 insertions, 0 deletions
| diff --git a/devscripts/buildserver.py b/devscripts/buildserver.py new file mode 100644 index 000000000..4fe80edce --- /dev/null +++ b/devscripts/buildserver.py @@ -0,0 +1,272 @@ +#!/usr/bin/python + +## This is free and unencumbered software released into the public domain. + +## Anyone is free to copy, modify, publish, use, compile, sell, or +## distribute this software, either in source code form or as a compiled +## binary, for any purpose, commercial or non-commercial, and by any +## means. + +## In jurisdictions that recognize copyright laws, the author or authors +## of this software dedicate any and all copyright interest in the +## software to the public domain. We make this dedication for the benefit +## of the public at large and to the detriment of our heirs and +## successors. We intend this dedication to be an overt act of +## relinquishment in perpetuity of all present and future rights to this +## software under copyright law. + +## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +## IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +## OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +## ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +## OTHER DEALINGS IN THE SOFTWARE. + +## For more information, please refer to <http://unlicense.org/> + +from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler +from SocketServer import ThreadingMixIn +import getopt, threading, sys, urlparse, _winreg, os, subprocess, shutil, tempfile + + +class BuildHTTPServer(ThreadingMixIn, HTTPServer): +    allow_reuse_address = True + + +def usage(): +    print 'Usage: %s [options]' +    print 'Options:' +    print +    print '  -h, --help               Display this help' +    print '  -i, --install            Launch at session startup' +    print '  -u, --uninstall          Do not launch at session startup' +    print '  -b, --bind <host[:port]> Bind to host:port (default localhost:8142)' +    sys.exit(0) + + +def main(argv): +    opts, args = getopt.getopt(argv, 'hb:iu', ['help', 'bind=', 'install', 'uninstall']) +    host = 'localhost' +    port = 8142 + +    for opt, val in opts: +        if opt in ['-h', '--help']: +            usage() +        elif opt in ['-b', '--bind']: +            try: +                host, port = val.split(':') +            except ValueError: +                host = val +            else: +                port = int(port) +        elif opt in ['-i', '--install']: +            key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Run', 0, _winreg.KEY_WRITE) +            try: +                _winreg.SetValueEx(key, 'Youtube-dl builder', 0, _winreg.REG_SZ, +                                   '"%s" "%s" -b %s:%d' % (sys.executable, os.path.normpath(os.path.abspath(sys.argv[0])), +                                                           host, port)) +            finally: +                _winreg.CloseKey(key) +            print 'Installed.' +            sys.exit(0) +        elif opt in ['-u', '--uninstall']: +            key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Run', 0, _winreg.KEY_WRITE) +            try: +                _winreg.DeleteValue(key, 'Youtube-dl builder') +            finally: +                _winreg.CloseKey(key) +            print 'Uninstalled.' +            sys.exit(0) + +    print 'Listening on %s:%d' % (host, port) +    srv = BuildHTTPServer((host, port), BuildHTTPRequestHandler) +    thr = threading.Thread(target=srv.serve_forever) +    thr.start() +    raw_input('Hit <ENTER> to stop...\n') +    srv.shutdown() +    thr.join() + + +def rmtree(path): +    for name in os.listdir(path): +        fname = os.path.join(path, name) +        if os.path.isdir(fname): +            rmtree(fname) +        else: +            os.chmod(fname, 0666) +            os.remove(fname) +    os.rmdir(path) + +#============================================================================== + +class BuildError(Exception): +    def __init__(self, output, code=500): +        self.output = output +        self.code = code + +    def __str__(self): +        return self.output + + +class HTTPError(BuildError): +    pass + + +class PythonBuilder(object): +    def __init__(self, **kwargs): +        pythonVersion = kwargs.pop('python', '2.7') +        try: +            key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\Python\PythonCore\%s\InstallPath' % pythonVersion) +            try: +                self.pythonPath, _ = _winreg.QueryValueEx(key, '') +            finally: +                _winreg.CloseKey(key) +        except Exception: +            raise BuildError('No such Python version: %s' % pythonVersion) + +        super(PythonBuilder, self).__init__(**kwargs) + + +class GITInfoBuilder(object): +    def __init__(self, **kwargs): +        try: +            self.user, self.repoName = kwargs['path'][:2] +            self.rev = kwargs.pop('rev') +        except ValueError: +            raise BuildError('Invalid path') +        except KeyError as e: +            raise BuildError('Missing mandatory parameter "%s"' % e.args[0]) + +        path = os.path.join(os.environ['APPDATA'], 'Build archive', self.repoName, self.user) +        if not os.path.exists(path): +            os.makedirs(path) +        self.basePath = tempfile.mkdtemp(dir=path) +        self.buildPath = os.path.join(self.basePath, 'build') + +        super(GITInfoBuilder, self).__init__(**kwargs) + + +class GITBuilder(GITInfoBuilder): +    def build(self): +        try: +            subprocess.check_output(['git', 'clone', 'git://github.com/%s/%s.git' % (self.user, self.repoName), self.buildPath]) +            subprocess.check_output(['git', 'checkout', self.rev], cwd=self.buildPath) +        except subprocess.CalledProcessError as e: +            raise BuildError(e.output) + +        super(GITBuilder, self).build() + + +class YoutubeDLBuilder(object): +    authorizedUsers = ['fraca7', 'phihag', 'rg3', 'FiloSottile'] + +    def __init__(self, **kwargs): +        if self.repoName != 'youtube-dl': +            raise BuildError('Invalid repository "%s"' % self.repoName) +        if self.user not in self.authorizedUsers: +            raise HTTPError('Unauthorized user "%s"' % self.user, 401) + +        super(YoutubeDLBuilder, self).__init__(**kwargs) + +    def build(self): +        try: +            subprocess.check_output([os.path.join(self.pythonPath, 'python.exe'), 'setup.py', 'py2exe'], +                                    cwd=self.buildPath) +        except subprocess.CalledProcessError as e: +            raise BuildError(e.output) + +        super(YoutubeDLBuilder, self).build() + + +class DownloadBuilder(object): +    def __init__(self, **kwargs): +        self.handler = kwargs.pop('handler') +        self.srcPath = os.path.join(self.buildPath, *tuple(kwargs['path'][2:])) +        self.srcPath = os.path.abspath(os.path.normpath(self.srcPath)) +        if not self.srcPath.startswith(self.buildPath): +            raise HTTPError(self.srcPath, 401) + +        super(DownloadBuilder, self).__init__(**kwargs) + +    def build(self): +        if not os.path.exists(self.srcPath): +            raise HTTPError('No such file', 404) +        if os.path.isdir(self.srcPath): +            raise HTTPError('Is a directory: %s' % self.srcPath, 401) + +        self.handler.send_response(200) +        self.handler.send_header('Content-Type', 'application/octet-stream') +        self.handler.send_header('Content-Disposition', 'attachment; filename=%s' % os.path.split(self.srcPath)[-1]) +        self.handler.send_header('Content-Length', str(os.stat(self.srcPath).st_size)) +        self.handler.end_headers() + +        with open(self.srcPath, 'rb') as src: +            shutil.copyfileobj(src, self.handler.wfile) + +        super(DownloadBuilder, self).build() + + +class CleanupTempDir(object): +    def build(self): +        try: +            rmtree(self.basePath) +        except Exception as e: +            print 'WARNING deleting "%s": %s' % (self.basePath, e) + +        super(CleanupTempDir, self).build() + + +class Null(object): +    def __init__(self, **kwargs): +        pass + +    def start(self): +        pass + +    def close(self): +        pass + +    def build(self): +        pass + + +class Builder(PythonBuilder, GITBuilder, YoutubeDLBuilder, DownloadBuilder, CleanupTempDir, Null): +    pass + + +class BuildHTTPRequestHandler(BaseHTTPRequestHandler): +    actionDict = { 'build': Builder, 'download': Builder } # They're the same, no more caching. + +    def do_GET(self): +        path = urlparse.urlparse(self.path) +        paramDict = dict([(key, value[0]) for key, value in urlparse.parse_qs(path.query).items()]) +        action, _, path = path.path.strip('/').partition('/') +        if path: +            path = path.split('/') +            if action in self.actionDict: +                try: +                    builder = self.actionDict[action](path=path, handler=self, **paramDict) +                    builder.start() +                    try: +                        builder.build() +                    finally: +                        builder.close() +                except BuildError as e: +                    self.send_response(e.code) +                    msg = unicode(e).encode('UTF-8') +                    self.send_header('Content-Type', 'text/plain; charset=UTF-8') +                    self.send_header('Content-Length', len(msg)) +                    self.end_headers() +                    self.wfile.write(msg) +                except HTTPError as e: +                    self.send_response(e.code, str(e)) +            else: +                self.send_response(500, 'Unknown build method "%s"' % action) +        else: +            self.send_response(500, 'Malformed URL') + +#============================================================================== + +if __name__ == '__main__': +    main(sys.argv[1:]) | 
