/*
* Copyright 2015 Daniel Dittmar
*
* 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 dan.dit.whatsthat.image;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.util.Log;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import dan.dit.whatsthat.BuildConfig;
import dan.dit.whatsthat.R;
import dan.dit.whatsthat.util.image.ExternalStorage;
/**
* Manager class for image related things.
* <ul><li>Syncs new drawables or updated drawable information to the database on startup.
* <li>Deletes any images from database that are no more accessible due to missing file or being removed from
* drawables for some reasons.
* </ul>
* Created by daniel on 28.03.15.
*/
public class ImageManager {
private static final String PREFERENCES_KEY_IMAGE_MANAGER_VERSION = "dan.dit.whatsthat.prefkey_imagemanagerversion";
private static boolean SYNCING_TASK_HAS_FINISHED_THIS_RUN;
private ImageManager() {}
private static SyncingTask SYNCING_TASK;
public static boolean calculateImagedataDeveloperFromFile(Context context, String fileName) {
if (!BuildConfig.DEBUG) {
Log.e("HomeStuff", "Using developer method in non debug build: calculateImagedataDeveloperFromFile");
return false;
}
//Step1: Load new images from XML and calculate their hash and preferences
ImageXmlParser parser = null;
File file = new File(ExternalStorage.getExternalStoragePathIfMounted(null), fileName);
Log.d("Image", "Starting calculating imagedata from file: " + file);
try {
Log.d("Image", "Starting parsing uncompiled images and compiling them.");
parser = ImageXmlParser.parseInput(context, new FileInputStream(file), Integer.MIN_VALUE, true);
Log.d("Image", "Loaded bundles: " + parser.getReadBundlesCount());
} catch (IOException e) {
Log.d("Image", "IOEXCEPTION: " + e);
} catch (XmlPullParserException e) {
Log.d("Image", "XML EXCEPTION " + e);
}
if (parser != null && parser.getReadBundlesCount() > 0) {
//Step 2: Save the updated images to new xml for future use
Log.d("Image", "Loaded " + parser.getReadBundlesCount() + " bundles, now writing again to compiled file.");
List<Integer> bundleNumers = new ArrayList<>(parser.getReadBundleNumbers());
Collections.sort(bundleNumers);
List<List<Image>> bundleImages = new ArrayList<>();
List<String> bundleOrigins = new ArrayList<>();
for (Integer bundleNumber : bundleNumers) {
bundleImages.add(parser.getBundle(bundleNumber));
bundleOrigins.add(parser.getOrigin(bundleNumber));
}
if (bundleImages.size() > 0) {
ImageXmlWriter.writeBundle(context, bundleImages, bundleNumers, bundleOrigins.get(0), file);
return true;
}
}
return false;
}
/**
* DEVELOPER METHOD; NOT FOR RELEASE.
* <br>Parses the imagedata_uncompiled xml file, calculates hashs and preferences if required
* and writes each read bundle to its own file. Will terminate the program with an Exception.
* @param context A context.
*/
public static boolean calculateImagedataDeveloper(Context context) {
if (!BuildConfig.DEBUG) {
Log.e("HomeStuff", "Using developer method in non debug build: calculateImagedataDeveloper");
return false; // do nothing, but better this should never be called
}
//Step1: Load new images from XML and calculate their hash and preferences
ImageXmlParser parser = null;
try {
Log.d("Image", "Starting parsing uncompiled images and compiling them.");
parser = ImageXmlParser.parseInput(context, context.getResources().openRawResource(R.raw.imagedata_uncompiled), 0, true);
Log.d("Image", "Loaded bundles: " + parser.getReadBundlesCount());
} catch (IOException e) {
Log.d("Image", "IOEXCEPTION: " + e);
} catch (XmlPullParserException e) {
Log.d("Image", "XML EXCEPTION " + e);
}
if (parser != null && parser.getReadBundlesCount() > 0) {
//Step 2: Save the updated images to new xml for future use
Log.d("Image", "Loaded " + parser.getReadBundlesCount() + " bundles, now writing again to compiled file.");
for (Integer bundleNumber : parser.getReadBundleNumbers()) {
ImageXmlWriter.writeBundle(context, Collections.singletonList(parser.getBundle(bundleNumber)), Collections.singletonList(bundleNumber), parser.getOrigin(bundleNumber), null);
}
return true;
}
return false;
}
public static void sync(Context context, SynchronizationListener listener) {
cancelSync();
SYNCING_TASK = new SyncingTask(context, listener);
SYNCING_TASK.execute();
}
public static void unregisterSynchronizationListener() {
if (SYNCING_TASK != null) {
SYNCING_TASK.mListener = null;
}
}
public static void cancelSync() {
if (SYNCING_TASK != null) {
SYNCING_TASK.mListener = null;
SYNCING_TASK.cancel(true);
SYNCING_TASK = null;
}
}
public static boolean isSyncing() {
return SYNCING_TASK != null;
}
public static boolean isSynced() {
return SYNCING_TASK_HAS_FINISHED_THIS_RUN;
}
private static class SyncingTask extends AsyncTask<Void, Integer, Void> {
private SynchronizationListener mListener;
private Context mContext;
public SyncingTask(Context context, SynchronizationListener listener) {
mContext = context;
mListener = listener;
if (context == null) {
throw new IllegalArgumentException("Null context given.");
}
}
@Override
protected Void doInBackground(Void... voids) {
SharedPreferences prefs = mContext.getSharedPreferences(Image.SHAREDPREFERENCES_FILENAME, Context.MODE_PRIVATE);
int currBundleNumber = prefs.getInt(ImageManager.PREFERENCES_KEY_IMAGE_MANAGER_VERSION, 0);
ImageXmlParser parser = null;
try {
parser = ImageXmlParser.parseInput(mContext, mContext.getResources().openRawResource(R.raw.imagedata), currBundleNumber + 1, false);
} catch (IOException e) {
Log.e("Image", "IO Error parsing bundles: " + e);
} catch (XmlPullParserException e) {
Log.e("Image", "XML Error parsing bundles: " + e);
}
if (parser != null) {
SynchronizationListener listener = new SynchronizationListener() {
@Override
public void onSyncProgress(int progress) {
publishProgress(progress);
}
@Override
public void onSyncComplete() {}
@Override
public boolean isSyncCancelled() {
return isCancelled();
}};
if (parser.syncToDatabase(listener)) {
int highestNumber = parser.getHighestReadBundleNumber();
if (highestNumber > currBundleNumber) {
prefs.edit().putInt(ImageManager.PREFERENCES_KEY_IMAGE_MANAGER_VERSION, highestNumber).apply();
}
Log.d("Image", "Parsed and synced bundles: Loaded images from XML with highest read number= " + highestNumber);
} else {
Log.d("Image", "Parsing for image sync: no new bundles, cancelled or failed.");
}
if (!isCancelled()) {
SYNCING_TASK_HAS_FINISHED_THIS_RUN = true;
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... progress) {
if (mListener != null && progress != null && progress.length > 0) {
mListener.onSyncProgress(progress[0]);
}
}
@Override
protected void onPostExecute(Void nothing) {
Log.d("Image", "ImageManager on sync done.");
SYNCING_TASK = null;
if (mListener != null) {
mListener.onSyncComplete();
mListener = null;
}
}
}
public interface SynchronizationListener {
void onSyncProgress( int progress); // image progress in percent
void onSyncComplete();
boolean isSyncCancelled();
}
public static void removeInvalidImageImmediately(Context context, Image image) {
if (image != null) {
image.deleteFromDatabase(context);
}
}
}