From b69ff68d7cc07dd96ee9c51c31c60fdd166ffb47 Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Thu, 12 Feb 2015 02:27:33 -0500 Subject: [PATCH] Added methods to delete songs and song groups. - OsuGroupList.get().deleteSongGroup() will delete a song group from the list. - OsuGroupList.get().deleteSong() will delete an individual song from the list (must be expanded), and will delete the song group if there are no remaining songs in the group. - If possible, deleted beatmap files are moved to system trash (using JNA); otherwise, they are deleted immediately. This is done through Utils.deleteToTrash(). Signed-off-by: Jeffrey Han --- pom.xml | 10 ++ src/itdelatrisu/opsu/OsuFile.java | 2 +- src/itdelatrisu/opsu/OsuGroupList.java | 136 +++++++++++++++++++++- src/itdelatrisu/opsu/OsuParser.java | 5 +- src/itdelatrisu/opsu/OszUnpacker.java | 2 +- src/itdelatrisu/opsu/Utils.java | 59 ++++++++++ src/itdelatrisu/opsu/states/SongMenu.java | 4 + 7 files changed, 211 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 5e8012dc..1ec51ac6 100644 --- a/pom.xml +++ b/pom.xml @@ -143,5 +143,15 @@ json 20140107 + + net.java.dev.jna + jna + 4.1.0 + + + net.java.dev.jna + jna-platform + 4.1.0 + diff --git a/src/itdelatrisu/opsu/OsuFile.java b/src/itdelatrisu/opsu/OsuFile.java index 01c1a9f9..39d888f7 100644 --- a/src/itdelatrisu/opsu/OsuFile.java +++ b/src/itdelatrisu/opsu/OsuFile.java @@ -262,9 +262,9 @@ public class OsuFile implements Comparable { try { Image bgImage = bgImageMap.get(this); if (bgImage == null) { - bgImage = new Image(bg); if (bgImageMap.size() > MAX_CACHE_SIZE) clearImageCache(); + bgImage = new Image(bg); bgImageMap.put(this, bgImage); } diff --git a/src/itdelatrisu/opsu/OsuGroupList.java b/src/itdelatrisu/opsu/OsuGroupList.java index ce95ebc6..d3375134 100644 --- a/src/itdelatrisu/opsu/OsuGroupList.java +++ b/src/itdelatrisu/opsu/OsuGroupList.java @@ -18,6 +18,10 @@ package itdelatrisu.opsu; +import itdelatrisu.opsu.audio.MusicController; + +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -54,6 +58,9 @@ public class OsuGroupList { /** Index of current expanded node (-1 if no node is expanded). */ private int expandedIndex; + /** Start and end nodes of expanded group. */ + private OsuGroupNode expandedStartNode, expandedEndNode; + /** The last search query. */ private String lastQuery; @@ -83,6 +90,7 @@ public class OsuGroupList { public void reset() { nodes = parsedNodes; expandedIndex = -1; + expandedStartNode = expandedEndNode = null; lastQuery = ""; } @@ -109,6 +117,123 @@ public class OsuGroupList { return node; } + /** + * Deletes a song group from the list, and also deletes the beatmap + * directory associated with the node. + * @param node the node containing the song group to delete + * @return true if the song group was deleted, false otherwise + */ + public boolean deleteSongGroup(OsuGroupNode node) { + if (node == null) + return false; + + // re-link base nodes + int index = node.index; + OsuGroupNode ePrev = getBaseNode(index - 1), eCur = getBaseNode(index), eNext = getBaseNode(index + 1); + if (ePrev != null) { + if (ePrev.index == expandedIndex) + expandedEndNode.next = eNext; + else if (eNext != null && eNext.index == expandedIndex) + ePrev.next = expandedStartNode; + else + ePrev.next = eNext; + } + if (eNext != null) { + if (eNext.index == expandedIndex) + expandedStartNode.prev = ePrev; + else if (ePrev != null && ePrev.index == expandedIndex) + eNext.prev = expandedEndNode; + else + eNext.prev = ePrev; + } + + // remove all node references + OsuFile osu = node.osuFiles.get(0); + nodes.remove(index); + parsedNodes.remove(eCur); + mapCount -= node.osuFiles.size(); + if (osu.beatmapSetID > 0) + MSIDdb.remove(osu.beatmapSetID); + + // reset indices + for (int i = index, size = size(); i < size; i++) + nodes.get(i).index = i; + if (index == expandedIndex) { + expandedIndex = -1; + expandedStartNode = expandedEndNode = null; + } else if (expandedIndex > index) { + expandedIndex--; + OsuGroupNode expandedNode = expandedStartNode; + for (int i = 0, size = expandedNode.osuFiles.size(); + i < size && expandedNode != null; + i++, expandedNode = expandedNode.next) + expandedNode.index = expandedIndex; + } + + // stop playing the track + File dir = osu.getFile().getParentFile(); + if (MusicController.trackExists() || MusicController.isTrackLoading()) { + File audioFile = MusicController.getOsuFile().audioFilename; + if (audioFile != null && audioFile.equals(osu.audioFilename)) { + MusicController.reset(); + System.gc(); // TODO: why can't files be deleted without calling this? + } + } + + // delete the associated directory + try { + Utils.deleteToTrash(dir); + } catch (IOException e) { + ErrorHandler.error("Could not delete song group.", e, true); + } + + return true; + } + + /** + * Deletes a song from a song group, and also deletes the beatmap file. + * If this causes the song group to be empty, then the song group and + * beatmap directory will be deleted altogether. + * @param node the node containing the song group to delete (expanded only) + * @return true if the song or song group was deleted, false otherwise + * @see #deleteSongGroup(OsuGroupNode) + */ + public boolean deleteSong(OsuGroupNode node) { + if (node == null || node.osuFileIndex == -1 || node.index != expandedIndex) + return false; + + // last song in group? + int size = node.osuFiles.size(); + if (node.osuFiles.size() == 1) + return deleteSongGroup(node); + + // reset indices + OsuGroupNode expandedNode = node.next; + for (int i = node.osuFileIndex + 1; + i < size && expandedNode != null && expandedNode.index == node.index; + i++, expandedNode = expandedNode.next) + expandedNode.osuFileIndex--; + + // remove song reference + OsuFile osu = node.osuFiles.remove(node.osuFileIndex); + mapCount--; + + // re-link nodes + if (node.prev != null) + node.prev.next = node.next; + if (node.next != null) + node.next.prev = node.prev; + + // delete the associated file + try { + Utils.deleteToTrash(osu.getFile()); + } catch (IOException e) { + ErrorHandler.error("Could not delete song.", e, true); + } + + return true; + } + /** * Returns the total number of parsed maps (i.e. OsuFile objects). */ @@ -169,13 +294,13 @@ public class OsuGroupList { if (node == null) return null; - OsuGroupNode firstInserted = null; + expandedStartNode = expandedEndNode = null; // create new nodes ArrayList osuFiles = node.osuFiles; OsuGroupNode prevNode = node.prev; OsuGroupNode nextNode = node.next; - for (int i = 0; i < node.osuFiles.size(); i++) { + for (int i = 0, size = node.osuFiles.size(); i < size; i++) { OsuGroupNode newNode = new OsuGroupNode(osuFiles); newNode.index = index; newNode.osuFileIndex = i; @@ -183,7 +308,7 @@ public class OsuGroupList { // unlink the group node if (i == 0) { - firstInserted = newNode; + expandedStartNode = newNode; newNode.prev = prevNode; if (prevNode != null) prevNode.next = newNode; @@ -196,9 +321,10 @@ public class OsuGroupList { node.next = nextNode; nextNode.prev = node; } + expandedEndNode = node; expandedIndex = index; - return firstInserted; + return expandedStartNode; } /** @@ -222,6 +348,7 @@ public class OsuGroupList { eNext.prev = eCur; expandedIndex = -1; + expandedStartNode = expandedEndNode = null; return; } @@ -235,6 +362,7 @@ public class OsuGroupList { // sort the list Collections.sort(nodes, SongSort.getSort().getComparator()); expandedIndex = -1; + expandedStartNode = expandedEndNode = null; // create links OsuGroupNode lastNode = nodes.get(0); diff --git a/src/itdelatrisu/opsu/OsuParser.java b/src/itdelatrisu/opsu/OsuParser.java index 5c424a7e..08999f98 100644 --- a/src/itdelatrisu/opsu/OsuParser.java +++ b/src/itdelatrisu/opsu/OsuParser.java @@ -72,9 +72,12 @@ public class OsuParser { * Invokes parser for each directory in the given array and * adds the OsuFiles to the existing OsuGroupList. * @param dirs the array of directories to parse - * @return the last OsuGroupNode parsed + * @return the last OsuGroupNode parsed, or null if none */ public static OsuGroupNode parseDirectories(File[] dirs) { + if (dirs == null) + return null; + // progress tracking currentDirectoryIndex = 0; totalDirectories = dirs.length; diff --git a/src/itdelatrisu/opsu/OszUnpacker.java b/src/itdelatrisu/opsu/OszUnpacker.java index 491ffaaa..b572de50 100644 --- a/src/itdelatrisu/opsu/OszUnpacker.java +++ b/src/itdelatrisu/opsu/OszUnpacker.java @@ -56,7 +56,7 @@ public class OszUnpacker { return name.toLowerCase().endsWith(".osz"); } }); - if (files.length < 1) { + if (files == null || files.length < 1) { files = null; return new File[0]; } diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index 6e2a1e43..9984e6fb 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -25,6 +25,7 @@ import itdelatrisu.opsu.downloads.DownloadNode; import java.awt.Font; import java.awt.image.BufferedImage; import java.io.File; +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.text.SimpleDateFormat; @@ -55,6 +56,8 @@ import org.newdawn.slick.state.StateBasedGame; import org.newdawn.slick.util.Log; import org.newdawn.slick.util.ResourceLoader; +import com.sun.jna.platform.FileUtils; + /** * Contains miscellaneous utilities. */ @@ -738,4 +741,60 @@ public class Utils { } return cleanName.toString(); } + + /** + * Deletes a file or directory. If a system trash directory is available, + * the file or directory will be moved there instead. + * @param file the file or directory to delete + * @return true if moved to trash, and false if deleted + * @throws IOException if given file does not exist + */ + public static boolean deleteToTrash(File file) throws IOException { + if (file == null) + throw new IOException("File cannot be null."); + if (!file.exists()) + throw new IOException(String.format("File '%s' does not exist.", file.getAbsolutePath())); + + // move to system trash, if possible + FileUtils fileUtils = FileUtils.getInstance(); + if (fileUtils.hasTrash()) { + try { + fileUtils.moveToTrash(new File[] { file }); + return true; + } catch (IOException e) { + Log.warn(String.format("Failed to move file '%s' to trash.", file.getAbsolutePath()), e); + } + } + + // delete otherwise + if (file.isDirectory()) + deleteDirectory(file); + else + file.delete(); + return false; + } + + /** + * Recursively deletes all files and folders in a directory, then + * deletes the directory itself. + * @param dir the directory to delete + */ + private static void deleteDirectory(File dir) { + if (dir == null || !dir.isDirectory()) + return; + + // recursively delete contents of directory + File[] files = dir.listFiles(); + if (files != null && files.length > 0) { + for (File file : files) { + if (file.isDirectory()) + deleteDirectory(file); + else + file.delete(); + } + } + + // delete the directory + dir.delete(); + } } \ No newline at end of file diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index c7dc8ef7..5a1ee591 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -396,6 +396,8 @@ public class SongMenu extends BasicGameState { // search produced new list: re-initialize it startNode = focusNode = null; + scoreMap = null; + focusScores = null; if (OsuGroupList.get().size() > 0) { OsuGroupList.get().init(); if (search.getText().isEmpty()) { // cleared search @@ -606,6 +608,8 @@ public class SongMenu extends BasicGameState { // reset state and node references MusicController.reset(); startNode = focusNode = null; + scoreMap = null; + focusScores = null; oldFocusNode = null; randomStack = new Stack(); songInfo = null;