diff --git a/app/src/main/java/com/all/pdfreader/pro/app/PDFReaderApplication.kt b/app/src/main/java/com/all/pdfreader/pro/app/PDFReaderApplication.kt index 9440544..a7f0ad7 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/PDFReaderApplication.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/PDFReaderApplication.kt @@ -11,6 +11,8 @@ class PDFReaderApplication : Application() { private lateinit var instance: PDFReaderApplication fun getInstance(): PDFReaderApplication = instance fun getContext(): Context = instance.applicationContext + //是新创建了界面,则需要全盘扫描,进入onResume判定,扫描方法调用后置为false + var isNeedFullScan = false } private lateinit var fileChangeObserver: FileChangeObserver @@ -18,7 +20,7 @@ class PDFReaderApplication : Application() { override fun onCreate() { super.onCreate() instance = this - + isNeedFullScan = true // 初始化文件变化监听(不立即启动) fileChangeObserver = FileChangeObserver(this) diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/MainActivity.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/MainActivity.kt index e46555e..1a39111 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/MainActivity.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/MainActivity.kt @@ -158,20 +158,28 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback } private fun scanningStrategy() { - // 智能扫描策略 - if (pdfScanner.shouldScan()) { - logDebug("🔄 需要扫描PDF文件 (首次启动或超过24小时)") - if (StoragePermissionHelper.hasBasicStoragePermission(this)) { - lifecycleScope.launch { - pdfScanner.scanAndLoadPdfFiles() - } - } else { - logDebug("❌ 权限不足,跳过扫描") + if (StoragePermissionHelper.hasBasicStoragePermission(this)) { + lifecycleScope.launch { + pdfScanner.scanAndLoadPdfFiles(PDFReaderApplication.isNeedFullScan) } } else { - val hoursAgo = pdfScanner.getHoursSinceLastScan() - logDebug("⏭️ 跳过扫描,上次扫描在${hoursAgo}小时前") + logDebug("❌ 权限不足,跳过扫描") } + +// // 智能扫描策略 +// if (pdfScanner.shouldScan()) { +// logDebug("🔄 需要扫描PDF文件 (首次启动或超过24小时)") +// if (StoragePermissionHelper.hasBasicStoragePermission(this)) { +// lifecycleScope.launch { +// pdfScanner.scanAndLoadPdfFiles() +// } +// } else { +// logDebug("❌ 权限不足,跳过扫描") +// } +// } else { +// val hoursAgo = pdfScanner.getHoursSinceLastScan() +// logDebug("⏭️ 跳过扫描,上次扫描在${hoursAgo}小时前") +// } } // 授权后续操作 diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/HomeFrag.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/HomeFrag.kt index e33b7e8..1001a40 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/HomeFrag.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/HomeFrag.kt @@ -52,7 +52,7 @@ class HomeFrag : BaseFrag(), MainActivity.SortableFragment { binding.swipeRefreshLayout.setOnRefreshListener { lifecycleScope.launch { val pdfScanner = PdfScanner(requireContext(), getRepository()) - pdfScanner.scanAndLoadPdfFiles { b -> + pdfScanner.scanAndLoadPdfFiles(true) { b -> binding.swipeRefreshLayout.isRefreshing = false } } diff --git a/app/src/main/java/com/all/pdfreader/pro/app/util/FileChangeObserver.kt b/app/src/main/java/com/all/pdfreader/pro/app/util/FileChangeObserver.kt index a5dbd75..327dbe5 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/util/FileChangeObserver.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/util/FileChangeObserver.kt @@ -8,11 +8,14 @@ import android.os.Looper import android.provider.MediaStore import android.util.Log import androidx.lifecycle.LifecycleObserver +import com.all.pdfreader.pro.app.room.repository.PdfRepository +import com.all.pdfreader.pro.app.util.FileUtils.getFileFromUri import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import java.io.File class FileChangeObserver( private val context: Context, @@ -32,7 +35,15 @@ class FileChangeObserver( contentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) { override fun onChange(selfChange: Boolean, uri: Uri?) { super.onChange(selfChange, uri) - handleFileChange() + if (uri != null) { + val file = getFileFromUri(context, uri) + if (file != null) { + Log.d("ocean", "修改的文件: ${file.absolutePath}") + // 可以传给你的增量扫描逻辑 + + handleFileChange(file) + } + } } } @@ -63,7 +74,7 @@ class FileChangeObserver( } } - private fun handleFileChange() { + private fun handleFileChange(file: File) { val currentTime = System.currentTimeMillis() if (currentTime - lastScanTime < debounceDelay) { return // 防抖 @@ -76,7 +87,18 @@ class FileChangeObserver( if (StoragePermissionHelper.hasBasicStoragePermission(context)) { Log.d("ocean", "📂 检测到文件变化,可以执行增量扫描...") - + val pdfRepository = PdfRepository.getInstance() + val existingDoc = pdfRepository.getDocumentByPath(file.absolutePath) + if (existingDoc != null) { + LogUtil.logDebug( + "ocean", + "PdfScanner: 📖 ${existingDoc.fileName} - ${existingDoc.pageCount}页 - ${ + FileUtils.formatFileSize( + existingDoc.fileSize + ) + } - ${existingDoc.thumbnailPath}" + ) + } } } } diff --git a/app/src/main/java/com/all/pdfreader/pro/app/util/FileUtils.kt b/app/src/main/java/com/all/pdfreader/pro/app/util/FileUtils.kt index f757861..41a1cd9 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/util/FileUtils.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/util/FileUtils.kt @@ -344,4 +344,35 @@ object FileUtils { } catch (_: Exception) {} } } + + fun getFileFromUri(context: Context, uri: Uri): File? { + // 先尝试通过 DATA 字段获取 + val projection = arrayOf(MediaStore.Files.FileColumns.DATA) + context.contentResolver.query(uri, projection, null, null, null)?.use { cursor -> + if (cursor.moveToFirst()) { + val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA)) + if (!path.isNullOrEmpty()) { + val file = File(path) + if (file.exists()) { + return file + } + } + } + } + + // Android 10+ 或 DATA 字段为空时,使用 ContentResolver 流访问 + return try { + val tempFile = File(context.cacheDir, "temp_pdf_${System.currentTimeMillis()}.pdf") + context.contentResolver.openInputStream(uri)?.use { input -> + tempFile.outputStream().use { output -> + input.copyTo(output) + } + } + tempFile + } catch (e: Exception) { + Log.e("ocean", "无法获取文件: ${e.message}", e) + null + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/util/PdfScanner.kt b/app/src/main/java/com/all/pdfreader/pro/app/util/PdfScanner.kt index 89d4c2a..cb84435 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/util/PdfScanner.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/util/PdfScanner.kt @@ -8,6 +8,7 @@ import android.graphics.pdf.PdfRenderer import android.os.ParcelFileDescriptor import android.util.Log import androidx.core.graphics.createBitmap +import com.all.pdfreader.pro.app.PDFReaderApplication import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity import com.all.pdfreader.pro.app.room.repository.PdfRepository import com.all.pdfreader.pro.app.util.FileUtils.isPdfEncrypted @@ -23,167 +24,208 @@ class PdfScanner( private val TAG = "ocean-PdfScanner" - suspend fun scanAndLoadPdfFiles(callback: (Boolean) -> Unit = {}) { + suspend fun scanAndLoadPdfFiles(isNeedFullScan: Boolean, callback: (Boolean) -> Unit = {}) { if (!StoragePermissionHelper.hasBasicStoragePermission(context)) { - LogUtil.logDebug(TAG, "PdfScanner: 权限不足") + LogUtil.logDebug(TAG, "权限不足") callback.invoke(false) return } withContext(Dispatchers.IO) { try { - LogUtil.logDebug(TAG, "PdfScanner: 🔍 开始扫描PDF文件...") - - // 扫描应用私有目录(无需权限) - val privateFiles = FileUtils.scanPdfFiles(context) - LogUtil.logDebug( - TAG, "PdfScanner: 📁 应用私有目录找到: ${privateFiles.size} 个PDF文件" - ) - privateFiles.forEach { file -> - LogUtil.logDebug( - TAG, - "PdfScanner: 📄 ${file.name} (${FileUtils.formatFileSize(file.length())})" - ) - } - - // 扫描MediaStore(需要权限) - val mediaStoreFiles = FileUtils.scanPdfFilesFromMediaStore(context) - LogUtil.logDebug( - TAG, "PdfScanner: 📱 MediaStore找到: ${mediaStoreFiles.size} 个PDF文件" - ) - mediaStoreFiles.forEach { file -> - LogUtil.logDebug( - TAG, - "PdfScanner: 📱 ${file.name} (${FileUtils.formatFileSize(file.length())})" - ) - } - - // 合并并去重 - val allFiles = (privateFiles + mediaStoreFiles).distinctBy { it.absolutePath } - LogUtil.logDebug(TAG, "PdfScanner: 📊 总计扫描到: ${allFiles.size} 个PDF文件") - - // 处理每个PDF文件 - allFiles.forEachIndexed { index, file -> - LogUtil.logDebug( - TAG, - "PdfScanner: 🔄 处理文件 ${index + 1}/${allFiles.size}: ${file.name} - ${file.absolutePath}" - ) - - if (FileUtils.isPdfFile(file)) { - val fileHash = FileUtils.calculateFileHash(file.absolutePath) - LogUtil.logDebug(TAG, "PdfScanner: 🔑 文件哈希: $fileHash") - - if (fileHash != null) { - val existingDoc = pdfRepository.getDocumentByPath(file.absolutePath) - - if (existingDoc == null) { - LogUtil.logDebug( - TAG, "PdfScanner: 🆕 发现新PDF文件: ${file.name}" - ) - var thumbnailPath: String? = null - val isPassword = isPdfEncrypted(file) - LogUtil.logDebug(TAG, "PdfScanner: isPassword->${isPassword}") - if (!isPassword) {//没有密码的情况下才去获取缩略图 - thumbnailPath = generateThumbnail(context, file) ?: "" - } - LogUtil.logDebug(TAG, "PdfScanner: thumbnailPath->${thumbnailPath}") - val metadata = - PdfMetadataExtractor.extractMetadata(file.absolutePath) - val document = PdfDocumentEntity( - fileHash = fileHash, - filePath = file.absolutePath, - fileName = file.name, - fileSize = file.length(), + val startScannerTime = System.currentTimeMillis() + LogUtil.logDebug(TAG, "🔍开始扫描PDF文件...") + LogUtil.logDebug(TAG, "获取数据库数据") + val cachedDocs = pdfRepository.getAllDocumentsOnce() + var needFullScan = isNeedFullScan + if (!needFullScan) { + cachedDocs.forEach { doc -> + val file = File(doc.filePath)//得到数据库存储的文件路径 + if (file.exists()) { + LogUtil.logDebug(TAG, "$file 文件存在,检测是否存在变化") + var needUpdate = false + var updatedDoc = doc.copy() + // 修改时间 与 文件大小 + if (file.lastModified() != doc.lastModified || file.length() != doc.fileSize) { + LogUtil.logDebug(TAG, "文件时间或者大小被修改 -> ${doc.fileName}") + updatedDoc = updatedDoc.copy( lastModified = file.lastModified(), - pageCount = metadata?.pageCount ?: 0, - thumbnailPath = thumbnailPath, - metadataTitle = metadata?.title, - metadataAuthor = metadata?.author, - metadataSubject = metadata?.subject, - metadataKeywords = metadata?.keywords, - metadataCreationDate = metadata?.creationDate?.time, - metadataModificationDate = metadata?.modificationDate?.time, - isPassword = isPassword + fileSize = file.length() ) - pdfRepository.insertOrUpdateDocument(document) - LogUtil.logDebug( - TAG, "PdfScanner: ✅ 已保存到数据库: ${file.name}" - ) - } else { - LogUtil.logDebug(TAG, "PdfScanner: 📋 文件已存在: ${file.name}") - // 🔹 文件已存在,检查是否需要更新 - var needUpdate = false - var updatedDoc = existingDoc.copy() - - // 路径/修改时间更新 - if (existingDoc.filePath != file.absolutePath || existingDoc.lastModified != file.lastModified()) { - LogUtil.logDebug(TAG, "PdfScanner: ✅ 路径/修改时间需要更新") - updatedDoc = updatedDoc.copy( - filePath = file.absolutePath, - lastModified = file.lastModified() - ) - needUpdate = true - } - - // 是否加密更新 + needUpdate = true + } + if (needUpdate) { + // 是否加密 val currentIsPassword = isPdfEncrypted(file) - if (existingDoc.isPassword != currentIsPassword) { - LogUtil.logDebug(TAG, "PdfScanner: ✅ 密码状态需要更新") + if (doc.isPassword != currentIsPassword) { + LogUtil.logDebug(TAG, "密码状态变化 -> ${doc.fileName}") updatedDoc = updatedDoc.copy(isPassword = currentIsPassword) - needUpdate = true } - + // 缩略图(仅非加密文件) if (!currentIsPassword) { - // 如果不是加密 PDF,再生成缩略图 val newThumbnail = generateThumbnail(context, file) - if (existingDoc.thumbnailPath != newThumbnail) { - LogUtil.logDebug(TAG, "PdfScanner: ✅ 缩略图需要更新") + if (doc.thumbnailPath != newThumbnail) { updatedDoc = updatedDoc.copy(thumbnailPath = newThumbnail) + } + } else if (doc.thumbnailPath != null) { + updatedDoc = updatedDoc.copy(thumbnailPath = null) + } + pdfRepository.insertOrUpdateDocument(updatedDoc) + LogUtil.logDebug(TAG, "✅数据库已更新: ${doc.fileName}") + } + } else { + // 文件不存在 → 删除数据库记录,并触发全盘扫描 + LogUtil.logDebug(TAG, "文件不存在 -> ${doc.fileName}, 删除记录") + pdfRepository.deleteDocument(doc.fileHash) + needFullScan = true + } + } + } + if (needFullScan || cachedDocs.isEmpty()) { + LogUtil.logDebug(TAG, "数据库不完整,执行全盘扫描...") + // 扫描应用私有目录(无需权限) + val privateFiles = FileUtils.scanPdfFiles(context) + LogUtil.logDebug(TAG, "📁应用私有目录找到: ${privateFiles.size} 个PDF文件") + // 扫描MediaStore(需要权限) + val mediaStoreFiles = FileUtils.scanPdfFilesFromMediaStore(context) + LogUtil.logDebug(TAG, "📱MediaStore找到: ${mediaStoreFiles.size} 个PDF文件") + // 合并并去重 + val allFiles = (privateFiles + mediaStoreFiles).distinctBy { it.absolutePath } + LogUtil.logDebug(TAG, "📊总计扫描到: ${allFiles.size} 个PDF文件") + // 处理每个PDF文件 + allFiles.forEachIndexed { index, file -> + LogUtil.logDebug( + TAG, + "🔄处理文件 ${index + 1}/${allFiles.size}: ${file.name} - ${file.absolutePath}" + ) + + if (FileUtils.isPdfFile(file)) { + val fileHash = FileUtils.calculateFileHash(file.absolutePath) + LogUtil.logDebug(TAG, "🔑文件哈希: $fileHash") + + if (fileHash != null) { + val existingDoc = pdfRepository.getDocumentByPath(file.absolutePath) + + if (existingDoc == null) { + LogUtil.logDebug( + TAG, "🆕发现新PDF文件: ${file.name}" + ) + var thumbnailPath: String? = null + val isPassword = isPdfEncrypted(file) + LogUtil.logDebug(TAG, "isPassword->${isPassword}") + if (!isPassword) {//没有密码的情况下才去获取缩略图 + thumbnailPath = generateThumbnail(context, file) ?: "" + } + LogUtil.logDebug(TAG, "thumbnailPath->${thumbnailPath}") + val metadata = + PdfMetadataExtractor.extractMetadata(file.absolutePath) + val document = PdfDocumentEntity( + fileHash = fileHash, + filePath = file.absolutePath, + fileName = file.name, + fileSize = file.length(), + lastModified = file.lastModified(), + pageCount = metadata?.pageCount ?: 0, + thumbnailPath = thumbnailPath, + metadataTitle = metadata?.title, + metadataAuthor = metadata?.author, + metadataSubject = metadata?.subject, + metadataKeywords = metadata?.keywords, + metadataCreationDate = metadata?.creationDate?.time, + metadataModificationDate = metadata?.modificationDate?.time, + isPassword = isPassword + ) + pdfRepository.insertOrUpdateDocument(document) + LogUtil.logDebug( + TAG, " ✅ 已保存到数据库: ${file.name}" + ) + } else { + LogUtil.logDebug(TAG, " 📋 文件已存在: ${file.name}") + // 🔹 文件已存在,检查是否需要更新 + var needUpdate = false + var updatedDoc = existingDoc.copy() + + // 路径/修改时间更新 + if (existingDoc.filePath != file.absolutePath || existingDoc.lastModified != file.lastModified()) { + LogUtil.logDebug(TAG, "✅ 路径/修改时间需要更新") + updatedDoc = updatedDoc.copy( + filePath = file.absolutePath, + lastModified = file.lastModified() + ) needUpdate = true } - } else { - updatedDoc = updatedDoc.copy(thumbnailPath = null) - needUpdate = true - } - // 执行更新 - if (needUpdate) { - pdfRepository.insertOrUpdateDocument(updatedDoc) - LogUtil.logDebug( - TAG, "PdfScanner: ✅ 数据库已更新: ${file.name}" - ) - } else { - LogUtil.logDebug( - TAG, "PdfScanner: ⏩ 无需更新: ${file.name}" - ) + // 是否加密更新 + val currentIsPassword = isPdfEncrypted(file) + if (existingDoc.isPassword != currentIsPassword) { + LogUtil.logDebug(TAG, "✅ 密码状态需要更新") + updatedDoc = updatedDoc.copy(isPassword = currentIsPassword) + needUpdate = true + } + + if (!currentIsPassword) { + // 如果不是加密 PDF,再生成缩略图 + val newThumbnail = generateThumbnail(context, file) + if (existingDoc.thumbnailPath != newThumbnail) { + LogUtil.logDebug(TAG, "✅ 缩略图需要更新") + updatedDoc = + updatedDoc.copy(thumbnailPath = newThumbnail) + needUpdate = true + } + } else { + updatedDoc = updatedDoc.copy(thumbnailPath = null) + needUpdate = true + } + + // 执行更新 + if (needUpdate) { + pdfRepository.insertOrUpdateDocument(updatedDoc) + LogUtil.logDebug( + TAG, "✅ 数据库已更新: ${file.name}" + ) + } else { + LogUtil.logDebug( + TAG, "⏩ 无需更新: ${file.name}" + ) + } } } } } + // 打印数据库中的总记录数 + pdfRepository.getAllDocumentsOnce().forEach { doc -> + LogUtil.logDebug( + TAG, " 📖 ${doc.fileName} - ${doc.pageCount}页 - ${ + FileUtils.formatFileSize( + doc.fileSize + ) + } - ${doc.thumbnailPath}" + ) + } } - - // 打印数据库中的总记录数 - pdfRepository.getAllDocumentsOnce().forEach { doc -> - LogUtil.logDebug( - TAG, "PdfScanner: 📖 ${doc.fileName} - ${doc.pageCount}页 - ${ - FileUtils.formatFileSize( - doc.fileSize - ) - } - ${doc.thumbnailPath}" - ) - } - // 标记扫描完成 ScanManager.markScanComplete(context) val lastScanTime = ScanManager.getLastScanTime(context) LogUtil.logDebug( - TAG, "PdfScanner: ✅ 扫描完成,记录时间: ${java.util.Date(lastScanTime)}" + TAG, "✅ 扫描完成,记录时间: ${java.util.Date(lastScanTime)}" ) - + var string = if (needFullScan) { + "全盘扫描" + } else if (cachedDocs.isEmpty()) { + "数据库没有值,第一次全盘扫描" + } else { + "快速扫描" + } + // 计算扫描耗时 + val scannerTime = System.currentTimeMillis() - startScannerTime + LogUtil.logDebug( + TAG, + "$string 本次扫描耗时: $scannerTime ms (${scannerTime / 1000.0} 秒)" + ) + PDFReaderApplication.isNeedFullScan = false callback.invoke(true) - } catch (e: Exception) { - Log.e(TAG, "PdfScanner: ❌ 扫描出错: ${e.message}", e) + Log.e(TAG, "❌ 扫描出错: ${e.message}", e) callback.invoke(false) } }