//
// BEGIN FLOCK GPL
// 
// Copyright Flock Inc. 2005-2006
// http://flock.com
// 
// This file may be used under the terms of of the
// GNU General Public License Version 2 or later (the "GPL"),
// http://www.gnu.org/licenses/gpl.html
// 
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
// 
// END FLOCK GPL
//

const DEBUG = false;                // set to false to suppress debug messages
const FLOCK_PHOTO_UPLOAD_CID               = Components.ID('{9d499deb-fd01-43d5-9eda-91783a37f3bc}');
const FLOCK_PHOTO_UPLOAD_FNAME             = "flock_photo_uploads.rdf";

const nsISupports                   = Components.interfaces.nsISupports;
const nsIClassInfo                  = Components.interfaces.nsIClassInfo;
const nsIFactory                    = Components.interfaces.nsIFactory;
const nsIProperties                 = Components.interfaces.nsIProperties;
const nsILocalFile                  = Components.interfaces.nsILocalFile;
const nsIFile                       = Components.interfaces.nsIFile;
const nsIIOService                  = Components.interfaces.nsIIOService;
const nsIFileProtocolHandler        = Components.interfaces.nsIFileProtocolHandler;
const nsIRDFRemoteDataSource        = Components.interfaces.nsIRDFRemoteDataSource;
const nsIRDFDataSource              = Components.interfaces.nsIRDFDataSource;
const flockIPhotoUploadService      = Components.interfaces.flockIPhotoUploadService;
const flockIPhotoAPI                = Components.interfaces.flockIPhotoAPI;
const flockIPhotoUpload = Components.interfaces.flockIPhotoUpload;
const FLOCK_PHOTO_UPLOAD_CONTRACTID = '@flock.com/photo-upload;1';

const FLOCK_PHOTO_UPLOADS_CONTRACTID        = '@mozilla.org/rdf/datasource;1?name=flock-photo-uploads';
const DIRECTORY_SERVICE_CONTRACTID  = '@mozilla.org/file/directory_service;1';
const LOCAL_FILE_CONTRACTID         = '@mozilla.org/file/local;1';
const PREFERENCES_CONTRACTID        = '@mozilla.org/preferences-service;1';
const IO_SERVICE_CONTRACTID         = '@mozilla.org/network/io-service;1';

const FLOCK_NS                      = 'http://flock.com/rdf#';
const FLOCK_RDF_UPLOAD_ROOT         = 'urn:flock:photo:upload';



function flockPhotoUploadService() { /*{{{*/
    dump ('CONSTRUCTOR: flockPhotoUploadService\n');
    var profd = Components.classes[DIRECTORY_SERVICE_CONTRACTID].
        getService(nsIProperties).get('ProfD', nsIFile);
    var file = Components.classes[LOCAL_FILE_CONTRACTID]
        .createInstance(nsILocalFile);
    try {
        file.initWithPath(profd.path);
    }
    catch(e) {
        var tmpd = Components.classes[DIRECTORY_SERVICE_CONTRACTID].
            getService(nsIProperties).get('TmpD', nsIFile);
        file.initWithPath(tmpd.path);
    }

    file.append(FLOCK_PHOTO_UPLOAD_FNAME);
    if(!file.exists()) file.createUnique(0,0600);

    var ios = Components.classes['@mozilla.org/network/io-service;1']
        .getService(Components.interfaces.nsIIOService);

    var fileHandler = ios.getProtocolHandler('file')
       .QueryInterface(Components.interfaces.nsIFileProtocolHandler);

    var spec = fileHandler.getURLSpecFromFile(file);
    
    this.listeners = Array();
    this.ds = new RDFDataSource(spec);
    this.dataSource = this.ds.getRawDataSource()
        .QueryInterface (nsIRDFRemoteDataSource);
    this.uploadRoot = this.ds.getNode(FLOCK_RDF_UPLOAD_ROOT);
    this.uploadRoot.makeSeq();

    var inst = this;
    this.mTimerFunc = {
        notify: function(aTimer) {
            inst.Flush();
        },
    }
    this.mTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
    this.running = false;
}/*}}}*/


flockPhotoUploadService.prototype.doSave = function(aListener) {
    this.mTimer.cancel();
    this.mTimer.initWithCallback(this.mTimerFunc,2*1000,0);//re-check token
}

flockPhotoUploadService.prototype.addListener = function(aListener) {
    this.listeners.push(aListener);
    dump("adding photo upload listener " + this.listeners.length + "\n");
}

flockPhotoUploadService.prototype.removeListener = function(aListener) {
    for(var i=0;i<this.listeners.length;++i) {
        if(aListener==this.listeners[i]) {
            this.listeners.splice(i,1);
            break;
        }
    }
}


flockPhotoUploadService.prototype.save = function (aListener) { /*{{{*/
    this.Flush();
}/*}}}*/


flockPhotoUploadService.prototype.getURI = function (aURI) { /*{{{*/
    return aURI; 
}/*}}}*/

