Refactoring: PdfFileManager
This commit is contained in:
@@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025 Pierre-Yves Nicolas
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU General Public License as published by the Free
|
|
||||||
* Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
* You should have received a copy of the GNU General Public License along with
|
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.mydomain.myscan
|
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
fun getAvailableFilename(desiredFile: File): File {
|
|
||||||
var file = desiredFile
|
|
||||||
val dir = desiredFile.parentFile
|
|
||||||
val desiredName = desiredFile.name
|
|
||||||
val nameWithoutExtension = desiredName.removeSuffix(".pdf")
|
|
||||||
var counter = 1
|
|
||||||
while (file.exists()) {
|
|
||||||
file = File(dir, "${nameWithoutExtension}_$counter.pdf")
|
|
||||||
counter++
|
|
||||||
}
|
|
||||||
return file
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cleanUpOldFiles(dir: File, thresholdInMillis: Int) {
|
|
||||||
val now = System.currentTimeMillis()
|
|
||||||
dir.listFiles { file -> now - file.lastModified() > thresholdInMillis }
|
|
||||||
?.forEach { file -> file.delete() }
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -53,10 +53,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
initLibraries()
|
initLibraries()
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
|
||||||
cleanUpOldFiles(File(cacheDir, "pdfs"), 1000 * 3600)
|
|
||||||
}
|
|
||||||
val viewModel: MainViewModel by viewModels { MainViewModel.getFactory(this) }
|
val viewModel: MainViewModel by viewModels { MainViewModel.getFactory(this) }
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
viewModel.cleanUpOldPdfs(1000 * 3600)
|
||||||
|
}
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
val currentScreen by viewModel.currentScreen.collectAsStateWithLifecycle()
|
val currentScreen by viewModel.currentScreen.collectAsStateWithLifecycle()
|
||||||
@@ -114,7 +114,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
private fun sharePdf(generatedPdf: GeneratedPdf?) {
|
private fun sharePdf(generatedPdf: GeneratedPdf?) {
|
||||||
if (generatedPdf == null)
|
if (generatedPdf == null)
|
||||||
return
|
return
|
||||||
val file = generatedPdf.uri.toFile()
|
val file = generatedPdf.file
|
||||||
val authority = "${applicationContext.packageName}.fileprovider"
|
val authority = "${applicationContext.packageName}.fileprovider"
|
||||||
val fileUri = FileProvider.getUriForFile(this, authority, file)
|
val fileUri = FileProvider.getUriForFile(this, authority, file)
|
||||||
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
||||||
@@ -139,16 +139,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
val context = this
|
val context = this
|
||||||
appScope.launch {
|
appScope.launch {
|
||||||
try {
|
try {
|
||||||
val downloadsDir =
|
val targetFile = viewModel.saveFile(generatedPdf.file)
|
||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
|
||||||
if (!downloadsDir.exists()) {
|
|
||||||
downloadsDir.mkdirs()
|
|
||||||
}
|
|
||||||
val generatedFile = generatedPdf.uri.toFile()
|
|
||||||
val desiredFile = File(downloadsDir, generatedFile.name)
|
|
||||||
val targetFile = getAvailableFilename(desiredFile)
|
|
||||||
generatedFile.copyTo(targetFile)
|
|
||||||
viewModel.markFileSaved(targetFile.toUri())
|
|
||||||
|
|
||||||
suspendCancellableCoroutine { continuation ->
|
suspendCancellableCoroutine { continuation ->
|
||||||
MediaScannerConnection.scanFile(
|
MediaScannerConnection.scanFile(
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import android.content.Context
|
|||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Environment
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.camera.core.ImageProxy
|
import androidx.camera.core.ImageProxy
|
||||||
import androidx.core.net.toFile
|
import androidx.core.net.toFile
|
||||||
@@ -41,12 +42,11 @@ import kotlinx.coroutines.withContext
|
|||||||
import org.mydomain.myscan.ui.PdfGenerationUiState
|
import org.mydomain.myscan.ui.PdfGenerationUiState
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
|
||||||
|
|
||||||
class MainViewModel(
|
class MainViewModel(
|
||||||
private val imageSegmentationService: ImageSegmentationService,
|
private val imageSegmentationService: ImageSegmentationService,
|
||||||
private val imageRepository: ImageRepository,
|
private val imageRepository: ImageRepository,
|
||||||
private val pdfDir: File,
|
private val pdfFileManager: PdfFileManager,
|
||||||
): ViewModel() {
|
): ViewModel() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -56,7 +56,10 @@ class MainViewModel(
|
|||||||
return MainViewModel(
|
return MainViewModel(
|
||||||
ImageSegmentationService(context),
|
ImageSegmentationService(context),
|
||||||
ImageRepository(context.filesDir),
|
ImageRepository(context.filesDir),
|
||||||
File(context.cacheDir, "pdfs"),
|
PdfFileManager(
|
||||||
|
File(context.cacheDir, "pdfs"),
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
AndroidPdfWriter()),
|
||||||
) as T
|
) as T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,19 +210,12 @@ class MainViewModel(
|
|||||||
|
|
||||||
private suspend fun generatePdf(): GeneratedPdf = withContext(Dispatchers.IO) {
|
private suspend fun generatePdf(): GeneratedPdf = withContext(Dispatchers.IO) {
|
||||||
val imageIds = imageRepository.imageIds()
|
val imageIds = imageRepository.imageIds()
|
||||||
pdfDir.mkdirs()
|
|
||||||
val file = File(pdfDir, "${System.currentTimeMillis()}.pdf")
|
|
||||||
val jpegs = imageIds.asSequence()
|
val jpegs = imageIds.asSequence()
|
||||||
.map { id -> imageRepository.getContent(id) }
|
.map { id -> imageRepository.getContent(id) }
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
writePdfFromJpegs(jpegs, FileOutputStream(file))
|
return@withContext pdfFileManager.generatePdf(jpegs)
|
||||||
val sizeBytes = file.length()
|
|
||||||
val uri = file.toUri()
|
|
||||||
return@withContext GeneratedPdf(uri, sizeBytes, imageIds.size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _generatedPdf = MutableStateFlow<GeneratedPdf?>(null)
|
|
||||||
|
|
||||||
private val _pdfUiState = MutableStateFlow(PdfGenerationUiState())
|
private val _pdfUiState = MutableStateFlow(PdfGenerationUiState())
|
||||||
val pdfUiState: StateFlow<PdfGenerationUiState> = _pdfUiState.asStateFlow()
|
val pdfUiState: StateFlow<PdfGenerationUiState> = _pdfUiState.asStateFlow()
|
||||||
|
|
||||||
@@ -264,7 +260,7 @@ class MainViewModel(
|
|||||||
|
|
||||||
fun getFinalPdf(): GeneratedPdf? {
|
fun getFinalPdf(): GeneratedPdf? {
|
||||||
val tempPdf = _pdfUiState.value.generatedPdf ?: return null
|
val tempPdf = _pdfUiState.value.generatedPdf ?: return null
|
||||||
val tempFile = tempPdf.uri.toFile()
|
val tempFile = tempPdf.file
|
||||||
val newFile = File(tempFile.parentFile, desiredFilename)
|
val newFile = File(tempFile.parentFile, desiredFilename)
|
||||||
if (tempFile.absolutePath != newFile.absolutePath) {
|
if (tempFile.absolutePath != newFile.absolutePath) {
|
||||||
if (newFile.exists()) newFile.delete()
|
if (newFile.exists()) newFile.delete()
|
||||||
@@ -272,20 +268,30 @@ class MainViewModel(
|
|||||||
if (!success) return null
|
if (!success) return null
|
||||||
_pdfUiState.update {
|
_pdfUiState.update {
|
||||||
it.copy(generatedPdf = GeneratedPdf(
|
it.copy(generatedPdf = GeneratedPdf(
|
||||||
uri = newFile.toUri(), tempPdf.sizeInBytes, tempPdf.pageCount)
|
newFile, tempPdf.sizeInBytes, tempPdf.pageCount)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _pdfUiState.value.generatedPdf
|
return _pdfUiState.value.generatedPdf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun saveFile(pdfFile: File): File {
|
||||||
|
val copiedFile = pdfFileManager.copyToExternalDir(pdfFile)
|
||||||
|
markFileSaved(pdfFile.toUri())
|
||||||
|
return copiedFile
|
||||||
|
}
|
||||||
|
|
||||||
fun markFileSaved(uri: Uri) {
|
fun markFileSaved(uri: Uri) {
|
||||||
_pdfUiState.update { it.copy(savedFileUri = uri) }
|
_pdfUiState.update { it.copy(savedFileUri = uri) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun cleanUpOldPdfs(thresholdInMillis: Int) {
|
||||||
|
pdfFileManager.cleanUpOldFiles(thresholdInMillis)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class GeneratedPdf(
|
data class GeneratedPdf(
|
||||||
val uri: Uri,
|
val file: File,
|
||||||
val sizeInBytes: Long,
|
val sizeInBytes: Long,
|
||||||
val pageCount: Int,
|
val pageCount: Int,
|
||||||
)
|
)
|
||||||
|
|||||||
70
app/src/main/java/org/mydomain/myscan/PdfFileManager.kt
Normal file
70
app/src/main/java/org/mydomain/myscan/PdfFileManager.kt
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Pierre-Yves Nicolas
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
|
* Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.mydomain.myscan
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
fun interface PdfWriter {
|
||||||
|
fun writePdfFromJpegs(jpegs: Sequence<ByteArray>, outputStream: OutputStream): Int
|
||||||
|
}
|
||||||
|
|
||||||
|
class PdfFileManager(
|
||||||
|
private val pdfDir: File,
|
||||||
|
private val externalDir: File,
|
||||||
|
private val pdfWriter: PdfWriter
|
||||||
|
) {
|
||||||
|
fun generatePdf(jpegs: Sequence<ByteArray>): GeneratedPdf {
|
||||||
|
pdfDir.mkdirs()
|
||||||
|
require(pdfDir.exists() && pdfDir.isDirectory) { "Invalid pdfDir: $pdfDir" }
|
||||||
|
val file = File(pdfDir, "${System.currentTimeMillis()}.pdf")
|
||||||
|
val pageCount = FileOutputStream(file).use {
|
||||||
|
pdfWriter.writePdfFromJpegs(jpegs, it)
|
||||||
|
}
|
||||||
|
val sizeBytes = file.length()
|
||||||
|
return GeneratedPdf(file, sizeBytes, pageCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun copyToExternalDir(original: File): File {
|
||||||
|
if (!externalDir.exists()) {
|
||||||
|
externalDir.mkdirs()
|
||||||
|
}
|
||||||
|
require(externalDir.exists() && externalDir.isDirectory) { "Invalid externalDir: $pdfDir" }
|
||||||
|
val desiredFile = File(externalDir, original.name)
|
||||||
|
val targetFile = getAvailableFilename(desiredFile)
|
||||||
|
original.copyTo(targetFile)
|
||||||
|
return targetFile
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAvailableFilename(desiredFile: File): File {
|
||||||
|
var file = desiredFile
|
||||||
|
val dir = desiredFile.parentFile
|
||||||
|
val nameWithoutExtension = desiredFile.nameWithoutExtension
|
||||||
|
val extension = desiredFile.extension
|
||||||
|
var counter = 1
|
||||||
|
while (file.exists()) {
|
||||||
|
file = File(dir, "${nameWithoutExtension}($counter).$extension")
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cleanUpOldFiles(thresholdInMillis: Int) {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
pdfDir.listFiles { file -> now - file.lastModified() > thresholdInMillis }
|
||||||
|
?.forEach { file -> file.delete() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,8 +14,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.mydomain.myscan
|
package org.mydomain.myscan
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import androidx.core.graphics.scale
|
|
||||||
import com.tom_roush.pdfbox.pdmodel.PDDocument
|
import com.tom_roush.pdfbox.pdmodel.PDDocument
|
||||||
import com.tom_roush.pdfbox.pdmodel.PDPage
|
import com.tom_roush.pdfbox.pdmodel.PDPage
|
||||||
import com.tom_roush.pdfbox.pdmodel.PDPageContentStream
|
import com.tom_roush.pdfbox.pdmodel.PDPageContentStream
|
||||||
@@ -23,18 +21,22 @@ import com.tom_roush.pdfbox.pdmodel.PDPageContentStream.AppendMode
|
|||||||
import com.tom_roush.pdfbox.pdmodel.common.PDRectangle
|
import com.tom_roush.pdfbox.pdmodel.common.PDRectangle
|
||||||
import com.tom_roush.pdfbox.pdmodel.graphics.image.JPEGFactory
|
import com.tom_roush.pdfbox.pdmodel.graphics.image.JPEGFactory
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
fun writePdfFromJpegs(jpegs: Sequence<ByteArray>, outputStream: OutputStream) {
|
class AndroidPdfWriter : PdfWriter {
|
||||||
PDDocument().use { document ->
|
override fun writePdfFromJpegs(jpegs: Sequence<ByteArray>, outputStream: OutputStream): Int {
|
||||||
for (jpegBytes in jpegs) {
|
val doc = PDDocument()
|
||||||
val image = JPEGFactory.createFromByteArray(document, jpegBytes)
|
doc.use { document ->
|
||||||
val page = PDPage(PDRectangle(image.width.toFloat(), image.height.toFloat()))
|
for (jpegBytes in jpegs) {
|
||||||
document.addPage(page)
|
val image = JPEGFactory.createFromByteArray(document, jpegBytes)
|
||||||
val contentStream = PDPageContentStream(document, page, AppendMode.OVERWRITE, false)
|
val page = PDPage(PDRectangle(image.width.toFloat(), image.height.toFloat()))
|
||||||
contentStream.drawImage(image, 0f, 0f)
|
document.addPage(page)
|
||||||
contentStream.close()
|
val contentStream = PDPageContentStream(document, page, AppendMode.OVERWRITE, false)
|
||||||
|
contentStream.drawImage(image, 0f, 0f)
|
||||||
|
contentStream.close()
|
||||||
|
}
|
||||||
|
// TODO So the whole document is in memory before this line...
|
||||||
|
document.save(outputStream)
|
||||||
}
|
}
|
||||||
document.save(outputStream)
|
return doc.numberOfPages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import org.mydomain.myscan.GeneratedPdf
|
|||||||
import org.mydomain.myscan.PdfGenerationActions
|
import org.mydomain.myscan.PdfGenerationActions
|
||||||
import org.mydomain.myscan.ui.PdfGenerationUiState
|
import org.mydomain.myscan.ui.PdfGenerationUiState
|
||||||
import org.mydomain.myscan.ui.theme.MyScanTheme
|
import org.mydomain.myscan.ui.theme.MyScanTheme
|
||||||
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -267,7 +268,7 @@ fun PreviewPdfGenerationDialogDuringGeneration() {
|
|||||||
fun PreviewPdfGenerationDialogAfterGeneration() {
|
fun PreviewPdfGenerationDialogAfterGeneration() {
|
||||||
PreviewToCustomize(
|
PreviewToCustomize(
|
||||||
uiState = PdfGenerationUiState(
|
uiState = PdfGenerationUiState(
|
||||||
generatedPdf = GeneratedPdf("file://fake.pdf".toUri(), 442897L, 1)
|
generatedPdf = GeneratedPdf(File("fake.pdf"), 442897L, 1)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -275,10 +276,11 @@ fun PreviewPdfGenerationDialogAfterGeneration() {
|
|||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
fun PreviewPdfGenerationDialogAfterSave() {
|
fun PreviewPdfGenerationDialogAfterSave() {
|
||||||
|
val file = File("fake.pdf")
|
||||||
PreviewToCustomize(
|
PreviewToCustomize(
|
||||||
uiState = PdfGenerationUiState(
|
uiState = PdfGenerationUiState(
|
||||||
generatedPdf = GeneratedPdf("file://fake.pdf".toUri(), 442897L, 3),
|
generatedPdf = GeneratedPdf(file, 442897L, 3),
|
||||||
savedFileUri = "file:///fake".toUri()
|
savedFileUri = file.toUri()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025 Pierre-Yves Nicolas
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU General Public License as published by the Free
|
|
||||||
* Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
* You should have received a copy of the GNU General Public License along with
|
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.mydomain.myscan
|
|
||||||
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
|
||||||
import org.junit.Test
|
|
||||||
import java.io.File
|
|
||||||
import kotlin.io.path.createTempDirectory
|
|
||||||
|
|
||||||
class FileUtilsTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getAvailableName() {
|
|
||||||
val dir = createTempDirectory().toFile()
|
|
||||||
val f = File(dir, "f.pdf")
|
|
||||||
val f1 = File(dir, "f_1.pdf")
|
|
||||||
val f2 = File(dir, "f_2.pdf")
|
|
||||||
|
|
||||||
assertThat(f).doesNotExist()
|
|
||||||
assertThat(f1).doesNotExist()
|
|
||||||
assertThat(getAvailableFilename(f)).isEqualTo(f)
|
|
||||||
|
|
||||||
f.apply { writeText("dummy") }
|
|
||||||
assertThat(f).exists()
|
|
||||||
assertThat(getAvailableFilename(f)).isEqualTo(f1)
|
|
||||||
|
|
||||||
f1.apply { writeText("dummy") }
|
|
||||||
assertThat(f1).exists()
|
|
||||||
assertThat(getAvailableFilename(f)).isEqualTo(f2)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun cleanUpOldFiles() {
|
|
||||||
val dir = createTempDirectory().toFile()
|
|
||||||
val subDir = File(dir,"subDir")
|
|
||||||
cleanUpOldFiles(subDir, 10)
|
|
||||||
assertThat(subDir).doesNotExist()
|
|
||||||
|
|
||||||
subDir.mkdirs()
|
|
||||||
assertThat(subDir).exists()
|
|
||||||
val file1 = File(subDir, "file1")
|
|
||||||
file1.createNewFile()
|
|
||||||
val file2 = File(subDir, "file2")
|
|
||||||
file2.createNewFile()
|
|
||||||
|
|
||||||
val now = System.currentTimeMillis()
|
|
||||||
file1.setLastModified(now - 10_000)
|
|
||||||
file2.setLastModified(now - 11_000)
|
|
||||||
cleanUpOldFiles(subDir, 10_500)
|
|
||||||
assertThat(file1).exists()
|
|
||||||
assertThat(file2).doesNotExist()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
87
app/src/test/java/org/mydomain/myscan/PdfFileManagerTest.kt
Normal file
87
app/src/test/java/org/mydomain/myscan/PdfFileManagerTest.kt
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Pierre-Yves Nicolas
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
|
* Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.mydomain.myscan
|
||||||
|
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.File
|
||||||
|
import java.io.OutputStream
|
||||||
|
import kotlin.io.path.createTempDirectory
|
||||||
|
|
||||||
|
class PdfFileManagerTest {
|
||||||
|
|
||||||
|
val pdfDir: File = createTempDirectory().toFile()
|
||||||
|
val externalDir: File = createTempDirectory().toFile()
|
||||||
|
val dummyPdfWriter = PdfWriter { _,_ -> 42 }
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun copyToExternalDir() {
|
||||||
|
val original = File(pdfDir, "f.pdf")
|
||||||
|
original.writeText("original content")
|
||||||
|
val f = File(externalDir, "f.pdf")
|
||||||
|
assertThat(f).doesNotExist()
|
||||||
|
|
||||||
|
val manager = PdfFileManager(pdfDir, externalDir, dummyPdfWriter)
|
||||||
|
assertThat(manager.copyToExternalDir(original))
|
||||||
|
.isEqualTo(f)
|
||||||
|
.hasContent("original content")
|
||||||
|
|
||||||
|
val f1 = File(externalDir, "f(1).pdf")
|
||||||
|
val f2 = File(externalDir, "f(2).pdf")
|
||||||
|
assertThat(f1).doesNotExist()
|
||||||
|
assertThat(manager.copyToExternalDir(original)).isEqualTo(f1)
|
||||||
|
assertThat(manager.copyToExternalDir(original)).isEqualTo(f2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun cleanUpOldFiles() {
|
||||||
|
val subDir = File(pdfDir,"subDir")
|
||||||
|
val manager = PdfFileManager(subDir, externalDir, dummyPdfWriter)
|
||||||
|
manager.cleanUpOldFiles(10)
|
||||||
|
assertThat(subDir).doesNotExist()
|
||||||
|
|
||||||
|
subDir.mkdirs()
|
||||||
|
assertThat(subDir).exists()
|
||||||
|
val file1 = File(subDir, "file1")
|
||||||
|
file1.createNewFile()
|
||||||
|
val file2 = File(subDir, "file2")
|
||||||
|
file2.createNewFile()
|
||||||
|
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
file1.setLastModified(now - 10_000)
|
||||||
|
file2.setLastModified(now - 11_000)
|
||||||
|
manager.cleanUpOldFiles(10_500)
|
||||||
|
assertThat(file1).exists()
|
||||||
|
assertThat(file2).doesNotExist()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun generatePdf() {
|
||||||
|
val fakePdfWriter = object : PdfWriter {
|
||||||
|
override fun writePdfFromJpegs(jpegs: Sequence<ByteArray>, outputStream: OutputStream): Int {
|
||||||
|
val list = jpegs.toList()
|
||||||
|
list.forEach { bytes -> outputStream.write(bytes) }
|
||||||
|
return list.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val manager = PdfFileManager(pdfDir, externalDir, fakePdfWriter)
|
||||||
|
val jpegs = listOf(byteArrayOf(0x01, 0x02), byteArrayOf(0x11)).asSequence()
|
||||||
|
val pdf = manager.generatePdf(jpegs)
|
||||||
|
assertThat(pdf.pageCount).isEqualTo(2)
|
||||||
|
assertThat(pdf.sizeInBytes).isEqualTo(3)
|
||||||
|
assertThat(pdf.file.readBytes()).isEqualTo(byteArrayOf(0x01, 0x02, 0x11))
|
||||||
|
assertThat(pdf.file.name).endsWith(".pdf")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user