|
|
/*
|
|
|
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
|
|
|
*
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
* You may obtain a copy of the License at
|
|
|
*
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
*
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
* See the License for the specific language governing permissions and
|
|
|
* limitations under the License.
|
|
|
*/
|
|
|
|
|
|
package net.micode.notes.gtask.remote; // Google任务管理功能的包
|
|
|
|
|
|
import android.app.Activity; // 导入Activity类
|
|
|
import android.content.ContentResolver; // 用于访问内容模型的类
|
|
|
import android.content.ContentUris; // 处理内容URI的实用类
|
|
|
import android.content.ContentValues; // 用于存储一组键值对
|
|
|
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; // 便签列常量
|
|
|
import net.micode.notes.gtask.data.MetaData; // Google 任务的元数据
|
|
|
import net.micode.notes.gtask.data.Node; // 任务的节点结构
|
|
|
import net.micode.notes.gtask.data.SqlNote; // SQL便签的数据结构
|
|
|
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 任务字符串的工具类
|
|
|
|
|
|
import org.json.JSONArray; // 处理JSON数组的类
|
|
|
import org.json.JSONException; // JSON操作的异常
|
|
|
import org.json.JSONObject; // 处理JSON对象的类
|
|
|
|
|
|
import java.util.HashMap; // HashMap用于数据存储
|
|
|
import java.util.HashSet; // HashSet用于存储唯一集合
|
|
|
import java.util.Iterator; // 用于迭代集合的类
|
|
|
import java.util.Map; // Map接口
|
|
|
|
|
|
|
|
|
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; // 单例实例
|
|
|
|
|
|
private Activity mActivity; // 当前活动上下文
|
|
|
private Context mContext; // 应用上下文
|
|
|
private ContentResolver mContentResolver; // 用于访问数据的内容解析器
|
|
|
|
|
|
private boolean mSyncing; // 标记同步是否正在进行
|
|
|
private boolean mCancelled; // 标记同步是否已取消
|
|
|
|
|
|
// 用于管理Google任务和元数据的Map
|
|
|
private HashMap<String, TaskList> mGTaskListHashMap; // 存储任务列表的映射
|
|
|
private HashMap<String, Node> mGTaskHashMap; // 存储任务的映射
|
|
|
private HashMap<String, MetaData> mMetaHashMap; // 存储元数据的映射
|
|
|
|
|
|
private TaskList mMetaList; // 元任务列表
|
|
|
private HashSet<Long> mLocalDeleteIdMap; // 用于跟踪本地删除的ID集合
|
|
|
private HashMap<String, Long> mGidToNid; // Google ID到便签ID的映射
|
|
|
private HashMap<Long, String> mNidToGid; // 便签ID到Google ID的映射
|
|
|
|
|
|
// 私有构造函数以实现单例模式
|
|
|
private GTaskManager() {
|
|
|
mSyncing = false;// 初始化为未同步
|
|
|
mCancelled = false;// 初始化为未取消
|
|
|
mGTaskListHashMap = new HashMap<String, TaskList>();// 初始化任务列表映射
|
|
|
mGTaskHashMap = new HashMap<String, Node>();// 初始化任务映射
|
|
|
mMetaHashMap = new HashMap<String, MetaData>();// 初始化元数据映射
|
|
|
mMetaList = null; // 初始化元列表为null
|
|
|
mLocalDeleteIdMap = new HashSet<Long>();// 创建本地删除ID集合
|
|
|
mGidToNid = new HashMap<String, Long>();// 初始化Google到便签ID的映射
|
|
|
mNidToGid = new HashMap<Long, String>();// 初始化便签到Google ID的映射
|
|
|
}
|
|
|
|
|
|
// 获取单例访问方法
|
|
|
public static synchronized GTaskManager getInstance() {
|
|
|
if (mInstance == null) {
|
|
|
mInstance = new GTaskManager(); // 如果尚未创建,则创建一个新实例
|
|
|
}
|
|
|
return mInstance; // 返回单例实例
|
|
|
}
|
|
|
|
|
|
// 设置Activity上下文,用于获取认证令牌
|
|
|
public synchronized void setActivityContext(Activity activity) {
|
|
|
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; // 标记为未取消
|
|
|
|
|
|
// 清空任务和元数据的HashMap
|
|
|
mGTaskListHashMap.clear();
|
|
|
mGTaskHashMap.clear();
|
|
|
mMetaHashMap.clear();
|
|
|
mLocalDeleteIdMap.clear();
|
|
|
mGidToNid.clear();
|
|
|
mNidToGid.clear();
|
|
|
|
|
|
try {
|
|
|
GTaskClient client = GTaskClient.getInstance(); // 获取Google任务客户端
|
|
|
client.resetUpdateArray(); // 重置更新数组
|
|
|
|
|
|
// 登录Google任务
|
|
|
if (!mCancelled) {
|
|
|
if (!client.login(mActivity)) { // 如果登录失败
|
|
|
throw new NetworkFailureException("login google task failed"); // 抛出网络失败异常
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 从Google获取任务列表
|
|
|
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); // 发布初始化进度
|
|
|
initGTaskList(); // 初始化任务列表
|
|
|
|
|
|
// 执行内容同步工作
|
|
|
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 {
|
|
|
// 清空HashMap数据
|
|
|
mGTaskListHashMap.clear();
|
|
|
mGTaskHashMap.clear();
|
|
|
mMetaHashMap.clear();
|
|
|
mLocalDeleteIdMap.clear();
|
|
|
mGidToNid.clear();
|
|
|
mNidToGid.clear();
|
|
|
mSyncing = false; // 将同步状态标记为未同步
|
|
|
}
|
|
|
|
|
|
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; // 返回结果状态
|
|
|
}
|
|
|
|
|
|
// 初始化Google任务列表
|
|
|
private void initGTaskList() throws NetworkFailureException {
|
|
|
if (mCancelled) // 检查同步是否被取消
|
|
|
return; // 如果被取消,直接返回
|
|
|
GTaskClient client = GTaskClient.getInstance(); // 获取Google任务客户端
|
|
|
try {
|
|
|
JSONArray jsTaskLists = client.getTaskLists(); // 获取任务列表的JSON数组
|
|
|
|
|
|
// 首先初始化元数据列表
|
|
|
mMetaList = null; // 初始化元列表为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); // 获取元数据的JSON数组
|
|
|
for (int j = 0; j < jsMetas.length(); j++) { // 遍历每个元数据对象
|
|
|
object = (JSONObject) jsMetas.getJSONObject(j); // 获取元数据对象
|
|
|
MetaData metaData = new MetaData(); // 实例化元数据
|
|
|
metaData.setContentByRemoteJSON(object); // 设置元数据内容
|
|
|
if (metaData.isWorthSaving()) { // 检查元数据是否值得保存
|
|
|
mMetaList.addChildTask(metaData); // 添加到元任务列表
|
|
|
if (metaData.getGid() != null) {
|
|
|
mMetaHashMap.put(metaData.getRelatedGid(), metaData); // 将元数据ID映射到元数据对象
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 如果元数据列表不存在,则创建它
|
|
|
if (mMetaList == null) {
|
|
|
mMetaList = new TaskList(); // 创建新的任务列表
|
|
|
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META); // 设置名称
|
|
|
GTaskClient.getInstance().createTaskList(mMetaList); // 在Google任务中创建任务列表
|
|
|
}
|
|
|
// 初始化任务列表
|
|
|
for (int i = 0; i < jsTaskLists.length(); i++) { // 遍历所有任务列表
|
|
|
JSONObject object = jsTaskLists.getJSONObject(i); // 获取当前任务列表的JSONObject
|
|
|
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); // 获取任务列表的ID
|
|
|
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); // 获取任务列表的名称
|
|
|
|
|
|
// 如果任务列表名称以指定前缀开头,并且不是元文件夹
|
|
|
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 = (JSONObject) jsTasks.getJSONObject(j); // 获取当前任务的JSONObject
|
|
|
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) {// 处理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; // 任务的Google ID
|
|
|
Node node;// 节点对象
|
|
|
|
|
|
mLocalDeleteIdMap.clear(); // 清空本地删除ID集合
|
|
|
|
|
|
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()) { // 遍历查询结果
|
|
|
gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取任务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; // 将游标设为null
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 首先同步文件夹
|
|
|
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()) { // 遍历查询结果
|
|
|
gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取任务ID
|
|
|
node = mGTaskHashMap.get(gid); // 从任务映射中获取节点
|
|
|
if (node != null) { // 如果节点存在
|
|
|
mGTaskHashMap.remove(gid); // 从映射中移除该节点
|
|
|
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); // 将Google ID映射到便签ID
|
|
|
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); // 将便签ID映射到Google ID
|
|
|
syncType = node.getSyncAction(c); // 获取同步操作类型
|
|
|
} else {
|
|
|
// 检查便签ID是否为空
|
|
|
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
|
|
|
// 如果便签ID为空,标记为本地新增
|
|
|
syncType = Node.SYNC_ACTION_ADD_REMOTE;
|
|
|
} else {
|
|
|
// 否则,标记为远程删除
|
|
|
syncType = 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; // 将游标设为null
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 针对远程新增的文件夹进行同步
|
|
|
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator(); // 遍历任务列表的映射
|
|
|
while (iter.hasNext()) { // 循环迭代
|
|
|
Map.Entry<String, TaskList> entry = iter.next(); // 获取当前条目
|
|
|
gid = entry.getKey(); // 获取Google ID
|
|
|
node = entry.getValue(); // 获取任务列表对象
|
|
|
if (mGTaskHashMap.containsKey(gid)) { // 如果任务映射中存在该Google ID
|
|
|
mGTaskHashMap.remove(gid); // 从任务映射中移除该节点
|
|
|
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); // 执行本地新增同步
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// refresh local sync 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 {
|
|
|
// 使用ContentResolver发起一个查询操作
|
|
|
c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
|
|
|
Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null);
|
|
|
// ContentUris.withAppendedId方法用于给一个基础的URI追加一个ID,这里是查询Notes表中根文件夹对应的记录
|
|
|
// SqlNote.PROJECTION_NOTE可能是一个列名数组,定义了要查询哪些列
|
|
|
|
|
|
if (c!= null) {
|
|
|
// 如果查询结果集不为空
|
|
|
c.moveToNext();
|
|
|
// 将游标移动到下一条记录,通常这里假设只有一条根文件夹记录
|
|
|
|
|
|
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
|
|
|
// 从查询结果中获取名为GTASK_ID_COLUMN的列值,存储到gid变量,推测是Google Task的ID
|
|
|
|
|
|
node = mGTaskHashMap.get(gid);
|
|
|
// 尝试从mGTaskHashMap这个哈希表中,根据gid获取对应的节点对象
|
|
|
|
|
|
if (node!= null) {
|
|
|
// 如果找到了对应的节点
|
|
|
mGTaskHashMap.remove(gid);
|
|
|
// 从哈希表中移除这个节点,因为可能要做后续更新处理
|
|
|
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER);
|
|
|
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid);
|
|
|
// 更新两个映射表,建立或更新gid和Notes.ID_ROOT_FOLDER之间的双向映射关系
|
|
|
|
|
|
// 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);
|
|
|
// 如果节点名称不等于某个默认系统文件夹名称,执行更新远程数据的同步操作,传入更新动作、节点和查询游标
|
|
|
} else {
|
|
|
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
|
|
|
// 如果没找到对应的节点,执行添加远程数据的同步操作
|
|
|
}
|
|
|
} else {
|
|
|
Log.w(TAG, "failed to query root folder");
|
|
|
// 如果查询结果为空,输出一条警告日志,提示查询根文件夹失败
|
|
|
}
|
|
|
} finally {
|
|
|
if (c!= null) {
|
|
|
c.close();
|
|
|
c = null;
|
|
|
// 无论try块中是否发生异常,最终都要关闭查询游标,避免资源泄漏,并将游标变量设为null
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// for call-note folder
|
|
|
try {
|
|
|
// 开始一个try块,用于捕获可能出现的异常,通常是与数据库查询相关的异常
|
|
|
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)",
|
|
|
new String[] {
|
|
|
String.valueOf(Notes.ID_CALL_RECORD_FOLDER)
|
|
|
}, null);
|
|
|
// 使用mContentResolver.query方法进行数据库查询:
|
|
|
// Notes.CONTENT_NOTE_URI是要查询的内容URI,指向笔记相关的数据表
|
|
|
// SqlNote.PROJECTION_NOTE是一个字符串数组,指定了需要查询返回的列
|
|
|
// "(_id=?)" 是查询的筛选条件,其中的?是占位符,后续会被填充实际值
|
|
|
// new String[] { String.valueOf(Notes.ID_CALL_RECORD_FOLDER) } 为占位符提供具体的值,即通话记录文件夹的ID
|
|
|
// null表示不进行排序
|
|
|
|
|
|
if (c!= null) {
|
|
|
// 如果查询返回的Cursor(游标)不为空,说明查询成功获取到了数据
|
|
|
if (c.moveToNext()) {
|
|
|
// 将游标移动到下一个位置,因为查询结果可能有多条记录,这里假设只关注第一条
|
|
|
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
|
|
|
// 从查询结果的当前行,获取列名为GTASK_ID_COLUMN的字符串值,存入gid变量
|
|
|
node = mGTaskHashMap.get(gid);
|
|
|
// 尝试从mGTaskHashMap这个Map中,根据gid获取对应的节点对象
|
|
|
if (node!= null) {
|
|
|
// 如果在mGTaskHashMap中找到了对应的节点
|
|
|
mGTaskHashMap.remove(gid);
|
|
|
// 从mGTaskHashMap中移除该节点
|
|
|
mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER);
|
|
|
// 在mGidToNid这个Map中,添加或更新键值对,将gid映射到通话记录文件夹的ID
|
|
|
mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid);
|
|
|
// 在mNidToGid这个Map中,添加或更新键值对,将通话记录文件夹的ID映射到gid
|
|
|
// for system folder, only update remote name if
|
|
|
// necessary
|
|
|
if (!node.getName().equals(
|
|
|
GTaskStringUtils.MIUI_FOLDER_PREFFIX
|
|
|
+ GTaskStringUtils.FOLDER_CALL_NOTE))
|
|
|
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
|
|
|
// 如果节点的名称不等于某个特定的系统文件夹名称组合,就调用doContentSync方法,执行更新远程数据的同步操作,传入更新动作、节点和游标
|
|
|
} else {
|
|
|
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
|
|
|
// 如果在mGTaskHashMap中没找到对应的节点,调用doContentSync方法,执行添加远程数据的同步操作
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
Log.w(TAG, "failed to query call note folder");
|
|
|
// 如果查询返回的游标为空,说明查询通话记录文件夹失败,输出一条警告日志
|
|
|
}
|
|
|
} finally {
|
|
|
if (c!= null) {
|
|
|
c.close();
|
|
|
c = null;
|
|
|
// 无论try块中是否发生异常,最终都要关闭游标c,避免资源泄漏,关闭后将c设为null
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// for local existing folders
|
|
|
try {
|
|
|
// 开启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");
|
|
|
// 使用ContentResolver发起数据库查询:
|
|
|
// Notes.CONTENT_NOTE_URI是查询的内容URI,指向笔记相关数据表
|
|
|
// SqlNote.PROJECTION_NOTE是要查询的列的数组
|
|
|
// "(type=? AND parent_id<>?)" 是查询条件,筛选类型是文件夹且父ID不等于回收站文件夹ID的记录
|
|
|
// new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER) } 为占位符提供具体值
|
|
|
// NoteColumns.TYPE + " DESC" 按类型列倒序排序查询结果
|
|
|
|
|
|
if (c!= null) {
|
|
|
// 如果查询返回的游标c不为空,说明查询成功获取到了数据
|
|
|
while (c.moveToNext()) {
|
|
|
// 遍历游标结果集,每次移动到下一条记录
|
|
|
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
|
|
|
// 从当前记录获取GTASK_ID_COLUMN列的字符串值,存入gid变量
|
|
|
node = mGTaskHashMap.get(gid);
|
|
|
// 尝试从mGTaskHashMap中依据gid获取对应的节点对象
|
|
|
if (node!= null) {
|
|
|
// 如果找到了对应的节点
|
|
|
mGTaskHashMap.remove(gid);
|
|
|
// 从mGTaskHashMap移除该节点
|
|
|
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
|
|
|
// 在mGidToNid映射表中,添加或更新键值对,把gid映射到当前记录的ID
|
|
|
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
|
|
|
// 在mNidToGid映射表中,反向添加或更新键值对
|
|
|
syncType = node.getSyncAction(c);
|
|
|
// 获取该节点对应的同步操作类型
|
|
|
} else {
|
|
|
// 如果没找到对应的节点
|
|
|
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
|
|
|
// 如果GTASK_ID_COLUMN列的值去除空格后长度为0,判定为本地新增
|
|
|
syncType = Node.SYNC_ACTION_ADD_REMOTE;
|
|
|
} else {
|
|
|
// 否则判定为远程删除
|
|
|
syncType = Node.SYNC_ACTION_DEL_LOCAL;
|
|
|
}
|
|
|
}
|
|
|
doContentSync(syncType, node, c);
|
|
|
// 根据确定的同步类型、节点及游标,执行同步操作
|
|
|
}
|
|
|
} else {
|
|
|
Log.w(TAG, "failed to query existing folder");
|
|
|
// 如果查询返回的游标为空,意味着查询现有文件夹失败,输出警告日志
|
|
|
}
|
|
|
} finally {
|
|
|
if (c!= null) {
|
|
|
c.close();
|
|
|
c = null;
|
|
|
// 无论try块是否发生异常,最终都要关闭游标c,防止资源泄漏,关闭后将c设为null
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// for remote add folders
|
|
|
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
|
|
|
// 创建一个迭代器`iter`,用于遍历`mGTaskListHashMap`这个Map的键值对集合。`mGTaskListHashMap`的键是`String`类型,值是`TaskList`类型,
|
|
|
// 通过`entrySet().iterator()`方法获取该Map所有键值对的迭代器,以便后续逐对处理。
|
|
|
|
|
|
while (iter.hasNext()) {
|
|
|
// 只要迭代器还有下一个元素,就进入循环,持续处理`mGTaskListHashMap`中的每一个键值对
|
|
|
Map.Entry<String, TaskList> entry = iter.next();
|
|
|
// 获取迭代器当前指向的键值对,并将其存储在`entry`变量中。`entry`包含了键(`getKey()`)和值(`getValue()`)两个部分
|
|
|
gid = entry.getKey();
|
|
|
// 从当前键值对中取出键,赋值给`gid`变量,推测`gid`用于后续业务逻辑中标识某个任务相关的唯一ID
|
|
|
node = entry.getValue();
|
|
|
// 从当前键值对中取出值,赋值给`node`变量,这里`node`是`TaskList`类型,可能包含了任务列表相关的详细数据
|
|
|
if (mGTaskHashMap.containsKey(gid)) {
|
|
|
// 检查`mGTaskHashMap`这个Map是否已经包含了当前的`gid`,如果包含
|
|
|
mGTaskHashMap.remove(gid);
|
|
|
// 就从`mGTaskHashMap`中移除对应的键值对,可能是要做数据清理或者更新前的预处理
|
|
|
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
|
|
|
// 调用`doContentSync`方法执行同步操作,传入同步动作类型为`Node.SYNC_ACTION_ADD_LOCAL`,
|
|
|
// 表示执行本地添加操作,同时传入当前的`node`数据,第三个参数传入`null`,可能在这个同步方法里,
|
|
|
// 第三个参数某些情况下不需要实际值也能完成对应的同步逻辑。
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (!mCancelled)
|
|
|
GTaskClient.getInstance().commitUpdate();
|
|
|
}
|
|
|
// 如果`mCancelled`为假,即任务没有被取消,就调用`GTaskClient`单例实例的`commitUpdate`方法。
|
|
|
// 推测这个方法用于提交之前积攒的更新操作,可能是和远程服务器同步相关的更新内容。
|
|
|
|
|
|
private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException {
|
|
|
if (mCancelled) {
|
|
|
return;
|
|
|
}
|
|
|
// 如果`mCancelled`为真,也就是同步任务被取消了,直接从这个方法返回,不执行后续同步操作。
|
|
|
|
|
|
MetaData meta;
|
|
|
switch (syncType) {
|
|
|
case Node.SYNC_ACTION_ADD_LOCAL:
|
|
|
addLocalNode(node);
|
|
|
break;
|
|
|
// 当同步类型是`Node.SYNC_ACTION_ADD_LOCAL`时,调用`addLocalNode`方法,
|
|
|
// 推测这个方法用于在本地添加一个节点,将传入的`node`数据添加到本地的数据结构或者存储中。
|
|
|
|
|
|
case Node.SYNC_ACTION_ADD_REMOTE:
|
|
|
addRemoteNode(node, c);
|
|
|
break;
|
|
|
// 若同步类型为`Node.SYNC_ACTION_ADD_REMOTE`,则调用`addRemoteNode`方法,
|
|
|
// 可能是向远程服务器添加一个节点,传入的`c`游标也许包含了添加操作所需的额外数据。
|
|
|
|
|
|
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));
|
|
|
break;
|
|
|
// 对于`Node.SYNC_ACTION_DEL_LOCAL`类型:
|
|
|
// 先从`mMetaHashMap`中依据`c.getString(SqlTest.GTASK_ID_COLUMN)`获取对应的`MetaData`,
|
|
|
// 如果获取到了,就调用`GTaskClient`实例的`deleteNode`方法删除这个元数据;
|
|
|
// 同时把当前游标`c`中对应记录的ID添加到`mLocalDeleteIdMap`集合里,可能用于后续清理本地记录。
|
|
|
|
|
|
case Node.SYNC_ACTION_DEL_REMOTE:
|
|
|
meta = mMetaHashMap.get(node.getGid());
|
|
|
if (meta!= null) {
|
|
|
GTaskClient.getInstance().deleteNode(meta);
|
|
|
}
|
|
|
GTaskClient.getInstance().deleteNode(node);
|
|
|
break;
|
|
|
// 当同步类型是`Node.SYNC_ACTION_DEL_REMOTE`时:
|
|
|
// 从`mMetaHashMap`里按`node`的`gid`获取`MetaData`,有则调用`GTaskClient`删除;
|
|
|
// 接着再调用`GTaskClient`删除传入的`node`,这一步可能是彻底从远程移除相关节点。
|
|
|
|
|
|
case Node.SYNC_ACTION_UPDATE_LOCAL:
|
|
|
updateLocalNode(node, c);
|
|
|
break;
|
|
|
// 同步类型为`Node.SYNC_ACTION_UPDATE_LOCAL`时,调用`updateLocalNode`方法,
|
|
|
// 用于更新本地的节点数据,`c`游标或许提供更新所需的补充信息。
|
|
|
|
|
|
case Node.SYNC_ACTION_UPDATE_REMOTE:
|
|
|
updateRemoteNode(node, c);
|
|
|
break;
|
|
|
// 若为`Node.SYNC_ACTION_UPDATE_REMOTE`,调用`updateRemoteNode`方法,
|
|
|
// 大概是把本地修改同步更新到远程服务器,`c`游标辅助更新操作。
|
|
|
|
|
|
case Node.SYNC_ACTION_UPDATE_CONFLICT:
|
|
|
// merging both modifications maybe a good idea
|
|
|
// right now just use local update simply
|
|
|
updateRemoteNode(node, c);
|
|
|
break;
|
|
|
// 同步类型是`Node.SYNC_ACTION_UPDATE_CONFLICT`时,代码注释提到合并修改可能是个好主意,
|
|
|
// 但目前只是简单采用本地更新的方式,也就是调用`updateRemoteNode`方法。
|
|
|
|
|
|
case Node.SYNC_ACTION_NONE:
|
|
|
break;
|
|
|
// 同步类型为`Node.SYNC_ACTION_NONE`时,不执行任何操作,直接跳过。
|
|
|
|
|
|
case Node.SYNC_ACTION_ERROR:
|
|
|
default:
|
|
|
throw new ActionFailureException("unkown sync action type");
|
|
|
// 如果同步类型是`Node.SYNC_ACTION_ERROR`或者其他未处理的默认情况,抛出一个异常,
|
|
|
// 提示遇到了未知的同步动作类型。
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void addLocalNode(Node node) throws NetworkFailureException {
|
|
|
if (mCancelled) {
|
|
|
return;
|
|
|
}
|
|
|
// 如果任务已经被取消(mCancelled为true),直接从这个方法返回,不执行后续添加本地节点的操作。
|
|
|
|
|
|
SqlNote sqlNote;
|
|
|
if (node instanceof TaskList) {
|
|
|
// 如果传入的节点node是TaskList类型
|
|
|
if (node.getName().equals(
|
|
|
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) {
|
|
|
// 当节点名称等于某个特定的默认系统文件夹前缀加默认文件夹名时
|
|
|
sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER);
|
|
|
// 创建一个新的SqlNote对象,传入上下文和根文件夹ID,可能用于关联到根文件夹相关的数据结构
|
|
|
} else if (node.getName().equals(
|
|
|
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) {
|
|
|
// 当节点名称等于某个特定的通话记录文件夹前缀加通话记录文件夹名时
|
|
|
sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER);
|
|
|
// 创建一个新的SqlNote对象,传入上下文和通话记录文件夹ID,关联到通话记录相关结构
|
|
|
} else {
|
|
|
sqlNote = new SqlNote(mContext);
|
|
|
// 创建一个普通的SqlNote对象,传入上下文
|
|
|
sqlNote.setContent(node.getLocalJSONFromContent());
|
|
|
// 设置SqlNote的内容,从节点获取本地JSON格式的内容数据
|
|
|
sqlNote.setParentId(Notes.ID_ROOT_FOLDER);
|
|
|
// 设置父ID为根文件夹ID,表明该节点挂载在根文件夹下
|
|
|
}
|
|
|
} else {
|
|
|
sqlNote = new SqlNote(mContext);
|
|
|
// 如果节点不是TaskList类型,创建一个普通的SqlNote对象,传入上下文
|
|
|
JSONObject js = node.getLocalJSONFromContent();
|
|
|
// 从节点获取本地JSON格式的内容数据,存到js变量
|
|
|
try {
|
|
|
if (js.has(GTaskStringUtils.META_HEAD_NOTE)) {
|
|
|
// 如果JSON数据中有特定的元数据头部节点(GTaskStringUtils.META_HEAD_NOTE)
|
|
|
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
|
|
|
// 获取这个元数据头部节点对应的JSON对象
|
|
|
if (note.has(NoteColumns.ID)) {
|
|
|
// 如果这个JSON对象中有ID字段
|
|
|
long id = note.getLong(NoteColumns.ID);
|
|
|
// 获取ID字段的值
|
|
|
if (DataUtils.existInNoteDatabase(mContentResolver, id)) {
|
|
|
// 调用DataUtils的existInNoteDatabase方法,检查该ID是否已经存在于笔记数据库中
|
|
|
// 如果已经存在,意味着这个ID不可用,需要创建一个新的
|
|
|
note.remove(NoteColumns.ID);
|
|
|
// 移除这个ID字段,后续可能重新生成新的ID
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
if (js.has(GTaskStringUtils.META_HEAD_DATA)) {
|
|
|
// 检查名为 `js` 的 `JSONObject` 中是否存在键为 `GTaskStringUtils.META_HEAD_DATA` 的元素。
|
|
|
// `has` 方法用于判断 JSON 对象里是否有指定的键,这一步是在确认是否有特定的元数据头部数据。
|
|
|
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
|
|
|
// 如果存在上述键,就从 `js` 这个 `JSONObject` 中获取对应键的值,该值是一个 `JSONArray` 类型,
|
|
|
// 也就是包含多个 JSON 对象的数组,使用 `getJSONArray` 方法获取它并存储在 `dataArray` 变量里。
|
|
|
for (int i = 0; i < dataArray.length(); i++) {
|
|
|
// 开启一个 `for` 循环,用于遍历 `dataArray` 数组中的每一个元素,
|
|
|
// `length` 方法返回数组的长度,循环变量 `i` 从 0 开始,每次递增 1,直到小于数组长度。
|
|
|
JSONObject data = dataArray.getJSONObject(i);
|
|
|
// 从 `dataArray` 数组中获取索引为 `i` 的元素,该元素是一个 `JSONObject`,
|
|
|
// 使用 `getJSONObject` 方法获取并存储在 `data` 变量里。
|
|
|
if (data.has(DataColumns.ID)) {
|
|
|
// 检查刚获取到的 `data` 这个 `JSONObject` 里是否存在键为 `DataColumns.ID` 的元素,
|
|
|
// 这一步是在判断当前 JSON 对象中是否含有数据 ID。
|
|
|
long dataId = data.getLong(DataColumns.ID);
|
|
|
// 如果存在该键,就使用 `getLong` 方法获取对应键的值,也就是获取数据 ID,将其存储在 `dataId` 变量里。
|
|
|
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) {
|
|
|
// 调用 `DataUtils` 类的 `existInDataDatabase` 方法,传入 `mContentResolver` 和 `dataId`,
|
|
|
// 该方法用于检查指定的数据 ID 是否已经存在于数据数据库中。
|
|
|
// 如果已经存在,意味着这个数据 ID 不可用,需要创建一个新的。
|
|
|
data.remove(DataColumns.ID);
|
|
|
// 移除 `data` 这个 `JSONObject` 里名为 `DataColumns.ID` 的键值对,
|
|
|
// 这么做可能是为后续重新生成新的合适的 ID 做准备。
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|
|
|
} catch (JSONException e) { // 捕获JSONException异常,当在处理JSON数据时发生格式错误、取值错误等与JSON相关的问题,就会进入这个catch块
|
|
|
Log.w(TAG, e.toString()); // 使用Log类输出一条警告日志,记录异常的字符串表示形式,方便调试时查看出现了什么异常
|
|
|
e.printStackTrace(); // 打印异常的堆栈跟踪信息,能精准定位异常发生的位置,有助于排查代码问题
|
|
|
}
|
|
|
sqlNote.setContent(js);// 将处理后的JSON对象js设置为SqlNote的内容,这一步是为了把相关数据填充进SqlNote对象,以便后续操作
|
|
|
|
|
|
Long parentId = mGidToNid.get(((Task) node).getParent().getGid());// 尝试从mGidToNid这个映射表中,获取当前节点(先转换为Task类型)的父节点的Gid对应的本地ID,存储在parentId变量
|
|
|
if (parentId == null) {// 如果没能从映射表中获取到对应的本地ID,说明出现了问题
|
|
|
Log.e(TAG, "cannot find task's parent id locally");// 使用Log类输出一条错误日志,提示无法在本地找到任务的父ID
|
|
|
throw new ActionFailureException("cannot add local node"); // 抛出一个ActionFailureException异常,表明由于找不到父ID,没办法添加本地节点
|
|
|
}
|
|
|
sqlNote.setParentId(parentId.longValue());// 将获取到的父ID值设置为SqlNote对象的父ID,完成SqlNote对象在本地层级关系上的关联
|
|
|
}
|
|
|
|
|
|
// create the local node
|
|
|
sqlNote.setGtaskId(node.getGid());// 设置SqlNote对象的GtaskId属性为当前节点的Gid,建立两者之间的标识关联
|
|
|
sqlNote.commit(false);// 调用SqlNote的commit方法提交更改,false参数可能表示某种提交模式,例如不强制立即持久化等
|
|
|
|
|
|
// update gid-nid mapping
|
|
|
mGidToNid.put(node.getGid(), sqlNote.getId());// 在mGidToNid映射表中,添加或更新一个键值对,将节点的Gid映射到SqlNote的ID,保持数据一致性
|
|
|
mNidToGid.put(sqlNote.getId(), node.getGid());// 同时更新反向映射,在mNidToGid映射表中把SqlNote的ID映射到节点的Gid
|
|
|
|
|
|
// update meta
|
|
|
updateRemoteMeta(node.getGid(), sqlNote);// 调用updateRemoteMeta方法,传入节点的Gid和SqlNote对象,更新远程元数据,具体更新逻辑要看该方法的实现
|
|
|
}
|
|
|
|
|
|
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
|
|
|
if (mCancelled) {
|
|
|
return;
|
|
|
} // 如果任务已经被取消(mCancelled为true),直接从这个方法返回,不执行后续更新本地节点的操作
|
|
|
|
|
|
SqlNote sqlNote;// 声明一个SqlNote类型的变量sqlNote,后续用于更新本地笔记相关操作
|
|
|
// update the note locally
|
|
|
sqlNote = new SqlNote(mContext, c); //创建一个新的SqlNote对象,传入当前上下文mContext和游标c,
|
|
|
// 推测是利用游标c中的数据来初始化这个SqlNote,比如获取现有笔记的一些属性。
|
|
|
sqlNote.setContent(node.getLocalJSONFromContent()); // 设置SqlNote的内容,从传入的节点node获取本地JSON格式的内容,更新笔记的主体数据。
|
|
|
|
|
|
Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid())
|
|
|
: new Long(Notes.ID_ROOT_FOLDER);// 根据节点类型确定父ID:
|
|
|
// 如果节点node是Task类型,就尝试从mGidToNid映射表中获取其父节点的Gid对应的本地ID;
|
|
|
// 如果不是Task类型,就默认设置父ID为根文件夹ID(Notes.ID_ROOT_FOLDER),存储在parentId变量。
|
|
|
if (parentId == null) {
|
|
|
Log.e(TAG, "cannot find task's parent id locally");
|
|
|
throw new ActionFailureException("cannot update local node");
|
|
|
} // 如果没能获取到父ID(parentId为null),输出错误日志提示找不到本地父ID,
|
|
|
// 并抛出ActionFailureException异常,表明无法更新本地节点。
|
|
|
sqlNote.setParentId(parentId.longValue());// 将获取到的父ID值设置为SqlNote对象的父ID,完成层级关系上的更新
|
|
|
sqlNote.commit(true);// 调用SqlNote的commit方法提交更改,参数true可能表示一种特定的提交模式,
|
|
|
// 例如立即持久化更新、同步更新到存储等。
|
|
|
|
|
|
// update meta info
|
|
|
updateRemoteMeta(node.getGid(), sqlNote);
|
|
|
// 调用updateRemoteMeta方法,传入节点的Gid和更新后的SqlNote对象,更新远程元数据,
|
|
|
// 具体更新逻辑依赖于updateRemoteMeta方法的内部实现。
|
|
|
|
|
|
// update meta info
|
|
|
updateRemoteMeta(node.getGid(), sqlNote);
|
|
|
}
|
|
|
// 调用 `updateRemoteMeta` 方法,传入节点的 `Gid` 和 `sqlNote` 对象,推测这个方法用于更新远程的元数据,
|
|
|
// 保持本地数据与远程数据在元数据层面的一致性,不过这里没展示 `updateRemoteMeta` 方法的具体实现。
|
|
|
|
|
|
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
|
|
|
if (mCancelled) {
|
|
|
return;
|
|
|
}
|
|
|
// 如果 `mCancelled` 为 `true`,意味着任务被取消了,直接返回,不执行后续添加远程节点的操作。
|
|
|
|
|
|
SqlNote sqlNote = new SqlNote(mContext, c);
|
|
|
// 创建一个新的 `SqlNote` 对象,传入当前上下文 `mContext` 和游标 `c`,
|
|
|
// 很可能利用游标中的数据来初始化这个 `SqlNote`,像是获取已有笔记相关属性用于后续远程添加操作。
|
|
|
|
|
|
Node n;
|
|
|
|
|
|
// update remotely
|
|
|
if (sqlNote.isNoteType()) {
|
|
|
// 如果 `sqlNote` 是笔记类型
|
|
|
Task task = new Task();
|
|
|
// 创建一个新的 `Task` 对象
|
|
|
task.setContentByLocalJSON(sqlNote.getContent());
|
|
|
// 使用 `sqlNote` 的内容,通过 `setContentByLocalJSON` 方法设置 `Task` 的内容,
|
|
|
// 让新创建的任务包含本地笔记中的数据。
|
|
|
|
|
|
String parentGid = mNidToGid.get(sqlNote.getParentId());
|
|
|
// 尝试从 `mNidToGid` 映射表中获取 `sqlNote` 的父ID对应的 `Gid`,存储在 `parentGid` 变量,
|
|
|
// 这个 `Gid` 大概率是用来标识远程的父任务列表。
|
|
|
if (parentGid == null) {
|
|
|
Log.e(TAG, "cannot find task's parent tasklist");
|
|
|
// 如果没获取到对应的 `Gid`,输出错误日志,提示找不到任务的父任务列表。
|
|
|
throw new ActionFailureException("cannot add remote task");
|
|
|
// 抛出异常,表明没办法添加远程任务。
|
|
|
}
|
|
|
mGTaskListHashMap.get(parentGid).addChildTask(task);
|
|
|
// 从 `mGTaskListHashMap` 中根据 `parentGid` 获取对应的父任务列表,
|
|
|
// 并把新创建的 `task` 添加到该父任务列表的子任务中。
|
|
|
|
|
|
GTaskClient.getInstance().createTask(task);
|
|
|
// 调用 `GTaskClient` 单例实例的 `createTask` 方法,将新创建的 `task` 发送到远程创建任务。
|
|
|
n = (Node) task;
|
|
|
// 把 `task` 转换为 `Node` 类型,赋值给 `n`,方便后续统一处理。
|
|
|
|
|
|
// add meta
|
|
|
updateRemoteMeta(task.getGid(), sqlNote);
|
|
|
// 调用 `updateRemoteMeta` 方法,传入任务的 `Gid` 和 `sqlNote`,更新远程元数据,
|
|
|
// 让远程任务的元数据和本地数据关联匹配。
|
|
|
} else {
|
|
|
// 如果前面的条件不满足(即sqlNote不是笔记类型),进入此分支,处理其他类型的节点,推测是任务列表相关逻辑
|
|
|
TaskList tasklist = null;
|
|
|
// 初始化一个TaskList类型的变量tasklist,用于后续存储找到的任务列表对象,初始值设为null
|
|
|
|
|
|
// we need to skip folder if it has already existed
|
|
|
String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX;
|
|
|
// 定义一个字符串变量folderName,先初始化为特定的系统文件夹前缀,用于后续拼接完整的文件夹名称
|
|
|
if (sqlNote.getId() == Notes.ID_ROOT_FOLDER)
|
|
|
folderName += GTaskStringUtils.FOLDER_DEFAULT;
|
|
|
// 如果sqlNote的ID是根文件夹ID,就在前缀后拼接默认的文件夹名称
|
|
|
else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER)
|
|
|
folderName += GTaskStringUtils.FOLDER_CALL_NOTE;
|
|
|
// 如果是通话记录文件夹ID,就在前缀后拼接通话记录相关的文件夹名称
|
|
|
else
|
|
|
folderName += sqlNote.getSnippet();
|
|
|
// 其他情况,就在前缀后拼接sqlNote的片段内容,形成完整的文件夹名称
|
|
|
|
|
|
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
|
|
|
// 获取mGTaskListHashMap这个Map的键值对集合的迭代器,mGTaskListHashMap存储着任务列表相关的映射关系,
|
|
|
// 迭代器用于遍历这个集合中的每一个键值对
|
|
|
while (iter.hasNext()) {
|
|
|
// 只要迭代器还有下一个元素,就进入循环
|
|
|
Map.Entry<String, TaskList> entry = iter.next();
|
|
|
// 获取迭代器当前指向的键值对,存储在entry变量中,其中entry.getKey()是键,entry.getValue()是值
|
|
|
String gid = entry.getKey();
|
|
|
// 从entry中取出键,赋值给gid变量,这个gid用于标识任务列表
|
|
|
TaskList list = entry.getValue();
|
|
|
// 从entry中取出值,赋值给list变量,得到对应的任务列表对象
|
|
|
|
|
|
if (list.getName().equals(folderName)) {
|
|
|
// 如果当前任务列表的名称与前面拼接好的folderName相等
|
|
|
tasklist = list;
|
|
|
// 就把这个任务列表赋值给tasklist变量,表示找到了对应的任务列表
|
|
|
if (mGTaskHashMap.containsKey(gid)) {
|
|
|
mGTaskHashMap.remove(gid);
|
|
|
}
|
|
|
// 如果mGTaskHashMap这个Map中包含当前的gid,就移除对应的键值对,可能是做一些清理操作
|
|
|
break;
|
|
|
// 找到匹配的任务列表后,就跳出循环,不再继续遍历
|
|
|
}
|
|
|
}
|
|
|
// no match we can add now
|
|
|
if (tasklist == null) { // 如果前面遍历查找后,tasklist依旧是null,说明没有找到对应的任务列表
|
|
|
tasklist = new TaskList();// 那就创建一个新的TaskList对象
|
|
|
tasklist.setContentByLocalJSON(sqlNote.getContent()); // 使用sqlNote中的内容,通过setContentByLocalJSON方法来设置新任务列表的内容,让其携带本地相关数据
|
|
|
GTaskClient.getInstance().createTaskList(tasklist);// 调用GTaskClient单例实例的createTaskList方法,将新创建的任务列表发送到远程端创建
|
|
|
mGTaskListHashMap.put(tasklist.getGid(), tasklist);// 把新创建的任务列表,以其Gid作为键,任务列表对象本身作为值,存入mGTaskListHashMap中
|
|
|
}
|
|
|
n = (Node) tasklist;// 将tasklist转换为Node类型,赋值给n,方便后续统一以Node类型来处理该对象
|
|
|
}
|
|
|
|
|
|
// update local note
|
|
|
sqlNote.setGtaskId(n.getGid());// 设置sqlNote的GtaskId为n的Gid,建立本地sqlNote与远程对应对象的关联标识
|
|
|
sqlNote.commit(false);// 先以false参数调用sqlNote的commit方法,可能是执行一种非强制立即持久化的提交操作
|
|
|
sqlNote.resetLocalModified();// 重置sqlNote的本地修改状态,可能是清除一些本地修改标识
|
|
|
sqlNote.commit(true);// 再以true参数调用commit方法,也许这次是执行强制立即持久化等更严格的提交操作,确保数据更新到位
|
|
|
|
|
|
// gid-id mapping
|
|
|
mGidToNid.put(n.getGid(), sqlNote.getId());// 在mGidToNid映射表中,添加或更新键值对,把n的Gid映射到sqlNote的ID
|
|
|
mNidToGid.put(sqlNote.getId(), n.getGid());// 在mNidToGid映射表中,反向添加或更新键值对,维持双向映射关系
|
|
|
}
|
|
|
|
|
|
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
|
|
|
if (mCancelled) {
|
|
|
return;
|
|
|
}
|
|
|
// 如果任务被取消(mCancelled为true),直接返回,不执行后续更新远程节点的操作。
|
|
|
|
|
|
SqlNote sqlNote = new SqlNote(mContext, c);
|
|
|
// 创建一个新的SqlNote对象,传入当前上下文mContext和游标c,利用游标数据初始化该对象。
|
|
|
|
|
|
// update remotely
|
|
|
node.setContentByLocalJSON(sqlNote.getContent());
|
|
|
// 使用sqlNote中的内容,通过setContentByLocalJSON方法更新节点的内容,使远程节点与本地数据同步。
|
|
|
GTaskClient.getInstance().addUpdateNode(node);
|
|
|
// 调用GTaskClient单例实例的addUpdateNode方法,将更新后的节点发送到远程端进行更新操作。
|
|
|
|
|
|
// update meta
|
|
|
updateRemoteMeta(node.getGid(), sqlNote);
|
|
|
// 调用updateRemoteMeta方法,传入节点的Gid和sqlNote,更新远程元数据,保持数据一致性。
|
|
|
|
|
|
// move task if necessary
|
|
|
if (sqlNote.isNoteType()) {
|
|
|
// 如果sqlNote是笔记类型,意味着当前处理的是任务相关节点
|
|
|
Task task = (Task) node;
|
|
|
// 将节点转换为Task类型,赋值给task变量
|
|
|
TaskList preParentList = task.getParent();
|
|
|
// 获取任务原本的父任务列表
|
|
|
|
|
|
String curParentGid = mNidToGid.get(sqlNote.getParentId());
|
|
|
// 从mNidToGid映射表中获取当前sqlNote的父ID对应的Gid,用于找到新的父任务列表
|
|
|
if (curParentGid == null) {
|
|
|
Log.e(TAG, "cannot find task's parent tasklist");
|
|
|
// 如果获取失败,输出错误日志
|
|
|
throw new ActionFailureException("cannot update remote task");
|
|
|
// 抛出异常,表明无法更新远程任务
|
|
|
}
|
|
|
TaskList curParentList = mGTaskListHashMap.get(curParentGid);
|
|
|
// 根据获取到的Gid,从mGTaskListHashMap中拿到新的父任务列表
|
|
|
|
|
|
if (preParentList!= curParentList) {
|
|
|
// 如果原本的父任务列表和新的父任务列表不一样
|
|
|
preParentList.removeChildTask(task);
|
|
|
// 先从原本的父任务列表移除该任务
|
|
|
curParentList.addChildTask(task);
|
|
|
// 再把任务添加到新的父任务列表中
|
|
|
GTaskClient.getInstance().moveTask(task, preParentList, curParentList);
|
|
|
// 调用GTaskClient实例的moveTask方法,在远程端执行移动任务的操作
|
|
|
}
|
|
|
}
|
|
|
// clear local modified flag
|
|
|
sqlNote.resetLocalModified();// 调用 `sqlNote` 的 `resetLocalModified` 方法,推测这个方法用于重置 `sqlNote` 的本地修改状态标识,
|
|
|
// 比如清除那些标记着数据有本地改动的标记位,让它回到未修改的初始状态。
|
|
|
sqlNote.commit(true);// 接着调用 `sqlNote` 的 `commit` 方法并传入 `true`,可能 `true` 代表着一种强制立即持久化更新的模式,
|
|
|
// 确保之前对 `sqlNote` 所做的更改,包括重置修改状态后的相关数据变动,都能切实保存下来。
|
|
|
}
|
|
|
|
|
|
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException {
|
|
|
if (sqlNote != null && sqlNote.isNoteType()) { // 首先检查 `sqlNote` 是否不为空,并且是否是笔记类型,如果这两个条件都满足,才执行后续更新远程元数据的操作
|
|
|
MetaData metaData = mMetaHashMap.get(gid); // 尝试从 `mMetaHashMap` 这个映射表中,依据 `gid` 获取对应的 `MetaData` 对象,这个对象大概率与远程元数据相关
|
|
|
if (metaData != null) { // 如果获取到了对应的 `MetaData` 对象
|
|
|
metaData.setMeta(gid, sqlNote.getContent());// 调用该 `MetaData` 对象的 `setMeta` 方法,传入 `gid` 和 `sqlNote` 的内容,更新元数据内容
|
|
|
GTaskClient.getInstance().addUpdateNode(metaData);// 调用 `GTaskClient` 单例实例的 `addUpdateNode` 方法,将更新后的 `MetaData` 发送到远程,更新远程节点
|
|
|
} else { // 如果没有从 `mMetaHashMap` 中获取到对应的 `MetaData` 对象
|
|
|
metaData = new MetaData();// 创建一个新的 `MetaData` 对象
|
|
|
metaData.setMeta(gid, sqlNote.getContent());// 使用 `setMeta` 方法初始化新创建的 `MetaData`,设置其元数据内容
|
|
|
mMetaList.addChildTask(metaData);// 将新创建的 `MetaData` 作为子任务添加到 `mMetaList` 中,推测 `mMetaList` 是一个任务列表相关的数据结构
|
|
|
mMetaHashMap.put(gid, metaData);// 在 `mMetaHashMap` 映射表中,添加新的键值对,把 `gid` 映射到新创建的 `MetaData`,方便后续查找
|
|
|
GTaskClient.getInstance().createTask(metaData); // 调用 `GTaskClient` 单例实例的 `createTask` 方法,将新创建的 `MetaData` 发送到远程创建
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void refreshLocalSyncId() throws NetworkFailureException {
|
|
|
if (mCancelled) {
|
|
|
return;
|
|
|
}
|
|
|
// 如果任务已经被取消(mCancelled为true),直接从这个方法返回,不执行后续操作。
|
|
|
|
|
|
// get the latest gtask list
|
|
|
mGTaskHashMap.clear();
|
|
|
mGTaskListHashMap.clear();
|
|
|
mMetaHashMap.clear();
|
|
|
initGTaskList();
|
|
|
// 清空mGTaskHashMap、mGTaskListHashMap和mMetaHashMap这几个哈希表,目的是清除旧数据。
|
|
|
// 然后调用initGTaskList方法,推测这个方法用于初始化GTask相关的列表,获取最新的任务相关数据。
|
|
|
|
|
|
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");
|
|
|
// 使用ContentResolver发起一个数据库查询:
|
|
|
// Notes.CONTENT_NOTE_URI是要查询的内容URI,指向笔记相关数据表。
|
|
|
// SqlNote.PROJECTION_NOTE是要查询的列的数组。
|
|
|
// "(type<>? AND parent_id<>?)"是查询条件,筛选类型不是系统类型且父ID不等于回收站文件夹ID的记录。
|
|
|
// new String[] { String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) } 为占位符提供具体值。
|
|
|
// NoteColumns.TYPE + " DESC" 按类型列倒序排序查询结果。
|
|
|
|
|
|
if (c!= null) {
|
|
|
while (c.moveToNext()) {
|
|
|
// 如果查询返回的游标c不为空,就遍历游标结果集,每次移动到下一条记录。
|
|
|
String gid = c.getString(SqlNote.GTASK_ID_COLUMN);
|
|
|
// 从当前记录获取GTASK_ID_COLUMN列的字符串值,存入gid变量。
|
|
|
Node node = mGTaskHashMap.get(gid);
|
|
|
// 尝试从mGTaskHashMap中依据gid获取对应的节点对象。
|
|
|
if (node!= null) {
|
|
|
mGTaskHashMap.remove(gid);
|
|
|
// 如果找到了对应的节点,就从mGTaskHashMap移除该节点。
|
|
|
ContentValues values = new ContentValues();
|
|
|
values.put(NoteColumns.SYNC_ID, node.getLastModified());
|
|
|
// 创建一个ContentValues对象,用于存放要更新的数据,这里把节点的最后修改时间存入SYNC_ID列。
|
|
|
mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
|
|
|
c.getLong(SqlNote.ID_COLUMN)), values, null, null);
|
|
|
// 使用ContentResolver更新数据库中对应记录,依据记录ID更新指定列的值。
|
|
|
} else {
|
|
|
Log.e(TAG, "something is missed");
|
|
|
throw new ActionFailureException(
|
|
|
"some local items don't have gid after sync");
|
|
|
// 如果没找到对应的节点,输出错误日志,提示有遗漏情况,并抛出异常,说明同步后一些本地项目没有Gid。
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
Log.w(TAG, "failed to query local note to refresh sync id");
|
|
|
// 如果前面查询本地笔记以刷新同步ID的操作失败(即查询返回的游标c为空),
|
|
|
// 输出一条警告日志,提示无法查询本地笔记来刷新同步ID。
|
|
|
}
|
|
|
} finally {
|
|
|
if (c!= null) {
|
|
|
c.close();
|
|
|
c = null;
|
|
|
}
|
|
|
// 无论try块中的查询操作是否发生异常,最终都要关闭游标c,避免资源泄漏,
|
|
|
// 关闭后将c赋值为null,确保它不再持有无效的资源引用。
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public String getSyncAccount() {
|
|
|
return GTaskClient.getInstance().getSyncAccount().name;
|
|
|
// 定义一个公共方法getSyncAccount,返回GTaskClient单例实例的getSyncAccount方法获取到的账户名。
|
|
|
// 推测这个账户与同步操作相关,用于标识当前执行同步任务的账户。
|
|
|
}
|
|
|
|
|
|
public void cancelSync() {
|
|
|
mCancelled = true;
|
|
|
// 定义一个公共方法cancelSync,将成员变量mCancelled设为true,
|
|
|
// 用于标记同步任务被取消,其他依赖这个状态的方法会据此做出相应处理。
|
|
|
}
|
|
|
}
|