flockPhotoUploadService.prototype.dictionary2Params = function (aDictionary) { /*{{{*/
    var params = {};
    var obj = {};
    var count = {};
    var keys = aDictionary.getKeys(count, obj);
    for(var i=0;i<keys.length;++i) {
        var supports = aDictionary.getValue(keys[i]);
        var supportsString = supports.QueryInterface(Components.interfaces.nsISupportsString);
        var val = supportsString.toString();
        params[keys[i]] = val;
    }
    return params;
}/*}}}*/


flockPhotoUploadService.prototype.cancelUpload = function () { /*{{{*/
    this.uploadListener.stop();
}/*}}}*/

flockPhotoUploadService.prototype.__defineGetter__('isUploading', function () { return this.running; })

flockPhotoUploadService.prototype.singleUpload = function(aListener, aAPI, aUpload) { /*{{{*/
    var url = this.doTransform(aUpload);
    aAPI.upload2(aListener, aUpload, url);// note that this doesn't use the parameter stuff
return;
/*
    for(var p in aListener) {
        dump(">>> " + p + " " + aListener[p] + "\n");
    }
    var uploadListener = {
        onResult: function() {
            aListener.onResult();
        },
        onUploadComplete: function(aUpload) {
            aListener.onUploadComplete(aUpload);
        },
        onUploadFinalized: function(aUpload, aPhoto) {
            aListener.onUploadFinalized(aUpload, aPhoto);
        },
        onError: function(aError) {
            aListener.onError(aError);
        },
    }
*/
/*
    var params = new Array();
    params.title = aUpload.title;
    params.description = aUpload.description;
    params.is_family = aUpload.is_family;
    params.is_friend = aUpload.is_friend;
    params.is_public = aUpload.is_public;
    params.async = "1";
    params.tags = aUpload.tags;
    this.svc = this;   
    this.currentUpload = aUpload;
    this.count = 0;
    this.max = 1;
    
    var url=aUpload.originalFileSpec;
    
    var inst = this;
    
    dump(inst.svc + '\n\n\n\n');
    
    var uploadListener = {
        onResult: function() {
            for(var i=0;i<inst.svc.listeners.length;++i) {
                inst.svc.listeners[i].onUpload(this.currentUpload, this.count, this.max);
            }
        },
        onUploadComplete: function(aUpload) {
            for(var i=0;i<inst.svc.listeners.length;++i) {
                inst.svc.listeners[i].onUploadComplete(aUpload, this.count, this.max);
            }
        },
        onUploadFinalized: function(aUpload) {
        },
        onError: function(aError) {
            for(var i=0;i<inst.svc.listeners.length;++i) {
                if(inst.svc.running)//don't send back error on cancelUpload
                    inst.svc.listeners[i].onUploadError(this.currentUpload, this.count, this.max, null);
            }
            this.stop();
            dump("error uploading\n");
        },
         stop: function() {
            inst.svc.running = false;
            aAPI.cancelUpload();
            for(var i=0;i<inst.svc.listeners.length;++i) {
                inst.svc.listeners[i].onUploadComplete();
            }
        },
    }
    
    dump(aAPI.shortName + '\n\n\n\n\n\n');
    //aAPI.upload(uploadListener, url, params2Dictionary(params), aUpload);
    aAPI.upload2(uploadListener, aUpload, url);// note that this doesn't use the parameter stuff
    */
}/*}}}*/

flockPhotoUploadService.prototype.getUniqueFname = function(aFName) {
    aFName.match(/.+\.(.+)/);
    var ext = RegExp.$1;
    if(!ext) ext = "";
    var now = new Date();
    var sinkFileName = hex_md5(aFName + now + flock.photo.util.callCount++);
    sinkFileName = sinkFileName + "." + ext;
    return sinkFileName;
}

function getOtherFName(aFName, aType) {
    aFName.match(/.+\.(.+)/);
    var ext = RegExp.$1;
    dump(aFName + "." + aType + "." + ext + "\n");
    return aFName + "." + aType + "." + ext;
}

flockPhotoUploadService.prototype.cropPhotoUpload = function (aPhotoUpload, aX1, aY1, aX2, aY2) {
    var rotation = parseInt(aPhotoUpload.rotation);
    var cropString = aPhotoUpload.crop;
    var coords = cropString.split(",");
    var x1, y1, x2, y2;
    x1 = parseFloat(coords[0]);
    y1 = parseFloat(coords[1]);
    x2 = parseFloat(coords[2]);
    y2 = parseFloat(coords[3]);

    // rotate coords back to coordinate system of the original photo
    function rotateLeftNinetyDegrees() {
        var tmp = aX1;
        aX1 = aY1;
        aY1 = 1 - aX2;
        aX2 = aY2;
        aY2 = 1 - tmp;
    }

    var tmp = rotation;
    while(tmp%360!=0) {
        rotateLeftNinetyDegrees();
        tmp -= 90;
    }

    var oldW = x2 - x1;
    var oldH = y2 - y1;

    aX1 = x1 + oldW * aX1;
    aX2 = x1 + oldW * aX2;

    aY1 = y1 + oldH * aY1;
    aY2 = y1 + oldH * aY2;

    this.regenerateThumbs(aPhotoUpload, rotation, aX1, aY1, aX2, aY2);
}

