diff options
Diffstat (limited to 'app/src/main/java/github/daneren2005/dsub/util/FileUtil.java')
-rw-r--r-- | app/src/main/java/github/daneren2005/dsub/util/FileUtil.java | 860 |
1 files changed, 860 insertions, 0 deletions
diff --git a/app/src/main/java/github/daneren2005/dsub/util/FileUtil.java b/app/src/main/java/github/daneren2005/dsub/util/FileUtil.java new file mode 100644 index 00000000..990eae06 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/util/FileUtil.java @@ -0,0 +1,860 @@ +/* + 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 <http://www.gnu.org/licenses/>. + + Copyright 2009 (C) Sindre Mehus + */ +package github.daneren2005.dsub.util; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.Iterator; +import java.util.List; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.InflaterInputStream; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Build; +import android.os.Environment; +import android.support.v4.content.ContextCompat; +import android.util.Log; +import github.daneren2005.dsub.domain.Artist; +import github.daneren2005.dsub.domain.Genre; +import github.daneren2005.dsub.domain.Indexes; +import github.daneren2005.dsub.domain.Playlist; +import github.daneren2005.dsub.domain.PodcastChannel; +import github.daneren2005.dsub.domain.MusicDirectory; +import github.daneren2005.dsub.domain.MusicFolder; +import github.daneren2005.dsub.domain.PodcastEpisode; +import github.daneren2005.dsub.service.MediaStoreService; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +/** + * @author Sindre Mehus + */ +public class FileUtil { + + private static final String TAG = FileUtil.class.getSimpleName(); + private static final String[] FILE_SYSTEM_UNSAFE = {"/", "\\", "..", ":", "\"", "?", "*", "<", ">", "|"}; + private static final String[] FILE_SYSTEM_UNSAFE_DIR = {"\\", "..", ":", "\"", "?", "*", "<", ">", "|"}; + private static final List<String> MUSIC_FILE_EXTENSIONS = Arrays.asList("mp3", "ogg", "aac", "flac", "m4a", "wav", "wma"); + private static final List<String> VIDEO_FILE_EXTENSIONS = Arrays.asList("flv", "mp4", "m4v", "wmv", "avi", "mov", "mpg", "mkv"); + private static final List<String> PLAYLIST_FILE_EXTENSIONS = Arrays.asList("m3u"); + private static File DEFAULT_MUSIC_DIR; + private static final Kryo kryo = new Kryo(); + private static HashMap<String, MusicDirectory.Entry> entryLookup; + + static { + kryo.register(MusicDirectory.Entry.class); + kryo.register(Indexes.class); + kryo.register(Artist.class); + kryo.register(MusicFolder.class); + kryo.register(PodcastChannel.class); + kryo.register(Playlist.class); + kryo.register(Genre.class); + } + + public static File getAnySong(Context context) { + File dir = getMusicDirectory(context); + return getAnySong(context, dir); + } + private static File getAnySong(Context context, File dir) { + for(File file: dir.listFiles()) { + if(file.isDirectory()) { + return getAnySong(context, file); + } + + String extension = getExtension(file.getName()); + if(MUSIC_FILE_EXTENSIONS.contains(extension)) { + return file; + } + } + + return null; + } + + public static File getEntryFile(Context context, MusicDirectory.Entry entry) { + if(entry.isDirectory()) { + return getAlbumDirectory(context, entry); + } else { + return getSongFile(context, entry); + } + } + + public static File getSongFile(Context context, MusicDirectory.Entry song) { + File dir = getAlbumDirectory(context, song); + + StringBuilder fileName = new StringBuilder(); + Integer track = song.getTrack(); + if (track != null) { + if (track < 10) { + fileName.append("0"); + } + fileName.append(track).append("-"); + } + + fileName.append(fileSystemSafe(song.getTitle())).append("."); + + if(song.isVideo()) { + String videoPlayerType = Util.getVideoPlayerType(context); + if("hls".equals(videoPlayerType)) { + // HLS should be able to transcode to mp4 automatically + fileName.append("mp4"); + } else if("raw".equals(videoPlayerType)) { + // Download the original video without any transcoding + fileName.append(song.getSuffix()); + } + } else { + if (song.getTranscodedSuffix() != null) { + fileName.append(song.getTranscodedSuffix()); + } else { + fileName.append(song.getSuffix()); + } + } + + return new File(dir, fileName.toString()); + } + + public static File getPlaylistFile(Context context, String server, String name) { + File playlistDir = getPlaylistDirectory(context, server); + return new File(playlistDir, fileSystemSafe(name) + ".m3u"); + } + public static void writePlaylistFile(Context context, File file, MusicDirectory playlist) throws IOException { + FileWriter fw = new FileWriter(file); + BufferedWriter bw = new BufferedWriter(fw); + try { + fw.write("#EXTM3U\n"); + for (MusicDirectory.Entry e : playlist.getChildren()) { + String filePath = FileUtil.getSongFile(context, e).getAbsolutePath(); + if(! new File(filePath).exists()){ + String ext = FileUtil.getExtension(filePath); + String base = FileUtil.getBaseName(filePath); + filePath = base + ".complete." + ext; + } + fw.write(filePath + "\n"); + } + } catch(Exception e) { + Log.w(TAG, "Failed to save playlist: " + playlist.getName()); + } finally { + bw.close(); + fw.close(); + } + } + public static File getPlaylistDirectory(Context context) { + File playlistDir = new File(getSubsonicDirectory(context), "playlists"); + ensureDirectoryExistsAndIsReadWritable(playlistDir); + return playlistDir; + } + public static File getPlaylistDirectory(Context context, String server) { + File playlistDir = new File(getPlaylistDirectory(context), server); + ensureDirectoryExistsAndIsReadWritable(playlistDir); + return playlistDir; + } + + public static File getAlbumArtFile(Context context, MusicDirectory.Entry entry) { + File albumDir = getAlbumDirectory(context, entry); + File artFile; + File albumFile = getAlbumArtFile(albumDir); + File hexFile = getHexAlbumArtFile(context, albumDir); + if(albumDir.exists()) { + if(hexFile.exists()) { + hexFile.renameTo(albumFile); + } + artFile = albumFile; + } else { + artFile = hexFile; + } + return artFile; + } + + public static File getAlbumArtFile(File albumDir) { + return new File(albumDir, Constants.ALBUM_ART_FILE); + } + public static File getHexAlbumArtFile(Context context, File albumDir) { + return new File(getAlbumArtDirectory(context), Util.md5Hex(albumDir.getPath()) + ".jpeg"); + } + + public static Bitmap getAlbumArtBitmap(Context context, MusicDirectory.Entry entry, int size) { + File albumArtFile = getAlbumArtFile(context, entry); + if (albumArtFile.exists()) { + final BitmapFactory.Options opt = new BitmapFactory.Options(); + opt.inJustDecodeBounds = true; + BitmapFactory.decodeFile(albumArtFile.getPath(), opt); + opt.inPurgeable = true; + opt.inSampleSize = Util.calculateInSampleSize(opt, size, Util.getScaledHeight(opt.outHeight, opt.outWidth, size)); + opt.inJustDecodeBounds = false; + + Bitmap bitmap = BitmapFactory.decodeFile(albumArtFile.getPath(), opt); + return bitmap == null ? null : getScaledBitmap(bitmap, size); + } + return null; + } + + public static File getAvatarDirectory(Context context) { + File avatarDir = new File(getSubsonicDirectory(context), "avatars"); + ensureDirectoryExistsAndIsReadWritable(avatarDir); + ensureDirectoryExistsAndIsReadWritable(new File(avatarDir, ".nomedia")); + return avatarDir; + } + + public static File getAvatarFile(Context context, String username) { + return new File(getAvatarDirectory(context), Util.md5Hex(username) + ".jpeg"); + } + + public static Bitmap getAvatarBitmap(Context context, String username, int size) { + File avatarFile = getAvatarFile(context, username); + if (avatarFile.exists()) { + final BitmapFactory.Options opt = new BitmapFactory.Options(); + opt.inJustDecodeBounds = true; + BitmapFactory.decodeFile(avatarFile.getPath(), opt); + opt.inPurgeable = true; + opt.inSampleSize = Util.calculateInSampleSize(opt, size, Util.getScaledHeight(opt.outHeight, opt.outWidth, size)); + opt.inJustDecodeBounds = false; + + Bitmap bitmap = BitmapFactory.decodeFile(avatarFile.getPath(), opt); + return bitmap == null ? null : getScaledBitmap(bitmap, size, false); + } + return null; + } + + public static File getMiscDirectory(Context context) { + File dir = new File(getSubsonicDirectory(context), "misc"); + ensureDirectoryExistsAndIsReadWritable(dir); + ensureDirectoryExistsAndIsReadWritable(new File(dir, ".nomedia")); + return dir; + } + + public static File getMiscFile(Context context, String url) { + return new File(getMiscDirectory(context), Util.md5Hex(url) + ".jpeg"); + } + + public static Bitmap getMiscBitmap(Context context, String url, int size) { + File avatarFile = getMiscFile(context, url); + if (avatarFile.exists()) { + final BitmapFactory.Options opt = new BitmapFactory.Options(); + opt.inJustDecodeBounds = true; + BitmapFactory.decodeFile(avatarFile.getPath(), opt); + opt.inPurgeable = true; + opt.inSampleSize = Util.calculateInSampleSize(opt, size, Util.getScaledHeight(opt.outHeight, opt.outWidth, size)); + opt.inJustDecodeBounds = false; + + Bitmap bitmap = BitmapFactory.decodeFile(avatarFile.getPath(), opt); + return bitmap == null ? null : getScaledBitmap(bitmap, size, false); + } + return null; + } + + public static Bitmap getSampledBitmap(byte[] bytes, int size) { + return getSampledBitmap(bytes, size, true); + } + public static Bitmap getSampledBitmap(byte[] bytes, int size, boolean allowUnscaled) { + final BitmapFactory.Options opt = new BitmapFactory.Options(); + opt.inJustDecodeBounds = true; + BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opt); + opt.inPurgeable = true; + opt.inSampleSize = Util.calculateInSampleSize(opt, size, Util.getScaledHeight(opt.outHeight, opt.outWidth, size)); + opt.inJustDecodeBounds = false; + Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opt); + if(bitmap == null) { + return null; + } else { + return getScaledBitmap(bitmap, size, allowUnscaled); + } + } + public static Bitmap getScaledBitmap(Bitmap bitmap, int size) { + return getScaledBitmap(bitmap, size, true); + } + public static Bitmap getScaledBitmap(Bitmap bitmap, int size, boolean allowUnscaled) { + // Don't waste time scaling if the difference is minor + // Large album arts still need to be scaled since displayed as is on now playing! + if(allowUnscaled && size < 400 && bitmap.getWidth() < (size * 1.1)) { + return bitmap; + } else { + return Bitmap.createScaledBitmap(bitmap, size, Util.getScaledHeight(bitmap, size), true); + } + } + + public static File getAlbumArtDirectory(Context context) { + File albumArtDir = new File(getSubsonicDirectory(context), "artwork"); + ensureDirectoryExistsAndIsReadWritable(albumArtDir); + ensureDirectoryExistsAndIsReadWritable(new File(albumArtDir, ".nomedia")); + return albumArtDir; + } + + public static File getArtistDirectory(Context context, Artist artist) { + File dir = new File(getMusicDirectory(context).getPath() + "/" + fileSystemSafe(artist.getName())); + return dir; + } + public static File getArtistDirectory(Context context, MusicDirectory.Entry artist) { + File dir = new File(getMusicDirectory(context).getPath() + "/" + fileSystemSafe(artist.getTitle())); + return dir; + } + + public static File getAlbumDirectory(Context context, MusicDirectory.Entry entry) { + File dir = null; + if (entry.getPath() != null) { + File f = new File(fileSystemSafeDir(entry.getPath())); + dir = new File(getMusicDirectory(context).getPath() + "/" + (entry.isDirectory() ? f.getPath() : f.getParent())); + } else { + MusicDirectory.Entry firstSong; + if(!Util.isOffline(context)) { + firstSong = lookupChild(context, entry, false); + if(firstSong != null) { + File songFile = FileUtil.getSongFile(context, firstSong); + dir = songFile.getParentFile(); + } + } + + if(dir == null) { + String artist = fileSystemSafe(entry.getArtist()); + String album = fileSystemSafe(entry.getAlbum()); + if("unnamed".equals(album)) { + album = fileSystemSafe(entry.getTitle()); + } + dir = new File(getMusicDirectory(context).getPath() + "/" + artist + "/" + album); + } + } + return dir; + } + + public static MusicDirectory.Entry lookupChild(Context context, MusicDirectory.Entry entry, boolean allowDir) { + // Initialize lookupMap if first time called + String lookupName = Util.getCacheName(context, "entryLookup"); + if(entryLookup == null) { + entryLookup = deserialize(context, lookupName, HashMap.class); + + // Create it if + if(entryLookup == null) { + entryLookup = new HashMap<String, MusicDirectory.Entry>(); + } + } + + // Check if this lookup has already been done before + MusicDirectory.Entry child = entryLookup.get(entry.getId()); + if(child != null) { + return child; + } + + // Do a special lookup since 4.7+ doesn't match artist/album to entry.getPath + String s = Util.getRestUrl(context, null, false) + entry.getId(); + String cacheName = (Util.isTagBrowsing(context) ? "album-" : "directory-") + s.hashCode() + ".ser"; + MusicDirectory entryDir = FileUtil.deserialize(context, cacheName, MusicDirectory.class); + + if(entryDir != null) { + List<MusicDirectory.Entry> songs = entryDir.getChildren(allowDir, true); + if(songs.size() > 0) { + child = songs.get(0); + entryLookup.put(entry.getId(), child); + serialize(context, entryLookup, lookupName); + return child; + } + } + + return null; + } + + public static String getPodcastPath(Context context, PodcastEpisode episode) { + return fileSystemSafe(episode.getArtist()) + "/" + fileSystemSafe(episode.getTitle()); + } + public static File getPodcastFile(Context context, String server) { + File dir = getPodcastDirectory(context); + return new File(dir.getPath() + "/" + fileSystemSafe(server)); + } + public static File getPodcastDirectory(Context context) { + File dir = new File(context.getCacheDir(), "podcasts"); + ensureDirectoryExistsAndIsReadWritable(dir); + return dir; + } + public static File getPodcastDirectory(Context context, PodcastChannel channel) { + File dir = new File(getMusicDirectory(context).getPath() + "/" + fileSystemSafe(channel.getName())); + return dir; + } + public static File getPodcastDirectory(Context context, String channel) { + File dir = new File(getMusicDirectory(context).getPath() + "/" + fileSystemSafe(channel)); + return dir; + } + + public static void createDirectoryForParent(File file) { + File dir = file.getParentFile(); + if (!dir.exists()) { + if (!dir.mkdirs()) { + Log.e(TAG, "Failed to create directory " + dir); + } + } + } + + private static File createDirectory(Context context, String name) { + File dir = new File(getSubsonicDirectory(context), name); + if (!dir.exists() && !dir.mkdirs()) { + Log.e(TAG, "Failed to create " + name); + } + return dir; + } + + public static File getSubsonicDirectory(Context context) { + return context.getExternalFilesDir(null); + } + + public static File getDefaultMusicDirectory(Context context) { + if(DEFAULT_MUSIC_DIR == null) { + File[] dirs; + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + dirs = context.getExternalMediaDirs(); + } else { + dirs = ContextCompat.getExternalFilesDirs(context, null); + } + + DEFAULT_MUSIC_DIR = new File(getBestDir(dirs), "music"); + Log.d(TAG, "Default: " + DEFAULT_MUSIC_DIR.getAbsolutePath()); + + if (!DEFAULT_MUSIC_DIR.exists() && !DEFAULT_MUSIC_DIR.mkdirs()) { + Log.e(TAG, "Failed to create default dir " + DEFAULT_MUSIC_DIR); + + // Some devices seem to have screwed up the new media directory API. Go figure. Try again with standard locations + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + dirs = ContextCompat.getExternalFilesDirs(context, null); + + DEFAULT_MUSIC_DIR = new File(getBestDir(dirs), "music"); + if (!DEFAULT_MUSIC_DIR.exists() && !DEFAULT_MUSIC_DIR.mkdirs()) { + Log.e(TAG, "Failed to create default dir " + DEFAULT_MUSIC_DIR); + } else { + Log.w(TAG, "Stupid OEM's messed up media dir API added in 5.0"); + } + } + } + } + + return DEFAULT_MUSIC_DIR; + } + private static File getBestDir(File[] dirs) { + // Past 5.0 we can query directly for SD Card + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + for(int i = 0; i < dirs.length; i++) { + if(dirs[i] != null && Environment.isExternalStorageRemovable(dirs[i])) { + return dirs[i]; + } + } + } + + // Before 5.0, we have to guess. Most of the time the SD card is last + for(int i = dirs.length - 1; i >= 0; i--) { + if(dirs[i] != null) { + return dirs[i]; + } + } + + // Should be impossible to be reached + return dirs[0]; + } + + public static File getMusicDirectory(Context context) { + String path = Util.getPreferences(context).getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, getDefaultMusicDirectory(context).getPath()); + File dir = new File(path); + return ensureDirectoryExistsAndIsReadWritable(dir) ? dir : getDefaultMusicDirectory(context); + } + public static boolean deleteMusicDirectory(Context context) { + File musicDirectory = FileUtil.getMusicDirectory(context); + MediaStoreService mediaStore = new MediaStoreService(context); + return recursiveDelete(musicDirectory, mediaStore); + } + public static void deleteSerializedCache(Context context) { + for(File file: context.getCacheDir().listFiles()) { + if(file.getName().indexOf(".ser") != -1) { + file.delete(); + } + } + } + public static boolean deleteArtworkCache(Context context) { + File artDirectory = FileUtil.getAlbumArtDirectory(context); + return recursiveDelete(artDirectory); + } + public static boolean deleteAvatarCache(Context context) { + File artDirectory = FileUtil.getAvatarDirectory(context); + return recursiveDelete(artDirectory); + } + + public static boolean recursiveDelete(File dir) { + return recursiveDelete(dir, null); + } + public static boolean recursiveDelete(File dir, MediaStoreService mediaStore) { + if (dir != null && dir.exists()) { + File[] list = dir.listFiles(); + if(list != null) { + for(File file: list) { + if(file.isDirectory()) { + if(!recursiveDelete(file, mediaStore)) { + return false; + } + } else if(file.exists()) { + if(!file.delete()) { + return false; + } else if(mediaStore != null) { + mediaStore.deleteFromMediaStore(file); + } + } + } + } + return dir.delete(); + } + return false; + } + + public static void deleteEmptyDir(File dir) { + try { + File[] children = dir.listFiles(); + if(children == null) { + return; + } + + // No songs left in the folder + if (children.length == 1 && children[0].getPath().equals(FileUtil.getAlbumArtFile(dir).getPath())) { + Util.delete(children[0]); + children = dir.listFiles(); + } + + // Delete empty directory + if (children.length == 0) { + Util.delete(dir); + } + } catch(Exception e) { + Log.w(TAG, "Error while trying to delete empty dir", e); + } + } + + public static void unpinSong(Context context, File saveFile) { + // Don't try to unpin a song which isn't actually pinned + if(saveFile.getName().contains(".complete")) { + return; + } + + // Unpin file, rename to .complete + File completeFile = new File(saveFile.getParent(), FileUtil.getBaseName(saveFile.getName()) + + ".complete." + FileUtil.getExtension(saveFile.getName())); + + if(!saveFile.renameTo(completeFile)) { + Log.w(TAG, "Failed to upin " + saveFile + " to " + completeFile); + } else { + try { + new MediaStoreService(context).renameInMediaStore(completeFile, saveFile); + } catch(Exception e) { + Log.w(TAG, "Failed to write to media store"); + } + } + } + + public static boolean ensureDirectoryExistsAndIsReadWritable(File dir) { + if (dir == null) { + return false; + } + + if (dir.exists()) { + if (!dir.isDirectory()) { + Log.w(TAG, dir + " exists but is not a directory."); + return false; + } + } else { + if (dir.mkdirs()) { + Log.i(TAG, "Created directory " + dir); + } else { + Log.w(TAG, "Failed to create directory " + dir); + return false; + } + } + + if (!dir.canRead()) { + Log.w(TAG, "No read permission for directory " + dir); + return false; + } + + if (!dir.canWrite()) { + Log.w(TAG, "No write permission for directory " + dir); + return false; + } + return true; + } + public static boolean verifyCanWrite(File dir) { + if(ensureDirectoryExistsAndIsReadWritable(dir)) { + try { + File tmp = new File(dir, "checkWrite"); + tmp.createNewFile(); + if(tmp.exists()) { + if(tmp.delete()) { + return true; + } else { + Log.w(TAG, "Failed to delete temp file, retrying"); + + // This should never be reached since this is a file DSub created! + Thread.sleep(100L); + tmp = new File(dir, "checkWrite"); + if(tmp.delete()) { + return true; + } else { + Log.w(TAG, "Failed retry to delete temp file"); + return false; + } + } + } else { + Log.w(TAG, "Temp file does not actually exist"); + return false; + } + } catch(Exception e) { + Log.w(TAG, "Failed to create tmp file", e); + return false; + } + } else { + return false; + } + } + + /** + * 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().length() == 0) { + 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().length() == 0) { + 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<File> listFiles(File dir) { + File[] files = dir.listFiles(); + if (files == null) { + Log.w(TAG, "Failed to list children for " + dir.getPath()); + return new TreeSet<File>(); + } + + return new TreeSet<File>(Arrays.asList(files)); + } + + public static SortedSet<File> listMediaFiles(File dir) { + SortedSet<File> files = listFiles(dir); + Iterator<File> 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 isMusicFile(File file) { + String extension = getExtension(file.getName()); + return MUSIC_FILE_EXTENSIONS.contains(extension); + } + public static boolean isVideoFile(File file) { + String extension = getExtension(file.getName()); + return 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); + } + + public static Pair<Long, Long> getUsedSize(Context context, File file) { + long number = 0L; + long size = 0L; + + if(file.isFile()) { + if(isMediaFile(file)) { + return new Pair<Long, Long>(1L, file.length()); + } else { + return new Pair<Long, Long>(0L, 0L); + } + } else { + for (File child : FileUtil.listFiles(file)) { + Pair<Long, Long> pair = getUsedSize(context, child); + number += pair.getFirst(); + size += pair.getSecond(); + } + return new Pair<Long, Long>(number, size); + } + } + + public static <T extends Serializable> boolean serialize(Context context, T obj, String fileName) { + Output out = null; + try { + RandomAccessFile file = new RandomAccessFile(context.getCacheDir() + "/" + fileName, "rw"); + out = new Output(new FileOutputStream(file.getFD())); + synchronized (kryo) { + kryo.writeObject(out, obj); + } + return true; + } catch (Throwable x) { + Log.w(TAG, "Failed to serialize object to " + fileName); + return false; + } finally { + Util.close(out); + } + } + + public static <T extends Serializable> T deserialize(Context context, String fileName, Class<T> tClass) { + return deserialize(context, fileName, tClass, 0); + } + + public static <T extends Serializable> T deserialize(Context context, String fileName, Class<T> tClass, int hoursOld) { + Input in = null; + try { + File file = new File(context.getCacheDir(), fileName); + if(!file.exists()) { + return null; + } + + if(hoursOld != 0) { + Date fileDate = new Date(file.lastModified()); + // Convert into hours + long age = (new Date().getTime() - fileDate.getTime()) / 1000 / 3600; + if(age > hoursOld) { + return null; + } + } + + RandomAccessFile randomFile = new RandomAccessFile(file, "r"); + + in = new Input(new FileInputStream(randomFile.getFD())); + synchronized (kryo) { + T result = kryo.readObject(in, tClass); + return result; + } + } catch(FileNotFoundException e) { + // Different error message + Log.w(TAG, "No serialization for object from " + fileName); + return null; + } catch (Throwable x) { + Log.w(TAG, "Failed to deserialize object from " + fileName); + return null; + } finally { + Util.close(in); + } + } + + public static <T extends Serializable> boolean serializeCompressed(Context context, T obj, String fileName) { + Output out = null; + try { + RandomAccessFile file = new RandomAccessFile(context.getCacheDir() + "/" + fileName, "rw"); + out = new Output(new DeflaterOutputStream(new FileOutputStream(file.getFD()))); + synchronized (kryo) { + kryo.writeObject(out, obj); + } + return true; + } catch (Throwable x) { + Log.w(TAG, "Failed to serialize compressed object to " + fileName); + return false; + } finally { + Util.close(out); + } + } + + @TargetApi(Build.VERSION_CODES.GINGERBREAD) + public static <T extends Serializable> T deserializeCompressed(Context context, String fileName, Class<T> tClass) { + Input in = null; + try { + RandomAccessFile file = new RandomAccessFile(context.getCacheDir() + "/" + fileName, "r"); + + in = new Input(new InflaterInputStream(new FileInputStream(file.getFD()))); + synchronized (kryo) { + T result = kryo.readObject(in, tClass); + return result; + } + } catch(FileNotFoundException e) { + // Different error message + Log.w(TAG, "No serialization compressed for object from " + fileName); + return null; + } catch (Throwable x) { + Log.w(TAG, "Failed to deserialize compressed object from " + fileName); + return null; + } finally { + Util.close(in); + } + } +} |