/* * 版权所有 (c) 2010-2011,MiCode 开源社区 (www.micode.net) * 根据 Apache 许可证 2.0 版本("许可证")授权; * 除非符合许可证的规定,否则不得使用本文件。 * 您可以从以下网址获取许可证副本: * http://www.apache.org/licenses/LICENSE-2.0 * 除非适用法律要求或书面同意,本软件按"原样"分发, * 没有任何明示或暗示的保证或条件。 * 详见许可证中规定的权限和限制。 * (注:这是一份标准的Apache许可证2.0版本的开源声明) */ // 定义当前类所在的包路径 package net.micode.notes.gtask.data; // Android数据库相关类 import android.database.Cursor; // 数据库查询结果集 // Android文本处理工具 import android.text.TextUtils; // 字符串处理工具类 // Android日志工具 import android.util.Log; // 日志记录工具 // 笔记应用数据相关常量 import androidx.annotation.NonNull; import net.micode.notes.data.Notes; // 笔记核心数据类 import net.micode.notes.data.Notes.DataColumns; // 数据表列名常量 import net.micode.notes.data.Notes.DataConstants; // 数据相关常量 import net.micode.notes.data.Notes.NoteColumns; // 笔记表列名常量 // 异常处理类 import net.micode.notes.gtask.exception.ActionFailureException; // 同步操作失败异常 // Google Task工具类 import net.micode.notes.tool.GTaskStringUtils; // Google Task字符串工具 // JSON处理相关类 import org.json.JSONArray; // JSON数组处理 import org.json.JSONException; // JSON异常处理 import org.json.JSONObject; // JSON对象处理 // Java工具类 import java.util.Objects; // 对象工具类 // Task类继承自Node基类,表示Google Task中的一个任务项 public class Task extends Node { // 日志标签 private static final String TAG = Task.class.getSimpleName(); // 任务状态字段 private boolean mCompleted; // 任务是否已完成 private String mNotes; // 任务备注/描述信息 private JSONObject mMetaInfo; // 存储元信息的JSON对象 // 任务关系字段 private Task mPriorSibling; // 前一个兄弟任务(用于排序) private TaskList mParent; // 所属任务列表 // 默认构造函数 public Task() { super(); // 调用父类构造函数 mCompleted = false; // 默认未完成 mNotes = null; // 默认无备注 mPriorSibling = null; // 默认无前驱任务 mParent = null; // 默认无父列表 mMetaInfo = null; // 默认无元信息 } /** * 生成创建任务的JSON动作 * @param actionId 动作ID * @return 包含创建任务数据的JSON对象 */ public JSONObject getCreateAction(int actionId) { JSONObject js = new JSONObject(); try { // 设置动作类型为创建 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); // 设置动作ID js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); // 设置任务索引位置 js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); // 构建任务实体数据 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 任务名称 entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); // 创建者ID entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, GTaskStringUtils.GTASK_JSON_TYPE_TASK); // 实体类型设为任务 // 如果有备注则添加 if (getNotes() != null) { entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); } js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 添加实体数据 // 设置父任务列表ID js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); // 设置目标父类型为组 js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, GTaskStringUtils.GTASK_JSON_TYPE_GROUP); // 设置列表ID js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); // 如果有前驱任务则设置其ID if (mPriorSibling != null) { js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); } } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); throw new ActionFailureException("生成任务创建JSON对象失败"); } return js; } /** * 生成更新任务的JSON动作 * @param actionId 动作ID * @return 包含更新数据的JSON对象 */ public JSONObject getUpdateAction(int actionId) { JSONObject js = new JSONObject(); try { // 设置动作类型为更新 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); // 设置动作ID js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); // 设置任务全局ID js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); // 构建更新数据 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 更新名称 // 如果有备注则更新 if (getNotes() != null) { entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); } // 设置删除状态 entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 添加更新数据 } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); throw new ActionFailureException("生成任务更新JSON对象失败"); } return js; } /** * 从远程JSON数据设置任务内容 * @param js 包含远程数据的JSON对象 */ public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { // 设置任务ID if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); } // 设置最后修改时间 if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); } // 设置任务名称 if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); } // 设置任务备注 if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) { setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); } // 设置删除状态 if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) { setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); } // 设置完成状态 if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) { setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); } } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); throw new ActionFailureException("从JSON获取任务内容失败"); } } } /** * 从本地JSON数据设置任务内容 * @param js 包含本地数据的JSON对象 */ public void setContentByLocalJSON(JSONObject js) { // 检查数据有效性 if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) || !js.has(GTaskStringUtils.META_HEAD_DATA)) { Log.w(TAG, "setContentByLocalJSON: 无可用数据"); } try { JSONObject note = Objects.requireNonNull(js).getJSONObject(GTaskStringUtils.META_HEAD_NOTE); JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 验证类型是否为普通笔记 if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) { Log.e(TAG, "无效的类型"); return; } // 从数据数组中查找笔记内容 for (int i = 0; i < dataArray.length(); i++) { JSONObject data = dataArray.getJSONObject(i); if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { setName(data.getString(DataColumns.CONTENT)); // 设置任务名称 break; } } } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); } } /** * 从任务内容生成本地JSON格式 * @return 包含任务数据的JSON对象 */ public JSONObject getLocalJSONFromContent() { String name = getName(); try { if (mMetaInfo == null) { // 从网页端创建的新任务 if (name == null) { Log.w(TAG, "笔记内容为空"); return null; } // 构建新的JSON结构 return getJsonObject(name); } else { // 已同步的任务 JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 更新笔记内容 for (int i = 0; i < dataArray.length(); i++) { JSONObject data = dataArray.getJSONObject(i); if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { data.put(DataColumns.CONTENT, getName()); break; } } note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // 确保类型正确 return mMetaInfo; // 返回修改后的元信息 } } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); return null; } } @NonNull private static JSONObject getJsonObject(String name) throws JSONException { JSONObject js = new JSONObject(); JSONObject note = new JSONObject(); JSONArray dataArray = new JSONArray(); JSONObject data = new JSONObject(); data.put(DataColumns.CONTENT, name); // 设置内容 dataArray.put(data); js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); // 添加数据数组 note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // 设置类型 js.put(GTaskStringUtils.META_HEAD_NOTE, note); // 添加笔记头 return js; } /** * 设置任务元信息 * @param metaData 元数据对象 */ public void setMetaInfo(MetaData metaData) { if (metaData != null && metaData.getNotes() != null) { try { // 将元数据中的notes字符串转换为JSON对象 mMetaInfo = new JSONObject(metaData.getNotes()); } catch (JSONException e) { Log.w(TAG, e.toString()); mMetaInfo = null; } } } /** * 获取同步动作类型 * @param c 数据库Cursor * @return 同步动作常量 */ public int getSyncAction(Cursor c) { try { JSONObject noteInfo = null; // 检查元信息中是否有笔记头 if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) { noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); } if (noteInfo == null) { Log.w(TAG, "笔记元信息似乎已被删除"); return SYNC_ACTION_UPDATE_REMOTE; // 需要从远程更新 } if (!noteInfo.has(NoteColumns.ID)) { Log.w(TAG, "远程笔记ID似乎已被删除"); return SYNC_ACTION_UPDATE_LOCAL; // 需要更新本地 } // 验证笔记ID是否匹配 if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) { Log.w(TAG, "笔记ID不匹配"); return SYNC_ACTION_UPDATE_LOCAL; } if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { // 本地无修改 if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { // 双方都无更新 return SYNC_ACTION_NONE; } else { // 应用远程修改到本地 return SYNC_ACTION_UPDATE_LOCAL; } } else { // 验证任务ID是否匹配 if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { Log.e(TAG, "任务ID不匹配"); return SYNC_ACTION_ERROR; } if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { // 仅本地有修改 return SYNC_ACTION_UPDATE_REMOTE; } else { // 存在冲突 return SYNC_ACTION_UPDATE_CONFLICT; } } } catch (Exception e) { Log.e(TAG, e.toString()); e.printStackTrace(); } return SYNC_ACTION_ERROR; // 默认返回错误 } /* * 检查任务是否值得保存 * @return 如果有有效数据返回true */ /** * 检查当前任务对象是否包含有效数据,值得保存 * 满足以下任一条件即认为值得保存: * 1. 存在元信息数据 或 * 2. 任务名称非空且不为纯空格 或 * 3. 任务备注非空且不为纯空格 * * @return 如果包含有效数据返回true,否则false */ public boolean isWorthSaving() { return mMetaInfo != null || // 存在元信息 (getName() != null && !getName().trim().isEmpty()) || // 有有效名称 (getNotes() != null && !getNotes().trim().isEmpty()); // 有有效备注 } /** * 设置任务完成状态 * @param completed true表示任务已完成,false表示未完成 */ public void setCompleted(boolean completed) { this.mCompleted = completed; // 更新完成状态字段 } /** * 设置任务备注内容 * @param notes 要设置的备注文本,可为null表示清空备注 */ public void setNotes(String notes) { this.mNotes = notes; // 更新备注字段 } /** * 设置前驱任务(用于任务排序) * @param priorSibling 前一个任务节点,可为null表示没有前驱 */ public void setPriorSibling(Task priorSibling) { this.mPriorSibling = priorSibling; // 更新前驱任务引用 } /** * 设置所属任务列表 * @param parent 父任务列表对象,不可为null */ public void setParent(TaskList parent) { this.mParent = parent; // 更新父列表引用 } /** * 获取任务完成状态 * @return true表示任务已完成,false表示未完成 */ public boolean getCompleted() { return this.mCompleted; // 返回当前完成状态 } /** * 获取任务备注内容 * @return 备注文本,可能为null表示无备注 */ public String getNotes() { return this.mNotes; // 返回当前备注内容 } /** * 获取前驱任务(用于任务排序) * @return 前一个任务节点,可能为null表示没有前驱 */ public Task getPriorSibling() { return this.mPriorSibling; // 返回前驱任务引用 } /** * 获取所属任务列表 * @return 父任务列表对象 */ public TaskList getParent() { return this.mParent; // 返回父列表引用 } }