From 5ff4d21abcb78a8a7230271600ed9a011454902c Mon Sep 17 00:00:00 2001 From: tzugen Date: Sun, 12 Sep 2021 12:01:24 +0200 Subject: [PATCH] Merge FileUtil functions into a single class. --- detekt-config.yml | 2 +- .../moire/ultrasonic/service/DownloadFile.kt | 12 +- .../ultrasonic/service/RESTMusicService.kt | 3 +- .../org/moire/ultrasonic/util/FileUtil.kt | 206 ++++++++++-------- .../org/moire/ultrasonic/util/FileUtilKt.kt | 47 ---- .../kotlin/org/moire/ultrasonic/util/Util.kt | 3 - 6 files changed, 124 insertions(+), 149 deletions(-) rename ultrasonic/src/main/{java => kotlin}/org/moire/ultrasonic/util/FileUtil.kt (69%) delete mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/FileUtilKt.kt diff --git a/detekt-config.yml b/detekt-config.yml index eb76d7f7..c9117d88 100644 --- a/detekt-config.yml +++ b/detekt-config.yml @@ -70,7 +70,7 @@ style: excludeImportStatements: false MagicNumber: # 100 common in percentage, 1000 in milliseconds - ignoreNumbers: ['-1', '0', '1', '2', '100', '1000'] + ignoreNumbers: ['-1', '0', '1', '2', '10', '100', '256', '512', '1000', '1024'] ignoreEnums: true ignorePropertyDeclaration: true UnnecessaryAbstractClass: diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt index de341825..c386ae2c 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt @@ -10,6 +10,12 @@ package org.moire.ultrasonic.service import android.net.wifi.WifiManager.WifiLock import android.text.TextUtils import androidx.lifecycle.MutableLiveData +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream +import java.io.RandomAccessFile import org.koin.core.component.KoinComponent import org.koin.core.component.inject import org.moire.ultrasonic.domain.MusicDirectory @@ -20,12 +26,6 @@ import org.moire.ultrasonic.util.CancellableTask import org.moire.ultrasonic.util.FileUtil import org.moire.ultrasonic.util.Util import timber.log.Timber -import java.io.File -import java.io.FileOutputStream -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream -import java.io.RandomAccessFile /** * This class represents a singe Song or Video that can be downloaded. diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt index 2e850f8a..4981992b 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt @@ -42,7 +42,6 @@ import org.moire.ultrasonic.domain.toDomainEntityList import org.moire.ultrasonic.domain.toIndexList import org.moire.ultrasonic.domain.toMusicDirectoryDomainEntity import org.moire.ultrasonic.util.FileUtil -import org.moire.ultrasonic.util.FileUtilKt import org.moire.ultrasonic.util.Util import timber.log.Timber @@ -242,7 +241,7 @@ open class RESTMusicService( activeServerProvider.getActiveServer().name, name ) - FileUtilKt.savePlaylist(playlistFile, playlist, name) + FileUtil.savePlaylist(playlistFile, playlist, name) } @Throws(Exception::class) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/FileUtil.kt similarity index 69% rename from ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.kt rename to ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/FileUtil.kt index 62cbcaa0..57f7d7b5 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/FileUtil.kt @@ -1,61 +1,47 @@ /* - This file is part of Subsonic. - - Subsonic 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. - - Subsonic 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 Subsonic. If not, see . - - Copyright 2009 (C) Sindre Mehus + * FileUtil.kt + * Copyright (C) 2009-2021 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. */ + package org.moire.ultrasonic.util import android.content.Context import android.os.Build import android.os.Environment import android.text.TextUtils -import org.koin.java.KoinJavaComponent.inject -import org.moire.ultrasonic.app.UApp.Companion.applicationContext -import org.moire.ultrasonic.domain.MusicDirectory -import org.moire.ultrasonic.util.Util.close -import org.moire.ultrasonic.util.Util.getPreferences -import org.moire.ultrasonic.util.Util.md5Hex -import timber.log.Timber +import java.io.BufferedWriter import java.io.File import java.io.FileInputStream import java.io.FileOutputStream +import java.io.FileWriter +import java.io.IOException import java.io.ObjectInputStream import java.io.ObjectOutputStream import java.io.Serializable -import java.util.Arrays import java.util.Locale import java.util.SortedSet import java.util.TreeSet import java.util.regex.Pattern +import org.koin.java.KoinJavaComponent +import org.moire.ultrasonic.app.UApp +import org.moire.ultrasonic.domain.MusicDirectory +import timber.log.Timber -/** - * @author Sindre Mehus - */ object FileUtil { + private val FILE_SYSTEM_UNSAFE = arrayOf("/", "\\", "..", ":", "\"", "?", "*", "<", ">", "|") private val FILE_SYSTEM_UNSAFE_DIR = arrayOf("\\", "..", ":", "\"", "?", "*", "<", ">", "|") private val MUSIC_FILE_EXTENSIONS = - Arrays.asList("mp3", "ogg", "aac", "flac", "m4a", "wav", "wma", "opus") + listOf("mp3", "ogg", "aac", "flac", "m4a", "wav", "wma", "opus") private val VIDEO_FILE_EXTENSIONS = - Arrays.asList("flv", "mp4", "m4v", "wmv", "avi", "mov", "mpg", "mkv") + listOf("flv", "mp4", "m4v", "wmv", "avi", "mov", "mpg", "mkv") private val PLAYLIST_FILE_EXTENSIONS = listOf("m3u") private val TITLE_WITH_TRACK = Pattern.compile("^\\d\\d-.*") const val SUFFIX_LARGE = ".jpeg" const val SUFFIX_SMALL = ".jpeg-small" - private val permissionUtil = inject( + private val permissionUtil = KoinJavaComponent.inject( PermissionUtil::class.java ) @@ -71,8 +57,8 @@ object FileUtil { val fileName = StringBuilder(256) val track = song.track - //check if filename already had track number - if (!TITLE_WITH_TRACK.matcher(song.title).matches()) { + // check if filename already had track number + if (song.title != null && !TITLE_WITH_TRACK.matcher(song.title!!).matches()) { if (track != null) { if (track < 10) { fileName.append('0') @@ -90,13 +76,13 @@ object FileUtil { } @JvmStatic - fun getPlaylistFile(server: String?, name: String): File { + fun getPlaylistFile(server: String?, name: String): File { val playlistDir = getPlaylistDirectory(server) - return File(playlistDir, String.format("%s.m3u", fileSystemSafe(name))) + return File(playlistDir, String.format(Locale.ROOT, "%s.m3u", fileSystemSafe(name))) } @JvmStatic - val playlistDirectory: File + val playlistDirectory: File get() { val playlistDir = File(ultrasonicDirectory, "playlists") ensureDirectoryExistsAndIsReadWritable(playlistDir) @@ -104,7 +90,12 @@ object FileUtil { } fun getPlaylistDirectory(server: String?): File { - val playlistDir = File(playlistDirectory, server) + val playlistDir: File + if (server != null) { + playlistDir = File(playlistDirectory, server) + } else { + playlistDir = playlistDirectory + } ensureDirectoryExistsAndIsReadWritable(playlistDir) return playlistDir } @@ -136,21 +127,21 @@ object FileUtil { * @param large Whether to get the key for the large or the default image * @return String The hash key */ - fun getAlbumArtKey(albumDir: File?, large: Boolean): String? { + private fun getAlbumArtKey(albumDir: File?, large: Boolean): String? { if (albumDir == null) { return null } val suffix = if (large) SUFFIX_LARGE else SUFFIX_SMALL - return String.format(Locale.ROOT, "%s%s", md5Hex(albumDir.path), suffix) + return String.format(Locale.ROOT, "%s%s", Util.md5Hex(albumDir.path), suffix) } fun getAvatarFile(username: String?): File? { - val albumArtDir = albumArtDirectory - if (albumArtDir == null || username == null) { + if (username == null) { return null } - val md5Hex = md5Hex(username) - return File(albumArtDir, String.format("%s%s", md5Hex, SUFFIX_LARGE)) + val albumArtDir = albumArtDirectory + val md5Hex = Util.md5Hex(username) + return File(albumArtDir, String.format(Locale.ROOT, "%s%s", md5Hex, SUFFIX_LARGE)) } /** @@ -161,7 +152,7 @@ object FileUtil { fun getAlbumArtFile(albumDir: File?): File? { val albumArtDir = albumArtDirectory val key = getAlbumArtKey(albumDir, true) - return if (key == null || albumArtDir == null) { + return if (key == null) { null } else File(albumArtDir, key) } @@ -173,7 +164,7 @@ object FileUtil { */ fun getAlbumArtFile(cacheKey: String?): File? { val albumArtDir = albumArtDirectory - return if (albumArtDir == null || cacheKey == null) { + return if (cacheKey == null) { null } else File(albumArtDir, cacheKey) } @@ -195,9 +186,10 @@ object FileUtil { val f = File(fileSystemSafeDir(entry.path)) dir = File( String.format( + Locale.ROOT, "%s/%s", musicDirectory.path, - if (entry.isDirectory) f.path else f.parent + if (entry.isDirectory) f.path else f.parent!! ) ) } else { @@ -206,20 +198,21 @@ object FileUtil { if ("unnamed" == album) { album = fileSystemSafe(entry.title!!) } - dir = File(String.format("%s/%s/%s", musicDirectory.path, artist, album)) + dir = File(String.format(Locale.ROOT, "%s/%s/%s", musicDirectory.path, artist, album)) } return dir } fun createDirectoryForParent(file: File) { val dir = file.parentFile - if (!dir.exists()) { + if (dir != null && !dir.exists()) { if (!dir.mkdirs()) { Timber.e("Failed to create directory %s", dir) } } } + @Suppress("SameParameterValue") private fun getOrCreateDirectory(name: String): File { val dir = File(ultrasonicDirectory, name) if (!dir.exists() && !dir.mkdirs()) { @@ -228,34 +221,36 @@ object FileUtil { return dir } - // After Android M, the location of the files must be queried differently. GetExternalFilesDir will always return a directory which Ultrasonic can access without any extra privileges. - @JvmStatic - val ultrasonicDirectory: File? + // After Android M, the location of the files must be queried differently. + // GetExternalFilesDir will always return a directory which Ultrasonic + // can access without any extra privileges. + @JvmStatic + val ultrasonicDirectory: File? get() = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) File( Environment.getExternalStorageDirectory(), "Android/data/org.moire.ultrasonic" - ) else applicationContext().getExternalFilesDir(null) + ) else UApp.applicationContext().getExternalFilesDir(null) - // After Android M, the location of the files must be queried differently. GetExternalFilesDir will always return a directory which Ultrasonic can access without any extra privileges. - @JvmStatic - val defaultMusicDirectory: File + // After Android M, the location of the files must be queried differently. + // GetExternalFilesDir will always return a directory which Ultrasonic + // can access without any extra privileges. + @JvmStatic + val defaultMusicDirectory: File get() = getOrCreateDirectory("music") @JvmStatic - val musicDirectory: File + val musicDirectory: File get() { - val defaultMusicDirectory = defaultMusicDirectory - val path = getPreferences().getString( - Constants.PREFERENCES_KEY_CACHE_LOCATION, - defaultMusicDirectory.path - ) - val dir = File(path) + val path = Util.getPreferences() + .getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, defaultMusicDirectory.path) + val dir = File(path!!) val hasAccess = ensureDirectoryExistsAndIsReadWritable(dir) if (!hasAccess) permissionUtil.value.handlePermissionFailed(null) return if (hasAccess) dir else defaultMusicDirectory } @JvmStatic - fun ensureDirectoryExistsAndIsReadWritable(dir: File?): Boolean { + @Suppress("ReturnCount") + fun ensureDirectoryExistsAndIsReadWritable(dir: File?): Boolean { if (dir == null) { return false } @@ -287,12 +282,12 @@ object FileUtil { * Makes a given filename safe by replacing special characters like slashes ("/" and "\") * with dashes ("-"). * - * @param filename The filename in question. + * @param name The filename in question. * @return The filename with special characters replaced by hyphens. */ - private fun fileSystemSafe(filename: String): String { - var filename = filename - if (filename == null || filename.trim { it <= ' ' }.isEmpty()) { + private fun fileSystemSafe(name: String): String { + var filename = name + if (filename.trim { it <= ' ' }.isEmpty()) { return "unnamed" } for (s in FILE_SYSTEM_UNSAFE) { @@ -308,29 +303,29 @@ object FileUtil { * @param path The path of the directory in question. * @return The the directory name with special characters replaced by hyphens. */ - private fun fileSystemSafeDir(path: String?): String? { - var path = path - if (path == null || path.trim { it <= ' ' }.isEmpty()) { + private fun fileSystemSafeDir(path: String?): String { + var filepath = path + if (filepath == null || filepath.trim { it <= ' ' }.isEmpty()) { return "" } for (s in FILE_SYSTEM_UNSAFE_DIR) { - path = path!!.replace(s, "-") + filepath = filepath!!.replace(s, "-") } - return path + return filepath!! } /** * Similar to [File.listFiles], but returns a sorted set. * Never returns `null`, instead a warning is logged, and an empty set is returned. */ - @JvmStatic - fun listFiles(dir: File): SortedSet { + @JvmStatic + fun listFiles(dir: File): SortedSet { val files = dir.listFiles() if (files == null) { Timber.w("Failed to list children for %s", dir.path) return TreeSet() } - return TreeSet(Arrays.asList(*files)) + return TreeSet(files.asList()) } fun listMediaFiles(dir: File): SortedSet { @@ -347,7 +342,8 @@ object FileUtil { private fun isMediaFile(file: File): Boolean { val extension = getExtension(file.name) - return MUSIC_FILE_EXTENSIONS.contains(extension) || VIDEO_FILE_EXTENSIONS.contains(extension) + return MUSIC_FILE_EXTENSIONS.contains(extension) || + VIDEO_FILE_EXTENSIONS.contains(extension) } fun isPlaylistFile(file: File): Boolean { @@ -364,7 +360,7 @@ object FileUtil { */ fun getExtension(name: String): String { val index = name.lastIndexOf('.') - return if (index == -1) "" else name.substring(index + 1).toLowerCase() + return if (index == -1) "" else name.substring(index + 1).lowercase(Locale.ROOT) } /** @@ -386,7 +382,7 @@ object FileUtil { * @return The .partial file name */ fun getPartialFile(name: String): String { - return String.format("%s.partial.%s", getBaseName(name), getExtension(name)) + return String.format(Locale.ROOT, "%s.partial.%s", getBaseName(name), getExtension(name)) } /** @@ -396,11 +392,11 @@ object FileUtil { * @return The .complete file name */ fun getCompleteFile(name: String): String { - return String.format("%s.complete.%s", getBaseName(name), getExtension(name)) + return String.format(Locale.ROOT, "%s.complete.%s", getBaseName(name), getExtension(name)) } @JvmStatic - fun serialize(context: Context, obj: T, fileName: String?): Boolean { + fun serialize(context: Context, obj: T, fileName: String): Boolean { val file = File(context.cacheDir, fileName) var out: ObjectOutputStream? = null return try { @@ -408,32 +404,62 @@ object FileUtil { out.writeObject(obj) Timber.i("Serialized object to %s", file) true - } catch (x: Throwable) { + } catch (ignored: Exception) { Timber.w("Failed to serialize object to %s", file) false } finally { - close(out) + Util.close(out) } } + @Suppress("UNCHECKED_CAST") @JvmStatic - fun deserialize(context: Context, fileName: String?): T? { + fun deserialize(context: Context, fileName: String): T? { val file = File(context.cacheDir, fileName) if (!file.exists() || !file.isFile) { return null } - var `in`: ObjectInputStream? = null + var inStream: ObjectInputStream? = null return try { - `in` = ObjectInputStream(FileInputStream(file)) - val `object` = `in`.readObject() - val result = `object` as T + inStream = ObjectInputStream(FileInputStream(file)) + val readObject = inStream.readObject() + val result = readObject as T Timber.i("Deserialized object from %s", file) result - } catch (x: Throwable) { - Timber.w(x, "Failed to deserialize object from %s", file) + } catch (all: Throwable) { + Timber.w(all, "Failed to deserialize object from %s", file) null } finally { - close(`in`) + Util.close(inStream) } } -} \ No newline at end of file + + fun savePlaylist( + playlistFile: File?, + playlist: MusicDirectory, + name: String + ) { + val fw = FileWriter(playlistFile) + val bw = BufferedWriter(fw) + + try { + fw.write("#EXTM3U\n") + for (e in playlist.getChildren()) { + var filePath = getSongFile(e).absolutePath + + if (!File(filePath).exists()) { + val ext = getExtension(filePath) + val base = getBaseName(filePath) + filePath = "$base.complete.$ext" + } + fw.write(filePath + "\n") + } + } catch (e: IOException) { + Timber.w("Failed to save playlist: %s", name) + throw e + } finally { + bw.close() + fw.close() + } + } +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/FileUtilKt.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/FileUtilKt.kt deleted file mode 100644 index fc527c15..00000000 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/FileUtilKt.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * FileUtil.kt - * Copyright (C) 2009-2021 Ultrasonic developers - * - * Distributed under terms of the GNU GPLv3 license. - */ - -package org.moire.ultrasonic.util - -import java.io.BufferedWriter -import java.io.File -import java.io.FileWriter -import java.io.IOException -import org.moire.ultrasonic.domain.MusicDirectory -import timber.log.Timber - -// TODO: Convert FileUtil.java and merge into here. -object FileUtilKt { - fun savePlaylist( - playlistFile: File?, - playlist: MusicDirectory, - name: String - ) { - val fw = FileWriter(playlistFile) - val bw = BufferedWriter(fw) - - try { - fw.write("#EXTM3U\n") - for (e in playlist.getChildren()) { - var filePath = FileUtil.getSongFile(e).absolutePath - - if (!File(filePath).exists()) { - val ext = FileUtil.getExtension(filePath) - val base = FileUtil.getBaseName(filePath) - filePath = "$base.complete.$ext" - } - fw.write(filePath + "\n") - } - } catch (e: IOException) { - Timber.w("Failed to save playlist: %s", name) - throw e - } finally { - bw.close() - fw.close() - } - } -} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt index ed847ffd..9105d461 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt @@ -40,14 +40,11 @@ import android.widget.Toast import androidx.annotation.AnyRes import androidx.media.utils.MediaConstants import androidx.preference.PreferenceManager -import java.io.ByteArrayOutputStream import java.io.Closeable import java.io.File import java.io.FileInputStream import java.io.FileOutputStream import java.io.IOException -import java.io.InputStream -import java.io.OutputStream import java.io.UnsupportedEncodingException import java.security.MessageDigest import java.text.DecimalFormat