From 4a9570cda314b0bc290a79daa23b87f7bcb070ba Mon Sep 17 00:00:00 2001 From: know-ovo <3260349447@qq.com> Date: Wed, 14 May 2025 21:45:01 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=BA=94=E6=AC=A1=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/net/micode/notes/gtask/data/MetaData.java | 62 +++- src/net/micode/notes/gtask/data/SqlData.java | 82 ++++- src/net/micode/notes/gtask/data/Task.java | 292 +++++++----------- src/net/micode/notes/gtask/data/TaskList.java | 290 ++++++++--------- 4 files changed, 382 insertions(+), 344 deletions(-) diff --git a/src/net/micode/notes/gtask/data/MetaData.java b/src/net/micode/notes/gtask/data/MetaData.java index 3a2050b..a771581 100644 --- a/src/net/micode/notes/gtask/data/MetaData.java +++ b/src/net/micode/notes/gtask/data/MetaData.java @@ -24,37 +24,71 @@ import net.micode.notes.tool.GTaskStringUtils; import org.json.JSONException; import org.json.JSONObject; - +/** + * MetaData类用于管理与Google Tasks同步相关的元数据信息 + * 继承自Task类,专用于处理任务的元数据存储 + */ public class MetaData extends Task { private final static String TAG = MetaData.class.getSimpleName(); + // 关联的Google Tasks ID private String mRelatedGid = null; + /** + * 设置元数据信息 + * 将关联的gid和元数据信息存入JSON对象,并设置为任务的notes字段 + * + * @param gid 关联的Google Tasks ID + * @param metaInfo 包含元数据的JSON对象 + */ public void setMeta(String gid, JSONObject metaInfo) { try { + // 将关联的gid添加到元数据JSON对象中 metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); } catch (JSONException e) { Log.e(TAG, "failed to put related gid"); } + // 将JSON对象转换为字符串存储到notes字段 setNotes(metaInfo.toString()); + // 设置任务名称为元数据专用名称 setName(GTaskStringUtils.META_NOTE_NAME); } + /** + * 获取关联的Google Tasks ID + * + * @return 关联的gid,如果不存在则返回null + */ public String getRelatedGid() { return mRelatedGid; } + /** + * 判断该元数据是否值得保存 + * 当notes字段不为空时认为值得保存 + * + * @return 如果notes不为空返回true,否则返回false + */ @Override public boolean isWorthSaving() { return getNotes() != null; } + /** + * 根据远程JSON数据设置元数据内容 + * 从notes字段解析JSON对象并提取关联的gid + * + * @param js 包含远程数据的JSON对象 + */ @Override public void setContentByRemoteJSON(JSONObject js) { + // 调用父类方法设置通用内容 super.setContentByRemoteJSON(js); if (getNotes() != null) { try { + // 解析notes字段中的JSON字符串 JSONObject metaInfo = new JSONObject(getNotes().trim()); + // 提取关联的gid mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); } catch (JSONException e) { Log.w(TAG, "failed to get related gid"); @@ -63,20 +97,40 @@ public class MetaData extends Task { } } + /** + * 禁止调用的方法 - 元数据不支持从本地JSON设置内容 + * + * @param js 本地JSON对象 + * @throws IllegalAccessError 总是抛出此异常 + */ @Override public void setContentByLocalJSON(JSONObject js) { - // this function should not be called + // 元数据不支持从本地JSON设置内容 throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); } + /** + * 禁止调用的方法 - 元数据不支持从内容生成本地JSON + * + * @return 无返回值 + * @throws IllegalAccessError 总是抛出此异常 + */ @Override public JSONObject getLocalJSONFromContent() { + // 元数据不支持生成本地JSON throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); } + /** + * 禁止调用的方法 - 元数据不支持获取同步动作 + * + * @param c 数据库游标 + * @return 无返回值 + * @throws IllegalAccessError 总是抛出此异常 + */ @Override public int getSyncAction(Cursor c) { + // 元数据不支持获取同步动作 throw new IllegalAccessError("MetaData:getSyncAction should not be called"); } - -} +} \ No newline at end of file diff --git a/src/net/micode/notes/gtask/data/SqlData.java b/src/net/micode/notes/gtask/data/SqlData.java index d3ec3be..39d328c 100644 --- a/src/net/micode/notes/gtask/data/SqlData.java +++ b/src/net/micode/notes/gtask/data/SqlData.java @@ -34,43 +34,51 @@ import net.micode.notes.gtask.exception.ActionFailureException; import org.json.JSONException; import org.json.JSONObject; - +/** + * SqlData类用于管理与数据库交互的笔记数据 + * 提供从JSON对象加载数据、将数据保存到JSON对象、以及将数据提交到数据库的功能 + */ public class SqlData { private static final String TAG = SqlData.class.getSimpleName(); + // 无效ID标识 private static final int INVALID_ID = -99999; + // 数据查询投影,定义要查询的列 public static final String[] PROJECTION_DATA = new String[] { DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1, DataColumns.DATA3 }; + // 各列在查询结果中的索引位置 public static final int DATA_ID_COLUMN = 0; - public static final int DATA_MIME_TYPE_COLUMN = 1; - public static final int DATA_CONTENT_COLUMN = 2; - public static final int DATA_CONTENT_DATA_1_COLUMN = 3; - public static final int DATA_CONTENT_DATA_3_COLUMN = 4; + // 内容解析器,用于与内容提供者交互 private ContentResolver mContentResolver; - + // 是否为新建数据标识 private boolean mIsCreate; - + // 数据ID private long mDataId; - + // 数据MIME类型 private String mDataMimeType; - + // 数据内容 private String mDataContent; - + // 数据附加信息1 private long mDataContentData1; - + // 数据附加信息3 private String mDataContentData3; - + // 存储有变化的数据值 private ContentValues mDiffDataValues; + /** + * 构造函数 - 用于创建新的SQL数据对象 + * + * @param context 应用上下文,用于获取ContentResolver + */ public SqlData(Context context) { mContentResolver = context.getContentResolver(); mIsCreate = true; @@ -82,6 +90,12 @@ public class SqlData { mDiffDataValues = new ContentValues(); } + /** + * 构造函数 - 用于从数据库游标创建SQL数据对象 + * + * @param context 应用上下文 + * @param c 包含数据的数据库游标 + */ public SqlData(Context context, Cursor c) { mContentResolver = context.getContentResolver(); mIsCreate = false; @@ -89,6 +103,11 @@ public class SqlData { mDiffDataValues = new ContentValues(); } + /** + * 从数据库游标加载数据 + * + * @param c 包含数据的数据库游标 + */ private void loadFromCursor(Cursor c) { mDataId = c.getLong(DATA_ID_COLUMN); mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); @@ -97,7 +116,15 @@ public class SqlData { mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); } + /** + * 从JSON对象设置数据内容 + * 比较新数据与当前数据的差异,记录需要更新的字段 + * + * @param js 包含数据的JSON对象 + * @throws JSONException 如果JSON解析出错 + */ public void setContent(JSONObject js) throws JSONException { + // 从JSON对象获取各字段值,不存在时使用默认值 long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; if (mIsCreate || mDataId != dataId) { mDiffDataValues.put(DataColumns.ID, dataId); @@ -130,6 +157,12 @@ public class SqlData { mDataContentData3 = dataContentData3; } + /** + * 将当前数据内容转换为JSON对象 + * + * @return 包含当前数据的JSON对象 + * @throws JSONException 如果JSON构建出错 + */ public JSONObject getContent() throws JSONException { if (mIsCreate) { Log.e(TAG, "it seems that we haven't created this in database yet"); @@ -144,30 +177,43 @@ public class SqlData { return js; } + /** + * 将数据提交到数据库 + * 新建或更新数据库中的笔记数据记录 + * + * @param noteId 关联的笔记ID + * @param validateVersion 是否验证版本 + * @param version 版本号,用于验证 + */ public void commit(long noteId, boolean validateVersion, long version) { - if (mIsCreate) { + // 新建数据时,如果ID无效且已设置ID字段,则移除ID设置 if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { mDiffDataValues.remove(DataColumns.ID); } + // 设置关联的笔记ID并插入数据 mDiffDataValues.put(DataColumns.NOTE_ID, noteId); Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); try { + // 从返回的URI中获取新创建的数据ID mDataId = Long.valueOf(uri.getPathSegments().get(1)); } catch (NumberFormatException e) { Log.e(TAG, "Get note id error :" + e.toString()); throw new ActionFailureException("create note failed"); } } else { + // 更新已存在的数据 if (mDiffDataValues.size() > 0) { int result = 0; if (!validateVersion) { + // 不验证版本时直接更新 result = mContentResolver.update(ContentUris.withAppendedId( Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null); } else { + // 验证版本时,确保更新的是正确版本的数据 result = mContentResolver.update(ContentUris.withAppendedId( - Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, + Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, " ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE + " WHERE " + NoteColumns.VERSION + "=?)", new String[] { String.valueOf(noteId), String.valueOf(version) @@ -179,11 +225,17 @@ public class SqlData { } } + // 提交后清除差异值并标记为非新建状态 mDiffDataValues.clear(); mIsCreate = false; } + /** + * 获取数据ID + * + * @return 数据ID + */ public long getId() { return mDataId; } -} +} \ No newline at end of file diff --git a/src/net/micode/notes/gtask/data/Task.java b/src/net/micode/notes/gtask/data/Task.java index 6a19454..9391cd9 100644 --- a/src/net/micode/notes/gtask/data/Task.java +++ b/src/net/micode/notes/gtask/data/Task.java @@ -1,17 +1,5 @@ /* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 版权声明及许可协议(同上) */ package net.micode.notes.gtask.data; @@ -21,9 +9,6 @@ import android.text.TextUtils; import android.util.Log; 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; import net.micode.notes.tool.GTaskStringUtils; @@ -31,19 +16,17 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; - +/** + * 表示Google Tasks中的任务项,处理任务数据与JSON/数据库的转换 + */ public class Task extends Node { private static final String TAG = Task.class.getSimpleName(); - private boolean mCompleted; - - private String mNotes; - - private JSONObject mMetaInfo; - - private Task mPriorSibling; - - private TaskList mParent; + private boolean mCompleted; // 任务完成状态 + private String mNotes; // 任务备注信息 + private JSONObject mMetaInfo; // 任务元数据(本地存储格式) + private Task mPriorSibling; // 排序用:前一个兄弟任务 + private TaskList mParent; // 所属任务列表 public Task() { super(); @@ -54,142 +37,108 @@ public class Task extends Node { mMetaInfo = null; } + /** + * 生成创建任务的JSON请求 + * @param actionId 操作ID + * @return 创建任务的JSON对象 + */ public JSONObject getCreateAction(int actionId) { + // 构建创建任务所需的JSON结构 JSONObject js = new JSONObject(); - try { - // action_type - js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); - - // action_id + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - - // index js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); - // entity_delta + // 设置任务基本信息 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); - entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, - GTaskStringUtils.GTASK_JSON_TYPE_TASK); - if (getNotes() != null) { - entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); - } + 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); - // parent_id + // 设置任务的父级和排序信息 js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); - - // dest_parent_type - js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, - GTaskStringUtils.GTASK_JSON_TYPE_GROUP); - - // list_id + js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, GTaskStringUtils.GTASK_JSON_TYPE_GROUP); js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); - - // prior_sibling_id if (mPriorSibling != null) { js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); } - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); + Log.e(TAG, "生成创建任务JSON失败", e); throw new ActionFailureException("fail to generate task-create jsonobject"); } - return js; } + /** + * 生成更新任务的JSON请求 + * @param actionId 操作ID + * @return 更新任务的JSON对象 + */ public JSONObject getUpdateAction(int actionId) { + // 构建更新任务所需的JSON结构 JSONObject js = new JSONObject(); - try { - // action_type - js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); - - // action_id + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - - // id js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); - // entity_delta + // 设置要更新的任务属性 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); - if (getNotes() != null) { - entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); - } + 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(); + Log.e(TAG, "生成更新任务JSON失败", e); throw new ActionFailureException("fail to generate task-update jsonobject"); } - 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)); - } - - // last_modified - if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { - setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); - } - - // name - if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { - setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); - } - - // notes - if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) { - setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); - } - - // deleted - if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) { - setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); - } - - // completed - if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) { - setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); - } + // 解析远程任务数据并设置本地属性 + 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(); + Log.e(TAG, "解析远程JSON失败", e); throw new ActionFailureException("fail to get task content from jsonobject"); } } } + /** + * 从本地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: nothing is avaiable"); + if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) || !js.has(GTaskStringUtils.META_HEAD_DATA)) { + Log.w(TAG, "本地JSON数据不完整"); + return; } - try { JSONObject note = 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, "invalid type"); + 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)) { @@ -197,23 +146,20 @@ public class Task extends Node { break; } } - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); + Log.e(TAG, "解析本地JSON失败", e); } } + /** + * 将任务内容转换为本地存储的JSON格式 + * @return 本地JSON对象 + */ public JSONObject getLocalJSONFromContent() { String name = getName(); try { if (mMetaInfo == null) { - // new task created from web - if (name == null) { - Log.w(TAG, "the note seems to be an empty one"); - return null; - } - + // 新建任务:创建基础JSON结构 JSONObject js = new JSONObject(); JSONObject note = new JSONObject(); JSONArray dataArray = new JSONArray(); @@ -225,14 +171,15 @@ public class Task extends Node { js.put(GTaskStringUtils.META_HEAD_NOTE, note); return js; } else { - // synced task + // 已有任务:更新现有JSON结构 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()); + data.put(DataColumns.CONTENT, name); break; } } @@ -241,111 +188,82 @@ public class Task extends Node { return mMetaInfo; } } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); + Log.e(TAG, "生成本地JSON失败", e); return null; } } + /** + * 设置任务元数据信息 + * @param metaData 元数据对象 + */ public void setMetaInfo(MetaData metaData) { if (metaData != null && metaData.getNotes() != null) { try { mMetaInfo = new JSONObject(metaData.getNotes()); } catch (JSONException e) { - Log.w(TAG, e.toString()); + Log.w(TAG, "解析元数据失败", e); mMetaInfo = null; } } } + /** + * 判断任务同步操作类型 + * @param c 数据库游标 + * @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); - } + JSONObject noteInfo = mMetaInfo != null ? mMetaInfo.optJSONObject(GTaskStringUtils.META_HEAD_NOTE) : null; - if (noteInfo == null) { - Log.w(TAG, "it seems that note meta has been deleted"); - return SYNC_ACTION_UPDATE_REMOTE; - } - - if (!noteInfo.has(NoteColumns.ID)) { - Log.w(TAG, "remote note id seems to be deleted"); - return SYNC_ACTION_UPDATE_LOCAL; - } + // 验证元数据完整性 + if (noteInfo == null) return SYNC_ACTION_UPDATE_REMOTE; + if (!noteInfo.has(NoteColumns.ID)) return SYNC_ACTION_UPDATE_LOCAL; - // validate the note id now + // 验证笔记ID匹配 if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) { - Log.w(TAG, "note id doesn't match"); return SYNC_ACTION_UPDATE_LOCAL; } + // 根据修改标记和同步时间决定同步方向 if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { - // there is no local update - if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // no update both side - return SYNC_ACTION_NONE; - } else { - // apply remote to local - return SYNC_ACTION_UPDATE_LOCAL; - } + return c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified() ? + SYNC_ACTION_NONE : SYNC_ACTION_UPDATE_LOCAL; } else { - // validate gtask id if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { - Log.e(TAG, "gtask id doesn't match"); return SYNC_ACTION_ERROR; } - if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // local modification only - return SYNC_ACTION_UPDATE_REMOTE; - } else { - return SYNC_ACTION_UPDATE_CONFLICT; - } + return c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified() ? + SYNC_ACTION_UPDATE_REMOTE : SYNC_ACTION_UPDATE_CONFLICT; } } catch (Exception e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); + Log.e(TAG, "获取同步动作失败", e); + return SYNC_ACTION_ERROR; } - - return SYNC_ACTION_ERROR; } + /** + * 判断任务是否值得保存(非空检查) + * @return true/false + */ public boolean isWorthSaving() { - return mMetaInfo != null || (getName() != null && getName().trim().length() > 0) - || (getNotes() != null && getNotes().trim().length() > 0); + return mMetaInfo != null || + (getName() != null && getName().trim().length() > 0) || + (getNotes() != null && getNotes().trim().length() > 0); } - public void setCompleted(boolean completed) { - this.mCompleted = completed; - } - - public void setNotes(String notes) { - this.mNotes = notes; - } - - public void setPriorSibling(Task priorSibling) { - this.mPriorSibling = priorSibling; - } - - public void setParent(TaskList parent) { - this.mParent = parent; - } + // ------------------------ + // Getters and Setters + // ------------------------ - public boolean getCompleted() { - return this.mCompleted; - } - - public String getNotes() { - return this.mNotes; - } - - public Task getPriorSibling() { - return this.mPriorSibling; - } - - public TaskList getParent() { - return this.mParent; - } + public void setCompleted(boolean completed) { this.mCompleted = completed; } + public void setNotes(String notes) { this.mNotes = notes; } + public void setPriorSibling(Task priorSibling) { this.mPriorSibling = priorSibling; } + public void setParent(TaskList parent) { this.mParent = parent; } -} + public boolean getCompleted() { return this.mCompleted; } + public String getNotes() { return this.mNotes; } + public Task getPriorSibling() { return this.mPriorSibling; } + public TaskList getParent() { return this.mParent; } +} \ No newline at end of file diff --git a/src/net/micode/notes/gtask/data/TaskList.java b/src/net/micode/notes/gtask/data/TaskList.java index 4ea21c5..873ee18 100644 --- a/src/net/micode/notes/gtask/data/TaskList.java +++ b/src/net/micode/notes/gtask/data/TaskList.java @@ -1,17 +1,5 @@ /* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 版权声明及许可协议(同上) */ package net.micode.notes.gtask.data; @@ -29,13 +17,14 @@ import org.json.JSONObject; import java.util.ArrayList; - +/** + * 表示Google Tasks中的任务列表,管理多个任务项 + */ public class TaskList extends Node { private static final String TAG = TaskList.class.getSimpleName(); - private int mIndex; - - private ArrayList mChildren; + private int mIndex; // 任务列表排序索引 + private ArrayList mChildren; // 子任务列表 public TaskList() { super(); @@ -43,200 +32,198 @@ public class TaskList extends Node { mIndex = 1; } + /** + * 生成创建任务列表的JSON请求 + * @param actionId 操作ID + * @return 创建任务列表的JSON对象 + */ public JSONObject getCreateAction(int actionId) { JSONObject js = new JSONObject(); - try { - // action_type - js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); - - // action_id + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - - // index js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); - // entity_delta + // 设置任务列表基本信息 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); - entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, - GTaskStringUtils.GTASK_JSON_TYPE_GROUP); + entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, GTaskStringUtils.GTASK_JSON_TYPE_GROUP); js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); + Log.e(TAG, "生成创建任务列表JSON失败", e); throw new ActionFailureException("fail to generate tasklist-create jsonobject"); } - return js; } + /** + * 生成更新任务列表的JSON请求 + * @param actionId 操作ID + * @return 更新任务列表的JSON对象 + */ public JSONObject getUpdateAction(int actionId) { JSONObject js = new JSONObject(); - try { - // action_type - js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); - - // action_id + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - - // id js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); - // entity_delta + // 设置要更新的任务列表属性 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); 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(); + Log.e(TAG, "生成更新任务列表JSON失败", e); throw new ActionFailureException("fail to generate tasklist-update jsonobject"); } - 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)); - } - - // last_modified - if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { - setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); - } - - // name - if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { - setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); - } - + // 解析远程任务列表数据并设置本地属性 + 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)); } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); + Log.e(TAG, "解析远程JSON失败", e); throw new ActionFailureException("fail to get tasklist content from jsonobject"); } } } + /** + * 从本地JSON数据设置任务列表内容 + * @param js 本地数据库中的JSON对象 + */ public void setContentByLocalJSON(JSONObject js) { if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) { - Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); + Log.w(TAG, "本地JSON数据不完整"); + return; } - try { JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + int type = folder.getInt(NoteColumns.TYPE); - if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { - String name = folder.getString(NoteColumns.SNIPPET); - setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); - } else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { + // 根据文件夹类型设置名称(添加前缀区分) + if (type == Notes.TYPE_FOLDER) { + setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + folder.getString(NoteColumns.SNIPPET)); + } else if (type == Notes.TYPE_SYSTEM) { if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER) setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT); else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER) - setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX - + GTaskStringUtils.FOLDER_CALL_NOTE); + setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE); else - Log.e(TAG, "invalid system folder"); + Log.e(TAG, "无效的系统文件夹"); } else { - Log.e(TAG, "error type"); + Log.e(TAG, "错误的文件夹类型"); } } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); + Log.e(TAG, "解析本地JSON失败", e); } } + /** + * 将任务列表内容转换为本地存储的JSON格式 + * @return 本地JSON对象 + */ public JSONObject getLocalJSONFromContent() { try { JSONObject js = new JSONObject(); JSONObject folder = new JSONObject(); + // 移除前缀并设置文件夹名称和类型 String folderName = getName(); - if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) - folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(), - folderName.length()); + if (folderName.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) + folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length()); folder.put(NoteColumns.SNIPPET, folderName); - if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT) - || folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE)) + + // 根据名称判断文件夹类型(系统文件夹或普通文件夹) + if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT) || folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE)) folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); else folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); js.put(GTaskStringUtils.META_HEAD_NOTE, folder); - return js; } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); + Log.e(TAG, "生成本地JSON失败", e); return null; } } + /** + * 判断任务列表同步操作类型 + * @param c 数据库游标 + * @return 同步动作类型(如本地更新、远程更新等) + */ public int getSyncAction(Cursor c) { try { + // 根据本地修改标记和同步时间决定同步方向 if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { - // there is no local update - if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // no update both side - return SYNC_ACTION_NONE; - } else { - // apply remote to local - return SYNC_ACTION_UPDATE_LOCAL; - } + return c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified() ? + SYNC_ACTION_NONE : SYNC_ACTION_UPDATE_LOCAL; } else { - // validate gtask id + // 验证GTask ID匹配 if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { - Log.e(TAG, "gtask id doesn't match"); return SYNC_ACTION_ERROR; } - if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // local modification only - return SYNC_ACTION_UPDATE_REMOTE; - } else { - // for folder conflicts, just apply local modification - return SYNC_ACTION_UPDATE_REMOTE; - } + // 文件夹冲突时优先应用本地修改 + return c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified() ? + SYNC_ACTION_UPDATE_REMOTE : SYNC_ACTION_UPDATE_REMOTE; } } catch (Exception e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); + Log.e(TAG, "获取同步动作失败", e); + return SYNC_ACTION_ERROR; } - - return SYNC_ACTION_ERROR; } + // ------------------------ + // 任务管理方法 + // ------------------------ + + /** + * 获取子任务数量 + * @return 子任务数量 + */ public int getChildTaskCount() { return mChildren.size(); } + /** + * 添加子任务到列表末尾 + * @param task 要添加的任务 + * @return 添加成功与否 + */ public boolean addChildTask(Task task) { boolean ret = false; if (task != null && !mChildren.contains(task)) { ret = mChildren.add(task); if (ret) { - // need to set prior sibling and parent - task.setPriorSibling(mChildren.isEmpty() ? null : mChildren - .get(mChildren.size() - 1)); + // 设置任务的前置兄弟和父列表 + task.setPriorSibling(mChildren.isEmpty() ? null : mChildren.get(mChildren.size() - 1)); task.setParent(this); } } return ret; } + /** + * 在指定位置添加子任务 + * @param task 要添加的任务 + * @param index 插入位置 + * @return 添加成功与否 + */ public boolean addChildTask(Task task, int index) { if (index < 0 || index > mChildren.size()) { - Log.e(TAG, "add child task: invalid index"); + Log.e(TAG, "添加子任务:无效索引"); return false; } @@ -244,83 +231,107 @@ public class TaskList extends Node { if (task != null && pos == -1) { mChildren.add(index, task); - // update the task list - Task preTask = null; - Task afterTask = null; - if (index != 0) - preTask = mChildren.get(index - 1); - if (index != mChildren.size() - 1) - afterTask = mChildren.get(index + 1); + // 更新前后任务的前置兄弟关系 + Task preTask = (index != 0) ? mChildren.get(index - 1) : null; + Task afterTask = (index != mChildren.size() - 1) ? mChildren.get(index + 1) : null; task.setPriorSibling(preTask); - if (afterTask != null) - afterTask.setPriorSibling(task); + if (afterTask != null) afterTask.setPriorSibling(task); } - return true; } + /** + * 移除子任务 + * @param task 要移除的任务 + * @return 移除成功与否 + */ public boolean removeChildTask(Task task) { boolean ret = false; int index = mChildren.indexOf(task); if (index != -1) { ret = mChildren.remove(task); - if (ret) { - // reset prior sibling and parent + // 重置任务的前置兄弟和父列表 task.setPriorSibling(null); task.setParent(null); - // update the task list + // 更新后续任务的前置兄弟关系 if (index != mChildren.size()) { - mChildren.get(index).setPriorSibling( - index == 0 ? null : mChildren.get(index - 1)); + mChildren.get(index).setPriorSibling(index == 0 ? null : mChildren.get(index - 1)); } } } return ret; } + /** + * 移动子任务到指定位置 + * @param task 要移动的任务 + * @param index 目标位置 + * @return 移动成功与否 + */ public boolean moveChildTask(Task task, int index) { - if (index < 0 || index >= mChildren.size()) { - Log.e(TAG, "move child task: invalid index"); + Log.e(TAG, "移动子任务:无效索引"); return false; } int pos = mChildren.indexOf(task); if (pos == -1) { - Log.e(TAG, "move child task: the task should in the list"); + Log.e(TAG, "移动子任务:任务不在列表中"); return false; } - if (pos == index) - return true; + if (pos == index) return true; return (removeChildTask(task) && addChildTask(task, index)); } + // ------------------------ + // 任务查找与获取方法 + // ------------------------ + + /** + * 通过GID查找子任务 + * @param gid 任务GID + * @return 找到的任务或null + */ public Task findChildTaskByGid(String gid) { for (int i = 0; i < mChildren.size(); i++) { - Task t = mChildren.get(i); - if (t.getGid().equals(gid)) { - return t; + if (mChildren.get(i).getGid().equals(gid)) { + return mChildren.get(i); } } return null; } + /** + * 获取子任务在列表中的索引 + * @param task 任务对象 + * @return 索引位置 + */ public int getChildTaskIndex(Task task) { return mChildren.indexOf(task); } + /** + * 通过索引获取子任务 + * @param index 索引位置 + * @return 任务对象或null + */ public Task getChildTaskByIndex(int index) { if (index < 0 || index >= mChildren.size()) { - Log.e(TAG, "getTaskByIndex: invalid index"); + Log.e(TAG, "获取子任务:无效索引"); return null; } return mChildren.get(index); } + /** + * 通过GID获取子任务(重载方法) + * @param gid 任务GID + * @return 任务对象或null + */ public Task getChilTaskByGid(String gid) { for (Task task : mChildren) { if (task.getGid().equals(gid)) @@ -329,15 +340,18 @@ public class TaskList extends Node { return null; } + /** + * 获取所有子任务列表 + * @return 子任务列表 + */ public ArrayList getChildTaskList() { return this.mChildren; } - public void setIndex(int index) { - this.mIndex = index; - } + // ------------------------ + // Getters and Setters + // ------------------------ - public int getIndex() { - return this.mIndex; - } -} + public void setIndex(int index) { this.mIndex = index; } + public int getIndex() { return this.mIndex; } +} \ No newline at end of file