flockPhotoUploadService.prototype.revertPhotoUpload = function (aPhotoUpload) {
    this.regenerateThumbs(aPhotoUpload,0,0,0,1,1);
}

flockPhotoUploadService.prototype.doCrop= function (aPhotoUpload, aDegrees, aSize) {

    var originalPath = aPhotoUpload.originalFilePath;
    var workingPath = aPhotoUpload.workingFilePath;
    var newWorkingFileName = flock.photo.util.getUniqueFname(workingPath);
    var newWorkingFile = flock_getTmpFile(newWorkingFileName); 
    var cropString = aPhotoUpload.crop;

    var rotation = parseInt(aPhotoUpload.rotation);
    var degrees = rotation;
    var size = null;

    if(aDegrees!=null) degrees = aDegrees;
    if(aSize!=null) size = aSize;
    // do the crop
    if(cropString=="0,0,1,1" && aDegrees==0) {
        flock.photo.util.resize(size, originalPath, newWorkingFile.path);
    }
    else {
        var coords = cropString.split(",");
        var x1, y1, x2, y2;
        x1 = parseFloat(coords[0]);
        y1 = parseFloat(coords[1]);
        x2 = parseFloat(coords[2]);
        y2 = parseFloat(coords[3]);


        var origDims = flock.photo.util.getDimensions(originalPath);

        var x = Math.floor(x1 * origDims.x);
        var y = Math.floor(y1 * origDims.y);
        var w = Math.ceil((x2 - x1) * origDims.x);
        var h = Math.ceil((y2 - y1) * origDims.y);
        flock.photo.util.cropExt(originalPath,
                          newWorkingFile.path,
                          w,
                          h,
                          x,
                          y,
                          degrees,
                          size,
                          origDims.px,
                          origDims.py
                          );
    }

    // create the working file
    return newWorkingFile;
}

flockPhotoUploadService.prototype.regenerateThumbs = function (aPhotoUpload, aDegrees, aX1, aY1, aX2, aY2) {
    if(aX1!=null) {
        aPhotoUpload.crop = aX1 + "," + aY1 + "," + aX2 + "," + aY2;
        var newWorkingFile = this.doCrop(aPhotoUpload, 0, FLOCK_PREVIEW_SIZE);
        var newWorkingSpec = flock.photo.util.getURLFromFile(newWorkingFile);
        flock.photo.util.removeFileByPath(aPhotoUpload.workingFilePath);
        aPhotoUpload.workingFilePath = newWorkingFile.path;
        aPhotoUpload.workingFileSpec = newWorkingSpec;
    }

    var originalPath = aPhotoUpload.originalFilePath;
    var previewPath = aPhotoUpload.previewFilePath;
    var thumbPath = aPhotoUpload.thumbFilePath;
    var workingPath = aPhotoUpload.workingFilePath;

    var rotation = aDegrees;
    var newPreviewFileName = flock.photo.util.getUniqueFname(previewPath);
    var newPreviewFile = flock_getTmpFile(newPreviewFileName); 
    flock.photo.util.rotate(rotation, aPhotoUpload.workingFilePath, newPreviewFile.path);

    var newPreviewSpec = flock.photo.util.getURLFromFile(newPreviewFile);

    var newThumbFileName = flock.photo.util.getUniqueFname(thumbPath);
    var newThumbFile = flock_getTmpFile(newThumbFileName); 
    flock.photo.util.thumb(FLOCK_THUMB_SIZE, newPreviewFile.path, newThumbFile.path);
    var newThumbSpec = flock.photo.util.getURLFromFile(newThumbFile);

    aPhotoUpload.previewFilePath = newPreviewFile.path;
    aPhotoUpload.previewFileSpec = newPreviewSpec;

    aPhotoUpload.thumbFilePath = newThumbFile.path;
    aPhotoUpload.thumbFileSpec = newThumbSpec;
    aPhotoUpload.rotation = rotation + "";

    flock.photo.util.removeFileByPath(thumbPath);
    flock.photo.util.removeFileByPath(previewPath);
}

flockPhotoUploadService.prototype.rotatePhotoUpload = function (aPhotoUpload, aDegrees) {
    var rotation = (parseInt(aPhotoUpload.rotation) + aDegrees) % 360;
    this.regenerateThumbs(aPhotoUpload, rotation, null, null, null, null);
}

