/*
* Copyright (C) 2015 Federico Iosue (federico.iosue@gmail.com)
*
* This program 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.
*
* This program 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 <http://www.gnu.org/licenses/>.
*/
package it.feio.android.omninotes.async;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.net.Uri;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import exceptions.ImportException;
import it.feio.android.omninotes.MainActivity;
import it.feio.android.omninotes.OmniNotes;
import it.feio.android.omninotes.R;
import it.feio.android.omninotes.db.DbHelper;
import it.feio.android.omninotes.models.Attachment;
import it.feio.android.omninotes.models.Category;
import it.feio.android.omninotes.models.Note;
import it.feio.android.omninotes.models.listeners.OnAttachingFileListener;
import it.feio.android.omninotes.utils.*;
import it.feio.android.springpadimporter.Importer;
import it.feio.android.springpadimporter.models.SpringpadAttachment;
import it.feio.android.springpadimporter.models.SpringpadComment;
import it.feio.android.springpadimporter.models.SpringpadElement;
import it.feio.android.springpadimporter.models.SpringpadItem;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.RegexFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.*;
public class DataBackupIntentService extends IntentService implements OnAttachingFileListener {
public final static String INTENT_BACKUP_NAME = "backup_name";
public final static String INTENT_BACKUP_INCLUDE_SETTINGS = "backup_include_settings";
public final static String ACTION_DATA_EXPORT = "action_data_export";
public final static String ACTION_DATA_IMPORT = "action_data_import";
public final static String ACTION_DATA_IMPORT_SPRINGPAD = "action_data_import_springpad";
public final static String ACTION_DATA_DELETE = "action_data_delete";
public final static String EXTRA_SPRINGPAD_BACKUP = "extra_springpad_backup";
private SharedPreferences prefs;
private NotificationsHelper mNotificationsHelper;
private int importedSpringpadNotes, importedSpringpadNotebooks;
public DataBackupIntentService() {
super("DataBackupIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
prefs = getSharedPreferences(Constants.PREFS_NAME, MODE_MULTI_PROCESS);
// Creates an indeterminate processing notification until the work is complete
mNotificationsHelper = new NotificationsHelper(this)
.createNotification(R.drawable.ic_content_save_white_24dp, getString(R.string.working), null)
.setIndeterminate().setOngoing().show();
// If an alarm has been fired a notification must be generated
if (ACTION_DATA_EXPORT.equals(intent.getAction())) {
exportData(intent);
} else if (ACTION_DATA_IMPORT.equals(intent.getAction())) {
importData(intent);
} else if (ACTION_DATA_IMPORT_SPRINGPAD.equals(intent.getAction())) {
importDataFromSpringpad(intent);
} else if (ACTION_DATA_DELETE.equals(intent.getAction())) {
deleteData(intent);
}
}
synchronized private void exportData(Intent intent) {
// Gets backup folder
String backupName = intent.getStringExtra(INTENT_BACKUP_NAME);
File backupDir = StorageHelper.getBackupDir(backupName);
// Directory clean in case of previously used backup name
StorageHelper.delete(this, backupDir.getAbsolutePath());
// Directory is re-created in case of previously used backup name (removed above)
backupDir = StorageHelper.getBackupDir(backupName);
// Database backup
exportDB(backupDir);
// exportNotes(backupDir);
// Attachments backup
exportAttachments(backupDir);
// Settings
if (intent.getBooleanExtra(INTENT_BACKUP_INCLUDE_SETTINGS, true)) {
exportSettings(backupDir);
}
// Notification of operation ended
String title = getString(R.string.data_export_completed);
String text = backupDir.getPath();
createNotification(intent, this, title, text, backupDir);
}
synchronized private void importData(Intent intent) {
// Gets backup folder
String backupName = intent.getStringExtra(INTENT_BACKUP_NAME);
File backupDir = StorageHelper.getBackupDir(backupName);
// Database backup
importDB(backupDir);
// importNotes(backupDir);
// Attachments backup
importAttachments(backupDir);
// Settings restore
importSettings(backupDir);
// Reminders restore
resetReminders();
String title = getString(R.string.data_import_completed);
String text = getString(R.string.click_to_refresh_application);
createNotification(intent, this, title, text, backupDir);
}
/**
* Imports notes and notebooks from Springpad exported archive
*
* @param intent
*/
synchronized private void importDataFromSpringpad(Intent intent) {
String backupPath = intent.getStringExtra(EXTRA_SPRINGPAD_BACKUP);
Importer importer = new Importer();
try {
importer.setZipProgressesListener(percentage -> mNotificationsHelper.setMessage(getString(R.string.extracted) + " " + percentage + "%").show());
importer.doImport(backupPath);
// Updating notification
updateImportNotification(importer);
} catch (ImportException e) {
new NotificationsHelper(this)
.createNotification(R.drawable.ic_emoticon_sad_white_24dp,
getString(R.string.import_fail) + ": " + e.getMessage(), null).setLedActive().show();
return;
}
List<SpringpadElement> elements = importer.getSpringpadNotes();
// If nothing is retrieved it will exit
if (elements == null || elements.size() == 0) {
return;
}
// These maps are used to associate with post processing notes to categories (notebooks)
HashMap<String, Category> categoriesWithUuid = new HashMap<>();
// Adds all the notebooks (categories)
for (SpringpadElement springpadElement : importer.getNotebooks()) {
Category cat = new Category();
cat.setName(springpadElement.getName());
cat.setColor(String.valueOf(Color.parseColor("#F9EA1B")));
DbHelper.getInstance().updateCategory(cat);
categoriesWithUuid.put(springpadElement.getUuid(), cat);
// Updating notification
importedSpringpadNotebooks++;
updateImportNotification(importer);
}
// And creates a default one for notes without notebook
Category defaulCategory = new Category();
defaulCategory.setName("Springpad");
defaulCategory.setColor(String.valueOf(Color.parseColor("#F9EA1B")));
DbHelper.getInstance().updateCategory(defaulCategory);
// And then notes are created
Note note;
Attachment mAttachment = null;
Uri uri;
for (SpringpadElement springpadElement : importer.getNotes()) {
note = new Note();
// Title
note.setTitle(springpadElement.getName());
// Content dependent from type of Springpad note
StringBuilder content = new StringBuilder();
content.append(TextUtils.isEmpty(springpadElement.getText()) ? "" : Html.fromHtml(springpadElement
.getText()));
content.append(TextUtils.isEmpty(springpadElement.getDescription()) ? "" : springpadElement
.getDescription());
// Some notes could have been exported wrongly
if (springpadElement.getType() == null) {
Toast.makeText(this, getString(R.string.error), Toast.LENGTH_SHORT).show();
continue;
}
if (springpadElement.getType().equals(SpringpadElement.TYPE_VIDEO)) {
try {
content.append(System.getProperty("line.separator")).append(springpadElement.getVideos().get(0));
} catch (IndexOutOfBoundsException e) {
content.append(System.getProperty("line.separator")).append(springpadElement.getUrl());
}
}
if (springpadElement.getType().equals(SpringpadElement.TYPE_TVSHOW)) {
content.append(System.getProperty("line.separator")).append(
TextUtils.join(", ", springpadElement.getCast()));
}
if (springpadElement.getType().equals(SpringpadElement.TYPE_BOOK)) {
content.append(System.getProperty("line.separator")).append("Author: ")
.append(springpadElement.getAuthor()).append(System.getProperty("line.separator"))
.append("Publication date: ").append(springpadElement.getPublicationDate());
}
if (springpadElement.getType().equals(SpringpadElement.TYPE_RECIPE)) {
content.append(System.getProperty("line.separator")).append("Ingredients: ")
.append(springpadElement.getIngredients()).append(System.getProperty("line.separator"))
.append("Directions: ").append(springpadElement.getDirections());
}
if (springpadElement.getType().equals(SpringpadElement.TYPE_BOOKMARK)) {
content.append(System.getProperty("line.separator")).append(springpadElement.getUrl());
}
if (springpadElement.getType().equals(SpringpadElement.TYPE_BUSINESS)
&& springpadElement.getPhoneNumbers() != null) {
content.append(System.getProperty("line.separator")).append("Phone number: ")
.append(springpadElement.getPhoneNumbers().getPhone());
}
if (springpadElement.getType().equals(SpringpadElement.TYPE_PRODUCT)) {
content.append(System.getProperty("line.separator")).append("Category: ")
.append(springpadElement.getCategory()).append(System.getProperty("line.separator"))
.append("Manufacturer: ").append(springpadElement.getManufacturer())
.append(System.getProperty("line.separator")).append("Price: ")
.append(springpadElement.getPrice());
}
if (springpadElement.getType().equals(SpringpadElement.TYPE_WINE)) {
content.append(System.getProperty("line.separator")).append("Wine type: ")
.append(springpadElement.getWine_type()).append(System.getProperty("line.separator"))
.append("Varietal: ").append(springpadElement.getVarietal())
.append(System.getProperty("line.separator")).append("Price: ")
.append(springpadElement.getPrice());
}
if (springpadElement.getType().equals(SpringpadElement.TYPE_ALBUM)) {
content.append(System.getProperty("line.separator")).append("Artist: ")
.append(springpadElement.getArtist());
}
for (SpringpadComment springpadComment : springpadElement.getComments()) {
content.append(System.getProperty("line.separator")).append(springpadComment.getCommenter())
.append(" commented at 0").append(springpadComment.getDate()).append(": ")
.append(springpadElement.getArtist());
}
note.setContent(content.toString());
// Checklists
if (springpadElement.getType().equals(SpringpadElement.TYPE_CHECKLIST)) {
StringBuilder sb = new StringBuilder();
String checkmark;
for (SpringpadItem mSpringpadItem : springpadElement.getItems()) {
checkmark = mSpringpadItem.getComplete() ? it.feio.android.checklistview.interfaces.Constants
.CHECKED_SYM
: it.feio.android.checklistview.interfaces.Constants.UNCHECKED_SYM;
sb.append(checkmark).append(mSpringpadItem.getName()).append(System.getProperty("line.separator"));
}
note.setContent(sb.toString());
note.setChecklist(true);
}
// Tags
String tags = springpadElement.getTags().size() > 0 ? "#"
+ TextUtils.join(" #", springpadElement.getTags()) : "";
if (note.isChecklist()) {
note.setTitle(note.getTitle() + tags);
} else {
note.setContent(note.getContent() + System.getProperty("line.separator") + tags);
}
// Address
String address = springpadElement.getAddresses() != null ? springpadElement.getAddresses().getAddress()
: "";
if (!TextUtils.isEmpty(address)) {
try {
double[] coords = GeocodeHelper.getCoordinatesFromAddress(this, address);
note.setLatitude(coords[0]);
note.setLongitude(coords[1]);
} catch (IOException e) {
Log.e(Constants.TAG, "An error occurred trying to resolve address to coords during Springpad import");
}
note.setAddress(address);
}
// Reminder
if (springpadElement.getDate() != null) {
note.setAlarm(springpadElement.getDate().getTime());
}
// Creation, modification, category
note.setCreation(springpadElement.getCreated().getTime());
note.setLastModification(springpadElement.getModified().getTime());
// Image
String image = springpadElement.getImage();
if (!TextUtils.isEmpty(image)) {
try {
File file = StorageHelper.createNewAttachmentFileFromHttp(this, image);
uri = Uri.fromFile(file);
String mimeType = StorageHelper.getMimeType(uri.getPath());
mAttachment = new Attachment(uri, mimeType);
} catch (MalformedURLException e) {
uri = Uri.parse(importer.getWorkingPath() + image);
mAttachment = StorageHelper.createAttachmentFromUri(this, uri, true);
} catch (IOException e) {
Log.e(Constants.TAG, "Error retrieving Springpad online image");
}
if (mAttachment != null) {
note.addAttachment(mAttachment);
}
mAttachment = null;
}
// Other attachments
for (SpringpadAttachment springpadAttachment : springpadElement.getAttachments()) {
// The attachment could be the image itself so it's jumped
if (image != null && image.equals(springpadAttachment.getUrl())) continue;
if (TextUtils.isEmpty(springpadAttachment.getUrl())) {
continue;
}
// Tries first with online images
try {
File file = StorageHelper.createNewAttachmentFileFromHttp(this, springpadAttachment.getUrl());
uri = Uri.fromFile(file);
String mimeType = StorageHelper.getMimeType(uri.getPath());
mAttachment = new Attachment(uri, mimeType);
} catch (MalformedURLException e) {
uri = Uri.parse(importer.getWorkingPath() + springpadAttachment.getUrl());
mAttachment = StorageHelper.createAttachmentFromUri(this, uri, true);
} catch (IOException e) {
Log.e(Constants.TAG, "Error retrieving Springpad online image");
}
if (mAttachment != null) {
note.addAttachment(mAttachment);
}
mAttachment = null;
}
// If the note has a category is added to the map to be post-processed
if (springpadElement.getNotebooks().size() > 0) {
note.setCategory(categoriesWithUuid.get(springpadElement.getNotebooks().get(0)));
} else {
note.setCategory(defaulCategory);
}
// The note is saved
DbHelper.getInstance().updateNote(note, false);
ReminderHelper.addReminder(OmniNotes.getAppContext(), note);
// Updating notification
importedSpringpadNotes++;
updateImportNotification(importer);
}
// Delete temp data
try {
importer.clean();
} catch (IOException e) {
Log.w(Constants.TAG, "Springpad import temp files not deleted");
}
String title = getString(R.string.data_import_completed);
String text = getString(R.string.click_to_refresh_application);
createNotification(intent, this, title, text, null);
}
private void updateImportNotification(Importer importer) {
mNotificationsHelper.setMessage(
importer.getNotebooksCount() + " " + getString(R.string.categories) + " ("
+ importedSpringpadNotebooks + " " + getString(R.string.imported) + "), "
+ +importer.getNotesCount() + " " + getString(R.string.notes) + " ("
+ importedSpringpadNotes + " " + getString(R.string.imported) + ")").show();
}
synchronized private void deleteData(Intent intent) {
// Gets backup folder
String backupName = intent.getStringExtra(INTENT_BACKUP_NAME);
File backupDir = StorageHelper.getBackupDir(backupName);
// Backup directory removal
StorageHelper.delete(this, backupDir.getAbsolutePath());
String title = getString(R.string.data_deletion_completed);
String text = backupName + " " + getString(R.string.deleted);
createNotification(intent, this, title, text, backupDir);
}
/**
* Creation of notification on operations completed
*/
private void createNotification(Intent intent, Context mContext, String title, String message, File backupDir) {
// The behavior differs depending on intent action
Intent intentLaunch;
if (DataBackupIntentService.ACTION_DATA_IMPORT.equals(intent.getAction())
|| DataBackupIntentService.ACTION_DATA_IMPORT_SPRINGPAD.equals(intent.getAction())) {
intentLaunch = new Intent(mContext, MainActivity.class);
intentLaunch.setAction(Constants.ACTION_RESTART_APP);
} else {
intentLaunch = new Intent();
}
// Add this bundle to the intent
intentLaunch.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intentLaunch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// Creates the PendingIntent
PendingIntent notifyIntent = PendingIntent.getActivity(mContext, 0, intentLaunch,
PendingIntent.FLAG_UPDATE_CURRENT);
NotificationsHelper mNotificationsHelper = new NotificationsHelper(mContext);
mNotificationsHelper.createNotification(R.drawable.ic_content_save_white_24dp, title, notifyIntent)
.setMessage(message).setRingtone(prefs.getString("settings_notification_ringtone", null))
.setLedActive();
if (prefs.getBoolean("settings_notification_vibration", true)) mNotificationsHelper.setVibration();
mNotificationsHelper.show();
}
/**
* Export database to backup folder
*
* @return True if success, false otherwise
*/
private boolean exportDB(File backupDir) {
File database = getDatabasePath(Constants.DATABASE_NAME);
return (StorageHelper.copyFile(database, new File(backupDir, Constants.DATABASE_NAME)));
}
private void exportNotes(File backupDir) {
for (Note note : DbHelper.getInstance().getAllNotes(false)) {
File noteFile = new File(backupDir, String.valueOf(note.get_id()));
try {
FileUtils.write(noteFile, note.toJSON());
} catch (IOException e) {
Log.e(Constants.TAG, "Error backupping note: " + note.get_id());
}
}
}
/**
* Export attachments to backup folder
*
* @return True if success, false otherwise
*/
private boolean exportAttachments(File backupDir) {
File attachmentsDir = StorageHelper.getAttachmentDir(this);
File destinationattachmentsDir = new File(backupDir, attachmentsDir.getName());
DbHelper db = DbHelper.getInstance();
ArrayList<Attachment> list = db.getAllAttachments();
int exported = 0;
for (Attachment attachment : list) {
StorageHelper.copyToBackupDir(destinationattachmentsDir, new File(attachment.getUri().getPath()));
mNotificationsHelper.setMessage(TextHelper.capitalize(getString(R.string.attachment)) + " " + exported++ + "/" + list.size())
.show();
}
return true;
}
/**
* Exports settings if required
*/
private boolean exportSettings(File backupDir) {
File preferences = StorageHelper.getSharedPreferencesFile(this);
return (StorageHelper.copyFile(preferences, new File(backupDir, preferences.getName())));
}
/**
* Imports settings
*/
private boolean importSettings(File backupDir) {
File preferences = StorageHelper.getSharedPreferencesFile(this);
File preferenceBackup = new File(backupDir, preferences.getName());
return (StorageHelper.copyFile(preferenceBackup, preferences));
}
/**
* Schedules reminders
*/
private void resetReminders() {
Log.d(Constants.TAG, "Resettings reminders");
for (Note note : DbHelper.getInstance().getNotesWithReminderNotFired()) {
ReminderHelper.addReminder(OmniNotes.getAppContext(), note);
}
}
/**
* Import database from backup folder
*/
private boolean importDB(File backupDir) {
File database = getDatabasePath(Constants.DATABASE_NAME);
if (database.exists()) {
database.delete();
}
return (StorageHelper.copyFile(new File(backupDir, Constants.DATABASE_NAME), database));
}
private void importNotes(File backupDir) {
for (File file : FileUtils.listFiles(backupDir, new RegexFileFilter("\\d{13}"), TrueFileFilter.INSTANCE)) {
try {
Note note = new Note();
note.buildFromJson(FileUtils.readFileToString(file));
if (note.getCategory() != null) {
DbHelper.getInstance().updateCategory(note.getCategory());
}
for (Attachment attachment : note.getAttachmentsList()) {
DbHelper.getInstance().updateAttachment(attachment);
}
DbHelper.getInstance().updateNote(note, false);
} catch (IOException e) {
Log.e(Constants.TAG, "Error parsing note json");
}
}
}
/**
* Import attachments from backup folder
*/
private boolean importAttachments(File backupDir) {
File attachmentsDir = StorageHelper.getAttachmentDir(this);
// Moving back
File backupAttachmentsDir = new File(backupDir, attachmentsDir.getName());
if (!backupAttachmentsDir.exists()) return true;
boolean result = true;
Collection list = FileUtils.listFiles(backupAttachmentsDir, FileFilterUtils.trueFileFilter(),
TrueFileFilter.INSTANCE);
Iterator i = list.iterator();
int imported = 0;
File file = null;
while (i.hasNext()) {
try {
file = (File) i.next();
FileUtils.copyFileToDirectory(file, attachmentsDir, true);
mNotificationsHelper.setMessage(TextHelper.capitalize(getString(R.string.attachment)) + " " + imported++ + "/" + list.size())
.show();
} catch (IOException e) {
result = false;
Log.e(Constants.TAG, "Error importing the attachment " + file.getName());
}
}
return result;
}
@Override
public void onAttachingFileErrorOccurred(Attachment mAttachment) {
// TODO Auto-generated method stub
}
@Override
public void onAttachingFileFinished(Attachment mAttachment) {
// TODO Auto-generated method stub
}
}