封装扫描方法。

This commit is contained in:
ocean 2025-09-03 12:05:03 +08:00
parent c642e3322e
commit 167b5574ec
6 changed files with 172 additions and 118 deletions

View File

@ -0,0 +1,8 @@
{
"permissions": {
"allow": [
"Bash(./gradlew:*)"
],
"deny": []
}
}

View File

@ -8,7 +8,6 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.all.pdfreader.pro.app.R import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.ActivityMainBinding import com.all.pdfreader.pro.app.databinding.ActivityMainBinding
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.ui.dialog.PermissionDialogFragment import com.all.pdfreader.pro.app.ui.dialog.PermissionDialogFragment
import com.all.pdfreader.pro.app.ui.fragment.FavoriteFrag import com.all.pdfreader.pro.app.ui.fragment.FavoriteFrag
import com.all.pdfreader.pro.app.ui.fragment.HomeFrag import com.all.pdfreader.pro.app.ui.fragment.HomeFrag
@ -20,15 +19,10 @@ import com.all.pdfreader.pro.app.model.SortDirection
import com.all.pdfreader.pro.app.ui.dialog.SortDialogFragment import com.all.pdfreader.pro.app.ui.dialog.SortDialogFragment
import com.all.pdfreader.pro.app.util.AppUtils.setClickWithAnimation import com.all.pdfreader.pro.app.util.AppUtils.setClickWithAnimation
import com.all.pdfreader.pro.app.util.FileChangeObserver import com.all.pdfreader.pro.app.util.FileChangeObserver
import com.all.pdfreader.pro.app.util.FileUtils import com.all.pdfreader.pro.app.util.PdfScanner
import com.all.pdfreader.pro.app.util.PdfMetadataExtractor
import com.all.pdfreader.pro.app.util.ScanManager
import com.all.pdfreader.pro.app.util.StoragePermissionHelper import com.all.pdfreader.pro.app.util.StoragePermissionHelper
import com.gyf.immersionbar.ImmersionBar import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.concurrent.TimeUnit
class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback, class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback,
PermissionDialogFragment.CloseCallback { PermissionDialogFragment.CloseCallback {
@ -38,6 +32,7 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
private val pdfRepository = getRepository() private val pdfRepository = getRepository()
private lateinit var fileChangeObserver: FileChangeObserver private lateinit var fileChangeObserver: FileChangeObserver
private lateinit var pdfScanner: PdfScanner
private val homeFragment = HomeFrag() private val homeFragment = HomeFrag()
private val recentlyFragment = RecentlyFrag() private val recentlyFragment = RecentlyFrag()
@ -56,6 +51,7 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
setupFragments() setupFragments()
setupNavigation() setupNavigation()
pdfScanner = PdfScanner(this, pdfRepository)
if (savedInstanceState != null) { if (savedInstanceState != null) {
val restoredFragment = val restoredFragment =
supportFragmentManager.getFragment(savedInstanceState, fragmentTag) supportFragmentManager.getFragment(savedInstanceState, fragmentTag)
@ -70,16 +66,17 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
private fun scanningStrategy() { private fun scanningStrategy() {
// 智能扫描策略 // 智能扫描策略
if (ScanManager.shouldScan(this)) { if (pdfScanner.shouldScan()) {
logDebug("🔄 需要扫描PDF文件 (首次启动或超过24小时)") logDebug("🔄 需要扫描PDF文件 (首次启动或超过24小时)")
if (StoragePermissionHelper.hasBasicStoragePermission(this)) { if (StoragePermissionHelper.hasBasicStoragePermission(this)) {
scanAndLoadPdfFiles() lifecycleScope.launch {
pdfScanner.scanAndLoadPdfFiles()
}
} else { } else {
logDebug("❌ 权限不足,跳过扫描") logDebug("❌ 权限不足,跳过扫描")
} }
} else { } else {
val lastScan = ScanManager.getLastScanTime(this) val hoursAgo = pdfScanner.getHoursSinceLastScan()
val hoursAgo = TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis() - lastScan)
logDebug("⏭️ 跳过扫描,上次扫描在${hoursAgo}小时前") logDebug("⏭️ 跳过扫描,上次扫描在${hoursAgo}小时前")
} }
} }
@ -153,108 +150,6 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
} }
} }
fun scanAndLoadPdfFiles(callback: (Boolean) -> Unit = {}) {
if (!StoragePermissionHelper.hasBasicStoragePermission(this)) {
logDebug("权限不足")
callback.invoke(false)
return
}
lifecycleScope.launch {
withContext(Dispatchers.IO) {
try {
logDebug("🔍 开始扫描PDF文件...")
// 扫描应用私有目录(无需权限)
val privateFiles = FileUtils.scanPdfFiles(this@MainActivity)
logDebug("📁 应用私有目录找到: ${privateFiles.size} 个PDF文件")
privateFiles.forEach { file ->
logDebug(" 📄 ${file.name} (${FileUtils.formatFileSize(file.length())})")
}
// 扫描MediaStore需要权限
val mediaStoreFiles = FileUtils.scanPdfFilesFromMediaStore(this@MainActivity)
logDebug("📱 MediaStore找到: ${mediaStoreFiles.size} 个PDF文件")
mediaStoreFiles.forEach { file ->
logDebug(" 📱 ${file.name} (${FileUtils.formatFileSize(file.length())})")
}
// 合并并去重
val allFiles = (privateFiles + mediaStoreFiles).distinctBy { it.absolutePath }
logDebug("📊 总计扫描到: ${allFiles.size} 个PDF文件")
// 处理每个PDF文件
allFiles.forEachIndexed { index, file ->
logDebug("🔄 处理文件 ${index + 1}/${allFiles.size}: ${file.name}")
if (FileUtils.isPdfFile(file)) {
val fileHash = FileUtils.calculateFileHash(file.absolutePath)
logDebug(" 🔑 文件哈希: $fileHash")
if (fileHash != null) {
val existingDoc = pdfRepository.getDocumentByHash(fileHash)
if (existingDoc == null) {
logDebug(" 🆕 发现新PDF文件: ${file.name}")
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,
metadataTitle = metadata?.title,
metadataAuthor = metadata?.author,
metadataSubject = metadata?.subject,
metadataKeywords = metadata?.keywords,
metadataCreationDate = metadata?.creationDate?.time,
metadataModificationDate = metadata?.modificationDate?.time
)
pdfRepository.insertOrUpdateDocument(document)
logDebug(" ✅ 已保存到数据库: ${file.name}")
} else {
logDebug(" 📋 文件已存在: ${file.name}")
if (existingDoc.filePath != file.absolutePath) {
logDebug(" 🔄 更新文件路径: ${existingDoc.filePath} -> ${file.absolutePath}")
val updatedDoc = existingDoc.copy(
filePath = file.absolutePath,
lastModified = file.lastModified()
)
pdfRepository.insertOrUpdateDocument(updatedDoc)
}
}
}
}
}
// 打印数据库中的总记录数
pdfRepository.getAllDocuments().collect { docs ->
logDebug("📊 数据库中共有: ${docs.size} 个PDF记录")
docs.forEach { doc ->
logDebug(
" 📖 ${doc.fileName} - ${doc.pageCount}页 - ${
FileUtils.formatFileSize(
doc.fileSize
)
} - ${doc.thumbnailPath}"
)
}
// 标记扫描完成
ScanManager.markScanComplete(this@MainActivity)
val lastScanTime = ScanManager.getLastScanTime(this@MainActivity)
logDebug("✅ 扫描完成,记录时间: ${java.util.Date(lastScanTime)}")
callback.invoke(true)
}
} catch (e: Exception) {
logError("❌ 扫描出错: ${e.message}", e)
callback.invoke(false)
}
}
}
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)

