diff --git a/ActionFailureException.java b/ActionFailureException.java new file mode 100644 index 0000000..e69de29 diff --git a/GTaskASyncTask.java b/GTaskASyncTask.java new file mode 100644 index 0000000..e69de29 diff --git a/MetaData.java b/MetaData.java new file mode 100644 index 0000000..7b64f80 --- /dev/null +++ b/MetaData.java @@ -0,0 +1,129 @@ +/* + * 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. + */ + +// MetaData 类继承自 Task,用于处理任务的元数据信息 +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; + +public class MetaData extends Task { + // 日志标签,用于日志记录 + private final static String TAG = MetaData.class.getSimpleName(); + + // 存储相关的 Google 任务 ID + private String mRelatedGid = null; + + /** + * 设置元数据信息 + * + * @param gid 相关的 Google 任务 ID + * @param metaInfo 元数据信息,以 JSON 格式表示 + */ + public void setMeta(String gid, JSONObject metaInfo) { + try { + // 将 Google 任务 ID 添加到元数据中 + metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); + } catch (JSONException e) { + // 如果添加失败,记录错误日志 + Log.e(TAG, "failed to put related gid"); + } + // 将元数据转换为字符串并设置为笔记 + setNotes(metaInfo.toString()); + // 设置元数据的名称 + setName(GTaskStringUtils.META_NOTE_NAME); + } + + /** + * 获取相关的 Google 任务 ID + * + * @return 返回 Google 任务 ID + */ + public String getRelatedGid() { + return mRelatedGid; + } + + /** + * 判断元数据是否值得保存 + * + * @return 如果笔记不为空,则返回 true,否则返回 false + */ + @Override + public boolean isWorthSaving() { + return getNotes() != null; + } + + /** + * 从远程 JSON 设置内容 + * + * @param js 远程 JSON 对象 + */ + @Override + public void setContentByRemoteJSON(JSONObject js) { + super.setContentByRemoteJSON(js); + if (getNotes() != null) { + try { + // 解析笔记以获取 Google 任务 ID + JSONObject metaInfo = new JSONObject(getNotes().trim()); + mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); + } catch (JSONException e) { + // 如果解析失败,记录警告日志 + Log.w(TAG, "failed to get related gid"); + mRelatedGid = null; + } + } + } + + /** + * 从本地 JSON 设置内容 + * 此方法不应被调用 + * + * @param js 本地 JSON 对象 + */ + @Override + public void setContentByLocalJSON(JSONObject js) { + // 此方法不应被调用 + throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); + } + + /** + * 从内容中获取本地 JSON + * 此方法不应被调用 + * + * @return 永远不会返回,将抛出异常 + */ + @Override + public JSONObject getLocalJSONFromContent() { + throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); + } + + /** + * 基于游标获取同步操作 + * 此方法不应被调用 + * + * @param c 游标 + * @return 永远不会返回,将抛出异常 + */ + @Override + public int getSyncAction(Cursor c) { + throw new IllegalAccessError("MetaData:getSyncAction should not be called"); + } +} diff --git a/NetworkFailureException.java b/NetworkFailureException.java new file mode 100644 index 0000000..e69de29 diff --git a/Node.java b/Node.java new file mode 100644 index 0000000..79259a3 --- /dev/null +++ b/Node.java @@ -0,0 +1,126 @@ +/* + * 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. + */ + +/** + * 抽象类Node代表一个基本的节点对象,提供了基本的属性和方法用于操作和同步节点数据 + * 主要用于笔记或任务的同步处理 + */ +package net.micode.notes.gtask.data; + +import android.database.Cursor; +import org.json.JSONObject; + +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; + + // 节点的基本属性 + private String mGid; + private String mName; + private long mLastModified; + private boolean mDeleted; + + /** + * 默认构造方法,初始化节点的基本属性 + */ + public Node() { + mGid = null; + mName = ""; + mLastModified = 0; + mDeleted = false; + } + + /** + * 根据操作ID生成创建操作的JSON对象 + * @param actionId 操作ID,表示需要执行的同步操作类型 + * @return 返回一个包含创建操作信息的JSON对象 + */ + public abstract JSONObject getCreateAction(int actionId); + + /** + * 根据操作ID生成更新操作的JSON对象 + * @param actionId 操作ID,表示需要执行的同步操作类型 + * @return 返回一个包含更新操作信息的JSON对象 + */ + public abstract JSONObject getUpdateAction(int actionId); + + /** + * 通过远程JSON对象设置节点内容 + * @param js 远程JSON对象,包含节点的最新数据 + */ + public abstract void setContentByRemoteJSON(JSONObject js); + + /** + * 通过本地JSON对象设置节点内容 + * @param js 本地JSON对象,包含节点的现有数据 + */ + public abstract void setContentByLocalJSON(JSONObject js); + + /** + * 从节点内容生成本地JSON对象 + * @return 返回一个包含节点当前状态的JSON对象 + */ + public abstract JSONObject getLocalJSONFromContent(); + + /** + * 根据数据库游标确定同步操作类型 + * @param c 数据库游标,指向查询到的节点数据 + * @return 返回一个整数,表示需要执行的同步操作类型 + */ + public abstract int getSyncAction(Cursor c); + + // 以下为节点属性的getter和setter方法 + 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; + } + + public String getGid() { + return this.mGid; + } + + public String getName() { + return this.mName; + } + + public long getLastModified() { + return this.mLastModified; + } + + public boolean getDeleted() { + return this.mDeleted; + } + +} diff --git a/NotesDatabaseHelper.java b/NotesDatabaseHelper.java new file mode 100644 index 0000000..dc77b6b --- /dev/null +++ b/NotesDatabaseHelper.java @@ -0,0 +1,273 @@ + +// 创建笔记表的SQL语句 +private static final String CREATE_NOTE_TABLE_SQL = "CREATE TABLE " + TABLE.NOTE + " (" + + NoteColumns.ID + " TEXT PRIMARY KEY, " + + NoteColumns.TITLE + " TEXT NOT NULL, " + + NoteColumns.CONTENT + " TEXT NOT NULL, " + + NoteColumns.CREATED_DATE + " INTEGER NOT NULL, " + + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL, " + + NoteColumns.TYPE + " INTEGER NOT NULL, " + + NoteColumns.PARENT_ID + " TEXT NOT NULL, " + + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT '', " + + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + + ");"; + +// 创建与笔记表相关的触发器的SQL语句 +private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = "CREATE TRIGGER increase_folder_count_on_update AFTER UPDATE ON " + TABLE.NOTE + + " BEGIN" + + " UPDATE " + TABLE.NOTE + " SET " + NoteColumns.COUNT + " = " + NoteColumns.COUNT + " + 1" + + " WHERE " + NoteColumns.ID + " = new." + NoteColumns.PARENT_ID + ";" + + " END;"; +// 类似的触发器SQL语句省略... + +// 创建数据表的SQL语句 +private static final String CREATE_DATA_TABLE_SQL = "CREATE TABLE " + TABLE.DATA + " (" + + DataColumns.ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + DataColumns.Note_ID + " TEXT NOT NULL, " + + DataColumns.TYPE + " INTEGER NOT NULL, " + + DataColumns.CONTENT + " TEXT NOT NULL, " + + DataColumns.CREATED_DATE + " INTEGER NOT NULL" + + ");"; + +// 创建与数据表相关的触发器的SQL语句 +private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = "CREATE TRIGGER update_note_content_on_insert AFTER INSERT ON " + TABLE.DATA + + " BEGIN" + + " UPDATE " + TABLE.NOTE + " SET " + NoteColumns.CONTENT + " = new." + DataColumns.CONTENT + + " WHERE " + NoteColumns.ID + " = new." + DataColumns.Note_ID + ";" + + " END;"; +// 类似的触发器SQL语句省略... + +// 在数据表上创建索引的SQL语句 +private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = "CREATE INDEX " + TABLE.DATA + "_note_id_index ON " + + TABLE.DATA + " (" + DataColumns.Note_ID + ");"; + +// 删除触发器的SQL语句 +private static final String DROP_TRIGGER_SQL = "DROP TRIGGER IF EXISTS %s;"; + +// 插入系统文件夹的SQL语句 +private static final String INSERT_SYSTEM_FOLDER_SQL = "INSERT INTO " + TABLE.NOTE + " (" + + NoteColumns.ID + ", " + + NoteColumns.TYPE + ", " + + NoteColumns.TITLE + ", " + + NoteColumns.CONTENT + ", " + + NoteColumns.CREATED_DATE + ", " + + NoteColumns.MODIFIED_DATE + ", " + + NoteColumns.PARENT_ID + ")" + + " VALUES (?, ?, ?, ?, ?, ?, ?);"; + +// 更新笔记表的SQL语句 +private static final String UPDATE_NOTE_TABLE_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.TITLE + " = ?, " + + NoteColumns.CONTENT + " = ?, " + + NoteColumns.MODIFIED_DATE + " = ?, " + + NoteColumns.PARENT_ID + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 删除笔记的SQL语句 +private static final String DELETE_NOTE_SQL = "DELETE FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 查询笔记的SQL语句 +private static final String QUERY_NOTES_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + " = ?" + + " ORDER BY " + NoteColumns.MODIFIED_DATE + " DESC;"; + +// 按ID查询笔记的SQL语句 +private static final String QUERY_NOTE_BY_ID_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 查询笔记的最大版本的SQL语句 +private static final String QUERY_MAX_VERSION_SQL = "SELECT MAX(" + NoteColumns.VERSION + ") FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 更新笔记版本的SQL语句 +private static final String UPDATE_NOTE_VERSION_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.VERSION + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 删除旧笔记的SQL语句 +private static final String DELETE_OLD_NOTES_SQL = "DELETE FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.VERSION + " < ?;"; + +// 创建新版本笔记的SQL语句 +private static final String CREATE_NEW_VERSION_SQL = "INSERT INTO " + TABLE.NOTE + " (" + + NoteColumns.ID + ", " + + NoteColumns.TITLE + ", " + + NoteColumns.CONTENT + ", " + + NoteColumns.CREATED_DATE + ", " + + NoteColumns.MODIFIED_DATE + ", " + + NoteColumns.TYPE + ", " + + NoteColumns.PARENT_ID + ", " + + NoteColumns.GTASK_ID + ", " + + NoteColumns.VERSION + ")" + + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);"; + +// 按类型查询笔记的SQL语句 +private static final String QUERY_NOTES_BY_TYPE_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.TYPE + " = ?" + + " ORDER BY " + NoteColumns.MODIFIED_DATE + " DESC;"; + +// 按标题查询笔记的SQL语句 +private static final String QUERY_NOTES_BY_TITLE_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.TITLE + " LIKE ?" + + " ORDER BY " + NoteColumns.MODIFIED_DATE + " DESC;"; + +// 按内容查询笔记的SQL语句 +private static final String QUERY_NOTES_BY_CONTENT_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.CONTENT + " LIKE ?" + + " ORDER BY " + NoteColumns.MODIFIED_DATE + " DESC;"; + +// 按日期范围查询笔记的SQL语句 +private static final String QUERY_NOTES_BY_DATE_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.CREATED_DATE + " BETWEEN ? AND ?" + + " ORDER BY " + NoteColumns.MODIFIED_DATE + " DESC;"; + +// 按GTASK ID查询笔记的SQL语句 +private static final String QUERY_NOTES_BY_GTASK_ID_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.GTASK_ID + " = ?;"; + +// 更新笔记的GTASK ID的SQL语句 +private static final String UPDATE_NOTE_GTASK_ID_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.GTASK_ID + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 查询回收站中的笔记的SQL语句 +private static final String QUERY_TRASH_NOTES_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + " = " + Notes.ID_TRASH_FOLER + + " ORDER BY " + NoteColumns.MODIFIED_DATE + " DESC;"; + +// 从回收站恢复笔记的SQL语句 +private static final String RESTORE_NOTE_FROM_TRASH_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.PARENT_ID + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 永久删除笔记的SQL语句 +private static final String PERMANENTLY_DELETE_NOTE_SQL = "DELETE FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 查询文件夹中笔记数量的SQL语句 +private static final String QUERY_FOLDER_COUNT_SQL = "SELECT COUNT(*) FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + " = ?;"; + +// 更新文件夹中笔记数量的SQL语句 +private static final String UPDATE_FOLDER_COUNT_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.COUNT + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 查询笔记表中的最大ID的SQL语句 +private static final String QUERY_MAX_ID_SQL = "SELECT MAX(" + NoteColumns.ID + ") FROM " + TABLE.NOTE; + +// 插入新笔记的SQL语句 +private static final String INSERT_NOTE_SQL = "INSERT INTO " + TABLE.NOTE + " (" + + NoteColumns.ID + ", " + + NoteColumns.TITLE + ", " + + NoteColumns.CONTENT + ", " + + NoteColumns.CREATED_DATE + ", " + + NoteColumns.MODIFIED_DATE + ", " + + NoteColumns.TYPE + ", " + + NoteColumns.PARENT_ID + ")" + + " VALUES (?, ?, ?, ?, ?, ?, ?);"; + +// 更新笔记标题的SQL语句 +private static final String UPDATE_NOTE_TITLE_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.TITLE + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 更新笔记内容的SQL语句 +private static final String UPDATE_NOTE_CONTENT_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.CONTENT + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 更新笔记修改日期的SQL语句 +private static final String UPDATE_NOTE_MODIFIED_DATE_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.MODIFIED_DATE + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 更新笔记父ID的SQL语句 +private static final String UPDATE_NOTE_PARENT_ID_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.PARENT_ID + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 删除文件夹中的笔记的SQL语句 +private static final String DELETE_NOTES_IN_FOLDER_SQL = "DELETE FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + " = ?;"; + +// 按父ID查询笔记的SQL语句 +private static final String QUERY_NOTES_BY_PARENT_ID_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + " = ?;"; + +// 更新笔记类型的SQL语句 +private static final String UPDATE_NOTE_TYPE_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.TYPE + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 按类型和父ID查询笔记的SQL语句 +private static final String QUERY_NOTES_BY_TYPE_AND_PARENT_ID_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.TYPE + " = ? AND " + + NoteColumns.PARENT_ID + " = ?;" + + " ORDER BY " + NoteColumns.MODIFIED_DATE + " DESC;"; + +/** + * 构造函数,初始化数据库帮助器。 + * + * @param context 上下文对象 + */ +public NotesDatabaseHelper(Context context) { + super(context, DB_NAME, null, DB_VERSION); +} + +/** + * 创建笔记表。 + * + * @param db 数据库对象 + */ +public void createNoteTable(SQLiteDatabase db) { + db.execSQL(CREATE_NOTE_TABLE_SQL); + reCreateNoteTableTriggers(db); + createSystemFolder(db); + Log.d(TAG, "note table has been created"); +} + +/** + * 重新创建笔记表的触发器。 + * + * @param db 数据库对象 + */ +private void reCreateNoteTableTriggers(SQLiteDatabase db) { + db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); + db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update"); + db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete"); + db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete"); + db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert"); + db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete"); + db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash"); + + db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); + db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); + db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER); + db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER); + db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER); + db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER); + db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); +} + +/** + * 创建系统文件夹。 + * + * @param db 数据库对象 + */ +private void createSystemFolder(SQLiteDatabase db) { + ContentValues values = new ContentValues(); + + /** + * 通话记录文件夹,用于存储通话笔记 + */ + values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + + /** + * 根文件夹,作为默认文件夹 + */ + values.clear(); + values.put(NoteColumns.ID, Notes.ID \ No newline at end of file diff --git a/NotesProvider.java b/NotesProvider.java new file mode 100644 index 0000000..dc77b6b --- /dev/null +++ b/NotesProvider.java @@ -0,0 +1,273 @@ + +// 创建笔记表的SQL语句 +private static final String CREATE_NOTE_TABLE_SQL = "CREATE TABLE " + TABLE.NOTE + " (" + + NoteColumns.ID + " TEXT PRIMARY KEY, " + + NoteColumns.TITLE + " TEXT NOT NULL, " + + NoteColumns.CONTENT + " TEXT NOT NULL, " + + NoteColumns.CREATED_DATE + " INTEGER NOT NULL, " + + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL, " + + NoteColumns.TYPE + " INTEGER NOT NULL, " + + NoteColumns.PARENT_ID + " TEXT NOT NULL, " + + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT '', " + + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + + ");"; + +// 创建与笔记表相关的触发器的SQL语句 +private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = "CREATE TRIGGER increase_folder_count_on_update AFTER UPDATE ON " + TABLE.NOTE + + " BEGIN" + + " UPDATE " + TABLE.NOTE + " SET " + NoteColumns.COUNT + " = " + NoteColumns.COUNT + " + 1" + + " WHERE " + NoteColumns.ID + " = new." + NoteColumns.PARENT_ID + ";" + + " END;"; +// 类似的触发器SQL语句省略... + +// 创建数据表的SQL语句 +private static final String CREATE_DATA_TABLE_SQL = "CREATE TABLE " + TABLE.DATA + " (" + + DataColumns.ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + DataColumns.Note_ID + " TEXT NOT NULL, " + + DataColumns.TYPE + " INTEGER NOT NULL, " + + DataColumns.CONTENT + " TEXT NOT NULL, " + + DataColumns.CREATED_DATE + " INTEGER NOT NULL" + + ");"; + +// 创建与数据表相关的触发器的SQL语句 +private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = "CREATE TRIGGER update_note_content_on_insert AFTER INSERT ON " + TABLE.DATA + + " BEGIN" + + " UPDATE " + TABLE.NOTE + " SET " + NoteColumns.CONTENT + " = new." + DataColumns.CONTENT + + " WHERE " + NoteColumns.ID + " = new." + DataColumns.Note_ID + ";" + + " END;"; +// 类似的触发器SQL语句省略... + +// 在数据表上创建索引的SQL语句 +private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = "CREATE INDEX " + TABLE.DATA + "_note_id_index ON " + + TABLE.DATA + " (" + DataColumns.Note_ID + ");"; + +// 删除触发器的SQL语句 +private static final String DROP_TRIGGER_SQL = "DROP TRIGGER IF EXISTS %s;"; + +// 插入系统文件夹的SQL语句 +private static final String INSERT_SYSTEM_FOLDER_SQL = "INSERT INTO " + TABLE.NOTE + " (" + + NoteColumns.ID + ", " + + NoteColumns.TYPE + ", " + + NoteColumns.TITLE + ", " + + NoteColumns.CONTENT + ", " + + NoteColumns.CREATED_DATE + ", " + + NoteColumns.MODIFIED_DATE + ", " + + NoteColumns.PARENT_ID + ")" + + " VALUES (?, ?, ?, ?, ?, ?, ?);"; + +// 更新笔记表的SQL语句 +private static final String UPDATE_NOTE_TABLE_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.TITLE + " = ?, " + + NoteColumns.CONTENT + " = ?, " + + NoteColumns.MODIFIED_DATE + " = ?, " + + NoteColumns.PARENT_ID + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 删除笔记的SQL语句 +private static final String DELETE_NOTE_SQL = "DELETE FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 查询笔记的SQL语句 +private static final String QUERY_NOTES_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + " = ?" + + " ORDER BY " + NoteColumns.MODIFIED_DATE + " DESC;"; + +// 按ID查询笔记的SQL语句 +private static final String QUERY_NOTE_BY_ID_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 查询笔记的最大版本的SQL语句 +private static final String QUERY_MAX_VERSION_SQL = "SELECT MAX(" + NoteColumns.VERSION + ") FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 更新笔记版本的SQL语句 +private static final String UPDATE_NOTE_VERSION_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.VERSION + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 删除旧笔记的SQL语句 +private static final String DELETE_OLD_NOTES_SQL = "DELETE FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.VERSION + " < ?;"; + +// 创建新版本笔记的SQL语句 +private static final String CREATE_NEW_VERSION_SQL = "INSERT INTO " + TABLE.NOTE + " (" + + NoteColumns.ID + ", " + + NoteColumns.TITLE + ", " + + NoteColumns.CONTENT + ", " + + NoteColumns.CREATED_DATE + ", " + + NoteColumns.MODIFIED_DATE + ", " + + NoteColumns.TYPE + ", " + + NoteColumns.PARENT_ID + ", " + + NoteColumns.GTASK_ID + ", " + + NoteColumns.VERSION + ")" + + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);"; + +// 按类型查询笔记的SQL语句 +private static final String QUERY_NOTES_BY_TYPE_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.TYPE + " = ?" + + " ORDER BY " + NoteColumns.MODIFIED_DATE + " DESC;"; + +// 按标题查询笔记的SQL语句 +private static final String QUERY_NOTES_BY_TITLE_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.TITLE + " LIKE ?" + + " ORDER BY " + NoteColumns.MODIFIED_DATE + " DESC;"; + +// 按内容查询笔记的SQL语句 +private static final String QUERY_NOTES_BY_CONTENT_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.CONTENT + " LIKE ?" + + " ORDER BY " + NoteColumns.MODIFIED_DATE + " DESC;"; + +// 按日期范围查询笔记的SQL语句 +private static final String QUERY_NOTES_BY_DATE_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.CREATED_DATE + " BETWEEN ? AND ?" + + " ORDER BY " + NoteColumns.MODIFIED_DATE + " DESC;"; + +// 按GTASK ID查询笔记的SQL语句 +private static final String QUERY_NOTES_BY_GTASK_ID_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.GTASK_ID + " = ?;"; + +// 更新笔记的GTASK ID的SQL语句 +private static final String UPDATE_NOTE_GTASK_ID_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.GTASK_ID + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 查询回收站中的笔记的SQL语句 +private static final String QUERY_TRASH_NOTES_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + " = " + Notes.ID_TRASH_FOLER + + " ORDER BY " + NoteColumns.MODIFIED_DATE + " DESC;"; + +// 从回收站恢复笔记的SQL语句 +private static final String RESTORE_NOTE_FROM_TRASH_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.PARENT_ID + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 永久删除笔记的SQL语句 +private static final String PERMANENTLY_DELETE_NOTE_SQL = "DELETE FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 查询文件夹中笔记数量的SQL语句 +private static final String QUERY_FOLDER_COUNT_SQL = "SELECT COUNT(*) FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + " = ?;"; + +// 更新文件夹中笔记数量的SQL语句 +private static final String UPDATE_FOLDER_COUNT_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.COUNT + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 查询笔记表中的最大ID的SQL语句 +private static final String QUERY_MAX_ID_SQL = "SELECT MAX(" + NoteColumns.ID + ") FROM " + TABLE.NOTE; + +// 插入新笔记的SQL语句 +private static final String INSERT_NOTE_SQL = "INSERT INTO " + TABLE.NOTE + " (" + + NoteColumns.ID + ", " + + NoteColumns.TITLE + ", " + + NoteColumns.CONTENT + ", " + + NoteColumns.CREATED_DATE + ", " + + NoteColumns.MODIFIED_DATE + ", " + + NoteColumns.TYPE + ", " + + NoteColumns.PARENT_ID + ")" + + " VALUES (?, ?, ?, ?, ?, ?, ?);"; + +// 更新笔记标题的SQL语句 +private static final String UPDATE_NOTE_TITLE_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.TITLE + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 更新笔记内容的SQL语句 +private static final String UPDATE_NOTE_CONTENT_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.CONTENT + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 更新笔记修改日期的SQL语句 +private static final String UPDATE_NOTE_MODIFIED_DATE_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.MODIFIED_DATE + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 更新笔记父ID的SQL语句 +private static final String UPDATE_NOTE_PARENT_ID_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.PARENT_ID + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 删除文件夹中的笔记的SQL语句 +private static final String DELETE_NOTES_IN_FOLDER_SQL = "DELETE FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + " = ?;"; + +// 按父ID查询笔记的SQL语句 +private static final String QUERY_NOTES_BY_PARENT_ID_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + " = ?;"; + +// 更新笔记类型的SQL语句 +private static final String UPDATE_NOTE_TYPE_SQL = "UPDATE " + TABLE.NOTE + " SET " + + NoteColumns.TYPE + " = ?" + + " WHERE " + NoteColumns.ID + " = ?;"; + +// 按类型和父ID查询笔记的SQL语句 +private static final String QUERY_NOTES_BY_TYPE_AND_PARENT_ID_SQL = "SELECT * FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.TYPE + " = ? AND " + + NoteColumns.PARENT_ID + " = ?;" + + " ORDER BY " + NoteColumns.MODIFIED_DATE + " DESC;"; + +/** + * 构造函数,初始化数据库帮助器。 + * + * @param context 上下文对象 + */ +public NotesDatabaseHelper(Context context) { + super(context, DB_NAME, null, DB_VERSION); +} + +/** + * 创建笔记表。 + * + * @param db 数据库对象 + */ +public void createNoteTable(SQLiteDatabase db) { + db.execSQL(CREATE_NOTE_TABLE_SQL); + reCreateNoteTableTriggers(db); + createSystemFolder(db); + Log.d(TAG, "note table has been created"); +} + +/** + * 重新创建笔记表的触发器。 + * + * @param db 数据库对象 + */ +private void reCreateNoteTableTriggers(SQLiteDatabase db) { + db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); + db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update"); + db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete"); + db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete"); + db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert"); + db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete"); + db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash"); + + db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); + db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); + db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER); + db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER); + db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER); + db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER); + db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); +} + +/** + * 创建系统文件夹。 + * + * @param db 数据库对象 + */ +private void createSystemFolder(SQLiteDatabase db) { + ContentValues values = new ContentValues(); + + /** + * 通话记录文件夹,用于存储通话笔记 + */ + values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + + /** + * 根文件夹,作为默认文件夹 + */ + values.clear(); + values.put(NoteColumns.ID, Notes.ID \ No newline at end of file diff --git a/SqlData.java b/SqlData.java new file mode 100644 index 0000000..150a6f8 --- /dev/null +++ b/SqlData.java @@ -0,0 +1,192 @@ +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 + }; + + // 下面是数据库查询结果中各列的索引位置 + public static final int DATA_ID_COLUMN = 0; + public static final int DATA_MIME_TYPE_COLUMN = 1; + public static final int DATA_CONTENT_COLUMN = 2; + public static final int DATA_CONTENT_DATA_1_COLUMN = 3; + public static final int DATA_CONTENT_DATA_3_COLUMN = 4; + + // ContentResolver用于访问内容提供者 + private ContentResolver mContentResolver; + + // 标识当前对象是否是新创建的 + private boolean mIsCreate; + + // 下面是数据库记录的各项字段 + private long mDataId; + private String mDataMimeType; + private String mDataContent; + private long mDataContentData1; + private String mDataContentData3; + + // 用于存储待更新的数据库记录值 + private ContentValues mDiffDataValues; + + /** + * 构造函数,用于创建新的SqlData对象 + * @param context 上下文环境,用于获取ContentResolver + */ + public SqlData(Context context) { + mContentResolver = context.getContentResolver(); + mIsCreate = true; + mDataId = INVALID_ID; + mDataMimeType = DataConstants.NOTE; + mDataContent = ""; + mDataContentData1 = 0; + mDataContentData3 = ""; + mDiffDataValues = new ContentValues(); + } + + /** + * 构造函数,用于从现有的数据库记录创建SqlData对象 + * @param context 上下文环境,用于获取ContentResolver + * @param c 游标对象,指向数据库记录 + */ + public SqlData(Context context, Cursor c) { + mContentResolver = context.getContentResolver(); + mIsCreate = false; + loadFromCursor(c); + mDiffDataValues = new ContentValues(); + } + + /** + * 从游标中加载数据库记录 + * @param c 游标对象,指向数据库记录 + */ + private void loadFromCursor(Cursor c) { + mDataId = c.getLong(DATA_ID_COLUMN); + mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); + mDataContent = c.getString(DATA_CONTENT_COLUMN); + mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); + mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); + } + + /** + * 设置内容,从JSONObject中提取数据并更新到SqlData对象 + * @param js 包含数据的JSONObject对象 + * @throws JSONException 当JSON操作失败时抛出 + */ + 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; + } + + /** + * 获取内容,将SqlData对象的数据封装到JSONObject中 + * @return 包含数据的JSONObject对象 + * @throws JSONException 当JSON操作失败时抛出 + */ + 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; + } + + /** + * 提交更改,将SqlData对象的数据更新到数据库中 + * @param noteId 笔记的ID + * @param validateVersion 是否需要验证版本号 + * @param version 版本号 + */ + public void commit(long noteId, boolean validateVersion, long version) { + if (mIsCreate) { + if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { + mDiffDataValues.remove(DataColumns.ID); + } + + mDiffDataValues.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 + * @return 数据库记录的ID + */ + public long getId() { + return mDataId; + } +} diff --git a/SqlNote.java b/SqlNote.java new file mode 100644 index 0000000..e48eea0 --- /dev/null +++ b/SqlNote.java @@ -0,0 +1,94 @@ +// The class Note is used to handle note-related data and operations, including the creation, updating, and querying of notes. +public class Note { + // Other member variables and constructors are omitted here. + + /** + * Update the content of the note. + * @param data The new content of the note. + * @return Returns true if the update is successful, otherwise returns false. + */ + public boolean setContent(String data) { + // Existing code is omitted here. + } + + /** + * Get the content of the note in the form of a JSONObject. + * @return Returns the JSONObject containing the note content, or null if the operation fails. + */ + public JSONObject getContent() { + // Existing code is omitted here. + } + + /** + * Set the parent ID of the note. + * @param id The new parent ID of the note. + */ + public void setParentId(long id) { + // Existing code is omitted here. + } + + /** + * Set the GTASK ID of the note. + * @param gid The new GTASK ID of the note. + */ + public void setGtaskId(String gid) { + // Existing code is omitted here. + } + + /** + * Set the sync ID of the note. + * @param syncId The new sync ID of the note. + */ + public void setSyncId(long syncId) { + // Existing code is omitted here. + } + + /** + * Reset the local modification flag of the note. + */ + public void resetLocalModified() { + // Existing code is omitted here. + } + + /** + * Get the ID of the note. + * @return Returns the ID of the note. + */ + public long getId() { + // Existing code is omitted here. + } + + /** + * Get the parent ID of the note. + * @return Returns the parent ID of the note. + */ + public long getParentId() { + // Existing code is omitted here. + } + + /** + * Get the snippet of the note. + * @return Returns the snippet of the note. + */ + public String getSnippet() { + // Existing code is omitted here. + } + + /** + * Check if the note type is a regular note. + * @return Returns true if the note type is a regular note, otherwise returns false. + */ + public boolean isNoteType() { + // Existing code is omitted here. + } + + /** + * Commit the changes of the note. + * @param validateVersion Whether to validate the version before updating. + * @throws ActionFailureException If the note creation fails. + * @throws IllegalStateException If the note ID is invalid or the update fails. + */ + public void commit(boolean validateVersion) { + // Existing code is omitted here. + } +} diff --git a/Task.java b/Task.java new file mode 100644 index 0000000..c66b077 --- /dev/null +++ b/Task.java @@ -0,0 +1,392 @@ +```java +// Task类继承自Node类,用于表示一个任务对象 +public class Task extends Node { + // 日志标签,用于标识日志信息来源 + private static final String TAG = Task.class.getSimpleName(); + + // 表示任务是否已完成 + private boolean mCompleted; + + // 任务的备注信息 + private String mNotes; + + // 任务的元信息,使用JSONObject存储 + private JSONObject mMetaInfo; + + // 任务的前一个兄弟任务,用于任务排序 + private Task mPriorSibling; + + // 任务所属的任务列表 + private TaskList mParent; + + // Task类的构造方法,初始化任务的默认状态 + public Task() { + super(); + mCompleted = false; + mNotes = null; + mPriorSibling = null; + mParent = null; + mMetaInfo = null; + } + + /** + * 生成任务创建操作的JSON对象 + * @param actionId 操作ID + * @return 返回表示创建操作的JSONObject + * @throws ActionFailureException 如果JSON对象生成失败,则抛出此异常 + */ + public JSONObject getCreateAction(int actionId) { + JSONObject js = new JSONObject(); + + try { + // action_type + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); + + // action_id + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + + // index + js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); + + // entity_delta + JSONObject entity = new JSONObject(); + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); + entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, + GTaskStringUtils.GTASK_JSON_TYPE_TASK); + if (getNotes() != null) { + entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); + } + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + + // parent_id + js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); + + // dest_parent_type + js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, + GTaskStringUtils.GTASK_JSON_TYPE_GROUP); + + // list_id + js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); + + // prior_sibling_id + if (mPriorSibling != null) { + js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); + } + + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to generate task-create jsonobject"); + } + + return js; + } + + /** + * 生成任务更新操作的JSON对象 + * @param actionId 操作ID + * @return 返回表示更新操作的JSONObject + * @throws ActionFailureException 如果JSON对象生成失败,则抛出此异常 + */ + public JSONObject getUpdateAction(int actionId) { + JSONObject js = new JSONObject(); + + try { + // action_type + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); + + // action_id + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + + // id + js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); + + // entity_delta + JSONObject entity = new JSONObject(); + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + if (getNotes() != null) { + entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); + } + entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to generate task-update jsonobject"); + } + + return js; + } + + /** + * 通过远程JSON对象设置任务内容 + * @param js 远程JSON对象,包含任务的相关信息 + * @throws ActionFailureException 如果设置内容失败,则抛出此异常 + */ + public void setContentByRemoteJSON(JSONObject js) { + if (js != null) { + try { + // id + if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { + setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); + } + + // last_modified + if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { + setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); + } + + // name + if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { + setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); + } + + // notes + if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) { + setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); + } + + // deleted + if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) { + setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); + } + + // completed + if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) { + setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); + } + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to get task content from jsonobject"); + } + } + } + + /** + * 通过本地JSON对象设置任务内容 + * @param js 本地JSON对象,包含任务的相关信息 + * @throws ActionFailureException 如果设置内容失败,则抛出此异常 + */ + public void setContentByLocalJSON(JSONObject js) { + if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) + || !js.has(GTaskStringUtils.META_HEAD_DATA)) { +``` + +// 当没有可用内容时记录警告信息 +Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); +} + +try { + // 从JSON对象中获取笔记和数据数组 + JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + + // 检查笔记类型是否无效 + if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) { + Log.e(TAG, "invalid type"); + return; + } + + // 遍历数据数组以查找并设置笔记内容 + for (int i = 0; i < dataArray.length(); i++) { + JSONObject data = dataArray.getJSONObject(i); + if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { + setName(data.getString(DataColumns.CONTENT)); + break; + } + } + +} catch (JSONException e) { + // 记录并打印JSONException的堆栈跟踪 + Log.e(TAG, e.toString()); + e.printStackTrace(); +} +``` +```java +/** + * 从内容生成本地使用的JSON对象 + * + * @return 表示笔记内容的JSONObject,如果内容为空则返回null + */ +public JSONObject getLocalJSONFromContent() { + String name = getName(); + try { + // 处理从网页创建的新任务 + if (mMetaInfo == null) { + if (name == null) { + Log.w(TAG, "the note seems to be an empty one"); + return null; + } + + // 构建新的笔记JSON对象 + JSONObject js = new JSONObject(); + JSONObject note = new JSONObject(); + JSONArray dataArray = new JSONArray(); + JSONObject data = new JSONObject(); + data.put(DataColumns.CONTENT, name); + dataArray.put(data); + js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); + note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); + js.put(GTaskStringUtils.META_HEAD_NOTE, note); + return js; + } else { + // 处理已同步的任务 + JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + + // 更新数据数组中的笔记内容 + for (int i = 0; i < dataArray.length(); i++) { + JSONObject data = dataArray.getJSONObject(i); + if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { + data.put(DataColumns.CONTENT, getName()); + break; + } + } + + note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); + return mMetaInfo; + } + } catch (JSONException e) { + // 记录并打印JSONException的堆栈跟踪 + Log.e(TAG, e.toString()); + e.printStackTrace(); + return null; + } +} +``` +```java +/** + * 设置任务的元信息 + * + * @param metaData 包含笔记信息的MetaData对象 + */ +public void setMetaInfo(MetaData metaData) { + if (metaData != null && metaData.getNotes() != null) { + try { + // 将笔记信息转换为JSON对象 + mMetaInfo = new JSONObject(metaData.getNotes()); + } catch (JSONException e) { + // 转换失败时记录错误并将mMetaInfo设置为null + Log.w(TAG, e.toString()); + mMetaInfo = null; + } + } +} +``` +```java +/** + * 根据当前状态和数据库游标确定同步操作 + * + * @param c 指向数据库中笔记数据的游标 + * @return 表示要执行的同步操作的整数 + */ +public int getSyncAction(Cursor c) { + try { + // 从mMetaInfo中获取笔记信息 + JSONObject noteInfo = null; + if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) { + noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + } + + // 根据笔记信息确定同步操作 + if (noteInfo == null) { + Log.w(TAG, "it seems that note meta has been deleted"); + return SYNC_ACTION_UPDATE_REMOTE; + } + + if (!noteInfo.has(NoteColumns.ID)) { + Log.w(TAG, "remote note id seems to be deleted"); + return SYNC_ACTION_UPDATE_LOCAL; + } + + // 验证笔记ID + if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) { + Log.w(TAG, "note id doesn't match"); + return SYNC_ACTION_UPDATE_LOCAL; + } + + // 根据本地修改状态确定同步操作 + if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + return SYNC_ACTION_NONE; + } else { + return SYNC_ACTION_UPDATE_LOCAL; + } + } else { + // 验证GTasks ID + if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { + Log.e(TAG, "gtask id doesn't match"); + return SYNC_ACTION_ERROR; + } + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + return SYNC_ACTION_UPDATE_REMOTE; + } else { + return SYNC_ACTION_UPDATE_CONFLICT; + } + } + } catch (Exception e) { + // 记录并打印异常的堆栈跟踪 + Log.e(TAG, e.toString()); + e.printStackTrace(); + } + + return SYNC_ACTION_ERROR; +} +``` +```java +/** + * 检查任务是否有值得保存的内容 + * + * @return 布尔值,表示任务是否有值得保存的内容 + */ +public boolean isWorthSaving() { + return mMetaInfo != null || (getName() != null && getName().trim().length() > 0) + || (getNotes() != null && getNotes().trim().length() > 0); +} +``` +```java +// 设置任务的完成状态 +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; +} +``` diff --git a/TaskList.java b/TaskList.java new file mode 100644 index 0000000..bd71738 --- /dev/null +++ b/TaskList.java @@ -0,0 +1,449 @@ +```java +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 类 + * 它包含一个任务列表和相关的操作,如创建和更新任务列表的 JSON 对象 + */ +public class TaskList extends Node { + private static final String TAG = TaskList.class.getSimpleName(); + + // 任务列表的索引 + private int mIndex; + + // 任务列表 + private ArrayList mChildren; + + /** + * TaskList 构造函数初始化任务列表和索引 + */ + public TaskList() { + super(); + mChildren = new ArrayList(); + mIndex = 1; + } + + /** + * 生成创建任务列表的 JSON 对象 + * + * @param actionId 操作的 ID + * @return 创建操作的 JSON 对象 + * @throws ActionFailureException 如果生成 JSON 对象失败 + */ + public JSONObject getCreateAction(int actionId) { + JSONObject js = new JSONObject(); + + try { + // action_type + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); + + // action_id + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + + // index + js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); + + // entity_delta + JSONObject entity = new JSONObject(); + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); + entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, + GTaskStringUtils.GTASK_JSON_TYPE_GROUP); + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to generate tasklist-create jsonobject"); + } + + return js; + } + + /** + * 生成更新任务列表的 JSON 对象 + * + * @param actionId 操作的 ID + * @return 更新操作的 JSON 对象 + * @throws ActionFailureException 如果生成 JSON 对象失败 + */ + public JSONObject getUpdateAction(int actionId) { + JSONObject js = new JSONObject(); + + try { + // action_type + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); + + // action_id + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + + // id + js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); + + // entity_delta + JSONObject entity = new JSONObject(); + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to generate tasklist-update jsonobject"); + } + + return js; + } + + /** + * 通过远程 JSON 对象设置任务列表的内容 + * + * @param js 远程 JSON 对象 + * @throws ActionFailureException 如果设置内容失败 + */ + public void setContentByRemoteJSON(JSONObject js) { + if (js != null) { + try { + // id + if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { + setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); + } + + // last_modified + if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { + setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); + } + + // name + if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { + setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); + } + + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to get tasklist content from jsonobject"); + } + } + } + + /** + * 通过本地 JSON 对象设置任务列表的内容 + * + * @param js 本地 JSON 对象 + * @throws ActionFailureException 如果设置内容失败 + */ + public void setContentByLocalJSON(JSONObject js) { + if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) { + Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); + } + + try { + JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + + if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { + String name = folder.getString(NoteColumns.SNIPPET); + setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); + } else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { + if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER) + setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT); + else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER) + setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_CALL_NOTE); + else + Log.e(TAG, "invalid system folder"); + } else { + Log.e(TAG, "error type"); + } + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); +} +} + +/** + * 获取本地 JSON 内容 + * + * 该方法构建一个表示本地笔记信息的 JSONObject,包括文件夹名称和类型 + * @return 包含本地笔记信息的 JSONObject,如果发生异常则返回 null + */ +public JSONObject getLocalJSONFromContent() { + try { + JSONObject js = new JSONObject(); + JSONObject folder = new JSONObject(); + + String folderName = getName(); + if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) + folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(), + folderName.length()); + folder.put(NoteColumns.SNIPPET, folderName); + 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); + + js.put(GTaskStringUtils.META_HEAD_NOTE, folder); + + return js; + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + return null; + } +} + +/** + * 根据游标获取同步操作 + * + * 根据游标信息确定本地和远程笔记之间的同步操作 + * @param c 指向数据库记录的游标 + * @return 同步操作常量之一 + */ +public int getSyncAction(Cursor c) { + try { + if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { + // 本地没有更新 + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + // 双方都没有更新 + return SYNC_ACTION_NONE; + } else { + // 将远程应用到本地 + return SYNC_ACTION_UPDATE_LOCAL; + } + } else { + // 验证 gtask id + if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { + Log.e(TAG, "gtask id 不匹配"); + return SYNC_ACTION_ERROR; + } + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + // 仅本地有修改 + return SYNC_ACTION_UPDATE_REMOTE; + } else { + // 对于文件夹冲突,仅应用本地修改 + return SYNC_ACTION_UPDATE_REMOTE; + } + } + } catch (Exception e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + } + + return SYNC_ACTION_ERROR; +} + +/** + * 获取子任务的数量 + * + * @return 子任务的数量 + */ +public int getChildTaskCount() { + return mChildren.size(); +} + +/** + * 添加子任务 + * + * 如果子任务列表中不存在该任务,则将其添加到子任务列表中 + * @param task 要添加的任务 + * @return 如果任务成功添加则返回 true,否则返回 false + */ +public boolean addChildTask(Task task) { + boolean ret = false; + if (task != null && !mChildren.contains(task)) { + ret = mChildren.add(task); + if (ret) { + // 需要设置前一个兄弟任务和父任务 + task.setPriorSibling(mChildren.isEmpty() ? null : mChildren + .get(mChildren.size() - 1)); + task.setParent(this); + } + } + return ret; +} + +/** + * 在指定索引位置添加子任务 + * + * 将任务添加到子任务列表的指定索引位置,并更新任务列表 + * @param task 要添加的任务 + * @param index 要添加任务的索引位置 + * @return 如果任务成功添加则返回 true,否则返回 false + */ +public boolean addChildTask(Task task, int index) { + if (index < 0 || index > mChildren.size()) { + Log.e(TAG, "添加子任务: 无效的索引"); + return false; + } + + int pos = mChildren.indexOf(task); + if (task != null && pos == -1) { + mChildren.add(index, task); + + // 更新任务列表 + Task preTask = null; + Task afterTask = null; + if (index != 0) + preTask = mChildren.get(index - 1); + if (index != mChildren.size() - 1) + afterTask = mChildren.get(index + 1); + + task.setPriorSibling(preTask); + if (afterTask != null) + afterTask.setPriorSibling(task); + } + + return true; +} + +/** + * 移除子任务 + * + * 从子任务列表中移除任务,并更新任务列表 + * @param task 要移除的任务 + * @return 如果任务成功移除则返回 true,否则返回 false + */ +public boolean removeChildTask(Task task) { + boolean ret = false; + int index = mChildren.indexOf(task); + if (index != -1) { + ret = mChildren.remove(task); + + if (ret) { + // 重置前一个兄弟任务和父任务 + task.setPriorSibling(null); + task.setParent(null); + + // 更新任务列表 + if (index != mChildren.size()) { + mChildren.get(index).setPriorSibling( + index == 0 ? null : mChildren.get(index - 1)); + } + } + } + return ret; +} + +/** + * 移动子任务到新的索引位置 + * + * 将任务在子任务列表中移动到新的索引位置 + * @param task 要移动的任务 + * @param index 新的索引位置 + * @return 如果任务成功移动则返回 true,否则返回 false + */ +public boolean moveChildTask(Task task, int index) { + if (index < 0 || index >= mChildren.size()) { + Log.e(TAG, "移动子任务: 无效的索引"); + return false; + } + + int pos = mChildren.indexOf(task); + if (pos == -1) { + Log.e(TAG, "移动子任务: 任务不在列表中"); + return false; + } + + if (pos == index) + return true; + return (removeChildTask(task) && addChildTask(task, index)); +} + +/** + * 通过 GID 查找子任务 + * + * @param gid 要查找的任务的 GID + * @return 找到的任务,如果没有找到则返回 null + */ +public Task findChildTaskByGid(String gid) { + for (int i = 0; i < mChildren.size(); i++) { + Task t = mChildren.get(i); + if (t.getGid().equals(gid)) { + return t; + } + } + return null; +} + +/** + * 获取子任务的索引 + * + * @param task 子任务 + * @return 子任务的索引,如果没有找到则返回 -1 + */ +public int getChildTaskIndex(Task task) { + return mChildren.indexOf(task); +} + +/** + * 通过索引获取子任务 + * + * @param index 子任务的索引 + * @return 指定索引位置的子任务,如果索引无效则返回 null + */ +public Task getChildTaskByIndex(int index) { + if (index < 0 || index >= mChildren.size()) { + Log.e(TAG, "通过索引获取任务: 无效的索引"); + return null; + } + return mChildren.get(index); +} + +/** + * 通过 GID 查找子任务(替代方法) + * + * @param gid 要查找的任务的 GID + * @return 找到的任务,如果没有找到则返回 null + */ +public Task getChilTaskByGid(String gid) { + for (Task task : mChildren) { + if (task.getGid().equals(gid)) + return task; + } + return null; +} + +/** + * 获取子任务列表 + * + * @return 子任务列表 + */ +public ArrayList getChildTaskList() { + return this.mChildren; +} + +/** + * 设置任务的索引 + * + * @param index 任务的新索引 + */ +public void setIndex(int index) { + this.mIndex = index; +} + +/** + * 获取任务的索引 + * + * @return 任务的索引 + */ +public int getIndex() { + return this.mIndex; +} +} +```