public final class

PicasaDataSource

extends Object
implements DataSource
/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.cooliris.media;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

import android.accounts.Account;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;

import com.cooliris.app.App;
import com.cooliris.picasa.AlbumEntry;
import com.cooliris.picasa.Entry;
import com.cooliris.picasa.EntrySchema;
import com.cooliris.picasa.PhotoEntry;
import com.cooliris.picasa.PicasaApi;
import com.cooliris.picasa.PicasaContentProvider;
import com.cooliris.picasa.PicasaService;

public final class PicasaDataSource implements DataSource {
    private static final String TAG = "PicasaDataSource";
    public static final DiskCache sThumbnailCache = new DiskCache("picasa-thumbs");
    private static final String DEFAULT_BUCKET_SORT_ORDER = AlbumEntry.Columns.USER + ", " + AlbumEntry.Columns.DATE_PUBLISHED
            + " DESC";

    private ContentProviderClient mProviderClient;
    private final Context mContext;

    public PicasaDataSource(final Context context) {
        mContext = context;
    }

    public static final HashMap<String, Boolean> getAccountStatus(final Context context) {
        final Account[] accounts = PicasaApi.getAccounts(context);
        int numAccounts = accounts.length;
        HashMap<String, Boolean> accountsEnabled = new HashMap<String, Boolean>(numAccounts);
        for (int i = 0; i < numAccounts; ++i) {
            Account account = accounts[i];
            boolean isEnabled = ContentResolver.getSyncAutomatically(account, PicasaContentProvider.AUTHORITY);
            String username = PicasaApi.canonicalizeUsername(account.name);
            accountsEnabled.put(username, new Boolean(isEnabled));
        }
        return accountsEnabled;
    }

    public void loadMediaSets(final MediaFeed feed) {
        // We do this here and not in the constructor to speed application
        // loading time since this method is called in a background thread
        if (mProviderClient == null) {
            mProviderClient = mContext.getContentResolver().acquireContentProviderClient(PicasaContentProvider.AUTHORITY);
        }

        // Ensure that users are up to date. TODO: also listen for accounts
        // changed broadcast.
        PicasaService.requestSync(mContext, PicasaService.TYPE_USERS_ALBUMS, 0);
        loadMediaSetsIntoFeed(feed, true);
    }

    public void shutdown() {
        ;
    }

    public void loadItemsForSet(final MediaFeed feed, final MediaSet parentSet, int rangeStart, int rangeEnd) {
        if (parentSet == null) {
            return;
        } else {
            // Return a list of items within an album.
            addItemsToFeed(feed, parentSet, rangeStart, rangeEnd);
        }
    }

    protected void loadMediaSetsIntoFeed(final MediaFeed feed, boolean sync) {
        final HashMap<String, Boolean> accountsEnabled = getAccountStatus(mContext);
        final ContentProviderClient client = mProviderClient;
        if (client == null)
            return;
        try {
            final EntrySchema albumSchema = AlbumEntry.SCHEMA;
            final Cursor cursor = client.query(PicasaContentProvider.ALBUMS_URI, albumSchema.getProjection(), null, null,
                    DEFAULT_BUCKET_SORT_ORDER);
            final AlbumEntry album = new AlbumEntry();
            MediaSet mediaSet;
            if (cursor.moveToFirst()) {
                final int numAlbums = cursor.getCount();
                final ArrayList<MediaSet> picasaSets = new ArrayList<MediaSet>(numAlbums);
                do {
                    albumSchema.cursorToObject(cursor, album);
                    String userLowerCase = album.syncAccount.toLowerCase();
                    final Boolean accountEnabledObj = accountsEnabled.get(userLowerCase);
                    final boolean accountEnabled = (accountEnabledObj == null) ? false : accountEnabledObj.booleanValue();
                    if (accountEnabled) {
                        mediaSet = feed.getMediaSet(album.id);
                        if (mediaSet == null) {
                            mediaSet = feed.addMediaSet(album.id, this);
                            mediaSet.mName = album.title;
                            mediaSet.mEditUri = album.editUri;
                            mediaSet.generateTitle(true);
                        } else {
                            mediaSet.setNumExpectedItems(album.numPhotos);
                        }
                        mediaSet.mPicasaAlbumId = album.id;
                        mediaSet.mIsLocal = false;
                        mediaSet.mSyncPending = album.photosDirty;
                        picasaSets.add(mediaSet);
                    }
                } while (cursor.moveToNext());
            }
            cursor.close();
        } catch (RemoteException e) {
            Log.e(TAG, "Error occurred loading albums");
        }
    }

