/** * */ package com.gmail.charleszq.picorner.offline; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import android.content.Context; import android.os.Environment; import android.util.Log; import com.gmail.charleszq.picorner.BuildConfig; import com.gmail.charleszq.picorner.SPUtil; import com.gmail.charleszq.picorner.model.MediaObject; import com.gmail.charleszq.picorner.model.MediaObjectCollection; import com.gmail.charleszq.picorner.offline.OfflineHandleService.IProgressReporter; import com.gmail.charleszq.picorner.utils.FlickrHelper; import com.gmail.charleszq.picorner.utils.IConstants; import com.gmail.charleszq.picorner.utils.ImageUtils; import com.gmail.charleszq.picorner.utils.ModelUtils; import com.googlecode.flickrjandroid.Flickr; import com.googlecode.flickrjandroid.people.User; import com.googlecode.flickrjandroid.photos.Extras; import com.googlecode.flickrjandroid.photosets.Photoset; /** * @author charles(charleszq@gmail.com) * */ public class FlickrPhotoSetOfflineProcessor implements IOfflinePhotoCollectionProcessor { private static final String TAG = FlickrPhotoSetOfflineProcessor.class .getSimpleName(); private static final int PAGE_SIZE = 100; /** * The extras. */ private Set<String> mExtras = null; /* * (non-Javadoc) * * @see * com.gmail.charleszq.picorner.offline.IOfflinePhotoCollectionService#process * (com.gmail.charleszq.picorner.offline.IOfflineViewParameter) */ @Override public void process(Context ctx, IOfflineViewParameter param, boolean download, IProgressReporter reporter) { String controlFileName = param.getControlFileName(); boolean isControlFileExist = OfflineControlFileUtil.isFileExist(ctx, controlFileName); if (isControlFileExist) { List<MediaObject> read = readPhotos(ctx, param); if (read != null) { Log.d(TAG, read.size() + " photos saved in file before."); //$NON-NLS-1$ boolean hasUpdatesOnServer = saveDeltaHandle(ctx, param, read); if (!hasUpdatesOnServer && !download) { return; } } } else { firstTimeHandle(ctx, param); } // starts the download List<MediaObject> photos = readPhotos(ctx, param); if (photos == null) { if (BuildConfig.DEBUG) Log.e(TAG, "error to read cache photo collection file."); //$NON-NLS-1$ return; } if (BuildConfig.DEBUG) Log.d(TAG, "Begin to download photos."); //$NON-NLS-1$ int progress = 0; for (MediaObject photo : photos) { if (BuildConfig.DEBUG) Log.d(TAG, String.format( "processing url '%s'.", photo.getLargeUrl())); //$NON-NLS-1$ String photoFileName = OfflineControlFileUtil .getOfflinePhotoFileName(photo); if (OfflineControlFileUtil.isFileExist(ctx, photoFileName)) { if (BuildConfig.DEBUG) Log.d(TAG, String.format( "photo %s was downloaded before.", photo.getId())); //$NON-NLS-1$ reporter.reportProgress(photos.size(), progress++); continue; } String url = photo.getLargeUrl(); try { FileOutputStream fos = ctx.openFileOutput(photoFileName, Context.MODE_PRIVATE); boolean ret = ImageUtils.downloadUrlToStream(url, fos); if (ret) { if (BuildConfig.DEBUG) Log.d(TAG, String.format( "photo %s saved for offline view later.", url)); //$NON-NLS-1$ } else { ctx.deleteFile(photoFileName); if (BuildConfig.DEBUG) Log.w(TAG, "unable to download the image: " + url); //$NON-NLS-1$ } } catch (Exception e) { ctx.deleteFile(photoFileName); if (BuildConfig.DEBUG) Log.e(TAG, "error to download and save photo: " + e.getMessage()); //$NON-NLS-1$ } reporter.reportProgress(photos.size(), progress++); } } /** * Returns <code>false</code> if there is no update; <code>true</code> if * there is, and update the local cache photo collection control file. * * @param ctx * @param param * @param photos * @return */ private boolean saveDeltaHandle(Context ctx, IOfflineViewParameter param, List<MediaObject> photos) { int serverPhotoCount = getCurrentCollectionPhotoCount(ctx, param); if (serverPhotoCount <= photos.size()) { // no addition on server for this photo set. if (BuildConfig.DEBUG) Log.d(TAG, "no update for this photo set."); //$NON-NLS-1$ return false; } Log.d(TAG, String.format("before, there are %d photos", photos.size())); //$NON-NLS-1$ int delta = serverPhotoCount - photos.size(); int lastPage = getLastPage(serverPhotoCount, delta); boolean duplicateFound = false; int newPhotoAdded = 0; while (lastPage > 0 && newPhotoAdded < delta) { MediaObjectCollection col = getPhotoForPage(ctx, param, lastPage, delta); if (col != null) { for (MediaObject p : col.getPhotos()) { if (photos.contains(p)) { continue; } else { photos.add(0, p); newPhotoAdded++; } } } if (duplicateFound) break; lastPage--; } if (BuildConfig.DEBUG) Log.d(TAG, String.format( "after, there are %d photos.", photos.size())); //$NON-NLS-1$ // if exceeded the limit, remove some old photos. int maxSize = SPUtil.getMaxPhotoSize(ctx); if (photos.size() > maxSize) { photos = photos.subList(0, maxSize); } savePhotoList(ctx, param, photos); return true; } /** * Saves the whole photo information. * * @param ctx * @param param */ private void firstTimeHandle(Context ctx, IOfflineViewParameter param) { int serverPhotoCount = getCurrentCollectionPhotoCount(ctx, param); if (serverPhotoCount == -1) { return; } int lastPageNo = getLastPage(serverPhotoCount, PAGE_SIZE); List<MediaObject> photos = new ArrayList<MediaObject>(); int maxSize = SPUtil.getMaxPhotoSize(ctx); int pageIndex = 0; while (lastPageNo > 0) { MediaObjectCollection col = getPhotoForPage(ctx, param, lastPageNo, PAGE_SIZE); if (col != null) { for (MediaObject p : col.getPhotos()) { photos.add(pageIndex, p); if (photos.size() >= maxSize) break; } } else { break; } pageIndex = photos.size(); if (photos.size() >= maxSize) break; lastPageNo--; } if (!photos.isEmpty()) { savePhotoList(ctx, param, photos); } } private MediaObjectCollection getPhotoForPage(Context ctx, IOfflineViewParameter param, int pageNo, int pageSize) { Flickr f = FlickrHelper.getInstance().getFlickrAuthed(ctx); if (mExtras == null) { prepareExtras(); } try { Photoset ps = f.getPhotosetsInterface().getPhotos( param.getPhotoCollectionId(), mExtras, Flickr.PRIVACY_LEVEL_NO_FILTER, pageSize, pageNo); User user = ps.getOwner(); MediaObjectCollection col = ModelUtils.convertFlickrPhotoList( ps.getPhotoList(), user); return col; } catch (Exception e) { return null; } } /** * Returns the last page which contains the most recent photos. * * @param serverPhotoCount * @return */ private int getLastPage(int serverPhotoCount, int pageSize) { int maxPage = serverPhotoCount / pageSize; int remain = serverPhotoCount % pageSize; if (remain > 0) { maxPage++; } return maxPage; } private int getCurrentCollectionPhotoCount(Context ctx, IOfflineViewParameter param) { Flickr f = FlickrHelper.getInstance().getFlickrAuthed(ctx); try { Photoset ps = f.getPhotosetsInterface().getInfo( param.getPhotoCollectionId()); if (BuildConfig.DEBUG) Log.d(TAG, "offline photo set photo count: " + ps.getPhotoCount()); //$NON-NLS-1$ return ps.getPhotoCount(); } catch (Exception e) { if (BuildConfig.DEBUG) Log.w(TAG, "unable to get the photo set information: " //$NON-NLS-1$ + e.getMessage()); return -1; } } private void prepareExtras() { mExtras = new HashSet<String>(); mExtras.add(Extras.URL_S); mExtras.add(Extras.URL_L); mExtras.add(Extras.OWNER_NAME); mExtras.add(Extras.GEO); mExtras.add(Extras.TAGS); mExtras.add(Extras.VIEWS); mExtras.add(Extras.DESCRIPTION); } private void savePhotoList(Context ctx, IOfflineViewParameter param, List<MediaObject> photos) { String controlFileName = param.getControlFileName(); ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(ctx.openFileOutput(controlFileName, Context.MODE_PRIVATE)); oos.writeObject(photos); } catch (Exception e) { Log.e(TAG, e.getMessage()); } finally { if (oos != null) { try { oos.flush(); oos.close(); } catch (IOException e) { } } } } @SuppressWarnings("unchecked") private List<MediaObject> readPhotos(Context ctx, IOfflineViewParameter param) { String controlFileName = param.getControlFileName(); if (!OfflineControlFileUtil.isFileExist(ctx, controlFileName)) { return null; } ObjectInputStream ois = null; try { ois = new ObjectInputStream(ctx.openFileInput(controlFileName)); List<MediaObject> photos = (List<MediaObject>) ois.readObject(); return photos; } catch (Exception e) { Log.e(TAG, e.getMessage()); return null; } finally { if (ois != null) { try { ois.close(); } catch (IOException e) { } } } } @Override public List<MediaObject> getCachedPhotos(Context ctx, IOfflineViewParameter param) { return readPhotos(ctx, param); } @Override public int removeCachedPhotos(Context ctx, IOfflineViewParameter param) { List<MediaObject> photos = readPhotos(ctx, param); if (photos == null) { return -1; } int count = 0; for (MediaObject photo : photos) { String photoFileName = OfflineControlFileUtil .getOfflinePhotoFileName(photo); if (ctx.deleteFile(photoFileName)) { count++; } } return count; } @Override public int exportCachedPhotos(Context ctx, IOfflineViewParameter param, String foldername, boolean overwrite, IProgressReporter reporter ) throws IOException { List<MediaObject> photos = readPhotos(ctx, param); int count = 0; if (photos != null) { // create the target folder File root = new File(Environment.getExternalStorageDirectory(), IConstants.SD_CARD_FOLDER_NAME); if (!root.exists()) root.mkdir(); File targetFolder = new File(root, foldername); if (!targetFolder.exists()) if( !targetFolder.mkdir() ) { throw new IOException("unable to create the folder."); //$NON-NLS-1$ } int progress = 0; for (MediaObject photo : photos) { String filename = OfflineControlFileUtil .getOfflinePhotoFileName(photo); File sourceFile = ctx.getFileStreamPath(filename); File targetFile = new File(targetFolder, filename); reporter.reportProgress(photos.size(), progress++); if( targetFile.exists() && !overwrite ) continue; try { copyFile(sourceFile,targetFile); count ++; } catch (IOException e) { } } } return count; } private void copyFile(File src, File dst) throws IOException { InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst); // Transfer bytes from in to out byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } in.close(); out.close(); } }