diff --git a/app/src/main/java/io/legado/app/help/FileHelp.kt b/app/src/main/java/io/legado/app/help/FileHelp.kt index f274c84e4..cb0228445 100644 --- a/app/src/main/java/io/legado/app/help/FileHelp.kt +++ b/app/src/main/java/io/legado/app/help/FileHelp.kt @@ -1,5 +1,6 @@ package io.legado.app.help +import io.legado.app.App import java.io.File import java.io.IOException @@ -32,4 +33,9 @@ object FileHelp { } return file } + + fun getCachePath(): String { + return App.INSTANCE.externalCacheDir?.absolutePath + ?: App.INSTANCE.cacheDir.absolutePath + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/storage/Backup.kt b/app/src/main/java/io/legado/app/help/storage/Backup.kt index 6fd1f079b..cd7c69edc 100644 --- a/app/src/main/java/io/legado/app/help/storage/Backup.kt +++ b/app/src/main/java/io/legado/app/help/storage/Backup.kt @@ -13,10 +13,13 @@ import java.io.File object Backup { + val defaultPath by lazy { + FileUtils.getSdCardPath() + File.separator + "YueDu" + File.separator + "legadoBackUp" + } + fun backup() { doAsync { - val path = - FileUtils.getSdCardPath() + File.separator + "YueDu" + File.separator + "legadoBackUp" + val path = defaultPath backupBookshelf(path) backupBookSource(path) backupRssSource(path) @@ -29,8 +32,7 @@ object Backup { fun autoBackup(activity: AppCompatActivity) { doAsync { - val path = - FileUtils.getSdCardPath() + File.separator + "YueDu" + File.separator + "legadoBackUp" + val path = defaultPath backupBookshelf(path) backupBookSource(path) backupRssSource(path) diff --git a/app/src/main/java/io/legado/app/help/storage/Restore.kt b/app/src/main/java/io/legado/app/help/storage/Restore.kt index 10bc36bf7..94b24cb00 100644 --- a/app/src/main/java/io/legado/app/help/storage/Restore.kt +++ b/app/src/main/java/io/legado/app/help/storage/Restore.kt @@ -12,6 +12,7 @@ import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.ReplaceRule import io.legado.app.data.entities.RssSource import io.legado.app.help.FileHelp +import io.legado.app.help.storage.Backup.defaultPath import io.legado.app.utils.* import org.jetbrains.anko.doAsync import org.jetbrains.anko.toast @@ -25,10 +26,6 @@ object Restore { .build() ) - private val defaultPath by lazy { - FileUtils.getSdCardPath() + File.separator + "YueDu" + File.separator + "legadoBackUp" - } - fun restore(path: String = defaultPath) { doAsync { try { @@ -67,6 +64,7 @@ object Restore { } catch (e: Exception) { e.printStackTrace() } + uiThread { App.INSTANCE.toast("恢复完成") } } } diff --git a/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt b/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt new file mode 100644 index 000000000..fb5b9045a --- /dev/null +++ b/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt @@ -0,0 +1,81 @@ +package io.legado.app.help.storage + +import android.content.Context +import io.legado.app.App +import io.legado.app.help.FileHelp +import io.legado.app.lib.webdav.WebDav +import io.legado.app.lib.webdav.http.HttpAuth +import io.legado.app.utils.ZipUtils +import io.legado.app.utils.getPrefString +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.selector +import org.jetbrains.anko.uiThread +import kotlin.math.min + +object WebDavHelp { + + fun getWebDavUrl(): String? { + var url = App.INSTANCE.getPrefString("web_dav_url") + if (url.isNullOrBlank()) return null + if (!url.endsWith("/")) url += "/" + return url + } + + private fun initWebDav(): Boolean { + val account = App.INSTANCE.getPrefString("web_dav_account") + val password = App.INSTANCE.getPrefString("web_dav_password") + if (!account.isNullOrBlank() && !password.isNullOrBlank()) { + HttpAuth.auth = HttpAuth.Auth(account, password) + return true + } + return false + } + + private fun getWebDavFileNames(): ArrayList { + val url = getWebDavUrl() + val names = arrayListOf() + if (!url.isNullOrBlank() && initWebDav()) { + val files = WebDav(url + "legado/").listFiles() + files.reversed() + for (index: Int in 0 until min(10, files.size)) { + files[index].displayName?.let { + names.add(it) + } + } + } + return names + } + + fun showRestoreDialog(context: Context) { + doAsync { + val names = getWebDavFileNames() + if (names.isNotEmpty()) { + uiThread { + context.selector(title = "选择恢复文件", items = names) { _, index -> + if (index in 0 until names.size) { + restoreWebDav(names[index]) + } + } + } + } else { + Restore.restore() + } + } + } + + private fun restoreWebDav(name: String) { + doAsync { + getWebDavUrl()?.let { + val file = WebDav(it + "legado/" + name) + val zipFilePath = FileHelp.getCachePath() + "/backup" + ".zip" + file.downloadTo(zipFilePath, true) + ZipUtils.unzipFile(zipFilePath, Backup.defaultPath) + Restore.restore() + } + } + } + + fun backUpWebDav() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt b/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt index 84e610932..4a74683f5 100644 --- a/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt @@ -59,6 +59,7 @@ abstract class BaseReadAloudService : BaseService(), initMediaSession() initBroadcastReceiver() upNotification() + upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING) } override fun onDestroy() { @@ -116,7 +117,6 @@ abstract class BaseReadAloudService : BaseService(), } open fun play() { - upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING) postEvent(Bus.ALOUD_STATE, Status.PLAY) upNotification() } @@ -132,6 +132,7 @@ abstract class BaseReadAloudService : BaseService(), @CallSuper open fun resumeReadAloud() { pause = false + upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING) } abstract fun upSpeechRate(reset: Boolean = false) diff --git a/app/src/main/java/io/legado/app/utils/ZipUtils.kt b/app/src/main/java/io/legado/app/utils/ZipUtils.kt new file mode 100644 index 000000000..fc8aa97ed --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/ZipUtils.kt @@ -0,0 +1,420 @@ +package io.legado.app.utils + +import android.util.Log +import java.io.* +import java.util.* +import java.util.zip.ZipEntry +import java.util.zip.ZipFile +import java.util.zip.ZipOutputStream + +/** + *
+ * author: Blankj
+ * blog  : http://blankj.com
+ * time  : 2016/08/27
+ * desc  : utils about zip
+
* + */ +object ZipUtils { + + /** + * Zip the files. + * + * @param srcFiles The source of files. + * @param zipFilePath The path of ZIP file. + * @return `true`: success

`false`: fail + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun zipFiles( + srcFiles: Collection, + zipFilePath: String + ): Boolean { + return zipFiles(srcFiles, zipFilePath, null) + } + + /** + * Zip the files. + * + * @param srcFilePaths The paths of source files. + * @param zipFilePath The path of ZIP file. + * @param comment The comment. + * @return `true`: success

`false`: fail + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun zipFiles( + srcFilePaths: Collection?, + zipFilePath: String?, + comment: String? + ): Boolean { + if (srcFilePaths == null || zipFilePath == null) return false + var zos: ZipOutputStream? = null + try { + zos = ZipOutputStream(FileOutputStream(zipFilePath)) + for (srcFile in srcFilePaths) { + if (!zipFile(getFileByPath(srcFile)!!, "", zos, comment)) return false + } + return true + } finally { + if (zos != null) { + zos.finish() + zos.close() + } + } + } + + /** + * Zip the files. + * + * @param srcFiles The source of files. + * @param zipFile The ZIP file. + * @param comment The comment. + * @return `true`: success

`false`: fail + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + @JvmOverloads + fun zipFiles( + srcFiles: Collection?, + zipFile: File?, + comment: String? = null + ): Boolean { + if (srcFiles == null || zipFile == null) return false + var zos: ZipOutputStream? = null + try { + zos = ZipOutputStream(FileOutputStream(zipFile)) + for (srcFile in srcFiles) { + if (!zipFile(srcFile, "", zos, comment)) return false + } + return true + } finally { + if (zos != null) { + zos.finish() + zos.close() + } + } + } + + /** + * Zip the file. + * + * @param srcFilePath The path of source file. + * @param zipFilePath The path of ZIP file. + * @return `true`: success

`false`: fail + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun zipFile( + srcFilePath: String, + zipFilePath: String + ): Boolean { + return zipFile(getFileByPath(srcFilePath), getFileByPath(zipFilePath), null) + } + + /** + * Zip the file. + * + * @param srcFilePath The path of source file. + * @param zipFilePath The path of ZIP file. + * @param comment The comment. + * @return `true`: success

`false`: fail + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun zipFile( + srcFilePath: String, + zipFilePath: String, + comment: String + ): Boolean { + return zipFile(getFileByPath(srcFilePath), getFileByPath(zipFilePath), comment) + } + + /** + * Zip the file. + * + * @param srcFile The source of file. + * @param zipFile The ZIP file. + * @param comment The comment. + * @return `true`: success

`false`: fail + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + @JvmOverloads + fun zipFile( + srcFile: File?, + zipFile: File?, + comment: String? = null + ): Boolean { + if (srcFile == null || zipFile == null) return false + ZipOutputStream(FileOutputStream(zipFile)).use { zos -> + return zipFile( + srcFile, + "", + zos, + comment + ) + } + } + + @Throws(IOException::class) + private fun zipFile( + srcFile: File, + rootPath: String, + zos: ZipOutputStream, + comment: String? + ): Boolean { + var rootPath = rootPath + if (!srcFile.exists()) return true + rootPath = rootPath + (if (isSpace(rootPath)) "" else File.separator) + srcFile.name + if (srcFile.isDirectory) { + val fileList = srcFile.listFiles() + if (fileList == null || fileList.isEmpty()) { + val entry = ZipEntry("$rootPath/") + entry.comment = comment + zos.putNextEntry(entry) + zos.closeEntry() + } else { + for (file in fileList) { + if (!zipFile(file, rootPath, zos, comment)) return false + } + } + } else { + BufferedInputStream(FileInputStream(srcFile)).use { `is` -> + val entry = ZipEntry(rootPath) + entry.comment = comment + zos.putNextEntry(entry) + zos.write(`is`.readBytes()) + zos.closeEntry() + } + } + return true + } + + /** + * Unzip the file. + * + * @param zipFilePath The path of ZIP file. + * @param destDirPath The path of destination directory. + * @return the unzipped files + * @throws IOException if unzip unsuccessfully + */ + @Throws(IOException::class) + fun unzipFile(zipFilePath: String, destDirPath: String): List? { + return unzipFileByKeyword(zipFilePath, destDirPath, null) + } + + /** + * Unzip the file. + * + * @param zipFile The ZIP file. + * @param destDir The destination directory. + * @return the unzipped files + * @throws IOException if unzip unsuccessfully + */ + @Throws(IOException::class) + fun unzipFile( + zipFile: File, + destDir: File + ): List? { + return unzipFileByKeyword(zipFile, destDir, null) + } + + /** + * Unzip the file by keyword. + * + * @param zipFilePath The path of ZIP file. + * @param destDirPath The path of destination directory. + * @param keyword The keyboard. + * @return the unzipped files + * @throws IOException if unzip unsuccessfully + */ + @Throws(IOException::class) + fun unzipFileByKeyword( + zipFilePath: String, + destDirPath: String, + keyword: String? + ): List? { + return unzipFileByKeyword( + getFileByPath(zipFilePath), + getFileByPath(destDirPath), + keyword + ) + } + + /** + * Unzip the file by keyword. + * + * @param zipFile The ZIP file. + * @param destDir The destination directory. + * @param keyword The keyboard. + * @return the unzipped files + * @throws IOException if unzip unsuccessfully + */ + @Throws(IOException::class) + fun unzipFileByKeyword( + zipFile: File?, + destDir: File?, + keyword: String? + ): List? { + if (zipFile == null || destDir == null) return null + val files = ArrayList() + val zip = ZipFile(zipFile) + val entries = zip.entries() + try { + if (isSpace(keyword)) { + while (entries.hasMoreElements()) { + val entry = entries.nextElement() as ZipEntry + val entryName = entry.name + if (entryName.contains("../")) { + Log.e("ZipUtils", "entryName: $entryName is dangerous!") + continue + } + if (!unzipChildFile(destDir, files, zip, entry, entryName)) return files + } + } else { + while (entries.hasMoreElements()) { + val entry = entries.nextElement() as ZipEntry + val entryName = entry.name + if (entryName.contains("../")) { + Log.e("ZipUtils", "entryName: $entryName is dangerous!") + continue + } + if (entryName.contains(keyword!!)) { + if (!unzipChildFile(destDir, files, zip, entry, entryName)) return files + } + } + } + } finally { + zip.close() + } + return files + } + + @Throws(IOException::class) + private fun unzipChildFile( + destDir: File, + files: MutableList, + zip: ZipFile, + entry: ZipEntry, + name: String + ): Boolean { + val file = File(destDir, name) + files.add(file) + if (entry.isDirectory) { + return createOrExistsDir(file) + } else { + if (!createOrExistsFile(file)) return false + BufferedInputStream(zip.getInputStream(entry)).use { `in` -> + BufferedOutputStream(FileOutputStream(file)).use { out -> + out.write(`in`.readBytes()) + } + } + } + return true + } + + /** + * Return the files' path in ZIP file. + * + * @param zipFilePath The path of ZIP file. + * @return the files' path in ZIP file + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun getFilesPath(zipFilePath: String): List? { + return getFilesPath(getFileByPath(zipFilePath)) + } + + /** + * Return the files' path in ZIP file. + * + * @param zipFile The ZIP file. + * @return the files' path in ZIP file + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun getFilesPath(zipFile: File?): List? { + if (zipFile == null) return null + val paths = ArrayList() + val zip = ZipFile(zipFile) + val entries = zip.entries() + while (entries.hasMoreElements()) { + val entryName = (entries.nextElement() as ZipEntry).name + if (entryName.contains("../")) { + Log.e("ZipUtils", "entryName: $entryName is dangerous!") + paths.add(entryName) + } else { + paths.add(entryName) + } + } + zip.close() + return paths + } + + /** + * Return the files' comment in ZIP file. + * + * @param zipFilePath The path of ZIP file. + * @return the files' comment in ZIP file + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun getComments(zipFilePath: String): List? { + return getComments(getFileByPath(zipFilePath)) + } + + /** + * Return the files' comment in ZIP file. + * + * @param zipFile The ZIP file. + * @return the files' comment in ZIP file + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun getComments(zipFile: File?): List? { + if (zipFile == null) return null + val comments = ArrayList() + val zip = ZipFile(zipFile) + val entries = zip.entries() + while (entries.hasMoreElements()) { + val entry = entries.nextElement() as ZipEntry + comments.add(entry.comment) + } + zip.close() + return comments + } + + private fun createOrExistsDir(file: File?): Boolean { + return file != null && if (file.exists()) file.isDirectory else file.mkdirs() + } + + private fun createOrExistsFile(file: File?): Boolean { + if (file == null) return false + if (file.exists()) return file.isFile + if (!createOrExistsDir(file.parentFile)) return false + return try { + file.createNewFile() + } catch (e: IOException) { + e.printStackTrace() + false + } + } + + private fun getFileByPath(filePath: String): File? { + return if (isSpace(filePath)) null else File(filePath) + } + + private fun isSpace(s: String?): Boolean { + if (s == null) return true + var i = 0 + val len = s.length + while (i < len) { + if (!Character.isWhitespace(s[i])) { + return false + } + ++i + } + return true + } +} \ No newline at end of file