flockPhotoUploadService.prototype.createPhotoUpload = function (aListener, aUrl) {
    var url = aUrl;
	var URIFixup = Components.classes["@mozilla.org/docshell/urifixup;1"].getService(Components.interfaces.nsIURIFixup);
	var uri = URIFixup.createFixupURI(url, URIFixup.FIXUP_FLAG_NONE);
	
   	var sourceFile = flock_getFileFromURL(url);
	var sourceBufferStream = flock_getBufferredInputStream(sourceFile);
  	var sourceTitle = sourceFile.leafName;
  	var sourceFilename = url;
      	
    var sinkFileName = this.getUniqueFname(sourceFilename);
    var sinkFile = flock_getTmpFile(sinkFileName);
    var sinkBufferStream = flock_getBufferredOutputStream(sinkFile);

    var asyncCopier = Components.classes["@mozilla.org/network/async-stream-copier;1"].createInstance(Components.interfaces.nsIAsyncStreamCopier);
    asyncCopier.init(sourceBufferStream, sinkBufferStream, null, true, true, FLOCK_IO_CHUNKSIZE);

    var inst = this;

    var requestObserver = {
        fname: sourceTitle,
        asyncCopier: asyncCopier,
        url: url,
        sinkFile: sinkFile,
        sinkFileName: sinkFileName,
        preview: false,
        onStartRequest: function(aRequest, aContext) {
        },
        onStopRequest: function(aRequest, aContext) {
            var sourcePath = this.sinkFile.path;
            var thumbPath = getOtherFName(sourcePath, "thumb");
            var previewPath = getOtherFName(sourcePath, "preview");
            var workingPath = getOtherFName(sourcePath, "working");
         
            // make a copy of the original to a working file
            flock.photo.util.resizeAsync(FLOCK_PREVIEW_SIZE, sourcePath, previewPath, this);
        },
        onFinished: function(aResult) {
        	dump("onFinished: " + aResult + "\n");
            var sourcePath = this.sinkFile.path;
            var thumbPath = getOtherFName(sourcePath, "thumb");
            var previewPath = getOtherFName(sourcePath, "preview");
            var workingFilePath = getOtherFName(sourcePath, "working");
            try {
                if(!this.preview) {
                    var previewFile = flock.photo.util.getFileFromPath(previewPath);
                    previewFile.copyTo(null, getOtherFName(this.sinkFile.leafName, "working"));
                    this.preview = true;
                    flock.photo.util.thumbAsync(FLOCK_THUMB_SIZE, previewPath, thumbPath, this);
                } else {
                    var thumbFile = flock.photo.util.getFileFromPath(thumbPath);
                    var thumbSpec = flock_getURLFromFile(thumbFile);

                    var previewFile = flock.photo.util.getFileFromPath(previewPath);
                    var previewSpec = flock_getURLFromFile(previewFile);
                    
                    var workingFile = flock.photo.util.getFileFromPath(workingFilePath);
                    
                    var date = new Date();
                    //var photo = {};
                    var photo = Components.classes[FLOCK_PHOTO_UPLOAD_CONTRACTID].createInstance(flockIPhotoUpload);
                    photo.originalFileSpec = flock_getURLFromFile(this.sinkFile);
                    photo.originalFilePath = sourcePath;
                    photo.workingFilePath = workingFilePath;
                    photo.thumbFileSpec = thumbSpec;
                    photo.thumbFilePath = thumbPath;
                    photo.previewFileSpec = previewSpec;
                    photo.previewFilePath = previewPath;
                    photo.is_public = "true";
                    photo.tags = "";
                    photo.description = "";
                    photo.is_friend = "false";
                    photo.is_family = "false";
                    photo.title = this.fname;
                    photo.fileName = this.fname;
                    photo.state = "";
                    photo.rotation = "0";
                    photo.crop = "0,0,1,1";
                    photo.id = "uri:" + hex_md5(sourcePath);
                    photo.album = ""; // null for now
                    aListener.onCreatePhotoUpload(photo);
                }
            } catch (ex) {
                 aListener.onUploadCreateError(this.fname);
            }
        },
    };

    asyncCopier.asyncCopy(requestObserver, requestObserver.sinkFile);
}

flockPhotoUploadService.prototype.doTransform = function (aUpload) {
    var url = aUpload.originalFileSpec;
    var path = aUpload.originalFilePath;

    // if there are no changes to be made do not create new copy (as it changes jpg compression), just return path
    // see bug 3891 

    if(flock_getBoolPref(FLOCK_PREF_PHOTO_RESIZE)==true || aUpload.crop!="0,0,1,1" || parseInt(aUpload.rotation)!=0) {
        var maxDim = null;
        if(flock_getBoolPref(FLOCK_PREF_PHOTO_RESIZE)==true) {
          var maxDim = flock_getIntPref(FLOCK_PREF_PHOTO_RESIZE_DIMENSIONS);
        }
        var newFile = this.doCrop(aUpload, null, maxDim);
        url = flock_getURLFromFile(newFile);

        flock.photo.util.transferExif(aUpload.originalFileSpec, url);
        return url;
    }

    return aUpload.originalFileSpec;
}