View File

@ -15,7 +15,7 @@ import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.room.repository.PdfRepository import com.all.pdfreader.pro.app.room.repository.PdfRepository
import com.all.pdfreader.pro.app.ui.act.MainActivity import com.all.pdfreader.pro.app.ui.act.MainActivity
import com.all.pdfreader.pro.app.ui.adapter.PdfAdapter import com.all.pdfreader.pro.app.ui.adapter.PdfAdapter
import kotlinx.coroutines.flow.firstOrNull import com.all.pdfreader.pro.app.util.PdfScanner
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class HomeFrag : BaseFrag(), MainActivity.SortableFragment { class HomeFrag : BaseFrag(), MainActivity.SortableFragment {
@ -48,7 +48,8 @@ class HomeFrag : BaseFrag(), MainActivity.SortableFragment {
// 下拉刷新示例 // 下拉刷新示例
binding.swipeRefreshLayout.setOnRefreshListener { binding.swipeRefreshLayout.setOnRefreshListener {
lifecycleScope.launch { lifecycleScope.launch {
(activity as? MainActivity)?.scanAndLoadPdfFiles { b -> val pdfScanner = PdfScanner(requireContext(), getRepository())
pdfScanner.scanAndLoadPdfFiles { b ->
binding.swipeRefreshLayout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
} }
} }

View File

@ -0,0 +1,22 @@
package com.all.pdfreader.pro.app.util
import android.util.Log
object LogUtil {
fun logDebug(tag: String, message: String) {
Log.d(tag, message)
}
fun logError(tag: String, message: String, throwable: Throwable? = null) {
Log.e(tag, message, throwable)
}
fun logInfo(tag: String, message: String) {
Log.i(tag, message)
}
fun logWarning(tag: String, message: String) {
Log.w(tag, message)
}
}

View File

@ -0,0 +1,127 @@
package com.all.pdfreader.pro.app.util
import android.content.Context
import android.util.Log
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.room.repository.PdfRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.concurrent.TimeUnit
class PdfScanner(
private val context: Context,
private val pdfRepository: PdfRepository
) {
suspend fun scanAndLoadPdfFiles(callback: (Boolean) -> Unit = {}) {
if (!StoragePermissionHelper.hasBasicStoragePermission(context)) {
LogUtil.logDebug("ocean", "PdfScanner: 权限不足")
callback.invoke(false)
return
}
withContext(Dispatchers.IO) {
try {
LogUtil.logDebug("ocean", "PdfScanner: 🔍 开始扫描PDF文件...")
// 扫描应用私有目录(无需权限)
val privateFiles = FileUtils.scanPdfFiles(context)
LogUtil.logDebug("ocean", "PdfScanner: 📁 应用私有目录找到: ${privateFiles.size} 个PDF文件")
privateFiles.forEach { file ->
LogUtil.logDebug("ocean", "PdfScanner: 📄 ${file.name} (${FileUtils.formatFileSize(file.length())})")
}
// 扫描MediaStore需要权限
val mediaStoreFiles = FileUtils.scanPdfFilesFromMediaStore(context)
LogUtil.logDebug("ocean", "PdfScanner: 📱 MediaStore找到: ${mediaStoreFiles.size} 个PDF文件")
mediaStoreFiles.forEach { file ->
LogUtil.logDebug("ocean", "PdfScanner: 📱 ${file.name} (${FileUtils.formatFileSize(file.length())})")
}
// 合并并去重
val allFiles = (privateFiles + mediaStoreFiles).distinctBy { it.absolutePath }
LogUtil.logDebug("ocean", "PdfScanner: 📊 总计扫描到: ${allFiles.size} 个PDF文件")
// 处理每个PDF文件
allFiles.forEachIndexed { index, file ->
LogUtil.logDebug("ocean", "PdfScanner: 🔄 处理文件 ${index + 1}/${allFiles.size}: ${file.name}")
if (FileUtils.isPdfFile(file)) {
val fileHash = FileUtils.calculateFileHash(file.absolutePath)
LogUtil.logDebug("ocean", "PdfScanner: 🔑 文件哈希: $fileHash")
if (fileHash != null) {
val existingDoc = pdfRepository.getDocumentByHash(fileHash)
if (existingDoc == null) {
LogUtil.logDebug("ocean", "PdfScanner: 🆕 发现新PDF文件: ${file.name}")
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,
metadataTitle = metadata?.title,
metadataAuthor = metadata?.author,
metadataSubject = metadata?.subject,
metadataKeywords = metadata?.keywords,
metadataCreationDate = metadata?.creationDate?.time,
metadataModificationDate = metadata?.modificationDate?.time
)
pdfRepository.insertOrUpdateDocument(document)
LogUtil.logDebug("ocean", "PdfScanner: ✅ 已保存到数据库: ${file.name}")
} else {
LogUtil.logDebug("ocean", "PdfScanner: 📋 文件已存在: ${file.name}")
if (existingDoc.filePath != file.absolutePath) {
LogUtil.logDebug("ocean", "PdfScanner: 🔄 更新文件路径: ${existingDoc.filePath} -> ${file.absolutePath}")
val updatedDoc = existingDoc.copy(
filePath = file.absolutePath,
lastModified = file.lastModified()
)
pdfRepository.insertOrUpdateDocument(updatedDoc)
}
}
}
}
}
// 打印数据库中的总记录数
pdfRepository.getAllDocuments().collect { docs ->
LogUtil.logDebug("ocean", "PdfScanner: 📊 数据库中共有: ${docs.size} 个PDF记录")
docs.forEach { doc ->
LogUtil.logDebug(
"ocean",
"PdfScanner: 📖 ${doc.fileName} - ${doc.pageCount}页 - ${FileUtils.formatFileSize(doc.fileSize)} - ${doc.thumbnailPath}"
)
}
// 标记扫描完成
ScanManager.markScanComplete(context)
val lastScanTime = ScanManager.getLastScanTime(context)
LogUtil.logDebug("ocean", "PdfScanner: ✅ 扫描完成,记录时间: ${java.util.Date(lastScanTime)}")
callback.invoke(true)
}
} catch (e: Exception) {
Log.e("ocean", "PdfScanner: ❌ 扫描出错: ${e.message}", e)
callback.invoke(false)
}
}
}
fun shouldScan(): Boolean {
return ScanManager.shouldScan(context)
}
fun getLastScanTime(): Long {
return ScanManager.getLastScanTime(context)
}
fun getHoursSinceLastScan(): Long {
val lastScan = getLastScanTime()
return TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis() - lastScan)
}
}

View File

@ -4,8 +4,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingStart="12dp" android:paddingStart="16dp"
android:paddingTop="12dp" android:paddingTop="16dp"
tools:ignore="RtlSymmetry"> tools:ignore="RtlSymmetry">
<LinearLayout <LinearLayout
@ -84,7 +84,8 @@
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_marginTop="8dp" android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:background="@color/line_color" /> android:background="@color/line_color" />
</LinearLayout> </LinearLayout>