添加Mutex互斥锁

This commit is contained in:
ocean 2025-09-08 12:06:00 +08:00
parent 1f664cd191
commit 86750a4429

View File

@ -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)
}
}
}