flockPhotoUploadService.prototype.upload = function (aAPI) { /*{{{*/
    if(this.isUploading) { return; }
    var inst = this;
    this.api = aAPI;
    this.uploadListener = {
        init: function(aAPI, aKids, aSvc, aMax) {
            this.api = aAPI;
            this.svc = aSvc;
            this.kids = aKids;
            this.count = 0;
            this.max = aMax;
            this.imageUtils = flock_getImageUtils();
            this.svc.running = true;
            this.doUpload();
        },
        stop: function() {
            this.svc.doSave();
            this.svc.running = false;
            this.api.cancelUpload();
            for(var i=0;i<this.svc.listeners.length;++i) {
                this.svc.listeners[i].onUploadComplete();
            }
        },
        doUpload: function() {
            if(!this.svc.running || !this.kids.hasMoreElements()) {
                if(this.svc.running) this.stop();
                return;
            }
            ++this.count;
            this.uploadResource = this.kids.getNext();
            var upload = this.svc.getPhotoUpload(this.uploadResource.getTarget(FLOCK_NS + "id").getValue());
            var url = inst.doTransform(upload);
            var params = new Array();
            params.title = upload.title;
            params.description = upload.description;
            params.is_family = upload.is_family;
            params.is_friend = upload.is_friend;
            params.is_public = upload.is_public;
            params.async = "1";
            params.tags = upload.tags;
            params.album = upload.album;
            this.currentUpload = upload;
            this.uploadResource.addTargetOnce(FLOCK_NS + "state", "uploading");
            for(var i=0;i<this.svc.listeners.length;++i) {
                this.svc.listeners[i].onUploadStart(this.currentUpload, this.count, this.max);
            }
            //this.api.upload(this, url, params2Dictionary(params), upload);
            this.api.upload2(this, upload, url);// note that this doesn't use the parameter stuff
        },
/*
        onResult: function() {
            for(var i=0;i<this.svc.listeners.length;++i) {
                this.svc.listeners[i].onUpload(this.currentUpload, this.count, this.max);
            }
            this.svc.removePhotoUpload(this.currentUpload, false);
            this.doUpload();
        },
*/
        onUploadComplete: function(aUpload) {
            this.doUpload();
            for(var i=0;i<this.svc.listeners.length;++i) {
                this.svc.listeners[i].onUpload(aUpload, this.count, this.max);
            }
            this.svc.removePhotoUpload(aUpload, false);
        },
        onUploadFinalized: function(aUpload, aPhoto) {
        },
        onError: function(aError) {
            this.uploadResource.addTargetOnce(FLOCK_NS + "state", "error");
            for(var i=0;i<this.svc.listeners.length;++i) {
                if(this.svc.running) //don't send back error on cancelUpload
                    this.svc.listeners[i].onUploadError(this.currentUpload, this.count, this.max, aError);
            }
            this.stop();
            dump("error uploading\n");
        },
    };
    var kids = this.uploadRoot.getChildren();
    
    // XXX have to reverse the order of the child nodes
    // so that the service gets the oldest photo first uploaded.
    var orderedKidsEnumerator  = {
        mList: Array(),
        currentIndex: 0,
        add : function(aItem) {
            this.mList.unshift(aItem);
        },
        getNext : function() {
            this.currentIndex++;
            return this.mList[this.currentIndex-1];
        },
        hasMoreElements : function() {
            return this.mList[this.currentIndex] != null ? true : false;
        },
        getChildCount: function() {
            return this.mList.length;
        }
    }
    while (kids.hasMoreElements())
        orderedKidsEnumerator.add(kids.getNext());
        
    this.uploadListener.init(this.api, orderedKidsEnumerator, this, orderedKidsEnumerator.getChildCount());
}/*}}}*/

flockPhotoUploadService.prototype.isNewUpload = function (aUpload) {
    if(aUpload.getTarget(FLOCK_NS + "id")) return false;
    return true;
}

flockPhotoUploadService.prototype.savePhotoUpload = function (aUpload) { /*{{{*/
    debug("flockPhotoUploadService.prototype.savePhotoUpload " + aUpload.id + "\n");
    var upload = this.ds.getNode(this.getURI(aUpload.id));
    upload.addTargetOnce(FLOCK_NS + "id", aUpload.id);
    upload.addTargetOnce(FLOCK_NS + "originalFileSpec", aUpload.originalFileSpec);
    upload.addTargetOnce(FLOCK_NS + "originalFilePath", aUpload.originalFilePath);
    upload.addTargetOnce(FLOCK_NS + "workingFilePath", aUpload.workingFilePath);
    upload.addTargetOnce(FLOCK_NS + "thumbFilePath", aUpload.thumbFilePath);
    upload.addTargetOnce(FLOCK_NS + "thumbFileSpec", aUpload.thumbFileSpec);
    upload.addTargetOnce(FLOCK_NS + "previewFilePath", aUpload.previewFilePath);
    upload.addTargetOnce(FLOCK_NS + "previewFileSpec", aUpload.previewFileSpec);
    upload.addTargetOnce(FLOCK_NS + "fileName", aUpload.fileName);
    upload.addTargetOnce(FLOCK_NS + "title", aUpload.title);
    upload.addTargetOnce(FLOCK_NS + "is_public", aUpload.is_public);
    upload.addTargetOnce(FLOCK_NS + "is_friend", aUpload.is_friend);
    upload.addTargetOnce(FLOCK_NS + "is_family", aUpload.is_family);
    upload.addTargetOnce(FLOCK_NS + "tags", aUpload.tags);
    upload.addTargetOnce(FLOCK_NS + "state", aUpload.state);
    upload.addTargetOnce(FLOCK_NS + "description", aUpload.description);
    upload.addTargetOnce(FLOCK_NS + "rotation", aUpload.rotation);
    upload.addTargetOnce(FLOCK_NS + "album", aUpload.album);   
    upload.addTargetOnce(FLOCK_NS + "crop", aUpload.crop);
    this.doSave();
}/*}}}*/

