/* * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) * * 遵循 Apache 许可证 2.0 版("许可证"); * 除非遵守许可证,否则不得使用此文件。 * 您可以在以下网址获取许可证副本: * * http://www.apache.org/licenses/LICENSE-2.0 * * 除非适用法律要求或书面同意,否则根据许可证分发的软件是按"原样"提供的, * 不附带任何明示或暗示的保证或条件。请参阅许可证,了解管理权限和限制的具体语言。 */ 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; /** * 便签数据库操作类 * 负责便签(Note)的数据库增删改查及 JSON 数据转换 */ public class SqlNote { private static final String TAG = SqlNote.class.getSimpleName(); // 日志标签 private static final int INVALID_ID = -99999; // 无效 ID 标识 // 便签查询投影(指定查询的数据库字段) public static final String[] PROJECTION_NOTE = new String[] { NoteColumns.ID, // 便签 ID NoteColumns.ALERTED_DATE, // 提醒时间 NoteColumns.BG_COLOR_ID, // 背景颜色 ID NoteColumns.CREATED_DATE, // 创建时间 NoteColumns.HAS_ATTACHMENT, // 是否有附件(0/1) NoteColumns.MODIFIED_DATE, // 修改时间 NoteColumns.NOTES_COUNT, // 子便签数量(用于文件夹) NoteColumns.PARENT_ID, // 父级 ID(用于层级结构) NoteColumns.SNIPPET, // 便签摘要 NoteColumns.TYPE, // 类型(普通便签、文件夹、系统文件夹等) NoteColumns.WIDGET_ID, // 桌面小部件 ID NoteColumns.WIDGET_TYPE, // 小部件类型 NoteColumns.SYNC_ID, // 同步 ID(用于云同步) NoteColumns.LOCAL_MODIFIED, // 本地修改标记(用于同步冲突检测) NoteColumns.ORIGIN_PARENT_ID, // 原始父级 ID(可能用于同步历史记录) NoteColumns.GTASK_ID, // Google Tasks ID(任务同步) NoteColumns.VERSION // 版本号(用于乐观锁) }; // 投影字段索引(方便通过索引访问字段值) public static final int ID_COLUMN = 0; public static final int ALERTED_DATE_COLUMN = 1; public static final int BG_COLOR_ID_COLUMN = 2; public static final int CREATED_DATE_COLUMN = 3; public static final int HAS_ATTACHMENT_COLUMN = 4; public static final int MODIFIED_DATE_COLUMN = 5; public static final int NOTES_COUNT_COLUMN = 6; public static final int PARENT_ID_COLUMN = 7; public static final int SNIPPET_COLUMN = 8; public static final int TYPE_COLUMN = 9; public static final int WIDGET_ID_COLUMN = 10; public static final int WIDGET_TYPE_COLUMN = 11; public static final int SYNC_ID_COLUMN = 12; public static final int LOCAL_MODIFIED_COLUMN = 13; public static final int ORIGIN_PARENT_ID_COLUMN = 14; public static final int GTASK_ID_COLUMN = 15; public static final int VERSION_COLUMN = 16; private Context mContext; // 上下文(用于获取 ContentResolver) private ContentResolver mContentResolver; // 内容解析器(操作数据库) private boolean mIsCreate; // 是否为新建便签(未插入数据库) private long mId; // 便签 ID private long mAlertDate; // 提醒时间 private int mBgColorId; // 背景颜色 ID(默认取系统默认值) private long mCreatedDate; // 创建时间(默认当前时间) private int mHasAttachment; // 是否有附件(默认无) private long mModifiedDate; // 修改时间(默认当前时间) private long mParentId; // 父级 ID(默认根目录) private String mSnippet; // 便签摘要(默认空字符串) private int mType; // 便签类型(默认普通便签) private int mWidgetId; // 小部件 ID(默认无效 ID) private int mWidgetType; // 小部件类型(默认无效类型) private long mOriginParent; // 原始父级 ID(默认 0) private long mVersion; // 版本号(用于数据版本控制) private ContentValues mDiffNoteValues; // 待更新的字段值(增量更新) private ArrayList mDataList; // 便签内容列表(普通便签包含文本、图片等子数据) /** * 构造函数(新建便签) * 初始化默认值,标记为新建状态 */ public SqlNote(Context context) { mContext = context; mContentResolver = context.getContentResolver(); mIsCreate = true; // 标记为新建 mId = INVALID_ID; // 初始 ID 无效 mAlertDate = 0; // 无提醒时间 // 获取默认背景颜色 ID(来自 ResourceParser 工具类) mBgColorId = ResourceParser.getDefaultBgId(context); mCreatedDate = System.currentTimeMillis(); // 创建时间为当前时间 mHasAttachment = 0; // 初始无附件 mModifiedDate = System.currentTimeMillis(); // 修改时间为当前时间 mParentId = 0; // 父级默认为根目录 mSnippet = ""; // 摘要为空 mType = Notes.TYPE_NOTE; // 类型为普通便签 // 小部件 ID 设为无效值(AppWidgetManager 定义) mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; mWidgetType = Notes.TYPE_WIDGET_INVALIDE; // 无效小部件类型 mOriginParent = 0; // 原始父级默认 0 mVersion = 0; // 版本号初始为 0 mDiffNoteValues = new ContentValues(); // 初始化待更新字段集合 mDataList = new ArrayList(); // 初始化内容列表 } /** * 构造函数(从 Cursor 加载已有便签) * @param c 数据库查询结果 Cursor */ public SqlNote(Context context, Cursor c) { mContext = context; mContentResolver = context.getContentResolver(); mIsCreate = false; // 标记为已存在 loadFromCursor(c); // 从 Cursor 加载数据 mDataList = new ArrayList(); // 初始化内容列表 // 如果是普通便签,加载其子内容(文本、图片等) if (mType == Notes.TYPE_NOTE) loadDataContent(); mDiffNoteValues = new ContentValues(); // 初始化待更新字段集合 } /** * 构造函数(通过 ID 加载便签) * @param id 便签 ID */ public SqlNote(Context context, long id) { mContext = context; mContentResolver = context.getContentResolver(); mIsCreate = false; // 标记为已存在 loadFromCursor(id); // 通过 ID 从数据库加载数据 mDataList = new ArrayList(); // 初始化内容列表 // 如果是普通便签,加载其子内容 if (mType == Notes.TYPE_NOTE) loadDataContent(); mDiffNoteValues = new ContentValues(); // 初始化待更新字段集合 } /** * 通过 ID 从数据库加载便签数据 * @param id 便签 ID */ private void loadFromCursor(long id) { Cursor c = null; try { // 查询便签表,条件为 ID 匹配 c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)", new String[] { String.valueOf(id) }, null); if (c != null) { c.moveToNext(); // 移动到查询结果第一条(假设唯一) loadFromCursor(c); // 从 Cursor 解析数据 } else { Log.w(TAG, "loadFromCursor: cursor = null"); // 日志:Cursor 为空 } } finally { if (c != null) c.close(); // 关闭 Cursor 释放资源 } } /** * 从 Cursor 解析便签数据到对象属性 * @param c 数据库查询结果 Cursor */ private void loadFromCursor(Cursor c) { // 从 Cursor 中按投影索引获取字段值 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); // 加载版本号用于乐观锁 } /** * 加载普通便签的子内容(如文本段落、图片等) * 数据存储在独立的表中,通过 note_id 关联 */ private void loadDataContent() { Cursor c = null; mDataList.clear(); // 清空现有内容列表 try { // 查询便签内容表,条件为当前便签 ID 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; } // 遍历 Cursor,创建 SqlData 对象并添加到列表 while (c.moveToNext()) { SqlData data = new SqlData(mContext, c); mDataList.add(data); } } else { Log.w(TAG, "loadDataContent: cursor = null"); // 日志:Cursor 为空 } } finally { if (c != null) c.close(); // 关闭 Cursor 释放资源 } } /** * 从 JSON 对象设置便签内容(用于同步数据解析) * @param js 包含便签数据的 JSON 对象 * @return 是否解析成功 */ public boolean setContent(JSONObject js) { try { // 获取便签头部数据(note 节点) JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); int type = note.getInt(NoteColumns.TYPE); // 系统文件夹不可修改 if (type == Notes.TYPE_SYSTEM) { Log.w(TAG, "cannot set system folder"); return false; } else if (type == Notes.TYPE_FOLDER) { // 文件夹类型 // 文件夹仅支持更新摘要和类型 String snippet = note.optString(NoteColumns.SNIPPET, ""); // 安全获取摘要 if (mIsCreate || !mSnippet.equals(snippet)) { // 记录待更新的摘要字段 mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); } mSnippet = snippet; // 更新本地摘要 // 更新类型(如果有变化) type = note.optInt(NoteColumns.TYPE, Notes.TYPE_NOTE); if (mIsCreate || mType != type) { mDiffNoteValues.put(NoteColumns.TYPE, type); } mType = type; // 更新本地类型 } else if (type == Notes.TYPE_NOTE) { // 普通便签类型 // 获取内容数组(data 节点) JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 解析便签基础属性 long id = note.optLong(NoteColumns.ID, INVALID_ID); // 安全获取 ID if (mIsCreate || mId != id) { mDiffNoteValues.put(NoteColumns.ID, id); // 新建时设置 ID } mId = id; // 更新本地 ID // 解析提醒时间 long alertDate = note.optLong(NoteColumns.ALERTED_DATE, 0); if (mIsCreate || mAlertDate != alertDate) { mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate); } mAlertDate = alertDate; // 解析背景颜色 ID(默认系统默认值) int bgColorId = note.optInt(NoteColumns.BG_COLOR_ID, ResourceParser.getDefaultBgId(mContext)); if (mIsCreate || mBgColorId != bgColorId) { mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId); } mBgColorId = bgColorId; // 解析创建时间(默认当前时间) long createDate = note.optLong(NoteColumns.CREATED_DATE, System.currentTimeMillis()); if (mIsCreate || mCreatedDate != createDate) { mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate); } mCreatedDate = createDate; // 解析附件标记(默认无附件) int hasAttachment = note.optInt(NoteColumns.HAS_ATTACHMENT, 0); if (mIsCreate || mHasAttachment != hasAttachment) { mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment); } mHasAttachment = hasAttachment; // 解析修改时间(默认当前时间) long modifiedDate = note.optLong(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); if (mIsCreate || mModifiedDate != modifiedDate) { mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate); } mModifiedDate = modifiedDate; // 解析父级 ID(默认根目录) long parentId = note.optLong(NoteColumns.PARENT_ID, 0); if (mIsCreate || mParentId != parentId) { mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId); } mParentId = parentId; // 解析摘要(默认空字符串) String snippet = note.optString(NoteColumns.SNIPPET, ""); if (mIsCreate || !mSnippet.equals(snippet)) { mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); } mSnippet = snippet; // 解析类型(默认普通便签) type = note.optInt(NoteColumns.TYPE, Notes.TYPE_NOTE); if (mIsCreate || mType != type) { mDiffNoteValues.put(NoteColumns.TYPE, type); } mType = type; // 解析小部件 ID(默认无效 ID) int widgetId = note.optInt(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); if (mIsCreate || mWidgetId != widgetId) { mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId); } mWidgetId = widgetId; // 解析小部件类型(默认无效类型) int widgetType = note.optInt(NoteColumns.WIDGET_TYPE, Notes.TYPE_WIDGET_INVALIDE); if (mIsCreate || mWidgetType != widgetType) { mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType); } mWidgetType = widgetType; // 解析原始父级 ID(默认 0) long originParent = note.optLong(NoteColumns.ORIGIN_PARENT_ID,