You are not logged in.
I got bored and decided to write a file server.
Server:
#!/usr/bin/env python
import optparse, os, socket, select, signal, sys
e_ok =        0
e_emptyreq =  1
e_notfile =   2
e_other =     3
fsize_len = 16 # length of file size word
def err(msg):
    sys.stderr.write(msg + '\n')
    sys.exit(1)
# send a message with appropriate status code
def sendmsg(sock, code, msg):
    sock.sendall('{0}{1}'.format(chr(code), msg))
    if code > e_ok:
        sock.close()
# turn an integer into a series of octets -- maximum value is 2^128 - 1
def makefsize(size):
    i = 0
    word = ''
    while i < fsize_len:
        word = '{0}{1}'.format(chr(size & 0xFF), word)
        size >>= 8
        i += 1
    return word
def main():
    parser = optparse.OptionParser(usage = 'Usage: %prog <options>')
    parser.add_option('-d', '--docroot', dest = 'docroot', action = 'store', default = os.path.expanduser('~'), help = 'directory to serve files from [~]')
    parser.add_option('-p', '--port', dest = 'port', action = 'store', type = 'int', default = 6666, help = 'port to listen on [%default]')
    parser.add_option('--allow-dotfiles', dest = 'allow_dotfiles', action = 'store_true', default = False, help = 'allow clients to access dotfiles [no]')
    parser.add_option('--send-blocksize', dest = 'send_blocksize', action = 'store', type = 'int', default = 4096, help = 'number of bytes to send at a time [%default]')
    parser.add_option('--connect-backlog', dest = 'connect_backlog', action = 'store', type = 'int', default = 3, help = 'backlog for connect() [%default]')
    opts, args = parser.parse_args()
    
    master = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
    master.bind(('localhost', opts.port))
    master.listen(opts.connect_backlog);
    os.chroot(opts.docroot)
    signal.signal(signal.SIGCHLD, signal.SIG_IGN) # let the system auto-reap zombie processes
    while True:
        select.select([master], [], []) # not sure what to use here; something in {poll}, maybe?
        
        # fire-and-forget a new process to handle the request
        if os.fork() == 0:
            client = master.accept()
            print('Incoming connection from {0}:{1}'.format(client[1][0], client[1][1]))
            
            # hmm, this just sits there and blocks if the client requests an empty file
            req = client[0].recv(4096).rstrip() # get rid of trailing newline
            if len(req) == 0:
                sendmsg(client[0], e_emptyreq, 'Empty request')
                sys.exit(1)
            if not os.path.isfile(req) or (not opts.allow_dotfiles and os.path.basename(req)[0] == '.'):
                sendmsg(client[0], e_notfile, 'Not a file')
                sys.exit(1)
            try:
                fp = open(req, 'rb')
            except IOError, e:
                sendmsg(client[0], e_other, str(e))
                sys.exit(1)
            # send info: status code + file size
            client[0].sendall('{0}{1}'.format(chr(e_ok), makefsize(os.stat(req).st_size)))
            
            while 1:
                read = fp.read(opts.send_blocksize)
                if read == '': # at EOF, terminate client
                    fp.close()
                    client[0].close()
                    break
                client[0].sendall(read)
            sys.exit(1) # done with this process
    master.close()
try: main()
except: raiseClient:
#!/usr/bin/env python
import optparse, os, socket, sys
e_ok = 0
fsize_len = 16 # length of file size word
def err(msg, fatal = False):
    sys.stderr.write(msg + '\n')
    if fatal: sys.exit(1)
# turn a series of octets into an integer
def getfsize(word):
    if len(word) != fsize_len:
        err('Bad file size')
    size = i = 0
    while i < fsize_len:
        size <<= 8
        size |= ord(word[i])
        i += 1
    return size
def main():
    parser = optparse.OptionParser(usage = 'Usage: %prog [options] [file1] [file2] ...')
    parser.add_option('-s', '--server', dest = 'server', action = 'store', default = 'localhost', help = 'server to download files from [%default]')
    parser.add_option('-p', '--port', dest = 'port', action = 'store', default = 6666, type = 'int',  help = 'port on server [%default]')
    parser.add_option('-d', '--dl-dir', dest = 'dldir', action = 'store', default = '.',  help = 'directory to place downloaded files in [%default]')
    parser.add_option('--recv-blocksize', dest = 'recv_blocksize', action = 'store', default = 4096, type = 'int', help = 'number of bytes to receive at a time [%default]')
    opts, filenames = parser.parse_args()    
    for filename in filenames:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((opts.server, opts.port))
        print('Requesting file {0}'.format(filename))
        s.sendall(filename)
        inf = s.recv(1 + fsize_len) # status code + file size
        if len(inf) == 0:
            err('Empty response')
            continue
        code = ord(inf[0])
        inf = inf[1:]
        if code != e_ok:
            err('Error: ' + inf)
            continue
        if len(inf) != fsize_len:
            err('Server sent invalid file information')
            continue
        
        already_exists = False
        filename = opts.dldir + '/' + os.path.basename(filename)
        print('Destination: {0}'.format(filename))
        while os.path.exists(filename):
            already_exists = True
            filename += '_'
        if already_exists: print('Already exists, writing to: {0}'.format(filename))
        fp = open(filename, 'wb')
        size = getfsize(inf)
        rsize = 0
        read = ''
        sys.stdout.write('\033[?25l') # hide the cursor
        while 1:
            sys.stdout.write('\033[sDownloading... {0}/{1} {2}%        \033[u'
                .format(rsize, size, int((float(rsize) / float(size)) * 100)))
            read = s.recv(opts.recv_blocksize)
            if read == '': break
            rsize += len(read)
            fp.write(read)
        fp.close()
        s.close()
        print('\033[?25h\nDone.') # show the cursor
try: main()
except:
    sys.stdout.write('\033[?25h') # restore the cursor if something went wrong
    raiseCan has feedback? 
(Note: I'm in the process of reworking clutter from an earlier (read: worse) design, so please point out anything useless in the code.)
Last edited by Peasantoid (2009-05-19 23:11:35)
Offline
sooo, this is basically a read only ftp client-server application without the ftp?
neat 
Offline
Nice and compact. I'll try it out this afternoon.
Offline

nice and minimal... great reference for python beginners. ^^
Offline

lol i was just about to start working on something like this.
"is adult entertainment killing our children or is killing our children entertaining adults?" Marilyn Manson
Offline
Hey thanks, guys.  Wasn't expecting to get such positive feedback — it's my first major Python project.
 Wasn't expecting to get such positive feedback — it's my first major Python project.
I plan to add command-line option support soon, once I figure out Python's getopt module.
Offline

just the thing for you then  
 
Let me know how you get on  
 
"is adult entertainment killing our children or is killing our children entertaining adults?" Marilyn Manson
Offline
just the thing for you then

Let me know how you get on

Thanks, that was great!
On a related note, I've updated the code.
Offline
whoops, missed you code updates. Feel free to delete.
Last edited by keenerd (2009-05-20 13:43:48)
Offline