Merge remote-tracking branch 'org/master' into KinecticScrolling
Conflicts: src/itdelatrisu/opsu/ScoreData.java src/itdelatrisu/opsu/downloads/DownloadNode.java src/itdelatrisu/opsu/states/DownloadsMenu.java src/itdelatrisu/opsu/states/SongMenu.java
This commit is contained in:
@@ -46,6 +46,9 @@ public class Download {
|
||||
/** Read timeout, in ms. */
|
||||
public static final int READ_TIMEOUT = 10000;
|
||||
|
||||
/** Maximum number of HTTP/HTTPS redirects to follow. */
|
||||
public static final int MAX_REDIRECTS = 3;
|
||||
|
||||
/** Time between download speed and ETA updates, in ms. */
|
||||
private static final int UPDATE_INTERVAL = 1000;
|
||||
|
||||
@@ -58,7 +61,7 @@ public class Download {
|
||||
ERROR ("Error");
|
||||
|
||||
/** The status name. */
|
||||
private String name;
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -172,13 +175,57 @@ public class Download {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
// open connection, get content length
|
||||
// open connection
|
||||
HttpURLConnection conn = null;
|
||||
try {
|
||||
conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setConnectTimeout(CONNECTION_TIMEOUT);
|
||||
conn.setReadTimeout(READ_TIMEOUT);
|
||||
conn.setUseCaches(false);
|
||||
URL downloadURL = url;
|
||||
int redirectCount = 0;
|
||||
boolean isRedirect = false;
|
||||
do {
|
||||
isRedirect = false;
|
||||
|
||||
conn = (HttpURLConnection) downloadURL.openConnection();
|
||||
conn.setConnectTimeout(CONNECTION_TIMEOUT);
|
||||
conn.setReadTimeout(READ_TIMEOUT);
|
||||
conn.setUseCaches(false);
|
||||
|
||||
// allow HTTP <--> HTTPS redirects
|
||||
// http://download.java.net/jdk7u2/docs/technotes/guides/deployment/deployment-guide/upgrade-guide/article-17.html
|
||||
conn.setInstanceFollowRedirects(false);
|
||||
conn.setRequestProperty("User-Agent", "Mozilla/5.0...");
|
||||
|
||||
// check for redirect
|
||||
int status = conn.getResponseCode();
|
||||
if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM ||
|
||||
status == HttpURLConnection.HTTP_SEE_OTHER || status == HttpURLConnection.HTTP_USE_PROXY) {
|
||||
URL base = conn.getURL();
|
||||
String location = conn.getHeaderField("Location");
|
||||
URL target = null;
|
||||
if (location != null)
|
||||
target = new URL(base, location);
|
||||
conn.disconnect();
|
||||
|
||||
// check for problems
|
||||
String error = null;
|
||||
if (location == null)
|
||||
error = String.format("Download for URL '%s' is attempting to redirect without a 'location' header.", base.toString());
|
||||
else if (!target.getProtocol().equals("http") && !target.getProtocol().equals("https"))
|
||||
error = String.format("Download for URL '%s' is attempting to redirect to a non-HTTP/HTTPS protocol '%s'.", base.toString(), target.getProtocol());
|
||||
else if (redirectCount > MAX_REDIRECTS)
|
||||
error = String.format("Download for URL '%s' is attempting too many redirects (over %d).", base.toString(), MAX_REDIRECTS);
|
||||
if (error != null) {
|
||||
ErrorHandler.error(error, null, false);
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
// follow redirect
|
||||
downloadURL = target;
|
||||
redirectCount++;
|
||||
isRedirect = true;
|
||||
}
|
||||
} while (isRedirect);
|
||||
|
||||
// store content length
|
||||
contentLength = conn.getContentLength();
|
||||
} catch (IOException e) {
|
||||
status = Status.ERROR;
|
||||
@@ -198,9 +245,18 @@ public class Download {
|
||||
fos = fileOutputStream;
|
||||
status = Status.DOWNLOADING;
|
||||
updateReadSoFar();
|
||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||
long bytesRead = fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||
if (status == Status.DOWNLOADING) { // not interrupted
|
||||
// TODO: if connection is lost before a download finishes, it's still marked as "complete"
|
||||
// check if the entire file was received
|
||||
if (bytesRead < contentLength) {
|
||||
status = Status.ERROR;
|
||||
Log.warn(String.format("Download '%s' failed: %d bytes expected, %d bytes received.", url.toString(), contentLength, bytesRead));
|
||||
if (listener != null)
|
||||
listener.error();
|
||||
return;
|
||||
}
|
||||
|
||||
// mark download as complete
|
||||
status = Status.COMPLETE;
|
||||
rbc.close();
|
||||
fos.close();
|
||||
@@ -273,7 +329,7 @@ public class Download {
|
||||
public long readSoFar() {
|
||||
switch (status) {
|
||||
case COMPLETE:
|
||||
return contentLength;
|
||||
return (rbc != null) ? rbc.getReadSoFar() : contentLength;
|
||||
case DOWNLOADING:
|
||||
if (rbc != null)
|
||||
return rbc.getReadSoFar();
|
||||
|
||||
@@ -75,7 +75,7 @@ public class DownloadList {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the doownloads list.
|
||||
* Returns the size of the downloads list.
|
||||
*/
|
||||
public int size() { return nodes.size(); }
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
||||
import itdelatrisu.opsu.downloads.Download.DownloadListener;
|
||||
import itdelatrisu.opsu.downloads.Download.Status;
|
||||
import itdelatrisu.opsu.downloads.servers.DownloadServer;
|
||||
import itdelatrisu.opsu.ui.Colors;
|
||||
import itdelatrisu.opsu.ui.Fonts;
|
||||
import itdelatrisu.opsu.ui.UI;
|
||||
|
||||
import java.io.File;
|
||||
@@ -42,19 +44,19 @@ public class DownloadNode {
|
||||
private Download download;
|
||||
|
||||
/** Beatmap set ID. */
|
||||
private int beatmapSetID;
|
||||
private final int beatmapSetID;
|
||||
|
||||
/** Last updated date string. */
|
||||
private String date;
|
||||
private final String date;
|
||||
|
||||
/** Song title. */
|
||||
private String title, titleUnicode;
|
||||
private final String title, titleUnicode;
|
||||
|
||||
/** Song artist. */
|
||||
private String artist, artistUnicode;
|
||||
private final String artist, artistUnicode;
|
||||
|
||||
/** Beatmap creator. */
|
||||
private String creator;
|
||||
private final String creator;
|
||||
|
||||
/** Button drawing values. */
|
||||
private static float buttonBaseX, buttonBaseY, buttonWidth, buttonHeight, buttonOffset;
|
||||
@@ -68,12 +70,6 @@ public class DownloadNode {
|
||||
/** Container width. */
|
||||
private static int containerWidth;
|
||||
|
||||
/** Button background colors. */
|
||||
public static final Color
|
||||
BG_NORMAL = new Color(0, 0, 0, 0.25f),
|
||||
BG_HOVER = new Color(0, 0, 0, 0.5f),
|
||||
BG_FOCUS = new Color(0, 0, 0, 0.75f);
|
||||
|
||||
/**
|
||||
* Initializes the base coordinates for drawing.
|
||||
* @param width the container width
|
||||
@@ -86,16 +82,16 @@ public class DownloadNode {
|
||||
buttonBaseX = width * 0.024f;
|
||||
buttonBaseY = height * 0.2f;
|
||||
buttonWidth = width * 0.7f;
|
||||
buttonHeight = Utils.FONT_MEDIUM.getLineHeight() * 2.1f;
|
||||
buttonHeight = Fonts.MEDIUM.getLineHeight() * 2.1f;
|
||||
buttonOffset = buttonHeight * 1.1f;
|
||||
|
||||
// download info
|
||||
infoBaseX = width * 0.75f;
|
||||
infoBaseY = height * 0.07f + Utils.FONT_LARGE.getLineHeight() * 2f;
|
||||
infoBaseY = height * 0.07f + Fonts.LARGE.getLineHeight() * 2f;
|
||||
infoWidth = width * 0.25f;
|
||||
infoHeight = Utils.FONT_DEFAULT.getLineHeight() * 2.4f;
|
||||
infoHeight = Fonts.DEFAULT.getLineHeight() * 2.4f;
|
||||
|
||||
float searchY = (height * 0.05f) + Utils.FONT_LARGE.getLineHeight();
|
||||
float searchY = (height * 0.05f) + Fonts.LARGE.getLineHeight();
|
||||
float buttonHeight = height * 0.038f;
|
||||
maxResultsShown = (int) ((height - buttonBaseY - searchY) / buttonOffset);
|
||||
maxDownloadsShown = (int) ((height - infoBaseY - searchY - buttonHeight) / infoHeight);
|
||||
@@ -228,10 +224,9 @@ public class DownloadNode {
|
||||
* @param total the total number of buttons
|
||||
*/
|
||||
public static void drawResultScrollbar(Graphics g, float position, float total) {
|
||||
UI.drawScrollbar(g, position, total, maxResultsShown * buttonOffset,
|
||||
buttonBaseX, buttonBaseY,
|
||||
UI.drawScrollbar(g, position, total, maxResultsShown * buttonOffset, buttonBaseX, buttonBaseY,
|
||||
buttonWidth * 1.01f, (maxResultsShown-1) * buttonOffset + buttonHeight,
|
||||
BG_NORMAL, Color.white, true);
|
||||
Colors.BLACK_BG_NORMAL, Color.white, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,11 +237,18 @@ public class DownloadNode {
|
||||
*/
|
||||
public static void drawDownloadScrollbar(Graphics g, float index, float total) {
|
||||
UI.drawScrollbar(g, index, total, maxDownloadsShown * infoHeight, infoBaseX, infoBaseY,
|
||||
infoWidth, maxDownloadsShown * infoHeight, BG_NORMAL, Color.white, true);
|
||||
infoWidth, maxDownloadsShown * infoHeight, Colors.BLACK_BG_NORMAL, Color.white, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param beatmapSetID the beatmap set ID
|
||||
* @param date the last modified date string
|
||||
* @param title the song title
|
||||
* @param titleUnicode the Unicode song title (or {@code null} if none)
|
||||
* @param artist the song artist
|
||||
* @param artistUnicode the Unicode song artist (or {@code null} if none)
|
||||
* @param creator the beatmap creator
|
||||
*/
|
||||
public DownloadNode(int beatmapSetID, String date, String title,
|
||||
String titleUnicode, String artist, String artistUnicode, String creator) {
|
||||
@@ -273,7 +275,7 @@ public class DownloadNode {
|
||||
return;
|
||||
String path = String.format("%s%c%d", Options.getOSZDir(), File.separatorChar, beatmapSetID);
|
||||
String rename = String.format("%d %s - %s.osz", beatmapSetID, artist, title);
|
||||
this.download = new Download(url, path, rename);
|
||||
Download download = new Download(url, path, rename);
|
||||
download.setListener(new DownloadListener() {
|
||||
@Override
|
||||
public void completed() {
|
||||
@@ -285,8 +287,9 @@ public class DownloadNode {
|
||||
UI.sendBarNotification("Download failed due to a connection error.");
|
||||
}
|
||||
});
|
||||
this.download = download;
|
||||
if (Options.useUnicodeMetadata()) // load glyphs
|
||||
Utils.loadGlyphs(Utils.FONT_LARGE, getTitle(), null);
|
||||
Fonts.loadGlyphs(Fonts.LARGE, getTitle());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -348,12 +351,12 @@ public class DownloadNode {
|
||||
Download dl = DownloadList.get().getDownload(beatmapSetID);
|
||||
|
||||
// rectangle outline
|
||||
g.setColor((focus) ? BG_FOCUS : (hover) ? BG_HOVER : BG_NORMAL);
|
||||
g.setColor((focus) ? Colors.BLACK_BG_FOCUS : (hover) ? Colors.BLACK_BG_HOVER : Colors.BLACK_BG_NORMAL);
|
||||
g.fillRect(buttonBaseX, y, buttonWidth, buttonHeight);
|
||||
|
||||
// map is already loaded
|
||||
if (BeatmapSetList.get().containsBeatmapSetID(beatmapSetID)) {
|
||||
g.setColor(Utils.COLOR_BLUE_BUTTON);
|
||||
g.setColor(Colors.BLUE_BUTTON);
|
||||
g.fillRect(buttonBaseX, y, buttonWidth, buttonHeight);
|
||||
}
|
||||
|
||||
@@ -361,7 +364,7 @@ public class DownloadNode {
|
||||
if (dl != null) {
|
||||
float progress = dl.getProgress();
|
||||
if (progress > 0f) {
|
||||
g.setColor(Utils.COLOR_GREEN);
|
||||
g.setColor(Colors.GREEN);
|
||||
g.fillRect(buttonBaseX, y, buttonWidth * progress / 100f, buttonHeight);
|
||||
}
|
||||
}
|
||||
@@ -373,21 +376,22 @@ public class DownloadNode {
|
||||
|
||||
// text
|
||||
// TODO: if the title/artist line is too long, shorten it (e.g. add "...") instead of just clipping
|
||||
if (Options.useUnicodeMetadata()) // load glyphs
|
||||
Utils.loadGlyphs(Utils.FONT_BOLD, getTitle(), getArtist());
|
||||
|
||||
if (Options.useUnicodeMetadata()) { // load glyphs
|
||||
Fonts.loadGlyphs(Fonts.BOLD, getTitle());
|
||||
Fonts.loadGlyphs(Fonts.BOLD, getArtist());
|
||||
}
|
||||
// TODO can't set clip again or else old clip will be cleared
|
||||
//g.setClip((int) textX, (int) (y + marginY), (int) (edgeX - textX - Utils.FONT_DEFAULT.getWidth(creator)), Utils.FONT_BOLD.getLineHeight());
|
||||
Utils.FONT_BOLD.drawString(
|
||||
//g.setClip((int) textX, (int) (y + marginY), (int) (edgeX - textX - Fonts.DEFAULT.getWidth(creator)), Fonts.BOLD.getLineHeight());
|
||||
Fonts.BOLD.drawString(
|
||||
textX, y + marginY,
|
||||
String.format("%s - %s%s", getArtist(), getTitle(),
|
||||
(dl != null) ? String.format(" [%s]", dl.getStatus().getName()) : ""), Color.white);
|
||||
//g.clearClip();
|
||||
Utils.FONT_DEFAULT.drawString(
|
||||
textX, y + marginY + Utils.FONT_BOLD.getLineHeight(),
|
||||
Fonts.DEFAULT.drawString(
|
||||
textX, y + marginY + Fonts.BOLD.getLineHeight(),
|
||||
String.format("Last updated: %s", date), Color.white);
|
||||
Utils.FONT_DEFAULT.drawString(
|
||||
edgeX - Utils.FONT_DEFAULT.getWidth(creator), y + marginY,
|
||||
Fonts.DEFAULT.drawString(
|
||||
edgeX - Fonts.DEFAULT.getWidth(creator), y + marginY,
|
||||
creator, Color.white);
|
||||
}
|
||||
|
||||
@@ -399,6 +403,7 @@ public class DownloadNode {
|
||||
* @param hover true if the mouse is hovering over this button
|
||||
*/
|
||||
public void drawDownload(Graphics g, float position, int id, boolean hover) {
|
||||
Download download = this.download; // in case clearDownload() is called asynchronously
|
||||
if (download == null) {
|
||||
ErrorHandler.error("Trying to draw download information for button without Download object.", null, false);
|
||||
return;
|
||||
@@ -410,7 +415,7 @@ public class DownloadNode {
|
||||
float marginY = infoHeight * 0.04f;
|
||||
|
||||
// rectangle outline
|
||||
g.setColor((id % 2 == 0) ? BG_HOVER : BG_NORMAL);
|
||||
g.setColor((id % 2 == 0) ? Colors.BLACK_BG_HOVER : Colors.BLACK_BG_NORMAL);
|
||||
g.fillRect(infoBaseX, y, infoWidth, infoHeight);
|
||||
|
||||
// text
|
||||
@@ -428,8 +433,8 @@ public class DownloadNode {
|
||||
info = String.format("%s: %.1f%% (%s/%s)", status.getName(), progress,
|
||||
Utils.bytesToString(download.readSoFar()), Utils.bytesToString(download.contentLength()));
|
||||
}
|
||||
Utils.FONT_BOLD.drawString(textX, y + marginY, getTitle(), Color.white);
|
||||
Utils.FONT_DEFAULT.drawString(textX, y + marginY + Utils.FONT_BOLD.getLineHeight(), info, Color.white);
|
||||
Fonts.BOLD.drawString(textX, y + marginY, getTitle(), Color.white);
|
||||
Fonts.DEFAULT.drawString(textX, y + marginY + Fonts.BOLD.getLineHeight(), info, Color.white);
|
||||
|
||||
// 'x' button
|
||||
if (hover) {
|
||||
|
||||
@@ -28,7 +28,7 @@ import java.nio.channels.ReadableByteChannel;
|
||||
*/
|
||||
public class ReadableByteChannelWrapper implements ReadableByteChannel {
|
||||
/** The wrapped ReadableByteChannel. */
|
||||
private ReadableByteChannel rbc;
|
||||
private final ReadableByteChannel rbc;
|
||||
|
||||
/** The number of bytes read. */
|
||||
private long bytesRead;
|
||||
|
||||
@@ -77,7 +77,7 @@ public class Updater {
|
||||
UPDATE_FINAL ("Update queued.");
|
||||
|
||||
/** The status description. */
|
||||
private String description;
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -194,9 +194,10 @@ public class Updater {
|
||||
|
||||
/**
|
||||
* Checks the program version against the version file on the update server.
|
||||
* @throws IOException if an I/O exception occurs
|
||||
*/
|
||||
public void checkForUpdates() throws IOException {
|
||||
if (status != Status.INITIAL || System.getProperty("XDG") != null)
|
||||
if (status != Status.INITIAL || Options.USE_XDG)
|
||||
return;
|
||||
|
||||
status = Status.CHECKING;
|
||||
|
||||
@@ -75,4 +75,7 @@ public abstract class DownloadServer {
|
||||
public String getPreviewURL(int beatmapSetID) {
|
||||
return String.format(PREVIEW_URL, beatmapSetID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() { return getName(); }
|
||||
}
|
||||
|
||||
202
src/itdelatrisu/opsu/downloads/servers/MengSkyServer.java
Normal file
202
src/itdelatrisu/opsu/downloads/servers/MengSkyServer.java
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* opsu! - an open-source osu! client
|
||||
* Copyright (C) 2014, 2015 Jeffrey Han
|
||||
*
|
||||
* opsu! 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.
|
||||
*
|
||||
* opsu! 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 opsu!. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package itdelatrisu.opsu.downloads.servers;
|
||||
|
||||
import itdelatrisu.opsu.ErrorHandler;
|
||||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.downloads.DownloadNode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Download server: http://osu.mengsky.net/
|
||||
*/
|
||||
public class MengSkyServer extends DownloadServer {
|
||||
/** Server name. */
|
||||
private static final String SERVER_NAME = "MengSky";
|
||||
|
||||
/** Formatted download URL: {@code beatmapSetID} */
|
||||
private static final String DOWNLOAD_URL = "http://osu.mengsky.net/d.php?id=%d";
|
||||
|
||||
/** Formatted search URL: {@code query} */
|
||||
private static final String SEARCH_URL = "http://osu.mengsky.net/index.php?search_keywords=%s";
|
||||
|
||||
/** Formatted home URL: {@code page} */
|
||||
private static final String HOME_URL = "http://osu.mengsky.net/index.php?next=1&page=%d";
|
||||
|
||||
/** Maximum beatmaps displayed per page. */
|
||||
private static final int PAGE_LIMIT = 20;
|
||||
|
||||
/** Total result count from the last query. */
|
||||
private int totalResults = -1;
|
||||
|
||||
/** Constructor. */
|
||||
public MengSkyServer() {}
|
||||
|
||||
@Override
|
||||
public String getName() { return SERVER_NAME; }
|
||||
|
||||
@Override
|
||||
public String getDownloadURL(int beatmapSetID) {
|
||||
return String.format(DOWNLOAD_URL, beatmapSetID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadNode[] resultList(String query, int page, boolean rankedOnly) throws IOException {
|
||||
DownloadNode[] nodes = null;
|
||||
try {
|
||||
// read HTML
|
||||
String search;
|
||||
boolean isSearch;
|
||||
if (query.isEmpty()) {
|
||||
isSearch = false;
|
||||
search = String.format(HOME_URL, page - 1);
|
||||
} else {
|
||||
isSearch = true;
|
||||
search = String.format(SEARCH_URL, URLEncoder.encode(query, "UTF-8"));
|
||||
}
|
||||
String html = Utils.readDataFromUrl(new URL(search));
|
||||
if (html == null) {
|
||||
this.totalResults = -1;
|
||||
return null;
|
||||
}
|
||||
|
||||
// parse results
|
||||
// NOTE: Maybe an HTML parser would be better for this...
|
||||
// FORMAT:
|
||||
// <div class="beatmap" style="{{...}}">
|
||||
// <div class="preview" style="background-image:url(http://b.ppy.sh/thumb/{{id}}l.jpg)"></div>
|
||||
// <div class="name"> <a href="">{{artist}} - {{title}}</a> </div>
|
||||
// <div class="douban_details">
|
||||
// <span>Creator:</span> {{creator}}<br>
|
||||
// <span>MaxBpm:</span> {{bpm}}<br>
|
||||
// <span>Title:</span> {{titleUnicode}}<br>
|
||||
// <span>Artist:</span> {{artistUnicode}}<br>
|
||||
// <span>Status:</span> <font color={{"#00CD00" || "#EE0000"}}>{{"Ranked?" || "Unranked"}}</font><br>
|
||||
// </div>
|
||||
// <div class="details"> <a href=""></a> <br>
|
||||
// <span>Fork:</span> bloodcat<br>
|
||||
// <span>UpdateTime:</span> {{yyyy}}/{{mm}}/{{dd}} {{hh}}:{{mm}}:{{ss}}<br>
|
||||
// <span>Mode:</span> <img id="{{'s' || 'c' || ...}}" src="/img/{{'s' || 'c' || ...}}.png"> {{...}}
|
||||
// </div>
|
||||
// <div class="download">
|
||||
// <a href="https://osu.ppy.sh/s/{{id}}" class=" btn" target="_blank">Osu.ppy</a>
|
||||
// </div>
|
||||
// <div class="download">
|
||||
// <a href="http://osu.mengsky.net/d.php?id={{id}}" class=" btn" target="_blank">DownLoad</a>
|
||||
// </div>
|
||||
// </div>
|
||||
List<DownloadNode> nodeList = new ArrayList<DownloadNode>();
|
||||
final String
|
||||
START_TAG = "<div class=\"beatmap\"", NAME_TAG = "<div class=\"name\"> <a href=\"\">",
|
||||
CREATOR_TAG = "<span>Creator:</span> ", TITLE_TAG = "<span>Title:</span> ", ARTIST_TAG = "<span>Artist:</span> ",
|
||||
TIMESTAMP_TAG = "<span>UpdateTime:</span> ", DOWNLOAD_TAG = "<div class=\"download\">",
|
||||
BR_TAG = "<br>", HREF_TAG = "<a href=\"", HREF_TAG_END = "</a>";
|
||||
int index = -1;
|
||||
int nextIndex = html.indexOf(START_TAG, index + 1);
|
||||
int divCount = 0;
|
||||
while ((index = nextIndex) != -1) {
|
||||
nextIndex = html.indexOf(START_TAG, index + 1);
|
||||
int n = (nextIndex == -1) ? html.length() : nextIndex;
|
||||
divCount++;
|
||||
int i, j;
|
||||
|
||||
// find beatmap
|
||||
i = html.indexOf(NAME_TAG, index + START_TAG.length());
|
||||
if (i == -1 || i > n) continue;
|
||||
j = html.indexOf(HREF_TAG_END, i + 1);
|
||||
if (j == -1 || j > n) continue;
|
||||
String beatmap = html.substring(i + NAME_TAG.length(), j);
|
||||
String[] beatmapTokens = beatmap.split(" - ", 2);
|
||||
if (beatmapTokens.length < 2)
|
||||
continue;
|
||||
String artist = beatmapTokens[0];
|
||||
String title = beatmapTokens[1];
|
||||
|
||||
// find other beatmap details
|
||||
i = html.indexOf(CREATOR_TAG, j + HREF_TAG_END.length());
|
||||
if (i == -1 || i > n) continue;
|
||||
j = html.indexOf(BR_TAG, i + CREATOR_TAG.length());
|
||||
if (j == -1 || j > n) continue;
|
||||
String creator = html.substring(i + CREATOR_TAG.length(), j);
|
||||
i = html.indexOf(TITLE_TAG, j + BR_TAG.length());
|
||||
if (i == -1 || i > n) continue;
|
||||
j = html.indexOf(BR_TAG, i + TITLE_TAG.length());
|
||||
if (j == -1 || j > n) continue;
|
||||
String titleUnicode = html.substring(i + TITLE_TAG.length(), j);
|
||||
i = html.indexOf(ARTIST_TAG, j + BR_TAG.length());
|
||||
if (i == -1 || i > n) continue;
|
||||
j = html.indexOf(BR_TAG, i + ARTIST_TAG.length());
|
||||
if (j == -1 || j > n) continue;
|
||||
String artistUnicode = html.substring(i + ARTIST_TAG.length(), j);
|
||||
i = html.indexOf(TIMESTAMP_TAG, j + BR_TAG.length());
|
||||
if (i == -1 || i >= n) continue;
|
||||
j = html.indexOf(BR_TAG, i + TIMESTAMP_TAG.length());
|
||||
if (j == -1 || j > n) continue;
|
||||
String date = html.substring(i + TIMESTAMP_TAG.length(), j);
|
||||
|
||||
// find beatmap ID
|
||||
i = html.indexOf(DOWNLOAD_TAG, j + BR_TAG.length());
|
||||
if (i == -1 || i >= n) continue;
|
||||
i = html.indexOf(HREF_TAG, i + DOWNLOAD_TAG.length());
|
||||
if (i == -1 || i > n) continue;
|
||||
j = html.indexOf('"', i + HREF_TAG.length());
|
||||
if (j == -1 || j > n) continue;
|
||||
String downloadURL = html.substring(i + HREF_TAG.length(), j);
|
||||
String[] downloadTokens = downloadURL.split("(?=\\d*$)", 2);
|
||||
if (downloadTokens[1].isEmpty()) continue;
|
||||
int id;
|
||||
try {
|
||||
id = Integer.parseInt(downloadTokens[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nodeList.add(new DownloadNode(id, date, title, titleUnicode, artist, artistUnicode, creator));
|
||||
}
|
||||
|
||||
nodes = nodeList.toArray(new DownloadNode[nodeList.size()]);
|
||||
|
||||
// store total result count
|
||||
if (isSearch)
|
||||
this.totalResults = nodes.length;
|
||||
else {
|
||||
int resultCount = nodes.length + (page - 1) * PAGE_LIMIT;
|
||||
if (divCount == PAGE_LIMIT)
|
||||
resultCount++;
|
||||
this.totalResults = resultCount;
|
||||
}
|
||||
} catch (MalformedURLException | UnsupportedEncodingException e) {
|
||||
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int minQueryLength() { return 2; }
|
||||
|
||||
@Override
|
||||
public int totalResults() { return totalResults; }
|
||||
}
|
||||
133
src/itdelatrisu/opsu/downloads/servers/MnetworkServer.java
Normal file
133
src/itdelatrisu/opsu/downloads/servers/MnetworkServer.java
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* opsu! - an open-source osu! client
|
||||
* Copyright (C) 2014, 2015 Jeffrey Han
|
||||
*
|
||||
* opsu! 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.
|
||||
*
|
||||
* opsu! 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 opsu!. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package itdelatrisu.opsu.downloads.servers;
|
||||
|
||||
import itdelatrisu.opsu.ErrorHandler;
|
||||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.downloads.DownloadNode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Download server: http://osu.uu.gl/
|
||||
*/
|
||||
public class MnetworkServer extends DownloadServer {
|
||||
/** Server name. */
|
||||
private static final String SERVER_NAME = "Mnetwork";
|
||||
|
||||
/** Formatted download URL: {@code beatmapSetID} */
|
||||
private static final String DOWNLOAD_URL = "http://osu.uu.gl/s/%d";
|
||||
|
||||
/** Formatted search URL: {@code query} */
|
||||
private static final String SEARCH_URL = "http://osu.uu.gl/d/%s";
|
||||
|
||||
/** Total result count from the last query. */
|
||||
private int totalResults = -1;
|
||||
|
||||
/** Beatmap pattern. */
|
||||
private Pattern BEATMAP_PATTERN = Pattern.compile("^(\\d+) ([^-]+) - (.+)\\.osz$");
|
||||
|
||||
/** Constructor. */
|
||||
public MnetworkServer() {}
|
||||
|
||||
@Override
|
||||
public String getName() { return SERVER_NAME; }
|
||||
|
||||
@Override
|
||||
public String getDownloadURL(int beatmapSetID) {
|
||||
return String.format(DOWNLOAD_URL, beatmapSetID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadNode[] resultList(String query, int page, boolean rankedOnly) throws IOException {
|
||||
DownloadNode[] nodes = null;
|
||||
try {
|
||||
// read HTML
|
||||
String queryString = (query.isEmpty()) ? "-" : query;
|
||||
String search = String.format(SEARCH_URL, URLEncoder.encode(queryString, "UTF-8"));
|
||||
String html = Utils.readDataFromUrl(new URL(search));
|
||||
if (html == null) {
|
||||
this.totalResults = -1;
|
||||
return null;
|
||||
}
|
||||
|
||||
// parse results
|
||||
// NOTE: Not using a full HTML parser because this is a relatively simple operation.
|
||||
// FORMAT:
|
||||
// <div class="tr_title">
|
||||
// <b><a href='/s/{{id}}'>{{id}} {{artist}} - {{title}}.osz</a></b><br />
|
||||
// BPM: {{bpm}} <b>|</b> Total Time: {{m}}:{{s}}<br/>
|
||||
// Genre: {{genre}} <b>|</b> Updated: {{MMM}} {{d}}, {{yyyy}}<br />
|
||||
List<DownloadNode> nodeList = new ArrayList<DownloadNode>();
|
||||
final String START_TAG = "<div class=\"tr_title\">", HREF_TAG = "<a href=", HREF_TAG_END = "</a>", UPDATED = "Updated: ";
|
||||
int index = -1;
|
||||
int nextIndex = html.indexOf(START_TAG, index + 1);
|
||||
while ((index = nextIndex) != -1) {
|
||||
nextIndex = html.indexOf(START_TAG, index + 1);
|
||||
int n = (nextIndex == -1) ? html.length() : nextIndex;
|
||||
int i, j;
|
||||
|
||||
// find beatmap
|
||||
i = html.indexOf(HREF_TAG, index + START_TAG.length());
|
||||
if (i == -1 || i > n) continue;
|
||||
i = html.indexOf('>', i + HREF_TAG.length());
|
||||
if (i == -1 || i >= n) continue;
|
||||
j = html.indexOf(HREF_TAG_END, i + 1);
|
||||
if (j == -1 || j > n) continue;
|
||||
String beatmap = html.substring(i + 1, j).trim();
|
||||
|
||||
// find date
|
||||
i = html.indexOf(UPDATED, j);
|
||||
if (i == -1 || i >= n) continue;
|
||||
j = html.indexOf('<', i + UPDATED.length());
|
||||
if (j == -1 || j > n) continue;
|
||||
String date = html.substring(i + UPDATED.length(), j).trim();
|
||||
|
||||
// parse id, title, and artist
|
||||
Matcher m = BEATMAP_PATTERN.matcher(beatmap);
|
||||
if (!m.matches())
|
||||
continue;
|
||||
|
||||
nodeList.add(new DownloadNode(Integer.parseInt(m.group(1)), date, m.group(3), null, m.group(2), null, ""));
|
||||
}
|
||||
|
||||
nodes = nodeList.toArray(new DownloadNode[nodeList.size()]);
|
||||
|
||||
// store total result count
|
||||
this.totalResults = nodes.length;
|
||||
} catch (MalformedURLException | UnsupportedEncodingException e) {
|
||||
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int minQueryLength() { return 0; }
|
||||
|
||||
@Override
|
||||
public int totalResults() { return totalResults; }
|
||||
}
|
||||
@@ -39,6 +39,8 @@ import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Download server: http://loli.al/
|
||||
* <p>
|
||||
* <i>This server went offline in August 2015.</i>
|
||||
*/
|
||||
public class OsuMirrorServer extends DownloadServer {
|
||||
/** Server name. */
|
||||
|
||||
204
src/itdelatrisu/opsu/downloads/servers/YaSOnlineServer.java
Normal file
204
src/itdelatrisu/opsu/downloads/servers/YaSOnlineServer.java
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* opsu! - an open-source osu! client
|
||||
* Copyright (C) 2014, 2015 Jeffrey Han
|
||||
*
|
||||
* opsu! 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.
|
||||
*
|
||||
* opsu! 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 opsu!. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package itdelatrisu.opsu.downloads.servers;
|
||||
|
||||
import itdelatrisu.opsu.ErrorHandler;
|
||||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.downloads.DownloadNode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Download server: http://osu.yas-online.net/
|
||||
*/
|
||||
public class YaSOnlineServer extends DownloadServer {
|
||||
/** Server name. */
|
||||
private static final String SERVER_NAME = "YaS Online";
|
||||
|
||||
/** Formatted download URL (returns JSON): {@code beatmapSetID} */
|
||||
private static final String DOWNLOAD_URL = "https://osu.yas-online.net/json.mapdata.php?mapId=%d";
|
||||
|
||||
/**
|
||||
* Formatted download fetch URL: {@code downloadLink}
|
||||
* (e.g. {@code /fetch/49125122158ef360a66a07bce2d0483596913843-m-10418.osz})
|
||||
*/
|
||||
private static final String DOWNLOAD_FETCH_URL = "https://osu.yas-online.net%s";
|
||||
|
||||
/** Maximum beatmaps displayed per page. */
|
||||
private static final int PAGE_LIMIT = 25;
|
||||
|
||||
/** Formatted home URL: {@code page} */
|
||||
private static final String HOME_URL = "https://osu.yas-online.net/json.maplist.php?o=%d";
|
||||
|
||||
/** Formatted search URL: {@code query} */
|
||||
private static final String SEARCH_URL = "https://osu.yas-online.net/json.search.php?searchQuery=%s";
|
||||
|
||||
/** Total result count from the last query. */
|
||||
private int totalResults = -1;
|
||||
|
||||
/** Max server download ID seen (for approximating total pages). */
|
||||
private int maxServerID = 0;
|
||||
|
||||
/** Constructor. */
|
||||
public YaSOnlineServer() {}
|
||||
|
||||
@Override
|
||||
public String getName() { return SERVER_NAME; }
|
||||
|
||||
@Override
|
||||
public String getDownloadURL(int beatmapSetID) {
|
||||
try {
|
||||
// TODO: do this asynchronously (will require lots of changes...)
|
||||
return getDownloadURLFromMapData(beatmapSetID);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the beatmap download URL by downloading its map data.
|
||||
* <p>
|
||||
* This is needed because there is no other way to find a beatmap's direct
|
||||
* download URL.
|
||||
* @param beatmapSetID the beatmap set ID
|
||||
* @return the URL string, or null if the address could not be determined
|
||||
* @throws IOException if any connection error occurred
|
||||
*/
|
||||
private String getDownloadURLFromMapData(int beatmapSetID) throws IOException {
|
||||
try {
|
||||
// read JSON
|
||||
String search = String.format(DOWNLOAD_URL, beatmapSetID);
|
||||
JSONObject json = Utils.readJsonObjectFromUrl(new URL(search));
|
||||
JSONObject results;
|
||||
if (json == null ||
|
||||
!json.getString("result").equals("success") ||
|
||||
(results = json.getJSONObject("success")).length() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// parse result
|
||||
Iterator<?> keys = results.keys();
|
||||
if (!keys.hasNext())
|
||||
return null;
|
||||
String key = (String) keys.next();
|
||||
JSONObject item = results.getJSONObject(key);
|
||||
String downloadLink = item.getString("downloadLink");
|
||||
return String.format(DOWNLOAD_FETCH_URL, downloadLink);
|
||||
} catch (MalformedURLException | UnsupportedEncodingException e) {
|
||||
ErrorHandler.error(String.format("Problem retrieving download URL for beatmap '%d'.", beatmapSetID), e, true);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadNode[] resultList(String query, int page, boolean rankedOnly) throws IOException {
|
||||
DownloadNode[] nodes = null;
|
||||
try {
|
||||
// read JSON
|
||||
String search;
|
||||
boolean isSearch;
|
||||
if (query.isEmpty()) {
|
||||
isSearch = false;
|
||||
search = String.format(HOME_URL, (page - 1) * PAGE_LIMIT);
|
||||
} else {
|
||||
isSearch = true;
|
||||
search = String.format(SEARCH_URL, URLEncoder.encode(query, "UTF-8"));
|
||||
}
|
||||
JSONObject json = Utils.readJsonObjectFromUrl(new URL(search));
|
||||
if (json == null) {
|
||||
this.totalResults = -1;
|
||||
return null;
|
||||
}
|
||||
JSONObject results;
|
||||
if (!json.getString("result").equals("success") ||
|
||||
(results = json.getJSONObject("success")).length() == 0) {
|
||||
this.totalResults = 0;
|
||||
return new DownloadNode[0];
|
||||
}
|
||||
|
||||
// parse result list
|
||||
List<DownloadNode> nodeList = new ArrayList<DownloadNode>();
|
||||
for (Object obj : results.keySet()) {
|
||||
String key = (String) obj;
|
||||
JSONObject item = results.getJSONObject(key);
|
||||
|
||||
// parse title and artist
|
||||
String title, artist;
|
||||
String str = item.getString("map");
|
||||
int index = str.indexOf(" - ");
|
||||
if (index > -1) {
|
||||
title = str.substring(0, index);
|
||||
artist = str.substring(index + 3);
|
||||
} else { // should never happen...
|
||||
title = str;
|
||||
artist = "?";
|
||||
}
|
||||
|
||||
// only contains date added if part of a beatmap pack
|
||||
int added = item.getInt("added");
|
||||
String date = (added == 0) ? "?" : formatDate(added);
|
||||
|
||||
// approximate page count
|
||||
int serverID = item.getInt("id");
|
||||
if (serverID > maxServerID)
|
||||
maxServerID = serverID;
|
||||
|
||||
nodeList.add(new DownloadNode(item.getInt("mapid"), date, title, null, artist, null, ""));
|
||||
}
|
||||
nodes = nodeList.toArray(new DownloadNode[nodeList.size()]);
|
||||
|
||||
// store total result count
|
||||
if (isSearch)
|
||||
this.totalResults = nodes.length;
|
||||
else
|
||||
this.totalResults = maxServerID;
|
||||
} catch (MalformedURLException | UnsupportedEncodingException e) {
|
||||
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int minQueryLength() { return 3; }
|
||||
|
||||
@Override
|
||||
public int totalResults() { return totalResults; }
|
||||
|
||||
/**
|
||||
* Returns a formatted date string from a raw date.
|
||||
* @param timestamp the UTC timestamp, in seconds
|
||||
* @return the formatted date
|
||||
*/
|
||||
private String formatDate(int timestamp) {
|
||||
Date d = new Date(timestamp * 1000L);
|
||||
DateFormat fmt = new SimpleDateFormat("d MMM yyyy HH:mm:ss");
|
||||
return fmt.format(d);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user