Merge remote-tracking branch 'origin/130208_sb_task_attachments' into 130122_sb_new_sync

pull/14/head
Sam Bosley 13 years ago
commit ee0b5ddae4

@ -10,6 +10,7 @@ import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@ -153,6 +154,32 @@ public class AndroidUtilities {
return BitmapFactory.decodeByteArray(decodedByte, 0, decodedByte.length);
}
private static final int BUFFER_SIZE = 4096;
public static String encodeBase64File(File file) {
try {
FileInputStream in = new FileInputStream(file);
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[BUFFER_SIZE];
int len = 0;
while ((len = in.read(buffer)) > 0) {
baos.write(buffer, 0, len);
}
byte[] bytes = baos.toByteArray();
return Base64.encodeToString(bytes, Base64.DEFAULT);
} finally {
in.close();
}
} catch (FileNotFoundException e) {
return null;
} catch (IOException e) {
return null;
}
}
/**
* Start the given intent, handling security exceptions if they arise
*

@ -0,0 +1,193 @@
/**
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.data;
import android.content.ContentValues;
import android.net.Uri;
import com.todoroo.andlib.data.AbstractModel;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.Property.IntegerProperty;
import com.todoroo.andlib.data.Property.LongProperty;
import com.todoroo.andlib.data.Property.StringProperty;
import com.todoroo.andlib.data.Table;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.api.AstridApiConstants;
/**
* Data Model which represents a user.
*
* @author Tim Su <tim@todoroo.com>
*
*/
@SuppressWarnings("nls")
public final class TaskAttachment extends RemoteModel {
// --- table and uri
/** table for this model */
public static final Table TABLE = new Table("task_attachments", TaskAttachment.class);
/** model class for entries in the outstanding table */
public static final Class<? extends OutstandingEntry<TaskAttachment>> OUTSTANDING_MODEL = TaskAttachmentOutstanding.class;
/** content uri for this model */
public static final Uri CONTENT_URI = Uri.parse("content://" + AstridApiConstants.API_PACKAGE + "/" +
TABLE.name);
// --- properties
/** ID */
public static final LongProperty ID = new LongProperty(
TABLE, ID_PROPERTY_NAME);
/** Remote id */
public static final StringProperty UUID = new StringProperty(
TABLE, UUID_PROPERTY_NAME);
/** Pushed at date */
public static final LongProperty PUSHED_AT = new LongProperty(
TABLE, PUSHED_AT_PROPERTY_NAME);
/** Creator user id */
public static final StringProperty USER_UUID = new StringProperty(
TABLE, "user_id");
/** Task uuid */
public static final StringProperty TASK_UUID = new StringProperty(
TABLE, "task_id");
/** File name */
public static final StringProperty NAME = new StringProperty(
TABLE, "name");
/** File url (for downloading) */
public static final StringProperty URL = new StringProperty(
TABLE, "url");
/** File path (on local storage) */
public static final StringProperty FILE_PATH = new StringProperty(
TABLE, "path");
/** File size (in bytes) */
public static final IntegerProperty SIZE = new IntegerProperty(
TABLE, "size");
/** File mimetype */
public static final StringProperty CONTENT_TYPE = new StringProperty(
TABLE, "content_type");
/** Attachment creation date */
public static final LongProperty CREATED_AT = new LongProperty(
TABLE, "created_at", Property.PROP_FLAG_DATE);
/** Attachment deletion date */
public static final LongProperty DELETED_AT = new LongProperty(
TABLE, "deleted_at", Property.PROP_FLAG_DATE);
/** List of all properties for this model */
public static final Property<?>[] PROPERTIES = generateProperties(TaskAttachment.class);
// --- defaults
/** Default values container */
private static final ContentValues defaultValues = new ContentValues();
static {
defaultValues.put(UUID.name, NO_UUID);
defaultValues.put(PUSHED_AT.name, 0);
defaultValues.put(USER_UUID.name, NO_UUID);
defaultValues.put(TASK_UUID.name, NO_UUID);
defaultValues.put(NAME.name, "");
defaultValues.put(URL.name, "");
defaultValues.put(FILE_PATH.name, "");
defaultValues.put(SIZE.name, 0);
defaultValues.put(CONTENT_TYPE.name, "");
defaultValues.put(CREATED_AT.name, 0);
defaultValues.put(DELETED_AT.name, 0);
}
@Override
public ContentValues getDefaultValues() {
return defaultValues;
}
// -- Constants
/** default directory for files on external storage */
public static final String FILES_DIRECTORY_DEFAULT = "attachments"; //$NON-NLS-1$
/** preference key for some other download directory */
public static final String FILES_DIRECTORY_PREF = "custom_files_dir"; //$NON-NLS-1$
/** Constants for file types */
public static final String FILE_TYPE_AUDIO = "audio/"; //$NON-NLS-1$
public static final String FILE_TYPE_IMAGE = "image/"; //$NON-NLS-1$
public static final String FILE_TYPE_PDF = "application/pdf"; //$NON-NLS-1$
public static final String FILE_TYPE_DOC = "application/msword"; //$NON-NLS-1$
public static final String FILE_TYPE_DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; //$NON-NLS-1$
public static final String FILE_TYPE_PPT = "application/vnd.ms-powerpoint"; //$NON-NLS-1$
public static final String FILE_TYPE_PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; //$NON-NLS-1$
public static final String FILE_TYPE_XLS = "application/vnd.ms-excel"; //$NON-NLS-1$
public static final String FILE_TYPE_XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; //$NON-NLS-1$
public static final String[] MS_FILETYPES = {
FILE_TYPE_DOC, FILE_TYPE_DOCX,
FILE_TYPE_XLS, FILE_TYPE_XLSX,
FILE_TYPE_PPT, FILE_TYPE_PPTX,
};
public static final String FILE_TYPE_OTHER = "application/octet-stream"; //$NON-NLS-1$
public static TaskAttachment createNewAttachment(String taskUuid, String filePath, String fileName, String fileType) {
TaskAttachment attachment = new TaskAttachment();
attachment.setValue(TaskAttachment.TASK_UUID, taskUuid);
attachment.setValue(NAME, fileName);
attachment.setValue(USER_UUID, Task.USER_ID_SELF);
attachment.setValue(FILE_PATH, filePath);
attachment.setValue(CONTENT_TYPE, fileType);
attachment.setValue(CREATED_AT, DateUtilities.now());
attachment.setValue(DELETED_AT, 0L);
return attachment;
}
// --- data access boilerplate
public TaskAttachment() {
super();
}
public TaskAttachment(TodorooCursor<TaskAttachment> cursor) {
this();
readPropertiesFromCursor(cursor);
}
public void readFromCursor(TodorooCursor<TaskAttachment> cursor) {
super.readPropertiesFromCursor(cursor);
}
@Override
public long getId() {
return getIdHelper(ID);
}
@Override
public String getUuid() {
return getUuidHelper(UUID);
}
// --- parcelable helpers
public static final Creator<TaskAttachment> CREATOR = new ModelCreator<TaskAttachment>(TaskAttachment.class);
@Override
protected Creator<? extends AbstractModel> getCreator() {
return CREATOR;
}
}

@ -0,0 +1,63 @@
package com.todoroo.astrid.data;
import android.content.ContentValues;
import com.todoroo.andlib.data.AbstractModel;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.Property.LongProperty;
import com.todoroo.andlib.data.Property.StringProperty;
import com.todoroo.andlib.data.Table;
@SuppressWarnings("nls")
public class TaskAttachmentOutstanding extends OutstandingEntry<TaskAttachment> {
/** table for this model */
public static final Table TABLE = new Table("task_attachment_outstanding", TaskAttachmentOutstanding.class);
// --- properties
/** ID */
public static final LongProperty ID = new LongProperty(
TABLE, ID_PROPERTY_NAME);
public static final LongProperty TASK_ATTACHMENT_ID = new LongProperty(
TABLE, ENTITY_ID_PROPERTY_NAME);
public static final StringProperty COLUMN_STRING = new StringProperty(
TABLE, COLUMN_STRING_PROPERTY_NAME);
public static final StringProperty VALUE_STRING = new StringProperty(
TABLE, VALUE_STRING_PROPERTY_NAME);
public static final LongProperty CREATED_AT = new LongProperty(
TABLE, CREATED_AT_PROPERTY_NAME);
private static final ContentValues defaultValues = new ContentValues();
static {
defaultValues.put(TASK_ATTACHMENT_ID.name, 0);
defaultValues.put(COLUMN_STRING.name, "");
defaultValues.put(VALUE_STRING.name, "");
}
/** List of all properties for this model */
public static final Property<?>[] PROPERTIES = generateProperties(TaskAttachmentOutstanding.class);
@Override
public ContentValues getDefaultValues() {
return defaultValues;
}
@Override
public long getId() {
return getIdHelper(ID);
}
public static final Creator<TaskAttachmentOutstanding> CREATOR = new ModelCreator<TaskAttachmentOutstanding>(TaskAttachmentOutstanding.class);
@Override
protected Creator<? extends AbstractModel> getCreator() {
return CREATOR;
}
}

