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" +