|
|
|
|
@ -0,0 +1,843 @@
|
|
|
|
|
/*
|
|
|
|
|
* 版权所有 (c) 2010-2011,MiCode 开源社区 (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.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(); // 关闭游标
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ===== 阶段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); // 设置父节点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 = 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; // 找到已有文件夹
|
|
|
|
|
mGTaskHashMap.remove(gid); // 从缓存移除
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果不存在则创建新文件夹
|
|
|
|
|
if (tasklist == null) {
|
|
|
|
|
tasklist = new TaskList(); // 创建新任务列表
|
|
|
|
|
tasklist.setContentByLocalJSON(sqlNote.getContent()); // 设置内容
|
|
|
|
|
GTaskClient.getInstance().createTaskList(tasklist); // 远程创建
|
|
|
|
|
mGTaskListHashMap.put(tasklist.getGid(), tasklist); // 加入缓存
|
|
|
|
|
}
|
|
|
|
|
n = 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 */
|
|
|
|
|
try (Cursor 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失败");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 确保关闭游标
|
|
|
|
|
}
|
|
|
|
|
// 获取当前同步账号名称
|
|
|
|
|
public String getSyncAccount() {
|
|
|
|
|
// 从GTaskClient获取账号信息并返回名称
|
|
|
|
|
return GTaskClient.getInstance().getSyncAccount().name;
|
|
|
|
|
}
|
|
|
|
|
// 取消同步操作
|
|
|
|
|
public void cancelSync() {
|
|
|
|
|
mCancelled = true; // 设置取消标志
|
|
|
|
|
}
|
|
|
|
|
}
|