From 86750a4429d7f595f2fc73f9ce6fba5d9da07d07 Mon Sep 17 00:00:00 2001 From: ocean <503259349@qq.com> Date: Mon, 8 Sep 2025 12:06:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0Mutex=E4=BA=92=E6=96=A5?= =?UTF-8?q?=E9=94=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../all/pdfreader/pro/app/util/PdfScanner.kt | 363 +++++++++--------- 1 file changed, 185 insertions(+), 178 deletions(-) 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 cb84435..e61c217 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 @@ -13,6 +13,8 @@ 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 import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import java.io.File import java.io.FileOutputStream @@ -23,210 +25,215 @@ class PdfScanner( ) { private val TAG = "ocean-PdfScanner" - + private val scanMutex = Mutex() suspend fun scanAndLoadPdfFiles(isNeedFullScan: Boolean, callback: (Boolean) -> Unit = {}) { if (!StoragePermissionHelper.hasBasicStoragePermission(context)) { LogUtil.logDebug(TAG, "权限不足") callback.invoke(false) return } - - withContext(Dispatchers.IO) { - try { - 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(), - fileSize = file.length() - ) - needUpdate = true - } - if (needUpdate) { - // 是否加密 - val currentIsPassword = isPdfEncrypted(file) - if (doc.isPassword != currentIsPassword) { - LogUtil.logDebug(TAG, "密码状态变化 -> ${doc.fileName}") - updatedDoc = updatedDoc.copy(isPassword = currentIsPassword) - } - // 缩略图(仅非加密文件) - if (!currentIsPassword) { - val newThumbnail = generateThumbnail(context, file) - 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) { + scanMutex.withLock {// 保证同一时间只有一次扫描 + withContext(Dispatchers.IO) { + try { + 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, "🆕发现新PDF文件: ${file.name}" + TAG, "文件时间或者大小被修改 -> ${doc.fileName}" ) - 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 + updatedDoc = updatedDoc.copy( + lastModified = file.lastModified(), fileSize = file.length() ) - 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 - } - - // 是否加密更新 + needUpdate = true + } + if (needUpdate) { + // 是否加密 val currentIsPassword = isPdfEncrypted(file) - if (existingDoc.isPassword != currentIsPassword) { - LogUtil.logDebug(TAG, "✅ 密码状态需要更新") + 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, "✅ 缩略图需要更新") + 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, "✅ 数据库已更新: ${file.name}" - ) - } else { - LogUtil.logDebug( - TAG, "⏩ 无需更新: ${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, " 📖 ${doc.fileName} - ${doc.pageCount}页 - ${ - FileUtils.formatFileSize( - doc.fileSize - ) - } - ${doc.thumbnailPath}" - ) + // 标记扫描完成 + ScanManager.markScanComplete(context) + val lastScanTime = ScanManager.getLastScanTime(context) + LogUtil.logDebug( + 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, "❌ 扫描出错: ${e.message}", e) + callback.invoke(false) } - // 标记扫描完成 - ScanManager.markScanComplete(context) - val lastScanTime = ScanManager.getLastScanTime(context) - LogUtil.logDebug( - 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, "❌ 扫描出错: ${e.message}", e) - callback.invoke(false) } } }