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.
2Q1/tool/BackupUtils.java

394 lines
24 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;
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
public static final int STATE_SD_CARD_UNMOUONTED = 0;
// The backup file not exist
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// The data is not well formated, may be changed by other programs
public static final int STATE_DATA_DESTROIED = 2;
// Some run-time exception which causes restore or backup fails
public static final int STATE_SYSTEM_ERROR = 3;
// Backup or restore success
public static final int STATE_SUCCESS = 4;
private TextExport mTextExport;
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
}
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
public int exportToText() {
return mTextExport.exportToText();
}
public String getExportedTextFileName() {
return mTextExport.mFileName;
}
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;
private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
private static final int NOTE_COLUMN_SNIPPET = 2;
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
private static final int DATA_COLUMN_CONTENT = 0;
private static final int DATA_COLUMN_MIME_TYPE = 1;
private static final int DATA_COLUMN_CALL_DATE = 2;
private static final int DATA_COLUMN_PHONE_NUMBER = 4;
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;
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
}
private String getFormat(int id) {
return TEXT_FORMAT[id];
}
/**
* Export the folder identified by folder id to text
*/
private void exportFolderToText(String folderId, PrintStream ps) {
// Query notes belong to this folder
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 {
// Print note's last modified date
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
}
notesCursor.close();
}
}
/**
* Export note identified by id to a print stream
*/
private void exportNoteToText(String noteId, PrintStream ps) {
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId
}, null);
if (dataCursor != null) {
if (dataCursor.moveToFirst()) {
do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// Print phone number
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));
}
// Print call date
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
// Print call attachment location
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();
}
// print a line separator between note
try {
ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
});
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
/**
* 该方法用于将笔记相关内容导出为用户可读的文本格式,会进行诸如检查外部存储是否可用、获取输出流、查询并处理文件夹及笔记数据等操作,最后返回表示导出操作结果的状态码。
*/
public int exportToText() {
// 调用externalStorageAvailable方法此处未展示其实现推测是用于检查外部存储是否可用的方法如果外部存储不可用执行以下逻辑。
if (!externalStorageAvailable()) {
// 记录调试日志提示“Media was not mounted”表示外部存储介质如SD卡等未挂载然后返回STATE_SD_CARD_UNMOUONTED应该是预定义的表示SD卡未挂载的状态码常量告知导出操作因存储问题失败。
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED;
}
// 调用getExportToTextPrintStream方法下方有其具体实现尝试获取一个用于将内容输出到文本文件的打印流对象如果获取失败返回null执行以下逻辑。
PrintStream ps = getExportToTextPrintStream();
if (ps == null) {
// 记录错误日志提示“get print stream error”表示获取打印流出现错误然后返回STATE_SYSTEM_ERROR应该是预定义的表示系统错误的状态码常量告知导出操作因获取输出流问题失败。
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// 首先导出文件夹及其包含的笔记信息
// 使用mContext应该是上下文对象用于获取内容解析器等操作与安卓系统交互的getContentResolver方法获取内容解析器并调用其query方法发起数据库查询操作查询的URI为Notes.CONTENT_NOTE_URI可能是预定义的表示笔记相关内容的通用URI
// 查询的字段列表为NOTE_PROJECTION应该是预定义的表示要获取的具体字段的数组常量筛选条件通过SQL语句指定筛选出类型为文件夹NoteColumns.TYPE字段等于Notes.TYPE_FOLDER并且父文件夹ID不是回收站文件夹IDNoteColumns.PARENT_ID字段不等于Notes.ID_TRASH_FOLER或者ID等于通话记录文件夹IDNoteColumns.ID字段等于Notes.ID_CALL_RECORD_FOLDER的记录最后两个参数为null表示无排序等额外条件。
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
// 判断游标对象folderCursor是否获取成功不为null如果获取成功说明查询操作至少在获取游标这一步是正常执行的可以尝试遍历游标获取查询结果数据并进行处理。
if (folderCursor!= null) {
// 将游标移动到第一条数据位置,准备开始遍历游标获取数据。
if (folderCursor.moveToFirst()) {
// 使用do-while循环遍历游标中的所有数据记录每次循环处理一条记录直到游标移动到最后一条记录后无法再移动moveToNext返回false为止。
do {
// 初始化一个空字符串用于存储文件夹名称,后续会根据不同情况赋值。
String folderName = "";
// 判断当前游标指向的记录的ID通过folderCursor.getLong(NOTE_COLUMN_ID)获取NOTE_COLUMN_ID应该是预定义的表示ID所在列索引的常量是否等于通话记录文件夹IDNotes.ID_CALL_RECORD_FOLDER如果相等执行以下逻辑。
if (folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
// 通过mContext.getString方法用于获取字符串资源获取预定义的通话记录文件夹名称字符串资源R.string.call_record_folder_name并赋值给folderName变量作为通话记录文件夹的名称。
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
// 如果不是通话记录文件夹ID通过folderCursor.getString方法用于从游标获取字符串类型数据获取记录中的文件夹名称字段NOTE_COLUMN_SNIPPET应该是预定义的表示文件夹名称所在列索引的常量的值并赋值给folderName变量获取普通文件夹的名称。
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
}
// 判断folderName是否不为空字符串即通过TextUtils.isEmpty方法判断是否有有效的文件夹名称如果不为空执行以下逻辑。
if (!TextUtils.isEmpty(folderName)) {
// 使用ps.println方法通过前面获取的打印流对象ps将内容输出到文本文件中println表示输出一行文本并换行按照指定的格式通过getFormat方法获取FORMAT_FOLDER_NAME对应的格式字符串此处getFormat方法实现未展示输出文件夹名称将folderName变量的值按照格式要求格式化后输出到文本文件中。
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
// 通过folderCursor.getString方法获取当前记录的文件夹IDNOTE_COLUMN_ID所在列的数据并赋值给folderId变量用于后续导出该文件夹下的笔记信息。
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
// 调用exportFolderToText方法此处未展示其实现推测是用于将指定文件夹及其内容导出为文本的方法传入获取的folderId和打印流对象ps将该文件夹及其包含的笔记信息导出到文本文件中。
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
// 无论是否成功遍历完所有数据记录都要关闭游标对象folderCursor释放相关的系统资源如数据库连接等资源避免资源泄漏。
folderCursor.close();
}
// 导出根文件夹下的笔记信息
// 使用mContext的getContentResolver方法获取内容解析器并调用其query方法发起数据库查询操作查询的URI为Notes.CONTENT_NOTE_URI查询的字段列表为NOTE_PROJECTION筛选条件通过SQL语句指定筛选出类型为笔记NoteColumns.TYPE字段等于Notes.TYPE_NOTE并且父文件夹ID为0即根文件夹NoteColumns.PARENT_ID字段等于0的记录最后两个参数为null表示无排序等额外条件。
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
+ "=0", null, null);
// 判断游标对象noteCursor是否获取成功不为null如果获取成功说明查询操作至少在获取游标这一步是正常执行的可以尝试遍历游标获取查询结果数据并进行处理。
if (noteCursor!= null) {
// 将游标移动到第一条数据位置,准备开始遍历游标获取数据。
if (noteCursor.moveToFirst()) {
// 使用do-while循环遍历游标中的所有数据记录每次循环处理一条记录直到游标移动到最后一条记录后无法再移动moveToNext返回false为止。
do {
// 使用ps.println方法按照指定的格式通过getFormat方法获取FORMAT_NOTE_DATE对应的格式字符串此处getFormat方法实现未展示输出笔记的修改日期信息
// 先通过DateFormat.format方法用于按照指定格式格式化日期将从游标中获取的笔记修改日期noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)获取NOTE_COLUMN_MODIFIED_DATE应该是预定义的表示修改日期所在列索引的常量按照预定义的日期时间格式mContext.getString(R.string.format_datetime_mdhm)获取的格式字符串进行格式化然后将格式化后的日期字符串按照FORMAT_NOTE_DATE格式要求输出到文本文件中。
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// 通过noteCursor.getString方法获取当前记录的笔记IDNOTE_COLUMN_ID所在列的数据并赋值给noteId变量用于后续导出该笔记的详细信息。
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
// 调用exportNoteToText方法此处未展示其实现推测是用于将指定笔记导出为文本的方法传入获取的noteId和打印流对象ps将该笔记的详细信息导出到文本文件中。
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
// 无论是否成功遍历完所有数据记录都要关闭游标对象noteCursor释放相关的系统资源如数据库连接等资源避免资源泄漏。
noteCursor.close();
}
// 关闭打印流对象ps释放相关资源完成整个文本导出操作此时文本文件中应该已经包含了按照格式要求输出的文件夹、笔记等相关信息。
ps.close();
// 如果整个导出过程顺利完成没有出现上述提到的各种错误情况返回STATE_SUCCESS应该是预定义的表示操作成功的状态码常量告知导出操作成功。
return STATE_SUCCESS;
}
/**
* 获取一个指向要导出文本文件的打印流对象会先尝试创建对应的文件如果文件创建失败或者获取输出流出现问题则返回null。
*/
private PrintStream getExportToTextPrintStream() {
// 调用generateFileMountedOnSDcard方法下方有其具体实现尝试创建一个挂载在SD卡上的文件用于存储导出的文本内容传入上下文对象mContext以及文件路径和文件名相关的字符串资源IDR.string.file_path和R.string.file_name_txt_format其具体内容应该是在资源文件中定义的路径和文件名格式相关字符串获取对应的文件对象如果获取失败返回null执行以下逻辑。
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
if (file == null) {
// 记录错误日志提示“create file to exported failed”表示创建用于导出的文件失败然后返回null告知调用者获取打印流失败。
Log.e(TAG, "create file to exported failed");
return null;
}
// 将创建成功的文件的名称赋值给mFileName变量可能用于后续记录等相关用途此处具体作用取决于整体业务逻辑
mFileName = file.getName();
// 通过mContext.getString方法获取文件路径相关的字符串资源R.string.file_path对应的字符串并赋值给mFileDirectory变量可能用于后续记录等相关用途此处具体作用取决于整体业务逻辑
mFileDirectory = mContext.getString(R.string.file_path);
PrintStream ps = null;
try {
// 创建一个基于前面创建的文件对象file的文件输出流对象fos用于后续创建打印流对象并向文件中写入数据。
FileOutputStream fos = new FileOutputStream(file);
// 使用创建的文件输出流对象fos创建一个打印流对象ps通过打印流对象可以方便地按照文本格式将数据输出到文件中。
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
// 如果在创建文件输出流或者打印流过程中出现文件未找到异常比如文件路径不可访问、文件不存在且无法创建等原因导致打印异常的堆栈信息用于调试排查问题然后返回null告知调用者获取打印流失败。
e.printStackTrace();
return null;
} catch (NullPointerException e) {
// 如果在创建文件输出流或者打印流过程中出现空指针异常比如传入的文件对象为null等原因导致打印异常的堆栈信息用于调试排查问题然后返回null告知调用者获取打印流失败。
e.printStackTrace();
return null;
}
// 如果成功创建了打印流对象ps返回该对象供调用者如exportToText方法使用该打印流将数据输出到文件中进行文本导出操作。
return ps;
}
/**
* 生成挂载在SD卡上用于存储导入数据的文本文件会尝试创建文件所在的目录以及文件本身如果出现安全、IO等异常情况则返回null。
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
// 创建一个可变字符串对象sb用于拼接构建文件的完整路径。
StringBuilder sb = new StringBuilder();
// 将外部存储的根目录通过Environment.getExternalStorageDirectory方法获取通常是SD卡的根目录或者安卓设备上的外部存储根目录添加到sb中作为文件路径的起始部分。
sb.append(Environment.getExternalStorageDirectory());
// 通过context.getString方法获取文件路径相关的字符串资源filePathResId对应的字符串并添加到sb中进一步构建文件的完整路径。
sb.append(context.getString(filePathResId));
// 使用拼接好的路径字符串创建一个File对象filedir用于表示文件所在的目录如果目录不存在后续会尝试创建它。
File filedir = new File(sb.toString());
// 继续向sb中添加内容通过context.getString方法获取文件名格式相关的字符串资源fileNameFormatResId对应的字符串并使用DateFormat.format方法按照预定义的日期格式context.getString(R.string.format_date_ymd)获取的格式字符串对当前系统时间System.currentTimeMillis获取当前时间戳进行格式化将格式化后的日期字符串作为文件名的一部分添加到sb中构建完整的文件名及路径。
sb.append(context.getString(
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
// 使用拼接好的完整路径字符串创建一个File对象file用于表示最终要创建的文件如果文件不存在后续会尝试创建它。
File file = new File(sb.toString());
try {
// 判断文件所在的目录filedir是否不存在如果不存在调用mkdir方法尝试创建该目录创建成功后该目录就可用于存放文件了。
if (!filedir.exists()) {
filedir.mkdir();
}
// 判断要创建的文件file是否不存在如果不存在调用createNewFile方法尝试创建该文件创建成功后就可以用于存储数据了。
if (!file.exists()) {
file.createNewFile();
}
// 如果文件和目录创建成功返回创建好的文件对象file供调用者如getExportToTextPrintStream方法使用该文件进行后续操作如创建输出流、写入数据等
return file;
} catch (SecurityException e) {
// 如果在创建目录或者文件过程中出现安全异常比如没有足够的权限访问、创建文件或目录等原因导致打印异常的堆栈信息用于调试排查问题然后返回null告知调用者文件创建失败。
e.printStackTrace();
} catch (IOException e) {
// 如果在创建目录或者文件过程中出现IO异常比如磁盘空间不足、文件系统错误等原因导致读写文件出现问题打印异常的堆栈信息用于调试排查问题然后返回null告知调用者文件创建失败。
e.printStackTrace();
}
// 如果出现上述异常情况导致文件或目录创建失败返回null告知调用者文件创建失败。
return null;
}