Cyrozap's Tech Projects

Computers. Circuits. Code.

Python is Fun

Usually, I don't do much programming because it's always taken a lot of work to do even the simplest of things. However, sinceĀ I've discovered Python, I've been having a blast. Just yesterday, I wrote a command line tool to upload files to Box.com. While I could have used somebody else's libraries to do the heavy lifting, I wanted to roll my own code. I thought it would be both more fun and more educational that way, and it was. My initial purpose for writing this program was that I could use it in a cron job to perform server backups. Now, because server backup was my primary goal and I can encrypt the backup archives before uploading, security wasn't much of an issue. Because I wasn't very concerned with the security of the transfers, I didn't bother to do things like check for SSL/TLS certificate validity. Additionally, I put my API key right in the code, so I can't just distribute it and let people see that. If you want it, I've posted it below (with my API key removed, of course).

The license is Public Domain because it's so simple and I only spent around 6 hours writing it. Currently, it can upload multiple files in the same command, but it's not exactly elegant because it makes a new connection for each file. The "proper" way to do it is to use the multiple-file-upload part of the API, but that would have taken a significant rewrite of the upload function and, well, "If it ain't broke, don't fix it!" Oh, and it freaks out if you tell it to upload a folder, so don't do that.

#!/usr/bin/env python
# Box Upload
# This code accepts arguments to upload files.

# WARNING!
# This code does not check SSL certificate validity!
# TODO: Implement certificate validation.

# Get the API key by making a Box application here: http://box.com/developers/services/edit/
api_key = 'replace-with-api-key'

import os
import sys
import urllib
import urllib2
import MultipartPostHandler, cookielib # MultipartPostHandler can be found at: http://pypi.python.org/pypi/MultipartPostHandler2
from xml.dom import minidom

def authenticate(api_key):
    xml_ticket = minidom.parseString(urllib.urlopen('https://www.box.com/api/1.0/rest?action=get_ticket&api_key='+api_key).read()) # This grabs the XML authentication ticket that we'll be using to generate the auth_token.
    status = xml_ticket.firstChild.childNodes[0].firstChild.data # This gets the ticket status to make sure we actually retrieved a valid ticket.
    if status == 'get_ticket_ok': # Did we get a valid ticket? If so, continue. If not, the user needs to check their API key.
        ticket = xml_ticket.firstChild.childNodes[1].firstChild.data
        print 'Please go to https://www.box.com/api/1.0/auth/'+ticket+' and follow the instructions there. Then, press ENTER or RETURN to continue.'
        raw_input()
        xml_auth_token = minidom.parseString(urllib.urlopen('https://www.box.net/api/1.0/rest?action=get_auth_token&api_key='+api_key+'&ticket='+ticket).read()) # This is the auth token in XML format.
        auth_status = xml_auth_token.firstChild.childNodes[0].firstChild.data # Reads the authentication status into "auth_status"
        if auth_status == 'get_auth_token_ok':
            auth_token = xml_auth_token.firstChild.childNodes[1].firstChild.data
            token_path = os.path.expanduser('~/.box_auth_token')
            auth_token_file = open(token_path,'w')
            auth_token_file.write(auth_token)
            auth_token_file.close()
            print 'The authentication token has been saved in the file "'+token_path+'". If you remove this file, you will need to reauthenticate.'
            print ''
            print 'Please run "boxupload" again to initiate your upload.'
            return
        elif auth_status == 'not_logged_in':
            print "I'm sorry, but you haven't authorized me to connect to your account. Please run \"boxupload\" again to restart the authentication process."
        elif auth_status == 'get_auth_token_error':
            print "I'm sorry, but there was an error in retrieving your authentication token. Please run \"boxupload\" again to restart the authentication process."
        else:
            print "Error: "+auth_status+". Try reauthenticating."
    else:
        print 'Error retrieving ticket! Check your API key.'

def get_account_info(api_key,auth_token):
    xml_account_info = minidom.parseString(urllib.urlopen('https://www.box.net/api/1.0/rest?action=get_account_info&api_key='+api_key+'&auth_token='+auth_token).read())
    status = xml_account_info.firstChild.childNodes[0].firstChild.data
    if status == 'get_account_info_ok':
        login = str(xml_account_info.firstChild.childNodes[1].childNodes[0].firstChild.data)
        email = str(xml_account_info.firstChild.childNodes[1].childNodes[1].firstChild.data)
        access_id = int(xml_account_info.firstChild.childNodes[1].childNodes[2].firstChild.data)
        user_id = int(xml_account_info.firstChild.childNodes[1].childNodes[3].firstChild.data)
        space_amount = int(xml_account_info.firstChild.childNodes[1].childNodes[4].firstChild.data) # Total capacity of the account in bytes
        space_used = int(xml_account_info.firstChild.childNodes[1].childNodes[5].firstChild.data) # Data usage of the account in bytes
        max_upload_size = int(xml_account_info.firstChild.childNodes[1].childNodes[6].firstChild.data) # Maximum file upload size in bytes
        account_info = [login, email, access_id, user_id, space_amount, space_used, max_upload_size] # Turn the data into a list for easy access
        return account_info
    elif status == 'not_logged_in':
        print "I'm sorry, your authentication token is invalid. Try removing your \"auth_token\" file and reauthenticating."
    elif status == 'Wrong input':
        print "I'm sorry, the API key you are using is mistyped."

def upload(file,auth_token):
    if not os.path.exists(file):
        print 'ERROR: File \"'+file+'\" was not found!'
        return
    url = 'https://upload.box.net/api/1.0/upload/'+auth_token+'/0'
    cookies = cookielib.CookieJar()
    opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), MultipartPostHandler.MultipartPostHandler)
    params = { "file" : open(file, "rb") }
    xml_response = minidom.parseString(opener.open(url, params).read())
    upload_status = xml_response.firstChild.childNodes[0].firstChild.data
    if upload_status == 'upload_ok':
        print "Upload successful!"
    else:
        print "Upload failure!"

try: # Check for and read auth_token file
    token_path = os.path.expanduser('~/.box_auth_token')
    auth_token_file = open(token_path,'r')
    auth_token = auth_token_file.read()
    pass
except IOError as e:
    authenticate(api_key) # If there's no auth_token, initiate authentication.
    sys.exit(1)

if len(sys.argv) < 2:
    print "usage: "+sys.argv[0]+" file_1 [file_2] [file_3] ... [file_n]"
    sys.exit(1)

# account_info = get_account_info(api_key,auth_token)

''' # Uncomment for debugging.
print 'Auth token: ' + auth_token
print 'Max upload size: ' + str((account_info[6])/1024.0/1024) + ' MB'
print 'Total capacity: ' + str((account_info[4])/1024.0/1024) + ' MB | Space used: ' + str((account_info[5])/1024.0/1024) + ' MB | Space remaining: ' + str((account_info[4]-account_info[5])/1024.0/1024) + ' MB | Percent free: ' + str(100.0*(account_info[4]-account_info[5])/account_info[4]) + '%'
'''

arguments = sys.argv
arguments.pop(0)

for argv in arguments:
    upload(argv,auth_token)

Comments