#!/usr/bin/env python

import getopt
import httplib
import md5
import mimetypes
import os
import sys
import time
import urllib
import urlparse

# replace the following two lines with your key/secret
# login to flickr and go to http://www.flickr.com/services/api/key.gne
apiKey = '45584dff059489d06fdf471a5b7d9378'
sharedSecret = '46a396cf11c5df3c'
restUrl = 'http://www.flickr.com/services/rest/'
tokenFile = os.environ['HOME'] + '/.flickr-upload-token'


def usage(msg=None):
    progname = sys.argv[0]

    if msg:
        sys.stderr.write(msg + '\n')

    sys.stderr.write('''\
usage: %s [options] pic.jpg [pic2.jpg pic3.gif ...]
Optional arguments:
\t-t|--title="a title"
\t-d|--description="a description"
\t-T|--tags="tag1 tag2 ..."
\t-p|--public=1 (0 for no, 1 for yes, defaults to yes)
\t-F|--friend=0 (defaults to no)
\t-A|--family=0 (defaults to no)
''' % (progname))
    sys.exit(1)

def getopts():

    optArgs = { 'title' : '',
             'description' : '',
             'tags' : '',
             'is_public' : '1',
             'is_friend' : '0',
             'is_family' : '0'
             }
    
    try:
        opts, args = getopt.getopt(sys.argv[1:], "t:d:T:p:F:A:",
                                   ["title=", "description=", "tags=",
                                    "public=", "friend=", "family="])
    except getopt.GetoptError:
        usage('invalid options specified')

    # we don't need no steenking switch
    for o, a in opts:
        if o in ('-t', '--title'):
            optArgs['title'] = a
        elif o in ('-d', '--description'):
            optArgs['description'] = a
        elif o in ('-T', '--tags'):
            optArgs['tags'] = ' ' + a
        elif o in ('-p', '--public'):
            optArgs['is_public'] = a
        elif o in ('-F', '--friend'):
            optArgs['is_friend'] = a
        elif o in ('-A', '--family'):
            optArgs['is_family'] = a

    if not args:
        usage('name of file to upload missing')

    return (optArgs, args)


def sign(argsDict):
    """Return a signature."""

    keys = argsDict.keys()
    keys.sort()
    str = sharedSecret
    for k in keys:
        str = str + k + argsDict[k]
    return md5.new(str).hexdigest()


def getFrob():
    """Get Frob for auth."""

    sys.stderr.write('getting frob...\n')

    d = { 'api_key' : apiKey,
          'method' : 'flickr.auth.getFrob'
          }

    u = urllib.urlopen('%s?method=flickr.auth.getFrob&api_key=%s&api_sig=%s' %
                       (restUrl, apiKey, sign(d)))
    lines = u.readlines()
    u.close()

    if lines[1] == '<rsp stat="ok">\n':
        return lines[2].replace('<frob>', '').replace('</frob>', '').strip()
    else:
        raise 'oops: %s' % (lines)


def getTokenFromFile():
    """Check if we have a valid saved token."""

    # we really should check the token with flickr.auth.checkToken
    if os.access(tokenFile, os.R_OK):
        f = file(tokenFile, 'r')
        line = f.readline()
        f.close()
        (token, date) = line.split()
        now = time.time()
        if now - long(date) < 3600:
            sys.stderr.write('found valid token saved on disk...\n')
            return token
        else:
            return None


def getToken(frob):
    """Get auth token after the user has authorized us."""

    sys.stderr.write('getting auth token...\n')


    d = { 'api_key' : apiKey,
          'method' : 'flickr.auth.getToken',
          'frob' : frob
          }

    u = urllib.urlopen('%s?method=flickr.auth.getToken&api_key=%s&frob=%s&api_sig=%s' %
                       (restUrl, apiKey, frob, sign(d)))
    lines = u.readlines()
    u.close()

    if lines[1] == '<rsp stat="ok">\n':
        for l in lines[1:]:
            if l.find('<token>') != -1:
                token = l.replace('<token>', '').replace('</token>', '').strip()
                os.umask(0077)
                f = file(tokenFile, 'w')
                f.write('%s %ld' % (token, time.time()))
                f.close()
                return token
    else:
        raise 'oops: %s' % (lines)

# three functions from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306
def post_url(url, fields, files):

    urlparts = urlparse.urlsplit(url)
    return post_multipart(urlparts[1], urlparts[2], fields, files)

def post_multipart(host, selector, fields, files):

    content_type, body = encode_multipart_formdata(fields, files)
    h = httplib.HTTPConnection(host)  
    headers = {
        'User-Agent': 'UploaderForToshok/2005-10-05',
        'Content-Type': content_type
        }
    h.request('POST', selector, body, headers)
    res = h.getresponse()
    return res.read()


def encode_multipart_formdata(fields, files):
    """
    fields is a sequence of (name, value) elements for regular form fields.
    files is a sequence of (name, filename, value) elements for data to be uploaded as files
    Return (content_type, body) ready for httplib.HTTP instance
    """

    BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
    CRLF = '\r\n'
    body = ''
    for (key, value) in fields.items():
        body = body + '--' + BOUNDARY + '\r\n'
        body = body + 'Content-Disposition: form-data; name="%s"\r\n\r\n' % key 
        body = body + value + '\r\n'
    for (filename, value) in files:
        body = body + '--' + BOUNDARY + '\r\n'
        body = body + 'Content-Disposition: form-data; name="photo"; filename="%s"\r\n' % (filename)
        body = body + 'Content-Type: %s\r\n\r\n' % get_content_type(filename)
        body = body + value
    body = body + '--' + BOUNDARY + '--\r\n\r\n'
    content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
    return content_type, body


def get_content_type(filename):
    guess = mimetypes.guess_type(filename)[0]
    sys.stderr.write('guessing file type is %s\n' % (guess))
    return guess


def uploadImage(optsDict, token, fname):
    """Upload the image."""

    sys.stderr.write('uploading %s...\n' % (fname))

    d = { 'api_key' : apiKey,
          'auth_token' : token
          }

    for o in ('title', 'description', 'tags', 'is_public', 'is_friend', 'is_family'):
        if optsDict[o]:
            d[o] = str(optsDict[o])

    api_sig = sign(d)
    d['api_sig'] = api_sig

    ret = post_url('http://flickr.com/services/upload/', d,
                   [[fname, file(fname, 'rb').read()]])
    sys.stderr.write(ret + '\n')



def main():
    (optsDict, fnames) = getopts()
    
    frob = getFrob()
    token = getTokenFromFile()

    if not token:
        d = { 'api_key' : apiKey,
              'perms' : 'write',
              'frob' : frob
              }

        # url to send the user
        u = 'http://flickr.com/services/auth/?api_key=%s&perms=write&frob=%s&api_sig=%s' % \
            (apiKey, frob, sign(d))

        print 'you must authorize me.  sending you to\n\n%s\n' % (u)
        print 'hit return after authorizing me.'
        os.system('firefox -remote "openURL(%s,new-tab)"' % (u))
        # os.system('lynx "%s"' % (u))
        ignore = sys.stdin.readline()
        token = getToken(frob)

    for fname in fnames:
        uploadImage(optsDict, token, fname)



if __name__ == '__main__': main()
