Advertisement
LinFreak92

serve-project.py

Jul 27th, 2015
285
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.29 KB | None | 0 0
  1. from txrestapi.resource import APIResource
  2. from twisted.web.server import Site
  3. from twisted.web.static import File
  4. from twisted.protocols.basic import FileSender
  5. from twisted.python.log import err
  6. from twisted.web.server import NOT_DONE_YET
  7. from twisted.internet import reactor
  8. from txrestapi.methods import GET, POST, PUT, ALL
  9. from BReader import BackwardsReader
  10. import json
  11. import os
  12. import shutil
  13. import magic
  14. from WrapSSH import *
  15. import pprint
  16.  
  17.  
  18. # TODO: Abstract utility functions into their own module.
  19.  
  20. # Builds a dict that references a given key in a list of dicts,
  21. # for quick retrieval.
  22. def build_dict(seq, key):
  23.     return dict((d[key], dict(d, index=i)) for (i, d) in enumerate(seq))
  24.  
  25.  
  26. # Would be nice to turn the things below into classes, but they won't serialize
  27. # nicely :S (Yes, this class is currently useless...)
  28. class TransferDir(object):
  29.     host = ""
  30.     path = ""
  31.     short = ""
  32.     friendly = ""
  33.  
  34.     def __init__(self, host, path, short=None, friendly=None):
  35.         self.host = host
  36.         self.path = path
  37.         self.short = short
  38.         self.friendly = friendly
  39.  
  40. # Statically defined stuffs..this whole section needs a rethink, but since
  41. # we're just prototyping...
  42. # TODO: Overhaul/abstract/rethink sources and destinations so that both local
  43. # and remote files can be handled.
  44. sourcedirs = [
  45.     {
  46.         'id': '1',
  47.         'host': 'localhost',
  48.         'path': './dirs/dira',
  49.         'short': 'src_a',
  50.         'friendly': 'File Source A'
  51.     },
  52.     {
  53.         'id': '2',
  54.         'host': 'localhost',
  55.         'path': './dirs/dirb',
  56.         'short': 'src_b',
  57.         'friendly': 'File Source B'
  58.     },
  59.     {
  60.         'id': '3',
  61.         'host': 'localhost',
  62.         'path': './dirs/dirc',
  63.         'short': 'src_c',
  64.         'friendly': 'File Source C'
  65.     },
  66.     {
  67.         'id': '4',
  68.         'host': 'localhost',
  69.         'path': './dirs/coma',
  70.         'short': 'com_a',
  71.         'friendly': 'File Common A'
  72.     },
  73.     {
  74.         'id': '4',
  75.         'host': 'localhost',
  76.         'path': './dirs/coma',
  77.         'short': 'com_b',
  78.         'friendly': 'File Common B'
  79.     },
  80.     {
  81.         'id': '5',
  82.         'host': 'Taris',
  83.         'path': '/home/####/testdir',
  84.         'short': 'tar_a',
  85.         'friendly': 'Taris Store A'
  86.     }
  87. ]
  88. destdirs = [
  89.     {
  90.         'id': '1',
  91.         'host': 'localhost',
  92.         'path': './dirs/dird',
  93.         'short': 'src_d',
  94.         'friendly': 'File Destination D'
  95.     },
  96.     {
  97.         'id': '2',
  98.         'host': 'localhost',
  99.         'path': './dirs/coma',
  100.         'short': 'com_a',
  101.         'friendly': 'File Common A'
  102.     },
  103.     {
  104.         'id': '3',
  105.         'host': 'localhost',
  106.         'path': './dirs/coma',
  107.         'short': 'com_b',
  108.         'friendly': 'File Common B'
  109.     },
  110.     {
  111.         'id': '4',
  112.         'host': 'Taris',
  113.         'path': '/home/####/testdir',
  114.         'short': 'tar_a',
  115.         'friendly': 'Taris Store A'
  116.     }
  117. ]
  118.  
  119. sshhosts = [
  120.     {
  121.         'name': 'a',
  122.         'addr': 'a',
  123.         'port': 0,
  124.         'user': 'a',
  125.         'auth': 'a',
  126.         'cred': 'a'
  127.     },
  128.     {
  129.         'name': 'Taris',
  130.         'addr': 'taris.##.##',
  131.         'port': 22,
  132.         'user': '#####',
  133.         'auth': 'pass',
  134.         'cred': "#####"
  135.     }
  136. ]
  137.  
  138. # Build a quick dictionary to make lookup operations O(1),
  139. # since these could could happen frequently.
  140. indexed_sources = build_dict(sourcedirs, 'id')
  141. indexed_dests = build_dict(destdirs, 'id')
  142. indexed_hosts = build_dict(sshhosts, 'name')
  143.  
  144.  
  145. errornf = {'error': 'Resource not found'}
  146.  
  147. # End of the statics
  148.  
  149.  
  150. class BwcResource(APIResource):
  151.  
  152.     # Class utility funcs:
  153.     def get_file_list(self, indexed_dir, key):
  154.         try:
  155.             directory = indexed_dir[key]
  156.             path = directory.get('path')
  157.             listing = os.listdir(path)
  158.             return listing
  159.         except KeyError, e:
  160.             print("Invalid indexed_dir key")
  161.             raise e
  162.  
  163.     ###########################################################################
  164.     # Static Content
  165.     ###########################################################################
  166.  
  167.     # Serves the Angular App
  168.     @ALL('^/static/')
  169.     def static_callback(self, request):
  170.         return File('./webapp')
  171.  
  172.     ###########################################################################
  173.     # Transfers section
  174.     ###########################################################################
  175.  
  176.     # List available transfer sources
  177.     @GET('^/api/transfer/sources(/)?$')
  178.     def transfer_sources(self, request):
  179.         return json.dumps(sourcedirs)
  180.  
  181.     # List available transfer destinations
  182.     @GET('^/api/transfer/destinations(/)?$')
  183.     def transfer_destinations(self, request):
  184.         return json.dumps(destdirs)
  185.  
  186.     # Fetch a single source's details by key
  187.     @GET('^/api/transfer/sources/(?P<key>[^/]+)/details(/)?')
  188.     def transfer_sources_details(self, request, key):
  189.         return json.dumps(indexed_sources[key])
  190.  
  191.     # Fetch a single destination's details by key
  192.     @GET('^/api/transfer/destinations/(?P<key>[^/]+)/details(/)?')
  193.     def transfer_dests_details(self, request, key):
  194.         return json.dumps(indexed_dests[key])
  195.  
  196.     # List contents of the requested source directory
  197.     @GET('^/api/transfer/sources/(?P<key>[^/]+)/listing(/)?')
  198.     def transfer_sources_listing(self, request, key):
  199.         source = indexed_sources[key]
  200.         hostname = source.get('host')
  201.  
  202.         # If this is a local path
  203.         if hostname == "localhost":
  204.  
  205.             try:
  206.                 listing = self.get_file_list(indexed_sources, key)
  207.                 return json.dumps(listing)
  208.             except KeyError:
  209.                 print("Tried to access nonexistent key "+key
  210.                       + " of indexed_sources\n")
  211.                 return json.dumps({'error':
  212.                                   "Tried to access nonexistant key"})
  213.  
  214.         # Else it must be a remote!
  215.         # TODO: Add a proper try wrapper
  216.         else:
  217.             host = indexed_hosts[hostname]
  218.             path = indexed_sources[key].get('path')
  219.  
  220.             wrap = WrapSSH(
  221.                 host.get('addr'),
  222.                 host.get('port'),
  223.                 host.get('user'),
  224.                 host.get('auth'),
  225.                 host.get('cred')
  226.                 )
  227.             return json.dumps(wrap.list(path))
  228.  
  229.  
  230.  
  231.     # List contents of the requested destination directory
  232.     @GET('^/api/transfer/destinations/(?P<key>[^/]+)/listing(/)?')
  233.     def transfer_destinations_listing(self, request, key):
  234.         try:
  235.             listing = self.get_file_list(indexed_dests, key)
  236.             return json.dumps(listing)
  237.         except KeyError:
  238.             print("Tried to access nonexistent key "+key+" of indexed_dests\n")
  239.             return json.dumps({'error': "Tried to access nonexistant key"})
  240.  
  241.     # Move a file
  242.     # QUESTION: What should the behavior be if the destination already exists?
  243.     @POST('^/api/transfer/move')
  244.     def transfer_move_file(self, request):
  245.         data = json.loads(request.content.getvalue())
  246.         srcpath = indexed_sources[data["srckey"]].get('path')+'/'+data["srcfile"]
  247.         destpath = indexed_dests[data["destkey"]].get('path')+'/'+data["destfile"]
  248.         result = shutil.move(srcpath, destpath)
  249.         return json.dumps({'status': 'success', 'result': result})
  250.  
  251.     # Download a file
  252.     @POST('^/api/transfer/download')
  253.     def transfer_download_file(self, request):
  254.         data = json.loads(request.content.getvalue())
  255.         source = indexed_sources[data["srckey"]]
  256.         srcpath = source.get('path')+'/'+data["srcfile"]
  257.         hostname = source.get('host')
  258.  
  259.         # If this is a local path
  260.         if hostname == "localhost":
  261.             mime = magic.Magic(mime=True)
  262.             mimetype = mime.from_file(srcpath)
  263.             rangedFile = File(srcpath, defaultType=mimetype)
  264.             return rangedFile.render_GET(request)
  265.        
  266.         else:
  267.             host = indexed_hosts[hostname]
  268.             wrap = WrapSSH(
  269.                 host.get('addr'),
  270.                 host.get('port'),
  271.                 host.get('user'),
  272.                 host.get('auth'),
  273.                 host.get('cred')
  274.                 )
  275.             # Bunch of magic to avoid having to cache file locally.
  276.             stats = wrap.stat(srcpath)
  277.             fp = wrap.fp(srcpath)
  278.             beginning = fp.tell()
  279.             mime = magic.Magic(mime=True)
  280.             mimetype = mime.from_buffer(fp.read(1024))
  281.             fp.seek(beginning)
  282.            
  283.             thing = SSHResource(fp, stats.st_size, mimetype, data['srcfile'])
  284.             return thing.render(request)
  285.  
  286.     # Tail a file
  287.     # QUESTION: Should we just fork to the OS's tail instead? Simpler, but
  288.     # less efficient
  289.     # TODO: Return an error/notification if the file is not a tailable type.
  290.     @POST('^/api/transfer/tail')
  291.     def transfer_tail_file(self, request):
  292.         LINES = 10
  293.  
  294.         data = json.loads(request.content.getvalue())
  295.         source = indexed_sources[data["srckey"]]
  296.         hostname = source.get('host')
  297.         path = source.get('path') + '/'+data["srcfile"]
  298.  
  299.         # If this is a local path
  300.         if hostname == "localhost":
  301.             try:
  302.                 br = BackwardsReader(path)
  303.                 out = ""
  304.                 for x in range(LINES):
  305.                     out = br.readline() + out
  306.                 return json.dumps({'status': 'success', 'result': out})
  307.             except UnicodeDecodeError:
  308.                 return json.dumps({'status': 'fail', 'result':
  309.                                    'File could not be tailed: UnicodeDecodeError'})
  310.  
  311.         # Else it must be a remote!
  312.         # TODO: Add a proper try wrapper
  313.         else:
  314.             host = indexed_hosts[hostname]
  315.             path = indexed_sources[data["srckey"]].get('path') + '/'+data["srcfile"]
  316.             print(path+'\n')
  317.             wrap = WrapSSH(
  318.                 host.get('addr'),
  319.                 host.get('port'),
  320.                 host.get('user'),
  321.                 host.get('auth'),
  322.                 host.get('cred')
  323.                 )
  324.             return json.dumps({'status': 'success',
  325.                                'result': wrap.tail(path, LINES)})
  326.  
  327.     # Email a file
  328.     @POST('^/api/transfer/email')
  329.     def transfer_email_file(self, request):
  330.  
  331.         ## Example email code from Dave's app
  332.         # ''' This method emails the admin invoice report '''
  333.         # from_dir = self.get_gadget(BMW.ID_DISP_DIR).GetStringSelection()
  334.         # if from_dir == '':
  335.         #     self.send_message('A listing directory must be selected', 'error')
  336.         # report = self.get_gadget(BMW.EDISelection).GetStringSelection()
  337.         # if  report == '':
  338.         #     self.send_message('A file must be selected', 'error')
  339.         #     return
  340.         # from_dir = BMS.DIR_DICT[from_dir]
  341.         # email_add = self.cfg['telnet']['email']
  342.         # telnet_conn = BMF.connect_and_cd(self.cfg['telnet'], BMF.get_env(self), self.cfg['sudo'],
  343.         #                                  self.single_opts['curr_db_name'], from_dir)
  344.         # email = '''/appl/bwc/%s/scripts/email %s "%s" "Your file is enclosed." %s /appl/bwc/%s/%s/%s email''' \
  345.         #            % (self.single_opts['curr_db_name'], email_add, "File Emailed",
  346.         #               email_add, self.single_opts['curr_db_name'], from_dir, report)
  347.         # telnet_conn.send(email)
  348.         # BMF.process_telnet_conn(telnet_conn, self.cfg['telnet']['prompt'])
  349.         # self.send_message('Successfully emailed %s' % report, 'info')
  350.  
  351.         return json.dumps({'error': "Unimplemented"})
  352.  
  353.     ###########################################################################
  354.     # Defaults
  355.     ###########################################################################
  356.  
  357.     # Pointless response to a call to the root of the API.
  358.     @ALL('^/api(/)?$')
  359.     def api_callback(self, request):
  360.         return "Root of the API. Should probably tell you how to use me?"
  361.  
  362.     # Response to a call to the root of the server.
  363.     @ALL('^/.*')
  364.     def default_callback(self, request):
  365.         return "This is the default response. It doesn't do anything fun."
  366.  
  367.  
  368. # Float the Boat
  369. # site = Site(api, timeout=None)
  370. api = BwcResource()
  371. site = Site(api, timeout=None)
  372. reactor.listenTCP(8880, site)
  373. reactor.run()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement