You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gitProject/doc/tool/BackupUtils.java

443 lines
19 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* 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.tool;
import android.content.Context;
import android.database.Cursor;
import android.os.Environment;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
public class BackupUtils {
private static final String TAG = "BackupUtils";
// Singleton stuff
private static BackupUtils sInstance;
/**
* 返回 BackupUtils 类的单例实例,并确保线程安全。
* @param context 应用程序的上下文。
* @return BackupUtils 类的实例。
*/
public static synchronized BackupUtils getInstance(Context context) {
// 如果实例不存在,创建一个新的实例
if (sInstance == null) {
sInstance = new BackupUtils(context);
}
// 返回现有实例或新创建的实例
return sInstance;
}
/**
* Following states are signs to represents backup or restore
* status
*/
// Currently, the sdcard is not mounted
// SD卡未挂载的状态
public static final int STATE_SD_CARD_UNMOUONTED = 0;
// 备份文件不存在的状态
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// 数据格式不正确,可能被其他程序更改的状态
public static final int STATE_DATA_DESTROIED = 2;
// 恢复或备份失败的状态,由运行时异常引起
public static final int STATE_SYSTEM_ERROR = 3;
// 备份或恢复成功的状态
public static final int STATE_SUCCESS = 4;
private TextExport mTextExport; // 用于导出文本数据的 TextExport 实例
// 构造函数,用于创建 BackupUtils 类的实例
private BackupUtils(Context context) {
mTextExport = new TextExport(context); // 创建 TextExport 实例并保存到成员变量中
}
/**
* 检查外部存储是否可用。
* @return 如果外部存储可用,则返回 true否则返回 false。
*/
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
/**
* 将数据导出到文本文件中。
* @return 导出操作的结果状态。
*/
public int exportToText() {
return mTextExport.exportToText();
}
/**
* 获取导出的文本文件名。
* @return 导出的文本文件名。
*/
public String getExportedTextFileName() {
return mTextExport.mFileName;
}
/**
* 获取导出的文本文件所在的目录。
* @return 导出的文本文件所在的目录。
*/
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory;
}
private static class TextExport {
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID, // 笔记的标识符
NoteColumns.MODIFIED_DATE, // 笔记最近修改的日期
NoteColumns.SNIPPET, // 笔记内容的片段,用于快速预览笔记
NoteColumns.TYPE // 笔记的类型
};
private static final int NOTE_COLUMN_ID = 0; // 列 ID
private static final int NOTE_COLUMN_MODIFIED_DATE = 1; // 列 MODIFIED_DATE
private static final int NOTE_COLUMN_SNIPPET = 2; // 列 SNIPPET
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT, // 数据的主体内容
DataColumns.MIME_TYPE, // 数据的 MIME 类型
DataColumns.DATA1, // 此列可能包含电话号码、电子邮件地址等。
DataColumns.DATA2, // 此列可能包含电话类型(家庭、工作等)、电子邮件类型等。
DataColumns.DATA3, // 通常情况下,此列的使用与 DATA2 相同。
DataColumns.DATA4 // 保留供将来使用。
};
private static final int DATA_COLUMN_CONTENT = 0; // 列 CONTENT
private static final int DATA_COLUMN_MIME_TYPE = 1; // 列 MIME_TYPE
private static final int DATA_COLUMN_CALL_DATE = 2; // 列 CALL_DATE
private static final int DATA_COLUMN_PHONE_NUMBER = 4; // 列 PHONE_NUMBER
private final String[] TEXT_FORMAT; // 存储导出文本的格式
private static final int FORMAT_FOLDER_NAME = 0; // 文件夹名对应的格式
private static final int FORMAT_NOTE_DATE = 1; // 笔记日期对应的格式
private static final int FORMAT_NOTE_CONTENT = 2; // 笔记内容对应的格式
private Context mContext; // 应用程序上下文
private String mFileName; // 导出文件的名称
private String mFileDirectory; // 导出文件所在的目录
/**
* 构造函数,用于创建 TextExport 类的实例。
*
* @param context 应用程序上下文。
*/
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); // 从资源中获取导出文本的格式
mContext = context; // 保存应用程序上下文
mFileName = ""; // 导出文件名称为空
mFileDirectory = ""; // 导出文件所在目录为空
}
/**
* 获取指定索引处的导出文本格式。
*
* @param id 索引值。
* @return 指定索引处的导出文本格式。
*/
private String getFormat(int id) {
return TEXT_FORMAT[id]; // 返回指定索引处的导出文本格式
}
/**
* Export the folder identified by folder id to text
*/
/**
* 导出指定笔记本中的所有笔记到文本文件。
*
* @param folderId 笔记本的标识符。
* @param ps PrintStream 对象,用于写入导出的文本数据。
*/
private void exportFolderToText(String folderId, PrintStream ps) {
// 查询属于该笔记本的笔记
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[]{folderId}, null);
if (notesCursor != null) {
if (notesCursor.moveToFirst()) {
do {
// 打印笔记的最近修改日期
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// 查询属于该笔记的数据
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps); // 将笔记导出到文本文件
} while (notesCursor.moveToNext());
}
notesCursor.close();
}
}
/**
* 将指定 ID 的笔记导出到打印流中。
*
* @param noteId 要导出的笔记 ID。
* @param ps 要写入笔记的打印流对象。
*/
private void exportNoteToText(String noteId, PrintStream ps) {
// 查询与该笔记关联的数据项
Cursor dataCursor = mContext.getContentResolver().query(
Notes.CONTENT_DATA_URI, // 查询的 URI
DATA_PROJECTION, // 要查询的列名
DataColumns.NOTE_ID + "=?", // WHERE 子句
new String[]{noteId}, // WHERE 子句中占位符的值
null // 排序方式
);
if (dataCursor != null) {
if (dataCursor.moveToFirst()) {
do {
// 获取当前数据项的 MIME 类型
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// 处理通话记录类型的笔记
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); // 电话号码
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); // 通话日期
String location = dataCursor.getString(DATA_COLUMN_CONTENT); // 通话位置
if (!TextUtils.isEmpty(phoneNumber)) {
// 如果电话号码不为空,就将其写入打印流中
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), phoneNumber));
}
// 将通话日期格式化为字符串,并写入打印流中
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
DateFormat.format(mContext.getString(R.string.format_datetime_mdhm), callDate)));
if (!TextUtils.isEmpty(location)) {
// 如果通话位置不为空,就将其写入打印流中
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), location));
}
} else if (DataConstants.NOTE.equals(mimeType)) {
// 处理普通笔记类型
String content = dataCursor.getString(DATA_COLUMN_CONTENT); // 笔记内容
if (!TextUtils.isEmpty(content)) {
// 如果笔记内容不为空,就将其写入打印流中
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), content));
}
}
} while (dataCursor.moveToNext());
}
// 关闭游标
dataCursor.close();
}
// 在每个笔记之间输出一个分隔符
try {
ps.write(new byte[]{Character.LINE_SEPARATOR, Character.LETTER_NUMBER});
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
/**
* 将笔记导出为纯文本文件。
*
* @return 导出的状态码。
*/
public int exportToText() {
// 检查外部存储是否可用
if (!externalStorageAvailable()) {
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED;
}
// 获取打印流对象,准备写入数据
PrintStream ps = getExportToTextPrintStream();
if (ps == null) {
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// 先导出所有的文件夹及其笔记
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI, // 查询的 URI
NOTE_PROJECTION, // 要查询的列名
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, // WHERE 子句
null, // WHERE 子句中占位符的值
null // 排序方式
);
if (folderCursor != null) {
if (folderCursor.moveToFirst()) {
do {
// 打印文件夹名称
String folderName = "";
if (folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
}
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
// 导出当前文件夹下的所有笔记
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
// 关闭游标
folderCursor.close();
}
// 导出根目录下的笔记
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI, // 查询的 URI
NOTE_PROJECTION, // 要查询的列名
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + "=0", // WHERE 子句
null, // WHERE 子句中占位符的值
null // 排序方式
);
if (noteCursor != null) {
if (noteCursor.moveToFirst()) {
do {
// 打印笔记的最后修改时间
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// 导出当前笔记的内容
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
// 关闭游标
noteCursor.close();
}
// 关闭打印流
ps.close();
return STATE_SUCCESS;
}
/**
* Get a print stream pointed to the file {@generateExportedTextFile}
*/
/**
* 生成用于将数据输出到文本文件中的PrintStream对象。
*
* @return 用于将数据输出到文本文件中的PrintStream对象。
*/
private PrintStream getExportToTextPrintStream() {
// 生成指定路径和名称的文本文件保存在SD卡上。
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
if (file == null) {
// 如果创建文件失败则记录错误日志并返回null。
Log.e(TAG, "create file to exported failed");
return null;
}
// 将文件名保存在成员变量中。
mFileName = file.getName();
// 将文件目录路径保存在成员变量中。
mFileDirectory = mContext.getString(R.string.file_path);
PrintStream ps = null;
try {
// 创建一个新的FileOutputStream对象并将其包装在PrintStream对象中。
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
// 如果找不到文件则打印堆栈跟踪并返回null。
e.printStackTrace();
return null;
} catch (NullPointerException e) {
// 如果发生空指针异常则打印堆栈跟踪并返回null。
e.printStackTrace();
return null;
}
// 返回用于将数据输出到文本文件中的PrintStream对象。
return ps;
}
}
/**
* Generate the text file to store imported data
*/
/**
* 在SD卡上创建目录和指定格式的文件。
*
* @param context 上下文对象。
* @param filePathResId 文件路径的字符串资源ID。
* @param fileNameFormatResId 文件名格式的字符串资源ID。
* @return 生成的文件对象如果创建失败则返回null。
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
// 创建一个StringBuilder对象用于拼接文件路径和名称。
StringBuilder sb = new StringBuilder();
// 将外部存储器目录添加到StringBuilder中。
sb.append(Environment.getExternalStorageDirectory());
// 将文件路径的字符串资源ID添加到StringBuilder中。
sb.append(context.getString(filePathResId));
// 创建一个包含文件路径的File对象。
File filedir = new File(sb.toString());
// 将文件名格式化并将其添加到StringBuilder末尾。
sb.append(context.getString(fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd), System.currentTimeMillis())));
// 创建一个包含完整文件路径的File对象。
File file = new File(sb.toString());
try {
// 如果文件夹不存在,则创建它。
if (!filedir.exists()) {
filedir.mkdir();
}
// 如果文件不存在,则创建它。
if (!file.exists()) {
file.createNewFile();
}
// 返回创建的文件对象。
return file;
} catch (SecurityException e) {
// 捕获安全异常,并打印错误日志。
e.printStackTrace();
} catch (IOException e) {
// 捕获I/O异常并打印错误日志。
e.printStackTrace();
}
// 如果创建文件失败则返回null。
return null;
}
}