org.syncany.plugins.flickr.FlickrTransferManager.java Source code

Java tutorial

Introduction

Here is the source code for org.syncany.plugins.flickr.FlickrTransferManager.java

Source

/*
 * Syncany, www.syncany.org
 * Copyright (C) 2011-2014 Philipp C. Heckel <philipp.heckel@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.syncany.plugins.flickr;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.io.FileUtils;
import org.syncany.config.Config;
import org.syncany.database.MultiChunkEntry.MultiChunkId;
import org.syncany.plugins.transfer.AbstractTransferManager;
import org.syncany.plugins.transfer.StorageException;
import org.syncany.plugins.transfer.files.MultichunkRemoteFile;
import org.syncany.plugins.transfer.files.RemoteFile;
import org.syncany.plugins.transfer.files.SyncanyRemoteFile;
import org.syncany.plugins.transfer.files.TempRemoteFile;

import com.flickr4java.flickr.Flickr;
import com.flickr4java.flickr.FlickrException;
import com.flickr4java.flickr.REST;
import com.flickr4java.flickr.RequestContext;
import com.flickr4java.flickr.auth.Auth;
import com.flickr4java.flickr.photos.Photo;
import com.flickr4java.flickr.photos.PhotoList;
import com.flickr4java.flickr.photos.Size;
import com.flickr4java.flickr.photosets.Photoset;
import com.flickr4java.flickr.uploader.UploadMetaData;
import com.flickr4java.flickr.uploader.Uploader;

public class FlickrTransferManager extends AbstractTransferManager {
    private static final Logger logger = Logger.getLogger(FlickrTransferManager.class.getSimpleName());
    private static final int FLICKR_MIN_IMAGE_BYTES = 17 * 17 * 3; // < 16x16 PNGs are rejected sometimes!

    private Flickr flickr;
    private Auth auth;
    private String photosetId;
    private Map<RemoteFile, Photo> remoteFilePhotoIdCache;

    public FlickrTransferManager(FlickrTransferSettings settings, Config config) throws Exception {
        super(settings, config);

        this.flickr = new Flickr(FlickrTransferPlugin.APP_KEY, FlickrTransferPlugin.APP_SECRET, new REST());
        this.auth = settings.getAuth().toAuth();
        this.photosetId = settings.getAlbum();
        this.remoteFilePhotoIdCache = new HashMap<RemoteFile, Photo>();

        // Init Flickr object
        flickr.setAuth(auth);
        RequestContext.getRequestContext().setAuth(auth);

        Flickr.debugRequest = false;
        Flickr.debugStream = false;
    }

    public FlickrTransferSettings getSettings() {
        return (FlickrTransferSettings) settings;
    }

    @Override
    public void connect() throws StorageException {
        // Nothing
    }

    @Override
    public void disconnect() {
        // Nothing
    }

    @Override
    public void init(boolean createIfRequired) throws StorageException {
        if (createIfRequired) {
            if (photosetId == null) {
                logger.log(Level.INFO,
                        "Flickr Init: Create target enabled, and NO album ID given. Creating album ...");

                photosetId = createNewAlbum();
                getSettings().setAlbum(photosetId);
            } else {
                logger.log(Level.INFO, "Flickr Init: Create target enabled, but album ID given (" + photosetId
                        + "). Using this album. Nothing to do.");
            }
        } else {
            if (photosetId == null) {
                logger.log(Level.INFO,
                        "Flickr Init: Create target NOT enabled, and NO album ID given. Cannot continue.");
                throw new StorageException("Album ID required if 'create target' option not selected.");
            } else {
                logger.log(Level.INFO, "Flickr Init: Create target NOT enabled, album ID given (" + photosetId
                        + "). Using this album. Nothing to do.");
            }
        }
    }

    private String createNewAlbum() throws StorageException {
        try {
            // Create and upload dummy file (album needs at least one photo)
            Path dummyFileTempPath = Files.createTempFile("syncany-temp", ".tmp");
            Files.write(dummyFileTempPath, "Syncany rocks!".getBytes());

            String dummyPhotoId = upload(dummyFileTempPath.toFile(),
                    new TempRemoteFile(new MultichunkRemoteFile(MultiChunkId.secureRandomMultiChunkId())), false);

            Files.delete(dummyFileTempPath);

            // Create album              
            String title = "Syncany " + (1000 + Math.abs(new Random().nextInt(8999)));
            String description = "Flickr-based Syncany repository. Details at www.syncany.org!";

            Photoset photoset = flickr.getPhotosetsInterface().create(title, description, dummyPhotoId);
            return photoset.getId();
        } catch (Exception e) {
            throw new StorageException("Cannot initialize repository. Creating Flickr album failed.", e);
        }
    }

    @Override
    public void download(RemoteFile remoteFile, File localFile) throws StorageException {
        Photo photo = getPhoto(remoteFile);

        try {
            // Copy PNG file to local cache; This indirection is necessary, because there are some 
            // ZIP/stream issues when the input stream is directly handed to the PNG decoder.

            InputStream rawImageStream = flickr.getPhotosInterface().getImageAsStream(photo, Size.ORIGINAL);
            File tmpFile = createTempFile(remoteFile.getName());

            FileUtils.copyInputStreamToFile(rawImageStream, tmpFile);
            rawImageStream.close();

            // Decode PNG from file to byte array and write to final file (removes Flickr-bug padding)
            byte[] paddedPngData = PngEncoder.decodeFromPng(tmpFile);
            FileOutputStream localFileOutputStream = new FileOutputStream(localFile);

            localFileOutputStream.write(paddedPngData, FLICKR_MIN_IMAGE_BYTES,
                    paddedPngData.length - FLICKR_MIN_IMAGE_BYTES);
            localFileOutputStream.close();
        } catch (Exception e) {
            throw new StorageException("Cannot download image " + remoteFile + ", Flickr photo ID " + photo.getId(),
                    e);
        }
    }

    @Override
    public void upload(File localFile, RemoteFile remoteFile) throws StorageException {
        upload(localFile, remoteFile, true);
    }

    private String upload(File localFile, RemoteFile remoteFile, boolean addToPhotoset) throws StorageException {
        try {
            UploadMetaData metaData = new UploadMetaData();

            metaData.setFilename(remoteFile.getName() + ".png");
            metaData.setTitle(remoteFile.getName());
            metaData.setFilemimetype("image/png");

            // Some weird Flickr bug: Images with dimensions < 17x17 are sometimes rejected.         
            ByteArrayOutputStream paddedContentsOutputStream = new ByteArrayOutputStream();
            paddedContentsOutputStream.write(new byte[FLICKR_MIN_IMAGE_BYTES]);
            paddedContentsOutputStream.write(FileUtils.readFileToByteArray(localFile));

            byte[] fileContents = paddedContentsOutputStream.toByteArray();

            // Encode local file to PNG image
            ByteArrayOutputStream encodedPngOutputStream = new ByteArrayOutputStream();

            PngEncoder.encodeToPng(fileContents, encodedPngOutputStream);
            encodedPngOutputStream.close();

            byte[] pngEncodedFileContents = encodedPngOutputStream.toByteArray();

            // Upload PNG image to Flickr 
            Uploader uploader = flickr.getUploader();
            String photoId = uploader.upload(pngEncodedFileContents, metaData);

            // Add image to photoset (album)
            if (addToPhotoset) {
                flickr.getPhotosetsInterface().addPhoto(photosetId, photoId);
            }

            logger.log(Level.INFO, "Uploaded file " + localFile + " to " + remoteFile + ", as photo ID " + photoId);
            return photoId;
        } catch (Exception e) {
            throw new StorageException("Cannot upload file " + localFile + " to remote file ", e);
        }
    }

    @Override
    public boolean delete(RemoteFile remoteFile) throws StorageException {
        try {
            Photo photo = getPhoto(remoteFile);
            flickr.getPhotosInterface().delete(photo.getId());

            return true;
        } catch (Exception e) {
            logger.log(Level.WARNING, "Cannot delete remote file " + remoteFile + ". IGNORING.", e);
            return false;
        }
    }

    @Override
    public void move(RemoteFile sourceFile, RemoteFile targetFile) throws StorageException {
        try {
            Photo photo = getPhoto(sourceFile);
            flickr.getPhotosInterface().setMeta(photo.getId(), targetFile.getName(), null);
        } catch (Exception e) {
            throw new StorageException(e);
        }
    }

    @Override
    public <T extends RemoteFile> Map<String, T> list(Class<T> remoteFileClass) throws StorageException {
        try {
            Map<String, T> fileList = new HashMap<String, T>();

            boolean morePhotos = true;
            int maxPhotos = 1000;
            int currentPage = 1;

            while (morePhotos) {
                PhotoList<Photo> partialPhotoList = flickr.getPhotosetsInterface().getPhotos(photosetId, maxPhotos,
                        currentPage);

                for (Photo photo : partialPhotoList) {
                    try {
                        RemoteFile remoteFile = RemoteFile.createRemoteFile(photo.getTitle());

                        if (remoteFile.getClass().equals(remoteFileClass)) {
                            T concreteRemoteFile = remoteFileClass.cast(remoteFile);
                            fileList.put(remoteFile.getName(), concreteRemoteFile);
                        }

                        remoteFilePhotoIdCache.put(remoteFile, photo);
                    } catch (Exception e) {
                        // Ignore invalid filenames
                    }
                }

                if (partialPhotoList.size() < 1000) {
                    morePhotos = false;
                } else {
                    currentPage++;
                    morePhotos = true;
                }
            }

            return fileList;
        } catch (FlickrException e) {
            throw new StorageException(e);
        }
    }

    @Override
    public boolean testTargetCanWrite() {
        return true;
    }

    @Override
    public boolean testTargetExists() {
        try {
            if (photosetId == null) {
                return false;
            } else {
                Photoset photoset = flickr.getPhotosetsInterface().getInfo(photosetId);
                return photoset != null;
            }
        } catch (FlickrException e) {
            logger.log(Level.SEVERE, "Cannot get information about photoset.", e);
            return false;
        }
    }

    @Override
    public boolean testTargetCanCreate() {
        return true;
    }

    @Override
    public boolean testRepoFileExists() {
        try {
            return list(SyncanyRemoteFile.class).size() == 1;
        } catch (StorageException e) {
            logger.log(Level.SEVERE, "Cannot get information about repo file.", e);
            return false;
        }
    }

    private Photo getPhoto(RemoteFile remoteFile) throws StorageException {
        Photo photo = remoteFilePhotoIdCache.get(remoteFile);

        if (photo != null) {
            return photo;
        } else {
            list(remoteFile.getClass()); // Update cache!

            photo = remoteFilePhotoIdCache.get(remoteFile);

            if (photo != null) {
                return photo;
            } else {
                throw new StorageException("Cannot find remote file " + remoteFile);
            }
        }
    }
}