flockPhotoUploadService.prototype.addPhotoUpload = function (aReference, aUpload) { /*{{{*/
    debug("flockPhotoUploadService.prototype.addPhotoUpload " + aUpload.state + "\n");
    for(var i=0;i<this.listeners.length;++i) {
        this.listeners[i].onUploadAdd(null);
    }
    //this.removePhotoUpload(aUpload, true);

    //var toRemove = this.ds.getNode(this.getURI(aUpload.id));
    var newUpload = this.ds.getNode(this.getURI(aUpload.id));
    this.uploadRoot.removeChildForReal(newUpload);

    if(this.isNewUpload(newUpload)) {
        this.savePhotoUpload(aUpload);
    }
    if(aReference) {
        var ref = this.ds.getNode(this.getURI(aReference.id));
        var index = this.uploadRoot.getChildIndex(ref);
        this.uploadRoot.addChildAt(newUpload, index);
    }
    else {
        this.uploadRoot.addChildAt(newUpload, 1);
    }
    this.doSave();
    for(var i=0;i<this.listeners.length;++i) {
        this.listeners[i].onUploadAdd(aUpload);
    }
}/*}}}*/


flockPhotoUploadService.prototype.removePhotoUpload = function (aUpload, aDontDelete) { /*{{{*/
    try {
        aUpload = this.getPhotoUpload(aUpload.id);
    }
    catch(e) {}
    var toDelete = this.ds.getNode(this.getURI(aUpload.id));
    this.uploadRoot.removeChild(toDelete);
    this.ds.deleteRecursive(toDelete);
    this.doSave();
    if(!aDontDelete) {
        try {
        flock.photo.util.removeFileByPath(aUpload.workingFilePath);
        flock.photo.util.removeFileByPath(aUpload.originalFilePath + "_backup");
        flock.photo.util.removeFileBySpec(aUpload.originalFileSpec);
        flock.photo.util.removeFileBySpec(aUpload.previewFileSpec);
        flock.photo.util.removeFileBySpec(aUpload.thumbFileSpec);
        flock.photo.util.removeFileByPath(aUpload.originalFilePath);
        flock.photo.util.removeFileByPath(aUpload.previewFilePath);
        flock.photo.util.removeFileByPath(aUpload.thumbFilePath);
        } catch(e) {
        }
    }
    for(var i=0;i<this.listeners.length;++i) {
        this.listeners[i].onUploadRemove();
    }
}/*}}}*/

flockPhotoUploadService.prototype.reorderPhotoUpload = function (aReference, aUpload) { /*{{{*/
    this.addPhotoUpload();
    return;
/*
    //I wish the code below worked :( no time to investigate right now
    //this.removePhotoUpload(aUpload);
    var upload = this.ds.getNode(this.getURI(aUpload.id));
    this.uploadRoot.removeChild(upload);
    var ref = this.ds.getNode(this.getURI(aReference.id));
    if(aReference) {
        var index = this.uploadRoot.getChildIndex(ref);
        this.uploadRoot.addChildAt(upload, index);
    }
    else {
        this.uploadRoot.addChild(upload);
    }
    this.Flush();
*/
}/*}}}*/

