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;