    private void addItemsToFeed(MediaFeed feed, MediaSet set, int start, int end) {
        final ContentProviderClient client = mProviderClient;
        Cursor cursor = null;
        try {
            // Query photos in the album.
            final EntrySchema photosSchema = PhotoProjection.SCHEMA;
            final String whereInAlbum = "album_id = " + Long.toString(set.mId);
            cursor = client.query(PicasaContentProvider.PHOTOS_URI, photosSchema.getProjection(), whereInAlbum, null, null);
            final PhotoProjection photo = new PhotoProjection();
            int count = cursor.getCount();
            if (count < end) {
                end = count;
            }
            set.setNumExpectedItems(count);
            set.generateTitle(true);
            // Move to the next unread item.
            final int newIndex = start + 1;
            if (newIndex > count || !cursor.move(newIndex)) {
                end = 0;
                cursor.close();
                set.updateNumExpectedItems();
                set.generateTitle(true);
                return;
            }
            if (set.mNumItemsLoaded == 0) {
                photosSchema.cursorToObject(cursor, photo);
                set.mMinTimestamp = photo.dateTaken;
                cursor.moveToLast();
                photosSchema.cursorToObject(cursor, photo);
                set.mMinTimestamp = photo.dateTaken;
                cursor.moveToFirst();
            }
            for (int i = 0; i < end; ++i) {
                photosSchema.cursorToObject(cursor, photo);
                final MediaItem item = new MediaItem();
                item.mId = photo.id;
                item.mEditUri = photo.editUri;
                item.mMimeType = photo.contentType;
                item.mDateTakenInMs = photo.dateTaken;
                item.mLatitude = photo.latitude;
                item.mLongitude = photo.longitude;
                item.mThumbnailUri = photo.thumbnailUrl;
                item.mScreennailUri = photo.screennailUrl;
                item.mContentUri = photo.contentUrl;
                item.mCaption = photo.title;
                item.mWeblink = photo.htmlPageUrl;
                item.mDescription = photo.summary;
                item.mFilePath = item.mContentUri;
                feed.addItemToMediaSet(item, set);
                if (!cursor.moveToNext()) {
                    break;
                }
            }
        } catch (Exception e) {
            Log.e(TAG, "Error occurred loading photos for album " + set.mId);
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }

    public boolean performOperation(final int operation, final ArrayList<MediaBucket> mediaBuckets, final Object data) {
        try {
            if (operation == MediaFeed.OPERATION_DELETE) {
                ContentProviderClient client = mProviderClient;
                for (int i = 0, numBuckets = mediaBuckets.size(); i != numBuckets; ++i) {
                    MediaBucket bucket = mediaBuckets.get(i);
                    ArrayList<MediaItem> items = bucket.mediaItems;
                    if (items == null) {
                        // Delete an album.
                        String albumUri = PicasaContentProvider.ALBUMS_URI + "/" + bucket.mediaSet.mId;
                        client.delete(Uri.parse(albumUri), null, null);
                    } else {
                        // Delete a set of photos.
                        for (int j = 0, numItems = items.size(); j != numItems; ++j) {
                            MediaItem item = items.get(j);
                            if (item != null) {
                                String itemUri = PicasaContentProvider.PHOTOS_URI + "/" + item.mId;
                                client.delete(Uri.parse(itemUri), null, null);
                            }
                        }
                    }
                }
            }
            return true;
        } catch (RemoteException e) {
            return false;
        }
    }

    public DiskCache getThumbnailCache() {
        return sThumbnailCache;
    }

    /**
     * The projection of PhotoEntry needed by the data source.
     */
    private static final class PhotoProjection extends Entry {
        public static final EntrySchema SCHEMA = new EntrySchema(PhotoProjection.class);
        @Column("edit_uri")
        public String editUri;
        @Column("title")
        public String title;
        @Column("summary")
        public String summary;
        @Column("date_taken")
        public long dateTaken;
        @Column("latitude")
        public double latitude;
        @Column("longitude")
        public double longitude;
        @Column("thumbnail_url")
        public String thumbnailUrl;
        @Column("screennail_url")
        public String screennailUrl;
        @Column("content_url")
        public String contentUrl;
        @Column("content_type")
        public String contentType;
        @Column("html_page_url")
        public String htmlPageUrl;
    }
    
    public String[] getDatabaseUris() {
        return new String[] { PicasaContentProvider.ALBUMS_URI.toString(), PicasaContentProvider.PHOTOS_URI.toString()};
    }
    
    public void refresh(final MediaFeed feed, final String[] databaseUris) {
        // Depending on what URI changed, we either need to update the mediasets or the mediaitems of a set.
        if (databaseUris != null && databaseUris.length > 0) {
            if (ArrayUtils.contains(databaseUris, PicasaContentProvider.ALBUMS_URI.toString())) {
                // We need to refresh all mediasets of this datasource type.
                final ArrayList<MediaSet> mediaSets = feed.getMediaSets();
                final int numMediaSets = mediaSets.size();
                for (int i = 0; i < numMediaSets; ++i) {
                    final MediaSet set = mediaSets.get(i);
                    if (set.mDataSource instanceof PicasaDataSource) {
                        set.mDataSource = this;
                        set.refresh();
                    }
                }
            } else if (ArrayUtils.contains(databaseUris, PicasaContentProvider.PHOTOS_URI.toString())) {
                // We need to update just the one set that has these photos.
                // This operation is not yet supported, so we might as well refresh everything.
                final ArrayList<MediaSet> mediaSets = feed.getMediaSets();
                final int numMediaSets = mediaSets.size();
                for (int i = 0; i < numMediaSets; ++i) {
                    final MediaSet set = mediaSets.get(i);
                    if (set.mDataSource instanceof PicasaDataSource) {
                        set.mDataSource = this;
                        set.refresh();
                    }
                }
            }
        }
    }
}