package de.blau.android.photos; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Locale; import org.acra.ACRA; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.os.Environment; import android.util.Log; import de.blau.android.App; import de.blau.android.contract.Paths; import de.blau.android.osm.BoundingBox; import de.blau.android.util.rtree.BoundedObject; import de.blau.android.util.rtree.RTree; /** * Scan the system for geo ref photos and store is in a DB * @author simon * */ public class PhotoIndex extends SQLiteOpenHelper { private final static int DATA_VERSION = 3; private final static String LOGTAG = "PhotoIndex"; private class JpgFilter implements FilenameFilter { @Override public boolean accept(File dir, String name) { return name.endsWith(Paths.FILE_EXTENSION_IMAGE); } } public PhotoIndex(Context context) { super(context, "PhotoIndex", null, DATA_VERSION); } @Override public synchronized void onCreate(SQLiteDatabase db) { Log.d(LOGTAG, "Creating photo index DB"); db.execSQL("CREATE TABLE IF NOT EXISTS photos (lat int, lon int, direction int DEFAULT NULL, dir VARCHAR, name VARCHAR);"); db.execSQL("CREATE INDEX latidx ON photos (lat)"); db.execSQL("CREATE INDEX lonidx ON photos (lon)"); db.execSQL("CREATE TABLE IF NOT EXISTS directories (dir VARCHAR, last_scan int8);"); db.execSQL("INSERT INTO directories VALUES ('DCIM', 0);"); db.execSQL("INSERT INTO directories VALUES ('Vespucci', 0);"); db.execSQL("INSERT INTO directories VALUES ('osmtracker', 0);"); } @Override public synchronized void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.d(LOGTAG, "Upgrading photo index DB"); if (oldVersion <= 2) { db.execSQL("ALTER TABLE photos ADD direction int DEFAULT NULL"); } } public synchronized void createOrUpdateIndex() { Log.d(LOGTAG,"starting scan"); // determine at least a few of the possible mount points File sdcard = Environment.getExternalStorageDirectory(); ArrayList<String> mountPoints = new ArrayList<String>(); mountPoints.add(sdcard.getAbsolutePath()); mountPoints.add(sdcard.getAbsolutePath() + Paths.DIRECTORY_PATH_EXTERNAL_SD_CARD); File storageDir = new File(Paths.DIRECTORY_PATH_STORAGE); File[] list = storageDir.listFiles(); if (list != null) { for (File f:list) { if (f.exists() && f.isDirectory() && !sdcard.getAbsolutePath().equals(f.getAbsolutePath())) { Log.d(LOGTAG, "Adding mount point " + f.getAbsolutePath()); mountPoints.add(f.getAbsolutePath()); } } } try { SQLiteDatabase db = getWritableDatabase(); Cursor dbresult = db.query( "directories", new String[] {"dir", "last_scan"}, null, null, null, null, null, null); int dirCount = dbresult.getCount(); dbresult.moveToFirst(); // loop over the directories configured for (int i = 0; i < dirCount; i++) { String dir = dbresult.getString(0); long lastScan = dbresult.getLong(1); Log.d(LOGTAG, dbresult.getString(0) + " " + dbresult.getLong(1)); // loop over all possible mount points for (String m:mountPoints) { File indir = new File(m + "/" + dir); Log.d(LOGTAG, "Scanning directory " + indir.getAbsolutePath()); if (indir.exists()) { Cursor dbresult2 = db.query( "photos", new String[] {"distinct dir"}, "dir LIKE '" + indir.getAbsolutePath() + "%'", null, null, null, null, null); int dirCount2 = dbresult2.getCount(); dbresult2.moveToFirst(); for (int j = 0; j < dirCount2; j++) { String dir2 = dbresult2.getString(0); Log.d(LOGTAG, "Checking dir " + dir2); File pDir = new File(dir2); if (!pDir.exists()) { Log.d(LOGTAG, "Deleting entries for gone dir " + dir2); db.delete("photos","dir = ?", new String[] {dir2}); } dbresult2.moveToNext(); } dbresult2.close(); scanDir(db, indir.getAbsolutePath(), lastScan); ContentValues values = new ContentValues(); Log.d(LOGTAG,"updating last scan for " + indir.getName() + " to " + System.currentTimeMillis()); values.put("last_scan", System.currentTimeMillis()); db.update("directories", values, "dir = ?", new String[] {indir.getName()}); } else { Log.d(LOGTAG, "Directory " + indir.getAbsolutePath() + " doesn't exist"); // remove all entries for this directory db.delete("photos","dir = ?", new String[] {indir.getAbsolutePath()}); db.delete("photos","dir LIKE ?", new String[] {indir.getAbsolutePath() + "/%"}); } } dbresult.moveToNext(); } dbresult.close(); db.close(); } catch (SQLiteException ex) { // Don't crash just report ACRA.getErrorReporter().putCustomData("STATUS","NOCRASH"); ACRA.getErrorReporter().handleException(ex); } } private void scanDir(SQLiteDatabase db, String dir, long lastScan) { Log.d(LOGTAG,"directory " + dir + " last Scan " + (new Date(lastScan)).toString()); File indir = new File(dir); boolean needsReindex = false; if (indir != null) { if (indir.lastModified() >= lastScan) { // directory was modified // remove all entries Log.d(LOGTAG,"deleteing refs for reindex"); try { db.delete("photos","dir = ?", new String[] { indir.getAbsolutePath()}); } catch (SQLiteException sqex) { Log.d(LOGTAG, sqex.toString()); ACRA.getErrorReporter().putCustomData("STATUS","NOCRASH"); ACRA.getErrorReporter().handleException(sqex); } needsReindex = true; } // now process File[] list = indir.listFiles(); if (list == null) { return; } // check if we shouldn't process this directory, not the most efficient way likely for (File f:list) { if (f.getName().equals(".novespucci")) { return; } } for (File f:list) { if (f.isDirectory()) { //recursive decent scanDir(db, f.getAbsolutePath(), lastScan); } if (needsReindex && f.getName() .toLowerCase(Locale.US) .endsWith(Paths.FILE_EXTENSION_IMAGE)) { addPhoto(db, indir, f); } } } } public synchronized void addPhoto(File f) { SQLiteDatabase db = getWritableDatabase(); // Log.i(LOGTAG,"Adding entry in " + f.getParent()); Photo p = addPhoto(db, f.getParentFile(), f); db.close(); RTree index = App.getPhotoIndex(); if (p!=null && index != null) { // if nothing is in the index the complete DB including this photo will be added index.insert(p); } } private Photo addPhoto(SQLiteDatabase db, File dir, File f) { // Log.i(LOGTAG,"Adding entry from " + f.getName()); try { Photo p = new Photo(dir, f); ContentValues values = new ContentValues(); values.put("lat", p.getLat()); values.put("lon", p.getLon()); // Log.i(LOGTAG,"Lat: " + p.getLat() + " " + p.getLon()); if (p.hasDirection()) { values.put("direction", p.getDirection()); } values.put("dir", dir.getAbsolutePath()); values.put("name", f.getName()); db.insert("photos", null, values); return p; } catch (SQLiteException sqex) { Log.d(LOGTAG, sqex.toString()); ACRA.getErrorReporter().putCustomData("STATUS","NOCRASH"); ACRA.getErrorReporter().handleException(sqex); } catch (IOException ioex) { // ignore silently, broken pictures are not our business } catch (NumberFormatException bfex) { // ignore silently, broken pictures are not our business } catch (Exception ex) { Log.d(LOGTAG, ex.toString()); ACRA.getErrorReporter().putCustomData("STATUS","NOCRASH"); ACRA.getErrorReporter().handleException(ex); } // ignore return null; } /** * Return all photographs in a given bounding box * If necessary fill in-memory index first * @param box * @return */ public Collection<Photo> getPhotos(BoundingBox box) { RTree index = App.getPhotoIndex(); if (index == null) { return new ArrayList<Photo>(); } return getPhotosFromIndex(index, box); } public synchronized void fill(RTree index) { if (index==null) { App.resetPhotoIndex(); // allocate r-tree index = App.getPhotoIndex(); } try { SQLiteDatabase db = getReadableDatabase(); Cursor dbresult = db.query( "photos", new String[] {"lat", "lon", "direction", "dir", "name"}, null, null, null, null, null, null); int photoCount = dbresult.getCount(); dbresult.moveToFirst(); Log.i(LOGTAG,"Query returned " + photoCount + " photos"); // for (int i = 0; i < photoCount; i++) { if (dbresult.isNull(2) ) { // no direction index.insert(new Photo(dbresult.getInt(0), dbresult.getInt(1), dbresult.getString(3) + "/" + dbresult.getString(4))); } else { index.insert(new Photo(dbresult.getInt(0), dbresult.getInt(1), dbresult.getInt(2), dbresult.getString(3) + "/" + dbresult.getString(4))); } dbresult.moveToNext(); } dbresult.close(); db.close(); } catch (SQLiteException ex) { // shoudn't happen (getReadableDatabase failed), simply report for now ACRA.getErrorReporter().handleException(ex); } } private ArrayList<Photo>getPhotosFromIndex(RTree index, BoundingBox box) { Collection<BoundedObject> queryResult = new ArrayList<BoundedObject>(); index.query(queryResult,box.getBounds()); Log.d(LOGTAG,"result count " + queryResult.size()); ArrayList<Photo>result = new ArrayList<Photo>(); for (BoundedObject bo:queryResult) { result.add((Photo)bo); } return result; } }