From 19aa954d801d7c0746ae407440b2eebe2fd9f20d Mon Sep 17 00:00:00 2001 From: p3o5nagmu <1329414441@qq.com> Date: Tue, 31 Dec 2024 01:44:10 +0800 Subject: [PATCH 01/13] ADD file via upload --- Contact.java | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 Contact.java diff --git a/Contact.java b/Contact.java new file mode 100644 index 0000000..d97ac5d --- /dev/null +++ b/Contact.java @@ -0,0 +1,73 @@ +/* + * 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.data; + +import android.content.Context; +import android.database.Cursor; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Data; +import android.telephony.PhoneNumberUtils; +import android.util.Log; + +import java.util.HashMap; + +public class Contact { + private static HashMap sContactCache; + private static final String TAG = "Contact"; + + private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + + " AND " + Data.RAW_CONTACT_ID + " IN " + + "(SELECT raw_contact_id " + + " FROM phone_lookup" + + " WHERE min_match = '+')"; + + public static String getContact(Context context, String phoneNumber) { + if(sContactCache == null) { + sContactCache = new HashMap(); + } + + if(sContactCache.containsKey(phoneNumber)) { + return sContactCache.get(phoneNumber); + } + + String selection = CALLER_ID_SELECTION.replace("+", + PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + Cursor cursor = context.getContentResolver().query( + Data.CONTENT_URI, + new String [] { Phone.DISPLAY_NAME }, + selection, + new String[] { phoneNumber }, + null); + + if (cursor != null && cursor.moveToFirst()) { + try { + String name = cursor.getString(0); + sContactCache.put(phoneNumber, name); + return name; + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, " Cursor get string error " + e.toString()); + return null; + } finally { + cursor.close(); + } + } else { + Log.d(TAG, "No contact matched with number:" + phoneNumber); + return null; + } + } +} -- 2.34.1 From b04237380eb30b7f2cfac0839d356273b90fa58b Mon Sep 17 00:00:00 2001 From: p3o5nagmu <1329414441@qq.com> Date: Tue, 31 Dec 2024 03:24:57 +0800 Subject: [PATCH 02/13] ADD file via upload --- Contact1.java | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 Contact1.java diff --git a/Contact1.java b/Contact1.java new file mode 100644 index 0000000..9f7a8b0 --- /dev/null +++ b/Contact1.java @@ -0,0 +1,92 @@ +/* + * 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.data; + +import android.content.Context; +import android.database.Cursor; +import // 引入安卓联系人契约中关于电话号码相关的数据类型,用于后续查询等操作 +android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Data; +import android.telephony.PhoneNumberUtils; +import android.util.Log; + +import java.util.HashMap; + +// Contact类,主要用于根据电话号码获取对应的联系人姓名相关操作 +public class Contact { + // 用于缓存联系人信息的哈希表,以电话号码为键,联系人姓名为值,目的是避免重复查询数据库获取相同联系人信息 + private static HashMap sContactCache; + // 用于日志记录的标签,方便在Logcat中识别该类输出的日志信息 + private static final String TAG = "Contact"; + + // 构建查询联系人的条件语句字符串,用于在联系人数据库中筛选出符合条件的记录 + // 这个条件语句主要是根据电话号码匹配联系人,并且限定了数据类型等条件 + private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + + " AND " + Data.RAW_CONTACT_ID + " IN " + + "(SELECT raw_contact_id " + + " FROM phone_lookup" + + " WHERE min_match = '+')"; + + // 根据给定的上下文(通常是Activity等组件的上下文环境)和电话号码获取对应的联系人姓名 + public static String getContact(Context context, String phoneNumber) { + // 如果联系人缓存为空,则创建一个新的HashMap用于缓存联系人信息 + if (sContactCache == null) { + sContactCache = new HashMap(); + } + + // 先检查缓存中是否已经存在该电话号码对应的联系人姓名,如果有则直接返回缓存中的姓名,避免重复查询数据库 + if (sContactCache.containsKey(phoneNumber)) { + return sContactCache.get(phoneNumber); + } + + // 根据传入的电话号码替换查询条件语句中的占位符(这里的'+'),将电话号码转换为适合查询的格式 + String selection = CALLER_ID_SELECTION.replace("+", + PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + // 通过上下文的内容解析器(ContentResolver)发起一个数据库查询操作 + // 查询的是联系人数据的通用URI,指定要获取的列(这里只获取联系人的显示姓名),传入构建好的查询条件以及对应的参数(电话号码),最后一个参数null表示按默认顺序排序 + Cursor cursor = context.getContentResolver().query( + Data.CONTENT_URI, + new String[]{Phone.DISPLAY_NAME}, + selection, + new String[]{phoneNumber}, + null); + + // 如果查询结果游标不为空且游标能移动到第一条记录(表示查询到了符合条件的数据) + if (cursor!= null && cursor.moveToFirst()) { + try { + // 从游标中获取第一列的数据,也就是联系人的姓名(因为前面查询只指定了获取显示姓名这一列) + String name = cursor.getString(0); + // 将获取到的联系人姓名存入缓存中,方便下次查询相同电话号码时直接使用 + sContactCache.put(phoneNumber, name); + return name; + } catch (IndexOutOfBoundsException e) { + // 如果在获取游标数据时发生越界异常,记录错误日志并返回null + Log.e(TAG, " Cursor get string error " + e.toString()); + return null; + } finally { + // 无论是否发生异常,都要关闭游标,释放相关资源 + cursor.close(); + } + } else { + // 如果没有查询到匹配的联系人,记录一条调试日志,并返回null + Log.d(TAG, "No contact matched with number:" + phoneNumber); + return null; + } + } +} \ No newline at end of file -- 2.34.1 From 685ab78271fd35e6c85a6ef22ac3a825919218c8 Mon Sep 17 00:00:00 2001 From: p3o5nagmu <1329414441@qq.com> Date: Tue, 31 Dec 2024 03:25:13 +0800 Subject: [PATCH 03/13] ADD file via upload --- MetaData.java | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 MetaData.java diff --git a/MetaData.java b/MetaData.java new file mode 100644 index 0000000..3e6b652 --- /dev/null +++ b/MetaData.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.gtask.data; + +import android.database.Cursor; +import android.util.Log; + +import net.micode.notes.tool.GTaskStringUtils; + +import org.json.JSONException; +import org.json.JSONObject; + +// MetaData类继承自Task类,从命名来看可能是用于处理与任务相关的元数据信息,比如关联的特定标识等 +public class MetaData extends Task { + // 定义一个用于日志记录的标签,使用类的简单名称(即不包含包名的类名),方便在查看日志时识别该类相关的输出信息 + private final static String TAG = MetaData.class.getSimpleName(); + + // 用于存储相关的GID(可能是某种全局唯一标识符,具体取决于业务逻辑),初始化为null + private String mRelatedGid = null; + + // 设置元数据的方法,接收一个GID(可能用于关联其他对象或任务)和一个JSON对象(包含具体的元数据信息)作为参数 + public void setMeta(String gid, JSONObject metaInfo) { + try { + // 尝试将给定的GID放入JSON对象中,对应的键由GTaskStringUtils.META_HEAD_GTASK_ID指定,可能用于标记该元数据关联的具体任务ID + metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); + } catch (JSONException e) { + // 如果在向JSON对象中放入数据时出现异常,记录错误日志,提示设置相关GID失败 + Log.e(TAG, "failed to put related gid"); + } + // 将处理后的JSON对象转换为字符串,并设置为该对象的笔记内容(这里假设Task类中有对应的setNotes方法用于设置笔记相关内容) + setNotes(metaInfo.toString()); + // 设置该对象的名称,名称由GTaskStringUtils.META_NOTE_NAME指定,可能是一个固定的用于标识元数据类型的名称 + setName(GTaskStringUtils.META_NOTE_NAME); + } + + // 获取关联的GID的方法,外部可以通过调用此方法获取之前设置的相关标识符 + public String getRelatedGid() { + return mRelatedGid; + } + + // 重写自Task类的方法,用于判断该元数据对象是否值得保存,这里的判断依据是看其笔记内容是否为null,若不为null则认为值得保存 + @Override + public boolean isWorthSaving() { + return getNotes()!= null; + } + + // 重写自Task类的方法,用于根据远程的JSON数据设置该对象的内容,并在这个过程中提取相关的GID信息 + @Override + public void setContentByRemoteJSON(JSONObject js) { + super.setContentByRemoteJSON(js); + if (getNotes()!= null) { + try { + // 将获取到的笔记内容字符串转换为JSON对象,以便从中提取相关信息 + JSONObject metaInfo = new JSONObject(getNotes().trim()); + // 从JSON对象中获取关联的GID字符串,并赋值给成员变量mRelatedGid + mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); + } catch (JSONException e) { + // 如果在解析JSON获取GID时出现异常,记录警告日志,并将mRelatedGid设置为null + Log.w(TAG, "failed to get related gid"); + mRelatedGid = null; + } + } + } + + // 重写自Task类的方法,这里明确抛出异常表示该方法不应该被调用,可能在业务逻辑中该类对于通过本地JSON设置内容的操作是不支持的 + @Override + public void setContentByLocalJSON(JSONObject js) { + // this function should not be called + throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); + } + + // 重写自Task类的方法,同样抛出异常表示该方法不应该被调用,可能该类不需要从内容生成本地JSON这种操作,或者这种操作不符合其业务逻辑 + @Override + public JSONObject getLocalJSONFromContent() { + throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); + } + + // 重写自Task类的方法,再次抛出异常表示该方法不应该被调用,可能在数据同步相关的操作中,该类不需要执行获取同步动作的这个逻辑,具体取决于整体的业务设计 + @Override + public int getSyncAction(Cursor c) { + throw new IllegalAccessError("MetaData:getSyncAction should not be called"); + } +} \ No newline at end of file -- 2.34.1 From 2dc4470379a1f2a6d0fdc8a33ee1e8ebade43a46 Mon Sep 17 00:00:00 2001 From: p3o5nagmu <1329414441@qq.com> Date: Tue, 31 Dec 2024 03:25:52 +0800 Subject: [PATCH 04/13] ADD file via upload --- Node.java | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 Node.java diff --git a/Node.java b/Node.java new file mode 100644 index 0000000..746409c --- /dev/null +++ b/Node.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.gtask.data; + +import android.database.Cursor; + +import org.json.JSONObject; + +// 抽象类Node,可能作为数据节点相关操作的基础类,定义了一些通用规范和抽象方法供子类实现 +public abstract class Node { + // 同步操作相关的各种类型常量,用于标记不同的同步动作情况 + public static final int SYNC_ACTION_NONE = 0; + public static final int SYNC_ACTION_ADD_REMOTE = 1; + public static final int SYNC_ACTION_ADD_LOCAL = 2; + public static final int SYNC_ACTION_DEL_REMOTE = 3; + public static final int SYNC_ACTION_DEL_LOCAL = 4; + public static final int SYNC_ACTION_UPDATE_REMOTE = 5; + public static final int SYNC_ACTION_UPDATE_LOCAL = 6; + public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; + public static final int SYNC_ACTION_ERROR = 8; + + // 存储全局唯一标识符(GID),初始值为null + private String mGid; + // 节点名称,初始为空字符串 + private String mName; + // 最后修改时间戳,初始为0 + private long mLastModified; + // 表示节点是否被删除的标志,初始为false + private boolean mDeleted; + + // 构造函数,初始化各成员变量 + public Node() { + mGid = null; + mName = ""; + mLastModified = 0; + mDeleted = false; + } + + // 抽象方法,获取创建操作对应的JSON对象,具体由子类实现 + public abstract JSONObject getCreateAction(int actionId); + + // 抽象方法,获取更新操作对应的JSON对象,子类按业务逻辑实现 + public abstract JSONObject getUpdateAction(int actionId); + + // 抽象方法,依据远程JSON数据设置节点内容,子类负责解析填充 + public abstract void setContentByRemoteJSON(JSONObject js); + + // 抽象方法,依据本地JSON数据设置节点内容,由子类实现具体逻辑 + public abstract void setContentByLocalJSON(JSONObject js); + + // 抽象方法,从节点内容生成本地JSON对象,子类确定JSON结构 + public abstract JSONObject getLocalJSONFromContent(); + + // 抽象方法,根据游标获取节点的同步操作类型,子类判断逻辑 + public abstract int getSyncAction(Cursor c); + + // 设置GID的方法 + public void setGid(String gid) { + this.mGid = gid; + } + + // 设置名称的方法 + public void setName(String name) { + this.mName = name; + } + + // 设置最后修改时间的方法 + public void setLastModified(long lastModified) { + this.mLastModified = lastModified; + } + + // 设置是否删除状态的方法 + public void setDeleted(boolean deleted) { + this.mDeleted = deleted; + } + + // 获取GID的方法 + public String getGid() { + return this.mGid; + } + + // 获取名称的方法 + public String getName() { + return this.mName; + } + + // 获取最后修改时间的方法 + public long getLastModified() { + return this.mLastModified; + } + + // 获取是否删除状态的方法 + public boolean getDeleted() { + return this.mDeleted; + } +} \ No newline at end of file -- 2.34.1 From 6daf78c42c7520c31daa81e5b0ab074894b59140 Mon Sep 17 00:00:00 2001 From: p3o5nagmu <1329414441@qq.com> Date: Tue, 31 Dec 2024 03:26:05 +0800 Subject: [PATCH 05/13] ADD file via upload --- Notes.java | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 Notes.java diff --git a/Notes.java b/Notes.java new file mode 100644 index 0000000..323bbbe --- /dev/null +++ b/Notes.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * 版权声明,说明本代码是由MiCode开源社区提供并遵循Apache 2.0许可证。 + */ + +package net.micode.notes.data; // 包声明,表示该类属于net.micode.notes.data包 + +import android.net.Uri; // 导入Uri类,用于处理URI + +public class Notes { + // 定义常量,用于表示笔记、文件夹和系统类型 + public static final String AUTHORITY = "micode_notes"; + public static final String TAG = "Notes"; + public static final int TYPE_NOTE = 0; // 笔记类型 + public static final int TYPE_FOLDER = 1; // 文件夹类型 + public static final int TYPE_SYSTEM = 2; // 系统类型 + + /** + * 以下是系统文件夹的标识符 + * ID_ROOT_FOLDER 是默认文件夹 + * ID_TEMPARAY_FOLDER 用于存储没有归类的笔记 + * ID_CALL_RECORD_FOLDER 用于存储通话记录 + */ + public static final int ID_ROOT_FOLDER = 0; + public static final int ID_TEMPARAY_FOLDER = -1; + public static final int ID_CALL_RECORD_FOLDER = -2; + public static final int ID_TRASH_FOLER = -3; // 回收站文件夹 + + // Intent中传递的数据常量 + public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; + public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; + public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; + public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; + public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; + public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; + + // 小部件类型 + public static final int TYPE_WIDGET_INVALIDE = -1; + public static final int TYPE_WIDGET_2X = 0; + public static final int TYPE_WIDGET_4X = 1; + + // 数据常量,指示笔记和通话记录的数据类型 + public static class DataConstants { + public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; // 文本笔记类型 + public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; // 通话记录类型 + } + + // 查询所有笔记和文件夹的URI + public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); + + // 查询数据的URI + public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); + + public interface NoteColumns { + /** + * 定义笔记和文件夹的数据库列 + * 每个字段后面有说明其数据类型 + */ + + public static final String ID = "_id"; // 唯一ID + public static final String PARENT_ID = "parent_id"; // 父级ID + public static final String CREATED_DATE = "created_date"; // 创建日期 + public static final String MODIFIED_DATE = "modified_date"; // 修改日期 + public static final String ALERTED_DATE = "alert_date"; // 提醒日期 + public static final String SNIPPET = "snippet"; // 文件夹名或笔记内容 + public static final String WIDGET_ID = "widget_id"; // 小部件ID + public static final String WIDGET_TYPE = "widget_type"; // 小部件类型 + public static final String BG_COLOR_ID = "bg_color_id"; // 背景颜色ID + public static final String HAS_ATTACHMENT = "has_attachment"; // 是否有附件 + public static final String NOTES_COUNT = "notes_count"; // 文件夹内笔记数量 + public static final String TYPE = "type"; // 类型:笔记或文件夹 + public static final String SYNC_ID = "sync_id"; // 同步ID + public static final String LOCAL_MODIFIED = "local_modified"; // 本地修改标志 + public static final String ORIGIN_PARENT_ID = "origin_parent_id"; // 原始父级ID + public static final String GTASK_ID = "gtask_id"; // GTask ID + public static final String VERSION = "version"; // 版本 + } + + public interface DataColumns { + /** + * 定义数据表的列,主要用于存储附件或其他类型的数据 + */ + public static final String ID = "_id"; // 唯一ID + public static final String MIME_TYPE = "mime_type"; // 数据的MIME类型 + public static final String NOTE_ID = "note_id"; // 对应笔记的ID + public static final String CREATED_DATE = "created_date"; // 创建日期 + public static final String MODIFIED_DATE = "modified_date"; // 修改日期 + public static final String CONTENT = "content"; // 数据内容 + public static final String DATA1 = "data1"; + public static final String DATA2 = "data2"; + public static final String DATA3 = "data3"; + public static final String DATA4 = "data4"; + public static final String DATA5 = "data5"; } + + // 文本笔记的实现 + public static final class TextNote implements DataColumns { + public static final String MODE = DATA1; // 标记是否为待办事项 + public static final int MODE_CHECK_LIST = 1; // 待办事项模式 + + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; // 文本笔记内容类型 + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; // 单项文本笔记内容类型 + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); // 文本笔记的URI + } + + // 通话记录笔记的实现 + public static final class CallNote implements DataColumns { + public static final String CALL_DATE = DATA1; // 通话日期 + public static final String PHONE_NUMBER = DATA3; // 电话号码 + + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; // 通话记录内容类型 + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; // 单项通话记录内容类型 + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); // 通话记录的URI + } +} -- 2.34.1 From c6c90fc51c4e1aad2a31f3daf69ba9b801ce0878 Mon Sep 17 00:00:00 2001 From: p3o5nagmu <1329414441@qq.com> Date: Tue, 31 Dec 2024 03:26:13 +0800 Subject: [PATCH 06/13] ADD file via upload --- NotesDatabaseHelper.java | 187 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 NotesDatabaseHelper.java diff --git a/NotesDatabaseHelper.java b/NotesDatabaseHelper.java new file mode 100644 index 0000000..4b09823 --- /dev/null +++ b/NotesDatabaseHelper.java @@ -0,0 +1,187 @@ +/* + * 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.data; + +import android.content.ContentValues; +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.DataConstants; +import net.micode.notes.data.Notes.NoteColumns; + +public class NotesDatabaseHelper extends SQLiteOpenHelper { + private static final String DB_NAME = "note.db"; // 数据库名称 + + private static final int DB_VERSION = 4; // 数据库版本 + + public interface TABLE { + public static final String NOTE = "note"; // 笔记表 + public static final String DATA = "data"; // 数据表 + } + + private static final String TAG = "NotesDatabaseHelper"; // 日志标签 + + private static NotesDatabaseHelper mInstance; // 单例实例 + + // 创建笔记表的 SQL 语句 + private static final String CREATE_NOTE_TABLE_SQL = + "CREATE TABLE " + TABLE.NOTE + "(" + + NoteColumns.ID + " INTEGER PRIMARY KEY," + + NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + + NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + + NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + + ")"; + + // 创建数据表的 SQL 语句 + private static final String CREATE_DATA_TABLE_SQL = + "CREATE TABLE " + TABLE.DATA + "(" + + DataColumns.ID + " INTEGER PRIMARY KEY," + + DataColumns.MIME_TYPE + " TEXT NOT NULL," + + DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + + DataColumns.DATA1 + " INTEGER," + + DataColumns.DATA2 + " INTEGER," + + DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + + DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + + DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + + ")"; + + // 为数据表创建索引 + private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = + "CREATE INDEX IF NOT EXISTS note_id_index ON " + + TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; + + // 触发器:更新文件夹中的笔记数(更新操作时) + private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = + "CREATE TRIGGER increase_folder_count_on_update "+ + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + + " END"; + + // 触发器:更新文件夹中的笔记数(更新操作时) + private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = + "CREATE TRIGGER decrease_folder_count_on_update " + + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + + " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + + " END"; + + // 触发器:更新文件夹中的笔记数(插入操作时) + private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = + "CREATE TRIGGER increase_folder_count_on_insert " + + " AFTER INSERT ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + + " END"; + + // 触发器:更新文件夹中的笔记数(删除操作时) + private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = + "CREATE TRIGGER decrease_folder_count_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + + " AND " + NoteColumns.NOTES_COUNT + ">0;" + + " END"; + + // 触发器:在插入数据时更新笔记内容 + private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = + "CREATE TRIGGER update_note_content_on_insert " + + " AFTER INSERT ON " + TABLE.DATA + + " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + + " END"; + + // 触发器:在更新数据时更新笔记内容 + private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = + "CREATE TRIGGER update_note_content_on_update " + + " AFTER UPDATE ON " + TABLE.DATA + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + + " END"; + + // 触发器:在删除数据时更新笔记内容 + private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = + "CREATE TRIGGER update_note_content_on_delete " + + " AFTER delete ON " + TABLE.DATA + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=''" + + " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + + " END"; + + // 触发器:在删除笔记时删除关联数据 + private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = + "CREATE TRIGGER delete_data_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN" + + " DELETE FROM " + TABLE.DATA + + " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + + " END"; + + // 触发器:删除文件夹时删除文件夹中的笔记 + private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = + "CREATE TRIGGER folder_delete_notes_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN" + + " DELETE FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + + " END"; + + // 触发器:将文件夹中的笔记移动到垃圾桶 + private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = + "CREATE TRIGGER folder_move_notes_on_trash " + + " AFTER UPDATE ON " + TABLE.NOTE + + " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" -- 2.34.1 From 5c74cd3f81fdd46462f247300f1aabbad26d1f5d Mon Sep 17 00:00:00 2001 From: p3o5nagmu <1329414441@qq.com> Date: Tue, 31 Dec 2024 03:26:24 +0800 Subject: [PATCH 07/13] ADD file via upload --- NotesProvider.java | 186 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 NotesProvider.java diff --git a/NotesProvider.java b/NotesProvider.java new file mode 100644 index 0000000..413448f --- /dev/null +++ b/NotesProvider.java @@ -0,0 +1,186 @@ +/* + * 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.data; + +import android.app.SearchManager; +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Intent; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; + +import net.micode.notes.R; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.data.NotesDatabaseHelper.TABLE; + +public class NotesProvider extends ContentProvider { + // URI匹配器,用于匹配不同的URI路径 + private static final UriMatcher mMatcher; + + // 数据库帮助类实例 + private NotesDatabaseHelper mHelper; + + // 调试标签 + private static final String TAG = "NotesProvider"; + + // 定义URI匹配码 + private static final int URI_NOTE = 1; + private static final int URI_NOTE_ITEM = 2; + private static final int URI_DATA = 3; + private static final int URI_DATA_ITEM = 4; + private static final int URI_SEARCH = 5; + private static final int URI_SEARCH_SUGGEST = 6; + + // 初始化URI匹配器 + static { + mMatcher = new UriMatcher(UriMatcher.NO_MATCH); + mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); + mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); + mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); + mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); + mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); + mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); + mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); + } + + // 搜索结果投影 + private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," + + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," + + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," + + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," + + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; + + // 搜索查询 + private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION + + " FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; + + @Override + public boolean onCreate() { + // 获取数据库帮助类实例 + mHelper = NotesDatabaseHelper.getInstance(getContext()); + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + // 处理查询请求 + Cursor c = null; + SQLiteDatabase db = mHelper.getReadableDatabase(); + String id = null; + switch (mMatcher.match(uri)) { + // 根据不同的URI匹配码执行不同的查询操作 + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } + if (c != null) { + c.setNotificationUri(getContext().getContentResolver(), uri); + } + return c; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + // 处理插入操作 + SQLiteDatabase db = mHelper.getWritableDatabase(); + long dataId = 0, noteId = 0, insertedId = 0; + switch (mMatcher.match(uri)) { + // 根据不同的URI匹配码执行不同的插入操作 + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } + return ContentUris.withAppendedId(uri, insertedId); + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + // 处理删除操作 + int count = 0; + String id = null; + SQLiteDatabase db = mHelper.getWritableDatabase(); + boolean deleteData = false; + switch (mMatcher.match(uri)) { + // 根据不同的URI匹配码执行不同的删除操作 + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } + return count; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + // 处理更新操作 + int count = 0; + String id = null; + SQLiteDatabase db = mHelper.getWritableDatabase(); + boolean updateData = false; + switch (mMatcher.match(uri)) { + // 根据不同的URI匹配码执行不同的更新操作 + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } + return count; + } + + private String parseSelection(String selection) { + // 解析选择条件 + return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); + } + + private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { + // 更新笔记版本 + StringBuilder sql = new StringBuilder(120); + sql.append("UPDATE "); + sql.append(TABLE.NOTE); + sql.append(" SET "); + sql.append(NoteColumns.VERSION); + sql.append("=" + NoteColumns.VERSION + "+1 "); + + if (id > 0 || !TextUtils.isEmpty(selection)) { + sql.append(" WHERE "); + } + if (id > 0) { + sql.append(NoteColumns.ID + "=" + String.valueOf(id)); + } + if (!TextUtils.isEmpty(selection)) { + String selectString = id > 0 ? parseSelection(selection) : selection; + for (String args : selectionArgs) { + selectString = selectString.replaceFirst("\\?", args); + } + sql.append(selectString); + } + + mHelper.getWritableDatabase().execSQL(sql.toString()); + } + + @Override + public String getType(Uri uri) { + // 获取内容类型 + return null; + } +} \ No newline at end of file -- 2.34.1 From 42f202210726c451223116737234d3b16efc3cc6 Mon Sep 17 00:00:00 2001 From: p3o5nagmu <1329414441@qq.com> Date: Tue, 31 Dec 2024 03:26:40 +0800 Subject: [PATCH 08/13] ADD file via upload --- SqlData.java | 199 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 SqlData.java diff --git a/SqlData.java b/SqlData.java new file mode 100644 index 0000000..3795bf0 --- /dev/null +++ b/SqlData.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.gtask.data; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.util.Log; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.DataConstants; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.data.NotesDatabaseHelper.TABLE; +import net.micode.notes.gtask.exception.ActionFailureException; + +import org.json.JSONException; +import org.json.JSONObject; + +// SqlData类,可能用于处理与数据库相关的数据操作,比如从数据库读取数据、将数据更新到数据库以及和JSON数据的相互转换等 +public class SqlData { + // 用于日志记录的标签,使用类的简单名称,方便在日志中识别该类相关的输出信息 + private static final String TAG = SqlData.class.getSimpleName(); + // 定义一个表示无效ID的值,可能用于初始化或者标记不符合要求的ID情况 + private static final int INVALID_ID = -99999; + + // 定义查询数据时要获取的列名数组,用于指定从数据库获取哪些字段的数据 + public static final String[] PROJECTION_DATA = new String[] { + DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1, + DataColumns.DATA3 + }; + // 表示在查询结果游标中数据ID列的索引位置,方便从游标中获取对应的数据 + public static final int DATA_ID_COLUMN = 0; + // 表示在查询结果游标中数据MIME类型列的索引位置 + public static final int DATA_MIME_TYPE_COLUMN = 1; + // 表示在查询结果游标中数据内容列的索引位置 + public static final int DATA_CONTENT_COLUMN = 2; + // 表示在查询结果游标中数据特定内容字段DATA1列的索引位置 + public static final int DATA_CONTENT_DATA_1_COLUMN = 3; + // 表示在查询结果游标中数据特定内容字段DATA3列的索引位置 + public static final int DATA_CONTENT_DATA_3_COLUMN = 4; + + // 用于操作内容提供器,实现和数据库的数据交互,比如查询、插入、更新等操作 + private ContentResolver mContentResolver; + // 标记是否是创建操作的标志位,true表示正在创建新数据,初始化为true + private boolean mIsCreate; + // 存储数据的ID,初始化为无效ID值 + private long mDataId; + // 存储数据的MIME类型,初始化为默认的笔记类型(从DataConstants.NOTE获取) + private String mDataMimeType; + // 存储数据的内容,初始化为空字符串 + private String mDataContent; + // 存储数据内容中特定的长整型数据(对应DATA1字段),初始化为0 + private long mDataContentData1; + // 存储数据内容中特定的字符串数据(对应DATA3字段),初始化为空字符串 + private String mDataContentData3; + // 用于存放要更新到数据库的数据值,类似一个键值对集合,方便批量更新操作 + private ContentValues mDiffDataValues; + + // 构造函数,传入上下文用于获取内容提供器,初始化相关成员变量,通常用于创建新数据的情况 + public SqlData(Context context) { + mContentResolver = context.getContentResolver(); + mIsCreate = true; + mDataId = INVALID_ID; + mDataMimeType = DataConstants.NOTE; + mDataContent = ""; + mDataContentData1 = 0; + mDataContentData3 = ""; + mDiffDataValues = new ContentValues(); + } + + // 另一个构造函数,传入上下文和游标,用于从已有的游标数据中加载信息初始化成员变量,通常用于读取现有数据的情况 + public SqlData(Context context, Cursor c) { + mContentResolver = context.getContentResolver(); + mIsCreate = false; + loadFromCursor(c); + mDiffDataValues = new ContentValues(); + } + + // 从游标中加载数据到成员变量的私有方法,按照定义好的列索引位置获取对应的数据赋值给成员变量 + private void loadFromCursor(Cursor c) { + mDataId = c.getLong(DATA_ID_COLUMN); + mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); + mDataContent = c.getString(DATA_CONTENT_COLUMN); + mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); + mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); + } + + // 根据传入的JSON对象设置相关数据成员变量,并将有变化的数据放入mDiffDataValues中,用于后续更新数据库操作 + public void setContent(JSONObject js) throws JSONException { + long dataId = js.has(DataColumns.ID)? js.getLong(DataColumns.ID) : INVALID_ID; + if (mIsCreate || mDataId!= dataId) { + mDiffDataValues.put(DataColumns.ID, dataId); + } + mDataId = dataId; + + String dataMimeType = js.has(DataColumns.MIME_TYPE)? js.getString(DataColumns.MIME_TYPE) + : DataConstants.NOTE; + if (mIsCreate ||!mDataMimeType.equals(dataMimeType)) { + mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); + } + mDataMimeType = dataMimeType; + + String dataContent = js.has(DataColumns.CONTENT)? js.getString(DataColumns.CONTENT) : ""; + if (mIsCreate ||!mDataContent.equals(dataContent)) { + mDiffDataValues.put(DataColumns.CONTENT, dataContent); + } + mDataContent = dataContent; + + long dataContentData1 = js.has(DataColumns.DATA1)? js.getLong(DataColumns.DATA1) : 0; + if (mIsCreate || mDataContentData1!= dataContentData1) { + mDiffDataValues.put(DataColumns.DATA1, dataContentData1); + } + mDataContentData1 = dataContentData1; + + String dataContentData3 = js.has(DataColumns.DATA3)? js.getString(DataColumns.DATA3) : ""; + if (mIsCreate ||!mDataContentData3.equals(dataContentData3)) { + mDiffDataValues.put(DataColumns.DATA3, dataContentData3); + } + mDataContentData3 = dataContentData3; + } + + // 根据当前成员变量的数据生成对应的JSON对象,若处于创建状态则记录错误日志并返回null + public JSONObject getContent() throws JSONException { + if (mIsCreate) { + Log.e(TAG, "it seems that we haven't created this in database yet"); + return null; + } + JSONObject js = new JSONObject(); + js.put(DataColumns.ID, mDataId); + js.put(DataColumns.MIME_TYPE, mDataMimeType); + js.put(DataColumns.CONTENT, mDataContent); + js.put(DataColumns.DATA1, mDataContentData1); + js.put(DataColumns.DATA3, mDataContentData3); + return js; + } + + // 将数据提交到数据库的方法,根据是否是创建操作执行不同的逻辑,创建则插入数据,否则根据条件更新数据 + public void commit(long noteId, boolean validateVersion, long version) { + + if (mIsCreate) { + if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { + mDiffDataValues.remove(DataColumns.ID); + } + + mDiffDataValues.put(DataColumns.NOTE_ID, noteId); + Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); + try { + mDataId = Long.valueOf(uri.getPathSegments().get(1)); + } catch (NumberFormatException e) { + Log.e(TAG, "Get note id error :" + e.toString()); + throw new ActionFailureException("create note failed"); + } + } else { + if (mDiffDataValues.size() > 0) { + int result = 0; + if (!validateVersion) { + result = mContentResolver.update(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null); + } else { + result = mContentResolver.update(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, + "? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.VERSION + "=?)", new String[] { + String.valueOf(noteId), String.valueOf(version) + }); + } + if (result == 0) { + Log.w(TAG, "there is no update. maybe user updates note when syncing"); + } + } + } + + mDiffDataValues.clear(); + mIsCreate = false; + } + + // 获取数据ID的方法,外部可调用此方法获取当前对象关联的数据ID + public long getId() { + return mDataId; + } +} \ No newline at end of file -- 2.34.1 From cfdea719e1c60219093c0d7411221240fb8ba051 Mon Sep 17 00:00:00 2001 From: p3o5nagmu <1329414441@qq.com> Date: Tue, 31 Dec 2024 03:26:52 +0800 Subject: [PATCH 09/13] ADD file via upload --- SqlNote.java | 670 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 670 insertions(+) create mode 100644 SqlNote.java diff --git a/SqlNote.java b/SqlNote.java new file mode 100644 index 0000000..f0ebc5a --- /dev/null +++ b/SqlNote.java @@ -0,0 +1,670 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.gtask.data; + +import android.appwidget.AppWidgetManager; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.util.Log; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.gtask.exception.ActionFailureException; +import net.micode.notes.tool.GTaskStringUtils; +import net.micode.notes.tool.ResourceParser; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; + + +public class SqlNote { + // 定义一个用于日志记录的标签,其值为类的简单名称(不含包名部分),方便在查看日志时能快速识别与该类相关的输出信息 + private static final String TAG = SqlNote.class.getSimpleName(); + // 定义一个表示无效的ID值,通常用于初始化成员变量或者在某些逻辑判断中表示不符合要求、尚未正确赋值的ID情况,这里设定为 -99999 + private static final int INVALID_ID = -99999; + + // 定义一个字符串数组,用于指定查询笔记信息时从数据库中要获取的列名列表,涵盖了笔记的各种属性字段,如ID、提醒日期、背景颜色ID等 + public static final String[] PROJECTION_NOTE = new String[] { + NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID, + NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE, + NoteColumns.NOTES_COUNT, NoteColumns.PARENT_ID, NoteColumns.SNIPPET, NoteColumns.TYPE, + NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, NoteColumns.SYNC_ID, + NoteColumns.LOCAL_MODIFIED, NoteColumns.ORIGIN_PARENT_ID, NoteColumns.GTASK_ID, + NoteColumns.VERSION + }; + // 定义一个常量,表示在查询结果游标(Cursor)中笔记ID列对应的索引位置,方便后续从游标中准确获取该列数据,值为0,对应上述PROJECTION_NOTE数组中的顺序 + public static final int ID_COLUMN = 0; + // 定义一个常量,表示在查询结果游标中提醒日期列对应的索引位置,用于从游标获取对应数据,值为1 + public static final int ALERTED_DATE_COLUMN = 1; + // 定义一个常量,表示在查询结果游标中背景颜色ID列对应的索引位置,便于提取相应数据,值为2 + public static final int BG_COLOR_ID_COLUMN = 2; + // 定义一个常量,表示在查询结果游标中创建日期列对应的索引位置,用于获取创建时间相关数据,值为3 + public static final int CREATED_DATE_COLUMN = 3; + // 定义一个常量,表示在查询结果游标中是否有附件列对应的索引位置,用于判断笔记是否包含附件,值为4 + public static final int HAS_ATTACHMENT_COLUMN = 4; + // 定义一个常量,表示在查询结果游标中修改日期列对应的索引位置,用于获取笔记最后修改时间信息,值为5 + public static final int MODIFIED_DATE_COLUMN = 5; + // 定义一个常量,表示在查询结果游标中笔记数量列对应的索引位置(可能用于表示文件夹下包含笔记的数量等情况),值为6 + public static final int NOTES_COUNT_COLUMN = 6; + // 定义一个常量,表示在查询结果游标中父级ID列对应的索引位置,用于确定笔记在层级结构中的位置,即父级的标识,值为7 + public static final int PARENT_ID_COLUMN = 7; + // 定义一个常量,表示在查询结果游标中摘要(或文本内容片段)列对应的索引位置,用于获取笔记相关的文本摘要信息,值为8 + public static final int SNIPPET_COLUMN = 8; + // 定义一个常量,表示在查询结果游标中笔记类型列对应的索引位置,用于区分不同类型的笔记(比如普通笔记、文件夹类型等),值为9 + public static final int TYPE_COLUMN = 9; + // 定义一个常量,表示在查询结果游标中小部件ID列对应的索引位置,可能用于关联笔记与桌面小部件相关的操作,值为10 + public static final int WIDGET_ID_COLUMN = 10; + // 定义一个常量,表示在查询结果游标中小部件类型列对应的索引位置,用于区分不同样式、功能的小部件,值为11 + public static final int WIDGET_TYPE_COLUMN = 11; + // 定义一个常量,表示在查询结果游标中同步ID列对应的索引位置,通常在数据同步相关操作中用于标识,值为12 + public static final int SYNC_ID_COLUMN = 12; + // 定义一个常量,表示在查询结果游标中本地是否修改列对应的索引位置,用于判断本地对笔记数据是否有修改,值为13 + public static final int LOCAL_MODIFIED_COLUMN = 13; + // 定义一个常量,表示在查询结果游标中原始父级ID列对应的索引位置,可能用于记录笔记在某些操作之前的原始归属情况,值为14 + public static final int ORIGIN_PARENT_ID_COLUMN = 14; + // 定义一个常量,表示在查询结果游标中Google Tasks相关ID列对应的索引位置,可能用于和Google Tasks服务进行关联操作,值为15 + public static final int GTASK_ID_COLUMN = 15; + // 定义一个常量,表示在查询结果游标中版本列对应的索引位置,用于跟踪笔记数据的版本变化情况,值为16 + public static final int VERSION_COLUMN = 16; + + // 保存传入的上下文对象(Context),通过它可以访问系统资源、服务等,例如获取内容提供器(ContentResolver)来操作数据库 + private Context mContext; + // 用于与安卓的内容提供器进行交互,实现对数据库的查询、插入、更新等操作,比如获取笔记数据或者将笔记数据保存到数据库中 + private ContentResolver mContentResolver; + // 一个布尔类型的标志位,用于标记当前操作是创建新笔记(true)还是对已有笔记进行操作(false),初始化为true,表示创建情况 + private boolean mIsCreate; + // 用于存储笔记的唯一标识符(ID),初始化为无效ID值(INVALID_ID),后续会根据实际情况进行赋值或者更新 + private long mId; + // 用于存储笔记的提醒日期,以时间戳的形式表示(通常是从某个特定时间点开始到提醒时间所经过的毫秒数),初始化为0 + private long mAlertDate; + // 用于存储笔记的背景颜色ID,初始值通过ResourceParser获取默认背景色ID来赋值,可能用于设置笔记的显示外观相关功能 + private int mBgColorId; + // 用于存储笔记的创建日期,以时间戳形式记录创建时刻,初始化为当前系统时间(调用System.currentTimeMillis()获取) + private long mCreatedDate; + // 用于存储表示笔记是否有附件的标志,0表示没有附件,初始化为0,根据实际情况后续可能会被修改 + private int mHasAttachment; + // 用于存储笔记的最后修改日期,同样以时间戳形式表示,初始化为当前系统时间,方便跟踪笔记内容的更新情况 + private long mModifiedDate; + // 用于存储笔记的父级ID,用于构建笔记的层级结构,比如笔记属于哪个文件夹等情况,初始化为0 + private long mParentId; + // 用于存储笔记的摘要(或文本内容片段),初始化为空字符串,可能是笔记内容的简短描述等,后续可根据实际情况更新 + private String mSnippet; + // 用于存储笔记的类型,通过Notes.TYPE_NOTE等常量来区分不同类型(如普通笔记、文件夹等),初始化为普通笔记类型(Notes.TYPE_NOTE) + private int mType; + // 用于存储笔记关联的小部件ID,初始化为AppWidgetManager.INVALID_APPWIDGET_ID,表示无效的小部件ID,在有实际关联小部件时会被更新 + private int mWidgetId; + // 用于存储笔记关联的小部件类型,初始化为Notes.TYPE_WIDGET_INVALIDE,表示无效的小部件类型,根据实际使用情况会改变 + private int mWidgetType; + // 用于存储笔记的原始父级ID,可能用于记录笔记在某些操作之前的归属情况,初始化为0,在特定业务场景下会被更新 + private long mOriginParent; + // 用于存储笔记的版本号,初始化为0,用于跟踪笔记数据的版本变化,例如每次更新后版本号可能会递增 + private long mVersion; + // 用于存放要更新到数据库的笔记相关数据值,是一种键值对形式的集合(类似Map),可以批量设置要更新的字段和对应的值,方便数据库更新操作 + private ContentValues mDiffNoteValues; + // 定义一个ArrayList,用于存储与该笔记相关的具体数据列表,每个元素是SqlData类型的对象,可能一条笔记对应多条相关的数据记录 + private ArrayList mDataList; + + // 构造函数,用于创建新笔记时的初始化操作,设置各种成员变量的初始值,例如默认的创建日期、初始的标志位等,为后续创建笔记流程做准备 + public SqlNote(Context context) { + mContext = context; + mContentResolver = context.getContentResolver(); + mIsCreate = true; + mId = INVALID_ID; + mAlertDate = 0; + mBgColorId = ResourceParser.getDefaultBgId(context); + mCreatedDate = System.currentTimeMillis(); + mHasAttachment = 0; + mModifiedDate = System.currentTimeMillis(); + mParentId = 0; + mSnippet = ""; + mType = Notes.TYPE_NOTE; + mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; + mWidgetType = Notes.TYPE_WIDGET_INVALIDE; + mOriginParent = 0; + mVersion = 0; + mDiffNoteValues = new ContentValues(); + mDataList = new ArrayList(); + } + + // 构造函数,用于从已有的游标(Cursor)数据中加载笔记信息,通常是在查询数据库获取笔记记录后进行初始化操作,设置相关成员变量,并根据笔记类型决定是否加载具体数据内容 + public SqlNote(Context context, Cursor c) { + mContext = context; + mContentResolver = context.getContentResolver(); + mIsCreate = false; + loadFromCursor(c); + mDataList = new ArrayList(); + if (mType == Notes.TYPE_NOTE) + loadDataContent(); + mDiffNoteValues = new ContentValues(); + } + + // 构造函数,根据给定的笔记ID从数据库加载笔记信息,先通过查询获取游标,再调用loadFromCursor方法来加载具体数据到成员变量中,同样根据笔记类型决定是否加载相关数据内容 + public SqlNote(Context context, long id) { + mContext = context; + mContentResolver = context.getContentResolver(); + mIsCreate = false; + loadFromCursor(id); + mDataList = new ArrayList(); + if (mType == Notes.TYPE_NOTE) + loadDataContent(); + mDiffNoteValues = new ContentValues(); + } + + // 根据给定的笔记ID从数据库查询并加载笔记信息到成员变量的私有方法,先发起数据库查询获取游标对象,若游标不为空则移动到第一条记录并继续调用loadFromCursor(Cursor c)方法进行具体数据加载,同时处理游标为空的情况并记录日志 + private void loadFromCursor(long id) { + Cursor c = null; + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)", + new String[] { + String.valueOf(id) + }, null); + if (c!= null) { + c.moveToNext(); + loadFromCursor(c); + } else { + Log.w(TAG, "loadFromCursor: cursor = null"); + } + } finally { + if (c!= null) + c.close(); + } + } + + // 从给定的游标(Cursor)中加载笔记各字段数据到对应的成员变量的私有方法,按照之前定义好的列索引位置(如ID_COLUMN等常量),从游标中获取相应数据并赋值给成员变量,实现数据的提取和初始化 + private void loadFromCursor(Cursor c) { + mId = c.getLong(ID_COLUMN); + mAlertDate = c.getLong(ALERTED_DATE_COLUMN); + mBgColorId = c.getInt(BG_COLOR_ID_COLUMN); + mCreatedDate = c.getLong(CREATED_DATE_COLUMN); + mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN); + mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN); + mParentId = c.getLong(PARENT_ID_COLUMN); + mSnippet = c.getString(SNIPPET_COLUMN); + mType = c.getInt(TYPE_COLUMN); + mWidgetId = c.getInt(WIDGET_ID_COLUMN); + mWidgetType = c.getInt(WIDGET_TYPE_COLUMN); + mVersion = c.getLong(VERSION_COLUMN); + } + + // 加载与该笔记相关的数据内容的私有方法,通过向数据库发起查询,获取与该笔记ID关联的数据记录(通过note_id字段关联),若查询到记录则循环创建SqlData对象并添加到mDataList列表中,同时处理查询结果为空以及游标关闭等情况并记录日志 + private void loadDataContent() { + Cursor c = null; + mDataList.clear(); + try { + c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA, + "(note_id=?)", new String[] { + String.valueOf(mId) + }, null); + if (c!= null) { + if (c.getCount() == 0) { + Log.w(TAG, "it seems that the note has not data"); + return; + } + while (c.moveToNext()) { + SqlData data = new SqlData(mContext, c); + mDataList.add(data); + } + } else { + Log.w(TAG, "loadDataContent: cursor = null"); + } + } finally { + if (c!= null) + c.close(); + } + } +// setContent方法用于根据传入的JSONObject对象来设置当前SqlNote对象的各项属性值。 +// 它会根据笔记的不同类型(系统、文件夹、普通笔记)进行相应的属性更新操作,若解析JSON出现异常则返回false。 +public boolean setContent(JSONObject js) { + try { + // 从传入的JSON对象(js)中获取名为GTaskStringUtils.META_HEAD_NOTE的子JSON对象, + // 这个子对象预期包含了笔记主体相关的属性信息,比如类型、ID、摘要等关键内容。 + JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + + // 判断笔记类型是否为系统类型(Notes.TYPE_SYSTEM),如果是,则记录一条警告日志,表示不能设置系统文件夹类型的笔记内容,然后直接返回,不进行后续操作。 + if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { + Log.w(TAG, "cannot set system folder"); + } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { + // 如果笔记类型是文件夹类型(Notes.TYPE_FOLDER),在此分支下,只允许更新笔记的摘要(snippet)和类型(type)这两个属性。 + + // 尝试获取笔记摘要字段(NoteColumns.SNIPPET)的值,如果该字段在JSON对象中存在,则获取对应的值,否则将其设为空字符串。 + String snippet = note.has(NoteColumns.SNIPPET)? note.getString(NoteColumns.SNIPPET) : ""; + + // 判断当前操作是否为创建操作(mIsCreate为true),或者当前笔记的摘要与传入的摘要不一致。 + // 如果满足条件,则将新的摘要值放入mDiffNoteValues中,这个mDiffNoteValues是用于后续更新数据库操作的键值对集合, + // 键为NoteColumns.SNIPPET,值为新的摘要内容,以此来标记该字段有更新变动,需要同步到数据库中。 + if (mIsCreate ||!mSnippet.equals(snippet)) { + mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); + } + + // 更新当前SqlNote对象的摘要字段值,使其与传入的摘要内容保持一致,便于后续使用该对象时获取到最新的摘要信息。 + mSnippet = snippet; + + // 尝试获取笔记类型字段(NoteColumns.TYPE)的值,如果该字段在JSON对象中存在,则获取对应的值,否则将其设为默认的普通笔记类型(Notes.TYPE_NOTE)。 + int type = note.has(NoteColumns.TYPE)? note.getInt(NoteColumns.TYPE) : Notes.TYPE_NOTE; + + // 判断当前操作是否为创建操作或者当前笔记的类型与传入的类型不一致。 + // 如果满足条件,则将新的类型值放入mDiffNoteValues中,键为NoteColumns.TYPE,用于后续数据库更新操作,标记类型字段有变化。 + if (mIsCreate || mType!= type) { + mDiffNoteValues.put(NoteColumns.TYPE, type); + } + + // 更新当前SqlNote对象的类型字段值,使其与传入的类型保持一致,确保对象状态反映最新的类型设定。 + mType = type; + } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) { + // 如果笔记类型是普通笔记类型(Notes.TYPE_NOTE),则进行一系列详细的属性更新操作。 + + // 从传入的JSON对象(js)中获取名为GTaskStringUtils.META_HEAD_DATA的JSON数组, + // 该数组预期包含了与该普通笔记相关的多条具体数据内容,比如可能是笔记正文的不同部分等具体信息。 + JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + + // 尝试获取笔记ID字段(NoteColumns.ID)的值,如果该字段在JSON对象(note)中存在,则获取对应的值,否则将其设为无效ID(INVALID_ID)。 + long id = note.has(NoteColumns.ID)? note.getLong(NoteColumns.ID) : INVALID_ID; + + // 判断当前操作是否为创建操作或者当前笔记的ID与传入的ID不一致。 + // 如果满足条件,则将新的ID值放入mDiffNoteValues中,键为NoteColumns.ID,以便后续在更新数据库时,能将新ID同步过去,标记ID字段有变化。 + if (mIsCreate || mId!= id) { + mDiffNoteValues.put(NoteColumns.ID, id); + } + + // 更新当前SqlNote对象的ID字段值,使其与传入的ID保持一致,保证对象内记录的笔记ID是最新的。 + mId = id; + + // 尝试获取笔记提醒日期字段(NoteColumns.ALERTED_DATE)的值,如果该字段在JSON对象(note)中存在,则获取对应的值,否则将其设为0。 + long alertDate = note.has(NoteColumns.ALERTED_DATE)? note.getLong(NoteColumns.ALERTED_DATE) : 0; + + // 判断当前操作是否为创建操作或者当前笔记的提醒日期与传入的提醒日期不一致。 + // 如果满足条件,则将新的提醒日期值放入mDiffNoteValues中,键为NoteColumns.ALERTED_DATE,用于后续数据库更新,标记提醒日期字段有变化。 + if (mIsCreate || mAlertDate!= alertDate) { + mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate); + } + + // 更新当前SqlNote对象的提醒日期字段值,使其与传入的提醒日期保持一致,确保对象内记录的提醒日期是最新的。 + mAlertDate = alertDate; + + // 尝试获取笔记背景颜色ID字段(NoteColumns.BG_COLOR_ID)的值,如果该字段在JSON对象(note)中存在,则获取对应的值, + // 否则通过ResourceParser获取默认背景色ID作为该字段的值,这意味着如果JSON中未指定背景色,会使用默认设定。 + int bgColorId = note.has(NoteColumns.BG_COLOR_ID)? note.getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext); + + // 判断当前操作是否为创建操作或者当前笔记的背景颜色ID与传入的背景颜色ID不一致。 + // 如果满足条件,则将新的背景颜色ID值放入mDiffNoteValues中,键为NoteColumns.BG_COLOR_ID,用于后续数据库更新,标记背景色字段有变化。 + if (mIsCreate || mBgColorId!= bgColorId) { + mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId); + } + + // 更新当前SqlNote对象的背景颜色ID字段值,使其与传入的背景颜色ID保持一致,保证对象内记录的背景色信息是最新的。 + mBgColorId = bgColorId; + + // 尝试获取笔记创建日期字段(NoteColumns.CREATED_DATE)的值,如果该字段在JSON对象(note)中存在,则获取对应的值, + // 否则将其设为当前系统时间(通过System.currentTimeMillis()获取),这表示如果JSON中未指定创建日期,就使用当前时间作为默认值。 + long createDate = note.has(NoteColumns.CREATED_DATE)? note.getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis(); + + // 判断当前操作是否为创建操作或者当前笔记的创建日期与传入的创建日期不一致。 + // 如果满足条件,则将新的创建日期值放入mDiffNoteValues中,键为NoteColumns.CREATED_DATE,用于后续数据库更新,标记创建日期字段有变化。 + if (mIsCreate || mCreatedDate!= createDate) { + mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate); + } + + // 更新当前SqlNote对象的创建日期字段值,使其与传入的创建日期保持一致,确保对象内记录的创建时间是最新的。 + mCreatedDate = createDate; + + // 尝试获取笔记是否有附件字段(NoteColumns.HAS_ATTACHMENT)的值,如果该字段在JSON对象(note)中存在,则获取对应的值,否则将其设为0(表示无附件)。 + int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT)? note.getInt(NoteColumns.HAS_ATTACHMENT) : 0; + + // 判断当前操作是否为创建操作或者当前笔记的是否有附件标识与传入的标识不一致。 + // 如果满足条件,则将新的是否有附件值放入mDiffNoteValues中,键为NoteColumns.HAS_ATTACHMENT,用于后续数据库更新,标记附件标识字段有变化。 + if (mIsCreate || mHasAttachment!= hasAttachment) { + mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment); + } + + // 更新当前SqlNote对象的是否有附件字段值,使其与传入的附件标识保持一致,保证对象内记录的附件情况是最新的。 + mHasAttachment = hasAttachment; + + // 尝试获取笔记修改日期字段(NoteColumns.MODIFIED_DATE)的值,如果该字段在JSON对象(note)中存在,则获取对应的值, + // 否则将其设为当前系统时间,意味着如果JSON中未指定修改日期,就使用当前时间作为默认值,方便记录最后修改时间。 + long modifiedDate = note.has(NoteColumns.MODIFIED_DATE)? note.getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis(); + + // 判断当前操作是否为创建操作或者当前笔记的修改日期与传入的修改日期不一致。 + // 如果满足条件,则将新的修改日期值放入mDiffNoteValues中,键为NoteColumns.MODIFIED_DATE,用于后续数据库更新,标记修改日期字段有变化。 + if (mIsCreate || mModifiedDate!= modifiedDate) { + mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate); + } + + // 更新当前SqlNote对象的修改日期字段值,使其与传入的修改日期保持一致,确保对象内记录的最后修改时间是最新的。 + mModifiedDate = modifiedDate; + + // 尝试获取笔记父级ID字段(NoteColumns.PARENT_ID)的值,如果该字段在JSON对象(note)中存在,则获取对应的值,否则将其设为0。 + long parentId = note.has(NoteColumns.PARENT_ID)? note.getLong(NoteColumns.PARENT_ID) : 0; + + // 判断当前操作是否为创建操作或者当前笔记的父级ID与传入的父级ID不一致。 + // 如果满足条件,则将新的父级ID值放入mDiffNoteValues中,键为NoteColumns.PARENT_ID,用于后续数据库更新,标记父级ID字段有变化。 + if (mIsCreate || mParentId!= parentId) { + mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId); + } + + // 更新当前SqlNote对象的父级ID字段值,使其与传入的父级ID保持一致,保证对象内记录的笔记层级关系是准确的。 + mParentId = parentId; + + // 再次尝试获取笔记摘要字段(NoteColumns.SNIPPET)的值,如果该字段在JSON对象(note)中存在,则获取对应的值,否则将其设为空字符串。 + String snippet = note.has(NoteColumns.SNIPPET)? note.getString(NoteColumns.SNIPPET) : ""; + + // 判断当前操作是否为创建操作或者当前笔记的摘要与传入的摘要不一致。 + // 如果满足条件,则将新的摘要值放入mDiffNoteValues中,键为NoteColumns.SNIPPET,用于后续数据库更新,标记摘要字段有变化。 + if (mIsCreate ||!mSnippet.equals(snippet)) { + mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); + } + + // 更新当前SqlNote对象的摘要字段值,使其与传入的摘要内容保持一致,便于后续使用该对象时获取到最新的摘要信息。 + mSnippet = snippet; + + // 再次尝试获取笔记类型字段(NoteColumns.TYPE)的值,如果该字段在JSON对象(note)中存在,则获取对应的值,否则将其设为默认的普通笔记类型(Notes.TYPE_NOTE)。 + int type = note.has(NoteColumns.TYPE)? note.getInt(NoteColumns.TYPE) : Notes.TYPE_NOTE; + + // 判断当前操作是否为创建操作或者当前笔记的类型与传入的类型不一致。 + // 如果满足条件,则将新的类型值放入mDiffNoteValues中,键为NoteColumns.TYPE,用于后续数据库更新,标记类型字段有变化。 + if (mIsCreate || mType!= type) { + mDiffNoteValues.put(NoteColumns.TYPE, type); + } + + // 更新当前SqlNote对象的类型字段值,使其与传入的类型保持一致,确保对象状态反映最新的类型设定。 + mType = type; + + // 尝试获取笔记小部件ID字段(NoteColumns.WIDGET_ID)的值,如果该字段在JSON对象(note)中存在,则获取对应的值, + // 否则将其设为无效的小部件ID(AppWidgetManager.INVALID_APPWIDGET_ID),这表示如果JSON中未指定小部件ID,就使用默认的无效标识。 + int widgetId = note.has(NoteColumns.WIDGET_ID)? note.getInt(NoteColumns.WIDGET_ID) : AppWidgetManager.INVALID_APPWIDGET_ID; + + // 判断当前操作是否为创建操作或者当前笔记的小部件ID与传入的小部件ID不一致。 + // 如果满足条件,则将新的小部件ID值放入mDiffNoteValues中,键为NoteColumns.WIDGET_ID,用于后续数据库更新,标记小部件ID字段有变化。 + if (mIsCreate || mWidgetId!= widgetId) { + mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId); + } + + // 更新当前SqlNote对象的小部件ID字段值,使其与传入的小部件ID保持一致,保证对象内记录的小部件关联信息是最新的。 + mWidgetId = widgetId; + + // 尝试获取笔记小部件类型字段(NoteColumns.WIDGET_TYPE)的值,如果该字段在JSON对象(note)中存在,则获取对应的值, + // 否则将其设为无效的小部件类型(Notes.TYPE_WIDGET_INVALIDE),即如果JSON中未指定小部件类型,就使用默认的无效类型标识。 + int widgetType = note.has(NoteColumns.WIDGET_TYPE)? note.getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE; + + // 判断当前操作是否为创建操作或者当前笔记的小部件类型与传入的小部件类型不一致。 + // 如果满足条件,则将新的小部件类型值放入mDiffNoteValues中,键为NoteColumns.WIDGET_TYPE,用于后续数据库更新,标记小部件类型字段有变化。 + if (mIsCreate || mWidgetType!= widgetType) { + mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType); + } + + // 更新当前SqlNote对象的小部件类型字段值,使其与传入的小部件类型保持一致,确保对象内记录的小部件类型信息是最新的。 + mWidgetType = widgetType; + + // 尝试获取笔记原始父级ID字段(NoteColumns.ORIGIN_PARENT_ID)的值,如果该字段在JSON对象(note)中存在,则获取对应的值,否则将其设为0。 + long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID)? note.getLong(NoteColumns.ORIGIN_PACTER_ID) : 0; + + // 判断当前操作是否为创建操作或者当前笔记的原始父级ID与传入的原始父级ID不一致。 + // 如果满足条件,则将新的原始父级ID值放入mDiffNoteValues中,键为NoteColumns.ORIGIN_PARENT_ID,用于后续数据库更新,标记原始父级ID字段有变化。 + if (mIsCreate || mOriginParent!= originParent) { + mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent); + } + + // 更新当前SqlNote对象的原始父级ID字段值,使其与传入的原始父级ID保持一致,保证对象内记录的原始层级关系信息是准确的。 + mOriginParent = originParent; + + // 遍历GTaskStringUtils.META_HEAD_DATA数组中的每一个JSON对象(即遍历与该普通笔记相关的每一条具体数据内容)。 + for (int i = 0; i < dataArray.length(); i++) { + JSONObject data = dataArray.getJSONObject(i); + SqlData sqlData = null; + // 如果当前具体数据对象(data)中包含DataColumns.ID字段,说明该数据有对应的唯一标识。 + if (data.has(DataColumns.ID)) { + long dataId = data.getLong(DataColumns.ID); + // 在当前SqlNote对象关联的mDataList列表中查找是否已存在具有相同ID的SqlData对象, + // mDataList可能存储了与该笔记相关的多条具体数据记录对应的SqlData对象实例。 + for (SqlData temp : mDataList) { + if (dataId == temp.getId()) { + sqlData = temp; + } + } + } + + // 如果在mDataList中未找到对应的SqlData对象(即当前遍历的数据是新的,之前不存在关联的SqlData实例), + // 则创建一个新的SqlData对象,传入当前上下文(mContext),用于后续操作该数据相关内容。 + if (sqlData == null) { + sqlData = new SqlData(mContext); + mDataList.add(sqlData); + } + + // 调用找到或新创建的SqlData对象的setContent方法,将当前遍历到的具体数据内容(data)传递进去, + + sqlData.setContent(data); + } + } + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + return false; + } + return true; + } + +// getContent方法用于将当前SqlNote对象的相关属性及数据内容转换为JSONObject格式返回, +// 如果当前对象处于创建状态(尚未在数据库中创建完成),则记录错误日志并返回null,若在构建JSON过程中出现异常也返回null。 +public JSONObject getContent() { + try { + // 创建一个新的JSONObject对象,用于组装并最终返回包含笔记相关信息的JSON数据结构。 + JSONObject js = new JSONObject(); + + // 判断当前笔记是否处于创建状态(mIsCreate为true表示正在创建,还未真正在数据库中创建好), + // 如果是创建状态,则记录一条错误日志,表示还未在数据库中创建该笔记,然后直接返回null,因为此时还没有完整有效的数据可供返回。 + if (mIsCreate) { + Log.e(TAG, "it seems that we haven't created this in database yet"); + return null; + } + + // 创建一个新的JSONObject对象,用于存放笔记主体相关的属性信息,后续会根据笔记类型往里面添加不同的字段数据。 + JSONObject note = new JSONObject(); + + // 判断当前笔记类型是否为普通笔记类型(Notes.TYPE_NOTE),如果是,则将笔记的多个属性字段及其对应的值添加到note对象中。 + if (mType == Notes.TYPE_NOTE) { + // 将笔记的ID字段(NoteColumns.ID)及其对应的值(mId)添加到note对象中,这样在生成的JSON数据中就包含了笔记的唯一标识符信息。 + note.put(NoteColumns.ID, mId); + // 将笔记的提醒日期字段(NoteColumns.ALERTED_DATE)及其对应的值(mAlertDate)添加到note对象中,用于表示笔记是否有提醒以及提醒的时间信息(以时间戳形式等)。 + note.put(NoteColumns.ALERTED_DATE, mAlertDate); + // 将笔记的背景颜色ID字段(NoteColumns.BG_COLOR_ID)及其对应的值(mBgColorId)添加到note对象中,方便后续在展示等场景中设置笔记的背景颜色。 + note.put(NoteColumns.BG_COLOR_ID, mBgColorId); + // 将笔记的创建日期字段(NoteColumns.CREATED_DATE)及其对应的值(mCreatedDate)添加到note对象中,记录笔记最初创建的时间信息(以时间戳形式)。 + note.put(NoteColumns.CREATED_DATE, mCreatedDate); + // 将笔记是否有附件字段(NoteColumns.HAS_ATTACHMENT)及其对应的值(mHasAttachment)添加到note对象中,用于标识该笔记是否关联了附件(比如文件等)。 + note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment); + // 将笔记的修改日期字段(NoteColumns.MODIFIED_DATE)及其对应的值(mModifiedDate)添加到note对象中,方便追踪笔记最后一次被修改的时间情况(以时间戳形式)。 + note.put(NoteColumns.MODIFIED_DATE, mModifiedDate); + // 将笔记的父级ID字段(NoteColumns.PARENT_ID)及其对应的值(mParentId)添加到note对象中,用于构建笔记的层级结构关系,比如属于哪个文件夹等情况。 + note.put(NoteColumns.PARENT_ID, mParentId); + // 将笔记的摘要字段(NoteColumns.SNIPPET)及其对应的值(mSnippet)添加到note对象中,摘要可能是笔记内容的简短描述等,方便快速预览笔记大致内容。 + note.put(NoteColumns.SNIPPET, mSnippet); + // 将笔记的类型字段(NoteColumns.TYPE)及其对应的值(mType)添加到note对象中,再次明确笔记的类型,虽然前面已经通过条件判断知道是普通笔记类型,但这里是完整的数据组装。 + note.put(NoteColumns.TYPE, mType); + // 将笔记的小部件ID字段(NoteColumns.WIDGET_ID)及其对应的值(mWidgetId)添加到note对象中,可能用于关联笔记与桌面小部件等相关的操作和展示情况。 + note.put(NoteColumns.WIDGET_ID, mWidgetId); + // 将笔记的小部件类型字段(NoteColumns.WIDGET_TYPE)及其对应的值(mWidgetType)添加到note对象中,用于区分不同样式、功能的小部件与笔记的关联情况。 + note.put(NoteColumns.WIDGET_TYPE, mWidgetType); + // 将笔记的原始父级ID字段(NoteColumns.ORIGIN_PARENT_ID)及其对应的值(mOriginParent)添加到note对象中,可能用于记录笔记在某些操作之前的原始归属情况等。 + note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent); + + // 将包含笔记主体属性信息的note对象,以GTaskStringUtils.META_HEAD_NOTE为键,添加到外层的js对象中,这样最终生成的JSON数据结构就符合特定的格式要求,方便后续解析使用。 + js.put(GTaskStringUtils.META_HEAD_NOTE, note); + + // 创建一个新的JSONArray对象,用于存放与该笔记相关的具体数据内容对应的JSON对象,这些具体数据可能是笔记正文的不同部分等详细信息。 + JSONArray dataArray = new JSONArray(); + + // 遍历当前SqlNote对象关联的mDataList列表,mDataList中存放的是与该笔记相关的SqlData类型对象,每个对象代表一部分具体数据内容。 + for (SqlData sqlData : mDataList) { + // 调用每个SqlData对象的getContent方法,获取其对应的JSON表示形式(返回一个JSONObject对象),该方法内部会根据SqlData对象自身的属性组装成相应的JSON数据结构。 + JSONObject data = sqlData.getContent(); + // 判断获取到的JSON对象是否为null,如果不为null,则表示该SqlData对象成功转换为有效的JSON数据,将其添加到dataArray数组中。 + if (data!= null) { + dataArray.put(data); + } + } + + // 将包含所有具体数据内容JSON对象的dataArray数组,以GTaskStringUtils.META_HEAD_DATA为键,添加到外层的js对象中,完善整个笔记的JSON数据结构,使其包含了主体属性和具体数据两部分内容。 + js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); + } else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) { + // 如果笔记类型是文件夹类型(Notes.TYPE_FOLDER)或者系统类型(Notes.TYPE_SYSTEM),则只将笔记的部分关键属性添加到note对象中。 + + // 将笔记的ID字段(NoteColumns.ID)及其对应的值(mId)添加到note对象中,确保有唯一标识符信息用于区分不同的文件夹或系统对象(如果是系统类型可能有特定用途等)。 + note.put(NoteColumns.ID, mId); + // 将笔记的类型字段(NoteColumns.TYPE)及其对应的值(mType)添加到note对象中,明确其是文件夹还是系统类型,方便后续处理逻辑识别。 + note.put(NoteColumns.TYPE, mType); + // 将笔记的摘要字段(NoteColumns.SNIPPET)及其对应的值(mSnippet)添加到note对象中,对于文件夹类型可能摘要就是文件夹名称等,方便展示和识别。 + note.put(NoteColumns.SNIPPET, mSnippet); + + // 将包含关键属性信息的note对象,以GTaskStringUtils.META_HEAD_NOTE为键,添加到外层的js对象中,形成符合特定格式要求的JSON数据结构,虽然相较于普通笔记类型数据内容会少一些,但满足对应类型的需求。 + js.put(GTaskStringUtils.META_HEAD_NOTE, note); + } + + // 如果整个JSON数据结构组装成功,没有出现异常情况,则返回最终组装好的js对象,该对象包含了符合要求的笔记相关信息的JSON表示形式。 + return js; + } catch (JSONException e) { + // 如果在构建JSON对象(如往JSONObject或JSONArray中添加元素等操作)过程中出现了JSONException异常,记录错误日志,打印异常堆栈信息,方便排查问题。 + Log.e(TAG, e.toString()); + e.printStackTrace(); + } + // 如果出现异常情况,最终返回null,表示无法成功获取笔记的JSON内容表示形式。 + return null; +} + + public void setParentId(long id) { + // 设置笔记的父ID,并将其添加到待更新的值中 + mParentId = id; + mDiffNoteValues.put(NoteColumns.PARENT_ID, id); +} + +public void setGtaskId(String gid) { + // 设置与Google任务同步的ID,并将其添加到待更新的值中 + mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); +} + +public void setSyncId(long syncId) { + // 设置同步ID,并将其添加到待更新的值中 + mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); +} + +public void resetLocalModified() { + // 重置本地修改标志 + mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); +} + +public long getId() { + // 获取笔记的ID + return mId; +} + +public long getParentId() { + // 获取笔记的父ID + return mParentId; +} + +public String getSnippet() { + // 获取笔记的摘要 + return mSnippet; +} + +public boolean isNoteType() { + // 检查笔记是否为普通笔记类型 + return mType == Notes.TYPE_NOTE; +} + +public void commit(boolean validateVersion) { + // 提交笔记的更改 + if (mIsCreate) { + // 如果是创建笔记 + if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) { + // 如果ID无效且待更新值中包含ID,则移除它 + mDiffNoteValues.remove(NoteColumns.ID); + } + + // 插入笔记并获取新插入的笔记ID + Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); + try { + mId = Long.valueOf(uri.getPathSegments().get(1)); + } catch (NumberFormatException e) { + // 处理获取ID时的异常 + Log.e(TAG, "Get note id error :" + e.toString()); + throw new ActionFailureException("create note failed"); + } + if (mId == 0) { + // 如果ID为0,则创建失败 + throw new IllegalStateException("Create thread id failed"); + } + + // 如果是普通笔记类型,则提交相关联的数据 + if (mType == Notes.TYPE_NOTE) { + for (SqlData sqlData : mDataList) { + sqlData.commit(mId, false, -1); + } + } + } else { + // 如果是更新笔记 + if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) { + // 如果ID无效,则抛出异常 + Log.e(TAG, "No such note"); + throw new IllegalStateException("Try to update note with invalid id"); + } + if (mDiffNoteValues.size() > 0) { + // 如果有待更新的值,则增加版本号并执行更新 + mVersion ++; + int result = 0; + if (!validateVersion) { + // 不验证版本号的更新 + result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" + + NoteColumns.ID + "=?)", new String[] { + String.valueOf(mId) + }); + } else { + // 验证版本号的更新 + result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" + + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", + new String[] { + String.valueOf(mId), String.valueOf(mVersion) + }); + } + if (result == 0) { + // 如果更新结果为0,可能是在同步时用户更新了笔记 + Log.w(TAG, "there is no update. maybe user updates note when syncing"); + } + } + + // 如果是普通笔记类型,则提交相关联的数据 + if (mType == Notes.TYPE_NOTE) { + for (SqlData sqlData : mDataList) { + sqlData.commit(mId, validateVersion, mVersion); + } + } + } + + // 刷新本地信息 + loadFromCursor(mId); + if (mType == Notes.TYPE_NOTE) + loadDataContent(); + + // 清除待更新的值,并标记为非创建状态 + mDiffNoteValues.clear(); + mIsCreate = false; +} -- 2.34.1 From b506085f718161d01100d4b6a611b70453395a42 Mon Sep 17 00:00:00 2001 From: p3o5nagmu <1329414441@qq.com> Date: Tue, 31 Dec 2024 03:27:03 +0800 Subject: [PATCH 10/13] ADD file via upload --- Task.java | 496 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 496 insertions(+) create mode 100644 Task.java diff --git a/Task.java b/Task.java new file mode 100644 index 0000000..b984686 --- /dev/null +++ b/Task.java @@ -0,0 +1,496 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.gtask.data; + +import android.database.Cursor; +import android.text.TextUtils; +import android.util.Log; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.DataConstants; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.gtask.exception.ActionFailureException; +import net.micode.notes.tool.GTaskStringUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + + +// Task类继承自Node类,用于表示一个任务相关的对象,可能在任务管理等业务场景中使用,包含了任务的一些属性以及操作方法。 +public class Task extends Node { + // 定义一个用于日志记录的标签,其值为类的简单名称(不含包名部分),方便在查看日志时能快速识别与该类相关的输出信息。 + private static final String TAG = Task.class.getSimpleName(); + + // 用于标记任务是否已完成的布尔变量,初始值为false,表示任务初始状态是未完成的。 + private boolean mCompleted; + + // 用于存储任务相关的备注信息(可能是文字描述等内容),初始值为null,后续可以根据实际情况进行赋值。 + private String mNotes; + + // 用于存储任务的元信息,以JSONObject的形式表示,可能包含一些额外的自定义属性等内容,初始值为null,会按需填充数据。 + private JSONObject mMetaInfo; + + // 用于指向该任务的前一个兄弟任务(在同一层级的任务列表中,排在当前任务之前的任务对象),初始值为null,若不存在前序兄弟任务则保持为null。 + private Task mPriorSibling; + + // 用于指向该任务所属的父任务列表(可以理解为包含该任务的集合或者分组),初始值为null,后续会进行相应的赋值关联操作。 + private TaskList mParent; + + // 无参构造函数,用于创建Task对象时进行默认的初始化操作,调用父类(Node类)的无参构造函数,并对本类的一些成员变量赋初始值。 + public Task() { + super(); + mCompleted = false; + mNotes = null; + mPriorSibling = null; + mParent = null; + mMetaInfo = null; + } + + // getCreateAction方法用于生成一个表示创建任务操作的JSONObject对象,该JSON对象包含了创建任务所需的各种相关信息, + // 例如操作类型、任务的基本属性、在父任务列表中的位置等信息,若在构建JSON过程中出现异常,则抛出ActionFailureException异常。 + public JSONObject getCreateAction(int actionId) { + // 创建一个新的JSONObject对象,用于组装并最终返回包含创建任务操作相关信息的JSON数据结构。 + JSONObject js = new JSONObject(); + + try { + // 设置JSON对象中的"action_type"字段,表示操作类型,将其值设为GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE, + // 意味着这个JSON数据代表的是创建任务的操作,方便后续解析该JSON数据的模块能识别操作意图。 + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); + + // 设置JSON对象中的"action_id"字段,将传入的actionId参数值赋给该字段,actionId可能是用于唯一标识这个创建任务操作的序号等, + // 在整个任务管理系统中便于区分不同的操作实例。 + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + + // 设置JSON对象中的"index"字段,通过调用mParent(所属的父任务列表对象)的getChildTaskIndex方法,传入当前任务对象(this), + // 获取当前任务在父任务列表中的索引位置,并将其赋给该字段,用于表示任务在所属分组中的顺序等情况。 + js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); + + // 创建一个新的JSONObject对象,用于组装任务实体相关的详细信息,后续会将其作为整体放入外层的js对象中,代表要创建的任务主体内容。 + JSONObject entity = new JSONObject(); + + // 设置entity JSON对象中的"name"字段,通过调用getName方法(该方法应该是在父类Node或者本类中定义的用于获取任务名称的方法)获取任务名称, + // 并将其赋给该字段,这样在创建任务的JSON数据中包含了任务的名称信息,方便识别任务。 + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + + // 设置entity JSON对象中的"creator_id"字段,暂时将其值设为"null",可能后续需要根据实际的创建者标识来进行准确赋值, + // 这里先占位表示创建者信息暂未明确或者还未填充。 + entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); + + // 设置entity JSON对象中的"entity_type"字段,将其值设为GTaskStringUtils.GTASK_JSON_TYPE_TASK, + // 明确表示这个实体是任务类型,便于系统区分不同类型的实体(比如可能还有分组类型等其他实体)。 + entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, + GTaskStringUtils.GTASK_JSON_TYPE_TASK); + + // 判断任务的备注信息(mNotes)是否不为null,如果有备注内容,则将其添加到entity JSON对象的"notes"字段中, + // 使得创建任务的JSON数据可以包含任务相关的备注描述信息,方便查看任务详情时有更多参考内容。 + if (getNotes()!= null) { + entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); + } + + // 将包含任务实体详细信息的entity对象,以GTaskStringUtils.GTASK_JSON_ENTITY_DELTA为键,添加到外层的js对象中, + // 这样整个JSON数据结构就完整地包含了创建任务操作以及要创建的任务具体内容等信息。 + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + + // 设置JSON对象中的"parent_id"字段,通过调用mParent(所属的父任务列表对象)的getGid方法获取父任务列表的唯一标识符(Gid), + // 并将其赋给该字段,表示要创建的任务所属的父任务分组,建立任务的层级关系。 + js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); + + // 设置JSON对象中的"dest_parent_type"字段,将其值设为GTaskStringUtils.GTASK_JSON_TYPE_GROUP, + // 表示目标父级的类型是分组,用于明确创建任务时其所属的父级实体在整个系统中的类型定位。 + js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, + GTaskStringUtils.GTASK_JSON_TYPE_GROUP); + + // 设置JSON对象中的"list_id"字段,同样通过调用mParent(所属的父任务列表对象)的getGid方法获取父任务列表的唯一标识符(Gid), + // 并将其赋给该字段,可能用于在列表相关操作中进一步标识任务所属的列表情况,与"parent_id"字段作用类似但可能在不同场景下使用。 + js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); + + // 判断前序兄弟任务(mPriorSibling)是否不为null,如果存在前序兄弟任务,则调用其getGid方法获取前序兄弟任务的唯一标识符(Gid), + // 并将其赋给JSON对象的"prior_sibling_id"字段,用于表示任务在兄弟任务序列中的先后顺序关系,方便在插入任务等操作时确定位置。 + if (mPriorSibling!= null) { + js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); + } + + } catch (JSONException e) { + // 如果在构建JSON对象(如往JSONObject中添加元素等操作)过程中出现了JSONException异常,记录错误日志,打印异常堆栈信息, + // 方便排查问题,同时抛出ActionFailureException异常,表示生成创建任务的JSON对象失败,以便上层调用者能捕获并处理该异常情况。 + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to generate task-create jsonobject"); + } + + // 如果成功构建了包含创建任务操作相关信息的JSON对象,没有出现异常情况,则返回该js对象,供后续操作(比如发送给服务器等场景)使用。 + return js; + } +} + // getUpdateAction方法用于生成一个表示更新任务操作的JSONObject对象,该JSON对象包含了更新任务所需的各种关键信息, +// 例如操作类型、任务的唯一标识符以及要更新的任务具体属性等内容,若在构建JSON过程中出现异常,则抛出ActionFailureException异常。 +public JSONObject getUpdateAction(int actionId) { + // 创建一个新的JSONObject对象,用于组装并最终返回包含更新任务操作相关信息的JSON数据结构。 + JSONObject js = new JSONObject(); + + try { + // 设置JSON对象中的"action_type"字段,表示操作类型,将其值设为GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE, + // 意味着这个JSON数据代表的是更新任务的操作,方便后续解析该JSON数据的模块能识别操作意图。 + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); + + // 设置JSON对象中的"action_id"字段,将传入的actionId参数值赋给该字段,actionId可能是用于唯一标识这个更新任务操作的序号等, + // 在整个任务管理系统中便于区分不同的操作实例。 + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + + // 设置JSON对象中的"id"字段,通过调用getGid方法(该方法应该是在当前类或者父类中定义的用于获取任务唯一标识符的方法)获取任务的唯一标识符(Gid), + // 并将其赋给该字段,用于明确要更新的是哪个具体任务,建立与具体任务实体的关联。 + js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); + + // 创建一个新的JSONObject对象,用于组装任务实体相关的详细信息,这些信息就是此次更新操作中涉及要改变的任务属性内容, + // 后续会将其作为整体放入外层的js对象中,代表更新任务时具体的变更内容。 + JSONObject entity = new JSONObject(); + + // 设置entity JSON对象中的"name"字段,通过调用getName方法(用于获取任务名称的方法)获取任务名称, + // 并将其赋给该字段,这样在更新任务的JSON数据中包含了可能要更新的任务名称信息,方便根据实际需求修改任务的显示名称等情况。 + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + + // 判断任务的备注信息(getNotes方法获取)是否不为null,如果有备注内容,则将其添加到entity JSON对象的"notes"字段中, + // 意味着更新任务操作可以包含对任务备注信息的修改,使得任务的相关描述内容能按需更新。 + if (getNotes()!= null) { + entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); + } + + // 设置entity JSON对象中的"deleted"字段,通过调用getDeleted方法(用于获取任务是否已删除状态的方法)获取任务的删除状态, + // 并将其赋给该字段,表明更新任务操作也可以涉及任务删除状态的变更,比如将未删除的任务标记为已删除等情况。 + entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); + + // 将包含任务实体详细信息(要更新的具体属性内容)的entity对象,以GTaskStringUtils.GTASK_JSON_ENTITY_DELTA为键,添加到外层的js对象中, + // 这样整个JSON数据结构就完整地包含了更新任务操作以及具体要更新的任务内容等信息。 + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + + } catch (JSONException e) { + // 如果在构建JSON对象(如往JSONObject中添加元素等操作)过程中出现了JSONException异常,记录错误日志,打印异常堆栈信息, + // 方便排查问题,同时抛出ActionFailureException异常,表示生成更新任务的JSON对象失败,以便上层调用者能捕获并处理该异常情况。 + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to generate task-update jsonobject"); + } + + // 如果成功构建了包含更新任务操作相关信息的JSON对象,没有出现异常情况,则返回该js对象,供后续操作(比如发送给服务器等场景)使用。 + return js; +} + +// setContentByRemoteJSON方法用于根据传入的JSONObject对象(通常是从远程获取的包含任务相关信息的JSON数据)来设置当前Task对象的各项属性值, +// 若传入的JSON对象为null或者在解析JSON过程中出现异常,则抛出ActionFailureException异常。 +public void setContentByRemoteJSON(JSONObject js) { + if (js!= null) { + try { + // 判断传入的JSON对象(js)中是否包含"id"字段,如果包含,则调用setGid方法(用于设置任务唯一标识符的方法), + // 并将该字段对应的字符串值(通过getString方法获取)传递进去,实现根据远程JSON数据更新当前任务对象的唯一标识符属性。 + if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { + setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); + } + + // 判断传入的JSON对象(js)中是否包含"last_modified"字段,如果包含,则调用setLastModified方法(用于设置任务最后修改时间的方法), + // 并将该字段对应的长整型值(通过getLong方法获取)传递进去,实现根据远程JSON数据更新当前任务对象的最后修改时间属性,用于记录任务最新的修改情况。 + if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { + setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); + } + + // 判断传入的JSON对象(js)中是否包含"name"字段,如果包含,则调用setName方法(用于设置任务名称的方法), + // 并将该字段对应的字符串值(通过getString方法获取)传递进去,实现根据远程JSON数据更新当前任务对象的任务名称属性,以便任务显示正确的名称。 + if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { + setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); + } + + // 判断传入的JSON对象(js)中是否包含"notes"字段,如果包含,则调用setNotes方法(用于设置任务备注信息的方法), + // 并将该字段对应的字符串值(通过getString方法获取)传递进去,实现根据远程JSON数据更新当前任务对象的备注信息属性,使备注内容与远程数据保持一致。 + if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) { + setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); + } + + // 判断传入的JSON对象(js)中是否包含"deleted"字段,如果包含,则调用setDeleted方法(用于设置任务是否已删除状态的方法), + // 并将该字段对应的布尔值(通过getBoolean方法获取)传递进去,实现根据远程JSON数据更新当前任务对象的删除状态属性,比如标记任务是否已被删除。 + if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) { + setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); + } + + // 判断传入的JSON对象(js)中是否包含"completed"字段,如果包含,则调用setCompleted方法(用于设置任务是否已完成状态的方法), + // 并将该字段对应的布尔值(通过getBoolean方法获取)传递进去,实现根据远程JSON数据更新当前任务对象的完成状态属性,准确反映任务的完成情况。 + if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) { + setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); + } + } catch (JSONException e) { + // 如果在解析JSON对象(如获取对应字段的值等操作)过程中出现了JSONException异常,记录错误日志,打印异常堆栈信息, + // 方便排查问题,同时抛出ActionFailureException异常,表示从JSON对象中获取任务内容并设置到当前对象失败,以便上层调用者能捕获并处理该异常情况。 + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to get task content from jsonobject"); + } + } +} + + // setContentByLocalJSON方法用于根据本地的JSONObject对象来设置当前Task对象的相关内容, +// 如果传入的JSON对象不符合要求(为null或者缺少关键的节点信息),则记录警告日志;若解析JSON出现异常也会记录错误日志。 +public void setContentByLocalJSON(JSONObject js) { + // 判断传入的JSON对象是否为null,或者是否不包含名为GTaskStringUtils.META_HEAD_NOTE的节点, + // 又或者是否不包含名为GTaskStringUtils.META_HEAD_DATA的节点,如果满足这些情况之一,则表示该JSON对象不符合处理要求。 + if (js == null ||!js.has(GTaskStringUtils.META_HEAD_NOTE) + ||!js.has(GTaskStringUtils.META_HEAD_DATA)) { + Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); + } + + try { + // 从传入的JSON对象中获取名为GTaskStringUtils.META_HEAD_NOTE的子JSON对象,该对象预期包含了笔记主体相关的属性信息,比如类型等关键内容。 + JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + // 从传入的JSON对象中获取名为GTaskStringUtils.META_HEAD_DATA的JSON数组,该数组预期包含了与该任务相关的多条具体数据内容。 + JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + + // 判断笔记类型是否不等于普通笔记类型(Notes.TYPE_NOTE),如果不是普通笔记类型,则记录错误日志表示类型无效,然后直接返回,不进行后续操作。 + if (note.getInt(NoteColumns.TYPE)!= Notes.TYPE_NOTE) { + Log.e(TAG, "invalid type"); + return; + } + + // 遍历GTaskStringUtils.META_HEAD_DATA数组中的每一个JSON对象(即遍历与该任务相关的每一条具体数据内容)。 + for (int i = 0; i < dataArray.length(); i++) { + JSONObject data = dataArray.getJSONObject(i); + // 判断当前具体数据对象(data)中的DataColumns.MIME_TYPE字段对应的字符串值是否等于DataConstants.NOTE, + // 如果相等,表示找到了与任务名称相关的数据内容(这里根据特定的类型标识来判断是否是任务名称相关的数据)。 + if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { + // 调用setName方法(用于设置任务名称的方法),将该数据对象中DataColumns.CONTENT字段对应的字符串值作为任务名称传递进去,实现根据本地JSON数据更新任务名称, + // 找到符合要求的数据后就通过break跳出循环,因为只需要找到任务名称对应的那部分数据进行处理即可。 + setName(data.getString(DataColumns.CONTENT)); + break; + } + } + + } catch (JSONException e) { + // 如果在解析JSON对象(如获取子对象、获取数组元素、获取字段值等操作)过程中出现了JSONException异常,记录错误日志,打印异常堆栈信息,方便排查问题。 + Log.e(TAG, e.toString()); + e.printStackTrace(); + } +} + +// getLocalJSONFromContent方法用于根据当前Task对象的内容生成对应的本地JSONObject表示形式, +// 如果在构建JSON过程中出现异常,则记录错误日志并返回null;会根据不同情况(新创建任务或已同步任务)进行不同的JSON组装逻辑。 +public JSONObject getLocalJSONFromContent() { + // 获取当前任务对象的名称,后续会根据名称以及其他相关属性来组装JSON数据。 + String name = getName(); + try { + // 判断当前任务对象的元信息(mMetaInfo,以JSONObject形式存储,可能包含了更丰富的任务相关信息)是否为null。 + if (mMetaInfo == null) { + // 如果mMetaInfo为null,表示这可能是一个从网页端新创建的任务(根据注释推测的情况)。 + // 进一步判断任务名称是否为null,如果名称也为null,则记录警告日志表示这个笔记似乎是空的,然后直接返回null,因为没有足够信息构建JSON。 + if (name == null) { + Log.w(TAG, "the note seems to be an empty one"); + return null; + } + + // 创建一个新的JSONObject对象,用于组装并最终返回包含任务相关信息的本地JSON数据结构。 + JSONObject js = new JSONObject(); + // 创建一个新的JSONObject对象,用于存放笔记主体相关的属性信息,后续会往里面添加相应字段数据。 + JSONObject note = new JSONObject(); + // 创建一个新的JSONArray对象,用于存放与该任务相关的具体数据内容对应的JSON对象,这些具体数据可能是任务正文等详细信息。 + JSONArray dataArray = new JSONArray(); + // 创建一个新的JSONObject对象,用于表示一条具体的数据内容,这里先准备组装任务名称相关的数据。 + JSONObject data = new JSONObject(); + + // 将任务名称(name)添加到data对象的DataColumns.CONTENT字段中,这样就在数据对象中记录了任务名称相关信息。 + data.put(DataColumns.CONTENT, name); + // 将包含任务名称数据的data对象添加到dataArray数组中,使得dataArray包含了任务相关的具体数据内容(目前只有名称相关的这一条数据)。 + dataArray.put(data); + + // 将包含具体数据内容的dataArray数组,以GTaskStringUtils.META_HEAD_DATA为键,添加到外层的js对象中,完善整个任务的JSON数据结构的一部分。 + js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); + + // 将笔记类型字段(NoteColumns.TYPE)的值设为普通笔记类型(Notes.TYPE_NOTE),并添加到note对象中,明确任务的类型属性。 + note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); + + // 将包含笔记主体属性信息的note对象,以GTaskStringUtils.META_HEAD_NOTE为键,添加到外层的js对象中,形成完整的符合要求的本地JSON数据结构,代表新创建任务的情况。 + js.put(GTaskStringUtils.META_HEAD_NOTE, note); + + // 返回组装好的js对象,即包含了新创建任务相关信息的本地JSON表示形式。 + return js; + } else { + // 如果mMetaInfo不为null,表示这是一个已同步过的任务,已有完整的元信息,基于这些元信息来生成要返回的本地JSON数据。 + + // 从mMetaInfo中获取名为GTaskStringUtils.META_HEAD_NOTE的子JSON对象,该对象包含了笔记主体相关属性信息。 + JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + // 从mMetaInfo中获取名为GTaskStringUtils.META_HEAD_DATA的JSON数组,该数组包含了与任务相关的多条具体数据内容。 + JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + + // 遍历GTaskStringUtils.META_HEAD_DATA数组中的每一个JSON对象(即遍历与该任务相关的每一条具体数据内容),查找与任务名称相关的数据部分。 + for (int i = 0; i < dataArray.length(); i++) { + JSONObject data = dataArray.getJSONObject(i); + // 判断当前具体数据对象(data)中的DataColumns.MIME_TYPE字段对应的字符串值是否等于DataConstants.NOTE, + // 如果相等,表示找到了与任务名称相关的数据内容(这里根据特定的类型标识来判断是否是任务名称相关的数据)。 + if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { + // 将任务名称(通过getName方法获取当前任务对象的名称)更新到该数据对象的DataColumns.CONTENT字段中,实现将最新的任务名称同步到JSON数据里, + // 找到符合要求的数据后就通过break跳出循环,因为只需要更新任务名称对应的那部分数据即可。 + data.put(DataColumns.CONTENT, getName()); + break; + } + } + + // 将笔记类型字段(NoteColumns.TYPE)的值设为普通笔记类型(Notes.TYPE_NOTE),并更新到note对象中,再次明确任务的类型属性,确保JSON数据中类型正确。 + note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); + + // 返回mMetaInfo对象,此时mMetaInfo已经更新了任务名称相关数据以及明确了任务类型,它代表了已同步任务的本地JSON数据结构,可供外部使用。 + return mMetaInfo; + } + } catch (JSONException e) { + // 如果在构建JSON对象(如往JSONObject或JSONArray中添加元素等操作)过程中出现了JSONException异常,记录错误日志,打印异常堆栈信息,方便排查问题。 + Log.e(TAG, e.toString()); + e.printStackTrace(); + // 如果出现异常情况,最终返回null,表示无法成功获取任务对应的本地JSON内容表示形式。 + return null; + } +} + + // setMetaInfo方法用于设置当前Task对象的元信息(mMetaInfo),它会尝试将传入的MetaData对象中的笔记信息(以字符串形式)转换为JSONObject格式并赋值给mMetaInfo, +// 如果在转换过程中出现JSONException异常,则记录警告日志,并将mMetaInfo设为null,表示设置元信息失败。 +public void setMetaInfo(MetaData metaData) { + // 首先判断传入的metaData对象不为null,并且其getNotes方法返回的笔记信息也不为null,才进行后续操作, + // 即只有当有有效元数据且其中的笔记信息存在时才尝试处理。 + if (metaData!= null && metaData.getNotes()!= null) { + try { + // 使用传入的MetaData对象中获取到的笔记信息字符串创建一个新的JSONObject对象, + // 这一步是将字符串形式的笔记信息转换为便于操作的JSON对象格式,方便后续根据JSON结构获取具体的属性等内容,然后将其赋值给mMetaInfo,完成元信息的设置。 + mMetaInfo = new JSONObject(metaData.getNotes()); + } catch (JSONException e) { + // 如果在创建JSONObject对象的过程中(也就是解析字符串为JSON格式时)出现了JSONException异常,记录警告日志(因为这只是一个可能不影响整体流程继续的错误情况), + // 并将mMetaInfo设为null,表示此次设置元信息操作失败,后续使用mMetaInfo时需要考虑其为null的情况。 + Log.w(TAG, e.toString()); + mMetaInfo = null; + } + } +} + +// getSyncAction方法用于根据传入的Cursor对象(通常用于从数据库查询结果中获取数据)以及当前Task对象的相关状态, +// 来确定在数据同步操作中应该执行的具体动作,返回对应的同步动作标识(SYNC_ACTION_* 系列常量之一),若出现异常则返回SYNC_ACTION_ERROR。 +public int getSyncAction(Cursor c) { + try { + JSONObject noteInfo = null; + // 判断当前Task对象的元信息(mMetaInfo)不为null,并且其中包含名为GTaskStringUtils.META_HEAD_NOTE的节点, + // 如果满足条件,则尝试获取该节点对应的JSONObject对象,这个对象预期包含了笔记主体相关的关键属性信息,后续用于各种判断和比较。 + if (mMetaInfo!= null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) { + noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + } + + // 如果noteInfo为null,意味着可能笔记的元信息已经被删除了(根据逻辑推测),记录警告日志,并返回SYNC_ACTION_UPDATE_REMOTE, + // 表示在同步操作中需要将远程数据更新到本地,可能本地的相关数据已经不完整或缺失了。 + if (noteInfo == null) { + Log.w(TAG, "it seems that note meta has been deleted"); + return SYNC_ACTION_UPDATE_REMOTE; + } + + // 判断noteInfo中是否不包含名为NoteColumns.ID的字段,如果不包含,说明远程笔记的ID似乎被删除了(根据逻辑推测), + // 记录警告日志,并返回SYNC_ACTION_UPDATE_LOCAL,表示在同步操作中需要用本地数据去更新远程数据,可能远程数据出现了关键信息缺失的情况。 + if (!noteInfo.has(NoteColumns.ID)) { + Log.w(TAG, "remote note id seems to be deleted"); + return SYNC_ACTION_UPDATE_LOCAL; + } + + // 验证笔记的ID是否匹配,通过比较从Cursor对象(数据库查询结果)中获取的笔记ID(通过SqlNote.ID_COLUMN指定的列获取长整型ID值), + // 和从noteInfo中获取的笔记ID(通过NoteColumns.ID字段获取长整型ID值)是否相等,如果不相等,记录警告日志, + // 并返回SYNC_ACTION_UPDATE_LOCAL,表示需要以本地数据为准进行更新,可能存在两边数据不一致的情况。 + if (c.getLong(SqlNote.ID_COLUMN)!= noteInfo.getLong(NoteColumns.ID)) { + Log.w(TAG, "note id doesn't match"); + return SYNC_ACTION_UPDATE_LOCAL; + } + + // 判断本地是否没有更新,通过检查Cursor对象中代表本地修改状态的列(SqlNote.LOCAL_MODIFIED_COLUMN指定的列)的值是否为0来确定, + // 如果为0,表示本地没有进行过更新操作。 + if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { + // 进一步判断远程和本地是否都没有更新,通过比较Cursor对象中代表同步ID的列(SqlNote.SYNC_ID_COLUMN指定的列)的值, + // 和当前Task对象的最后修改时间(通过getLastModified方法获取)是否相等来确定,如果相等,说明两边数据都没有更新, + // 则返回SYNC_ACTION_NONE,表示不需要进行任何同步操作,两边数据是一致的。 + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + return SYNC_ACTION_NONE; + } else { + // 如果同步ID不相等,说明远程有更新,需要将远程数据应用到本地,返回SYNC_ACTION_UPDATE_LOCAL,表示以远程数据更新本地数据。 + return SYNC_ACTION_UPDATE_LOCAL; + } + } else { + // 如果本地有更新(即本地修改状态列的值不为0),则需要进一步验证任务的Gtask ID是否匹配, + // 通过比较Cursor对象中代表Gtask ID的列(SqlNote.GTASK_ID_COLUMN指定的列)获取的字符串值, + // 和当前Task对象通过getGid方法获取的Gtask ID是否相等来确定,如果不相等,记录错误日志(因为这可能是比较严重的不一致情况), + // 并返回SYNC_ACTION_ERROR,表示出现了同步错误,两边的关键标识不一致了。 + if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { + Log.e(TAG, "gtask id doesn't match"); + return SYNC_ACTION_ERROR; + } + // 再次判断远程和本地的更新情况,通过比较Cursor对象中代表同步ID的列(SqlNote.SYNC_ID_COLUMN指定的列)的值, + // 和当前Task对象的最后修改时间(通过getLastModified方法获取)是否相等来确定,如果相等,说明只有本地进行了修改, + // 则返回SYNC_ACTION_UPDATE_REMOTE,表示需要将本地的修改同步到远程,让远程数据也更新为本地修改后的状态。 + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + return SYNC_ACTION_UPDATE_REMOTE; + } else { + // 如果同步ID不相等,说明远程和本地都有不同的修改,出现了数据冲突情况,返回SYNC_ACTION_UPDATE_CONFLICT,表示需要处理同步冲突, + // 例如可能需要人工介入或者按照特定的冲突解决策略来决定以哪边数据为准等操作。 + return SYNC_ACTION_UPDATE_CONFLICT; + } + } + } catch (Exception e) { + // 如果在整个方法执行过程中出现了任何异常(捕获了Exception类型,涵盖了各种可能的运行时异常等情况),记录错误日志,打印异常堆栈信息,方便排查问题, + // 并返回SYNC_ACTION_ERROR,表示出现了同步相关的错误情况,无法准确判断同步动作。 + Log.e(TAG, e.toString()); + e.printStackTrace(); + } + + // 如果前面执行过程中出现了未处理的情况或者最终正常流程执行完毕但没有返回合适的同步动作标识,默认返回SYNC_ACTION_ERROR,表示出现错误。 + return SYNC_ACTION_ERROR; +} + + public boolean isWorthSaving() { + return mMetaInfo != null || (getName() != null && getName().trim().length() > 0) + || (getNotes() != null && getNotes().trim().length() > 0); + } + + public void setCompleted(boolean completed) { + this.mCompleted = completed; + } + + public void setNotes(String notes) { + this.mNotes = notes; + } + + public void setPriorSibling(Task priorSibling) { + this.mPriorSibling = priorSibling; + } + + public void setParent(TaskList parent) { + this.mParent = parent; + } + + public boolean getCompleted() { + return this.mCompleted; + } + + public String getNotes() { + return this.mNotes; + } + + public Task getPriorSibling() { + return this.mPriorSibling; + } + + public TaskList getParent() { + return this.mParent; + } + +} -- 2.34.1 From 7e999f5d3e79444f29e9f580c9516cb79784cbd5 Mon Sep 17 00:00:00 2001 From: p3o5nagmu <1329414441@qq.com> Date: Tue, 31 Dec 2024 03:27:13 +0800 Subject: [PATCH 11/13] ADD file via upload --- TaskList.java | 504 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 504 insertions(+) create mode 100644 TaskList.java diff --git a/TaskList.java b/TaskList.java new file mode 100644 index 0000000..4b76fc3 --- /dev/null +++ b/TaskList.java @@ -0,0 +1,504 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.gtask.data; + +import android.database.Cursor; +import android.util.Log; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.gtask.exception.ActionFailureException; +import net.micode.notes.tool.GTaskStringUtils; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; + + +// TaskList类继承自Node类,可理解为用于表示任务列表相关的对象,包含了任务列表自身的一些属性以及操作方法。 +public class TaskList extends Node { + // 定义一个用于日志记录的标签,其值为类的简单名称(不含包名部分),便于在日志中清晰识别与该类相关的输出信息。 + private static final String TAG = TaskList.class.getSimpleName(); + + // 用于记录任务列表的索引位置(可能在某个更大的分组或者排序体系中的序号等),初始值为1,后续可根据实际情况改变。 + private int mIndex; + + // 用于存储该任务列表包含的多个任务对象,以ArrayList形式进行管理,方便进行任务的添加、删除、遍历等操作,初始化为一个空的ArrayList。 + private ArrayList mChildren; + + // 无参构造函数,用于创建TaskList对象时进行默认的初始化操作,先调用父类(Node类)的无参构造函数,然后初始化mChildren为一个新的空ArrayList,并将mIndex设为1。 + public TaskList() { + super(); + mChildren = new ArrayList(); + mIndex = 1; + } + + // getCreateAction方法用于生成一个表示创建任务列表操作的JSONObject对象,该JSON对象包含了创建任务列表所需的关键信息, + // 例如操作类型、任务列表的基本属性等内容,若在构建JSON过程中出现异常,则抛出ActionFailureException异常。 + public JSONObject getCreateAction(int actionId) { + // 创建一个新的JSONObject对象,用于组装并最终返回包含创建任务列表操作相关信息的JSON数据结构。 + JSONObject js = new JSONObject(); + + try { + // 设置JSON对象中的"action_type"字段,表示操作类型,将其值设为GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE, + // 明确该JSON数据代表的是创建任务列表的操作,方便后续解析此JSON的模块知晓操作意图。 + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); + + // 设置JSON对象中的"action_id"字段,把传入的actionId参数值赋给该字段,actionId可能用于唯一标识这个创建任务列表操作的序号等,便于区分不同操作实例。 + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + + // 设置JSON对象中的"index"字段,将当前任务列表对象的索引位置(mIndex)赋给该字段,用于表示任务列表在所属体系中的顺序等相关情况。 + js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); + + // 创建一个新的JSONObject对象,用于组装任务列表实体相关的详细信息,后续会将其整体放入外层的js对象中,代表要创建的任务列表主体内容。 + JSONObject entity = new JSONObject(); + + // 设置entity JSON对象中的"name"字段,通过调用getName方法(应是在父类Node或者本类中定义的获取名称的方法)获取任务列表名称,并赋给该字段,使创建操作的JSON数据包含名称信息,便于识别。 + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + + // 设置entity JSON对象中的"creator_id"字段,暂时将其值设为"null",可能后续需根据实际创建者标识准确赋值,这里先占位表示创建者信息暂未明确。 + entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); + + // 设置entity JSON对象中的"entity_type"字段,将其值设为GTaskStringUtils.GTASK_JSON_TYPE_GROUP, + // 表明这个实体是任务列表类型(分组类型),方便系统区分不同类型的实体。 + entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, + GTaskStringUtils.GTASK_JSON_TYPE_GROUP); + + // 将包含任务列表实体详细信息的entity对象,以GTaskStringUtils.GTASK_JSON_ENTITY_DELTA为键,添加到外层的js对象中, + // 这样整个JSON数据结构就完整包含了创建任务列表操作以及要创建的任务列表具体内容等信息。 + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + + } catch (JSONException e) { + // 如果在构建JSON对象(如往JSONObject添加元素等操作)过程中出现JSONException异常,记录错误日志,打印异常堆栈信息,方便排查问题, + // 同时抛出ActionFailureException异常,表示生成创建任务列表的JSON对象失败,以便上层调用者捕获并处理该异常情况。 + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to generate tasklist-create jsonobject"); + } + + // 如果成功构建了包含创建任务列表操作相关信息的JSON对象,无异常情况,则返回该js对象,供后续操作(如发送给服务器等场景)使用。 + return js; + } +} + // getUpdateAction方法用于生成一个表示更新任务列表操作的JSONObject对象,该JSON对象包含了更新任务列表所需的关键信息, +// 例如操作类型、任务列表的唯一标识符以及要更新的任务列表具体属性等内容,若在构建JSON过程中出现异常,则抛出ActionFailureException异常。 +public JSONObject getUpdateAction(int actionId) { + // 创建一个新的JSONObject对象,用于组装并最终返回包含更新任务列表操作相关信息的JSON数据结构。 + JSONObject js = new JSONObject(); + + try { + // 设置JSON对象中的"action_type"字段,表示操作类型,将其值设为GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE, + // 意味着这个JSON数据代表的是更新任务列表的操作,方便后续解析该JSON数据的模块能识别操作意图。 + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); + + // 设置JSON对象中的"action_id"字段,将传入的actionId参数值赋给该字段,actionId可能是用于唯一标识这个更新任务列表操作的序号等, + // 在整个任务管理系统中便于区分不同的操作实例。 + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + + // 设置JSON对象中的"id"字段,通过调用getGid方法(该方法应该是在当前类或者父类中定义的用于获取任务列表唯一标识符的方法)获取任务列表的唯一标识符(Gid), + // 并将其赋给该字段,用于明确要更新的是哪个具体任务列表,建立与具体任务列表实体的关联。 + js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); + + // 创建一个新的JSONObject对象,用于组装任务列表实体相关的详细信息,这些信息就是此次更新操作中涉及要改变的任务列表属性内容, + // 后续会将其作为整体放入外层的js对象中,代表更新任务列表时具体的变更内容。 + JSONObject entity = new JSONObject(); + + // 设置entity JSON对象中的"name"字段,通过调用getName方法(用于获取任务列表名称的方法)获取任务列表名称, + // 并将其赋给该字段,这样在更新任务列表的JSON数据中包含了可能要更新的任务列表名称信息,方便根据实际需求修改任务列表的显示名称等情况。 + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + + // 设置entity JSON对象中的"deleted"字段,通过调用getDeleted方法(用于获取任务列表是否已删除状态的方法)获取任务列表的删除状态, + // 并将其赋给该字段,表明更新任务列表操作也可以涉及任务列表删除状态的变更,比如将未删除的任务列表标记为已删除等情况。 + entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); + + // 将包含任务列表实体详细信息(要更新的具体属性内容)的entity对象,以GTaskStringUtils.GTASK_JSON_ENTITY_DELTA为键,添加到外层的js对象中, + // 这样整个JSON数据结构就完整地包含了更新任务列表操作以及具体要更新的任务列表内容等信息。 + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + + } catch (JSONException e) { + // 如果在构建JSON对象(如往JSONObject中添加元素等操作)过程中出现了JSONException异常,记录错误日志,打印异常堆栈信息, + // 方便排查问题,同时抛出ActionFailureException异常,表示生成更新任务列表的JSON对象失败,以便上层调用者能捕获并处理该异常情况。 + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to generate tasklist-update jsonobject"); + } + + // 如果成功构建了包含更新任务列表操作相关信息的JSON对象,没有出现异常情况,则返回该js对象,供后续操作(比如发送给服务器等场景)使用。 + return js; +} + +// setContentByRemoteJSON方法用于根据传入的JSONObject对象(通常是从远程获取的包含任务列表相关信息的JSON数据)来设置当前TaskList对象的各项属性值, +// 若传入的JSON对象为null或者在解析JSON过程中出现异常,则抛出ActionFailureException异常。 +public void setContentByRemoteJSON(JSONObject js) { + if (js!= null) { + try { + // 判断传入的JSON对象(js)中是否包含"id"字段,如果包含,则调用setGid方法(用于设置任务列表唯一标识符的方法), + // 并将该字段对应的字符串值(通过getString方法获取)传递进去,实现根据远程JSON数据更新当前任务列表对象的唯一标识符属性。 + if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { + setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); + } + + // 判断传入的JSON对象(js)中是否包含"last_modified"字段,如果包含,则调用setLastModified方法(用于设置任务列表最后修改时间的方法), + // 并将该字段对应的长整型值(通过getLong方法获取)传递进去,实现根据远程JSON数据更新当前任务列表对象的最后修改时间属性,用于记录任务列表最新的修改情况。 + if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { + setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); + } + + // 判断传入的JSON对象(js)中是否包含"name"字段,如果包含,则调用setName方法(用于设置任务列表名称的方法), + // 并将该字段对应的字符串值(通过getString方法获取)传递进去,实现根据远程JSON数据更新当前任务列表对象的任务列表名称属性,以便任务列表显示正确的名称。 + if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { + setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); + } + + } catch (JSONException e) { + // 如果在解析JSON对象(如获取对应字段的值等操作)过程中出现了JSONException异常,记录错误日志,打印异常堆栈信息, + // 方便排查问题,同时抛出ActionFailureException异常,表示从JSON对象中获取任务列表内容并设置到当前对象失败,以便上层调用者能捕获并处理该异常情况。 + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to get tasklist content from jsonobject"); + } + } +} + + // setContentByLocalJSON方法用于根据本地的JSONObject对象来设置当前TaskList对象的相关内容, +// 如果传入的JSON对象不符合要求(为null或者缺少关键的节点信息),则记录警告日志;若解析JSON出现异常也会记录错误日志。 +public void setContentByLocalJSON(JSONObject js) { + // 判断传入的JSON对象是否为null,或者是否不包含名为GTaskStringUtils.META_HEAD_NOTE的节点, + // 如果满足这些情况之一,则表示该JSON对象不符合处理要求,记录警告日志提示没有可用信息。 + if (js == null ||!js.has(GTaskStringUtils.META_HEAD_NOTE)) { + Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); + } + + try { + // 从传入的JSON对象中获取名为GTaskStringUtils.META_HEAD_NOTE的子JSON对象,该对象预期包含了与文件夹或系统相关的属性信息,比如类型、名称等关键内容。 + JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + + // 判断获取到的folder对象中表示类型的字段(NoteColumns.TYPE)的值是否等于文件夹类型(Notes.TYPE_FOLDER), + // 如果是文件夹类型,则从folder对象中获取表示摘要(在这里可理解为文件夹名称)的字段(NoteColumns.SNIPPET)对应的字符串值, + // 然后调用setName方法(用于设置任务列表名称的方法),将其设置为特定格式(加上前缀GTaskStringUtils.MIUI_FOLDER_PREFFIX)的名称,完成根据本地JSON数据对任务列表名称的更新。 + if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { + String name = folder.getString(NoteColumns.SNIPPET); + setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); + } else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { + // 如果类型是系统类型(Notes.TYPE_SYSTEM),则进一步根据系统文件夹的ID来设置不同的默认名称。 + // 判断folder对象中表示ID的字段(NoteColumns.ID)的值是否等于根文件夹的ID(Notes.ID_ROOT_FOLDER), + // 如果是,则调用setName方法,将任务列表名称设置为特定格式(加上前缀GTaskStringUtils.MIUI_FOLDER_PREFFIX和默认文件夹名称GTaskStringUtils.FOLDER_DEFAULT)的名称。 + if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER) + setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT); + // 判断folder对象中表示ID的字段(NoteColumns.ID)的值是否等于通话记录文件夹的ID(Notes.ID_CALL_RECORD_FOLDER), + // 如果是,则调用setName方法,将任务列表名称设置为特定格式(加上前缀GTaskStringUtils.MIUI_FOLDER_PREFFIX和通话记录文件夹对应的名称GTaskStringUtils.FOLDER_CALL_NOTE)的名称。 + else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER) + setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_CALL_NOTE); + else + // 如果不是上述已知的系统文件夹ID,则记录错误日志,表示是无效的系统文件夹,因为无法确定如何正确设置名称了。 + Log.e(TAG, "invalid system folder"); + } else { + // 如果类型既不是文件夹类型也不是系统类型,则记录错误日志,表示出现了错误的类型,不符合预期的处理逻辑。 + Log.e(TAG, "error type"); + } + } catch (JSONException e) { + // 如果在解析JSON对象(如获取子对象、获取字段值等操作)过程中出现了JSONException异常,记录错误日志,打印异常堆栈信息,方便排查问题。 + Log.e(TAG, e.toString()); + e.printStackTrace(); + } +} + +// getLocalJSONFromContent方法用于根据当前TaskList对象的内容生成对应的本地JSONObject表示形式, +// 如果在构建JSON过程中出现异常,则记录错误日志并返回null,主要是按照特定格式组装包含任务列表相关信息的JSON数据。 +public JSONObject getLocalJSONFromContent() { + try { + // 创建一个新的JSONObject对象,用于组装并最终返回包含任务列表相关信息的本地JSON数据结构。 + JSONObject js = new JSONObject(); + // 创建一个新的JSONObject对象,用于存放与任务列表相关的属性信息(这里主要是类型和名称相关内容),后续会将其作为整体放入外层的js对象中。 + JSONObject folder = new JSONObject(); + + // 获取当前任务列表对象的名称,后续会根据名称来判断和设置相应的属性信息到folder对象中。 + String folderName = getName(); + // 判断任务列表名称是否以特定前缀(GTaskStringUtils.MIUI_FOLDER_PREFFIX)开头,如果是,则截取去掉前缀后的部分作为实际的文件夹名称, + // 这样处理可能是为了在生成JSON数据时只保留关键的名称内容,去除特定格式相关的前缀部分。 + if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) + folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(), + folderName.length()); + + // 将处理后的文件夹名称(folderName)添加到folder对象的NoteColumns.SNIPPET字段中,相当于在JSON数据中记录了任务列表对应的名称信息。 + folder.put(NoteColumns.SNIPPET, folderName); + + // 判断处理后的文件夹名称是否等于默认文件夹名称(GTaskStringUtils.FOLDER_DEFAULT)或者通话记录文件夹对应的名称(GTaskStringUtils.FOLDER_CALL_NOTE), + // 如果是,则将folder对象中表示类型的字段(NoteColumns.TYPE)的值设为系统类型(Notes.TYPE_SYSTEM),表明这是系统相关的文件夹; + // 否则将类型字段设为文件夹类型(Notes.TYPE_FOLDER),以此来明确任务列表对应的类型属性并添加到folder对象中。 + if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT) + || folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE)) + folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + else + folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); + + // 将包含任务列表相关属性信息的folder对象,以GTaskStringUtils.META_HEAD_NOTE为键,添加到外层的js对象中,形成完整的符合要求的本地JSON数据结构。 + js.put(GTaskStringUtils.META_HEAD_NOTE, folder); + + // 返回组装好的js对象,即包含了当前任务列表相关信息的本地JSON表示形式,可供在本地存储、传输或者与其他本地模块交互时使用。 + return js; + } catch (JSONException e) { + // 如果在构建JSON对象(如往JSONObject中添加元素等操作)过程中出现了JSONException异常,记录错误日志,打印异常堆栈信息,方便排查问题。 + Log.e(TAG, e.toString()); + e.printStackTrace(); + // 如果出现异常情况,最终返回null,表示无法成功获取任务列表对应的本地JSON内容表示形式。 + return null; + } +} + +// getSyncAction方法用于根据传入的Cursor对象(通常用于从数据库查询结果中获取数据)以及当前TaskList对象的相关状态, +// 来确定在数据同步操作中应该执行的具体动作,返回对应的同步动作标识(SYNC_ACTION_* 系列常量之一),若出现异常则返回SYNC_ACTION_ERROR。 +public int getSyncAction(Cursor c) { + try { + // 判断本地是否没有更新,通过检查Cursor对象中代表本地修改状态的列(SqlNote.LOCAL_MODIFIED_COLUMN指定的列)的值是否为0来确定, + // 如果为0,表示本地没有进行过更新操作。 + if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { + // 进一步判断远程和本地是否都没有更新,通过比较Cursor对象中代表同步ID的列(SqlNote.SYNC_ID_COLUMN指定的列)的值, + // 和当前TaskList对象的最后修改时间(通过getLastModified方法获取)是否相等来确定,如果相等,说明两边数据都没有更新, + // 则返回SYNC_ACTION_NONE,表示不需要进行任何同步操作,两边数据是一致的。 + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + return SYNC_ACTION_NONE; + } else { + // 如果同步ID不相等,说明远程有更新,需要将远程数据应用到本地,返回SYNC_ACTION_UPDATE_LOCAL,表示以远程数据更新本地数据。 + return SYNC_ACTION_UPDATE_LOCAL; + } + } else { + // 如果本地有更新(即本地修改状态列的值不为0),则需要进一步验证任务列表的Gtask ID是否匹配, + // 通过比较Cursor对象中代表Gtask ID的列(SqlNote.GTASK_ID_COLUMN指定的列)获取的字符串值, + // 和当前TaskList对象通过getGid方法获取的Gtask ID是否相等来确定,如果不相等,记录错误日志(因为这可能是比较严重的不一致情况), + // 并返回SYNC_ACTION_ERROR,表示出现了同步错误,两边的关键标识不一致了。 + if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { + Log.e(TAG, "gtask id doesn't match"); + return SYNC_ACTION_ERROR; + } + // 再次判断远程和本地的更新情况,通过比较Cursor对象中代表同步ID的列(SqlNote.SYNC_ID_COLUMN指定的列)的值, + // 和当前TaskList对象的最后修改时间(通过getLastModified方法获取)是否相等来确定,如果相等,说明只有本地进行了修改, + // 则返回SYNC_ACTION_UPDATE_REMOTE,表示需要将本地的修改同步到远程,让远程数据也更新为本地修改后的状态。 + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + return SYNC_ACTION_UPDATE_REMOTE; + } else { + // 对于文件夹类型的冲突情况(根据注释这里针对文件夹做了特殊处理),直接采用本地修改进行同步, + // 也就是返回SYNC_ACTION_UPDATE_REMOTE,表示将本地的修改应用到远程数据上,以本地为准进行同步,可能是基于特定业务逻辑考虑文件夹的同步策略。 + return SYNC_ACTION_UPDATE_REMOTE; + } + } + } catch (Exception e) { + // 如果在整个方法执行过程中出现了任何异常(捕获了Exception类型,涵盖了各种可能的运行时异常等情况),记录错误日志,打印异常堆栈信息,方便排查问题, + // 并返回SYNC_ACTION_ERROR,表示出现了同步相关的错误情况,无法准确判断同步动作。 + Log.e(TAG, "gtask id doesn't match"); + e.printStackTrace(); + } + + // 如果前面执行过程中出现了未处理的情况或者最终正常流程执行完毕但没有返回合适的同步动作标识,默认返回SYNC_ACTION_ERROR,表示出现错误。 + return SYNC_ACTION_ERROR; +} + +// getChildTaskCount方法用于获取当前TaskList对象中包含的子任务(Task类型)的数量, +// 它直接返回存储子任务的ArrayList(mChildren)的大小,即其中包含的元素个数,也就是子任务的数量。 +public int getChildTaskCount() { + return mChildren.size(); +} + + // addChildTask方法(单个参数版本)用于向当前TaskList对象的子任务列表(mChildren)中添加一个Task类型的子任务, +// 如果添加成功,还会设置该子任务的前序兄弟任务(priorSibling)和父任务(parent)属性,返回添加操作是否成功的布尔值。 +public boolean addChildTask(Task task) { + // 初始化一个布尔变量ret,用于记录最终添加子任务操作是否成功,初始值设为false。 + boolean ret = false; + // 首先判断传入的任务对象(task)不为null,并且当前子任务列表(mChildren)中不包含该任务对象(避免重复添加),才进行后续添加操作。 + if (task!= null &&!mChildren.contains(task)) { + // 调用ArrayList的add方法尝试将任务对象添加到子任务列表(mChildren)中,add方法会返回添加操作是否成功的布尔值,将其赋值给ret变量。 + ret = mChildren.add(task); + // 如果添加操作成功(ret为true),则需要设置该子任务的前序兄弟任务(priorSibling)和父任务(parent)属性。 + if (ret) { + // 判断子任务列表是否为空,如果为空,则将该子任务的前序兄弟任务设为null,因为没有其他任务在它之前; + // 如果不为空,则将该子任务的前序兄弟任务设为子任务列表中的最后一个任务(即当前刚添加任务之前的那个任务),通过获取子任务列表最后一个元素的方式来设置。 + task.setPriorSibling(mChildren.isEmpty()? null : mChildren + .get(mChildren.size() - 1)); + // 设置该子任务的父任务为当前TaskList对象,表明该任务属于这个任务列表,建立正确的层级关系。 + task.setParent(this); + } + } + // 返回添加操作是否成功的结果(ret的值),供外部调用者知晓添加子任务的情况。 + return ret; +} + +// addChildTask方法(两个参数版本)用于向当前TaskList对象的子任务列表(mChildren)中指定索引位置添加一个Task类型的子任务, +// 如果索引位置不合法会记录错误日志并返回false,添加成功会更新相关任务的前序兄弟任务关系,返回添加操作是否成功的布尔值(按逻辑这里始终返回true,但可以根据实际情况调整返回值来更准确反馈操作结果)。 +public boolean addChildTask(Task task, int index) { + // 判断传入的索引值(index)是否小于0或者大于子任务列表的当前大小(mChildren.size()),如果是,则表示索引位置不合法,记录错误日志并返回false,表示添加操作失败。 + if (index < 0 || index > mChildren.size()) { + Log.e(TAG, "add child task: invalid index"); + return false; + } + + // 查找传入的任务对象(task)在子任务列表(mChildren)中的位置索引,如果不存在则返回 -1,这里获取其位置索引用于后续判断是否重复添加等情况。 + int pos = mChildren.indexOf(task); + // 再次判断任务对象(task)不为null,并且在子任务列表中的位置索引为 -1(即不存在于列表中,避免重复添加),才进行后续添加操作。 + if (task!= null && pos == -1) { + // 使用ArrayList的add方法,在指定的索引位置(index)添加任务对象,将任务插入到子任务列表中相应位置。 + mChildren.add(index, task); + + // 更新任务列表中相关任务的前序兄弟任务关系,以下操作是为了维护任务之间正确的顺序关系。 + + // 初始化前序任务(preTask)和后续任务(afterTask)变量,分别用于存储插入位置之前和之后的任务对象,初始值设为null,后续根据实际情况赋值。 + Task preTask = null; + Task afterTask = null; + // 如果插入的索引位置不是0(即不是在列表开头插入),则获取插入位置前一个位置的任务对象,赋值给preTask,作为新插入任务的前序兄弟任务。 + if (index!= 0) + preTask = mChildren.get(index - 1); + // 如果插入的索引位置不是子任务列表的最后一个位置(即不是在列表末尾插入),则获取插入位置后一个位置的任务对象,赋值给afterTask,后续用于更新其前序兄弟任务关系。 + if (index!= mChildren.size() - 1) + afterTask = mChildren.get(index + 1); + + // 设置新插入任务(task)的前序兄弟任务为preTask,建立正确的顺序关系。 + task.setPriorSibling(preTask); + // 如果afterTask不为null(即存在后续任务),则设置该后续任务(afterTask)的前序兄弟任务为新插入的任务(task),更新顺序关系,确保任务之间的先后顺序正确。 + if (afterTask!= null) + afterTask.setPriorSibling(task); + } + + // 按当前逻辑,只要执行到这里就认为添加操作是进行了的(即使可能有一些逻辑上的异常情况未完全考虑周全),所以返回true,表示添加子任务操作成功, + // 也可以根据更严格的业务逻辑需求,进一步完善这里的返回值判断,使其更准确地反馈添加操作的实际结果。 + return true; +} + +// removeChildTask方法用于从当前TaskList对象的子任务列表(mChildren)中移除指定的Task类型子任务, +// 如果移除成功,会重置该子任务的前序兄弟任务和父任务属性,同时更新子任务列表中相关任务的前序兄弟任务关系,返回移除操作是否成功的布尔值。 +public boolean removeChildTask(Task task) { + // 初始化一个布尔变量ret,用于记录最终移除子任务操作是否成功,初始值设为false。 + boolean ret = false; + // 获取要移除的任务对象(task)在子任务列表(mChildren)中的索引位置,如果不存在则返回 -1,后续根据该索引进行相关操作判断。 + int index = mChildren.indexOf(task); + // 如果任务对象在子任务列表中的索引位置不为 -1(即存在于列表中),则进行移除操作。 + if (index!= -1) { + // 调用ArrayList的remove方法尝试移除任务对象,remove方法会返回移除操作是否成功的布尔值,将其赋值给ret变量。 + ret = mChildren.remove(task); + + // 如果移除操作成功(ret为true),则需要重置该子任务的前序兄弟任务和父任务属性,以及更新子任务列表中相关任务的前序兄弟任务关系。 + if (ret) { + // 将该子任务的前序兄弟任务设为null,因为它已经从任务列表中移除了,不再有前序兄弟任务的关联。 + task.setPriorSibling(null); + // 将该子任务的父任务设为null,表明它不再属于当前的任务列表,解除层级关系。 + task.setParent(null); + + // 更新子任务列表中相关任务的前序兄弟任务关系,以下操作是为了维护剩余任务之间正确的顺序关系。 + + // 判断移除的任务所在索引位置(index)是否不等于子任务列表的当前大小(即不是移除的最后一个任务),如果是,则需要更新后续任务的前序兄弟任务关系。 + if (index!= mChildren.size()) { + // 获取移除任务所在索引位置对应的任务对象(即后续任务),并设置其前序兄弟任务为移除任务之前的那个任务(如果移除的是第一个任务则设为null), + // 通过获取相应索引位置的任务对象来更新其前序兄弟任务属性,确保任务之间的先后顺序正确。 + mChildren.get(index).setPriorSibling( + index == 0? null : mChildren.get(index - 1)); + } + } + } + // 返回移除操作是否成功的结果(ret的值),供外部调用者知晓移除子任务的情况。 + return ret; +} + +// moveChildTask方法用于在当前TaskList对象的子任务列表(mChildren)中移动指定的Task类型子任务到新的索引位置, +// 如果索引位置不合法或者任务不在列表中会记录错误日志并返回false,移动成功则返回true,内部通过先移除再添加的方式实现移动操作。 +public boolean moveChildTask(Task task, int index) { + // 判断传入的新索引位置(index)是否小于0或者大于等于子任务列表的当前大小(mChildren.size()),如果是,则表示索引位置不合法,记录错误日志并返回false,表示移动操作失败。 + if (index < 0 || index >= mChildren.size()) { + Log.e(TAG, "move child task: invalid index"); + return false; + } + + // 获取要移动的任务对象(task)在子任务列表(mChildren)中的当前索引位置,如果不存在则返回 -1,后续根据该索引进行相关操作判断。 + int pos = mChildren.indexOf(task); + // 如果任务对象在子任务列表中的索引位置为 -1(即任务不在列表中),记录错误日志并返回false,表示移动操作失败,因为无法移动一个不存在于列表中的任务。 + if (pos == -1) { + Log.e(TAG, "move child task: the task should in the list"); + return false; + } + + // 判断任务对象当前的索引位置(pos)是否与传入的新索引位置(index)相等,如果相等,说明任务已经在目标位置了,不需要进行移动操作,直接返回true,表示移动操作成功(其实就是无需操作的情况)。 + if (pos == index) + return true; + + // 通过先调用removeChildTask方法移除任务对象,再调用addChildTask方法(两个参数版本)将任务对象添加到新的索引位置的方式,来实现移动任务的操作, + // 如果移除和添加操作都成功,则整体移动操作成功,返回true;如果有任何一个操作失败,则返回false,表示移动操作失败。 + return (removeChildTask(task) && addChildTask(task, index)); +} + +// findChildTaskByGid方法用于在当前TaskList对象的子任务列表(mChildren)中根据任务的唯一标识符(Gid)查找对应的Task类型子任务, +// 如果找到则返回该任务对象,没找到则返回null,通过遍历子任务列表进行查找匹配。 +public Task findChildTaskByGid(String gid) { + // 遍历子任务列表(mChildren),从索引0开始,到列表大小结束(不包含列表大小对应的索引,即遍历所有元素)。 + for (int i = 0; i < mChildren.size(); i++) { + // 获取当前遍历到的任务对象。 + Task t = mChildren.get(i); + // 判断当前任务对象的唯一标识符(通过getGid方法获取)是否与传入的Gid参数相等,如果相等,则找到了对应的任务对象,返回该任务对象。 + if (t.getGid().equals(gid)) { + return t; + } + } + // 如果遍历完整个子任务列表都没有找到匹配的任务对象,则返回null,表示未找到。 + return null; +} + +// getChildTaskIndex方法用于获取指定Task类型子任务在当前TaskList对象的子任务列表(mChildren)中的索引位置, +// 通过调用ArrayList的indexOf方法来查找并返回对应的索引值,如果任务不在列表中则返回 -1。 +public int getChildTaskIndex(Task task) { + return mChildren.indexOf(task); +} + +// getChildTaskByIndex方法用于根据索引位置从当前TaskList对象的子任务列表(mChildren)中获取对应的Task类型子任务, +// 如果索引位置不合法会记录错误日志并返回null,合法则返回对应索引位置的任务对象,通过直接从列表中获取元素的方式来获取任务对象。 +public Task getChildTaskByIndex(int index) { + // 判断传入的索引值(index)是否小于0或者大于等于子任务列表的当前大小(mChildren.size()),如果是,则表示索引位置不合法,记录错误日志并返回null,表示无法获取任务对象。 + if (index < 0 || index >= mChildren.size()) { + Log.e(TAG, "getTaskByIndex: invalid index"); + return null; + } + // 如果索引位置合法,则直接从子任务列表中获取对应索引位置的任务对象,并返回该任务对象。 + return mChildren.get(index); +} + +// getChilTaskByGid方法同样用于在当前TaskList对象的子任务列表(mChildren)中根据任务的唯一标识符(Gid)查找对应的Task类型子任务, +// 不过这里是通过增强for循环来遍历列表进行查找匹配,找到则返回任务对象,没找到则返回null,功能和findChildTaskByGid方法类似,但遍历方式不同。 +public Task getChilTaskByGid(String gid) { + // 使用增强for循环遍历子任务列表(mChildren),依次获取每个任务对象进行判断。 + for (Task task : mChildren) { + // 判断当前遍历到的任务对象的唯一标识符(通过getGid方法获取)是否与传入的Gid参数相等,如果相等,则找到了对应的任务对象,返回该任务对象。 + if (task.getGid().equals(gid)) + return task; + } + // 如果遍历完整个子任务列表都没有找到匹配的任务对象,则返回null,表示未找到。 + return null; +} + +// getChildTaskList方法用于获取当前TaskList对象的子任务列表(mChildren),直接返回存储子任务的ArrayList对象, +// 外部调用者可以通过获取到的列表进行进一步的操作,比如遍历、修改等(但需遵循相应的规则和逻辑)。 +public ArrayList getChildTaskList() { + return this.mChildren; +} + +// setIndex方法用于设置当前TaskList对象的索引属性(mIndex),将传入的索引值赋给该属性,方便更新任务列表在所属体系中的顺序等相关情况。 +public void setIndex(int index) { + this.mIndex = index; +} + +// getIndex方法用于获取当前TaskList对象的索引属性(mIndex)的值,直接返回该属性的值,外部调用者可以获取该索引值来了解任务列表的相关顺序信息等。 +public int getIndex() { + return this.mIndex; +} -- 2.34.1 From acd61ccf2dc997461c74587809d844bdf660d76a Mon Sep 17 00:00:00 2001 From: p3o5nagmu <1329414441@qq.com> Date: Tue, 31 Dec 2024 03:27:23 +0800 Subject: [PATCH 12/13] ADD file via upload --- ActionFailureException.java | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 ActionFailureException.java diff --git a/ActionFailureException.java b/ActionFailureException.java new file mode 100644 index 0000000..8054e20 --- /dev/null +++ b/ActionFailureException.java @@ -0,0 +1,47 @@ +/* + * 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. + */ + +// 包声明,表明该类位于net.micode.notes.gtask.exception包下,用于对特定业务逻辑中的异常情况进行统一管理和处理,这里主要和任务相关操作的异常有关(从包名推测)。 +package net.micode.notes.gtask.exception; + +// ActionFailureException类继承自RuntimeException,意味着它是一个运行时异常,不需要在编译时显式地被捕获或者声明抛出(当然也可以根据需要进行捕获处理)。 +// 该类主要用于表示在任务相关操作(比如创建、更新任务等操作,从使用场景推测)中出现失败情况时抛出的异常,方便在业务逻辑中区分不同类型的异常情况并进行针对性处理。 +public class ActionFailureException extends RuntimeException { + // 定义一个序列化版本号,用于在对象序列化和反序列化过程中保证版本兼容性,这个值是根据Java的序列化机制要求生成的一个固定的长整型数字, + // 当类的结构发生变化(如添加、删除成员变量等情况)时,可能需要相应地更新这个版本号,以避免反序列化出现问题,但如果类结构稳定,通常保持这个初始值即可。 + private static final long serialVersionUID = 4425249765923293627L; + + // 无参构造函数,调用父类(RuntimeException)的无参构造函数,用于创建一个默认的ActionFailureException实例, + // 当在业务逻辑中不需要指定具体的异常信息时,可以使用这个构造函数创建异常对象并抛出,不过通常这种情况较少,更多是希望传递一些描述失败原因的信息。 + public ActionFailureException() { + super(); + } + + // 带有一个字符串参数的构造函数,接收一个表示异常详细信息的字符串(paramString),调用父类(RuntimeException)相应的构造函数, + // 将该字符串作为异常信息传递进去,用于创建一个带有具体描述信息的ActionFailureException实例,方便在抛出异常时告知调用者具体的失败原因, + // 比如在任务创建操作失败时,可以传递类似"创建任务时参数不合法"这样的信息,便于定位问题。 + public ActionFailureException(String paramString) { + super(paramString); + } + + // 带有一个字符串参数和一个Throwable参数的构造函数,接收一个表示异常详细信息的字符串(paramString)以及一个表示引起当前异常的根异常对象(paramThrowable), + // 调用父类(RuntimeException)相应的构造函数,将这两个参数传递进去,用于创建一个既包含具体描述信息又关联了引起当前异常的底层异常的ActionFailureException实例, + // 例如在任务更新操作因为数据库连接异常而失败时,可以传递类似"更新任务时数据库连接失败"作为paramString,同时将数据库连接相关的异常对象作为paramThrowable传入, + // 这样在排查问题时可以通过获取根异常信息更全面地了解导致操作失败的原因。 + public ActionFailureException(String paramString, Throwable paramThrowable) { + super(paramString, paramThrowable); + } +} \ No newline at end of file -- 2.34.1 From 46ec2c48ea21de11ce43cd0e4b18545f0625a491 Mon Sep 17 00:00:00 2001 From: p3o5nagmu <1329414441@qq.com> Date: Tue, 31 Dec 2024 03:27:32 +0800 Subject: [PATCH 13/13] ADD file via upload --- NetworkFailureException.java | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 NetworkFailureException.java diff --git a/NetworkFailureException.java b/NetworkFailureException.java new file mode 100644 index 0000000..4402bdf --- /dev/null +++ b/NetworkFailureException.java @@ -0,0 +1,50 @@ +/* + * 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.exception; + +// 声明该类所在的包,此处表示该类属于某个特定的包结构下(具体功能相关的包,从类名推测和网络相关操作出现异常的处理有关)。 +package net.micode.notes.gtask.exception; + +// NetworkFailureException类继承自Exception类,这意味着它是一个受检异常,在使用的地方必须显式地进行捕获或者继续向上抛出, +// 该类主要用于表示在网络相关操作(比如网络请求、网络连接等操作,根据类名推测其用途)出现失败情况时抛出的异常,方便在业务逻辑中区分不同类型的异常情况并针对性处理。 +public class NetworkFailureException extends Exception { + // 定义一个序列化版本号,用于在对象序列化和反序列化过程中保证版本兼容性。 + // 这个值是一个固定的长整型数字,按照Java序列化机制的要求设置,当类的结构后续有变动(例如添加、删除成员变量等情况)时, + // 可能需要相应地更新这个版本号,不过若类结构保持稳定,通常维持这个初始给定的值即可。 + private static final long serialVersionUID = 2107610287180234136L; + + // 无参构造函数,调用父类(Exception)的无参构造函数,用于创建一个默认的NetworkFailureException实例, + // 当在业务逻辑中不需要明确指定具体的异常信息时,可以使用这个构造函数创建异常对象并抛出,不过实际应用中这种情况相对较少,更多时候希望传递一些具体描述失败原因的信息。 + public NetworkFailureException() { + super(); + } + + // 带有一个字符串参数的构造函数,接收一个表示异常详细信息的字符串(paramString),调用父类(Exception)对应的构造函数, + // 将该字符串作为异常信息传递进去,用于创建一个带有具体描述内容的NetworkFailureException实例,方便在抛出异常时告知调用者具体的网络相关失败原因, + // 例如在网络请求超时的情况下,可以传递类似“网络请求超时,请检查网络连接”这样的信息,便于定位和排查问题。 + public NetworkFailureException(String paramString) { + super(paramString); + } + + // 带有一个字符串参数和一个Throwable参数的构造函数,接收一个表示异常详细信息的字符串(paramString)以及一个表示引起当前异常的根异常对象(paramThrowable), + // 调用父类(Exception)相应的构造函数,把这两个参数传递进去,用于创建一个既包含具体描述信息又关联了引起当前异常的底层异常的NetworkFailureException实例, + // 比如在网络连接因为底层Socket异常而失败时,可以传递如“网络连接出现Socket异常”作为paramString,同时将对应的Socket异常对象作为paramThrowable传入, + // 这样在排查问题时,能够通过获取根异常的详细信息更全面地了解导致网络操作失败的根源所在。 + public NetworkFailureException(String paramString, Throwable paramThrowable) { + super(paramString, paramThrowable); + } +} \ No newline at end of file -- 2.34.1