/**
* galaxy inc.
* meetup client for android
*/
package com.galaxy.picasa.sync;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import android.content.ContentValues;
import android.content.Context;
import android.content.SyncResult;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.StatFs;
import android.util.Log;
import com.android.gallery3d.common.Utils;
import com.galaxy.picasa.Config;
import com.galaxy.picasa.store.MetricsUtils;
import com.galaxy.picasa.store.PicasaStoreFacade;
/**
*
* @author sihai
*
*/
public class PrefetchHelper {
private static final String ALBUM_TABLE_NAME;
private static final String PHOTO_TABLE_NAME;
private static final String PROJECTION_ID[] = {
"_id"
};
private static final String PROJECTION_ID_CACHE_FLAG_STATUS_THUMBNAIL[] = {
"_id", "cache_flag", "cache_status", "thumbnail_url"
};
private static final String PROJECTION_ID_ROTATION_CONTENT_URL_CONTENT_TYPE_SCREENNAIL_URL[] = {
"_id", "rotation", "content_url", "content_type", "screennail_url"
};
private static final String PROJECTION_ID_SCREENNAIL_URL[] = {
"_id", "screennail_url"
};
private static final String PROJECTION_ID_THUMBNAIL_URL[] = {
"_id", "thumbnail_url"
};
private static final String QUERY_CACHE_STATUS_COUNT;
private static final String WHERE_ALBUM_ID_AND_CACHE_STATUS = String.format("%s=? AND %s=?", new Object[] {
"album_id", "cache_status"
});
private static final String WHERE_CACHE_STATUS_AND_USER_ID = String.format("%s = ? AND %s = ?", new Object[] {
"cache_status", "user_id"
});
private static final String WHERE_USER_ID_AND_CACHE_FLAG = String.format("%s=? AND %s=?", new Object[] {
"user_id", "cache_flag"
});
private static PrefetchHelper sInstance;
private AtomicInteger mCacheConfigVersion;
private String mCacheDir;
private final Context mContext;
private final PicasaDatabaseHelper mDbHelper;
static {
ALBUM_TABLE_NAME = AlbumEntry.SCHEMA.getTableName();
PHOTO_TABLE_NAME = PhotoEntry.SCHEMA.getTableName();
Object aobj[] = new Object[10];
aobj[0] = PHOTO_TABLE_NAME;
aobj[1] = "cache_status";
aobj[2] = PHOTO_TABLE_NAME;
aobj[3] = ALBUM_TABLE_NAME;
aobj[4] = PHOTO_TABLE_NAME;
aobj[5] = "album_id";
aobj[6] = ALBUM_TABLE_NAME;
aobj[7] = "_id";
aobj[8] = ALBUM_TABLE_NAME;
aobj[9] = "cache_flag";
QUERY_CACHE_STATUS_COUNT = String.format("SELECT count(*), %s.%s AS status FROM %s, %s WHERE %s.%s = %s.%s AND %s.%s = ? GROUP BY status", aobj);
}
private PrefetchHelper(Context context) {
mCacheConfigVersion = new AtomicInteger(0);
mContext = context.getApplicationContext();
mDbHelper = PicasaDatabaseHelper.get(context);
}
private static void collectKeepSet(SQLiteDatabase sqlitedatabase, long l, HashMap hashmap, Integer integer) {
Cursor cursor = null;
String as[] = new String[1];
as[0] = String.valueOf(l);
try {
cursor = sqlitedatabase.query(PHOTO_TABLE_NAME, PROJECTION_ID, "album_id=?", as, null, null, null);
while(cursor.moveToNext())
hashmap.put(Long.valueOf(cursor.getLong(0)), integer);
} finally {
if(null != cursor) {
cursor.close();
}
}
}
private void deleteUnusedAlbumCovers(HashSet hashset) throws IOException {
File file = new File(getCacheDirectory(), "picasa_covers");
String as[] = file.list();
if (as != null) {
int i = as.length;
int j = 0;
while (j < i) {
String s = as[j];
int k = s.lastIndexOf('.');
String s1;
if (k < 0)
s1 = s;
else
s1 = s.substring(0, k);
if (!hashset.contains(s1) && !(new File(file, s)).delete())
Log.w("PrefetchHelper", (new StringBuilder(
"cannot delete album cover: ")).append(s)
.toString());
j++;
}
}
}
private void deleteUnusedCacheFiles(PrefetchContext prefetchcontext,
HashMap hashmap) throws IOException {
String s;
String as[];
int i;
int j;
s = getCacheDirectory();
as = (new File(s)).list();
i = as.length;
j = 0;
while (j < i) {
String s1 = as[j];
if (prefetchcontext.syncInterrupted()) {
break;
}
if (s1.startsWith("picasa-")) {
File file;
String as1[];
file = new File(s, s1);
as1 = file.list();
int k;
int l;
k = as1.length;
l = 0;
while (l < k) {
String s2 = as1[l];
if (!prefetchcontext.syncInterrupted()) {
if (!keepCacheFile(file, s2, hashmap))
(new File(file, s2)).delete();
}
}
if (file.list().length == 0)
file.delete();
}
}
}
private String getCacheDirectory() throws IOException {
if (mCacheDir == null) {
File file = PicasaStoreFacade.getCacheDirectory();
if (file == null)
throw new IOException("external storage is not present");
mCacheDir = file.getAbsolutePath();
}
return mCacheDir;
}
private static boolean keepCacheFile(File file, String s, HashMap hashmap) {
boolean flag = false;
int i = s.lastIndexOf('.');
if (-1 == i) {
return flag;
}
String s1 = s.substring(0, i);
String s2 = s.substring(i);
long l = Long.parseLong(s1);
Integer integer = (Integer) hashmap.get(Long.valueOf(l));
if (null != integer) {
if (2 == integer.intValue()) {
flag = false;
if (".full".equals(s2)) {
boolean i1 = (new File(file, (new StringBuilder())
.append(s1).append(".screen").toString())).length() != 0L;
flag = false;
if (i1)
hashmap.remove(Long.valueOf(l));
}
return flag;
} else if (1 == integer.intValue()) {
if (".screen".equals(s2)) {
hashmap.remove(Long.valueOf(l));
flag = true;
}
return flag;
}
}
return true;
}
private void notifyAlbumsChange() {
mContext.getContentResolver().notifyChange(
PicasaFacade.get(mContext).getAlbumsUri(), null, false);
}
private static void setCacheStatus(SQLiteDatabase sqlitedatabase,
HashMap hashmap) {
String as[];
ContentValues contentvalues = new ContentValues();
as = new String[1];
Iterator iterator = hashmap.entrySet().iterator();
try {
sqlitedatabase.beginTransaction();
Map.Entry entry;
while (iterator.hasNext()) {
entry = (Map.Entry) iterator.next();
if (((Integer) entry.getValue()).intValue() == 2) {
contentvalues.put("cache_status", 2);
as[0] = String.valueOf(entry.getKey());
sqlitedatabase.update(PHOTO_TABLE_NAME, contentvalues,
"_id=?", as);
}
}
sqlitedatabase.endTransaction();
} finally {
sqlitedatabase.endTransaction();
}
}
private boolean syncOnePhoto(PrefetchContext prefetchcontext, long l,
String s, String s1) throws IOException {
long l1 = getAvailableStorage();
if (l1 < 0x40000000L)
throw new RuntimeException(
(new StringBuilder("space not enough: ")).append(l1)
.append(", stop sync").toString());
File file = PicasaStoreFacade.createCacheFile(l, ".download");
if (file == null)
throw new IOException("external storage absent?");
if (Log.isLoggable("PrefetchHelper", 2) && s1 == ".full")
Log.v("PrefetchHelper",
(new StringBuilder("download full image for ")).append(l)
.append(": ").append(Utils.maskDebugInfo(s))
.toString());
boolean flag;
if (!downloadPhoto(prefetchcontext, s, file)) {
file.delete();
prefetchcontext.onDownloadFinish(l, false);
flag = false;
} else if (!file.renameTo(PicasaStoreFacade.createCacheFile(l, s1))) {
Log.e("PrefetchHelper", (new StringBuilder("cannot rename file: "))
.append(file).toString());
file.delete();
prefetchcontext.onDownloadFinish(l, false);
flag = false;
} else {
prefetchcontext.onDownloadFinish(l, true);
ContentValues contentvalues = new ContentValues();
contentvalues.put("cache_status", Integer.valueOf(0));
SQLiteDatabase sqlitedatabase = mDbHelper.getWritableDatabase();
String s2 = PHOTO_TABLE_NAME;
String as[] = new String[1];
as[0] = String.valueOf(l);
sqlitedatabase.update(s2, contentvalues, "_id=?", as);
flag = true;
}
return flag;
}
public final void syncFullImagesForUser(PrefetchContext prefetchcontext, UserEntry userentry) throws IOException {
// TODO
}
private void updateAlbumCacheStatus(SQLiteDatabase sqlitedatabase, long l,
int i) {
ContentValues contentvalues = new ContentValues();
contentvalues.put("cache_status", Integer.valueOf(i));
String as[] = new String[1];
as[0] = String.valueOf(l);
sqlitedatabase.update(ALBUM_TABLE_NAME, contentvalues, "_id=?", as);
notifyAlbumsChange();
}
public final void cleanCache(PrefetchContext prefetchcontext)
throws IOException {
int i;
HashMap hashmap;
HashSet hashset;
SQLiteDatabase sqlitedatabase;
Cursor cursor = null;
i = MetricsUtils.begin("PrefetchHelper.cleanCache");
hashmap = new HashMap();
hashset = new HashSet();
sqlitedatabase = mDbHelper.getWritableDatabase();
ContentValues contentvalues = new ContentValues();
contentvalues.put("cache_status", Integer.valueOf(0));
sqlitedatabase.update(PHOTO_TABLE_NAME, contentvalues,
"cache_status <> 0", null);
try {
cursor = sqlitedatabase.query(ALBUM_TABLE_NAME,
PROJECTION_ID_CACHE_FLAG_STATUS_THUMBNAIL, null, null,
null, null, null);
while (cursor.moveToNext()) {
if (prefetchcontext.syncInterrupted()) {
return;
}
long l = cursor.getLong(0);
int j = cursor.getInt(1);
int k = cursor.getInt(2);
hashset.add(PicasaStoreFacade.getAlbumCoverKey(l,
cursor.getString(3)));
if (j == 2) {
if ((k != 3) && (k != 1))
updateAlbumCacheStatus(sqlitedatabase, l, 1);
collectKeepSet(sqlitedatabase, l, hashmap,
Integer.valueOf(2));
} else if (j == 1) {
if (k != 0)
updateAlbumCacheStatus(sqlitedatabase, l, 0);
collectKeepSet(sqlitedatabase, l, hashmap,
Integer.valueOf(1));
} else if ((j == 0) && (k != 0)) {
updateAlbumCacheStatus(sqlitedatabase, l, 0);
}
}
} finally {
if (null != cursor) {
cursor.close();
}
}
deleteUnusedAlbumCovers(hashset);
deleteUnusedCacheFiles(prefetchcontext, hashmap);
setCacheStatus(sqlitedatabase, hashmap);
MetricsUtils.end(i);
}
public final PrefetchContext createPrefetchContext(SyncResult syncresult, Thread thread) {
return new PrefetchContext(syncresult, thread);
}
public final CacheStats getCacheStatistics(int i) {
Cursor cursor = null;
CacheStats cachestats;
SQLiteDatabase sqlitedatabase = mDbHelper.getReadableDatabase();
String as[] = new String[1];
as[0] = String.valueOf(2);
try {
cursor = sqlitedatabase.rawQuery(QUERY_CACHE_STATUS_COUNT, as);
cachestats = new CacheStats();
if (null != cursor) {
while (cursor.moveToNext()) {
int j = cursor.getInt(0);
if (cursor.getInt(1) != 0)
cachestats.pendingCount = j + cachestats.pendingCount;
cachestats.totalCount = j + cachestats.totalCount;
}
}
} finally {
if (null != cursor) {
cursor.close();
}
}
return cachestats;
}
public final void setAlbumCachingFlag(long l, int i) {
if (0 == i || 1 == i || 2 == i) {
ContentValues contentvalues = new ContentValues();
contentvalues.put("cache_flag", Integer.valueOf(i));
String as[] = new String[1];
as[0] = String.valueOf(l);
if (mDbHelper.getWritableDatabase().update(ALBUM_TABLE_NAME,
contentvalues, "_id=?", as) > 0) {
mCacheConfigVersion.incrementAndGet();
notifyAlbumsChange();
PicasaSyncManager.get(mContext).requestPrefetchSync();
}
}
}
public final void syncAlbumCoversForUser(PrefetchContext prefetchcontext,
UserEntry userentry) throws IOException {
File file = new File(getCacheDirectory(), "picasa_covers");
if (!file.isDirectory() && !file.mkdirs()) {
Log.e("PrefetchHelper", "cannot create album-cover folder");
return;
}
Cursor cursor = null;
SQLiteDatabase sqlitedatabase = mDbHelper.getWritableDatabase();
String as[] = new String[1];
as[0] = String.valueOf(userentry.id);
try {
cursor = sqlitedatabase.query(ALBUM_TABLE_NAME,
PROJECTION_ID_THUMBNAIL_URL, "user_id=?", as, null, null,
null);
if (null != cursor) {
while (cursor.moveToNext()) {
if (prefetchcontext.syncInterrupted()) {
break;
}
long l;
String s;
File file1;
l = cursor.getLong(0);
s = cursor.getString(1);
file1 = getAlbumCoverCacheFile(l, s, ".thumb");
if (file1.isFile())
continue;
long size = getAvailableStorage();
if (size < 0x40000000L)
throw new RuntimeException((new StringBuilder(
"space not enough: ")).append(size)
.append(", stop sync").toString());
File file2 = getAlbumCoverCacheFile(l, s, ".download");
if (!downloadPhoto(prefetchcontext,
PicasaApi.convertImageUrl(s, Config.sThumbNailSize,
true), file2))
file2.delete();
else if (!file2.renameTo(getAlbumCoverCacheFile(l, s,
".thumb"))) {
Log.e("PrefetchHelper", (new StringBuilder(
"cannot rename file: ")).append(file2)
.toString());
file2.delete();
}
}
}
} finally {
if (null != cursor) {
cursor.close();
}
}
}
private static File getAlbumCoverCacheFile(long l, String s, String s1) throws IOException {
File file = PicasaStoreFacade.getAlbumCoverCacheFile(l, s, s1);
if (file == null)
throw new IOException("external storage not present");
else
return file;
}
private long getAvailableStorage() {
try {
StatFs statfs = new StatFs(getCacheDirectory());
return statfs.getAvailableBlocks() * statfs.getBlockSize();
} catch (Throwable t) {
Log.w("PrefetchHelper", "Fail to getAvailableStorage", t);
return 0L;
}
}
private static boolean downloadPhoto(PrefetchContext prefetchcontext, String s, File file)
{
// TODO
return false;
}
public final void syncScreenNailsForUser(PrefetchContext prefetchcontext, UserEntry userentry) throws IOException {
Cursor cursor = null;
String as[] = new String[2];
as[0] = String.valueOf(1);
as[1] = String.valueOf(userentry.id);
try {
cursor = mDbHelper.getWritableDatabase().query(PHOTO_TABLE_NAME, PROJECTION_ID_SCREENNAIL_URL, WHERE_CACHE_STATUS_AND_USER_ID, as, null, null, "display_index");
if(null != cursor) {
while(cursor.moveToNext()) {
if(syncOnePhoto(prefetchcontext, cursor.getLong(0), PicasaApi.convertImageUrl(cursor.getString(1), Config.sScreenNailSize, false), ".screen") || prefetchcontext.getDownloadFailCount() <= 3)
continue;
else
throw new RuntimeException("too many fail downloads");
}
}
if(!prefetchcontext.checkCacheConfigVersion())
{
Log.w("PrefetchHelper", "cache config has changed, stop sync");
prefetchcontext.stopSync();
}
prefetchcontext.syncInterrupted();
} finally {
if(null == cursor) {
cursor.close();
}
}
}
public static synchronized PrefetchHelper get(Context context)
{
PrefetchHelper prefetchhelper;
if(sInstance == null)
sInstance = new PrefetchHelper(context);
prefetchhelper = sInstance;
return prefetchhelper;
}
public static final class CacheStats {
public int pendingCount;
public int totalCount;
public CacheStats() {
}
}
public final class PrefetchContext {
private PrefetchListener mCacheListener;
private int mDownloadFailCount;
private int mLastVersion;
private volatile boolean mStopSync;
private Thread mThread;
public SyncResult result;
public PrefetchContext(SyncResult syncresult, Thread thread) {
super();
result = (SyncResult) Utils.checkNotNull(syncresult);
mThread = thread;
}
public final boolean checkCacheConfigVersion() {
boolean flag;
if (mLastVersion == mCacheConfigVersion.get())
flag = true;
else
flag = false;
return flag;
}
public final int getDownloadFailCount() {
return mDownloadFailCount;
}
public final void onDownloadFinish(long l, boolean flag) {
int i;
if (flag)
i = 0;
else
i = 1 + mDownloadFailCount;
mDownloadFailCount = i;
if (mCacheListener != null)
mCacheListener.onDownloadFinish();
}
public final void setCacheDownloadListener(
PrefetchListener prefetchlistener) {
mCacheListener = prefetchlistener;
}
public final void stopSync() {
mStopSync = true;
mThread.interrupt();
}
public final boolean syncInterrupted() {
return mStopSync;
}
public final void updateCacheConfigVersion() {
mLastVersion = mCacheConfigVersion.get();
}
}
public static interface PrefetchListener {
void onDownloadFinish();
}
}