mirror of
https://github.com/ankidroid/Anki-Android.git
synced 2024-09-20 03:52:15 +02:00
Relative File Paths
For use in conflict management in scoped storage. Once we have a relative path, we can move it to "/conflict/" + path and properly handle the creation of directories.
This commit is contained in:
parent
38d00b0e11
commit
88e48e423e
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2022 David Allison <davidallisongithub@gmail.com>
|
||||
* Copyright (c) 2022 Arthur Milchior <arthur@milchior.fr>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.ichi2.anki.model
|
||||
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* A relative path, with the final component representing the filename.
|
||||
* During a recursive copy of a folder `source` to `destination`, this relative file path `relative`
|
||||
* can be used both on top of source and destination folder to get the path of `source/relative`
|
||||
* and `destination/relative`.
|
||||
* It can also be used to move `source/relative` to `source/conflict/relative` in case of conflict.
|
||||
*/
|
||||
class RelativeFilePath private constructor(
|
||||
/** Relative path, as a sequence of directory, excluding the file name. */
|
||||
val path: List<String>,
|
||||
val fileName: String
|
||||
) {
|
||||
/**
|
||||
* Combination of [baseDir] and this relative Path.
|
||||
*/
|
||||
fun toFile(baseDir: Directory): File {
|
||||
var directory = baseDir.directory
|
||||
for (dirName in path) {
|
||||
directory = File(directory, dirName)
|
||||
}
|
||||
return File(directory, fileName)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Return the relative path from Folder [baseDir] to file [file]. If [file]
|
||||
* is contained in [baseDir], return [null].
|
||||
* Similar to [Path.relativize], but available in all APIs.
|
||||
*/
|
||||
fun fromPaths(baseDir: Directory, file: DiskFile): RelativeFilePath? =
|
||||
fromPaths(baseDir.directory, file.file)
|
||||
fun fromPaths(baseDir: File, file: File): RelativeFilePath? =
|
||||
fromCanonicalFiles(baseDir.canonicalFile, file.canonicalFile)
|
||||
|
||||
/**
|
||||
* Return the relative path from Folder [baseDir] to file [file]. If [file]
|
||||
* is contained in [baseDir], return [null].
|
||||
* Assumes that [file] is actually a file and [baseDir] a directory, hence distinct.
|
||||
* Similar to [Path.relativize], but available in all APIs.
|
||||
* @param baseDir A directory.
|
||||
* @param file some file, assumed to be contained in baseDir.
|
||||
*/
|
||||
private fun fromCanonicalFiles(baseDir: File, file: File): RelativeFilePath? {
|
||||
val name = file.name
|
||||
val directoryPath = mutableListOf<String>()
|
||||
var mutablePath = file.parentFile
|
||||
while (mutablePath != baseDir) {
|
||||
if (mutablePath == null) {
|
||||
// File was not inside the directory
|
||||
return null
|
||||
}
|
||||
directoryPath.add(mutablePath.name)
|
||||
mutablePath = mutablePath.parentFile
|
||||
}
|
||||
// attempt to create a relative file path
|
||||
return RelativeFilePath(directoryPath.reversed(), name)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2022 David Allison <davidallisongithub@gmail.com>
|
||||
* Copyright (c) 2022 Arthur Milchior <arthur@milchior.fr>
|
||||
*
|
||||
* 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
|
||||
@ -19,11 +20,24 @@ package com.ichi2.anki.servicelayer
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import com.ichi2.anki.AnkiDroidApp
|
||||
import com.ichi2.anki.model.Directory
|
||||
import com.ichi2.anki.model.DiskFile
|
||||
import com.ichi2.anki.model.RelativeFilePath
|
||||
import com.ichi2.anki.servicelayer.scopedstorage.MigrateUserData
|
||||
import java.io.File
|
||||
|
||||
/** A path to the AnkiDroid directory, named "AnkiDroid" by default */
|
||||
typealias AnkiDroidDirectory = String
|
||||
typealias AnkiDroidDirectory = Directory
|
||||
|
||||
/**
|
||||
* Returns the relative file path from a given [AnkiDroidDirectory]
|
||||
* @return null if the file was not inside the directory, or referred to the root directory
|
||||
*/
|
||||
fun AnkiDroidDirectory.getRelativeFilePath(file: DiskFile): RelativeFilePath? =
|
||||
RelativeFilePath.fromPaths(
|
||||
baseDir = this,
|
||||
file = file
|
||||
)
|
||||
|
||||
object ScopedStorageService {
|
||||
/**
|
||||
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2022 Arthur Milchior <arthur@milchior.fr>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.ichi2.anki.model
|
||||
|
||||
import com.ichi2.testutils.addTempFile
|
||||
import com.ichi2.testutils.createTransientDirectory
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.*
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
class RelativeFilePathTest {
|
||||
@Test
|
||||
fun test_distinct_base() {
|
||||
val file = DiskFile.createInstance(createTransientDirectory().addTempFile("fileName"))!!
|
||||
val dir = Directory.createInstance(createTransientDirectory())!!
|
||||
assertThat("If file is not in dir, fromPaths should return null.", RelativeFilePath.fromPaths(dir, file), nullValue())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_recursive_file() {
|
||||
val base = createTransientDirectory()
|
||||
val subDir = base.createTransientDirectory("sub")
|
||||
val file = subDir.addTempFile("fileName")
|
||||
val relative = RelativeFilePath.fromPaths(base, file)!!
|
||||
assertThat(relative.fileName, equalTo("fileName"))
|
||||
assertThat(relative.path, hasSize(1))
|
||||
assertThat(relative.path[0], equalTo("sub"))
|
||||
checkBasePlusRelativeEqualsExpected(base, relative, file)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_move_file() {
|
||||
val source = createTransientDirectory()
|
||||
val destination = createTransientDirectory()
|
||||
val subDir = source.createTransientDirectory("sub")
|
||||
val file = subDir.addTempFile("fileName")
|
||||
val relative = RelativeFilePath.fromPaths(source, file)!!
|
||||
assertThat(relative.fileName, equalTo("fileName"))
|
||||
assertThat(relative.path, hasSize(1))
|
||||
assertThat(relative.path[0], equalTo("sub"))
|
||||
checkBasePlusRelativeEqualsExpected(destination, relative, File(File(destination, "sub"), "fileName"))
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun checkBasePlusRelativeEqualsExpected(baseDir: File, relative: RelativeFilePath, expected: File) {
|
||||
assertThat(relative.toFile(Directory.createInstance(baseDir)!!), equalTo(expected))
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user