flockPhotoUploadService.prototype.getPhotoUpload = function (aURI) { /*{{{*/
    var upload = this.ds.getNode(this.getURI(aURI));
    var rval = new Object();
    rval.id = upload.getTarget(FLOCK_NS + "id").getValue();
    rval.originalFileSpec = upload.getTarget(FLOCK_NS + "originalFileSpec").getValue();
    rval.originalFilePath = upload.getTarget(FLOCK_NS + "originalFilePath").getValue();
    rval.workingFilePath = upload.getTarget(FLOCK_NS + "workingFilePath").getValue();
    rval.thumbFileSpec = upload.getTarget(FLOCK_NS + "thumbFileSpec").getValue();
    rval.thumbFilePath = upload.getTarget(FLOCK_NS + "thumbFilePath").getValue();
    rval.previewFileSpec = upload.getTarget(FLOCK_NS + "previewFileSpec").getValue();
    rval.previewFilePath = upload.getTarget(FLOCK_NS + "previewFilePath").getValue();
    rval.fileName = upload.getTarget(FLOCK_NS + "fileName").getValue();
    rval.title = upload.getTarget(FLOCK_NS + "title").getValue();
    rval.is_public = upload.getTarget(FLOCK_NS + "is_public").getValue();
    rval.is_friend = upload.getTarget(FLOCK_NS + "is_friend").getValue();
    rval.is_family = upload.getTarget(FLOCK_NS + "is_family").getValue();
    rval.tags = upload.getTarget(FLOCK_NS + "tags").getValue();
    rval.state = upload.getTarget(FLOCK_NS + "state").getValue();
    rval.description = upload.getTarget(FLOCK_NS + "description").getValue();
    rval.rotation = upload.getTarget(FLOCK_NS + "rotation").getValue();
    rval.album = upload.getTarget(FLOCK_NS + "album").getValue();
    rval.crop = upload.getTarget(FLOCK_NS + "crop").getValue();
    return rval;
}/*}}}*/

flockPhotoUploadService.prototype.cleanPhotoUploadCache = function () { /*{{{*/
  dump("Clean the cache.\n");
  try {
    var sinkFile = Components.classes["@mozilla.org/file/directory_service;1"]
      .getService(Components.interfaces.nsIProperties)
      .get("ProfD", Components.interfaces.nsIFile);
    sinkFile.append("flock_imagecache");
    if (sinkFile.exists())
      sinkFile.remove(true);
    } catch(e) {
  }

}/*}}}*/

loadLibraryFromSpec('chrome://browser/content/flock/common/flocksafe.js');
loadLibraryFromSpec('chrome://browser/content/flock/common/md5.js');
loadLibraryFromSpec('chrome://browser/content/flock/contrib/rdfds.js');
loadLibraryFromSpec('chrome://browser/content/flock/photo/photoAPI.js');
loadLibraryFromSpec('chrome://browser/content/flock/photo/photo.js');

function loadLibraryFromSpec(aSpec) {
    var loader = Components.classes['@mozilla.org/moz/jssubscript-loader;1']
        .getService(Components.interfaces.mozIJSSubScriptLoader);

    loader.loadSubScript(aSpec);
}

flockPhotoUploadService.prototype.URI = 'rdf:flock-photo-uploads';
flockPhotoUploadService.prototype.GetSource = function (aProperty, aTarget, aTruthValue) { /*{{{*/
    return this.dataSource.GetSource (aProperty, aTarget, aTruthValue); 
}/*}}}*/
flockPhotoUploadService.prototype.GetSources = function (aProperty, aTarget, aTruthValue) { /*{{{*/
    return this.dataSource.GetSources (aProperty, aTarget, aTruthValue); 
}/*}}}*/
flockPhotoUploadService.prototype.GetTarget = function (aSource, aProperty, aTruthValue) { /*{{{*/
    return this.dataSource.GetTarget (aSource, aProperty, aTruthValue); 
}/*}}}*/
flockPhotoUploadService.prototype.GetTargets = function (aSource, aProperty, aTruthValue) { /*{{{*/
    return this.dataSource.GetTargets (aSource, aProperty, aTruthValue); 
}/*}}}*/
flockPhotoUploadService.prototype.Assert = function (aSource, aProperty, aTarget, aTruthValue) { /*{{{*/
    this.dataSource.Assert (aSource, aProperty, aTarget, aTruthValue); 
}/*}}}*/
flockPhotoUploadService.prototype.Unassert = function (aSource, aProperty, aTarget) { /*{{{*/
    this.dataSource.Unassert (aSource, aProperty, aTarget); 
}/*}}}*/
flockPhotoUploadService.prototype.Change = function (aSource, aProperty, aOldTarget, aNewTarget) { /*{{{*/
    this.dataSource.Change (aSource, aProperty, aOldTarget, aNewTarget); 
}/*}}}*/
flockPhotoUploadService.prototype.Move = function (aOldSource, aNewSource, aProperty, aTarget) { /*{{{*/
    this.dataSource.Move (aOldSource, aNewSource, aProperty, aTarget); 
}/*}}}*/
flockPhotoUploadService.prototype.HasAssertion = function (aSource, aProperty, aTarget, aTruthValue) { /*{{{*/
    return this.dataSource.HasAssertion (aSource, aProperty, aTarget, aTruthValue); 
}/*}}}*/
flockPhotoUploadService.prototype.AddObserver = function (aObserver) { /*{{{*/
    this.dataSource.AddObserver (aObserver); 
}/*}}}*/
flockPhotoUploadService.prototype.RemoveObserver = function (aObserver) { /*{{{*/
    this.dataSource.RemoveObserver (aObserver); 
}/*}}}*/
flockPhotoUploadService.prototype.ArcLabelsIn = function (aNode) { /*{{{*/
    return this.dataSource.ArcLabelsIn (aNode); 
}/*}}}*/
flockPhotoUploadService.prototype.ArcLabelsOut = function (aNode) { /*{{{*/
    return this.dataSource.ArcLabelsOut (aNode); 
}/*}}}*/
flockPhotoUploadService.prototype.GetAllResources = function () { /*{{{*/
    return this.dataSource.GetAllResources (); 
}/*}}}*/
flockPhotoUploadService.prototype.IsCommandEnabled = function (aSources, aCommand, aArguments) { /*{{{*/
    return this.dataSource.IsCommandEnabled (aSources, aCommand, aArguments); 
}/*}}}*/
flockPhotoUploadService.prototype.DoCommand = function (aSources, aCommand, aArguments) { /*{{{*/
    this.dataSource.DoCommand (aSources, aCommand, aArguments); 
}/*}}}*/
flockPhotoUploadService.prototype.GetAllCmds = function (aSource) { /*{{{*/
    return this.dataSource.GetAllCmds (aSource); 
}/*}}}*/
flockPhotoUploadService.prototype.hasArcIn = function (aNode, aArc) { /*{{{*/
    return this.dataSource.hasArcIn (aNode, aArc); 
}/*}}}*/
flockPhotoUploadService.prototype.hasArcOut = function (aNode, aArc) { /*{{{*/
    return this.dataSource.hasArcOut (aNode, aArc); 
}/*}}}*/
flockPhotoUploadService.prototype.beginUpdateBatch = function () { /*{{{*/
    this.dataSource.beginUpdateBatch (); 
}/*}}}*/
flockPhotoUploadService.prototype.endUpdateBatch = function () { /*{{{*/
    this.dataSource.endUpdateBatch (); 
}/*}}}*/
flockPhotoUploadService.prototype.Flush = function () { /*{{{*/
    this.dataSource.Flush (); 
}/*}}}*/
flockPhotoUploadService.prototype.FlushTo = function (aURI) { /*{{{*/
    this.dataSource.FlushTo (aURI); 
}/*}}}*/
flockPhotoUploadService.prototype.Init = function (aURI) { /*{{{*/
    this.dataSource.Init (aURI); 
}/*}}}*/
flockPhotoUploadService.prototype.Refresh = function (aBlocking) { /*{{{*/
    this.dataSource.Refresh (aBlocking); 
}/*}}}*/

