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 <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han 2015-02-12 02:27:33 -05:00
parent 484bb1ba31
commit b69ff68d7c
7 changed files with 211 additions and 7 deletions

10
pom.xml
View File

@ -143,5 +143,15 @@
<artifactId>json</artifactId>
<version>20140107</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>4.1.0</version>
</dependency>
</dependencies>
</project>

View File

@ -262,9 +262,9 @@ public class OsuFile implements Comparable<OsuFile> {
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);
}

View File

@ -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<OsuFile> 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);

View File

@ -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;

View File

@ -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];
}

View File

@ -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();
}
}

View File

@ -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<SongNode>();
songInfo = null;