You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
xiaomi/src/gtask/remote/GTaskManager.java

853 lines
42 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* 版权所有 (c) 2010-2011MiCode 开源社区 (www.micode.net)
* 根据 Apache 许可证 2.0 版本("许可证")授权;
* 除非符合许可证的规定,否则不得使用本文件。
* 您可以从以下网址获取许可证副本:
* http://www.apache.org/licenses/LICENSE-2.0
* 除非适用法律要求或书面同意,本软件按"原样"分发,
* 没有任何明示或暗示的保证或条件。
* 详见许可证中规定的权限和限制。
* 这是一份标准的Apache许可证2.0版本的开源声明)
*/
// 定义Google任务远程操作包路径
package net.micode.notes.gtask.remote;
// 导入Android基础组件
import android.app.Activity; // 活动组件,提供用户交互界面
import android.content.ContentResolver; // 内容解析器用于访问ContentProvider
import android.content.ContentUris; // 用于操作ContentProvider的URI工具
import android.content.ContentValues; // 键值对集合,用于数据库操作
import android.content.Context; // 应用上下文,提供全局资源访问
import android.database.Cursor; // 数据库查询结果集
import android.os.Build; // 系统版本信息
import android.util.Log; // 日志工具类
// 导入AndroidX兼容库注解
import androidx.annotation.RequiresApi; // API版本要求注解
// 导入应用资源
import net.micode.notes.R; // 资源ID引用
// 导入笔记数据相关类
import net.micode.notes.data.Notes; // 笔记数据库常量
import net.micode.notes.data.Notes.DataColumns; // 数据表列名常量
import net.micode.notes.data.Notes.NoteColumns; // 笔记表列名常量
// 导入Google任务数据模型
import net.micode.notes.gtask.data.MetaData; // 元数据封装类
import net.micode.notes.gtask.data.Node; // 节点基类
import net.micode.notes.gtask.data.SqlNote; // SQLite笔记实体
import net.micode.notes.gtask.data.Task; // 任务项实现
import net.micode.notes.gtask.data.TaskList; // 任务列表实现
// 导入自定义异常
import net.micode.notes.gtask.exception.ActionFailureException; // 操作失败异常
import net.micode.notes.gtask.exception.NetworkFailureException; // 网络异常
// 导入工具类
import net.micode.notes.tool.DataUtils; // 数据转换工具
import net.micode.notes.tool.GTaskStringUtils; // Google任务字符串工具
// 导入JSON处理库
import org.json.JSONArray; // JSON数组处理
import org.json.JSONException; // JSON解析异常
import org.json.JSONObject; // JSON对象处理
// 导入Java集合框架
import java.util.HashMap; // 哈希映射表
import java.util.HashSet; // 哈希集合
import java.util.Iterator; // 集合迭代器
import java.util.Map; // 映射接口
import java.util.Objects; // 对象工具类
// Google任务同步管理器实现本地笔记与Google Tasks双向同步
public class GTaskManager {
// 日志标签使用类名作为TAG
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;
// 上下文相关变量
private Activity mActivity; // 用于获取认证令牌的Activity
private Context mContext; // 应用上下文
private ContentResolver mContentResolver; // 内容解析器
// 同步状态标志
private boolean mSyncing; // 是否正在同步的标志
private boolean mCancelled; // 是否已取消同步的标志
// 数据存储结构
private final HashMap<String, TaskList> mGTaskListHashMap; // Google任务列表缓存key: 任务列表ID
private final HashMap<String, Node> mGTaskHashMap; // 所有任务节点缓存key: 任务ID
private final HashMap<String, MetaData> mMetaHashMap; // 元数据缓存key: 关联任务ID
private TaskList mMetaList; // 元数据专用任务列表
// ID映射关系
private final HashSet<Long> mLocalDeleteIdMap; // 待删除的本地笔记ID集合
private final HashMap<String, Long> mGidToNid; // Google任务ID到本地笔记ID的映射
private final HashMap<Long, String> mNidToGid; // 本地笔记ID到Google任务ID的映射
// 私有构造函数(单例模式)
private GTaskManager() {
mSyncing = false; // 初始化时未在同步状态
mCancelled = false; // 初始化时未取消
mGTaskListHashMap = new HashMap<>(); // 初始化任务列表缓存
mGTaskHashMap = new HashMap<>(); // 初始化任务节点缓存
mMetaHashMap = new HashMap<>(); // 初始化元数据缓存
mMetaList = null; // 初始化元数据列表为空
mLocalDeleteIdMap = new HashSet<>(); // 初始化待删除ID集合
mGidToNid = new HashMap<>(); // 初始化GoogleID到本地ID映射
mNidToGid = new HashMap<>(); // 初始化本地ID到GoogleID映射
}
// 获取单例实例(线程安全)
public static synchronized GTaskManager getInstance() {
if (mInstance == null) { // 如果实例不存在
mInstance = new GTaskManager(); // 创建新实例
}
return mInstance; // 返回单例实例
}
// 设置Activity上下文用于认证
public synchronized void setActivityContext(Activity activity) {
mActivity = activity; // 保存Activity引用
}
// 执行同步操作(主入口方法)
@RequiresApi(api = Build.VERSION_CODES.R)
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();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
try {
// 获取Google任务客户端实例
GTaskClient client = GTaskClient.getInstance();
client.resetUpdateArray(); // 重置更新队列
// 步骤1登录Google Tasks如果未取消
if (!mCancelled) {
if (!client.login(mActivity)) { // 尝试登录
throw new NetworkFailureException("login google task failed"); // 登录失败抛出异常
}
}
// 步骤2初始化远程任务列表
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); // 更新进度
initGTaskList(); // 初始化任务列表
// 步骤3执行内容同步
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(); // 清空元数据缓存
mLocalDeleteIdMap.clear(); // 清空待删除ID集合
mGidToNid.clear(); // 清空ID映射
mNidToGid.clear(); // 清空反向ID映射
mSyncing = false; // 重置同步标志
}
// 返回最终状态(根据是否取消)
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
}
// 初始化远程任务列表
private void initGTaskList() throws NetworkFailureException {
if (mCancelled) return; // 如果已取消则直接返回
GTaskClient client = GTaskClient.getInstance(); // 获取客户端实例
try {
// 获取所有任务列表
JSONArray jsTaskLists = client.getTaskLists();
// 首先初始化元数据列表
mMetaList = null; // 重置元数据列表
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i); // 获取单个列表
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); // 获取列表ID
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); // 获取列表名称
// 检查是否是元数据专用列表
if (name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
mMetaList = new TaskList(); // 创建元数据列表
mMetaList.setContentByRemoteJSON(object); // 设置内容
// 加载该列表下的所有元数据项
JSONArray jsMetas = client.getTaskList(gid);
for (int j = 0; j < jsMetas.length(); j++) {
object = jsMetas.getJSONObject(j); // 获取元数据项
MetaData metaData = new MetaData(); // 创建元数据对象
metaData.setContentByRemoteJSON(object); // 设置内容
if (metaData.isWorthSaving()) { // 检查是否需要保存
mMetaList.addChildTask(metaData); // 添加到元数据列表
if (metaData.getGid() != null) { // 如果有有效ID
mMetaHashMap.put(metaData.getRelatedGid(), metaData); // 加入缓存
}
}
}
}
}
// 如果不存在元数据列表则创建
if (mMetaList == null) {
mMetaList = new TaskList(); // 创建新列表
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META); // 设置名称
GTaskClient.getInstance().createTaskList(mMetaList); // 在服务器创建
}
// 初始化普通任务列表
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i); // 获取单个列表
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); // 获取列表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)) {
TaskList tasklist = new TaskList(); // 创建任务列表
tasklist.setContentByRemoteJSON(object); // 设置内容
mGTaskListHashMap.put(gid, tasklist); // 加入列表缓存
mGTaskHashMap.put(gid, tasklist); // 加入节点缓存
// 加载该列表下的所有任务
JSONArray jsTasks = client.getTaskList(gid);
for (int j = 0; j < jsTasks.length(); j++) {
object = jsTasks.getJSONObject(j); // 获取单个任务
gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); // 获取任务ID
Task task = new Task(); // 创建任务对象
task.setContentByRemoteJSON(object); // 设置内容
if (task.isWorthSaving()) { // 检查是否需要保存
task.setMetaInfo(mMetaHashMap.get(gid)); // 关联元数据
tasklist.addChildTask(task); // 添加到列表
mGTaskHashMap.put(gid, task); // 加入节点缓存
}
}
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString()); // 记录JSON异常
e.printStackTrace(); // 打印堆栈轨迹
throw new ActionFailureException("initGTaskList: handing JSONObject failed"); // 抛出操作异常
}
}
// 执行内容同步(核心方法)
@RequiresApi(api = Build.VERSION_CODES.R)
private void syncContent() throws NetworkFailureException {
int syncType; // 同步类型标识
Cursor c = null; // 数据库游标
String gid; // Google任务ID
Node node; // 任务节点
mLocalDeleteIdMap.clear(); // 清空待删除集合
if (mCancelled) return; // 检查是否已取消
/* ===== 阶段1处理本地已删除的笔记 ===== */
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()) { // 遍历结果集
gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取Google ID
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)); // 记录待删除ID
}
} else {
Log.w(TAG, "failed to query trash folder"); // 记录查询失败
}
} finally {
if (c != null) {
c.close(); // 关闭游标
c = null;
}
}
/* ===== 阶段2同步文件夹 ===== */
syncFolder(); // 同步文件夹结构
/* ===== 阶段3处理数据库中的现有笔记 ===== */
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()) { // 遍历结果集
gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取Google ID
node = mGTaskHashMap.get(gid); // 查找对应节点
// 确定同步类型
if (node != null) { // 如果存在对应节点
mGTaskHashMap.remove(gid); // 从缓存移除
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); // 建立ID映射
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); // 建立反向映射
syncType = node.getSyncAction(c); // 获取同步类型
} else {
// 根据是否存在Google ID判断是本地新增还是远程删除
syncType = c.getString(SqlNote.GTASK_ID_COLUMN).trim().isEmpty() ?
Node.SYNC_ACTION_ADD_REMOTE : Node.SYNC_ACTION_DEL_LOCAL;
}
doContentSync(syncType, node, c); // 执行同步操作
}
} else {
Log.w(TAG, "failed to query existing note in database"); // 记录查询失败
}
} finally {
if (c != null) {
c.close(); // 关闭游标
c = null;
}
}
/* ===== 阶段4处理剩余的远程节点本地不存在 ===== */
for (Map.Entry<String, Node> entry : mGTaskHashMap.entrySet()) {
node = entry.getValue(); // 获取节点
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); // 执行本地添加
}
/* ===== 阶段5批量删除本地标记项 ===== */
if (!mCancelled) {
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
throw new ActionFailureException("failed to batch-delete local deleted notes"); // 删除失败抛出异常
}
}
/* ===== 阶段6刷新本地同步ID ===== */
if (!mCancelled) {
GTaskClient.getInstance().commitUpdate(); // 提交未完成的更新
refreshLocalSyncId(); // 刷新本地同步ID
}
}
// 同步文件夹方法需要Android R及以上版本
@RequiresApi(api = Build.VERSION_CODES.R)
private void syncFolder() throws NetworkFailureException {
Cursor c = null; // 数据库查询游标
String gid; // Google任务ID
Node node; // 任务节点对象
int syncType; // 同步类型标识
if (mCancelled) return; // 检查同步是否被取消
/* ===== 1. 处理根文件夹 ===== */
try {
// 查询根文件夹ID_ROOT_FOLDER
c = mContentResolver.query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, Notes.ID_ROOT_FOLDER),
SqlNote.PROJECTION_NOTE, null, null, null);
if (c != null && c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取Google ID
node = mGTaskHashMap.get(gid); // 查找对应节点
if (node != null) { // 如果远程存在对应节点
mGTaskHashMap.remove(gid); // 从缓存移除
// 建立ID映射关系
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER);
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid);
// 系统文件夹:仅在名称不匹配时更新远程
if (!node.getName().equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX +
GTaskStringUtils.FOLDER_DEFAULT)) {
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
}
} else { // 如果远程不存在
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, null, c); // 在远程创建
}
} else {
Log.w(TAG, "failed to query root folder"); // 记录查询失败
}
} finally {
if (c != null) c.close(); // 确保游标关闭
}
/* ===== 2. 处理通话记录文件夹 ===== */
try {
// 查询通话记录文件夹ID_CALL_RECORD_FOLDER
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(_id=?)", new String[]{String.valueOf(Notes.ID_CALL_RECORD_FOLDER)}, null);
if (c != null && c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取Google ID
node = mGTaskHashMap.get(gid); // 查找对应节点
if (node != null) { // 如果远程存在对应节点
mGTaskHashMap.remove(gid); // 从缓存移除
// 建立ID映射关系
mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER);
mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid);
// 系统文件夹:仅在名称不匹配时更新远程
if (!node.getName().equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX +
GTaskStringUtils.FOLDER_CALL_NOTE)) {
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
}
} else { // 如果远程不存在
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, null, c); // 在远程创建
}
} else {
Log.w(TAG, "failed to query call note folder"); // 记录查询失败
}
} finally {
if (c != null) c.close(); // 确保游标关闭
}
/* ===== 3. 处理普通本地文件夹 ===== */
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()) { // 遍历查询结果
gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取Google ID
node = mGTaskHashMap.get(gid); // 查找对应节点
// 确定同步类型
if (node != null) { // 存在对应节点
mGTaskHashMap.remove(gid); // 从缓存移除
// 建立ID映射关系
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c); // 获取同步类型
} else {
// 根据Google ID是否存在判断是本地新增还是远程删除
syncType = c.getString(SqlNote.GTASK_ID_COLUMN).trim().isEmpty() ?
Node.SYNC_ACTION_ADD_REMOTE : Node.SYNC_ACTION_DEL_LOCAL;
}
doContentSync(syncType, node, c); // 执行同步操作
}
} else {
Log.w(TAG, "failed to query existing folder"); // 记录查询失败
}
} finally {
if (c != null) c.close(); // 确保游标关闭
}
/* ===== 4. 处理远程新增的文件夹 ===== */
for (Map.Entry<String, TaskList> entry : mGTaskListHashMap.entrySet()) {
gid = entry.getKey(); // 获取Google ID
node = entry.getValue(); // 获取节点对象
// 如果缓存中仍存在该节点(说明本地不存在)
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid); // 从缓存移除
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); // 在本地创建
}
}
// 如果未被取消,提交所有未完成的更新
if (!mCancelled) GTaskClient.getInstance().commitUpdate();
}
// 执行具体同步操作(根据同步类型分发)
@RequiresApi(api = Build.VERSION_CODES.R)
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); // 删除元数据
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 记录待删除ID
break;
case Node.SYNC_ACTION_DEL_REMOTE: // 远程删除操作
// 查找关联的元数据
meta = mMetaHashMap.get(node.getGid());
if (meta != null) {
GTaskClient.getInstance().deleteNode(meta); // 删除元数据
}
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: // 冲突处理(目前简单采用远程更新)
updateRemoteNode(node, c);
break;
case Node.SYNC_ACTION_NONE: // 无操作
break;
case Node.SYNC_ACTION_ERROR: // 错误状态
default:
throw new ActionFailureException("unkown sync action type"); // 抛出异常
}
}
// 在本地添加节点
@RequiresApi(api = Build.VERSION_CODES.R)
private void addLocalNode(Node node) throws NetworkFailureException {
if (mCancelled) return; // 检查是否已取消
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); // 设置父ID为根文件夹
}
}
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)) {
note.remove(NoteColumns.ID); // 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++) {
JSONObject data = dataArray.getJSONObject(i);
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) {
data.remove(DataColumns.ID); // ID已存在则移除
}
}
}
}
} catch (JSONException e) {
Log.w(TAG, e.toString()); // 记录JSON异常
e.printStackTrace();
}
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");
throw new ActionFailureException("cannot add local node");
}
sqlNote.setParentId(parentId); // 设置父节点ID
}
// 创建本地节点
sqlNote.setGtaskId(node.getGid()); // 设置Google任务ID
sqlNote.commit(false); // 提交到数据库
// 更新ID映射关系
mGidToNid.put(node.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), node.getGid());
// 更新远程元数据
updateRemoteMeta(node.getGid(), sqlNote);
}
// 更新本地节点需要Android R及以上版本
@RequiresApi(api = Build.VERSION_CODES.R)
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return; // 如果同步已取消则直接返回
}
SqlNote sqlNote;
// 步骤1更新本地笔记
sqlNote = new SqlNote(mContext, c); // 根据游标创建SqlNote对象
sqlNote.setContent(node.getLocalJSONFromContent()); // 设置节点内容
// 步骤2设置父节点ID任务节点取父任务列表ID否则使用根文件夹ID
Long parentId = (node instanceof Task) ?
mGidToNid.get(((Task) node).getParent().getGid()) : // 获取任务的父列表本地ID
Long.valueOf(Notes.ID_ROOT_FOLDER); // 默认使用根文件夹ID
if (parentId == null) {
Log.e(TAG, "无法在本地找到任务的父ID");
throw new ActionFailureException("无法更新本地节点");
}
sqlNote.setParentId(parentId.longValue()); // 设置父节点ID
sqlNote.commit(true); // 提交修改到数据库
// 步骤3更新元数据信息
updateRemoteMeta(node.getGid(), sqlNote);
}
// 添加远程节点需要Android R及以上版本
@RequiresApi(api = Build.VERSION_CODES.R)
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return; // 如果同步已取消则直接返回
}
SqlNote sqlNote = new SqlNote(mContext, c); // 根据游标创建SqlNote对象
Node n; // 要创建的节点
// 步骤1判断节点类型并处理
if (sqlNote.isNoteType()) {
// 情况1普通笔记节点
Task task = new Task(); // 创建新任务
task.setContentByLocalJSON(sqlNote.getContent()); // 设置任务内容
// 获取父任务列表的Google ID
String parentGid = mNidToGid.get(sqlNote.getParentId());
if (parentGid == null) {
Log.e(TAG, "无法找到任务的父任务列表");
throw new ActionFailureException("无法添加远程任务");
}
// 将任务添加到父列表
Objects.requireNonNull(mGTaskListHashMap.get(parentGid)).addChildTask(task);
// 在远程创建任务
GTaskClient.getInstance().createTask(task);
n = (Node) task; // 设置返回节点
// 添加元数据
updateRemoteMeta(task.getGid(), sqlNote);
} else {
// 情况2文件夹节点
TaskList tasklist = null;
// 构建文件夹名称带MIUI前缀
String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX;
if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) {
folderName += GTaskStringUtils.FOLDER_DEFAULT; // 根文件夹
} else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER) {
folderName += GTaskStringUtils.FOLDER_CALL_NOTE; // 通话记录文件夹
} else {
folderName += sqlNote.getSnippet(); // 普通文件夹使用笔记摘要
}
// 检查是否已存在同名文件夹
for (Map.Entry<String, TaskList> entry : mGTaskListHashMap.entrySet()) {
String gid = entry.getKey();
TaskList list = entry.getValue();
if (list.getName().equals(folderName)) {
tasklist = list; // 找到已有文件夹
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid); // 从缓存移除
}
break;
}
}
// 如果不存在则创建新文件夹
if (tasklist == null) {
tasklist = new TaskList(); // 创建新任务列表
tasklist.setContentByLocalJSON(sqlNote.getContent()); // 设置内容
GTaskClient.getInstance().createTaskList(tasklist); // 远程创建
mGTaskListHashMap.put(tasklist.getGid(), tasklist); // 加入缓存
}
n = (Node) tasklist; // 设置返回节点
}
// 步骤2更新本地笔记
sqlNote.setGtaskId(n.getGid()); // 设置Google任务ID
sqlNote.commit(false); // 提交修改到数据库
sqlNote.resetLocalModified(); // 重置本地修改标记
sqlNote.commit(true); // 再次提交确保状态更新
// 步骤3更新ID映射关系
mGidToNid.put(n.getGid(), sqlNote.getId()); // Google ID → 本地ID
mNidToGid.put(sqlNote.getId(), n.getGid()); // 本地ID → Google ID
}
// 更新远程节点需要Android R及以上版本
@RequiresApi(api = Build.VERSION_CODES.R)
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return; // 如果同步已取消则直接返回
}
SqlNote sqlNote = new SqlNote(mContext, c); // 根据游标创建SqlNote对象
// 步骤1更新远程节点内容
node.setContentByLocalJSON(sqlNote.getContent()); // 设置节点内容
GTaskClient.getInstance().addUpdateNode(node); // 添加更新到队列
// 步骤2更新元数据
updateRemoteMeta(node.getGid(), sqlNote);
// 步骤3处理任务移动如果是笔记类型
if (sqlNote.isNoteType()) {
Task task = (Task) node; // 转换为任务对象
TaskList preParentList = task.getParent(); // 获取原父列表
// 获取当前父列表的Google ID
String curParentGid = mNidToGid.get(sqlNote.getParentId());
if (curParentGid == null) {
Log.e(TAG, "无法找到任务的父任务列表");
throw new ActionFailureException("无法更新远程任务");
}
TaskList curParentList = mGTaskListHashMap.get(curParentGid); // 获取当前父列表
// 如果父列表发生变化
if (preParentList != curParentList) {
preParentList.removeChildTask(task); // 从原列表移除
if (curParentList != null) {
curParentList.addChildTask(task); // 添加到新列表
}
// 在远程执行移动操作
GTaskClient.getInstance().moveTask(task, preParentList, curParentList);
}
}
// 步骤4清除本地修改标记
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) {
// 情况1元数据已存在 - 更新操作
metaData.setMeta(gid, sqlNote.getContent()); // 设置新的元数据内容
GTaskClient.getInstance().addUpdateNode(metaData); // 添加到更新队列
} else {
// 情况2元数据不存在 - 创建操作
metaData = new MetaData(); // 创建新元数据对象
metaData.setMeta(gid, sqlNote.getContent()); // 设置元数据内容
mMetaList.addChildTask(metaData); // 添加到元数据列表
mMetaHashMap.put(gid, metaData); // 加入缓存
GTaskClient.getInstance().createTask(metaData); // 在远程创建
}
}
}
// 刷新本地同步ID确保与远程一致
private void refreshLocalSyncId() throws NetworkFailureException {
if (mCancelled) {
return; // 如果同步已取消则直接返回
}
/* 步骤1重新加载远程任务列表 */
mGTaskHashMap.clear(); // 清空任务缓存
mGTaskListHashMap.clear(); // 清空任务列表缓存
mMetaHashMap.clear(); // 清空元数据缓存
initGTaskList(); // 重新初始化任务列表
/* 步骤2更新本地同步ID */
Cursor c = null;
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)
},
NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) { // 遍历查询结果
String gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取Google ID
Node node = mGTaskHashMap.get(gid); // 查找对应远程节点
if (node != null) {
mGTaskHashMap.remove(gid); // 从缓存移除
// 准备更新数据
ContentValues values = new ContentValues();
// 使用远程节点的最后修改时间作为同步ID
values.put(NoteColumns.SYNC_ID, node.getLastModified());
// 更新数据库
mContentResolver.update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
c.getLong(SqlNote.ID_COLUMN)), // 定位要更新的笔记
values, // 更新内容
null, // WHERE条件
null // WHERE参数
);
} else {
// 错误处理:本地存在但远程不存在的笔记
Log.e(TAG, "本地存在但远程找不到对应的任务");
throw new ActionFailureException("同步后仍有本地条目缺少Google ID");
}
}
} else {
Log.w(TAG, "查询本地笔记刷新同步ID失败");
}
} finally {
if (c != null) {
c.close(); // 确保关闭游标
c = null;
}
}
}
// 获取当前同步账号名称
public String getSyncAccount() {
// 从GTaskClient获取账号信息并返回名称
return GTaskClient.getInstance().getSyncAccount().name;
}
// 取消同步操作
public void cancelSync() {
mCancelled = true; // 设置取消标志
}
}