flockPhotoUploadService.prototype.flags = nsIClassInfo.SINGLETON;
flockPhotoUploadService.prototype.classDescription = "Flock Photo Service";
flockPhotoUploadService.prototype.getInterfaces = function (count) {
    var interfaceList = [flockIPhotoUploadService, nsIClassInfo];
    count.value = interfaceList.length;
    return interfaceList;
}
flockPhotoUploadService.prototype.getHelperForLanguage = function (count) {return null;}/*}}}*/

// the nsISupports implementation/*{{{*/
flockPhotoUploadService.prototype.QueryInterface =
function (iid) {
    if (!iid.equals(flockIPhotoUploadService) && 
        !iid.equals(nsIRDFDataSource) &&
        !iid.equals(nsIClassInfo) &&
        !iid.equals(nsISupports))
        throw Components.results.NS_ERROR_NO_INTERFACE;
    if (iid.equals(nsIRDFDataSource) && !this.dataSourceSetup) {
    }
    return this;
}/*}}}*/

// Module implementation/*{{{*/
var Module = new Object();

Module.registerSelf =
function (compMgr, fileSpec, location, type)
{
    compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);

    compMgr.registerFactoryLocation(FLOCK_PHOTO_UPLOAD_CID, 
                                    "Flock Photo JS Component",
                                    FLOCK_PHOTO_UPLOADS_CONTRACTID, 
                                    fileSpec, 
                                    location,
                                    type);
}

Module.getClassObject =
function (compMgr, cid, iid) {
    if (!cid.equals(FLOCK_PHOTO_UPLOAD_CID))
        throw Components.results.NS_ERROR_NO_INTERFACE;
    
    if (!iid.equals(Components.interfaces.nsIFactory))
        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
    
    return PhotoUploadServiceFactory;
}

Module.canUnload =
function(compMgr)
{
    debug("Unloading photo component.\n");
    //gPhotoUploadService.dataSource.Flush();
    return true;
}/*}}}*/
    
/* factory object *//*{{{*/
var PhotoUploadServiceFactory = new Object();

PhotoUploadServiceFactory.createInstance =
function (outer, iid) {
    debug("CI: " + iid + "\n");
    if (outer != null)
        throw Components.results.NS_ERROR_NO_AGGREGATION;

    return (new flockPhotoUploadService()).QueryInterface(iid);
}/*}}}*/

/* entrypoint *//*{{{*/
function NSGetModule(compMgr, fileSpec) {
    return Module;
}/*}}}*/
