diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 8cf0601..91a2952 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -8,7 +8,7 @@ Um deinen eigenen Server zu installieren und konfigurieren, schaue bitte hier:PhotoBackup Version %1$s Abbrechen Informationen - Gespeichert + Fertig Fehler Starte erst den Dienst um das Protokoll zu sehen Upload Protokoll @@ -44,4 +44,5 @@ Um deinen eigenen Server zu installieren und konfigurieren, schaue bitte hier:Passwort darf nicht leer sein! Server testen Die Serveradresse ist ungültig. + Ordner zur Sicherung auswählen diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 1cedda6..afe6f9c 100755 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -31,4 +31,6 @@ @string/not_only_recent_upload + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 4ca22c4..a15852f 100755 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -62,5 +62,7 @@ Stop the service Manual backup Save this picture now! + Select photo folder to backup + diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index ae02581..f725806 100755 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -37,6 +37,16 @@ android:entryValues="@array/pref_upload_values" android:defaultValue="@string/only_recent_upload" /> + + diff --git a/src/fr/s13d/photobackup/PBMediaSender.java b/src/fr/s13d/photobackup/PBMediaSender.java index ad70514..884fca8 100644 --- a/src/fr/s13d/photobackup/PBMediaSender.java +++ b/src/fr/s13d/photobackup/PBMediaSender.java @@ -127,13 +127,16 @@ private void sendMedia(final PBMedia media) { getOkClient().newCall(request).enqueue(new Callback() { @Override public void onResponse(Call call, Response response) throws IOException { - Log.i(LOG_TAG, "Get response with code " + response.code()); - if (response.code() == 200) { - sendDidSucceed(media); - } else { - sendDidFail(media, new Throwable(response.message())); + try { + Log.i(LOG_TAG, "Get response with code " + response.code()); + if (response.isSuccessful() || response.code() == 409) { + sendDidSucceed(media); + } else { + sendDidFail(media, new Throwable(response.message())); + } + } finally { + response.body().close(); } - response.body().close(); } @Override @@ -163,12 +166,15 @@ public void test() { getOkClient().newCall(request).enqueue(new Callback() { @Override public void onResponse(Call call, Response response) throws IOException { - if (response.isSuccessful() || response.code() == 409) { - testDidSucceed(toast); - } else { - testDidFail(toast, response.message()); + try { + if (response.isSuccessful()) { + testDidSucceed(toast); + } else { + testDidFail(toast, response.message()); + } + } finally { + response.body().close(); } - response.body().close(); } @Override @@ -195,7 +201,6 @@ private Request makePostRequest(RequestBody requestBody, String pathSuffix) { return requestBuilder.build(); } - ///////////////////// // Private methods // ///////////////////// diff --git a/src/fr/s13d/photobackup/PBMediaStore.java b/src/fr/s13d/photobackup/PBMediaStore.java index ac77c19..dc0c7fd 100644 --- a/src/fr/s13d/photobackup/PBMediaStore.java +++ b/src/fr/s13d/photobackup/PBMediaStore.java @@ -42,6 +42,8 @@ public class PBMediaStore { private static SyncMediaStoreTask syncTask; private static SharedPreferences picturesPreferences; private static SharedPreferences.Editor picturesPreferencesEditor; + public static final String PhotoBackupPicturesSharedPreferences = "PhotoBackupPicturesSharedPreferences"; + private static PBMediaStoreQueries queries; private List interfaces = new ArrayList<>(); @@ -51,6 +53,7 @@ public PBMediaStore(Context theContext) { picturesPreferences = context.getSharedPreferences(PBApplication.PB_PICTURES_SHARED_PREFS, Context.MODE_PRIVATE); picturesPreferencesEditor = picturesPreferences.edit(); picturesPreferencesEditor.apply(); + queries = new PBMediaStoreQueries(context); syncTask=new SyncMediaStoreTask(); } @@ -65,6 +68,14 @@ private static void setPicturesPreferencesEditorToNull(){ picturesPreferencesEditor = null; } + public PBMedia getLastMediaInStore() { + int id = queries.getLastMediaIdInStore(); + return getMedia(id); + } + + public PBMediaStoreQueries getQueries() { + return queries; + } public void addInterface(PBMediaStoreInterface storeInterface) { interfaces.add(storeInterface); @@ -83,27 +94,30 @@ public void close() { public PBMedia getMedia(int id) { - PBMedia media = null; - if (id != 0) { - final Cursor cursor = context.getContentResolver().query(uri, null, "_id = " + id, null, null); - if (cursor != null && cursor.moveToFirst()) { - media = new PBMedia(context, cursor); + if (id == 0) { + return null; + } + Cursor cursor = queries.getMediaById(id); + if (cursor == null) { + Log.e(LOG_TAG, "Photo not returned. Probably filtered by Bucket or deleted"); + return null; + } + Integer idCol = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID); + queries.isSelectedBucket(cursor.getString(idCol)); - try { - String stateString = picturesPreferences.getString(String.valueOf(media.getId()), PBMedia.PBMediaState.WAITING.name()); - media.setState(PBMedia.PBMediaState.valueOf(stateString)); - } - catch (Exception e) { - Log.e(LOG_TAG, "Explosion!!"); - } - } - if (cursor != null && !cursor.isClosed()) { - cursor.close(); - } + media = new PBMedia(context, cursor); + try { + String stateString = picturesPreferences.getString(String.valueOf(media.getId()), PBMedia.PBMediaState.WAITING.name()); + media.setState(PBMedia.PBMediaState.valueOf(stateString)); + } catch (Exception e) { + Log.e(LOG_TAG, "Explosion!!"); } + if (!cursor.isClosed()) { + cursor.close(); + } return media; } @@ -113,17 +127,6 @@ public List getMedias() { } - public PBMedia getLastMediaInStore() { - int id = 0; - final String[] projection = new String[] { "_id" }; - final Cursor cursor = context.getContentResolver().query(uri, projection, null, null, "date_added DESC"); - if (cursor != null && cursor.moveToFirst()) { - int idColumn = cursor.getColumnIndexOrThrow("_id"); - id = cursor.getInt(idColumn); - cursor.close(); - } - return getMedia(id); - } ///////////////////////////////// @@ -162,13 +165,7 @@ protected Void doInBackground(Void... voids) { Set inCursor = new HashSet<>(); // Get all pictures on device - final String[] projection = new String[] { "_id", "_data", "date_added" }; - Cursor cursor = null; - try { - cursor = context.getContentResolver().query(uri, projection, null, null, "date_added DESC"); - } catch(SecurityException e) { - Log.w(LOG_TAG, e.toString()); - } + Cursor cursor = queries.getAllMedia(); // loop through them to sync PBMedia media; diff --git a/src/fr/s13d/photobackup/PBMediaStoreQueries.java b/src/fr/s13d/photobackup/PBMediaStoreQueries.java new file mode 100644 index 0000000..4fbbfe2 --- /dev/null +++ b/src/fr/s13d/photobackup/PBMediaStoreQueries.java @@ -0,0 +1,164 @@ +/** + * Copyright (C) 2013-2015 Stéphane Péchard. + * + * This file is part of PhotoBackup. + * + * PhotoBackup 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. + * + * PhotoBackup 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 . + */ +package fr.s13d.photobackup; + +import android.content.Context; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.net.Uri; +import android.preference.PreferenceManager; +import android.provider.MediaStore; +import android.text.TextUtils; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Set; + +import fr.s13d.photobackup.preferences.PBPreferenceFragment; + + +public class PBMediaStoreQueries { + + private static final String LOG_TAG = "PBMediaStoreQueries"; + private static Context context; + private static final Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + private final SharedPreferences prefs; + + + public PBMediaStoreQueries(Context theContext) { + context = theContext; + prefs = PreferenceManager.getDefaultSharedPreferences(context); + } + + public boolean isSelectedBucket(String bucketId) { + Set buckets = prefs.getStringSet(PBPreferenceFragment.PREF_BUCKETS, null); + // User has selected nothing - all buckets ok! + if (buckets == null || buckets.size() == 0) { + return true; + } + Log.d(LOG_TAG, "Checking if " + bucketId + " is selected by user.."); + + for (String bucket: buckets) { + if(bucket.equals(bucketId)) { + Log.d(LOG_TAG, "Is selected bucket! Continuing"); + return true; + } + } + Log.i(LOG_TAG, "Is not in selected buckets - Ignoring"); + return false; + } + + public Cursor getAllMedia() { + String where; + Cursor cursor = null; + final String[] projection = new String[] { "_id", "_data", "date_added" }; + Set buckets = prefs.getStringSet(PBPreferenceFragment.PREF_BUCKETS, null); + if (buckets != null && buckets.size() > 0) { + String args = TextUtils.join(", ", buckets); + where = "bucket_id in (" + args + ")"; + } else { + where = null; + } + try { + cursor = context.getContentResolver().query(uri, projection, where, null, "date_added DESC"); + } catch(SecurityException e) { + e.printStackTrace(); + } + return cursor; + } + + public Integer getLastMediaIdInStore() { + int id = 0; + String bucketId = ""; + final String[] projection = new String[] { "_id", MediaStore.Images.Media.BUCKET_ID }; + final Cursor cursor = context.getContentResolver().query(uri, projection, null, null, "date_added DESC"); + if (cursor != null && cursor.moveToFirst()) { + int idColumn = cursor.getColumnIndexOrThrow("_id"); + id = cursor.getInt(idColumn); + int bucketColumn = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID); + bucketId = cursor.getString(bucketColumn); + } + closeCursor(cursor); + isSelectedBucket(bucketId); + return id; + } + + public Cursor getMediaById(Integer id) { + String localWhere = "_id = " + id; + final Cursor cursor = context.getContentResolver().query(uri, null, localWhere, null, null); + if (cursor != null && cursor.moveToFirst()) { + return cursor; + } else { + return null; + } + } + public LinkedHashMap getAllBucketNames() { + // which image properties are we querying + String[] PROJECTION_BUCKET = { + MediaStore.Images.ImageColumns.BUCKET_ID, + MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME, + "count(*) as photo_count" + }; + // We want to order the albums by reverse chronological order. We abuse the + // "WHERE" parameter to insert a "GROUP BY" clause into the SQL statement. + // The template for "WHERE" parameter is like: + // SELECT ... FROM ... WHERE (%s) + // and we make it look like: + // SELECT ... FROM ... WHERE (1) GROUP BY 1,(2) + // The "(1)" means true. The "1,(2)" means the first two columns specified + // after SELECT. Note that because there is a ")" in the template, we use + // "(2" to match it. + String BUCKET_GROUP_BY = + "1) GROUP BY 1,(2"; + + // Get the base URI for the People table in the Contacts content provider. + Uri images = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + + Cursor cur = context.getContentResolver().query( + images, PROJECTION_BUCKET, BUCKET_GROUP_BY, null, "photo_count desc"); + + LinkedHashMap imageBuckets = new LinkedHashMap(); + if (cur != null && cur.moveToFirst()) { + String bucket; + String id; + String bucketCount; + int bucketColumn = cur.getColumnIndex( + MediaStore.Images.Media.BUCKET_DISPLAY_NAME); + int bucketIdColumn = cur.getColumnIndex( + MediaStore.Images.Media.BUCKET_ID); + int bucketCountColumn = cur.getColumnIndex("photo_count"); + + do { + bucket = cur.getString(bucketColumn); + id = cur.getString(bucketIdColumn); + bucketCount = cur.getString(bucketCountColumn); + imageBuckets.put(id, bucket + " (" + bucketCount + ")"); + } while (cur.moveToNext()); + } + closeCursor(cur); + return imageBuckets; + } + + private void closeCursor(Cursor cursor) { + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + +} diff --git a/src/fr/s13d/photobackup/PBService.java b/src/fr/s13d/photobackup/PBService.java index 31b601d..a26b7f4 100644 --- a/src/fr/s13d/photobackup/PBService.java +++ b/src/fr/s13d/photobackup/PBService.java @@ -20,6 +20,7 @@ import android.app.NotificationManager; import android.app.Service; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.ContentObserver; @@ -54,7 +55,9 @@ public void onCreate() { mediaStore.addInterface(this); mediaSender = new PBMediaSender(this); mediaSender.addInterface(this); - this.getApplicationContext().getContentResolver().registerContentObserver( + ContentResolver contentResolver = this.getApplicationContext().getContentResolver(); + + contentResolver.registerContentObserver( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false, newMediaContentObserver); Log.i(LOG_TAG, "PhotoBackup service is created"); @@ -166,11 +169,15 @@ public void onChange(boolean selfChange) { public void onChange(boolean selfChange, Uri uri) { super.onChange(selfChange); Log.i(LOG_TAG, "MediaContentObserver:onChange()"); + Log.i(LOG_TAG, "Content-URI: " + uri); if (uri.toString().equals("content://media/external/images/media")) { try { final PBMedia media = mediaStore.getLastMediaInStore(); + if (media == null) { + return; + } media.setState(PBMedia.PBMediaState.WAITING); mediaSender.send(media, false); //mediaStore.sync(); diff --git a/src/fr/s13d/photobackup/preferences/PBPreferenceFragment.java b/src/fr/s13d/photobackup/preferences/PBPreferenceFragment.java index 095367f..1053e2d 100644 --- a/src/fr/s13d/photobackup/preferences/PBPreferenceFragment.java +++ b/src/fr/s13d/photobackup/preferences/PBPreferenceFragment.java @@ -34,6 +34,7 @@ import android.os.Bundle; import android.os.IBinder; import android.preference.ListPreference; +import android.preference.MultiSelectListPreference; import android.preference.Preference; import android.preference.PreferenceFragment; import android.preference.PreferenceManager; @@ -41,16 +42,23 @@ import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v4.content.LocalBroadcastManager; +import android.text.TextUtils; +import android.util.ArraySet; import android.webkit.URLUtil; import android.widget.Toast; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.Map; +import java.util.Set; +import java.util.zip.CheckedInputStream; import fr.s13d.photobackup.Log; import fr.s13d.photobackup.PBActivity; import fr.s13d.photobackup.PBApplication; import fr.s13d.photobackup.PBMediaSender; +import fr.s13d.photobackup.PBMediaStoreQueries; import fr.s13d.photobackup.PBService; import fr.s13d.photobackup.R; import fr.s13d.photobackup.interfaces.PBMediaSenderInterface; @@ -80,6 +88,7 @@ public void onServiceConnected(ComponentName className, IBinder binder) { currentService.getMediaStore().addInterface(self); updateUploadJournalPreference(); // update journal serverKeys number Log.i(LOG_TAG, "Connected to service"); + fillBuckets(); } public void onServiceDisconnected(ComponentName className) { @@ -105,7 +114,8 @@ public void onReceive(Context context, Intent intent) { public static final String PREF_SERVER = "PREF_SERVER"; public static final String PREF_WIFI_ONLY = "PREF_WIFI_ONLY"; public static final String PREF_RECENT_UPLOAD_ONLY = "PREF_RECENT_UPLOAD_ONLY"; - + public static final String PREF_BUCKETS = "PREF_BUCKETS"; + private HashMap bucketNames; ////////////////// // Constructors // @@ -127,8 +137,23 @@ public void onCreate(final Bundle savedInstanceState) { preferencesEditor.apply(); } migratePreferences(); - addPreferencesFromResource(R.xml.preferences); + + } + + private void fillBuckets() { + HashMap buckets = currentService.getMediaStore().getQueries().getAllBucketNames(); + this.bucketNames = buckets; + + CharSequence[] entries = buckets.values().toArray(new CharSequence[buckets.size()]); + CharSequence[] entryValues = buckets.keySet().toArray(new CharSequence[buckets.size()]); + + MultiSelectListPreference lp = (MultiSelectListPreference)findPreference("PREF_BUCKETS"); + lp.setEntries(entries); + lp.setEnabled(true); + lp.setEntryValues(entryValues); + + setSummaries(); } @@ -173,13 +198,13 @@ public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, Log.i(LOG_TAG, "onSharedPreferenceChanged: " + key); if (key.equals(PREF_SERVICE_RUNNING)) { startOrStopService(sharedPreferences); - - } else if (key.equals(PREF_WIFI_ONLY)) { + } else if (key.equals(PREF_WIFI_ONLY) || key.equals(PREF_RECENT_UPLOAD_ONLY)) { setSummaries(); - - } else if (key.equals(PREF_RECENT_UPLOAD_ONLY)) { + } else if (key.equals(PREF_BUCKETS)) { + if (currentService != null) { + currentService.getMediaStore().sync(); + } setSummaries(); - } else if (sharedPreferences == null) { Log.e(LOG_TAG, "Error: preferences == null"); } @@ -221,6 +246,23 @@ private void initPreferences() { private void setSummaries() { + + String buckets = ""; + Set selectedBuckets = preferences.getStringSet(PREF_BUCKETS, null); + if (selectedBuckets != null && bucketNames != null) { + ArrayList selectedBucketNames = new ArrayList(); + for(String entry: selectedBuckets) { + String oneName = bucketNames.get(entry); + if (oneName != null) { + selectedBucketNames.add(oneName); + } + } + buckets = TextUtils.join(", ", selectedBucketNames); + } + MultiSelectListPreference bucketPreference = (MultiSelectListPreference)findPreference(PREF_BUCKETS); + bucketPreference.setSummary(buckets); + + final String wifiOnly = preferences.getString(PREF_WIFI_ONLY, getResources().getString(R.string.only_wifi_default)); // default final ListPreference wifiPreference = (ListPreference) findPreference(PREF_WIFI_ONLY); diff --git a/src/fr/s13d/photobackup/preferences/PBServerPreferenceFragment.java b/src/fr/s13d/photobackup/preferences/PBServerPreferenceFragment.java index 278e911..4d13d52 100755 --- a/src/fr/s13d/photobackup/preferences/PBServerPreferenceFragment.java +++ b/src/fr/s13d/photobackup/preferences/PBServerPreferenceFragment.java @@ -118,7 +118,6 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin } else if (sharedPreferences == null) { Log.e(LOG_TAG, "Error: preferences == null"); } - } @@ -146,7 +145,6 @@ private void configurePreference() { Preference pref = findPreference(param); screen.removePreference(pref); } - }