diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java deleted file mode 100644 index 3d3f8b27..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java +++ /dev/null @@ -1,536 +0,0 @@ -/* - 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 - */ -package org.moire.ultrasonic.util; - -import android.content.Context; -import android.os.Build; -import android.os.Environment; -import android.text.TextUtils; - -import org.moire.ultrasonic.app.UApp; -import org.moire.ultrasonic.domain.MusicDirectory; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.regex.Pattern; - -import kotlin.Lazy; -import timber.log.Timber; - -import static org.koin.java.KoinJavaComponent.inject; - -/** - * @author Sindre Mehus - */ -public class FileUtil -{ - private static final String[] FILE_SYSTEM_UNSAFE = {"/", "\\", "..", ":", "\"", "?", "*", "<", ">", "|"}; - private static final String[] FILE_SYSTEM_UNSAFE_DIR = {"\\", "..", ":", "\"", "?", "*", "<", ">", "|"}; - private static final List MUSIC_FILE_EXTENSIONS = Arrays.asList("mp3", "ogg", "aac", "flac", "m4a", "wav", "wma", "opus"); - private static final List VIDEO_FILE_EXTENSIONS = Arrays.asList("flv", "mp4", "m4v", "wmv", "avi", "mov", "mpg", "mkv"); - private static final List PLAYLIST_FILE_EXTENSIONS = Collections.singletonList("m3u"); - private static final Pattern TITLE_WITH_TRACK = Pattern.compile("^\\d\\d-.*"); - public static final String SUFFIX_LARGE = ".jpeg"; - public static final String SUFFIX_SMALL = ".jpeg-small"; - - private static final Lazy permissionUtil = inject(PermissionUtil.class); - - public static File getSongFile(MusicDirectory.Entry song) - { - File dir = getAlbumDirectory(song); - - // Do not generate new name for offline files. Offline files will have their Path as their Id. - if (!TextUtils.isEmpty(song.getId())) - { - if (song.getId().startsWith(dir.getAbsolutePath())) return new File(song.getId()); - } - - // Generate a file name for the song - StringBuilder fileName = new StringBuilder(256); - Integer track = song.getTrack(); - - //check if filename already had track number - if (!TITLE_WITH_TRACK.matcher(song.getTitle()).matches()) { - if (track != null) { - if (track < 10) { - fileName.append('0'); - } - - fileName.append(track).append('-'); - } - } - fileName.append(fileSystemSafe(song.getTitle())).append('.'); - - if (!TextUtils.isEmpty(song.getTranscodedSuffix())) { - fileName.append(song.getTranscodedSuffix()); - } else { - fileName.append(song.getSuffix()); - } - - return new File(dir, fileName.toString()); - } - - public static File getPlaylistFile(String server, String name) - { - File playlistDir = getPlaylistDirectory(server); - return new File(playlistDir, String.format("%s.m3u", fileSystemSafe(name))); - } - - public static File getPlaylistDirectory() - { - File playlistDir = new File(getUltrasonicDirectory(), "playlists"); - ensureDirectoryExistsAndIsReadWritable(playlistDir); - return playlistDir; - } - - public static File getPlaylistDirectory(String server) - { - File playlistDir = new File(getPlaylistDirectory(), server); - ensureDirectoryExistsAndIsReadWritable(playlistDir); - return playlistDir; - } - - /** - * Get the album art file for a given album entry - * @param entry The album entry - * @return File object. Not guaranteed that it exists - */ - public static File getAlbumArtFile(MusicDirectory.Entry entry) - { - File albumDir = getAlbumDirectory(entry); - return getAlbumArtFile(albumDir); - } - - /** - * Get the cache key for a given album entry - * @param entry The album entry - * @param large Whether to get the key for the large or the default image - * @return String The hash key - */ - public static String getAlbumArtKey(MusicDirectory.Entry entry, boolean large) - { - File albumDir = getAlbumDirectory(entry); - - return getAlbumArtKey(albumDir, large); - } - - /** - * Get the cache key for a given album entry - * @param albumDir The album directory - * @param large Whether to get the key for the large or the default image - * @return String The hash key - */ - public static String getAlbumArtKey(File albumDir, boolean large) - { - if (albumDir == null) { - return null; - } - - String suffix = (large) ? SUFFIX_LARGE : SUFFIX_SMALL; - - return String.format(Locale.ROOT, "%s%s", Util.md5Hex(albumDir.getPath()), suffix); - } - - - - public static File getAvatarFile(String username) - { - File albumArtDir = getAlbumArtDirectory(); - - if (albumArtDir == null || username == null) - { - return null; - } - - String md5Hex = Util.md5Hex(username); - return new File(albumArtDir, String.format("%s%s", md5Hex, SUFFIX_LARGE)); - } - - /** - * Get the album art file for a given album directory - * @param albumDir The album directory - * @return File object. Not guaranteed that it exists - */ - public static File getAlbumArtFile(File albumDir) - { - File albumArtDir = getAlbumArtDirectory(); - String key = getAlbumArtKey(albumDir, true); - - if (key == null || albumArtDir == null) - { - return null; - } - - return new File(albumArtDir, key); - } - - - /** - * Get the album art file for a given cache key - * @param cacheKey The key (== the filename) - * @return File object. Not guaranteed that it exists - */ - public static File getAlbumArtFile(String cacheKey) - { - File albumArtDir = getAlbumArtDirectory(); - - if (albumArtDir == null || cacheKey == null) - { - return null; - } - - return new File(albumArtDir, cacheKey); - } - - - public static File getAlbumArtDirectory() - { - File albumArtDir = new File(getUltrasonicDirectory(), "artwork"); - ensureDirectoryExistsAndIsReadWritable(albumArtDir); - ensureDirectoryExistsAndIsReadWritable(new File(albumArtDir, ".nomedia")); - return albumArtDir; - } - - public static File getAlbumDirectory(MusicDirectory.Entry entry) - { - if (entry == null) - { - return null; - } - - File dir; - - if (!TextUtils.isEmpty(entry.getPath())) - { - File f = new File(fileSystemSafeDir(entry.getPath())); - dir = new File(String.format("%s/%s", getMusicDirectory().getPath(), entry.isDirectory() ? f.getPath() : f.getParent())); - } - else - { - String artist = fileSystemSafe(entry.getArtist()); - String album = fileSystemSafe(entry.getAlbum()); - - if ("unnamed".equals(album)) - { - album = fileSystemSafe(entry.getTitle()); - } - - dir = new File(String.format("%s/%s/%s", getMusicDirectory().getPath(), artist, album)); - } - - return dir; - } - - public static void createDirectoryForParent(File file) - { - File dir = file.getParentFile(); - if (!dir.exists()) - { - if (!dir.mkdirs()) - { - Timber.e("Failed to create directory %s", dir); - } - } - } - - private static File getOrCreateDirectory(String name) - { - File dir = new File(getUltrasonicDirectory(), name); - - if (!dir.exists() && !dir.mkdirs()) - { - Timber.e("Failed to create %s", name); - } - - return dir; - } - - public static File getUltrasonicDirectory() - { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) - return new File(Environment.getExternalStorageDirectory(), "Android/data/org.moire.ultrasonic"); - - // 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. - return UApp.Companion.applicationContext().getExternalFilesDir(null); - } - - public static File getDefaultMusicDirectory() - { - return getOrCreateDirectory("music"); - } - - public static File getMusicDirectory() - { - File defaultMusicDirectory = getDefaultMusicDirectory(); - String path = Util.getPreferences().getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, defaultMusicDirectory.getPath()); - File dir = new File(path); - - boolean hasAccess = ensureDirectoryExistsAndIsReadWritable(dir); - if (!hasAccess) permissionUtil.getValue().handlePermissionFailed(null); - - return hasAccess ? dir : defaultMusicDirectory; - } - - public static boolean ensureDirectoryExistsAndIsReadWritable(File dir) - { - if (dir == null) - { - return false; - } - - if (dir.exists()) - { - if (!dir.isDirectory()) - { - Timber.w("%s exists but is not a directory.", dir); - return false; - } - } - else - { - if (dir.mkdirs()) - { - Timber.i("Created directory %s", dir); - } - else - { - Timber.w("Failed to create directory %s", dir); - return false; - } - } - - if (!dir.canRead()) - { - Timber.w("No read permission for directory %s", dir); - return false; - } - - if (!dir.canWrite()) - { - Timber.w("No write permission for directory %s", dir); - return false; - } - - return true; - } - - /** - * Makes a given filename safe by replacing special characters like slashes ("/" and "\") - * with dashes ("-"). - * - * @param filename The filename in question. - * @return The filename with special characters replaced by hyphens. - */ - private static String fileSystemSafe(String filename) - { - if (filename == null || filename.trim().isEmpty()) - { - return "unnamed"; - } - - for (String s : FILE_SYSTEM_UNSAFE) - { - filename = filename.replace(s, "-"); - } - - return filename; - } - - /** - * Makes a given filename safe by replacing special characters like colons (":") - * with dashes ("-"). - * - * @param path The path of the directory in question. - * @return The the directory name with special characters replaced by hyphens. - */ - private static String fileSystemSafeDir(String path) - { - if (path == null || path.trim().isEmpty()) - { - return ""; - } - - for (String s : FILE_SYSTEM_UNSAFE_DIR) - { - path = path.replace(s, "-"); - } - - return path; - } - - /** - * Similar to {@link File#listFiles()}, but returns a sorted set. - * Never returns {@code null}, instead a warning is logged, and an empty set is returned. - */ - public static SortedSet listFiles(File dir) - { - File[] files = dir.listFiles(); - - if (files == null) - { - Timber.w("Failed to list children for %s", dir.getPath()); - return new TreeSet<>(); - } - - return new TreeSet<>(Arrays.asList(files)); - } - - public static SortedSet listMediaFiles(File dir) - { - SortedSet files = listFiles(dir); - Iterator iterator = files.iterator(); - - while (iterator.hasNext()) - { - File file = iterator.next(); - - if (!file.isDirectory() && !isMediaFile(file)) - { - iterator.remove(); - } - } - - return files; - } - - private static boolean isMediaFile(File file) - { - String extension = getExtension(file.getName()); - return MUSIC_FILE_EXTENSIONS.contains(extension) || VIDEO_FILE_EXTENSIONS.contains(extension); - } - - public static boolean isPlaylistFile(File file) - { - String extension = getExtension(file.getName()); - return PLAYLIST_FILE_EXTENSIONS.contains(extension); - } - - /** - * Returns the extension (the substring after the last dot) of the given file. The dot - * is not included in the returned extension. - * - * @param name The filename in question. - * @return The extension, or an empty string if no extension is found. - */ - public static String getExtension(String name) - { - int index = name.lastIndexOf('.'); - return index == -1 ? "" : name.substring(index + 1).toLowerCase(); - } - - /** - * Returns the base name (the substring before the last dot) of the given file. The dot - * is not included in the returned basename. - * - * @param name The filename in question. - * @return The base name, or an empty string if no basename is found. - */ - public static String getBaseName(String name) - { - int index = name.lastIndexOf('.'); - return index == -1 ? name : name.substring(0, index); - } - - /** - * Returns the file name of a .partial file of the given file. - * - * @param name The filename in question. - * @return The .partial file name - */ - public static String getPartialFile(String name) - { - return String.format("%s.partial.%s", FileUtil.getBaseName(name), FileUtil.getExtension(name)); - } - - /** - * Returns the file name of a .complete file of the given file. - * - * @param name The filename in question. - * @return The .complete file name - */ - public static String getCompleteFile(String name) - { - return String.format("%s.complete.%s", FileUtil.getBaseName(name), FileUtil.getExtension(name)); - } - - public static boolean serialize(Context context, T obj, String fileName) - { - File file = new File(context.getCacheDir(), fileName); - ObjectOutputStream out = null; - - try - { - out = new ObjectOutputStream(new FileOutputStream(file)); - out.writeObject(obj); - Timber.i("Serialized object to %s", file); - return true; - } - catch (Throwable x) - { - Timber.w("Failed to serialize object to %s", file); - return false; - } - finally - { - Util.close(out); - } - } - - @SuppressWarnings({"unchecked"}) - public static T deserialize(Context context, String fileName) - { - File file = new File(context.getCacheDir(), fileName); - - if (!file.exists() || !file.isFile()) - { - return null; - } - - ObjectInputStream in = null; - - try - { - in = new ObjectInputStream(new FileInputStream(file)); - Object object = in.readObject(); - T result = (T) object; - Timber.i("Deserialized object from %s", file); - return result; - } - catch (Throwable x) - { - Timber.w(x,"Failed to deserialize object from %s", file); - return null; - } - finally - { - Util.close(in); - } - } -} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.kt b/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.kt new file mode 100644 index 00000000..62cbcaa0 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.kt @@ -0,0 +1,439 @@ +/* + 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 + */ +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.File +import java.io.FileInputStream +import java.io.FileOutputStream +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 + +/** + * @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") + private val VIDEO_FILE_EXTENSIONS = + Arrays.asList("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( + PermissionUtil::class.java + ) + + fun getSongFile(song: MusicDirectory.Entry): File { + val dir = getAlbumDirectory(song) + + // Do not generate new name for offline files. Offline files will have their Path as their Id. + if (!TextUtils.isEmpty(song.id)) { + if (song.id.startsWith(dir!!.absolutePath)) return File(song.id) + } + + // Generate a file name for the song + val fileName = StringBuilder(256) + val track = song.track + + //check if filename already had track number + if (!TITLE_WITH_TRACK.matcher(song.title).matches()) { + if (track != null) { + if (track < 10) { + fileName.append('0') + } + fileName.append(track).append('-') + } + } + fileName.append(fileSystemSafe(song.title!!)).append('.') + if (!TextUtils.isEmpty(song.transcodedSuffix)) { + fileName.append(song.transcodedSuffix) + } else { + fileName.append(song.suffix) + } + return File(dir, fileName.toString()) + } + + @JvmStatic + fun getPlaylistFile(server: String?, name: String): File { + val playlistDir = getPlaylistDirectory(server) + return File(playlistDir, String.format("%s.m3u", fileSystemSafe(name))) + } + + @JvmStatic + val playlistDirectory: File + get() { + val playlistDir = File(ultrasonicDirectory, "playlists") + ensureDirectoryExistsAndIsReadWritable(playlistDir) + return playlistDir + } + + fun getPlaylistDirectory(server: String?): File { + val playlistDir = File(playlistDirectory, server) + ensureDirectoryExistsAndIsReadWritable(playlistDir) + return playlistDir + } + + /** + * Get the album art file for a given album entry + * @param entry The album entry + * @return File object. Not guaranteed that it exists + */ + fun getAlbumArtFile(entry: MusicDirectory.Entry?): File? { + val albumDir = getAlbumDirectory(entry) + return getAlbumArtFile(albumDir) + } + + /** + * Get the cache key for a given album entry + * @param entry The album entry + * @param large Whether to get the key for the large or the default image + * @return String The hash key + */ + fun getAlbumArtKey(entry: MusicDirectory.Entry?, large: Boolean): String? { + val albumDir = getAlbumDirectory(entry) + return getAlbumArtKey(albumDir, large) + } + + /** + * Get the cache key for a given album entry + * @param albumDir The album directory + * @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? { + 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) + } + + fun getAvatarFile(username: String?): File? { + val albumArtDir = albumArtDirectory + if (albumArtDir == null || username == null) { + return null + } + val md5Hex = md5Hex(username) + return File(albumArtDir, String.format("%s%s", md5Hex, SUFFIX_LARGE)) + } + + /** + * Get the album art file for a given album directory + * @param albumDir The album directory + * @return File object. Not guaranteed that it exists + */ + fun getAlbumArtFile(albumDir: File?): File? { + val albumArtDir = albumArtDirectory + val key = getAlbumArtKey(albumDir, true) + return if (key == null || albumArtDir == null) { + null + } else File(albumArtDir, key) + } + + /** + * Get the album art file for a given cache key + * @param cacheKey The key (== the filename) + * @return File object. Not guaranteed that it exists + */ + fun getAlbumArtFile(cacheKey: String?): File? { + val albumArtDir = albumArtDirectory + return if (albumArtDir == null || cacheKey == null) { + null + } else File(albumArtDir, cacheKey) + } + + val albumArtDirectory: File + get() { + val albumArtDir = File(ultrasonicDirectory, "artwork") + ensureDirectoryExistsAndIsReadWritable(albumArtDir) + ensureDirectoryExistsAndIsReadWritable(File(albumArtDir, ".nomedia")) + return albumArtDir + } + + fun getAlbumDirectory(entry: MusicDirectory.Entry?): File? { + if (entry == null) { + return null + } + val dir: File + if (!TextUtils.isEmpty(entry.path)) { + val f = File(fileSystemSafeDir(entry.path)) + dir = File( + String.format( + "%s/%s", + musicDirectory.path, + if (entry.isDirectory) f.path else f.parent + ) + ) + } else { + val artist = fileSystemSafe(entry.artist!!) + var album = fileSystemSafe(entry.album!!) + if ("unnamed" == album) { + album = fileSystemSafe(entry.title!!) + } + dir = File(String.format("%s/%s/%s", musicDirectory.path, artist, album)) + } + return dir + } + + fun createDirectoryForParent(file: File) { + val dir = file.parentFile + if (!dir.exists()) { + if (!dir.mkdirs()) { + Timber.e("Failed to create directory %s", dir) + } + } + } + + private fun getOrCreateDirectory(name: String): File { + val dir = File(ultrasonicDirectory, name) + if (!dir.exists() && !dir.mkdirs()) { + Timber.e("Failed to create %s", name) + } + 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? + get() = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) File( + Environment.getExternalStorageDirectory(), + "Android/data/org.moire.ultrasonic" + ) else 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 + get() = getOrCreateDirectory("music") + @JvmStatic + val musicDirectory: File + get() { + val defaultMusicDirectory = defaultMusicDirectory + val path = 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 { + if (dir == null) { + return false + } + if (dir.exists()) { + if (!dir.isDirectory) { + Timber.w("%s exists but is not a directory.", dir) + return false + } + } else { + if (dir.mkdirs()) { + Timber.i("Created directory %s", dir) + } else { + Timber.w("Failed to create directory %s", dir) + return false + } + } + if (!dir.canRead()) { + Timber.w("No read permission for directory %s", dir) + return false + } + if (!dir.canWrite()) { + Timber.w("No write permission for directory %s", dir) + return false + } + return true + } + + /** + * Makes a given filename safe by replacing special characters like slashes ("/" and "\") + * with dashes ("-"). + * + * @param filename 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()) { + return "unnamed" + } + for (s in FILE_SYSTEM_UNSAFE) { + filename = filename.replace(s, "-") + } + return filename + } + + /** + * Makes a given filename safe by replacing special characters like colons (":") + * with dashes ("-"). + * + * @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()) { + return "" + } + for (s in FILE_SYSTEM_UNSAFE_DIR) { + path = path!!.replace(s, "-") + } + return path + } + + /** + * 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 { + val files = dir.listFiles() + if (files == null) { + Timber.w("Failed to list children for %s", dir.path) + return TreeSet() + } + return TreeSet(Arrays.asList(*files)) + } + + fun listMediaFiles(dir: File): SortedSet { + val files = listFiles(dir) + val iterator = files.iterator() + while (iterator.hasNext()) { + val file = iterator.next() + if (!file.isDirectory && !isMediaFile(file)) { + iterator.remove() + } + } + return files + } + + private fun isMediaFile(file: File): Boolean { + val extension = getExtension(file.name) + return MUSIC_FILE_EXTENSIONS.contains(extension) || VIDEO_FILE_EXTENSIONS.contains(extension) + } + + fun isPlaylistFile(file: File): Boolean { + val extension = getExtension(file.name) + return PLAYLIST_FILE_EXTENSIONS.contains(extension) + } + + /** + * Returns the extension (the substring after the last dot) of the given file. The dot + * is not included in the returned extension. + * + * @param name The filename in question. + * @return The extension, or an empty string if no extension is found. + */ + fun getExtension(name: String): String { + val index = name.lastIndexOf('.') + return if (index == -1) "" else name.substring(index + 1).toLowerCase() + } + + /** + * Returns the base name (the substring before the last dot) of the given file. The dot + * is not included in the returned basename. + * + * @param name The filename in question. + * @return The base name, or an empty string if no basename is found. + */ + fun getBaseName(name: String): String { + val index = name.lastIndexOf('.') + return if (index == -1) name else name.substring(0, index) + } + + /** + * Returns the file name of a .partial file of the given file. + * + * @param name The filename in question. + * @return The .partial file name + */ + fun getPartialFile(name: String): String { + return String.format("%s.partial.%s", getBaseName(name), getExtension(name)) + } + + /** + * Returns the file name of a .complete file of the given file. + * + * @param name The filename in question. + * @return The .complete file name + */ + fun getCompleteFile(name: String): String { + return String.format("%s.complete.%s", getBaseName(name), getExtension(name)) + } + + @JvmStatic + fun serialize(context: Context, obj: T, fileName: String?): Boolean { + val file = File(context.cacheDir, fileName) + var out: ObjectOutputStream? = null + return try { + out = ObjectOutputStream(FileOutputStream(file)) + out.writeObject(obj) + Timber.i("Serialized object to %s", file) + true + } catch (x: Throwable) { + Timber.w("Failed to serialize object to %s", file) + false + } finally { + close(out) + } + } + + @JvmStatic + fun deserialize(context: Context, fileName: String?): T? { + val file = File(context.cacheDir, fileName) + if (!file.exists() || !file.isFile) { + return null + } + var `in`: ObjectInputStream? = null + return try { + `in` = ObjectInputStream(FileInputStream(file)) + val `object` = `in`.readObject() + val result = `object` as T + Timber.i("Deserialized object from %s", file) + result + } catch (x: Throwable) { + Timber.w(x, "Failed to deserialize object from %s", file) + null + } finally { + close(`in`) + } + } +} \ No newline at end of file diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt index c770efe4..6ca87dfa 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt @@ -306,7 +306,7 @@ class NavigationActivity : AppCompatActivity() { val editor = preferences.edit() editor.putString( Constants.PREFERENCES_KEY_CACHE_LOCATION, - FileUtil.getDefaultMusicDirectory().path + FileUtil.defaultMusicDirectory.path ) editor.apply() } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt index 28be7d2a..0b8d894b 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt @@ -94,7 +94,7 @@ class FileLoggerTree : Timber.DebugTree() { if (next) fileNum++ file = File( - FileUtil.getUltrasonicDirectory(), + FileUtil.ultrasonicDirectory, FILENAME.replace("*", fileNum.toString()) ) } @@ -162,7 +162,7 @@ class FileLoggerTree : Timber.DebugTree() { } private fun getLogFileList(): Array? { - val directory = FileUtil.getUltrasonicDirectory() + val directory = FileUtil.ultrasonicDirectory return directory.listFiles { t -> t.name.matches(fileNameRegex) } } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt index 874138ce..d705119a 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt @@ -52,7 +52,7 @@ class OfflineMusicService : MusicService, KoinComponent { override fun getIndexes(musicFolderId: String?, refresh: Boolean): List { val indexes: MutableList = ArrayList() - val root = FileUtil.getMusicDirectory() + val root = FileUtil.musicDirectory for (file in FileUtil.listFiles(root)) { if (file.isDirectory) { val index = Index(file.path) @@ -121,7 +121,7 @@ class OfflineMusicService : MusicService, KoinComponent { val artists: MutableList = ArrayList() val albums: MutableList = ArrayList() val songs: MutableList = ArrayList() - val root = FileUtil.getMusicDirectory() + val root = FileUtil.musicDirectory var closeness: Int for (artistFile in FileUtil.listFiles(root)) { val artistName = artistFile.name @@ -250,7 +250,7 @@ class OfflineMusicService : MusicService, KoinComponent { } override fun getRandomSongs(size: Int): MusicDirectory { - val root = FileUtil.getMusicDirectory() + val root = FileUtil.musicDirectory val children: MutableList = LinkedList() listFilesRecursively(root, children) val result = MusicDirectory() @@ -503,7 +503,7 @@ class OfflineMusicService : MusicService, KoinComponent { entry.isDirectory = file.isDirectory entry.parent = file.parent entry.size = file.length() - val root = FileUtil.getMusicDirectory().path + val root = FileUtil.musicDirectory.path entry.path = file.path.replaceFirst( String.format(Locale.ROOT, "^%s/", root).toRegex(), "" ) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt index 6156b781..84a89986 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt @@ -51,7 +51,7 @@ class ImageLoaderProvider(val context: Context) : KoinComponent { ImageLoaderConfig( Util.getMaxDisplayMetric(), defaultSize, - FileUtil.getAlbumArtDirectory() + FileUtil.albumArtDirectory ) } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/SubsonicUncaughtExceptionHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/SubsonicUncaughtExceptionHandler.kt index 5a5d77cf..658d2943 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/SubsonicUncaughtExceptionHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/SubsonicUncaughtExceptionHandler.kt @@ -20,7 +20,7 @@ class SubsonicUncaughtExceptionHandler( var printWriter: PrintWriter? = null try { - file = File(FileUtil.getUltrasonicDirectory(), STACKTRACE_NAME) + file = File(FileUtil.ultrasonicDirectory, STACKTRACE_NAME) printWriter = PrintWriter(file) val logMessage = String.format( "Android API level: %s\nUltrasonic version name: %s\n" +