@ -5,18 +5,13 @@
*/
package com.todoroo.astrid.actfm.sync;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -26,11 +21,8 @@ import android.text.TextUtils;
import android.util.Log;
import com.timsu.astrid.GCMIntentService;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.billing.BillingConstants;
@ -38,13 +30,9 @@ import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.UserDao;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.MetadataApiDao.MetadataCriteria;
import com.todoroo.astrid.data.RemoteModel;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.User;
import com.todoroo.astrid.files.FileMetadata;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.TagDataService;
@ -290,99 +278,99 @@ public final class ActFmSyncService {
handleException("fetch-tag-order-io", e);
}
}
public void pushAttachmentInBackground(final Metadata fileMetadata) {
if (!ActFmPreferenceService.isPremiumUser())
return;
new Thread(new Runnable() {
@Override
public void run() {
waitUntilEmpty.close();
taskPushThreads.incrementAndGet();
try {
Task t = taskDao.fetch(fileMetadata.getValue(Metadata.TASK), Task.UUID);
if (t == null || !RemoteModel.isValidUuid(t.getUuid()))
return;
if (fileMetadata.getValue(FileMetadata.DELETION_DATE) > 0)
deleteAttachment(fileMetadata);
else
pushAttachment(t.getValue(Task.UUID), fileMetadata);
} finally {
if (taskPushThreads.decrementAndGet() == 0) {
waitUntilEmpty.open();
}
}
}
}).start();
}
/**
* Push a file attachment to the server
* @param remoteTaskId
* @param fileMetadata
*/
public void pushAttachment(String remoteTaskId, Metadata fileMetadata) {
if (!ActFmPreferenceService.isPremiumUser())
return;
if (!fileMetadata.containsNonNullValue(FileMetadata.FILE_PATH) || !RemoteModel.isValidUuid(remoteTaskId))
return;
File f = new File(fileMetadata.getValue(FileMetadata.FILE_PATH));
if (!f.exists())
return;
if (!checkForToken())
return;
ArrayList<Object> params = new ArrayList<Object>();
params.add("task_id"); params.add(remoteTaskId);
params.add("token"); params.add(token);
try {
MultipartEntity entity = new MultipartEntity();
FileBody body = new FileBody(f, fileMetadata.getValue(FileMetadata.FILE_TYPE));
entity.addPart("file", body);
JSONObject result = actFmInvoker.post("task_attachment_create", entity,
params.toArray(new Object[params.size()]));
fileMetadata.setValue(FileMetadata.REMOTE_ID, result.optLong("id"));
fileMetadata.setValue(FileMetadata.URL, result.optString("url"));
metadataService.save(fileMetadata);
} catch (ActFmServiceException e) {
handleException("push-attachment-error", e);
} catch (IOException e) {
handleException("push-attachment-error", e);
}
}
public void deleteAttachment(Metadata fileMetadata) {
long attachmentId = fileMetadata.getValue(FileMetadata.REMOTE_ID);
if (attachmentId <= 0)
return;
if (!checkForToken())
return;
ArrayList<Object> params = new ArrayList<Object>();
params.add("id"); params.add(attachmentId);
params.add("token"); params.add(token);
try {
JSONObject result = actFmInvoker.post("task_attachment_remove", null, params.toArray(new Object[params.size()]));
if (result.optString("status").equals("success")) {
metadataService.delete(fileMetadata);
}
} catch (ActFmServiceException e) {
if (e.result != null && e.result.optString("code").equals("not_found"))
metadataService.delete(fileMetadata);
else
handleException("push-attachment-error", e);
} catch (IOException e) {
handleException("push-attachment-error", e);
}
}
//
// public void pushAttachmentInBackground(final Metadata fileMetadata) {
// if (!ActFmPreferenceService.isPremiumUser())
// return;
// new Thread(new Runnable() {
// @Override
// public void run() {
// waitUntilEmpty.close();
// taskPushThreads.incrementAndGet();
// try {
// Task t = taskDao.fetch(fileMetadata.getValue(Metadata.TASK), Task.UUID);
// if (t == null || !RemoteModel.isValidUuid(t.getUuid()))
// return;
// if (fileMetadata.getValue(FileMetadata.DELETION_DATE) > 0)
// deleteAttachment(fileMetadata);
// else
// pushAttachment(t.getValue(Task.UUID), fileMetadata);
// } finally {
// if (taskPushThreads.decrementAndGet() == 0) {
// waitUntilEmpty.open();
// }
// }
// }
// }).start();
// }
//
// /**
// * Push a file attachment to the server
// * @param remoteTaskId
// * @param fileMetadata
// */
// public void pushAttachment(String remoteTaskId, Metadata fileMetadata) {
// if (!ActFmPreferenceService.isPremiumUser())
// return;
//
// if (!fileMetadata.containsNonNullValue(FileMetadata.FILE_PATH) || !RemoteModel.isValidUuid(remoteTaskId))
// return;
//
// File f = new File(fileMetadata.getValue(FileMetadata.FILE_PATH));
// if (!f.exists())
// return;
//
// if (!checkForToken())
// return;
//
// ArrayList<Object> params = new ArrayList<Object>();
// params.add("task_id"); params.add(remoteTaskId);
// params.add("token"); params.add(token);
//
// try {
// MultipartEntity entity = new MultipartEntity();
// FileBody body = new FileBody(f, fileMetadata.getValue(FileMetadata.FILE_TYPE));
// entity.addPart("file", body);
//
// JSONObject result = actFmInvoker.post("task_attachment_create", entity,
// params.toArray(new Object[params.size()]));
//
// fileMetadata.setValue(FileMetadata.REMOTE_ID, result.optLong("id"));
// fileMetadata.setValue(FileMetadata.URL, result.optString("url"));
// metadataService.save(fileMetadata);
// } catch (ActFmServiceException e) {
// handleException("push-attachment-error", e);
// } catch (IOException e) {
// handleException("push-attachment-error", e);
// }
// }
//
// public void deleteAttachment(Metadata fileMetadata) {
// long attachmentId = fileMetadata.getValue(FileMetadata.REMOTE_ID);
// if (attachmentId <= 0)
// return;
//
// if (!checkForToken())
// return;
//
// ArrayList<Object> params = new ArrayList<Object>();
// params.add("id"); params.add(attachmentId);
// params.add("token"); params.add(token);
//
// try {
// JSONObject result = actFmInvoker.post("task_attachment_remove", null, params.toArray(new Object[params.size()]));
// if (result.optString("status").equals("success")) {
// metadataService.delete(fileMetadata);
// }
// } catch (ActFmServiceException e) {
// if (e.result != null && e.result.optString("code").equals("not_found"))
// metadataService.delete(fileMetadata);
// else
// handleException("push-attachment-error", e);
// } catch (IOException e) {
// handleException("push-attachment-error", e);
// }
// }
// --- data fetch methods
public int fetchFeaturedLists(int serverTime) throws JSONException, IOException {
@ -477,64 +465,64 @@ public final class ActFmSyncService {
parameters[i+2] = getParameters[i];
return actFmInvoker.invoke(method, parameters);
}
// --- helpers
private void synchronizeAttachments(JSONObject item, Task model) {
TodorooCursor<Metadata> attachments = metadataService.query(Query.select(Metadata.PROPERTIES)
.where(Criterion.and(MetadataCriteria.byTaskAndwithKey(model.getId(),
FileMetadata.METADATA_KEY), FileMetadata.REMOTE_ID.gt(0))));
try {
HashMap<Long, Metadata> currentFiles = new HashMap<Long, Metadata>();
for (attachments.moveToFirst(); !attachments.isAfterLast(); attachments.moveToNext()) {
Metadata m = new Metadata(attachments);
currentFiles.put(m.getValue(FileMetadata.REMOTE_ID), m);
}
JSONArray remoteFiles = item.getJSONArray("attachments");
for (int i = 0; i < remoteFiles.length(); i++) {
JSONObject file = remoteFiles.getJSONObject(i);
long id = file.optLong("id");
if (currentFiles.containsKey(id)) {
// Match, make sure name and url are up to date, then remove from map
Metadata fileMetadata = currentFiles.get(id);
fileMetadata.setValue(FileMetadata.URL, file.getString("url"));
fileMetadata.setValue(FileMetadata.NAME, file.getString("name"));
metadataService.save(fileMetadata);
currentFiles.remove(id);
} else {
// Create new file attachment
Metadata newAttachment = FileMetadata.createNewFileMetadata(model.getId(), "",
file.getString("name"), file.getString("content_type"));
String url = file.getString("url");
newAttachment.setValue(FileMetadata.URL, url);
newAttachment.setValue(FileMetadata.REMOTE_ID, id);
metadataService.save(newAttachment);
}
}
// Remove all the leftovers
Set<Long> attachmentsToDelete = currentFiles.keySet();
for (Long remoteId : attachmentsToDelete) {
Metadata toDelete = currentFiles.get(remoteId);
String path = toDelete.getValue(FileMetadata.FILE_PATH);
if (TextUtils.isEmpty(path))
metadataService.delete(toDelete);
else {
File f = new File(toDelete.getValue(FileMetadata.FILE_PATH));
if (!f.exists() || f.delete()) {
metadataService.delete(toDelete);
}
}
}
} catch (JSONException e) {
e.printStackTrace();
} finally {
attachments.close();
}
}
//
// // --- helpers
// private void synchronizeAttachments(JSONObject item, Task model) {
// TodorooCursor<Metadata> attachments = metadataService.query(Query.select(Metadata.PROPERTIES)
// .where(Criterion.and(MetadataCriteria.byTaskAndwithKey(model.getId(),
// FileMetadata.METADATA_KEY), FileMetadata.REMOTE_ID.gt(0))));
// try {
// HashMap<Long, Metadata> currentFiles = new HashMap<Long, Metadata>();
// for (attachments.moveToFirst(); !attachments.isAfterLast(); attachments.moveToNext()) {
// Metadata m = new Metadata(attachments);
// currentFiles.put(m.getValue(FileMetadata.REMOTE_ID), m);
// }
//
// JSONArray remoteFiles = item.getJSONArray("attachments");
// for (int i = 0; i < remoteFiles.length(); i++) {
// JSONObject file = remoteFiles.getJSONObject(i);
//
// long id = file.optLong("id");
// if (currentFiles.containsKey(id)) {
// // Match, make sure name and url are up to date, then remove from map
// Metadata fileMetadata = currentFiles.get(id);
// fileMetadata.setValue(FileMetadata.URL, file.getString("url"));
// fileMetadata.setValue(FileMetadata.NAME, file.getString("name"));
// metadataService.save(fileMetadata);
// currentFiles.remove(id);
// } else {
// // Create new file attachment
// Metadata newAttachment = FileMetadata.createNewFileMetadata(model.getId(), "",
// file.getString("name"), file.getString("content_type"));
// String url = file.getString("url");
// newAttachment.setValue(FileMetadata.URL, url);
// newAttachment.setValue(FileMetadata.REMOTE_ID, id);
// metadataService.save(newAttachment);
// }
// }
//
// // Remove all the leftovers
// Set<Long> attachmentsToDelete = currentFiles.keySet();
// for (Long remoteId : attachmentsToDelete) {
// Metadata toDelete = currentFiles.get(remoteId);
// String path = toDelete.getValue(FileMetadata.FILE_PATH);
// if (TextUtils.isEmpty(path))
// metadataService.delete(toDelete);
// else {
// File f = new File(toDelete.getValue(FileMetadata.FILE_PATH));
// if (!f.exists() || f.delete()) {
// metadataService.delete(toDelete);
// }
//
// }
// }
//
// } catch (JSONException e) {
// e.printStackTrace();
// } finally {
// attachments.close();
// }
// }
protected void handleException(String message, Exception exception) {

@ -32,6 +32,7 @@ import com.todoroo.astrid.dao.OutstandingEntryDao;
import com.todoroo.astrid.dao.RemoteModelDao;
import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.dao.TagOutstandingDao;
import com.todoroo.astrid.dao.TaskAttachmentDao;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.TaskOutstandingDao;
import com.todoroo.astrid.dao.UserActivityDao;
@ -41,6 +42,7 @@ import com.todoroo.astrid.data.RemoteModel;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.TagOutstanding;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.data.TaskOutstanding;
import com.todoroo.astrid.data.User;
import com.todoroo.astrid.data.UserActivity;
@ -87,7 +89,8 @@ public class ActFmSyncThread {
public static enum ModelType {
TYPE_TASK,
TYPE_TAG,
TYPE_ACTIVITY
TYPE_ACTIVITY,
TYPE_ATTACHMENT
}
private static volatile ActFmSyncThread instance;
@ -96,14 +99,14 @@ public class ActFmSyncThread {
if (instance == null) {
synchronized(ActFmSyncThread.class) {
if (instance == null) {
initializeSyncComponents(PluginServices.getTaskDao(), PluginServices.getTagDataDao(), PluginServices.getUserActivityDao());
initializeSyncComponents(PluginServices.getTaskDao(), PluginServices.getTagDataDao(), PluginServices.getUserActivityDao(), PluginServices.getTaskAttachmentDao());
}
}
}
return instance;
}
public static ActFmSyncThread initializeSyncComponents(TaskDao taskDao, TagDataDao tagDataDao, UserActivityDao userActivityDao) {
public static ActFmSyncThread initializeSyncComponents(TaskDao taskDao, TagDataDao tagDataDao, UserActivityDao userActivityDao, TaskAttachmentDao taskAttachmentDao) {
if (instance == null) {
synchronized(ActFmSyncThread.class) {
if (instance == null) {
@ -115,6 +118,7 @@ public class ActFmSyncThread {
taskDao.addListener(new SyncDatabaseListener<Task>(instance, ModelType.TYPE_TASK));
tagDataDao.addListener(new SyncDatabaseListener<TagData>(instance, ModelType.TYPE_TAG));
userActivityDao.addListener(new SyncDatabaseListener<UserActivity>(instance, ModelType.TYPE_ACTIVITY));
taskAttachmentDao.addListener(new SyncDatabaseListener<TaskAttachment>(instance, ModelType.TYPE_ATTACHMENT));
instance.startSyncThread();
}

@ -17,6 +17,7 @@ import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.OutstandingEntryDao;
import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.dao.TagOutstandingDao;
import com.todoroo.astrid.dao.TaskAttachmentDao;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.TaskOutstandingDao;
import com.todoroo.astrid.dao.UpdateDao;
@ -29,10 +30,12 @@ import com.todoroo.astrid.data.SyncFlags;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.TagOutstanding;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.data.TaskOutstanding;
import com.todoroo.astrid.data.Update;
import com.todoroo.astrid.data.User;
import com.todoroo.astrid.data.UserActivity;
import com.todoroo.astrid.files.FileMetadata;
import com.todoroo.astrid.helper.UUIDHelper;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.TagDataService;
@ -48,6 +51,7 @@ public class AstridNewSyncMigrator {
@Autowired private UpdateDao updateDao;
@Autowired private UserActivityDao userActivityDao;
@Autowired private UserDao userDao;
@Autowired private TaskAttachmentDao taskAttachmentDao;
@Autowired private TaskOutstandingDao taskOutstandingDao;
@Autowired private TagOutstandingDao tagOutstandingDao;
@ -131,9 +135,9 @@ public class AstridNewSyncMigrator {
Log.e(LOG_TAG, "Error asserting UUIDs", e);
}
// ----------
// --------------
// Migrate unsynced task comments to UserActivity table
// ----------
// --------------
try {
TodorooCursor<Update> updates = updateDao.query(Query.select(Update.PROPERTIES).where(
Criterion.and(Criterion.or(Update.UUID.eq(0), Update.UUID.isNull()), Criterion.or(Update.ACTION_CODE.eq(UserActivity.ACTION_TAG_COMMENT),
@ -177,15 +181,58 @@ public class AstridNewSyncMigrator {
}
// ----------
// --------------
// Drop any entries from the Users table that don't have a UUID
// ----------
// --------------
try {
userDao.deleteWhere(Criterion.or(User.UUID.isNull(), User.UUID.eq(""), User.UUID.eq("0")));
} catch (Exception e) {
Log.e(LOG_TAG, "Error deleting incomplete user entries", e);
}
// --------------
// Migrate legacy FileMetadata models to new TaskAttachment models
// --------------
try {
TodorooCursor<Metadata> fmCursor = metadataService.query(Query.select(Metadata.PROPERTIES)
.where(MetadataCriteria.withKey(FileMetadata.METADATA_KEY)));
try {
Metadata m = new Metadata();
for (fmCursor.moveToFirst(); !fmCursor.isAfterLast(); fmCursor.moveToNext()) {
m.clear();
m.readFromCursor(fmCursor);
TaskAttachment attachment = new TaskAttachment();
Task task = taskDao.fetch(m.getValue(Metadata.TASK), Task.UUID);
if (task == null || !RemoteModel.isValidUuid(task.getUuid()))
continue;
Long oldRemoteId = m.getValue(FileMetadata.REMOTE_ID);
boolean synced = false;
if (oldRemoteId != null && oldRemoteId > 0) {
synced = true;
attachment.setValue(TaskAttachment.UUID, Long.toString(oldRemoteId));
}
attachment.setValue(TaskAttachment.TASK_UUID, task.getUuid());
attachment.setValue(TaskAttachment.NAME, m.getValue(FileMetadata.NAME));
attachment.setValue(TaskAttachment.URL, m.getValue(FileMetadata.URL));
attachment.setValue(TaskAttachment.FILE_PATH, m.getValue(FileMetadata.FILE_PATH));
attachment.setValue(TaskAttachment.CONTENT_TYPE, m.getValue(FileMetadata.FILE_TYPE));
attachment.setValue(TaskAttachment.DELETED_AT, m.getValue(FileMetadata.DELETION_DATE));
if (synced)
attachment.putTransitory(SyncFlags.ACTFM_SUPPRESS_OUTSTANDING_ENTRIES, true);
taskAttachmentDao.createNew(attachment);
}
} finally {
fmCursor.close();
}
} catch (Exception e) {
Log.e(LOG_TAG, "Error migrating task attachment metadata", e);
}
// --------------
// Finally, ensure that all tag metadata entities have all important fields filled in
// --------------

@ -27,6 +27,8 @@ public class AcknowledgeChange extends ServerToClientMessage {
dao = PluginServices.getTagOutstandingDao();
else if (NameMaps.TABLE_ID_USER_ACTIVITY.equals(table))
dao = PluginServices.getUserActivityOutstandingDao();
else if (NameMaps.TABLE_ID_ATTACHMENTS.equals(table))
dao = PluginServices.getTaskAttachmentOutstandingDao();
else
dao = null;
}

@ -1,5 +1,6 @@
package com.todoroo.astrid.actfm.sync.messages;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
@ -7,6 +8,7 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.text.TextUtils;
import android.util.Log;
import com.todoroo.andlib.data.Property;
@ -14,6 +16,7 @@ import com.todoroo.andlib.data.Property.PropertyVisitor;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.sql.Order;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
import com.todoroo.astrid.actfm.sync.ActFmSyncThread.ModelType;
@ -26,6 +29,8 @@ import com.todoroo.astrid.data.RemoteModel;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.TagOutstanding;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.data.TaskAttachmentOutstanding;
import com.todoroo.astrid.data.TaskOutstanding;
import com.todoroo.astrid.data.UserActivity;
import com.todoroo.astrid.data.UserActivityOutstanding;
@ -52,6 +57,9 @@ public class ChangesHappened<TYPE extends RemoteModel, OE extends OutstandingEnt
case TYPE_ACTIVITY:
return new ChangesHappened<UserActivity, UserActivityOutstanding>(id, UserActivity.class,
PluginServices.getUserActivityDao(), PluginServices.getUserActivityOutstandingDao());
case TYPE_ATTACHMENT:
return new ChangesHappened<TaskAttachment, TaskAttachmentOutstanding>(id, TaskAttachment.class,
PluginServices.getTaskAttachmentDao(), PluginServices.getTaskAttachmentOutstandingDao());
default:
return null;
}
@ -110,6 +118,15 @@ public class ChangesHappened<TYPE extends RemoteModel, OE extends OutstandingEnt
} else if (NameMaps.MEMBER_REMOVED_COLUMN.equals(localColumn)) {
serverColumn = NameMaps.MEMBER_REMOVED_COLUMN;
changeJson.put("value", change.getValue(OutstandingEntry.VALUE_STRING_PROPERTY));
} else if (NameMaps.ATTACHMENT_ADDED_COLUMN.equals(localColumn)) {
serverColumn = NameMaps.ATTACHMENT_ADDED_COLUMN;
JSONObject fileJson = getFileJson(change.getValue(OutstandingEntry.VALUE_STRING_PROPERTY));
if (fileJson == null) {
PluginServices.getTaskAttachmentDao().delete(id);
continue;
} else {
changeJson.put("value", fileJson);
}
} else {
Property<?> localProperty = NameMaps.localColumnNameToProperty(table, localColumn);
if (localProperty == null)
@ -155,6 +172,28 @@ public class ChangesHappened<TYPE extends RemoteModel, OE extends OutstandingEnt
}
}
private JSONObject getFileJson(String value) {
try {
JSONObject obj = new JSONObject(value);
String path = obj.optString("path");
if (TextUtils.isEmpty(path))
return null;
File f = new File(path);
if (!f.exists())
return null;
String encodedFile = AndroidUtilities.encodeBase64File(f);
if (TextUtils.isEmpty(encodedFile))
return null;
obj.remove("path");
obj.put("data", encodedFile);
return obj;
} catch (JSONException e) {
return null;
}
}
private class PropertyToJSONVisitor implements PropertyVisitor<Object, OE> {
private String getAsString(OE data) {

@ -8,8 +8,10 @@ import java.util.Set;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.Table;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.astrid.data.History;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.data.User;
import com.todoroo.astrid.data.UserActivity;
@ -28,6 +30,7 @@ public class NameMaps {
public static final String TABLE_ID_USERS = "users";
public static final String TABLE_ID_USER_ACTIVITY = "user_activities";
public static final String TABLE_ID_HISTORY = "history";
public static final String TABLE_ID_ATTACHMENTS = "task_attachments";
private static final String PUSHED_AT_PREFIX = "pushed_at";
public static final String PUSHED_AT_TASKS = PUSHED_AT_PREFIX + "_" + TABLE_ID_TASKS;
@ -41,7 +44,9 @@ public class NameMaps {
TABLE_LOCAL_TO_SERVER.put(Task.TABLE, TABLE_ID_TASKS);
TABLE_LOCAL_TO_SERVER.put(TagData.TABLE, TABLE_ID_TAGS);
TABLE_LOCAL_TO_SERVER.put(User.TABLE, TABLE_ID_USERS);
TABLE_LOCAL_TO_SERVER.put(History.TABLE, TABLE_ID_HISTORY);
TABLE_LOCAL_TO_SERVER.put(UserActivity.TABLE, TABLE_ID_USER_ACTIVITY);
TABLE_LOCAL_TO_SERVER.put(TaskAttachment.TABLE, TABLE_ID_ATTACHMENTS);
// Reverse the mapping to construct the server to local map
TABLE_SERVER_TO_LOCAL = AndroidUtilities.reverseMap(TABLE_LOCAL_TO_SERVER);
@ -76,6 +81,8 @@ public class NameMaps {
return computeSyncableProperties(TAG_DATA_PROPERTIES_LOCAL_TO_SERVER.keySet(), TAG_PROPERTIES_EXCLUDED);
else if (TABLE_ID_USER_ACTIVITY.equals(table))
return computeSyncableProperties(USER_ACTIVITY_PROPERTIES_LOCAL_TO_SERVER.keySet(), USER_ACTIVITY_PROPERTIES_EXCLUDED);
else if (TABLE_ID_ATTACHMENTS.equals(table))
return computeSyncableProperties(TASK_ATTACHMENT_PROPERTIES_LOCAL_TO_SERVER.keySet(), TASK_ATTACHMENT_PROPERTIES_EXCLUDED);
return null;
}
@ -240,42 +247,48 @@ public class NameMaps {
}
// ----------
// History
// TaskAttachment
// ----------
// private static final Map<Property<?>, String> HISTORY_PROPERTIES_LOCAL_TO_SERVER;
// private static final Map<String, Property<?>> HISTORY_COLUMN_NAMES_TO_PROPERTIES;
// private static final Map<String, String> HISTORY_COLUMNS_LOCAL_TO_SERVER;
// private static final Map<String, Property<?>> HISTORY_PROPERTIES_SERVER_TO_LOCAL;
// private static final Set<String> HISTORY_PROPERTIES_EXCLUDED;
//
// private static void putHistoryPropertyToServerName(Property<?> property, String serverName, boolean writeable) {
// putPropertyToServerName(property, serverName, HISTORY_PROPERTIES_LOCAL_TO_SERVER, HISTORY_COLUMN_NAMES_TO_PROPERTIES,
// HISTORY_COLUMNS_LOCAL_TO_SERVER, HISTORY_PROPERTIES_EXCLUDED, writeable);
// }
//
// static {
// HISTORY_PROPERTIES_LOCAL_TO_SERVER = new HashMap<Property<?>, String>();
// HISTORY_COLUMN_NAMES_TO_PROPERTIES = new HashMap<String, Property<?>>();
// HISTORY_COLUMNS_LOCAL_TO_SERVER = new HashMap<String, String>();
// HISTORY_PROPERTIES_EXCLUDED = new HashSet<String>();
//
// putHistoryPropertyToServerName(History.UUID, "id", false);
// putHistoryPropertyToServerName(History.CREATED_AT, "created_at", false);
// putHistoryPropertyToServerName(History.USER_UUID, "user_id", false);
// putHistoryPropertyToServerName(History.COLUMN, "column", false);
// putHistoryPropertyToServerName(History.OLD_VALUE, "prev", false);
// putHistoryPropertyToServerName(History.NEW_VALUE, "value", false);
//
// // Reverse the mapping to construct the server to local map
// HISTORY_PROPERTIES_SERVER_TO_LOCAL = AndroidUtilities.reverseMap(USER_ACTIVITY_PROPERTIES_LOCAL_TO_SERVER);
// }
private static final Map<Property<?>, String> TASK_ATTACHMENT_PROPERTIES_LOCAL_TO_SERVER;
private static final Map<String, Property<?>> TASK_ATTACHMENT_COLUMN_NAMES_TO_PROPERTIES;
private static final Map<String, String> TASK_ATTACHMENT_COLUMNS_LOCAL_TO_SERVER;
private static final Map<String, Property<?>> TASK_ATTACHMENT_PROPERTIES_SERVER_TO_LOCAL;
private static final Set<String> TASK_ATTACHMENT_PROPERTIES_EXCLUDED;
public static final String ATTACHMENT_ADDED_COLUMN = "file";
private static void putTaskAttachmentPropertyToServerName(Property<?> property, String serverName, boolean writeable) {
putPropertyToServerName(property, serverName, TASK_ATTACHMENT_PROPERTIES_LOCAL_TO_SERVER, TASK_ATTACHMENT_COLUMN_NAMES_TO_PROPERTIES,
TASK_ATTACHMENT_COLUMNS_LOCAL_TO_SERVER, TASK_ATTACHMENT_PROPERTIES_EXCLUDED, writeable);
}
static {
TASK_ATTACHMENT_PROPERTIES_LOCAL_TO_SERVER = new HashMap<Property<?>, String>();
TASK_ATTACHMENT_COLUMN_NAMES_TO_PROPERTIES = new HashMap<String, Property<?>>();
TASK_ATTACHMENT_COLUMNS_LOCAL_TO_SERVER = new HashMap<String, String>();
TASK_ATTACHMENT_PROPERTIES_EXCLUDED = new HashSet<String>();
putTaskAttachmentPropertyToServerName(TaskAttachment.UUID, "uuid", false);
putTaskAttachmentPropertyToServerName(TaskAttachment.USER_UUID, "user_id", false);
putTaskAttachmentPropertyToServerName(TaskAttachment.TASK_UUID, "task_id", true);
putTaskAttachmentPropertyToServerName(TaskAttachment.NAME, "name", false);
putTaskAttachmentPropertyToServerName(TaskAttachment.URL, "url", false);
putTaskAttachmentPropertyToServerName(TaskAttachment.SIZE, "size", false);
putTaskAttachmentPropertyToServerName(TaskAttachment.CONTENT_TYPE, "content_type", false);
putTaskAttachmentPropertyToServerName(TaskAttachment.CREATED_AT, "created_at", true);
putTaskAttachmentPropertyToServerName(TaskAttachment.DELETED_AT, "deleted_at", true);
// Reverse the mapping to construct the server to local map
TASK_ATTACHMENT_PROPERTIES_SERVER_TO_LOCAL = AndroidUtilities.reverseMap(TASK_ATTACHMENT_PROPERTIES_LOCAL_TO_SERVER);
}
// ----------
// Mapping helpers
// ----------
private static <A, B> B mapColumnName(String table, String col, Map<A, B> taskMap, Map<A, B> tagMap, Map<A, B> userMap, Map<A, B> userActivityMap) {
private static <A, B> B mapColumnName(String table, String col, Map<A, B> taskMap, Map<A, B> tagMap, Map<A, B> userMap, Map<A, B> userActivityMap, Map<A, B> taskAttachmentMap) {
Map<A, B> map = null;
if (TABLE_ID_TASKS.equals(table))
map = taskMap;
@ -285,6 +298,8 @@ public class NameMaps {
map = userMap;
else if (TABLE_ID_USER_ACTIVITY.equals(table))
map = userActivityMap;
else if (TABLE_ID_ATTACHMENTS.equals(table))
map = taskAttachmentMap;
if (map == null)
return null;
@ -302,20 +317,23 @@ public class NameMaps {
} else if (TABLE_ID_USER_ACTIVITY.equals(table)) {
if (USER_ACTIVITY_COLUMN_NAMES_TO_PROPERTIES.containsKey(column))
return !USER_ACTIVITY_PROPERTIES_EXCLUDED.contains(column);
} else if (TABLE_ID_ATTACHMENTS.equals(table)) {
if (TASK_ATTACHMENT_COLUMN_NAMES_TO_PROPERTIES.containsKey(column))
return !TASK_ATTACHMENT_PROPERTIES_EXCLUDED.contains(column);
}
return false;
}
public static String localColumnNameToServerColumnName(String table, String localColumn) {
return mapColumnName(table, localColumn, TASK_COLUMNS_LOCAL_TO_SERVER, TAG_DATA_COLUMNS_LOCAL_TO_SERVER, USER_COLUMNS_LOCAL_TO_SERVER, USER_ACTIVITY_COLUMNS_LOCAL_TO_SERVER);
return mapColumnName(table, localColumn, TASK_COLUMNS_LOCAL_TO_SERVER, TAG_DATA_COLUMNS_LOCAL_TO_SERVER, USER_COLUMNS_LOCAL_TO_SERVER, USER_ACTIVITY_COLUMNS_LOCAL_TO_SERVER, TASK_ATTACHMENT_COLUMNS_LOCAL_TO_SERVER);
}
public static Property<?> localColumnNameToProperty(String table, String localColumn) {
return mapColumnName(table, localColumn, TASK_COLUMN_NAMES_TO_PROPERTIES, TAG_DATA_COLUMN_NAMES_TO_PROPERTIES, USER_COLUMN_NAMES_TO_PROPERTIES, USER_ACTIVITY_COLUMN_NAMES_TO_PROPERTIES);
return mapColumnName(table, localColumn, TASK_COLUMN_NAMES_TO_PROPERTIES, TAG_DATA_COLUMN_NAMES_TO_PROPERTIES, USER_COLUMN_NAMES_TO_PROPERTIES, USER_ACTIVITY_COLUMN_NAMES_TO_PROPERTIES, TASK_ATTACHMENT_COLUMN_NAMES_TO_PROPERTIES);
}
public static Property<?> serverColumnNameToLocalProperty(String table, String serverColumn) {
return mapColumnName(table, serverColumn, TASK_PROPERTIES_SERVER_TO_LOCAL, TAG_DATA_PROPERTIES_SERVER_TO_LOCAL, USER_PROPERTIES_SERVER_TO_LOCAL, USER_ACTIVITY_PROPERTIES_SERVER_TO_LOCAL);
return mapColumnName(table, serverColumn, TASK_PROPERTIES_SERVER_TO_LOCAL, TAG_DATA_PROPERTIES_SERVER_TO_LOCAL, USER_PROPERTIES_SERVER_TO_LOCAL, USER_ACTIVITY_PROPERTIES_SERVER_TO_LOCAL, TASK_ATTACHMENT_PROPERTIES_SERVER_TO_LOCAL);
}
}

@ -5,6 +5,7 @@ import org.json.JSONObject;
import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.data.User;
import com.todoroo.astrid.data.UserActivity;
@ -54,6 +55,8 @@ public abstract class ServerToClientMessage {
return new MakeChanges<User>(json, PluginServices.getUserDao());
else if (NameMaps.TABLE_ID_USER_ACTIVITY.equals(table))
return new MakeChanges<UserActivity>(json, PluginServices.getUserActivityDao());
else if (NameMaps.TABLE_ID_ATTACHMENTS.equals(table))
return new MakeChanges<TaskAttachment>(json, PluginServices.getTaskAttachmentDao());
else
return null;
}
@ -68,6 +71,8 @@ public abstract class ServerToClientMessage {
return new NowBriefed<UserActivity>(json, PluginServices.getUserActivityDao());
else if (NameMaps.TABLE_ID_USERS.equals(table))
return new NowBriefed<User>(json, PluginServices.getUserDao());
else if (NameMaps.TABLE_ID_ATTACHMENTS.equals(table))
return new NowBriefed<TaskAttachment>(json, PluginServices.getTaskAttachmentDao());
else
return null;
}

@ -17,6 +17,8 @@ import com.todoroo.astrid.dao.StoreObjectDao;
import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.dao.TagMetadataDao;
import com.todoroo.astrid.dao.TagOutstandingDao;
import com.todoroo.astrid.dao.TaskAttachmentDao;
import com.todoroo.astrid.dao.TaskAttachmentOutstandingDao;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.TaskOutstandingDao;
import com.todoroo.astrid.dao.UserActivityDao;
@ -85,6 +87,12 @@ public final class PluginServices {
@Autowired
HistoryDao historyDao;
@Autowired
TaskAttachmentDao taskAttachmentDao;
@Autowired
TaskAttachmentOutstandingDao taskAttachmentOutstandingDao;
private static volatile PluginServices instance;
static {
@ -172,6 +180,14 @@ public final class PluginServices {
return getInstance().historyDao;
}
public static TaskAttachmentDao getTaskAttachmentDao() {
return getInstance().taskAttachmentDao;
}
public static TaskAttachmentOutstandingDao getTaskAttachmentOutstandingDao() {
return getInstance().taskAttachmentOutstandingDao;
}
// -- helpers
/**

@ -7,44 +7,19 @@ package com.todoroo.astrid.files;
import com.todoroo.andlib.data.Property.LongProperty;
import com.todoroo.andlib.data.Property.StringProperty;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Query;
import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.data.Metadata;
/**
* This class was deprecated with SyncV2. Use TaskAttachment instead.
* @author Sam
*
*/
@Deprecated
public class FileMetadata {
/** metadata key */
public static final String METADATA_KEY = "file"; //$NON-NLS-1$
/** default directory for files on external storage */
public static final String FILES_DIRECTORY_DEFAULT = "attachments"; //$NON-NLS-1$
/** preference key for some other download directory */
public static final String FILES_DIRECTORY_PREF = "custom_files_dir"; //$NON-NLS-1$
/** Constants for file types */
public static final String FILE_TYPE_AUDIO = "audio/"; //$NON-NLS-1$
public static final String FILE_TYPE_IMAGE = "image/"; //$NON-NLS-1$
public static final String FILE_TYPE_PDF = "application/pdf"; //$NON-NLS-1$
public static final String FILE_TYPE_DOC = "application/msword"; //$NON-NLS-1$
public static final String FILE_TYPE_DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; //$NON-NLS-1$
public static final String FILE_TYPE_PPT = "application/vnd.ms-powerpoint"; //$NON-NLS-1$
public static final String FILE_TYPE_PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; //$NON-NLS-1$
public static final String FILE_TYPE_XLS = "application/vnd.ms-excel"; //$NON-NLS-1$
public static final String FILE_TYPE_XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; //$NON-NLS-1$
public static final String[] MS_FILETYPES = {
FILE_TYPE_DOC, FILE_TYPE_DOCX,
FILE_TYPE_XLS, FILE_TYPE_XLSX,
FILE_TYPE_PPT, FILE_TYPE_PPTX,
};
public static final String FILE_TYPE_OTHER = "application/octet-stream"; //$NON-NLS-1$
public static final StringProperty FILE_PATH = new StringProperty(Metadata.TABLE,
Metadata.VALUE1.name);
@ -63,29 +38,4 @@ public class FileMetadata {
public static final StringProperty NAME = new StringProperty(Metadata.TABLE,
Metadata.VALUE6.name);
public static Metadata createNewFileMetadata(long taskId, String filePath, String fileName, String fileType) {
Metadata metadata = new Metadata();
metadata.setValue(Metadata.KEY, METADATA_KEY);
metadata.setValue(Metadata.TASK, taskId);
metadata.setValue(NAME, fileName);
metadata.setValue(FILE_PATH, filePath);
metadata.setValue(FILE_TYPE, fileType);
metadata.setValue(REMOTE_ID, 0L);
metadata.setValue(DELETION_DATE, 0L);
return metadata;
}
public static boolean taskHasAttachments(long taskId) {
TodorooCursor<Metadata> files = PluginServices.getMetadataService()
.query(Query.select(Metadata.TASK).where(
Criterion.and(MetadataCriteria.byTaskAndwithKey(taskId, METADATA_KEY),
DELETION_DATE.eq(0))).limit(1));
try {
return files.getCount() > 0;
} finally {
files.close();
}
}
}

@ -16,6 +16,7 @@ import android.text.TextUtils;
import com.timsu.astrid.R;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.data.TaskAttachment;
public class FileUtilities {
@ -68,12 +69,12 @@ public class FileUtilities {
public static File getAttachmentsDirectory(Context context) {
File directory = null;
String customDir = Preferences.getStringValue(FileMetadata.FILES_DIRECTORY_PREF);
String customDir = Preferences.getStringValue(TaskAttachment.FILES_DIRECTORY_PREF);
if (!TextUtils.isEmpty(customDir))
directory = new File(customDir);
if (directory == null || !directory.exists())
directory = context.getExternalFilesDir(FileMetadata.FILES_DIRECTORY_DEFAULT);
directory = context.getExternalFilesDir(TaskAttachment.FILES_DIRECTORY_DEFAULT);
return directory;
}

@ -44,23 +44,20 @@ import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
import com.todoroo.astrid.actfm.sync.ActFmSyncService;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.dao.TaskAttachmentDao;
import com.todoroo.astrid.data.RemoteModel;
import com.todoroo.astrid.data.SyncFlags;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.ui.PopupControlSet;
import com.todoroo.astrid.utility.Constants;
public class FilesControlSet extends PopupControlSet {
@Autowired
private MetadataService metadataService;
private TaskAttachmentDao taskAttachmentDao;
@Autowired
private ActFmSyncService actFmSyncService;
private final ArrayList<Metadata> files = new ArrayList<Metadata>();
private final ArrayList<TaskAttachment> files = new ArrayList<TaskAttachment>();
private final LinearLayout fileDisplayList;
private LinearLayout fileList;
private final LayoutInflater inflater;
@ -77,7 +74,7 @@ public class FilesControlSet extends PopupControlSet {
@Override
protected void refreshDisplayView() {
fileDisplayList.removeAllViews();
for (final Metadata m : files) {
for (final TaskAttachment m : files) {
View fileRow = inflater.inflate(R.layout.file_display_row, null);
LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.RIGHT;
@ -95,16 +92,16 @@ public class FilesControlSet extends PopupControlSet {
public void refreshMetadata() {
if (model != null) {
TodorooCursor<Metadata> cursor = metadataService.query(
Query.select(Metadata.PROPERTIES)
.where(Criterion.and(MetadataCriteria.byTaskAndwithKey(model.getId(), FileMetadata.METADATA_KEY),
FileMetadata.DELETION_DATE.eq(0))));
TodorooCursor<TaskAttachment> cursor = taskAttachmentDao.query(
Query.select(TaskAttachment.PROPERTIES)
.where(Criterion.and(TaskAttachment.TASK_UUID.eq(model.getUuid()),
TaskAttachment.DELETED_AT.eq(0))));
try {
files.clear();
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
Metadata metadata = new Metadata();
metadata.readFromCursor(cursor);
files.add(metadata);
TaskAttachment attachment = new TaskAttachment();
attachment.readFromCursor(cursor);
files.add(attachment);
}
} finally {
cursor.close();
@ -117,15 +114,15 @@ public class FilesControlSet extends PopupControlSet {
private void validateFiles() {
for (int i = 0; i < files.size(); i++) {
Metadata m = files.get(i);
if (m.containsNonNullValue(FileMetadata.FILE_PATH)) {
File f = new File(m.getValue(FileMetadata.FILE_PATH));
TaskAttachment m = files.get(i);
if (m.containsNonNullValue(TaskAttachment.FILE_PATH)) {
File f = new File(m.getValue(TaskAttachment.FILE_PATH));
if (!f.exists()) {
m.setValue(FileMetadata.FILE_PATH, ""); //$NON-NLS-1$
if (m.containsNonNullValue(FileMetadata.URL)) { // We're ok, just the local file was deleted
metadataService.save(m);
m.setValue(TaskAttachment.FILE_PATH, ""); //$NON-NLS-1$
if (m.containsNonNullValue(TaskAttachment.URL)) { // We're ok, just the local file was deleted
taskAttachmentDao.saveExisting(m);
} else { // No local file and no url -- delete the metadata
metadataService.delete(m);
taskAttachmentDao.delete(m.getId());
files.remove(i);
i--;
}
@ -152,7 +149,7 @@ public class FilesControlSet extends PopupControlSet {
final LinearLayout finalList = fileList;
fileList.removeAllViews();
LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
for (final Metadata m : files) {
for (final TaskAttachment m : files) {
final View fileRow = inflater.inflate(R.layout.file_row, null);
setUpFileRow(m, fileRow, fileList, lp);
@ -170,16 +167,15 @@ public class FilesControlSet extends PopupControlSet {
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface d, int which) {
if (m.getValue(FileMetadata.REMOTE_ID) > 0) {
m.setValue(FileMetadata.DELETION_DATE, DateUtilities.now());
metadataService.save(m);
actFmSyncService.pushAttachmentInBackground(m);
if (RemoteModel.isValidUuid(m.getValue(TaskAttachment.UUID))) {
m.setValue(TaskAttachment.DELETED_AT, DateUtilities.now());
taskAttachmentDao.saveExisting(m);
} else {
metadataService.delete(m);
taskAttachmentDao.delete(m.getId());
}
if (m.containsNonNullValue(FileMetadata.FILE_PATH)) {
File f = new File(m.getValue(FileMetadata.FILE_PATH));
if (m.containsNonNullValue(TaskAttachment.FILE_PATH)) {
File f = new File(m.getValue(TaskAttachment.FILE_PATH));
f.delete();
}
files.remove(m);
@ -193,8 +189,8 @@ public class FilesControlSet extends PopupControlSet {
}
}
private void setupFileClickListener(View view, final Metadata m) {
final String filePath = m.containsNonNullValue(FileMetadata.FILE_PATH) ? m.getValue(FileMetadata.FILE_PATH) : null;
private void setupFileClickListener(View view, final TaskAttachment m) {
final String filePath = m.containsNonNullValue(TaskAttachment.FILE_PATH) ? m.getValue(TaskAttachment.FILE_PATH) : null;
if (TextUtils.isEmpty(filePath)) {
view.setOnClickListener(new OnClickListener() {
@Override
@ -218,18 +214,18 @@ public class FilesControlSet extends PopupControlSet {
}
}
private void showFile(final Metadata m) {
final String fileType = m.containsNonNullValue(FileMetadata.FILE_TYPE) ? m.getValue(FileMetadata.FILE_TYPE) : FileMetadata.FILE_TYPE_OTHER;
final String filePath = m.getValue(FileMetadata.FILE_PATH);
private void showFile(final TaskAttachment m) {
final String fileType = m.containsNonNullValue(TaskAttachment.CONTENT_TYPE) ? m.getValue(TaskAttachment.CONTENT_TYPE) : TaskAttachment.FILE_TYPE_OTHER;
final String filePath = m.getValue(TaskAttachment.FILE_PATH);
if (fileType.startsWith(FileMetadata.FILE_TYPE_AUDIO)) {
RecognizerApi.play(activity, m.getValue(FileMetadata.FILE_PATH), new PlaybackExceptionHandler() {
if (fileType.startsWith(TaskAttachment.FILE_TYPE_AUDIO)) {
RecognizerApi.play(activity, m.getValue(TaskAttachment.FILE_PATH), new PlaybackExceptionHandler() {
@Override
public void playbackFailed(String file) {
showFromIntent(filePath, fileType);
}
});
} else if (fileType.startsWith(FileMetadata.FILE_TYPE_IMAGE)) {
} else if (fileType.startsWith(TaskAttachment.FILE_TYPE_IMAGE)) {
AlertDialog image = new AlertDialog.Builder(activity).create();
ImageView imageView = new ImageView(activity);
imageView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
@ -252,7 +248,7 @@ public class FilesControlSet extends PopupControlSet {
image.show();
} else {
String useType = fileType;
if (fileType.equals(FileMetadata.FILE_TYPE_OTHER)) {
if (fileType.equals(TaskAttachment.FILE_TYPE_OTHER)) {
String extension = AndroidUtilities.getFileExtension(filePath);
MimeTypeMap map = MimeTypeMap.getSingleton();
@ -260,8 +256,9 @@ public class FilesControlSet extends PopupControlSet {
if (!TextUtils.isEmpty(guessedType))
useType = guessedType;
if (useType != guessedType) {
m.setValue(FileMetadata.FILE_TYPE, useType);
metadataService.save(m);
m.setValue(TaskAttachment.CONTENT_TYPE, useType);
m.putTransitory(SyncFlags.ACTFM_SUPPRESS_OUTSTANDING_ENTRIES, true);
taskAttachmentDao.saveExisting(m);
}
}
showFromIntent(filePath, useType);
@ -279,11 +276,11 @@ public class FilesControlSet extends PopupControlSet {
}
private void handleActivityNotFound(String fileType) {
if (fileType.startsWith(FileMetadata.FILE_TYPE_AUDIO)) {
if (fileType.startsWith(TaskAttachment.FILE_TYPE_AUDIO)) {
searchMarket("com.clov4r.android.nil", R.string.search_market_audio_title, R.string.search_market_audio); //$NON-NLS-1$
} else if (fileType.equals(FileMetadata.FILE_TYPE_PDF)) {
} else if (fileType.equals(TaskAttachment.FILE_TYPE_PDF)) {
searchMarket("com.adobe.reader", R.string.search_market_pdf_title, R.string.search_market_pdf); //$NON-NLS-1$
} else if (AndroidUtilities.indexOf(FileMetadata.MS_FILETYPES, fileType) >= 0) {
} else if (AndroidUtilities.indexOf(TaskAttachment.MS_FILETYPES, fileType) >= 0) {
searchMarket("com.dataviz.docstogo", R.string.search_market_ms_title, R.string.search_market_ms); //$NON-NLS-1$
} else {
DialogUtilities.okDialog(activity, activity.getString(R.string.file_type_unhandled_title),
@ -311,7 +308,7 @@ public class FilesControlSet extends PopupControlSet {
}
@SuppressWarnings("nls")
private void downloadFile(final Metadata m) {
private void downloadFile(final TaskAttachment m) {
final ProgressDialog pd = new ProgressDialog(activity);
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
pd.setMessage(activity.getString(R.string.file_download_progress));
@ -320,9 +317,9 @@ public class FilesControlSet extends PopupControlSet {
new Thread() {
@Override
public void run() {
String urlString = m.getValue(FileMetadata.URL);
String urlString = m.getValue(TaskAttachment.URL);
urlString = urlString.replace(" ", "%20");
String name = m.getValue(FileMetadata.NAME);
String name = m.getValue(TaskAttachment.NAME);
StringBuilder filePathBuilder = new StringBuilder();
File directory = FileUtilities.getAttachmentsDirectory(activity);
@ -378,8 +375,8 @@ public class FilesControlSet extends PopupControlSet {
fileOutput.flush();
fileOutput.close();
m.setValue(FileMetadata.FILE_PATH, file.getAbsolutePath());
metadataService.save(m);
m.setValue(TaskAttachment.FILE_PATH, file.getAbsolutePath());
taskAttachmentDao.saveExisting(m);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
@ -404,11 +401,11 @@ public class FilesControlSet extends PopupControlSet {
pd.show();
}
private void setUpFileRow(Metadata m, View row, LinearLayout parent, LayoutParams lp) {
private void setUpFileRow(TaskAttachment m, View row, LinearLayout parent, LayoutParams lp) {
TextView nameView = (TextView) row.findViewById(R.id.file_text);
TextView typeView = (TextView) row.findViewById(R.id.file_type);
String name = getNameString(m);
String type = getTypeString(m.getValue(FileMetadata.NAME));
String type = getTypeString(m.getValue(TaskAttachment.NAME));
nameView.setText(name);
if (TextUtils.isEmpty(type))
@ -419,8 +416,8 @@ public class FilesControlSet extends PopupControlSet {
parent.addView(row, lp);
}
private String getNameString(Metadata metadata) {
String name = metadata.getValue(FileMetadata.NAME);
private String getNameString(TaskAttachment metadata) {
String name = metadata.getValue(TaskAttachment.NAME);
int extension = name.lastIndexOf('.');
if (extension < 0)
return name;

@ -148,7 +148,7 @@ public class EditNoteActivity extends LinearLayout implements TimerActionListene
public void loadViewForTaskID(long t){
try {
task = PluginServices.getTaskService().fetchById(t, Task.NOTES, Task.ID, Task.UUID, Task.TITLE, Task.HISTORY_FETCH_DATE);
task = PluginServices.getTaskService().fetchById(t, Task.NOTES, Task.ID, Task.UUID, Task.TITLE, Task.HISTORY_FETCH_DATE, Task.PUSHED_AT);
} catch (SQLiteException e) {
StartupService.handleSQLiteError(ContextManager.getContext(), e);
}

@ -50,8 +50,8 @@ import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.files.FileExplore;
import com.todoroo.astrid.files.FileMetadata;
import com.todoroo.astrid.gcal.CalendarStartupReceiver;
import com.todoroo.astrid.gtasks.GtasksPreferences;
import com.todoroo.astrid.helper.MetadataHelper;
@ -575,7 +575,7 @@ public class EditPreferences extends TodorooPreferenceActivity {
// pp preferences
else if (r.getString(R.string.p_files_dir).equals(preference.getKey())) {
String dir = Preferences.getStringValue(FileMetadata.FILES_DIRECTORY_PREF);
String dir = Preferences.getStringValue(TaskAttachment.FILES_DIRECTORY_PREF);
if (TextUtils.isEmpty(dir)) {
dir = r.getString(R.string.p_files_dir_desc_default);
@ -665,7 +665,7 @@ public class EditPreferences extends TodorooPreferenceActivity {
} else if (requestCode == REQUEST_CODE_FILES_DIR && resultCode == RESULT_OK) {
if (data != null) {
String dir = data.getStringExtra(FileExplore.RESULT_DIR_SELECTED);
Preferences.setString(FileMetadata.FILES_DIRECTORY_PREF, dir);
Preferences.setString(TaskAttachment.FILES_DIRECTORY_PREF, dir);
}
return;
}

@ -70,12 +70,12 @@ import com.todoroo.astrid.actfm.TaskCommentsFragment;
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
import com.todoroo.astrid.actfm.sync.ActFmSyncService;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.dao.TaskAttachmentDao;
import com.todoroo.astrid.data.RemoteModel;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.files.AACRecordingActivity;
import com.todoroo.astrid.files.FileExplore;
import com.todoroo.astrid.files.FileMetadata;
import com.todoroo.astrid.files.FileUtilities;
import com.todoroo.astrid.files.FilesControlSet;
import com.todoroo.astrid.gcal.GCalControlSet;
@ -212,6 +212,9 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
@Autowired
private MetadataService metadataService;
@Autowired
private TaskAttachmentDao taskAttachmentDao;
@Autowired
private AddOnService addOnService;
@ -870,7 +873,7 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
loadItem(intent);
synchronized (controls) {
if (!FileMetadata.taskHasAttachments(model.getId())) {
if (!taskAttachmentDao.taskHasAttachments(model.getUuid())) {
filesControlSet.getDisplayView().setVisibility(View.GONE);
}
for (TaskEditControlSet controlSet : controls)
@ -1140,7 +1143,7 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
String name = dst.getName();
String extension = AndroidUtilities.getFileExtension(name);
String type = FileMetadata.FILE_TYPE_OTHER;
String type = TaskAttachment.FILE_TYPE_OTHER;
if (!TextUtils.isEmpty(extension)) {
MimeTypeMap map = MimeTypeMap.getSingleton();
String guessedType = map.getMimeTypeFromExtension(extension);
@ -1163,16 +1166,15 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
fos.flush();
fos.close();
createNewFileAttachment(path, nameRef.get(), FileMetadata.FILE_TYPE_IMAGE + "png");
createNewFileAttachment(path, nameRef.get(), TaskAttachment.FILE_TYPE_IMAGE + "png");
} catch (Exception e) {
Toast.makeText(getActivity(), R.string.file_err_copy, Toast.LENGTH_LONG).show();
}
}
private void createNewFileAttachment(String path, String fileName, String fileType) {
Metadata fileMetadata = FileMetadata.createNewFileMetadata(model.getId(), path, fileName, fileType);
metadataService.save(fileMetadata);
actFmSyncService.pushAttachmentInBackground(fileMetadata);
TaskAttachment attachment = TaskAttachment.createNewAttachment(model.getUuid(), path, fileName, fileType);
taskAttachmentDao.createNew(attachment);
filesControlSet.refreshMetadata();
filesControlSet.getDisplayView().setVisibility(View.VISIBLE);
}
@ -1291,7 +1293,7 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
} else if (requestCode == REQUEST_CODE_RECORD && resultCode == Activity.RESULT_OK) {
String recordedAudioPath = data.getStringExtra(AACRecordingActivity.RESULT_OUTFILE);
String recordedAudioName = data.getStringExtra(AACRecordingActivity.RESULT_FILENAME);
createNewFileAttachment(recordedAudioPath, recordedAudioName, FileMetadata.FILE_TYPE_AUDIO + "m4a"); //$NON-NLS-1$
createNewFileAttachment(recordedAudioPath, recordedAudioName, TaskAttachment.FILE_TYPE_AUDIO + "m4a"); //$NON-NLS-1$
} else if (requestCode == REQUEST_CODE_ATTACH_FILE && resultCode == Activity.RESULT_OK) {
attachFile(data.getStringExtra(FileExplore.RESULT_FILE_SELECTED));
} else if (requestCode == REQUEST_CODE_BEAST_MODE) {

@ -87,8 +87,8 @@ import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.data.User;
import com.todoroo.astrid.files.FileMetadata;
import com.todoroo.astrid.helper.SyncActionHelper;
import com.todoroo.astrid.helper.TaskListContextMenuExtensionLoader;
import com.todoroo.astrid.helper.TaskListContextMenuExtensionLoader.ContextMenuItem;
@ -981,9 +981,7 @@ public class TaskListFragment extends ListFragment implements OnScrollListener,
tagsJoinCriterion).toString() //$NON-NLS-1$
+ Join.left(User.TABLE.as(USER_IMAGE_JOIN),
Task.USER_ID.eq(Field.field(USER_IMAGE_JOIN + "." + User.UUID.name))).toString()
+ Join.left(Metadata.TABLE.as(FILE_METADATA_JOIN),
Criterion.and(Field.field(FILE_METADATA_JOIN + "." + Metadata.KEY.name).eq(FileMetadata.METADATA_KEY),
Task.ID.eq(Field.field(FILE_METADATA_JOIN + "." + Metadata.TASK.name))))
+ Join.left(TaskAttachment.TABLE.as(FILE_METADATA_JOIN), Task.UUID.eq(Field.field(FILE_METADATA_JOIN) + "." + TaskAttachment.UUID.name))
+ filter.getSqlQuery();
sqlQueryTemplate.set(SortHelper.adjustQueryForFlagsAndSort(

@ -77,6 +77,7 @@ import com.todoroo.astrid.core.LinkActionExposer;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.RemoteModel;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.data.User;
import com.todoroo.astrid.files.FilesAction;
import com.todoroo.astrid.files.FilesControlSet;
@ -113,7 +114,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
@SuppressWarnings("nls")
private static final StringProperty TAGS = new StringProperty(null, "group_concat(" + TaskListFragment.TAGS_METADATA_JOIN + "." + TaskToTagMetadata.TAG_NAME.name + ", ' | ')").as("tags");
@SuppressWarnings("nls")
private static final LongProperty FILE_ID_PROPERTY = Metadata.ID.cloneAs(TaskListFragment.FILE_METADATA_JOIN, "fileId");
private static final LongProperty FILE_ID_PROPERTY = TaskAttachment.ID.cloneAs(TaskListFragment.FILE_METADATA_JOIN, "fileId");
@SuppressWarnings("nls")
private static final IntegerProperty HAS_NOTES_PROPERTY = new IntegerProperty(null, "length(" + Task.NOTES + ") > 0").as("hasNotes");

@ -22,6 +22,8 @@ import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.TagMetadata;
import com.todoroo.astrid.data.TagOutstanding;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.data.TaskAttachmentOutstanding;
import com.todoroo.astrid.data.TaskOutstanding;
import com.todoroo.astrid.data.Update;
import com.todoroo.astrid.data.User;
@ -68,10 +70,12 @@ public class Database extends AbstractDatabase {
ABTestEvent.TABLE,
TagMetadata.TABLE,
History.TABLE,
TaskAttachment.TABLE,
TaskOutstanding.TABLE,
TagOutstanding.TABLE,
UserActivityOutstanding.TABLE,
TaskAttachmentOutstanding.TABLE
};
// --- listeners
@ -338,10 +342,12 @@ public class Database extends AbstractDatabase {
case 28: try {
database.execSQL(createTableSql(visitor, TaskOutstanding.TABLE.name, TaskOutstanding.PROPERTIES));
database.execSQL(createTableSql(visitor, TagOutstanding.TABLE.name, TagOutstanding.PROPERTIES));
database.execSQL(createTableSql(visitor, TaskAttachmentOutstanding.TABLE.name, TagOutstanding.PROPERTIES));
database.execSQL(createTableSql(visitor, TagMetadata.TABLE.name, TagMetadata.PROPERTIES));
database.execSQL(createTableSql(visitor, UserActivity.TABLE.name, UserActivity.PROPERTIES));
database.execSQL(createTableSql(visitor, UserActivityOutstanding.TABLE.name, UserActivityOutstanding.PROPERTIES));
database.execSQL(createTableSql(visitor, History.TABLE.name, History.PROPERTIES));
database.execSQL(createTableSql(visitor, TaskAttachment.TABLE.name, TaskAttachment.PROPERTIES));
database.execSQL(addColumnSql(Task.TABLE, Task.PUSHED_AT, visitor, null));
database.execSQL(addColumnSql(Task.TABLE, Task.IS_PUBLIC, visitor, "0"));

@ -0,0 +1,107 @@
/**
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.dao;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.ContentValues;
import android.text.TextUtils;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.actfm.sync.messages.NameMaps;
import com.todoroo.astrid.data.OutstandingEntry;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.data.TaskAttachmentOutstanding;
/**
* Data Access layer for {@link TagData}-related operations.
*
* @author Tim Su <tim@todoroo.com>
*
*/
public class TaskAttachmentDao extends RemoteModelDao<TaskAttachment> {
@Autowired Database database;
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UR_UNINIT_READ")
public TaskAttachmentDao() {
super(TaskAttachment.class);
DependencyInjectionService.getInstance().inject(this);
setDatabase(database);
}
@Override
protected boolean shouldRecordOutstandingEntry(String columnName) {
return NameMaps.shouldRecordOutstandingColumnForTable(NameMaps.TABLE_ID_ATTACHMENTS, columnName);
}
@Override
protected int createOutstandingEntries(long modelId, ContentValues modelSetValues) {
// new attachment case -- only set by us when creating new attachments; when setting during sync outstanding entries suppressed
if (modelSetValues.containsKey(TaskAttachment.CONTENT_TYPE.name)) {
TaskAttachmentOutstanding m = new TaskAttachmentOutstanding();
String path = modelSetValues.getAsString(TaskAttachment.FILE_PATH.name);
if (TextUtils.isEmpty(path))
return -1;
try {
JSONObject newFileHash = new JSONObject();
newFileHash.put("name", modelSetValues.getAsString(TaskAttachment.NAME.name)); //$NON-NLS-1$
newFileHash.put("type", modelSetValues.getAsString(TaskAttachment.CONTENT_TYPE.name)); //$NON-NLS-1$
newFileHash.put("path", path); //$NON-NLS-1$
m.setValue(OutstandingEntry.ENTITY_ID_PROPERTY, modelId);
m.setValue(OutstandingEntry.COLUMN_STRING_PROPERTY, NameMaps.ATTACHMENT_ADDED_COLUMN);
m.setValue(OutstandingEntry.VALUE_STRING_PROPERTY, newFileHash.toString());
m.setValue(OutstandingEntry.CREATED_AT_PROPERTY, DateUtilities.now());
database.insert(outstandingTable.name, null, m.getSetValues());
} catch (JSONException e) {
return -1;
}
}
int result = super.createOutstandingEntries(modelId, modelSetValues);
if (result < 0) // Error
return result;
return 1 + result;
}
public boolean taskHasAttachments(String taskUuid) {
TodorooCursor<TaskAttachment> files = query(Query.select(TaskAttachment.TASK_UUID).where(
Criterion.and(TaskAttachment.TASK_UUID.eq(taskUuid),
TaskAttachment.DELETED_AT.eq(0))).limit(1));
try {
return files.getCount() > 0;
} finally {
files.close();
}
}
// --- SQL clause generators
/**
* Generates SQL clauses
*/
public static class TagDataCriteria {
/** @returns tasks by id */
public static Criterion byId(long id) {
return TagData.ID.eq(id);
}
public static Criterion isTeam() {
return TagData.IS_TEAM.eq(1);
}
}
}

@ -0,0 +1,12 @@
package com.todoroo.astrid.dao;
import com.todoroo.astrid.data.TaskAttachmentOutstanding;
public class TaskAttachmentOutstandingDao extends OutstandingEntryDao<TaskAttachmentOutstanding> {
public TaskAttachmentOutstandingDao() {
super(TaskAttachmentOutstanding.class);
}
}

@ -21,6 +21,8 @@ import com.todoroo.astrid.dao.StoreObjectDao;
import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.dao.TagMetadataDao;
import com.todoroo.astrid.dao.TagOutstandingDao;
import com.todoroo.astrid.dao.TaskAttachmentDao;
import com.todoroo.astrid.dao.TaskAttachmentOutstandingDao;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.TaskOutstandingDao;
import com.todoroo.astrid.dao.UpdateDao;
@ -83,6 +85,8 @@ public class AstridDependencyInjector extends AbstractDependencyInjector {
injectables.put("tagOutstandingDao", TagOutstandingDao.class);
injectables.put("userActivityOutstandingDao", UserActivityOutstandingDao.class);
injectables.put("historyDao", HistoryDao.class);
injectables.put("taskAttachmentDao", TaskAttachmentDao.class);
injectables.put("taskAttachmentOutstandingDao", TaskAttachmentOutstandingDao.class);
// com.todoroo.astrid.service
injectables.put("taskService", TaskService.class);

@ -52,6 +52,7 @@ import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.dao.TaskAttachmentDao;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.UserActivityDao;
import com.todoroo.astrid.data.Metadata;
@ -106,6 +107,8 @@ public class StartupService {
@Autowired UserActivityDao userActivityDao;
@Autowired TaskAttachmentDao taskAttachmentDao;
@Autowired MetadataService metadataService;
@Autowired Database database;
@ -221,7 +224,7 @@ public class StartupService {
abTestInvoker.reportAcquisition();
initializeDatabaseListeners();
ActFmSyncThread.initializeSyncComponents(taskDao, tagDataDao, userActivityDao);
ActFmSyncThread.initializeSyncComponents(taskDao, tagDataDao, userActivityDao, taskAttachmentDao);
// perform startup activities in a background thread
new Thread(new Runnable() {

@ -46,12 +46,12 @@ import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.activity.TaskListFragment.OnTaskListItemClickedListener;
import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.dao.TaskAttachmentDao;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.SyncFlags;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.files.FileMetadata;
import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.files.FileUtilities;
import com.todoroo.astrid.gcal.GCalControlSet;
import com.todoroo.astrid.gcal.GCalHelper;
@ -90,6 +90,8 @@ public class QuickAddBar extends LinearLayout {
@Autowired ExceptionService exceptionService;
@Autowired MetadataService metadataService;
@Autowired ActFmPreferenceService actFmPreferenceService;
@Autowired
private TaskAttachmentDao taskAttachmentDao;
private VoiceRecognizer voiceRecognizer;
@ -361,8 +363,8 @@ public class QuickAddBar extends LinearLayout {
voiceRecognizer.convert(path);
currentVoiceFile = null;
Metadata fileMetadata = FileMetadata.createNewFileMetadata(task.getId(), path, nameRef.get(), FileMetadata.FILE_TYPE_AUDIO + "m4a");
metadataService.save(fileMetadata);
TaskAttachment attachment = TaskAttachment.createNewAttachment(task.getUuid(), path, nameRef.get(), TaskAttachment.FILE_TYPE_AUDIO + "m4a");
taskAttachmentDao.createNew(attachment);
}
fragment.onTaskCreated(task);

Loading…
Cancel
Save