#!/usr/bin/env python """ zenftp.py A simple service that bridges an FTP client with Zenfolio via SOAP Start zenftp.py, providing the name of the target photoset on Zenfolio and then connect to 127.0.0.1 with your FTP client. Username and password are the same as your Zenfolio account. Usage: zenftp.py [-p local_port] -f photoset_name Requires: pyftpdlib - http://code.google.com/p/pyftpdlib/ suds - https://fedorahosted.org/suds/ poster - http://atlee.ca/software/poster/ http://code.irondojo.com/#zenftp Jonathan Camp jonathan@irondojo.com 0.1 - 06/07/2009 """ from __future__ import with_statement import warnings warnings.filterwarnings("ignore") import os, sys, getopt import urllib2 from poster.encode import multipart_encode from poster.streaminghttp import register_openers from pyftpdlib import ftpserver from suds.client import Client from suds import WebFault TARGET_PHOTOSET = "Incoming" AUTH_TOKENS = dict() TARGET_URLS = dict() class MyAuthorizer(ftpserver.DummyAuthorizer): def validate_authentication(self, username, password): auth_token = authenticate(username, password) if auth_token is None: return False AUTH_TOKENS[username] = auth_token zenfolio = Client("http://www.zenfolio.com/zf/api/zfapi.asmx?wsdl") zenfolio.options.__setattr__('headers', dict({'X-Zenfolio-Token': auth_token})) print("Getting group hierarchy...") root = zenfolio.service.LoadGroupHierarchy(username) group_id = findPhotoSetIdByName(root, TARGET_PHOTOSET) if group_id == -1: print("unable to find photoset: %s" % TARGET_PHOTOSET) return False target_photoset = zenfolio.service.LoadPhotoSet(group_id) url = "http://www.zenfolio.com%s" % (target_photoset.UploadUrl) TARGET_URLS[username] = url dir = "./tmp/%s" % username if not os.path.isdir("./tmp"): os.mkdir("./tmp") if not os.path.isdir(dir): os.mkdir(dir) try: self.remove_user(username) except: pass self.add_user(username, password, dir, perm="wle") return True class MyHandler(ftpserver.FTPHandler): def on_file_received(self, file): if not os.path.splitext(file.lower())[1] in (".gif", ".jpg", ".jpeg", ".tiff", ".tif", ".png"): print("Zenfolio does not accept filetype: %s" % (os.path.splitext(file.lower())[1])) os.remove(file) return print("uploading...") url = TARGET_URLS[self.username] auth_token = AUTH_TOKENS[self.username] id = uploadPhoto(file, url, auth_token) os.remove(file) print("%s uploaded to Zenfolio (id=%s)" % (file, id)) def authenticate(username, password): print("Authenticating...") zenfolio = Client("https://www.zenfolio.com/zf/api/zfapi.asmx?wsdl") if zenfolio is None: return None try: return zenfolio.service.AuthenticatePlain(username, password) except WebFault, f: return None def findPhotoSetIdByName(group, name, depth = 0): if group is None: return -1 if group.__class__.__name__ == "Group": for subgroup in group.Elements: if subgroup[0] == "Group": ret_val = findPhotoSetIdByName(subgroup[1], name, depth + 1) if ret_val != -1: return ret_val elif subgroup[0] == "PhotoSet": if subgroup[1].__class__.__name__ == "PhotoSet": if subgroup[1].Title == name: return subgroup[1].Id else: for item in subgroup[1]: if item.Title == name: return item.Id elif group.__class__.__name__ == "list": for item in group: ret_val = findPhotoSetIdByName(item, name, depth + 1) if ret_val != -1: return ret_val return -1 def uploadPhoto(file_name, url, auth_token): with open(file_name, "rb") as fimg: datagen, headers = multipart_encode({"file": fimg}) headers["X-Zenfolio-Token"] = auth_token request = urllib2.Request(url, datagen, headers) response = urllib2.urlopen(request) new_photo_id = response.read() return new_photo_id if __name__ == "__main__": try: optlist, args = getopt.getopt(sys.argv[1:], "p:f:", ["port=", "photoset-name="]) except getopt.GetoptError, err: print(str(err)) print("Usage: zenftp.py [-p local_port] -f photoset_name") sys.exit(2) register_openers() local_port = 21 for o, a in optlist: if o == ("-p", "--port"): local_port = int(a) elif o == ("-f", "--photoset-name"): TARGET_PHOTOSET = a ftp_handler = MyHandler ftp_handler.authorizer = MyAuthorizer() ftpd = ftpserver.FTPServer(("127.0.0.1", local_port), ftp_handler) #ftpd.max_cons = 2 ftpd.serve_forever()