From 44afe2c8c18b5ec4ce1def0a3bbab082b5c19bf4 Mon Sep 17 00:00:00 2001 From: Wang-YYu-Lu <1756892659@qq.com> Date: Tue, 27 May 2025 09:26:27 +0800 Subject: [PATCH] =?UTF-8?q?gtask=E4=BB=A3=E7=A0=81=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/micode/notes/gtask/data/MetaData.java | 55 +++- .../src/net/micode/notes/gtask/data/Node.java | 92 ++++++- .../net/micode/notes/gtask/data/SqlData.java | 129 ++++++--- .../net/micode/notes/gtask/data/SqlNote.java | 257 +++++++++++++----- .../src/net/micode/notes/gtask/data/Task.java | 151 ++++++---- .../net/micode/notes/gtask/data/TaskList.java | 187 ++++++++++--- .../exception/ActionFailureException.java | 31 ++- .../exception/NetworkFailureException.java | 32 ++- .../notes/gtask/remote/GTaskASyncTask.java | 154 +++++++---- .../notes/gtask/remote/GTaskClient.java | 242 ++++++++++++++--- .../notes/gtask/remote/GTaskManager.java | 257 +++++++++++++----- .../notes/gtask/remote/GTaskSyncService.java | 167 ++++++++---- 12 files changed, 1311 insertions(+), 443 deletions(-) diff --git a/src/Notes-master/src/net/micode/notes/gtask/data/MetaData.java b/src/Notes-master/src/net/micode/notes/gtask/data/MetaData.java index 3a2050b..f78d05b 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/data/MetaData.java +++ b/src/Notes-master/src/net/micode/notes/gtask/data/MetaData.java @@ -14,69 +14,116 @@ * limitations under the License. */ +// 元数据类,用于处理与Google Task相关的元数据信息 package net.micode.notes.gtask.data; +// 导入所需的Android和Java类 import android.database.Cursor; import android.util.Log; +// 导入工具类 import net.micode.notes.tool.GTaskStringUtils; +// 导入JSON处理类 import org.json.JSONException; import org.json.JSONObject; - +// 元数据类,继承自Task基类 public class MetaData extends Task { + // 日志标签,使用类名作为标签 private final static String TAG = MetaData.class.getSimpleName(); + // 关联的Google Task ID private String mRelatedGid = null; + /** + * 设置元数据信息 + * @param gid 关联的Google Task ID + * @param metaInfo 包含元数据的JSON对象 + */ public void setMeta(String gid, JSONObject metaInfo) { try { + // 将Google Task ID添加到元数据中 metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); } catch (JSONException e) { + // 记录添加关联ID失败的错误 Log.e(TAG, "failed to put related gid"); } + // 将JSON对象转换为字符串并设置为备注 setNotes(metaInfo.toString()); + // 设置元数据的名称为固定值 setName(GTaskStringUtils.META_NOTE_NAME); } + /** + * 获取关联的Google Task ID + * @return 关联的Google Task ID + */ public String getRelatedGid() { return mRelatedGid; } + /** + * 检查是否需要保存 + * @return 如果备注不为null则返回true,表示需要保存 + */ @Override public boolean isWorthSaving() { return getNotes() != null; } + /** + * 从远程JSON数据设置内容 + * @param js 包含远程数据的JSON对象 + */ @Override public void setContentByRemoteJSON(JSONObject js) { + // 调用父类方法设置基本内容 super.setContentByRemoteJSON(js); + // 如果备注不为空,尝试解析关联的Google Task ID if (getNotes() != null) { try { + // 从备注字符串创建JSON对象 JSONObject metaInfo = new JSONObject(getNotes().trim()); + // 获取关联的Google Task ID mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); } catch (JSONException e) { + // 记录获取关联ID失败的警告 Log.w(TAG, "failed to get related gid"); + // 将关联ID设为null mRelatedGid = null; } } } + /** + * 从本地JSON数据设置内容(不应被调用) + * @param js 包含本地数据的JSON对象 + */ @Override public void setContentByLocalJSON(JSONObject js) { - // this function should not be called + // 抛出错误,表示该方法不应被调用 throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); } + /** + * 从内容获取本地JSON数据(不应被调用) + * @return 不应被调用,总是抛出异常 + */ @Override public JSONObject getLocalJSONFromContent() { + // 抛出错误,表示该方法不应被调用 throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); } + /** + * 获取同步动作(不应被调用) + * @param c 数据库游标 + * @return 不应被调用,总是抛出异常 + */ @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/Notes-master/src/net/micode/notes/gtask/data/Node.java b/src/Notes-master/src/net/micode/notes/gtask/data/Node.java index 63950e0..b1b27fd 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/data/Node.java +++ b/src/Notes-master/src/net/micode/notes/gtask/data/Node.java @@ -20,82 +20,162 @@ import android.database.Cursor; import org.json.JSONObject; +/** + * 表示Google Tasks同步系统中的一个节点的抽象基类 + * 定义了节点的基本属性和同步操作 + */ public abstract class Node { + // 无同步操作需要执行 public static final int SYNC_ACTION_NONE = 0; + // 将本地节点添加到远程服务器 public static final int SYNC_ACTION_ADD_REMOTE = 1; + // 将远程节点添加到本地数据库 public static final int SYNC_ACTION_ADD_LOCAL = 2; + // 删除远程服务器上的节点 public static final int SYNC_ACTION_DEL_REMOTE = 3; + // 删除本地数据库中的节点 public static final int SYNC_ACTION_DEL_LOCAL = 4; + // 将本地更改更新到远程服务器 public static final int SYNC_ACTION_UPDATE_REMOTE = 5; + // 将远程更改更新到本地数据库 public static final int SYNC_ACTION_UPDATE_LOCAL = 6; + // 本地和远程都有更新,发生冲突 public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; + // 同步过程中发生错误 public static final int SYNC_ACTION_ERROR = 8; + // Google Tasks服务中的唯一标识符 private String mGid; + // 节点名称/标题 private String mName; + // 最后修改时间戳 private long mLastModified; + // 标记节点是否已删除 private boolean mDeleted; + /** + * 默认构造函数,初始化节点属性 + */ public Node() { - mGid = null; - mName = ""; - mLastModified = 0; - mDeleted = false; + mGid = null; // 初始化为空,等待从服务器获取或本地生成 + mName = ""; // 默认空名称 + mLastModified = 0; // 默认时间戳为0 + mDeleted = false; // 默认未删除状态 } + /** + * 创建用于添加节点的JSON同步操作 + * @param actionId 操作ID + * @return 包含创建操作的JSON对象 + */ public abstract JSONObject getCreateAction(int actionId); + /** + * 创建用于更新节点的JSON同步操作 + * @param actionId 操作ID + * @return 包含更新操作的JSON对象 + */ public abstract JSONObject getUpdateAction(int actionId); + /** + * 根据远程JSON数据设置节点内容 + * @param js 包含远程数据的JSON对象 + */ public abstract void setContentByRemoteJSON(JSONObject js); + /** + * 根据本地JSON数据设置节点内容 + * @param js 包含本地数据的JSON对象 + */ public abstract void setContentByLocalJSON(JSONObject js); + /** + * 从节点内容生成用于本地存储的JSON对象 + * @return 包含节点内容的JSON对象 + */ public abstract JSONObject getLocalJSONFromContent(); + /** + * 根据本地数据库光标确定需要执行的同步操作类型 + * @param c 数据库查询返回的光标 + * @return 同步操作类型常量 + */ public abstract int getSyncAction(Cursor c); + // 以下是节点属性的访问器和修改器方法 + + /** + * 设置节点的Google Tasks标识符 + * @param gid Google Tasks服务中的唯一ID + */ public void setGid(String gid) { this.mGid = gid; } + /** + * 设置节点名称 + * @param name 节点名称 + */ public void setName(String name) { this.mName = name; } + /** + * 设置节点最后修改时间 + * @param lastModified 时间戳(毫秒) + */ public void setLastModified(long lastModified) { this.mLastModified = lastModified; } + /** + * 设置节点删除状态 + * @param deleted true表示已删除,false表示未删除 + */ public void setDeleted(boolean deleted) { this.mDeleted = deleted; } + /** + * 获取节点的Google Tasks标识符 + * @return Google Tasks ID + */ public String getGid() { return this.mGid; } + /** + * 获取节点名称 + * @return 节点名称 + */ public String getName() { return this.mName; } + /** + * 获取节点最后修改时间 + * @return 时间戳(毫秒) + */ public long getLastModified() { return this.mLastModified; } + /** + * 获取节点删除状态 + * @return true表示已删除,false表示未删除 + */ public boolean getDeleted() { return this.mDeleted; } - -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/gtask/data/SqlData.java b/src/Notes-master/src/net/micode/notes/gtask/data/SqlData.java index d3ec3be..8e3c7c6 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/data/SqlData.java +++ b/src/Notes-master/src/net/micode/notes/gtask/data/SqlData.java @@ -34,61 +34,72 @@ import net.micode.notes.gtask.exception.ActionFailureException; import org.json.JSONException; import org.json.JSONObject; - +/** + * 处理与本地SQLite数据库中笔记数据的交互 + * 提供从数据库加载数据、更新数据和提交更改的功能 + */ public class SqlData { private static final String TAG = SqlData.class.getSimpleName(); + // 无效ID值,用于标识尚未从数据库获取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 + DataColumns.ID, // 数据ID + DataColumns.MIME_TYPE, // 数据MIME类型 + DataColumns.CONTENT, // 数据内容 + DataColumns.DATA1, // 额外数据字段1 + DataColumns.DATA3 // 额外数据字段3 }; + // 投影列的索引位置 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; - - private long mDataId; - - private String mDataMimeType; - - private String mDataContent; - - private long mDataContentData1; - - private String mDataContentData3; - - private ContentValues mDiffDataValues; - + private ContentResolver mContentResolver; // 内容解析器,用于与内容提供者交互 + private boolean mIsCreate; // 标记数据是否为新创建 + private long mDataId; // 数据ID + private String mDataMimeType; // 数据MIME类型 + private String mDataContent; // 数据内容 + private long mDataContentData1; // 额外数据字段1 + private String mDataContentData3; // 额外数据字段3 + private ContentValues mDiffDataValues; // 存储与原始数据不同的值,用于更新 + + /** + * 构造函数,用于创建新的SQL数据对象 + * @param context 应用上下文 + */ public SqlData(Context context) { mContentResolver = context.getContentResolver(); - mIsCreate = true; - mDataId = INVALID_ID; - mDataMimeType = DataConstants.NOTE; - mDataContent = ""; - mDataContentData1 = 0; - mDataContentData3 = ""; - mDiffDataValues = new ContentValues(); + mIsCreate = true; // 标记为新创建的数据 + mDataId = INVALID_ID; // 初始化为无效ID + mDataMimeType = DataConstants.NOTE; // 默认MIME类型为笔记 + mDataContent = ""; // 空内容 + mDataContentData1 = 0; // 初始化数据字段1 + mDataContentData3 = ""; // 初始化数据字段3 + mDiffDataValues = new ContentValues(); // 初始化差异值容器 } + /** + * 构造函数,用于从数据库游标加载现有数据 + * @param context 应用上下文 + * @param c 包含数据的数据库游标 + */ public SqlData(Context context, Cursor c) { mContentResolver = context.getContentResolver(); - mIsCreate = false; - loadFromCursor(c); - mDiffDataValues = new ContentValues(); + mIsCreate = false; // 标记为现有数据 + loadFromCursor(c); // 从游标加载数据 + mDiffDataValues = new ContentValues(); // 初始化差异值容器 } + /** + * 从数据库游标加载数据 + * @param c 包含数据的游标 + */ private void loadFromCursor(Cursor c) { mDataId = c.getLong(DATA_ID_COLUMN); mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); @@ -97,13 +108,20 @@ 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中获取ID并设置 long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; if (mIsCreate || mDataId != dataId) { mDiffDataValues.put(DataColumns.ID, dataId); } mDataId = dataId; + // 从JSON中获取MIME类型并设置 String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE) : DataConstants.NOTE; if (mIsCreate || !mDataMimeType.equals(dataMimeType)) { @@ -111,18 +129,21 @@ public class SqlData { } mDataMimeType = dataMimeType; + // 从JSON中获取内容并设置 String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : ""; if (mIsCreate || !mDataContent.equals(dataContent)) { mDiffDataValues.put(DataColumns.CONTENT, dataContent); } mDataContent = dataContent; + // 从JSON中获取数据字段1并设置 long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0; if (mIsCreate || mDataContentData1 != dataContentData1) { mDiffDataValues.put(DataColumns.DATA1, dataContentData1); } mDataContentData1 = dataContentData1; + // 从JSON中获取数据字段3并设置 String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : ""; if (mIsCreate || !mDataContentData3.equals(dataContentData3)) { mDiffDataValues.put(DataColumns.DATA3, dataContentData3); @@ -130,9 +151,14 @@ 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"); + Log.e(TAG, "数据尚未提交到数据库,无法获取内容"); return null; } JSONObject js = new JSONObject(); @@ -144,46 +170,59 @@ public class SqlData { return js; } + /** + * 将数据更改提交到数据库 + * @param noteId 关联的笔记ID + * @param validateVersion 是否验证版本 + * @param version 版本号 + */ public void commit(long noteId, boolean validateVersion, long version) { - if (mIsCreate) { + // 创建新数据 if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { - mDiffDataValues.remove(DataColumns.ID); + mDiffDataValues.remove(DataColumns.ID); // 移除无效ID } - mDiffDataValues.put(DataColumns.NOTE_ID, noteId); + mDiffDataValues.put(DataColumns.NOTE_ID, noteId); // 设置关联笔记ID Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); try { - mDataId = Long.valueOf(uri.getPathSegments().get(1)); + mDataId = Long.valueOf(uri.getPathSegments().get(1)); // 获取新生成的ID } catch (NumberFormatException e) { - Log.e(TAG, "Get note id error :" + e.toString()); - throw new ActionFailureException("create note failed"); + Log.e(TAG, "获取笔记ID错误: " + e.toString()); + throw new ActionFailureException("创建笔记失败"); } } 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) }); } if (result == 0) { - Log.w(TAG, "there is no update. maybe user updates note when syncing"); + Log.w(TAG, "没有数据更新,可能用户在同步时修改了笔记"); } } } - mDiffDataValues.clear(); - mIsCreate = false; + mDiffDataValues.clear(); // 清空差异值 + mIsCreate = false; // 标记为已创建 } + /** + * 获取数据ID + * @return 数据ID + */ public long getId() { return mDataId; } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/gtask/data/SqlNote.java b/src/Notes-master/src/net/micode/notes/gtask/data/SqlNote.java index 79a4095..ffc529b 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/data/SqlNote.java +++ b/src/Notes-master/src/net/micode/notes/gtask/data/SqlNote.java @@ -14,8 +14,10 @@ * limitations under the License. */ +// 数据库笔记实体类,负责处理便签数据的本地存储和同步操作 package net.micode.notes.gtask.data; +// Android基础组件导入 import android.appwidget.AppWidgetManager; import android.content.ContentResolver; import android.content.ContentValues; @@ -24,6 +26,7 @@ import android.database.Cursor; import android.net.Uri; import android.util.Log; +// 项目内部类导入 import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.NoteColumns; @@ -31,18 +34,25 @@ import net.micode.notes.gtask.exception.ActionFailureException; import net.micode.notes.tool.GTaskStringUtils; import net.micode.notes.tool.ResourceParser; +// JSON处理类导入 import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +// Java工具类导入 import java.util.ArrayList; - +/** + * SQLite数据库笔记操作类,封装便签的CRUD操作和Google Task同步功能 + */ public class SqlNote { + // 日志标签 private static final String TAG = SqlNote.class.getSimpleName(); + // 无效ID常量 private static final int INVALID_ID = -99999; + // 笔记表查询字段投影 public static final String[] PROJECTION_NOTE = new String[] { NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID, NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE, @@ -52,80 +62,92 @@ public class SqlNote { NoteColumns.VERSION }; + // 字段列索引常量定义 public static final int ID_COLUMN = 0; - public static final int ALERTED_DATE_COLUMN = 1; - public static final int BG_COLOR_ID_COLUMN = 2; - public static final int CREATED_DATE_COLUMN = 3; - public static final int HAS_ATTACHMENT_COLUMN = 4; - public static final int MODIFIED_DATE_COLUMN = 5; - public static final int NOTES_COUNT_COLUMN = 6; - public static final int PARENT_ID_COLUMN = 7; - public static final int SNIPPET_COLUMN = 8; - public static final int TYPE_COLUMN = 9; - public static final int WIDGET_ID_COLUMN = 10; - public static final int WIDGET_TYPE_COLUMN = 11; - public static final int SYNC_ID_COLUMN = 12; - public static final int LOCAL_MODIFIED_COLUMN = 13; - public static final int ORIGIN_PARENT_ID_COLUMN = 14; - public static final int GTASK_ID_COLUMN = 15; - public static final int VERSION_COLUMN = 16; + // 上下文对象 private Context mContext; + // 内容解析器 private ContentResolver mContentResolver; + // 是否为新创建标志 private boolean mIsCreate; + // 笔记ID private long mId; + // 提醒日期 private long mAlertDate; + // 背景颜色ID private int mBgColorId; + // 创建日期 private long mCreatedDate; + // 是否有附件标志 private int mHasAttachment; + // 修改日期 private long mModifiedDate; + // 父节点ID private long mParentId; + // 内容摘要 private String mSnippet; + // 笔记类型 private int mType; + // 小部件ID private int mWidgetId; + // 小部件类型 private int mWidgetType; + // 原始父节点ID private long mOriginParent; + // 数据版本号 private long mVersion; + // 差异内容值对象 private ContentValues mDiffNoteValues; + // 关联数据列表 private ArrayList mDataList; + /** + * 构造函数(新建笔记) + * @param context 上下文对象 + */ public SqlNote(Context context) { + // 初始化上下文和内容解析器 mContext = context; mContentResolver = context.getContentResolver(); + + // 设置新建标志 mIsCreate = true; + + // 初始化默认值 mId = INVALID_ID; mAlertDate = 0; mBgColorId = ResourceParser.getDefaultBgId(context); @@ -139,40 +161,65 @@ public class SqlNote { mWidgetType = Notes.TYPE_WIDGET_INVALIDE; mOriginParent = 0; mVersion = 0; + + // 初始化差异值和数据列表 mDiffNoteValues = new ContentValues(); mDataList = new ArrayList(); } + /** + * 构造函数(从游标加载) + * @param context 上下文对象 + * @param c 数据库游标 + */ public SqlNote(Context context, Cursor c) { mContext = context; mContentResolver = context.getContentResolver(); mIsCreate = false; + + // 从游标加载数据 loadFromCursor(c); mDataList = new ArrayList(); + + // 如果是笔记类型,加载关联数据 if (mType == Notes.TYPE_NOTE) loadDataContent(); + mDiffNoteValues = new ContentValues(); } + /** + * 构造函数(从ID加载) + * @param context 上下文对象 + * @param id 笔记ID + */ public SqlNote(Context context, long id) { mContext = context; mContentResolver = context.getContentResolver(); mIsCreate = false; + + // 从ID加载数据 loadFromCursor(id); mDataList = new ArrayList(); + + // 如果是笔记类型,加载关联数据 if (mType == Notes.TYPE_NOTE) loadDataContent(); - mDiffNoteValues = new ContentValues(); + mDiffNoteValues = new ContentValues(); } + /** + * 从ID加载笔记数据 + * @param id 笔记ID + */ private void loadFromCursor(long id) { Cursor c = null; try { + // 查询指定ID的笔记 c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)", - new String[] { - String.valueOf(id) - }, null); + new String[] { String.valueOf(id) }, null); + if (c != null) { c.moveToNext(); loadFromCursor(c); @@ -185,7 +232,12 @@ public class SqlNote { } } + /** + * 从游标加载笔记数据 + * @param c 数据库游标 + */ private void loadFromCursor(Cursor c) { + // 从游标读取各字段值 mId = c.getLong(ID_COLUMN); mAlertDate = c.getLong(ALERTED_DATE_COLUMN); mBgColorId = c.getInt(BG_COLOR_ID_COLUMN); @@ -200,19 +252,24 @@ public class SqlNote { mVersion = c.getLong(VERSION_COLUMN); } + /** + * 加载笔记关联数据内容 + */ private void loadDataContent() { Cursor c = null; mDataList.clear(); try { + // 查询关联数据 c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA, - "(note_id=?)", new String[] { - String.valueOf(mId) - }, null); + "(note_id=?)", new String[] { String.valueOf(mId) }, null); + if (c != null) { if (c.getCount() == 0) { Log.w(TAG, "it seems that the note has not data"); return; } + + // 遍历游标创建数据对象 while (c.moveToNext()) { SqlData data = new SqlData(mContext, c); mDataList.add(data); @@ -226,85 +283,93 @@ public class SqlNote { } } + /** + * 从JSON对象设置笔记内容 + * @param js JSON对象 + * @return 是否设置成功 + */ public boolean setContent(JSONObject js) { try { + // 获取笔记节点 JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + + // 处理系统文件夹 if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { Log.w(TAG, "cannot set system folder"); - } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { - // for folder we can only update the snnipet and type - String snippet = note.has(NoteColumns.SNIPPET) ? note - .getString(NoteColumns.SNIPPET) : ""; + } + // 处理普通文件夹 + else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { + // 更新摘要和类型 + String snippet = note.has(NoteColumns.SNIPPET) ? note.getString(NoteColumns.SNIPPET) : ""; if (mIsCreate || !mSnippet.equals(snippet)) { mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); } mSnippet = snippet; - int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) - : Notes.TYPE_NOTE; + int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) : Notes.TYPE_NOTE; if (mIsCreate || mType != type) { mDiffNoteValues.put(NoteColumns.TYPE, type); } mType = type; - } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) { + } + // 处理普通笔记 + else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) { + // 获取数据数组 JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + + // 更新各字段值 long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID; if (mIsCreate || mId != id) { mDiffNoteValues.put(NoteColumns.ID, id); } mId = id; - long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note - .getLong(NoteColumns.ALERTED_DATE) : 0; + long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note.getLong(NoteColumns.ALERTED_DATE) : 0; if (mIsCreate || mAlertDate != alertDate) { mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate); } mAlertDate = alertDate; - int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note - .getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext); + int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note.getInt(NoteColumns.BG_COLOR_ID) + : ResourceParser.getDefaultBgId(mContext); if (mIsCreate || mBgColorId != bgColorId) { mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId); } mBgColorId = bgColorId; - long createDate = note.has(NoteColumns.CREATED_DATE) ? note - .getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis(); + long createDate = note.has(NoteColumns.CREATED_DATE) ? note.getLong(NoteColumns.CREATED_DATE) + : System.currentTimeMillis(); if (mIsCreate || mCreatedDate != createDate) { mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate); } mCreatedDate = createDate; - int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ? note - .getInt(NoteColumns.HAS_ATTACHMENT) : 0; + int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ? note.getInt(NoteColumns.HAS_ATTACHMENT) : 0; if (mIsCreate || mHasAttachment != hasAttachment) { mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment); } mHasAttachment = hasAttachment; - long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ? note - .getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis(); + long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ? note.getLong(NoteColumns.MODIFIED_DATE) + : System.currentTimeMillis(); if (mIsCreate || mModifiedDate != modifiedDate) { mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate); } mModifiedDate = modifiedDate; - long parentId = note.has(NoteColumns.PARENT_ID) ? note - .getLong(NoteColumns.PARENT_ID) : 0; + long parentId = note.has(NoteColumns.PARENT_ID) ? note.getLong(NoteColumns.PARENT_ID) : 0; if (mIsCreate || mParentId != parentId) { mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId); } mParentId = parentId; - String snippet = note.has(NoteColumns.SNIPPET) ? note - .getString(NoteColumns.SNIPPET) : ""; + String snippet = note.has(NoteColumns.SNIPPET) ? note.getString(NoteColumns.SNIPPET) : ""; if (mIsCreate || !mSnippet.equals(snippet)) { mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); } mSnippet = snippet; - int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) - : Notes.TYPE_NOTE; + int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) : Notes.TYPE_NOTE; if (mIsCreate || mType != type) { mDiffNoteValues.put(NoteColumns.TYPE, type); } @@ -317,23 +382,25 @@ public class SqlNote { } mWidgetId = widgetId; - int widgetType = note.has(NoteColumns.WIDGET_TYPE) ? note - .getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE; + int widgetType = note.has(NoteColumns.WIDGET_TYPE) ? note.getInt(NoteColumns.WIDGET_TYPE) + : Notes.TYPE_WIDGET_INVALIDE; if (mIsCreate || mWidgetType != widgetType) { mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType); } mWidgetType = widgetType; - long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ? note - .getLong(NoteColumns.ORIGIN_PARENT_ID) : 0; + long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ? note.getLong(NoteColumns.ORIGIN_PARENT_ID) : 0; if (mIsCreate || mOriginParent != originParent) { mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent); } mOriginParent = originParent; + // 处理关联数据 for (int i = 0; i < dataArray.length(); i++) { JSONObject data = dataArray.getJSONObject(i); SqlData sqlData = null; + + // 查找已有数据 if (data.has(DataColumns.ID)) { long dataId = data.getLong(DataColumns.ID); for (SqlData temp : mDataList) { @@ -343,11 +410,13 @@ public class SqlNote { } } + // 创建新数据对象 if (sqlData == null) { sqlData = new SqlData(mContext); mDataList.add(sqlData); } + // 设置数据内容 sqlData.setContent(data); } } @@ -359,17 +428,25 @@ public class SqlNote { return true; } + /** + * 获取笔记内容的JSON表示 + * @return JSON对象 + */ public JSONObject getContent() { try { JSONObject js = new JSONObject(); + // 检查是否已创建 if (mIsCreate) { Log.e(TAG, "it seems that we haven't created this in database yet"); return null; } JSONObject note = new JSONObject(); + + // 处理普通笔记 if (mType == Notes.TYPE_NOTE) { + // 添加笔记字段 note.put(NoteColumns.ID, mId); note.put(NoteColumns.ALERTED_DATE, mAlertDate); note.put(NoteColumns.BG_COLOR_ID, mBgColorId); @@ -384,6 +461,7 @@ public class SqlNote { note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent); js.put(GTaskStringUtils.META_HEAD_NOTE, note); + // 添加关联数据 JSONArray dataArray = new JSONArray(); for (SqlData sqlData : mDataList) { JSONObject data = sqlData.getContent(); @@ -392,7 +470,9 @@ public class SqlNote { } } js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); - } else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) { + } + // 处理文件夹和系统文件夹 + else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) { note.put(NoteColumns.ID, mId); note.put(NoteColumns.TYPE, mType); note.put(NoteColumns.SNIPPET, mSnippet); @@ -407,45 +487,83 @@ public class SqlNote { return null; } + /** + * 设置父节点ID + * @param id 父节点ID + */ public void setParentId(long id) { mParentId = id; mDiffNoteValues.put(NoteColumns.PARENT_ID, id); } + /** + * 设置Google Task ID + * @param gid Google Task ID + */ public void setGtaskId(String gid) { mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); } + /** + * 设置同步ID + * @param syncId 同步ID + */ public void setSyncId(long syncId) { mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); } + /** + * 重置本地修改标志 + */ public void resetLocalModified() { mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); } + /** + * 获取笔记ID + * @return 笔记ID + */ public long getId() { return mId; } + /** + * 获取父节点ID + * @return 父节点ID + */ public long getParentId() { return mParentId; } + /** + * 获取内容摘要 + * @return 内容摘要 + */ public String getSnippet() { return mSnippet; } + /** + * 检查是否为笔记类型 + * @return 是否为笔记类型 + */ public boolean isNoteType() { return mType == Notes.TYPE_NOTE; } + /** + * 提交更改到数据库 + * @param validateVersion 是否验证版本号 + */ public void commit(boolean validateVersion) { + // 处理新建笔记 if (mIsCreate) { + // 处理无效ID if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) { mDiffNoteValues.remove(NoteColumns.ID); } + // 插入新笔记 Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); try { mId = Long.valueOf(uri.getPathSegments().get(1)); @@ -453,40 +571,50 @@ public class SqlNote { Log.e(TAG, "Get note id error :" + e.toString()); throw new ActionFailureException("create note failed"); } + + // 检查ID是否有效 if (mId == 0) { throw new IllegalStateException("Create thread id failed"); } + // 提交关联数据 if (mType == Notes.TYPE_NOTE) { for (SqlData sqlData : mDataList) { sqlData.commit(mId, false, -1); } } - } else { + } + // 处理已有笔记更新 + else { + // 检查ID有效性 if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) { Log.e(TAG, "No such note"); throw new IllegalStateException("Try to update note with invalid id"); } + + // 如果有差异值需要更新 if (mDiffNoteValues.size() > 0) { mVersion ++; int result = 0; + + // 根据是否验证版本选择更新方式 if (!validateVersion) { - result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" - + NoteColumns.ID + "=?)", new String[] { - String.valueOf(mId) - }); + result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, + "(" + NoteColumns.ID + "=?)", + new String[] { String.valueOf(mId) }); } else { - result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" - + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", - new String[] { - String.valueOf(mId), String.valueOf(mVersion) - }); + result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, + "(" + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", + new String[] { String.valueOf(mId), String.valueOf(mVersion) }); } + + // 检查更新结果 if (result == 0) { Log.w(TAG, "there is no update. maybe user updates note when syncing"); } } + // 提交关联数据 if (mType == Notes.TYPE_NOTE) { for (SqlData sqlData : mDataList) { sqlData.commit(mId, validateVersion, mVersion); @@ -494,12 +622,13 @@ public class SqlNote { } } - // refresh local info + // 刷新本地数据 loadFromCursor(mId); if (mType == Notes.TYPE_NOTE) loadDataContent(); + // 重置差异值和创建标志 mDiffNoteValues.clear(); mIsCreate = false; } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/gtask/data/Task.java b/src/Notes-master/src/net/micode/notes/gtask/data/Task.java index 6a19454..cb35d4d 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/data/Task.java +++ b/src/Notes-master/src/net/micode/notes/gtask/data/Task.java @@ -2,16 +2,13 @@ * 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 + * 您可以在遵守 License 的前提下使用本文件。 + * 您可以从以下地址获取 License 副本: * * 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. + * 除非适用法律要求或书面同意,本软件根据 License 分发时“按现状”提供, + * 不附带任何明示或暗示的保证或条件。请参阅 License 了解具体的权限和限制。 */ package net.micode.notes.gtask.data; @@ -31,44 +28,48 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; - +/** + * 表示Google Tasks中的任务实体 + * 负责与远程任务数据和本地笔记数据的转换和同步 + */ 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(); - mCompleted = false; - mNotes = null; - mPriorSibling = null; - mParent = null; - mMetaInfo = null; + 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 { - // action_type + // 设置操作类型为创建任务 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); - // action_id + // 设置操作ID 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"); @@ -79,17 +80,15 @@ public class Task extends Node { } js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); - // parent_id + // 设置父任务列表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 + // 设置所属列表ID(与父ID相同) js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); - // prior_sibling_id + // 设置前一个兄弟任务ID(用于排序) if (mPriorSibling != null) { js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); } @@ -103,21 +102,26 @@ public class Task extends Node { 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 + // 设置操作ID js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // id + // 设置要更新的任务ID js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); - // entity_delta + // 设置要更新的任务实体信息 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); if (getNotes() != null) { @@ -135,35 +139,39 @@ public class Task extends Node { return js; } + /** + * 从远程JSON数据设置任务内容 + * @param js 包含远程任务数据的JSON对象 + */ public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { - // id + // 从JSON中提取任务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)); } @@ -175,6 +183,10 @@ public class Task extends Node { } } + /** + * 从本地JSON数据设置任务内容 + * @param js 包含本地笔记数据的JSON对象 + */ public void setContentByLocalJSON(JSONObject js) { if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) || !js.has(GTaskStringUtils.META_HEAD_DATA)) { @@ -182,14 +194,17 @@ public class Task extends Node { } 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"); return; } + // 从数据数组中提取任务名称 for (int i = 0; i < dataArray.length(); i++) { JSONObject data = dataArray.getJSONObject(i); if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { @@ -204,16 +219,21 @@ public class Task extends Node { } } + /** + * 根据任务内容生成本地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,10 +245,11 @@ public class Task extends Node { js.put(GTaskStringUtils.META_HEAD_NOTE, note); return js; } else { - // synced task + // 处理已同步过的任务(有元信息) 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)) { @@ -247,6 +268,10 @@ public class Task extends Node { } } + /** + * 设置任务的元信息(从本地存储的元数据中解析) + * @param metaData 包含元信息的对象 + */ public void setMetaInfo(MetaData metaData) { if (metaData != null && metaData.getNotes() != null) { try { @@ -258,48 +283,57 @@ public class Task extends Node { } } + /** + * 根据本地数据库记录和任务状态确定同步动作类型 + * @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); } if (noteInfo == null) { Log.w(TAG, "it seems that note meta has been deleted"); - return SYNC_ACTION_UPDATE_REMOTE; + 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; + return SYNC_ACTION_UPDATE_LOCAL; // 缺少笔记ID,需要更新本地 } - // 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; + return SYNC_ACTION_UPDATE_LOCAL; // ID不匹配,需要更新本地 } + // 判断本地是否有修改 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; } } 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; + return SYNC_ACTION_ERROR; // GTask ID不匹配,同步错误 } if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // local modification only + // 只有本地修改,更新远程 return SYNC_ACTION_UPDATE_REMOTE; } else { + // 本地和远程都有修改,发生冲突 return SYNC_ACTION_UPDATE_CONFLICT; } } @@ -308,14 +342,21 @@ public class Task extends Node { e.printStackTrace(); } - 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); } + // 以下为属性的setter和getter方法 + public void setCompleted(boolean completed) { this.mCompleted = completed; } @@ -348,4 +389,4 @@ public class Task extends Node { return this.mParent; } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/gtask/data/TaskList.java b/src/Notes-master/src/net/micode/notes/gtask/data/TaskList.java index 4ea21c5..d204618 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/data/TaskList.java +++ b/src/Notes-master/src/net/micode/notes/gtask/data/TaskList.java @@ -2,16 +2,13 @@ * 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 + * 您可以在遵守 License 的前提下使用本文件。 + * 您可以从以下地址获取 License 副本: * * 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. + * 除非适用法律要求或书面同意,本软件根据 License 分发时“按现状”提供, + * 不附带任何明示或暗示的保证或条件。请参阅 License 了解具体的权限和限制。 */ package net.micode.notes.gtask.data; @@ -29,35 +26,42 @@ 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(); - mChildren = new ArrayList(); - mIndex = 1; + mChildren = new ArrayList(); // 初始化子任务列表 + mIndex = 1; // 默认索引为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 + // 设置操作ID 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"); @@ -74,21 +78,26 @@ public class TaskList extends Node { 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 + // 设置操作ID js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // id + // 设置要更新的任务列表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()); @@ -103,20 +112,24 @@ public class TaskList extends Node { return js; } + /** + * 从远程JSON数据设置任务列表内容 + * @param js 包含远程任务列表数据的JSON对象 + */ public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { - // id + // 从JSON中提取任务列表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)); } @@ -129,23 +142,30 @@ public class TaskList extends Node { } } + /** + * 从本地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"); } try { + // 提取笔记头部信息 JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + // 根据笔记类型设置任务列表名称 if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { + // 普通文件夹 String name = folder.getString(NoteColumns.SNIPPET); - setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); + setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); // 添加前缀标识为MIUI文件夹 } else if (folder.getInt(NoteColumns.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"); } else { @@ -157,16 +177,24 @@ public class TaskList extends Node { } } + /** + * 根据任务列表内容生成本地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()); + 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)) folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); @@ -183,28 +211,35 @@ public class TaskList extends Node { } } + /** + * 根据本地数据库记录和任务列表状态确定同步动作类型 + * @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; } } 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; + return SYNC_ACTION_ERROR; // GTask ID不匹配,同步错误 } 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; } } @@ -213,27 +248,43 @@ public class TaskList extends Node { e.printStackTrace(); } - return SYNC_ACTION_ERROR; + return SYNC_ACTION_ERROR; // 发生异常,返回错误 } + // 以下为任务列表管理方法 + + /** + * 获取子任务数量 + * @return 子任务数量 + */ public int getChildTaskCount() { return mChildren.size(); } + /** + * 添加子任务到任务列表末尾 + * @param task 要添加的任务 + * @return 添加成功返回true,否则返回false + */ 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 添加成功返回true,否则返回false + */ public boolean addChildTask(Task task, int index) { if (index < 0 || index > mChildren.size()) { Log.e(TAG, "add child task: invalid index"); @@ -244,7 +295,7 @@ 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) @@ -260,6 +311,11 @@ public class TaskList extends Node { return true; } + /** + * 从任务列表中移除子任务 + * @param task 要移除的任务 + * @return 移除成功返回true,否则返回false + */ public boolean removeChildTask(Task task) { boolean ret = false; int index = mChildren.indexOf(task); @@ -267,11 +323,11 @@ public class TaskList extends Node { 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)); @@ -281,8 +337,13 @@ public class TaskList extends Node { return ret; } + /** + * 移动子任务到指定位置 + * @param task 要移动的任务 + * @param index 目标位置索引 + * @return 移动成功返回true,否则返回false + */ public boolean moveChildTask(Task task, int index) { - if (index < 0 || index >= mChildren.size()) { Log.e(TAG, "move child task: invalid index"); return false; @@ -296,9 +357,16 @@ public class TaskList extends Node { if (pos == index) return true; + + // 通过先移除再添加的方式实现移动 return (removeChildTask(task) && addChildTask(task, index)); } + /** + * 根据GTask ID查找子任务 + * @param gid GTask ID + * @return 找到的任务,未找到返回null + */ public Task findChildTaskByGid(String gid) { for (int i = 0; i < mChildren.size(); i++) { Task t = mChildren.get(i); @@ -309,10 +377,20 @@ public class TaskList extends Node { return null; } + /** + * 获取子任务在列表中的索引位置 + * @param task 任务对象 + * @return 索引位置,未找到返回-1 + */ 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"); @@ -321,6 +399,11 @@ public class TaskList extends Node { return mChildren.get(index); } + /** + * 根据GTask ID获取子任务(与findChildTaskByGid功能相同) + * @param gid GTask ID + * @return 任务对象,未找到返回null + */ public Task getChilTaskByGid(String gid) { for (Task task : mChildren) { if (task.getGid().equals(gid)) @@ -329,15 +412,29 @@ public class TaskList extends Node { return null; } + /** + * 获取所有子任务列表 + * @return 子任务列表 + */ public ArrayList getChildTaskList() { return this.mChildren; } + // 以下为属性的setter和getter方法 + + /** + * 设置任务列表的排序索引 + * @param index 排序索引 + */ public void setIndex(int index) { this.mIndex = index; } + /** + * 获取任务列表的排序索引 + * @return 排序索引 + */ public int getIndex() { return this.mIndex; } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/gtask/exception/ActionFailureException.java b/src/Notes-master/src/net/micode/notes/gtask/exception/ActionFailureException.java index 15504be..66a94cf 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/exception/ActionFailureException.java +++ b/src/Notes-master/src/net/micode/notes/gtask/exception/ActionFailureException.java @@ -2,32 +2,45 @@ * 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 + * 您可以在遵守 License 的前提下使用本文件。 + * 您可以从以下地址获取 License 副本: * * 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. + * 除非适用法律要求或书面同意,本软件根据 License 分发时“按现状”提供, + * 不附带任何明示或暗示的保证或条件。请参阅 License 了解具体的权限和限制。 */ package net.micode.notes.gtask.exception; +/** + * 操作失败异常类 + * 用于表示任务同步、数据操作等过程中发生的不可恢复错误 + */ public class ActionFailureException extends RuntimeException { - private static final long serialVersionUID = 4425249765923293627L; + private static final long serialVersionUID = 4425249765923293627L; // 序列化版本号 + /** + * 无参构造函数 + */ public ActionFailureException() { super(); } + /** + * 带错误消息的构造函数 + * @param paramString 错误消息字符串 + */ public ActionFailureException(String paramString) { super(paramString); } + /** + * 带错误消息和原因的构造函数 + * @param paramString 错误消息字符串 + * @param paramThrowable 导致异常的根本原因 + */ public ActionFailureException(String paramString, Throwable paramThrowable) { super(paramString, paramThrowable); } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/gtask/exception/NetworkFailureException.java b/src/Notes-master/src/net/micode/notes/gtask/exception/NetworkFailureException.java index b08cfb1..9d7cd2c 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/exception/NetworkFailureException.java +++ b/src/Notes-master/src/net/micode/notes/gtask/exception/NetworkFailureException.java @@ -2,32 +2,46 @@ * 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 + * 您可以在遵守 License 的前提下使用本文件。 + * 您可以从以下地址获取 License 副本: * * 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. + * 除非适用法律要求或书面同意,本软件根据 License 分发时“按现状”提供, + * 不附带任何明示或暗示的保证或条件。请参阅 License 了解具体的权限和限制。 */ package net.micode.notes.gtask.exception; +/** + * 网络失败异常类 + * 用于表示网络连接相关的异常(如超时、连接中断等) + */ public class NetworkFailureException extends Exception { - private static final long serialVersionUID = 2107610287180234136L; + private static final long serialVersionUID = 2107610287180234136L; // 序列化版本号,用于保持类版本兼容性 + /** + * 无参构造函数 + * 创建一个默认的网络失败异常对象 + */ public NetworkFailureException() { super(); } + /** + * 带错误消息的构造函数 + * @param paramString 描述异常原因的消息字符串 + */ public NetworkFailureException(String paramString) { super(paramString); } + /** + * 带错误消息和根源异常的构造函数 + * @param paramString 描述异常原因的消息字符串 + * @param paramThrowable 导致当前异常的根本异常对象 + */ public NetworkFailureException(String paramString, Throwable paramThrowable) { super(paramString, paramThrowable); } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskASyncTask.java b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskASyncTask.java index b3b61e7..3d3edae 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskASyncTask.java +++ b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskASyncTask.java @@ -1,18 +1,14 @@ - /* * 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 + * 您可以在遵守 License 的前提下使用本文件。 + * 您可以从以下地址获取 License 副本: * * 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. + * 除非适用法律要求或书面同意,本软件根据 License 分发时“按现状”提供, + * 不附带任何明示或暗示的保证或条件。请参阅 License 了解具体的权限和限制。 */ package net.micode.notes.gtask.remote; @@ -28,96 +24,154 @@ import net.micode.notes.R; import net.micode.notes.ui.NotesListActivity; import net.micode.notes.ui.NotesPreferenceActivity; - +/** + * 处理Google Tasks同步的异步任务类 + * 负责在后台执行同步操作,并通过通知和回调机制反馈状态 + */ public class GTaskASyncTask extends AsyncTask { + // 同步通知的唯一标识ID private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; + // 完成回调接口,用于通知同步结束 public interface OnCompleteListener { - void onComplete(); + void onComplete(); // 同步完成时的回调方法 } - private Context mContext; - - private NotificationManager mNotifiManager; - - private GTaskManager mTaskManager; - - private OnCompleteListener mOnCompleteListener; + private Context mContext; // 应用上下文 + private NotificationManager mNotifiManager; // 通知管理器 + private GTaskManager mTaskManager; // 任务管理实例 + private OnCompleteListener mOnCompleteListener; // 完成回调监听器 + /** + * 构造函数 + * @param context 应用上下文 + * @param listener 同步完成回调监听器 + */ public GTaskASyncTask(Context context, OnCompleteListener listener) { mContext = context; mOnCompleteListener = listener; - mNotifiManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); + // 获取系统通知服务 + mNotifiManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + // 获取任务管理单例 mTaskManager = GTaskManager.getInstance(); } + /** + * 取消同步操作 + */ public void cancelSync() { - mTaskManager.cancelSync(); + mTaskManager.cancelSync(); // 调用任务管理器的取消同步方法 } + /** + * 发布同步进度消息(支持字符串数组参数) + * @param message 进度消息内容 + */ public void publishProgess(String message) { - publishProgress(new String[] { - message - }); + publishProgress(new String[]{message}); // 调用父类方法发布进度 } + /** + * 显示同步状态通知 + * @param tickerId 通知标题的资源ID + * @param content 通知内容文本 + */ private void showNotification(int tickerId, String content) { - Notification notification = new Notification(R.drawable.notification, mContext - .getString(tickerId), System.currentTimeMillis()); - notification.defaults = Notification.DEFAULT_LIGHTS; - notification.flags = Notification.FLAG_AUTO_CANCEL; PendingIntent pendingIntent; + // 根据通知类型设置不同的点击跳转目标 if (tickerId != R.string.ticker_success) { - pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, - NotesPreferenceActivity.class), 0); - + // 非成功状态跳转到设置界面 + pendingIntent = PendingIntent.getActivity(mContext, 0, + new Intent(mContext, NotesPreferenceActivity.class), + PendingIntent.FLAG_IMMUTABLE); } else { - pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, - NotesListActivity.class), 0); + // 成功状态跳转到笔记列表界面 + pendingIntent = PendingIntent.getActivity(mContext, 0, + new Intent(mContext, NotesListActivity.class), + PendingIntent.FLAG_IMMUTABLE); } - notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, - pendingIntent); + // 构建通知对象 + Notification.Builder builder = new Notification.Builder(mContext) + .setAutoCancel(true) // 点击后自动清除通知 + .setContentTitle(mContext.getString(R.string.app_name)) // 应用名称作为标题 + .setContentText(content) // 通知内容 + .setContentIntent(pendingIntent) // 点击通知后的跳转意图 + .setWhen(System.currentTimeMillis()) // 通知时间 + .setOngoing(true); // 显示为进行中状态(不可滑动清除) + Notification notification = builder.getNotification(); + // 显示通知 mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); } + /** + * 后台执行同步操作的核心方法 + * @param unused 未使用的参数(继承自AsyncTask) + * @return 同步结果状态码(来自GTaskManager的定义) + */ @Override protected Integer doInBackground(Void... unused) { - publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity - .getSyncAccountName(mContext))); + // 发布登录进度消息(包含当前同步账户名) + publishProgess(mContext.getString(R.string.sync_progress_login, + NotesPreferenceActivity.getSyncAccountName(mContext))); + // 调用任务管理器的同步方法,传入上下文和当前任务实例 return mTaskManager.sync(mContext, this); } + /** + * 处理进度更新的回调方法(在主线程执行) + * @param progress 包含进度消息的字符串数组 + */ @Override protected void onProgressUpdate(String... progress) { + // 显示同步中通知 showNotification(R.string.ticker_syncing, progress[0]); + // 如果上下文是同步服务,发送广播通知进度 if (mContext instanceof GTaskSyncService) { ((GTaskSyncService) mContext).sendBroadcast(progress[0]); } } + /** + * 处理后台任务完成的回调方法(在主线程执行) + * @param result 同步结果状态码 + */ @Override protected void onPostExecute(Integer result) { - if (result == GTaskManager.STATE_SUCCESS) { - showNotification(R.string.ticker_success, mContext.getString( - R.string.success_sync_account, mTaskManager.getSyncAccount())); - NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis()); - } else if (result == GTaskManager.STATE_NETWORK_ERROR) { - showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network)); - } else if (result == GTaskManager.STATE_INTERNAL_ERROR) { - showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal)); - } else if (result == GTaskManager.STATE_SYNC_CANCELLED) { - showNotification(R.string.ticker_cancel, mContext - .getString(R.string.error_sync_cancelled)); + String content; + int tickerId; + // 根据不同的同步结果显示对应的通知 + switch (result) { + case GTaskManager.STATE_SUCCESS: + tickerId = R.string.ticker_success; + content = mContext.getString(R.string.success_sync_account, + mTaskManager.getSyncAccount()); // 显示成功消息和账户名 + NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis()); // 记录最后同步时间 + break; + case GTaskManager.STATE_NETWORK_ERROR: + tickerId = R.string.ticker_fail; + content = mContext.getString(R.string.error_sync_network); // 网络错误消息 + break; + case GTaskManager.STATE_INTERNAL_ERROR: + tickerId = R.string.ticker_fail; + content = mContext.getString(R.string.error_sync_internal); // 内部错误消息 + break; + case GTaskManager.STATE_SYNC_CANCELLED: + tickerId = R.string.ticker_cancel; + content = mContext.getString(R.string.error_sync_cancelled); // 取消同步消息 + break; + default: + return; } + showNotification(tickerId, content); // 显示结果通知 + + // 在后台线程执行回调,避免阻塞主线程 if (mOnCompleteListener != null) { new Thread(new Runnable() { - public void run() { - mOnCompleteListener.onComplete(); + mOnCompleteListener.onComplete(); // 触发完成回调 } }).start(); } } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskClient.java b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskClient.java index c67dfdf..7637e41 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskClient.java +++ b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskClient.java @@ -14,8 +14,10 @@ * limitations under the License. */ +// Google Tasks API客户端实现类,负责与Google Tasks服务通信 package net.micode.notes.gtask.remote; +// Android基础组件导入 import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AccountManagerFuture; @@ -24,6 +26,7 @@ import android.os.Bundle; import android.text.TextUtils; import android.util.Log; +// 项目内部类导入 import net.micode.notes.gtask.data.Node; import net.micode.notes.gtask.data.Task; import net.micode.notes.gtask.data.TaskList; @@ -32,6 +35,7 @@ import net.micode.notes.gtask.exception.NetworkFailureException; import net.micode.notes.tool.GTaskStringUtils; import net.micode.notes.ui.NotesPreferenceActivity; +// HTTP客户端和JSON处理类导入 import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; @@ -50,6 +54,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +// Java IO和工具类导入 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -60,36 +65,55 @@ import java.util.zip.GZIPInputStream; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; - +/** + * Google Tasks API客户端类,封装了与Google Tasks服务的所有交互 + */ public class GTaskClient { + // 日志标签 private static final String TAG = GTaskClient.class.getSimpleName(); + // Google Tasks基础URL private static final String GTASK_URL = "https://mail.google.com/tasks/"; + // Google Tasks获取数据URL private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; + // Google Tasks提交数据URL private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; + // 单例实例 private static GTaskClient mInstance = null; + // HTTP客户端 private DefaultHttpClient mHttpClient; + // 获取数据URL private String mGetUrl; + // 提交数据URL private String mPostUrl; + // 客户端版本号 private long mClientVersion; + // 登录状态标志 private boolean mLoggedin; + // 最后登录时间 private long mLastLoginTime; + // 操作ID计数器 private int mActionId; + // 关联的Google账户 private Account mAccount; + // 待更新操作数组 private JSONArray mUpdateArray; + /** + * 私有构造函数,初始化默认值 + */ private GTaskClient() { mHttpClient = null; mGetUrl = GTASK_GET_URL; @@ -102,6 +126,10 @@ public class GTaskClient { mUpdateArray = null; } + /** + * 获取单例实例 + * @return GTaskClient单例实例 + */ public static synchronized GTaskClient getInstance() { if (mInstance == null) { mInstance = new GTaskClient(); @@ -109,36 +137,43 @@ public class GTaskClient { return mInstance; } + /** + * 登录Google Tasks服务 + * @param activity 上下文Activity + * @return 是否登录成功 + */ public boolean login(Activity activity) { - // we suppose that the cookie would expire after 5 minutes - // then we need to re-login + // 假设cookie在5分钟后过期,需要重新登录 final long interval = 1000 * 60 * 5; if (mLastLoginTime + interval < System.currentTimeMillis()) { mLoggedin = false; } - // need to re-login after account switch - if (mLoggedin - && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity - .getSyncAccountName(activity))) { + // 账户切换后需要重新登录 + if (mLoggedin && !TextUtils.equals(getSyncAccount().name, + NotesPreferenceActivity.getSyncAccountName(activity))) { mLoggedin = false; } + // 如果已经登录则直接返回 if (mLoggedin) { Log.d(TAG, "already logged in"); return true; } + // 更新最后登录时间 mLastLoginTime = System.currentTimeMillis(); + + // 获取Google账户认证令牌 String authToken = loginGoogleAccount(activity, false); if (authToken == null) { Log.e(TAG, "login google account failed"); return false; } - // login with custom domain if necessary - if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() - .endsWith("googlemail.com"))) { + // 处理自定义域名的登录(非gmail.com或googlemail.com) + if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || + mAccount.name.toLowerCase().endsWith("googlemail.com"))) { StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); int index = mAccount.name.indexOf('@') + 1; String suffix = mAccount.name.substring(index); @@ -146,12 +181,13 @@ public class GTaskClient { mGetUrl = url.toString() + "ig"; mPostUrl = url.toString() + "r/ig"; + // 尝试使用自定义域名登录 if (tryToLoginGtask(activity, authToken)) { mLoggedin = true; } } - // try to login with google official url + // 如果自定义域名登录失败,尝试使用官方URL登录 if (!mLoggedin) { mGetUrl = GTASK_GET_URL; mPostUrl = GTASK_POST_URL; @@ -164,16 +200,26 @@ public class GTaskClient { return true; } + /** + * 登录Google账户并获取认证令牌 + * @param activity 上下文Activity + * @param invalidateToken 是否使旧令牌失效 + * @return 认证令牌 + */ private String loginGoogleAccount(Activity activity, boolean invalidateToken) { String authToken; AccountManager accountManager = AccountManager.get(activity); + + // 获取所有Google账户 Account[] accounts = accountManager.getAccountsByType("com.google"); + // 检查是否有可用账户 if (accounts.length == 0) { Log.e(TAG, "there is no available google account"); return null; } + // 获取设置中指定的同步账户 String accountName = NotesPreferenceActivity.getSyncAccountName(activity); Account account = null; for (Account a : accounts) { @@ -182,6 +228,8 @@ public class GTaskClient { break; } } + + // 验证账户是否存在 if (account != null) { mAccount = account; } else { @@ -189,12 +237,14 @@ public class GTaskClient { return null; } - // get the token now + // 获取认证令牌 AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account, "goanna_mobile", null, activity, null, null); try { Bundle authTokenBundle = accountManagerFuture.getResult(); authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN); + + // 如果需要使旧令牌失效 if (invalidateToken) { accountManager.invalidateAuthToken("com.google", authToken); loginGoogleAccount(activity, false); @@ -207,16 +257,23 @@ public class GTaskClient { return authToken; } + /** + * 尝试登录Google Tasks服务 + * @param activity 上下文Activity + * @param authToken 认证令牌 + * @return 是否登录成功 + */ private boolean tryToLoginGtask(Activity activity, String authToken) { + // 第一次尝试登录 if (!loginGtask(authToken)) { - // maybe the auth token is out of date, now let's invalidate the - // token and try again + // 如果失败,可能是令牌过期,使旧令牌失效并获取新令牌 authToken = loginGoogleAccount(activity, true); if (authToken == null) { Log.e(TAG, "login google account failed"); return false; } + // 使用新令牌再次尝试登录 if (!loginGtask(authToken)) { Log.e(TAG, "login gtask failed"); return false; @@ -225,25 +282,33 @@ public class GTaskClient { return true; } + /** + * 实际执行Google Tasks登录操作 + * @param authToken 认证令牌 + * @return 是否登录成功 + */ private boolean loginGtask(String authToken) { + // 设置HTTP连接参数 int timeoutConnection = 10000; int timeoutSocket = 15000; HttpParams httpParameters = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); + + // 创建HTTP客户端 mHttpClient = new DefaultHttpClient(httpParameters); BasicCookieStore localBasicCookieStore = new BasicCookieStore(); mHttpClient.setCookieStore(localBasicCookieStore); HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); - // login gtask + // 执行登录请求 try { String loginUrl = mGetUrl + "?auth=" + authToken; HttpGet httpGet = new HttpGet(loginUrl); HttpResponse response = null; response = mHttpClient.execute(httpGet); - // get the cookie now + // 检查是否获取到认证cookie List cookies = mHttpClient.getCookieStore().getCookies(); boolean hasAuthCookie = false; for (Cookie cookie : cookies) { @@ -255,7 +320,7 @@ public class GTaskClient { Log.w(TAG, "it seems that there is no auth cookie"); } - // get the client version + // 从响应中提取客户端版本号 String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -272,7 +337,6 @@ public class GTaskClient { e.printStackTrace(); return false; } catch (Exception e) { - // simply catch all exceptions Log.e(TAG, "httpget gtask_url failed"); return false; } @@ -280,10 +344,18 @@ public class GTaskClient { return true; } + /** + * 获取下一个操作ID + * @return 操作ID + */ private int getActionId() { return mActionId++; } + /** + * 创建HTTP POST请求 + * @return 配置好的HttpPost对象 + */ private HttpPost createHttpPost() { HttpPost httpPost = new HttpPost(mPostUrl); httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); @@ -291,13 +363,21 @@ public class GTaskClient { return httpPost; } + /** + * 从HTTP实体获取响应内容 + * @param entity HTTP实体 + * @return 响应内容字符串 + * @throws IOException 如果读取失败 + */ private String getResponseContent(HttpEntity entity) throws IOException { + // 检查内容编码 String contentEncoding = null; if (entity.getContentEncoding() != null) { contentEncoding = entity.getContentEncoding().getValue(); Log.d(TAG, "encoding: " + contentEncoding); } + // 根据编码类型创建适当的输入流 InputStream input = entity.getContent(); if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) { input = new GZIPInputStream(entity.getContent()); @@ -306,6 +386,7 @@ public class GTaskClient { input = new InflaterInputStream(entity.getContent(), inflater); } + // 读取响应内容 try { InputStreamReader isr = new InputStreamReader(input); BufferedReader br = new BufferedReader(isr); @@ -323,7 +404,14 @@ public class GTaskClient { } } + /** + * 发送POST请求 + * @param js 要发送的JSON对象 + * @return 响应JSON对象 + * @throws NetworkFailureException 网络错误时抛出 + */ private JSONObject postRequest(JSONObject js) throws NetworkFailureException { + // 检查登录状态 if (!mLoggedin) { Log.e(TAG, "please login first"); throw new ActionFailureException("not logged in"); @@ -331,12 +419,13 @@ public class GTaskClient { HttpPost httpPost = createHttpPost(); try { + // 准备请求参数 LinkedList list = new LinkedList(); list.add(new BasicNameValuePair("r", js.toString())); UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); httpPost.setEntity(entity); - // execute the post + // 执行请求并处理响应 HttpResponse response = mHttpClient.execute(httpPost); String jsString = getResponseContent(response.getEntity()); return new JSONObject(jsString); @@ -360,23 +449,31 @@ public class GTaskClient { } } + /** + * 创建任务 + * @param task 要创建的任务对象 + * @throws NetworkFailureException 网络错误时抛出 + */ public void createTask(Task task) throws NetworkFailureException { + // 提交所有待更新操作 commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); - // action_list + // 添加创建任务操作 actionList.put(task.getCreateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version + // 设置客户端版本 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - // post + // 发送请求并处理响应 JSONObject jsResponse = postRequest(jsPost); JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( GTaskStringUtils.GTASK_JSON_RESULTS).get(0); + + // 设置任务ID task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); } catch (JSONException e) { @@ -386,23 +483,31 @@ public class GTaskClient { } } + /** + * 创建任务列表 + * @param tasklist 要创建的任务列表对象 + * @throws NetworkFailureException 网络错误时抛出 + */ public void createTaskList(TaskList tasklist) throws NetworkFailureException { + // 提交所有待更新操作 commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); - // action_list + // 添加创建任务列表操作 actionList.put(tasklist.getCreateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client version + // 设置客户端版本 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - // post + // 发送请求并处理响应 JSONObject jsResponse = postRequest(jsPost); JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( GTaskStringUtils.GTASK_JSON_RESULTS).get(0); + + // 设置任务列表ID tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); } catch (JSONException e) { @@ -412,17 +517,22 @@ public class GTaskClient { } } + /** + * 提交所有待更新操作 + * @throws NetworkFailureException 网络错误时抛出 + */ public void commitUpdate() throws NetworkFailureException { if (mUpdateArray != null) { try { JSONObject jsPost = new JSONObject(); - // action_list + // 添加待更新操作列表 jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray); - // client_version + // 设置客户端版本 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + // 发送请求 postRequest(jsPost); mUpdateArray = null; } catch (JSONException e) { @@ -433,50 +543,67 @@ public class GTaskClient { } } + /** + * 添加待更新节点 + * @param node 要更新的节点 + * @throws NetworkFailureException 网络错误时抛出 + */ public void addUpdateNode(Node node) throws NetworkFailureException { if (node != null) { - // too many update items may result in an error - // set max to 10 items + // 如果待更新操作过多(超过10个),先提交 if (mUpdateArray != null && mUpdateArray.length() > 10) { commitUpdate(); } + // 初始化更新数组(如果为空) if (mUpdateArray == null) mUpdateArray = new JSONArray(); + + // 添加更新操作 mUpdateArray.put(node.getUpdateAction(getActionId())); } } + /** + * 移动任务 + * @param task 要移动的任务 + * @param preParent 原父任务列表 + * @param curParent 新父任务列表 + * @throws NetworkFailureException 网络错误时抛出 + */ public void moveTask(Task task, TaskList preParent, TaskList curParent) throws NetworkFailureException { + // 提交所有待更新操作 commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); JSONObject action = new JSONObject(); - // action_list + // 设置移动操作参数 action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); + + // 如果在同一任务列表中移动且有前驱兄弟节点 if (preParent == curParent && task.getPriorSibling() != null) { - // put prioring_sibing_id only if moving within the tasklist and - // it is not the first one action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); } + + // 设置源列表和目标父节点 action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); + + // 如果是在不同任务列表间移动 if (preParent != curParent) { - // put the dest_list only if moving between tasklists action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); } + + // 添加操作并发送请求 actionList.put(action); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - - // client_version jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - postRequest(jsPost); } catch (JSONException e) { @@ -486,20 +613,25 @@ public class GTaskClient { } } + /** + * 删除节点 + * @param node 要删除的节点 + * @throws NetworkFailureException 网络错误时抛出 + */ public void deleteNode(Node node) throws NetworkFailureException { + // 提交所有待更新操作 commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); - // action_list + // 标记节点为已删除并添加更新操作 node.setDeleted(true); actionList.put(node.getUpdateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version + // 设置客户端版本并发送请求 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - postRequest(jsPost); mUpdateArray = null; } catch (JSONException e) { @@ -509,18 +641,25 @@ public class GTaskClient { } } + /** + * 获取所有任务列表 + * @return 任务列表JSON数组 + * @throws NetworkFailureException 网络错误时抛出 + */ public JSONArray getTaskLists() throws NetworkFailureException { + // 检查登录状态 if (!mLoggedin) { Log.e(TAG, "please login first"); throw new ActionFailureException("not logged in"); } try { + // 发送GET请求获取任务列表 HttpGet httpGet = new HttpGet(mGetUrl); HttpResponse response = null; response = mHttpClient.execute(httpGet); - // get the task list + // 从响应中提取任务列表数据 String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -547,25 +686,33 @@ public class GTaskClient { } } + /** + * 获取指定任务列表的所有任务 + * @param listGid 任务列表ID + * @return 任务JSON数组 + * @throws NetworkFailureException 网络错误时抛出 + */ public JSONArray getTaskList(String listGid) throws NetworkFailureException { + // 提交所有待更新操作 commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); JSONObject action = new JSONObject(); - // action_list + // 设置获取所有任务操作参数 action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); + + // 添加操作并发送请求 actionList.put(action); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - - // client_version jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + // 处理响应 JSONObject jsResponse = postRequest(jsPost); return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS); } catch (JSONException e) { @@ -575,11 +722,18 @@ public class GTaskClient { } } + /** + * 获取同步账户 + * @return 同步账户对象 + */ public Account getSyncAccount() { return mAccount; } + /** + * 重置待更新数组 + */ public void resetUpdateArray() { mUpdateArray = null; } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java index d2b4082..a312c03 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java +++ b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java @@ -14,8 +14,10 @@ * limitations under the License. */ +// Google Tasks同步管理类,负责本地便签与Google Tasks的同步操作 package net.micode.notes.gtask.remote; +// Android基础组件导入 import android.app.Activity; import android.content.ContentResolver; import android.content.ContentUris; @@ -24,7 +26,10 @@ import android.content.Context; import android.database.Cursor; import android.util.Log; +// 项目资源文件导入 import net.micode.notes.R; + +// 数据模型和工具类导入 import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.NoteColumns; @@ -38,88 +43,125 @@ import net.micode.notes.gtask.exception.NetworkFailureException; import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.GTaskStringUtils; +// JSON处理类导入 import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +// Java集合类导入 import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; - +// Google Tasks同步管理主类 public class GTaskManager { + // 日志标签 private static final String TAG = GTaskManager.class.getSimpleName(); + // 同步状态常量定义 public static final int STATE_SUCCESS = 0; - public static final int STATE_NETWORK_ERROR = 1; - public static final int STATE_INTERNAL_ERROR = 2; - public static final int STATE_SYNC_IN_PROGRESS = 3; - public static final int STATE_SYNC_CANCELLED = 4; + // 单例实例 private static GTaskManager mInstance = null; + // Activity上下文 private Activity mActivity; + // 应用上下文 private Context mContext; + // 内容解析器 private ContentResolver mContentResolver; + // 同步状态标志 private boolean mSyncing; + // 取消同步标志 private boolean mCancelled; + // 任务列表哈希表(gid -> TaskList) private HashMap mGTaskListHashMap; + // 任务节点哈希表(gid -> Node) private HashMap mGTaskHashMap; + // 元数据哈希表(gid -> MetaData) private HashMap mMetaHashMap; + // 元数据任务列表 private TaskList mMetaList; + // 本地删除ID集合 private HashSet mLocalDeleteIdMap; + // Google任务ID到本地ID的映射 private HashMap mGidToNid; + // 本地ID到Google任务ID的映射 private HashMap mNidToGid; + // 私有构造函数 private GTaskManager() { + // 初始化同步状态标志 mSyncing = false; mCancelled = false; + + // 初始化任务列表哈希表 mGTaskListHashMap = new HashMap(); + + // 初始化任务节点哈希表 mGTaskHashMap = new HashMap(); + + // 初始化元数据哈希表 mMetaHashMap = new HashMap(); + + // 初始化元数据任务列表 mMetaList = null; + + // 初始化本地删除ID集合 mLocalDeleteIdMap = new HashSet(); + + // 初始化ID映射表 mGidToNid = new HashMap(); mNidToGid = new HashMap(); } + // 获取单例实例 public static synchronized GTaskManager getInstance() { + // 如果实例不存在则创建新实例 if (mInstance == null) { mInstance = new GTaskManager(); } return mInstance; } + // 设置Activity上下文(用于获取认证令牌) public synchronized void setActivityContext(Activity activity) { - // used for getting authtoken mActivity = activity; } + // 执行同步操作 public int sync(Context context, GTaskASyncTask asyncTask) { + // 检查是否正在同步 if (mSyncing) { Log.d(TAG, "Sync is in progress"); return STATE_SYNC_IN_PROGRESS; } + + // 初始化上下文和内容解析器 mContext = context; mContentResolver = mContext.getContentResolver(); + + // 设置同步状态标志 mSyncing = true; mCancelled = false; + + // 清空各种缓存和映射表 mGTaskListHashMap.clear(); mGTaskHashMap.clear(); mMetaHashMap.clear(); @@ -128,34 +170,39 @@ public class GTaskManager { mNidToGid.clear(); try { + // 获取GTask客户端实例 GTaskClient client = GTaskClient.getInstance(); client.resetUpdateArray(); - // login google task + // 登录Google Tasks(如果未取消) if (!mCancelled) { if (!client.login(mActivity)) { throw new NetworkFailureException("login google task failed"); } } - // get the task list from google + // 初始化任务列表 asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); initGTaskList(); - // do content sync work + // 执行内容同步 asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing)); syncContent(); } catch (NetworkFailureException e) { + // 处理网络错误 Log.e(TAG, e.toString()); return STATE_NETWORK_ERROR; } catch (ActionFailureException e) { + // 处理操作错误 Log.e(TAG, e.toString()); return STATE_INTERNAL_ERROR; } catch (Exception e) { + // 处理其他异常 Log.e(TAG, e.toString()); e.printStackTrace(); return STATE_INTERNAL_ERROR; } finally { + // 清理资源 mGTaskListHashMap.clear(); mGTaskHashMap.clear(); mMetaHashMap.clear(); @@ -165,29 +212,37 @@ public class GTaskManager { mSyncing = false; } + // 返回同步结果状态 return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; } + // 初始化Google Tasks列表 private void initGTaskList() throws NetworkFailureException { + // 检查是否已取消 if (mCancelled) return; + + // 获取GTask客户端实例 GTaskClient client = GTaskClient.getInstance(); try { + // 获取任务列表JSON数组 JSONArray jsTaskLists = client.getTaskLists(); - // init meta list first + // 首先初始化元数据列表 mMetaList = null; for (int i = 0; i < jsTaskLists.length(); i++) { + // 获取任务列表JSON对象 JSONObject object = jsTaskLists.getJSONObject(i); String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); - if (name - .equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { + // 检查是否为元数据文件夹 + if (name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { + // 创建元数据任务列表 mMetaList = new TaskList(); mMetaList.setContentByRemoteJSON(object); - // load meta data + // 加载元数据 JSONArray jsMetas = client.getTaskList(gid); for (int j = 0; j < jsMetas.length(); j++) { object = (JSONObject) jsMetas.getJSONObject(j); @@ -203,7 +258,7 @@ public class GTaskManager { } } - // create meta list if not existed + // 如果元数据列表不存在则创建 if (mMetaList == null) { mMetaList = new TaskList(); mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX @@ -211,21 +266,23 @@ public class GTaskManager { GTaskClient.getInstance().createTaskList(mMetaList); } - // init task list + // 初始化普通任务列表 for (int i = 0; i < jsTaskLists.length(); i++) { JSONObject object = jsTaskLists.getJSONObject(i); String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); + // 检查是否为MIUI文件夹(非元数据文件夹) if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX - + GTaskStringUtils.FOLDER_META)) { + + GTaskStringUtils.FOLDER_META)) { + // 创建任务列表并添加到哈希表 TaskList tasklist = new TaskList(); tasklist.setContentByRemoteJSON(object); mGTaskListHashMap.put(gid, tasklist); mGTaskHashMap.put(gid, tasklist); - // load tasks + // 加载任务列表中的任务 JSONArray jsTasks = client.getTaskList(gid); for (int j = 0; j < jsTasks.length(); j++) { object = (JSONObject) jsTasks.getJSONObject(j); @@ -241,78 +298,91 @@ public class GTaskManager { } } } catch (JSONException e) { + // 处理JSON解析错误 Log.e(TAG, e.toString()); e.printStackTrace(); throw new ActionFailureException("initGTaskList: handing JSONObject failed"); } } + // 同步内容数据 private void syncContent() throws NetworkFailureException { + // 同步类型和临时变量 int syncType; Cursor c = null; String gid; Node node; + // 清空本地删除ID集合 mLocalDeleteIdMap.clear(); + // 检查是否已取消 if (mCancelled) { return; } - // for local deleted note + // 处理本地已删除的便签 try { + // 查询回收站中的便签 c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type<>? AND parent_id=?)", new String[] { String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) }, null); if (c != null) { while (c.moveToNext()) { + // 获取Google任务ID和对应节点 gid = c.getString(SqlNote.GTASK_ID_COLUMN); node = mGTaskHashMap.get(gid); if (node != null) { + // 从哈希表中移除并执行同步 mGTaskHashMap.remove(gid); doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); } + // 添加到本地删除集合 mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); } } else { Log.w(TAG, "failed to query trash folder"); } } finally { + // 关闭Cursor if (c != null) { c.close(); c = null; } } - // sync folder first + // 首先同步文件夹 syncFolder(); - // for note existing in database + // 处理数据库中存在的便签 try { + // 查询非回收站中的便签 c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type=? AND parent_id<>?)", new String[] { String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER) }, NoteColumns.TYPE + " DESC"); if (c != null) { while (c.moveToNext()) { + // 获取Google任务ID和对应节点 gid = c.getString(SqlNote.GTASK_ID_COLUMN); node = mGTaskHashMap.get(gid); if (node != null) { + // 从哈希表中移除并确定同步类型 mGTaskHashMap.remove(gid); mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); syncType = node.getSyncAction(c); } else { + // 根据情况确定是本地添加还是远程删除 if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { - // local add syncType = Node.SYNC_ACTION_ADD_REMOTE; } else { - // remote delete syncType = Node.SYNC_ACTION_DEL_LOCAL; } } + // 执行内容同步 doContentSync(syncType, node, c); } } else { @@ -320,13 +390,14 @@ public class GTaskManager { } } finally { + // 关闭Cursor if (c != null) { c.close(); c = null; } } - // go through remaining items + // 处理剩余的项目(远程添加) Iterator> iter = mGTaskHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -334,46 +405,50 @@ public class GTaskManager { doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); } - // mCancelled can be set by another thread, so we neet to check one by - // one - // clear local delete table + // 检查是否已取消(可能由其他线程设置) + // 清理本地删除表 if (!mCancelled) { if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { throw new ActionFailureException("failed to batch-delete local deleted notes"); } } - // refresh local sync id + // 刷新本地同步ID(如果未取消) if (!mCancelled) { GTaskClient.getInstance().commitUpdate(); refreshLocalSyncId(); } - } + // 同步文件夹 private void syncFolder() throws NetworkFailureException { + // 临时变量 Cursor c = null; String gid; Node node; int syncType; + // 检查是否已取消 if (mCancelled) { return; } - // for root folder + // 处理根文件夹 try { + // 查询根文件夹 c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); if (c != null) { c.moveToNext(); + // 获取Google任务ID和对应节点 gid = c.getString(SqlNote.GTASK_ID_COLUMN); node = mGTaskHashMap.get(gid); if (node != null) { + // 从哈希表中移除并更新映射 mGTaskHashMap.remove(gid); mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); - // for system folder, only update remote name if necessary + // 对于系统文件夹,只在必要时更新远程名称 if (!node.getName().equals( GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); @@ -384,28 +459,31 @@ public class GTaskManager { Log.w(TAG, "failed to query root folder"); } } finally { + // 关闭Cursor if (c != null) { c.close(); c = null; } } - // for call-note folder + // 处理通话记录文件夹 try { + // 查询通话记录文件夹 c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", new String[] { - String.valueOf(Notes.ID_CALL_RECORD_FOLDER) + String.valueOf(Notes.ID_CALL_RECORD_FOLDER) }, null); if (c != null) { if (c.moveToNext()) { + // 获取Google任务ID和对应节点 gid = c.getString(SqlNote.GTASK_ID_COLUMN); node = mGTaskHashMap.get(gid); if (node != null) { + // 从哈希表中移除并更新映射 mGTaskHashMap.remove(gid); mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); - // for system folder, only update remote name if - // necessary + // 对于系统文件夹,只在必要时更新远程名称 if (!node.getName().equals( GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) @@ -418,49 +496,54 @@ public class GTaskManager { Log.w(TAG, "failed to query call note folder"); } } finally { + // 关闭Cursor if (c != null) { c.close(); c = null; } } - // for local existing folders + // 处理本地存在的文件夹 try { + // 查询非回收站中的文件夹 c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type=? AND parent_id<>?)", new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER) }, NoteColumns.TYPE + " DESC"); if (c != null) { while (c.moveToNext()) { + // 获取Google任务ID和对应节点 gid = c.getString(SqlNote.GTASK_ID_COLUMN); node = mGTaskHashMap.get(gid); if (node != null) { + // 从哈希表中移除并确定同步类型 mGTaskHashMap.remove(gid); mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); syncType = node.getSyncAction(c); } else { + // 根据情况确定是本地添加还是远程删除 if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { - // local add syncType = Node.SYNC_ACTION_ADD_REMOTE; } else { - // remote delete syncType = Node.SYNC_ACTION_DEL_LOCAL; } } + // 执行内容同步 doContentSync(syncType, node, c); } } else { Log.w(TAG, "failed to query existing folder"); } } finally { + // 关闭Cursor if (c != null) { c.close(); c = null; } } - // for remote add folders + // 处理远程添加的文件夹 Iterator> iter = mGTaskListHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -472,24 +555,33 @@ public class GTaskManager { } } + // 如果未取消则提交更新 if (!mCancelled) GTaskClient.getInstance().commitUpdate(); } + // 执行具体的内容同步操作 private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { + // 检查是否已取消 if (mCancelled) { return; } + // 元数据临时变量 MetaData meta; + + // 根据同步类型执行不同操作 switch (syncType) { case Node.SYNC_ACTION_ADD_LOCAL: + // 本地添加节点 addLocalNode(node); break; case Node.SYNC_ACTION_ADD_REMOTE: + // 远程添加节点 addRemoteNode(node, c); break; case Node.SYNC_ACTION_DEL_LOCAL: + // 本地删除节点 meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); if (meta != null) { GTaskClient.getInstance().deleteNode(meta); @@ -497,6 +589,7 @@ public class GTaskManager { mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); break; case Node.SYNC_ACTION_DEL_REMOTE: + // 远程删除节点 meta = mMetaHashMap.get(node.getGid()); if (meta != null) { GTaskClient.getInstance().deleteNode(meta); @@ -504,57 +597,70 @@ public class GTaskManager { GTaskClient.getInstance().deleteNode(node); break; case Node.SYNC_ACTION_UPDATE_LOCAL: + // 更新本地节点 updateLocalNode(node, c); break; case Node.SYNC_ACTION_UPDATE_REMOTE: + // 更新远程节点 updateRemoteNode(node, c); break; case Node.SYNC_ACTION_UPDATE_CONFLICT: - // merging both modifications maybe a good idea - // right now just use local update simply + // 更新冲突(目前简单使用本地更新) updateRemoteNode(node, c); break; case Node.SYNC_ACTION_NONE: + // 无操作 break; case Node.SYNC_ACTION_ERROR: default: + // 未知操作类型错误 throw new ActionFailureException("unkown sync action type"); } } + // 添加本地节点 private void addLocalNode(Node node) throws NetworkFailureException { + // 检查是否已取消 if (mCancelled) { return; } + // 创建SQLite便签对象 SqlNote sqlNote; if (node instanceof TaskList) { + // 处理任务列表(文件夹) if (node.getName().equals( GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { + // 根文件夹 sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER); } else if (node.getName().equals( GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { + // 通话记录文件夹 sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); } else { + // 普通文件夹 sqlNote = new SqlNote(mContext); sqlNote.setContent(node.getLocalJSONFromContent()); sqlNote.setParentId(Notes.ID_ROOT_FOLDER); } } else { + // 处理普通便签 sqlNote = new SqlNote(mContext); JSONObject js = node.getLocalJSONFromContent(); try { + // 处理便签ID冲突 if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); if (note.has(NoteColumns.ID)) { long id = note.getLong(NoteColumns.ID); if (DataUtils.existInNoteDatabase(mContentResolver, id)) { - // the id is not available, have to create a new one + // ID不可用,需要创建新ID note.remove(NoteColumns.ID); } } } + // 处理数据ID冲突 if (js.has(GTaskStringUtils.META_HEAD_DATA)) { JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); for (int i = 0; i < dataArray.length(); i++) { @@ -562,13 +668,11 @@ public class GTaskManager { if (data.has(DataColumns.ID)) { long dataId = data.getLong(DataColumns.ID); if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { - // the data id is not available, have to create - // a new one + // 数据ID不可用,需要创建新ID data.remove(DataColumns.ID); } } } - } } catch (JSONException e) { Log.w(TAG, e.toString()); @@ -576,6 +680,7 @@ public class GTaskManager { } sqlNote.setContent(js); + // 设置父节点ID Long parentId = mGidToNid.get(((Task) node).getParent().getGid()); if (parentId == null) { Log.e(TAG, "cannot find task's parent id locally"); @@ -584,28 +689,32 @@ public class GTaskManager { sqlNote.setParentId(parentId.longValue()); } - // create the local node + // 创建本地节点 sqlNote.setGtaskId(node.getGid()); sqlNote.commit(false); - // update gid-nid mapping + // 更新gid-nid映射 mGidToNid.put(node.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), node.getGid()); - // update meta + // 更新元数据 updateRemoteMeta(node.getGid(), sqlNote); } + // 更新本地节点 private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { + // 检查是否已取消 if (mCancelled) { return; } + // 创建SQLite便签对象 SqlNote sqlNote; - // update the note locally + // 更新本地便签 sqlNote = new SqlNote(mContext, c); sqlNote.setContent(node.getLocalJSONFromContent()); + // 设置父节点ID Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid()) : new Long(Notes.ID_ROOT_FOLDER); if (parentId == null) { @@ -615,23 +724,28 @@ public class GTaskManager { sqlNote.setParentId(parentId.longValue()); sqlNote.commit(true); - // update meta info + // 更新元数据 updateRemoteMeta(node.getGid(), sqlNote); } + // 添加远程节点 private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { + // 检查是否已取消 if (mCancelled) { return; } + // 创建SQLite便签对象 SqlNote sqlNote = new SqlNote(mContext, c); Node n; - // update remotely + // 远程更新 if (sqlNote.isNoteType()) { + // 处理普通便签 Task task = new Task(); task.setContentByLocalJSON(sqlNote.getContent()); + // 设置父任务列表 String parentGid = mNidToGid.get(sqlNote.getParentId()); if (parentGid == null) { Log.e(TAG, "cannot find task's parent tasklist"); @@ -639,15 +753,17 @@ public class GTaskManager { } mGTaskListHashMap.get(parentGid).addChildTask(task); + // 创建远程任务 GTaskClient.getInstance().createTask(task); n = (Node) task; - // add meta + // 添加元数据 updateRemoteMeta(task.getGid(), sqlNote); } else { + // 处理文件夹 TaskList tasklist = null; - // we need to skip folder if it has already existed + // 构建文件夹名称 String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) folderName += GTaskStringUtils.FOLDER_DEFAULT; @@ -656,6 +772,7 @@ public class GTaskManager { else folderName += sqlNote.getSnippet(); + // 检查是否已存在同名文件夹 Iterator> iter = mGTaskListHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -671,7 +788,7 @@ public class GTaskManager { } } - // no match we can add now + // 如果没有匹配则创建新文件夹 if (tasklist == null) { tasklist = new TaskList(); tasklist.setContentByLocalJSON(sqlNote.getContent()); @@ -681,36 +798,40 @@ public class GTaskManager { n = (Node) tasklist; } - // update local note + // 更新本地便签 sqlNote.setGtaskId(n.getGid()); sqlNote.commit(false); sqlNote.resetLocalModified(); sqlNote.commit(true); - // gid-id mapping + // 更新gid-id映射 mGidToNid.put(n.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), n.getGid()); } + // 更新远程节点 private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { + // 检查是否已取消 if (mCancelled) { return; } + // 创建SQLite便签对象 SqlNote sqlNote = new SqlNote(mContext, c); - // update remotely + // 远程更新 node.setContentByLocalJSON(sqlNote.getContent()); GTaskClient.getInstance().addUpdateNode(node); - // update meta + // 更新元数据 updateRemoteMeta(node.getGid(), sqlNote); - // move task if necessary + // 如果需要则移动任务 if (sqlNote.isNoteType()) { Task task = (Task) node; TaskList preParentList = task.getParent(); + // 获取当前父任务列表 String curParentGid = mNidToGid.get(sqlNote.getParentId()); if (curParentGid == null) { Log.e(TAG, "cannot find task's parent tasklist"); @@ -718,6 +839,7 @@ public class GTaskManager { } TaskList curParentList = mGTaskListHashMap.get(curParentGid); + // 如果父任务列表发生变化则移动任务 if (preParentList != curParentList) { preParentList.removeChildTask(task); curParentList.addChildTask(task); @@ -725,18 +847,23 @@ public class GTaskManager { } } - // clear local modified flag + // 清除本地修改标志 sqlNote.resetLocalModified(); sqlNote.commit(true); } + // 更新远程元数据 private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { + // 只处理普通便签的元数据 if (sqlNote != null && sqlNote.isNoteType()) { + // 获取或创建元数据 MetaData metaData = mMetaHashMap.get(gid); if (metaData != null) { + // 更新现有元数据 metaData.setMeta(gid, sqlNote.getContent()); GTaskClient.getInstance().addUpdateNode(metaData); } else { + // 创建新元数据 metaData = new MetaData(); metaData.setMeta(gid, sqlNote.getContent()); mMetaList.addChildTask(metaData); @@ -746,17 +873,20 @@ public class GTaskManager { } } + // 刷新本地同步ID private void refreshLocalSyncId() throws NetworkFailureException { + // 检查是否已取消 if (mCancelled) { return; } - // get the latest gtask list + // 获取最新的Google任务列表 mGTaskHashMap.clear(); mGTaskListHashMap.clear(); mMetaHashMap.clear(); initGTaskList(); + // 查询并更新本地同步ID Cursor c = null; try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, @@ -765,9 +895,11 @@ public class GTaskManager { }, NoteColumns.TYPE + " DESC"); if (c != null) { while (c.moveToNext()) { + // 获取Google任务ID和对应节点 String gid = c.getString(SqlNote.GTASK_ID_COLUMN); Node node = mGTaskHashMap.get(gid); if (node != null) { + // 从哈希表中移除并更新同步ID mGTaskHashMap.remove(gid); ContentValues values = new ContentValues(); values.put(NoteColumns.SYNC_ID, node.getLastModified()); @@ -783,6 +915,7 @@ public class GTaskManager { Log.w(TAG, "failed to query local note to refresh sync id"); } } finally { + // 关闭Cursor if (c != null) { c.close(); c = null; @@ -790,11 +923,13 @@ public class GTaskManager { } } + // 获取同步账户名称 public String getSyncAccount() { return GTaskClient.getInstance().getSyncAccount().name; } + // 取消同步操作 public void cancelSync() { mCancelled = true; } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskSyncService.java b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskSyncService.java index cca36f7..af41346 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskSyncService.java +++ b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskSyncService.java @@ -2,127 +2,192 @@ * 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 + * 您可以在遵守 License 的前提下使用本文件。 + * 您可以从以下地址获取 License 副本: * * 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. + * 除非适用法律要求或书面同意,本软件根据 License 分发时“按现状”提供, + * 不附带任何明示或暗示的保证或条件。请参阅 License 了解具体的权限和限制。 */ -package net.micode.notes.gtask.remote; +package net.micode.notes.gtask.remote; // 指定代码所属的包名 -import android.app.Activity; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.IBinder; +import android.app.Activity; // 导入Activity类,用于获取上下文 +import android.app.Service; // 导入Service类,用于创建后台服务 +import android.content.Context; // 导入Context类,提供应用环境信息 +import android.content.Intent; // 导入Intent类,用于组件间通信 +import android.os.Bundle; // 导入Bundle类,用于传递数据 +import android.os.IBinder; // 导入IBinder接口,用于服务间通信 +/** + * Google Tasks同步服务类,负责在后台执行任务同步操作 + * 通过Service机制实现,支持启动、取消同步等操作,并通过广播通知同步状态 + */ public class GTaskSyncService extends Service { + // 同步动作类型的Extra键名 public final static String ACTION_STRING_NAME = "sync_action_type"; + // 启动同步的动作类型常量 public final static int ACTION_START_SYNC = 0; + // 取消同步的动作类型常量 public final static int ACTION_CANCEL_SYNC = 1; + // 无效动作的类型常量 public final static int ACTION_INVALID = 2; + // 同步服务广播的名称,用于发送同步状态更新 public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; + // 广播中表示是否正在同步的Extra键名 public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; + // 广播中表示同步进度消息的Extra键名 public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; + // 静态变量,保存当前执行同步的异步任务实例 private static GTaskASyncTask mSyncTask = null; + // 静态变量,保存当前同步进度消息 private static String mSyncProgress = ""; + /** + * 启动Google Tasks同步操作 + * 检查是否已有同步任务在运行,若无则创建并执行新的同步任务 + */ private void startSync() { - if (mSyncTask == null) { + if (mSyncTask == null) { // 检查是否已有同步任务在运行 + // 创建新的异步同步任务实例,传入服务上下文和完成监听器 mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { + @Override public void onComplete() { - mSyncTask = null; - sendBroadcast(""); - stopSelf(); + mSyncTask = null; // 同步完成后将任务实例置为null + sendBroadcast(""); // 发送同步完成的广播 + stopSelf(); // 停止当前服务 } }); - sendBroadcast(""); - mSyncTask.execute(); + sendBroadcast(""); // 发送开始同步的广播 + mSyncTask.execute(); // 执行异步同步任务 } } + /** + * 取消正在进行的同步操作 + * 如果有同步任务正在运行,则调用其取消方法 + */ private void cancelSync() { - if (mSyncTask != null) { - mSyncTask.cancelSync(); + if (mSyncTask != null) { // 检查是否有同步任务在运行 + mSyncTask.cancelSync(); // 调用任务的取消方法 } } + /** + * 服务创建时调用的方法 + * 初始化服务状态,将同步任务实例置为null + */ @Override public void onCreate() { - mSyncTask = null; + mSyncTask = null; // 确保服务创建时没有残留的同步任务 } + /** + * 处理启动服务的命令 + * 根据Intent中携带的动作类型,执行相应的同步操作 + * @param intent 启动服务的Intent,可能包含同步动作类型 + * @param flags 启动标志 + * @param startId 启动ID + * @return 服务启动模式,此处返回START_STICKY表示服务被终止后会自动重启 + */ @Override public int onStartCommand(Intent intent, int flags, int startId) { - Bundle bundle = intent.getExtras(); - if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { - switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { - case ACTION_START_SYNC: - startSync(); + Bundle bundle = intent.getExtras(); // 获取Intent携带的额外数据 + if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { // 检查是否包含动作类型 + switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { // 获取动作类型并处理 + case ACTION_START_SYNC: // 启动同步动作 + startSync(); // 调用启动同步方法 break; - case ACTION_CANCEL_SYNC: - cancelSync(); + case ACTION_CANCEL_SYNC: // 取消同步动作 + cancelSync(); // 调用取消同步方法 break; - default: + default: // 无效动作类型 break; } - return START_STICKY; + return START_STICKY; // 返回START_STICKY表示服务被终止后会自动重启 } - return super.onStartCommand(intent, flags, startId); + return super.onStartCommand(intent, flags, startId); // 处理默认情况 } + /** + * 系统内存不足时调用的方法 + * 取消正在进行的同步任务,释放资源 + */ @Override public void onLowMemory() { - if (mSyncTask != null) { - mSyncTask.cancelSync(); + if (mSyncTask != null) { // 检查是否有同步任务在运行 + mSyncTask.cancelSync(); // 取消同步任务以释放资源 } } + /** + * 绑定服务时调用的方法 + * 由于本服务不支持绑定,返回null + * @param intent 绑定意图 + * @return null,表示不支持绑定 + */ public IBinder onBind(Intent intent) { - return null; + return null; // 本服务不支持绑定,返回null } + /** + * 发送同步状态广播 + * 将同步进度消息封装到Intent中并发送广播 + * @param msg 同步进度消息 + */ public void sendBroadcast(String msg) { - mSyncProgress = msg; - Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); - intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); - intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); - sendBroadcast(intent); + mSyncProgress = msg; // 更新同步进度消息 + Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); // 创建广播Intent + intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); // 添加是否正在同步的状态 + intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); // 添加同步进度消息 + sendBroadcast(intent); // 发送广播 } + /** + * 静态方法:启动Google Tasks同步服务 + * 从Activity上下文启动同步服务并传递启动同步的动作类型 + * @param activity 调用此方法的Activity,提供上下文 + */ public static void startSync(Activity activity) { - GTaskManager.getInstance().setActivityContext(activity); - Intent intent = new Intent(activity, GTaskSyncService.class); - intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); - activity.startService(intent); + GTaskManager.getInstance().setActivityContext(activity); // 设置GTaskManager的Activity上下文 + Intent intent = new Intent(activity, GTaskSyncService.class); // 创建启动服务的Intent + intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); // 添加启动同步的动作类型 + activity.startService(intent); // 启动服务 } + /** + * 静态方法:取消Google Tasks同步服务 + * 从上下文启动服务并传递取消同步的动作类型 + * @param context 调用此方法的上下文 + */ public static void cancelSync(Context context) { - Intent intent = new Intent(context, GTaskSyncService.class); - intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); - context.startService(intent); + Intent intent = new Intent(context, GTaskSyncService.class); // 创建启动服务的Intent + intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); // 添加取消同步的动作类型 + context.startService(intent); // 启动服务 } + /** + * 静态方法:检查是否正在进行同步 + * @return true表示正在同步,false表示未同步 + */ public static boolean isSyncing() { - return mSyncTask != null; + return mSyncTask != null; // 通过检查同步任务实例是否存在判断是否正在同步 } + /** + * 静态方法:获取当前同步进度消息 + * @return 当前同步进度消息字符串 + */ public static String getProgressString() { - return mSyncProgress; + return mSyncProgress; // 返回当前同步进度消息 } -} +} \ No newline at end of file