Subsonic-->Libresonic regex

This commit is contained in:
Eugene E. Kashpureff Jr
2016-05-14 22:26:36 +00:00
parent 8cd951f9a7
commit 745969264a
1324 changed files with 4084 additions and 4100 deletions
+279
View File
@@ -0,0 +1,279 @@
package org.json;
/*
Copyright (c) 2002 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/**
* This provides static methods to convert comma delimited text into a
* JSONArray, and to covert a JSONArray into comma delimited text. Comma
* delimited text is a very popular format for data interchange. It is
* understood by most database, spreadsheet, and organizer programs.
* <p>
* Each row of text represents a row in a table or a data record. Each row
* ends with a NEWLINE character. Each row contains one or more values.
* Values are separated by commas. A value can contain any character except
* for comma, unless is is wrapped in single quotes or double quotes.
* <p>
* The first row usually contains the names of the columns.
* <p>
* A comma delimited list can be converted into a JSONArray of JSONObjects.
* The names for the elements in the JSONObjects can be taken from the names
* in the first row.
* @author JSON.org
* @version 2010-12-24
*/
public class CDL {
/**
* Get the next value. The value can be wrapped in quotes. The value can
* be empty.
* @param x A JSONTokener of the source text.
* @return The value string, or null if empty.
* @throws JSONException if the quoted string is badly formed.
*/
private static String getValue(JSONTokener x) throws JSONException {
char c;
char q;
StringBuffer sb;
do {
c = x.next();
} while (c == ' ' || c == '\t');
switch (c) {
case 0:
return null;
case '"':
case '\'':
q = c;
sb = new StringBuffer();
for (;;) {
c = x.next();
if (c == q) {
break;
}
if (c == 0 || c == '\n' || c == '\r') {
throw x.syntaxError("Missing close quote '" + q + "'.");
}
sb.append(c);
}
return sb.toString();
case ',':
x.back();
return "";
default:
x.back();
return x.nextTo(',');
}
}
/**
* Produce a JSONArray of strings from a row of comma delimited values.
* @param x A JSONTokener of the source text.
* @return A JSONArray of strings.
* @throws JSONException
*/
public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException {
JSONArray ja = new JSONArray();
for (;;) {
String value = getValue(x);
char c = x.next();
if (value == null ||
(ja.length() == 0 && value.length() == 0 && c != ',')) {
return null;
}
ja.put(value);
for (;;) {
if (c == ',') {
break;
}
if (c != ' ') {
if (c == '\n' || c == '\r' || c == 0) {
return ja;
}
throw x.syntaxError("Bad character '" + c + "' (" +
(int)c + ").");
}
c = x.next();
}
}
}
/**
* Produce a JSONObject from a row of comma delimited text, using a
* parallel JSONArray of strings to provides the names of the elements.
* @param names A JSONArray of names. This is commonly obtained from the
* first row of a comma delimited text file using the rowToJSONArray
* method.
* @param x A JSONTokener of the source text.
* @return A JSONObject combining the names and values.
* @throws JSONException
*/
public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x)
throws JSONException {
JSONArray ja = rowToJSONArray(x);
return ja != null ? ja.toJSONObject(names) : null;
}
/**
* Produce a comma delimited text row from a JSONArray. Values containing
* the comma character will be quoted. Troublesome characters may be
* removed.
* @param ja A JSONArray of strings.
* @return A string ending in NEWLINE.
*/
public static String rowToString(JSONArray ja) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < ja.length(); i += 1) {
if (i > 0) {
sb.append(',');
}
Object object = ja.opt(i);
if (object != null) {
String string = object.toString();
if (string.length() > 0 && (string.indexOf(',') >= 0 ||
string.indexOf('\n') >= 0 || string.indexOf('\r') >= 0 ||
string.indexOf(0) >= 0 || string.charAt(0) == '"')) {
sb.append('"');
int length = string.length();
for (int j = 0; j < length; j += 1) {
char c = string.charAt(j);
if (c >= ' ' && c != '"') {
sb.append(c);
}
}
sb.append('"');
} else {
sb.append(string);
}
}
}
sb.append('\n');
return sb.toString();
}
/**
* Produce a JSONArray of JSONObjects from a comma delimited text string,
* using the first row as a source of names.
* @param string The comma delimited text.
* @return A JSONArray of JSONObjects.
* @throws JSONException
*/
public static JSONArray toJSONArray(String string) throws JSONException {
return toJSONArray(new JSONTokener(string));
}
/**
* Produce a JSONArray of JSONObjects from a comma delimited text string,
* using the first row as a source of names.
* @param x The JSONTokener containing the comma delimited text.
* @return A JSONArray of JSONObjects.
* @throws JSONException
*/
public static JSONArray toJSONArray(JSONTokener x) throws JSONException {
return toJSONArray(rowToJSONArray(x), x);
}
/**
* Produce a JSONArray of JSONObjects from a comma delimited text string
* using a supplied JSONArray as the source of element names.
* @param names A JSONArray of strings.
* @param string The comma delimited text.
* @return A JSONArray of JSONObjects.
* @throws JSONException
*/
public static JSONArray toJSONArray(JSONArray names, String string)
throws JSONException {
return toJSONArray(names, new JSONTokener(string));
}
/**
* Produce a JSONArray of JSONObjects from a comma delimited text string
* using a supplied JSONArray as the source of element names.
* @param names A JSONArray of strings.
* @param x A JSONTokener of the source text.
* @return A JSONArray of JSONObjects.
* @throws JSONException
*/
public static JSONArray toJSONArray(JSONArray names, JSONTokener x)
throws JSONException {
if (names == null || names.length() == 0) {
return null;
}
JSONArray ja = new JSONArray();
for (;;) {
JSONObject jo = rowToJSONObject(names, x);
if (jo == null) {
break;
}
ja.put(jo);
}
if (ja.length() == 0) {
return null;
}
return ja;
}
/**
* Produce a comma delimited text from a JSONArray of JSONObjects. The
* first row will be a list of names obtained by inspecting the first
* JSONObject.
* @param ja A JSONArray of JSONObjects.
* @return A comma delimited text.
* @throws JSONException
*/
public static String toString(JSONArray ja) throws JSONException {
JSONObject jo = ja.optJSONObject(0);
if (jo != null) {
JSONArray names = jo.names();
if (names != null) {
return rowToString(names) + toString(names, ja);
}
}
return null;
}
/**
* Produce a comma delimited text from a JSONArray of JSONObjects using
* a provided list of names. The list of names is not included in the
* output.
* @param names A JSONArray of strings.
* @param ja A JSONArray of JSONObjects.
* @return A comma delimited text.
* @throws JSONException
*/
public static String toString(JSONArray names, JSONArray ja)
throws JSONException {
if (names == null || names.length() == 0) {
return null;
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < ja.length(); i += 1) {
JSONObject jo = ja.optJSONObject(i);
if (jo != null) {
sb.append(rowToString(jo.toJSONArray(names)));
}
}
return sb.toString();
}
}
+169
View File
@@ -0,0 +1,169 @@
package org.json;
/*
Copyright (c) 2002 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/**
* Convert a web browser cookie specification to a JSONObject and back.
* JSON and Cookies are both notations for name/value pairs.
* @author JSON.org
* @version 2010-12-24
*/
public class Cookie {
/**
* Produce a copy of a string in which the characters '+', '%', '=', ';'
* and control characters are replaced with "%hh". This is a gentle form
* of URL encoding, attempting to cause as little distortion to the
* string as possible. The characters '=' and ';' are meta characters in
* cookies. By convention, they are escaped using the URL-encoding. This is
* only a convention, not a standard. Often, cookies are expected to have
* encoded values. We encode '=' and ';' because we must. We encode '%' and
* '+' because they are meta characters in URL encoding.
* @param string The source string.
* @return The escaped result.
*/
public static String escape(String string) {
char c;
String s = string.trim();
StringBuffer sb = new StringBuffer();
int length = s.length();
for (int i = 0; i < length; i += 1) {
c = s.charAt(i);
if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') {
sb.append('%');
sb.append(Character.forDigit((char)((c >>> 4) & 0x0f), 16));
sb.append(Character.forDigit((char)(c & 0x0f), 16));
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* Convert a cookie specification string into a JSONObject. The string
* will contain a name value pair separated by '='. The name and the value
* will be unescaped, possibly converting '+' and '%' sequences. The
* cookie properties may follow, separated by ';', also represented as
* name=value (except the secure property, which does not have a value).
* The name will be stored under the key "name", and the value will be
* stored under the key "value". This method does not do checking or
* validation of the parameters. It only converts the cookie string into
* a JSONObject.
* @param string The cookie specification string.
* @return A JSONObject containing "name", "value", and possibly other
* members.
* @throws JSONException
*/
public static JSONObject toJSONObject(String string) throws JSONException {
String name;
JSONObject jo = new JSONObject();
Object value;
JSONTokener x = new JSONTokener(string);
jo.put("name", x.nextTo('='));
x.next('=');
jo.put("value", x.nextTo(';'));
x.next();
while (x.more()) {
name = unescape(x.nextTo("=;"));
if (x.next() != '=') {
if (name.equals("secure")) {
value = Boolean.TRUE;
} else {
throw x.syntaxError("Missing '=' in cookie parameter.");
}
} else {
value = unescape(x.nextTo(';'));
x.next();
}
jo.put(name, value);
}
return jo;
}
/**
* Convert a JSONObject into a cookie specification string. The JSONObject
* must contain "name" and "value" members.
* If the JSONObject contains "expires", "domain", "path", or "secure"
* members, they will be appended to the cookie specification string.
* All other members are ignored.
* @param jo A JSONObject
* @return A cookie specification string
* @throws JSONException
*/
public static String toString(JSONObject jo) throws JSONException {
StringBuffer sb = new StringBuffer();
sb.append(escape(jo.getString("name")));
sb.append("=");
sb.append(escape(jo.getString("value")));
if (jo.has("expires")) {
sb.append(";expires=");
sb.append(jo.getString("expires"));
}
if (jo.has("domain")) {
sb.append(";domain=");
sb.append(escape(jo.getString("domain")));
}
if (jo.has("path")) {
sb.append(";path=");
sb.append(escape(jo.getString("path")));
}
if (jo.optBoolean("secure")) {
sb.append(";secure");
}
return sb.toString();
}
/**
* Convert <code>%</code><i>hh</i> sequences to single characters, and
* convert plus to space.
* @param string A string that may contain
* <code>+</code>&nbsp;<small>(plus)</small> and
* <code>%</code><i>hh</i> sequences.
* @return The unescaped string.
*/
public static String unescape(String string) {
int length = string.length();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; ++i) {
char c = string.charAt(i);
if (c == '+') {
c = ' ';
} else if (c == '%' && i + 2 < length) {
int d = JSONTokener.dehexchar(string.charAt(i + 1));
int e = JSONTokener.dehexchar(string.charAt(i + 2));
if (d >= 0 && e >= 0) {
c = (char)(d * 16 + e);
i += 2;
}
}
sb.append(c);
}
return sb.toString();
}
}
+90
View File
@@ -0,0 +1,90 @@
package org.json;
/*
Copyright (c) 2002 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import java.util.Iterator;
/**
* Convert a web browser cookie list string to a JSONObject and back.
* @author JSON.org
* @version 2010-12-24
*/
public class CookieList {
/**
* Convert a cookie list into a JSONObject. A cookie list is a sequence
* of name/value pairs. The names are separated from the values by '='.
* The pairs are separated by ';'. The names and the values
* will be unescaped, possibly converting '+' and '%' sequences.
*
* To add a cookie to a cooklist,
* cookielistJSONObject.put(cookieJSONObject.getString("name"),
* cookieJSONObject.getString("value"));
* @param string A cookie list string
* @return A JSONObject
* @throws JSONException
*/
public static JSONObject toJSONObject(String string) throws JSONException {
JSONObject jo = new JSONObject();
JSONTokener x = new JSONTokener(string);
while (x.more()) {
String name = Cookie.unescape(x.nextTo('='));
x.next('=');
jo.put(name, Cookie.unescape(x.nextTo(';')));
x.next();
}
return jo;
}
/**
* Convert a JSONObject into a cookie list. A cookie list is a sequence
* of name/value pairs. The names are separated from the values by '='.
* The pairs are separated by ';'. The characters '%', '+', '=', and ';'
* in the names and values are replaced by "%hh".
* @param jo A JSONObject
* @return A cookie list string
* @throws JSONException
*/
public static String toString(JSONObject jo) throws JSONException {
boolean b = false;
Iterator keys = jo.keys();
String string;
StringBuffer sb = new StringBuffer();
while (keys.hasNext()) {
string = keys.next().toString();
if (!jo.isNull(string)) {
if (b) {
sb.append(';');
}
sb.append(Cookie.escape(string));
sb.append("=");
sb.append(Cookie.escape(jo.getString(string)));
b = true;
}
}
return sb.toString();
}
}
+163
View File
@@ -0,0 +1,163 @@
package org.json;
/*
Copyright (c) 2002 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import java.util.Iterator;
/**
* Convert an HTTP header to a JSONObject and back.
* @author JSON.org
* @version 2010-12-24
*/
public class HTTP {
/** Carriage return/line feed. */
public static final String CRLF = "\r\n";
/**
* Convert an HTTP header string into a JSONObject. It can be a request
* header or a response header. A request header will contain
* <pre>{
* Method: "POST" (for example),
* "Request-URI": "/" (for example),
* "HTTP-Version": "HTTP/1.1" (for example)
* }</pre>
* A response header will contain
* <pre>{
* "HTTP-Version": "HTTP/1.1" (for example),
* "Status-Code": "200" (for example),
* "Reason-Phrase": "OK" (for example)
* }</pre>
* In addition, the other parameters in the header will be captured, using
* the HTTP field names as JSON names, so that <pre>
* Date: Sun, 26 May 2002 18:06:04 GMT
* Cookie: Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s
* Cache-Control: no-cache</pre>
* become
* <pre>{...
* Date: "Sun, 26 May 2002 18:06:04 GMT",
* Cookie: "Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s",
* "Cache-Control": "no-cache",
* ...}</pre>
* It does no further checking or conversion. It does not parse dates.
* It does not do '%' transforms on URLs.
* @param string An HTTP header string.
* @return A JSONObject containing the elements and attributes
* of the XML string.
* @throws JSONException
*/
public static JSONObject toJSONObject(String string) throws JSONException {
JSONObject jo = new JSONObject();
HTTPTokener x = new HTTPTokener(string);
String token;
token = x.nextToken();
if (token.toUpperCase().startsWith("HTTP")) {
// Response
jo.put("HTTP-Version", token);
jo.put("Status-Code", x.nextToken());
jo.put("Reason-Phrase", x.nextTo('\0'));
x.next();
} else {
// Request
jo.put("Method", token);
jo.put("Request-URI", x.nextToken());
jo.put("HTTP-Version", x.nextToken());
}
// Fields
while (x.more()) {
String name = x.nextTo(':');
x.next(':');
jo.put(name, x.nextTo('\0'));
x.next();
}
return jo;
}
/**
* Convert a JSONObject into an HTTP header. A request header must contain
* <pre>{
* Method: "POST" (for example),
* "Request-URI": "/" (for example),
* "HTTP-Version": "HTTP/1.1" (for example)
* }</pre>
* A response header must contain
* <pre>{
* "HTTP-Version": "HTTP/1.1" (for example),
* "Status-Code": "200" (for example),
* "Reason-Phrase": "OK" (for example)
* }</pre>
* Any other members of the JSONObject will be output as HTTP fields.
* The result will end with two CRLF pairs.
* @param jo A JSONObject
* @return An HTTP header string.
* @throws JSONException if the object does not contain enough
* information.
*/
public static String toString(JSONObject jo) throws JSONException {
Iterator keys = jo.keys();
String string;
StringBuffer sb = new StringBuffer();
if (jo.has("Status-Code") && jo.has("Reason-Phrase")) {
sb.append(jo.getString("HTTP-Version"));
sb.append(' ');
sb.append(jo.getString("Status-Code"));
sb.append(' ');
sb.append(jo.getString("Reason-Phrase"));
} else if (jo.has("Method") && jo.has("Request-URI")) {
sb.append(jo.getString("Method"));
sb.append(' ');
sb.append('"');
sb.append(jo.getString("Request-URI"));
sb.append('"');
sb.append(' ');
sb.append(jo.getString("HTTP-Version"));
} else {
throw new JSONException("Not enough material for an HTTP header.");
}
sb.append(CRLF);
while (keys.hasNext()) {
string = keys.next().toString();
if (!"HTTP-Version".equals(string) && !"Status-Code".equals(string) &&
!"Reason-Phrase".equals(string) && !"Method".equals(string) &&
!"Request-URI".equals(string) && !jo.isNull(string)) {
sb.append(string);
sb.append(": ");
sb.append(jo.getString(string));
sb.append(CRLF);
}
}
sb.append(CRLF);
return sb.toString();
}
}
+77
View File
@@ -0,0 +1,77 @@
package org.json;
/*
Copyright (c) 2002 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/**
* The HTTPTokener extends the JSONTokener to provide additional methods
* for the parsing of HTTP headers.
* @author JSON.org
* @version 2010-12-24
*/
public class HTTPTokener extends JSONTokener {
/**
* Construct an HTTPTokener from a string.
* @param string A source string.
*/
public HTTPTokener(String string) {
super(string);
}
/**
* Get the next token or string. This is used in parsing HTTP headers.
* @throws JSONException
* @return A String.
*/
public String nextToken() throws JSONException {
char c;
char q;
StringBuffer sb = new StringBuffer();
do {
c = next();
} while (Character.isWhitespace(c));
if (c == '"' || c == '\'') {
q = c;
for (;;) {
c = next();
if (c < ' ') {
throw syntaxError("Unterminated string.");
}
if (c == q) {
return sb.toString();
}
sb.append(c);
}
}
for (;;) {
if (c == 0 || Character.isWhitespace(c)) {
return sb.toString();
}
sb.append(c);
c = next();
}
}
}
@@ -0,0 +1,920 @@
package org.json;
/*
Copyright (c) 2002 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
/**
* A JSONArray is an ordered sequence of values. Its external text form is a
* string wrapped in square brackets with commas separating the values. The
* internal form is an object having <code>get</code> and <code>opt</code>
* methods for accessing the values by index, and <code>put</code> methods for
* adding or replacing values. The values can be any of these types:
* <code>Boolean</code>, <code>JSONArray</code>, <code>JSONObject</code>,
* <code>Number</code>, <code>String</code>, or the
* <code>JSONObject.NULL object</code>.
* <p>
* The constructor can convert a JSON text into a Java object. The
* <code>toString</code> method converts to JSON text.
* <p>
* A <code>get</code> method returns a value if one can be found, and throws an
* exception if one cannot be found. An <code>opt</code> method returns a
* default value instead of throwing an exception, and so is useful for
* obtaining optional values.
* <p>
* The generic <code>get()</code> and <code>opt()</code> methods return an
* object which you can cast or query for type. There are also typed
* <code>get</code> and <code>opt</code> methods that do type checking and type
* coercion for you.
* <p>
* The texts produced by the <code>toString</code> methods strictly conform to
* JSON syntax rules. The constructors are more forgiving in the texts they will
* accept:
* <ul>
* <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just
* before the closing bracket.</li>
* <li>The <code>null</code> value will be inserted when there
* is <code>,</code>&nbsp;<small>(comma)</small> elision.</li>
* <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single
* quote)</small>.</li>
* <li>Strings do not need to be quoted at all if they do not begin with a quote
* or single quote, and if they do not contain leading or trailing spaces,
* and if they do not contain any of these characters:
* <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers
* and if they are not the reserved words <code>true</code>,
* <code>false</code>, or <code>null</code>.</li>
* <li>Values can be separated by <code>;</code> <small>(semicolon)</small> as
* well as by <code>,</code> <small>(comma)</small>.</li>
* </ul>
* @author JSON.org
* @version 2011-12-19
*/
public class JSONArray {
/**
* The arrayList where the JSONArray's properties are kept.
*/
private final ArrayList myArrayList;
/**
* Construct an empty JSONArray.
*/
public JSONArray() {
this.myArrayList = new ArrayList();
}
/**
* Construct a JSONArray from a JSONTokener.
* @param x A JSONTokener
* @throws JSONException If there is a syntax error.
*/
public JSONArray(JSONTokener x) throws JSONException {
this();
if (x.nextClean() != '[') {
throw x.syntaxError("A JSONArray text must start with '['");
}
if (x.nextClean() != ']') {
x.back();
for (;;) {
if (x.nextClean() == ',') {
x.back();
this.myArrayList.add(JSONObject.NULL);
} else {
x.back();
this.myArrayList.add(x.nextValue());
}
switch (x.nextClean()) {
case ';':
case ',':
if (x.nextClean() == ']') {
return;
}
x.back();
break;
case ']':
return;
default:
throw x.syntaxError("Expected a ',' or ']'");
}
}
}
}
/**
* Construct a JSONArray from a source JSON text.
* @param source A string that begins with
* <code>[</code>&nbsp;<small>(left bracket)</small>
* and ends with <code>]</code>&nbsp;<small>(right bracket)</small>.
* @throws JSONException If there is a syntax error.
*/
public JSONArray(String source) throws JSONException {
this(new JSONTokener(source));
}
/**
* Construct a JSONArray from a Collection.
* @param collection A Collection.
*/
public JSONArray(Collection collection) {
this.myArrayList = new ArrayList();
if (collection != null) {
Iterator iter = collection.iterator();
while (iter.hasNext()) {
this.myArrayList.add(JSONObject.wrap(iter.next()));
}
}
}
/**
* Construct a JSONArray from an array
* @throws JSONException If not an array.
*/
public JSONArray(Object array) throws JSONException {
this();
if (array.getClass().isArray()) {
int length = Array.getLength(array);
for (int i = 0; i < length; i += 1) {
this.put(JSONObject.wrap(Array.get(array, i)));
}
} else {
throw new JSONException(
"JSONArray initial value should be a string or collection or array.");
}
}
/**
* Get the object value associated with an index.
* @param index
* The index must be between 0 and length() - 1.
* @return An object value.
* @throws JSONException If there is no value for the index.
*/
public Object get(int index) throws JSONException {
Object object = this.opt(index);
if (object == null) {
throw new JSONException("JSONArray[" + index + "] not found.");
}
return object;
}
/**
* Get the boolean value associated with an index.
* The string values "true" and "false" are converted to boolean.
*
* @param index The index must be between 0 and length() - 1.
* @return The truth.
* @throws JSONException If there is no value for the index or if the
* value is not convertible to boolean.
*/
public boolean getBoolean(int index) throws JSONException {
Object object = this.get(index);
if (object.equals(Boolean.FALSE) ||
(object instanceof String &&
((String)object).equalsIgnoreCase("false"))) {
return false;
} else if (object.equals(Boolean.TRUE) ||
(object instanceof String &&
((String)object).equalsIgnoreCase("true"))) {
return true;
}
throw new JSONException("JSONArray[" + index + "] is not a boolean.");
}
/**
* Get the double value associated with an index.
*
* @param index The index must be between 0 and length() - 1.
* @return The value.
* @throws JSONException If the key is not found or if the value cannot
* be converted to a number.
*/
public double getDouble(int index) throws JSONException {
Object object = this.get(index);
try {
return object instanceof Number
? ((Number)object).doubleValue()
: Double.parseDouble((String)object);
} catch (Exception e) {
throw new JSONException("JSONArray[" + index +
"] is not a number.");
}
}
/**
* Get the int value associated with an index.
*
* @param index The index must be between 0 and length() - 1.
* @return The value.
* @throws JSONException If the key is not found or if the value is not a number.
*/
public int getInt(int index) throws JSONException {
Object object = this.get(index);
try {
return object instanceof Number
? ((Number)object).intValue()
: Integer.parseInt((String)object);
} catch (Exception e) {
throw new JSONException("JSONArray[" + index +
"] is not a number.");
}
}
/**
* Get the JSONArray associated with an index.
* @param index The index must be between 0 and length() - 1.
* @return A JSONArray value.
* @throws JSONException If there is no value for the index. or if the
* value is not a JSONArray
*/
public JSONArray getJSONArray(int index) throws JSONException {
Object object = this.get(index);
if (object instanceof JSONArray) {
return (JSONArray)object;
}
throw new JSONException("JSONArray[" + index +
"] is not a JSONArray.");
}
/**
* Get the JSONObject associated with an index.
* @param index subscript
* @return A JSONObject value.
* @throws JSONException If there is no value for the index or if the
* value is not a JSONObject
*/
public JSONObject getJSONObject(int index) throws JSONException {
Object object = this.get(index);
if (object instanceof JSONObject) {
return (JSONObject)object;
}
throw new JSONException("JSONArray[" + index +
"] is not a JSONObject.");
}
/**
* Get the long value associated with an index.
*
* @param index The index must be between 0 and length() - 1.
* @return The value.
* @throws JSONException If the key is not found or if the value cannot
* be converted to a number.
*/
public long getLong(int index) throws JSONException {
Object object = this.get(index);
try {
return object instanceof Number
? ((Number)object).longValue()
: Long.parseLong((String)object);
} catch (Exception e) {
throw new JSONException("JSONArray[" + index +
"] is not a number.");
}
}
/**
* Get the string associated with an index.
* @param index The index must be between 0 and length() - 1.
* @return A string value.
* @throws JSONException If there is no string value for the index.
*/
public String getString(int index) throws JSONException {
Object object = this.get(index);
if (object instanceof String) {
return (String)object;
}
throw new JSONException("JSONArray[" + index + "] not a string.");
}
/**
* Determine if the value is null.
* @param index The index must be between 0 and length() - 1.
* @return true if the value at the index is null, or if there is no value.
*/
public boolean isNull(int index) {
return JSONObject.NULL.equals(this.opt(index));
}
/**
* Make a string from the contents of this JSONArray. The
* <code>separator</code> string is inserted between each element.
* Warning: This method assumes that the data structure is acyclical.
* @param separator A string that will be inserted between the elements.
* @return a string.
* @throws JSONException If the array contains an invalid number.
*/
public String join(String separator) throws JSONException {
int len = this.length();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < len; i += 1) {
if (i > 0) {
sb.append(separator);
}
sb.append(JSONObject.valueToString(this.myArrayList.get(i)));
}
return sb.toString();
}
/**
* Get the number of elements in the JSONArray, included nulls.
*
* @return The length (or size).
*/
public int length() {
return this.myArrayList.size();
}
/**
* Get the optional object value associated with an index.
* @param index The index must be between 0 and length() - 1.
* @return An object value, or null if there is no
* object at that index.
*/
public Object opt(int index) {
return (index < 0 || index >= this.length())
? null
: this.myArrayList.get(index);
}
/**
* Get the optional boolean value associated with an index.
* It returns false if there is no value at that index,
* or if the value is not Boolean.TRUE or the String "true".
*
* @param index The index must be between 0 and length() - 1.
* @return The truth.
*/
public boolean optBoolean(int index) {
return this.optBoolean(index, false);
}
/**
* Get the optional boolean value associated with an index.
* It returns the defaultValue if there is no value at that index or if
* it is not a Boolean or the String "true" or "false" (case insensitive).
*
* @param index The index must be between 0 and length() - 1.
* @param defaultValue A boolean default.
* @return The truth.
*/
public boolean optBoolean(int index, boolean defaultValue) {
try {
return this.getBoolean(index);
} catch (Exception e) {
return defaultValue;
}
}
/**
* Get the optional double value associated with an index.
* NaN is returned if there is no value for the index,
* or if the value is not a number and cannot be converted to a number.
*
* @param index The index must be between 0 and length() - 1.
* @return The value.
*/
public double optDouble(int index) {
return this.optDouble(index, Double.NaN);
}
/**
* Get the optional double value associated with an index.
* The defaultValue is returned if there is no value for the index,
* or if the value is not a number and cannot be converted to a number.
*
* @param index subscript
* @param defaultValue The default value.
* @return The value.
*/
public double optDouble(int index, double defaultValue) {
try {
return this.getDouble(index);
} catch (Exception e) {
return defaultValue;
}
}
/**
* Get the optional int value associated with an index.
* Zero is returned if there is no value for the index,
* or if the value is not a number and cannot be converted to a number.
*
* @param index The index must be between 0 and length() - 1.
* @return The value.
*/
public int optInt(int index) {
return this.optInt(index, 0);
}
/**
* Get the optional int value associated with an index.
* The defaultValue is returned if there is no value for the index,
* or if the value is not a number and cannot be converted to a number.
* @param index The index must be between 0 and length() - 1.
* @param defaultValue The default value.
* @return The value.
*/
public int optInt(int index, int defaultValue) {
try {
return this.getInt(index);
} catch (Exception e) {
return defaultValue;
}
}
/**
* Get the optional JSONArray associated with an index.
* @param index subscript
* @return A JSONArray value, or null if the index has no value,
* or if the value is not a JSONArray.
*/
public JSONArray optJSONArray(int index) {
Object o = this.opt(index);
return o instanceof JSONArray ? (JSONArray)o : null;
}
/**
* Get the optional JSONObject associated with an index.
* Null is returned if the key is not found, or null if the index has
* no value, or if the value is not a JSONObject.
*
* @param index The index must be between 0 and length() - 1.
* @return A JSONObject value.
*/
public JSONObject optJSONObject(int index) {
Object o = this.opt(index);
return o instanceof JSONObject ? (JSONObject)o : null;
}
/**
* Get the optional long value associated with an index.
* Zero is returned if there is no value for the index,
* or if the value is not a number and cannot be converted to a number.
*
* @param index The index must be between 0 and length() - 1.
* @return The value.
*/
public long optLong(int index) {
return this.optLong(index, 0);
}
/**
* Get the optional long value associated with an index.
* The defaultValue is returned if there is no value for the index,
* or if the value is not a number and cannot be converted to a number.
* @param index The index must be between 0 and length() - 1.
* @param defaultValue The default value.
* @return The value.
*/
public long optLong(int index, long defaultValue) {
try {
return this.getLong(index);
} catch (Exception e) {
return defaultValue;
}
}
/**
* Get the optional string value associated with an index. It returns an
* empty string if there is no value at that index. If the value
* is not a string and is not null, then it is coverted to a string.
*
* @param index The index must be between 0 and length() - 1.
* @return A String value.
*/
public String optString(int index) {
return this.optString(index, "");
}
/**
* Get the optional string associated with an index.
* The defaultValue is returned if the key is not found.
*
* @param index The index must be between 0 and length() - 1.
* @param defaultValue The default value.
* @return A String value.
*/
public String optString(int index, String defaultValue) {
Object object = this.opt(index);
return JSONObject.NULL.equals(object)
? defaultValue
: object.toString();
}
/**
* Append a boolean value. This increases the array's length by one.
*
* @param value A boolean value.
* @return this.
*/
public JSONArray put(boolean value) {
this.put(value ? Boolean.TRUE : Boolean.FALSE);
return this;
}
/**
* Put a value in the JSONArray, where the value will be a
* JSONArray which is produced from a Collection.
* @param value A Collection value.
* @return this.
*/
public JSONArray put(Collection value) {
this.put(new JSONArray(value));
return this;
}
/**
* Append a double value. This increases the array's length by one.
*
* @param value A double value.
* @throws JSONException if the value is not finite.
* @return this.
*/
public JSONArray put(double value) throws JSONException {
Double d = new Double(value);
JSONObject.testValidity(d);
this.put(d);
return this;
}
/**
* Append an int value. This increases the array's length by one.
*
* @param value An int value.
* @return this.
*/
public JSONArray put(int value) {
this.put(new Integer(value));
return this;
}
/**
* Append an long value. This increases the array's length by one.
*
* @param value A long value.
* @return this.
*/
public JSONArray put(long value) {
this.put(new Long(value));
return this;
}
/**
* Put a value in the JSONArray, where the value will be a
* JSONObject which is produced from a Map.
* @param value A Map value.
* @return this.
*/
public JSONArray put(Map value) {
this.put(new JSONObject(value));
return this;
}
/**
* Append an object value. This increases the array's length by one.
* @param value An object value. The value should be a
* Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the
* JSONObject.NULL object.
* @return this.
*/
public JSONArray put(Object value) {
this.myArrayList.add(value);
return this;
}
/**
* Put or replace a boolean value in the JSONArray. If the index is greater
* than the length of the JSONArray, then null elements will be added as
* necessary to pad it out.
* @param index The subscript.
* @param value A boolean value.
* @return this.
* @throws JSONException If the index is negative.
*/
public JSONArray put(int index, boolean value) throws JSONException {
this.put(index, value ? Boolean.TRUE : Boolean.FALSE);
return this;
}
/**
* Put a value in the JSONArray, where the value will be a
* JSONArray which is produced from a Collection.
* @param index The subscript.
* @param value A Collection value.
* @return this.
* @throws JSONException If the index is negative or if the value is
* not finite.
*/
public JSONArray put(int index, Collection value) throws JSONException {
this.put(index, new JSONArray(value));
return this;
}
/**
* Put or replace a double value. If the index is greater than the length of
* the JSONArray, then null elements will be added as necessary to pad
* it out.
* @param index The subscript.
* @param value A double value.
* @return this.
* @throws JSONException If the index is negative or if the value is
* not finite.
*/
public JSONArray put(int index, double value) throws JSONException {
this.put(index, new Double(value));
return this;
}
/**
* Put or replace an int value. If the index is greater than the length of
* the JSONArray, then null elements will be added as necessary to pad
* it out.
* @param index The subscript.
* @param value An int value.
* @return this.
* @throws JSONException If the index is negative.
*/
public JSONArray put(int index, int value) throws JSONException {
this.put(index, new Integer(value));
return this;
}
/**
* Put or replace a long value. If the index is greater than the length of
* the JSONArray, then null elements will be added as necessary to pad
* it out.
* @param index The subscript.
* @param value A long value.
* @return this.
* @throws JSONException If the index is negative.
*/
public JSONArray put(int index, long value) throws JSONException {
this.put(index, new Long(value));
return this;
}
/**
* Put a value in the JSONArray, where the value will be a
* JSONObject that is produced from a Map.
* @param index The subscript.
* @param value The Map value.
* @return this.
* @throws JSONException If the index is negative or if the the value is
* an invalid number.
*/
public JSONArray put(int index, Map value) throws JSONException {
this.put(index, new JSONObject(value));
return this;
}
/**
* Put or replace an object value in the JSONArray. If the index is greater
* than the length of the JSONArray, then null elements will be added as
* necessary to pad it out.
* @param index The subscript.
* @param value The value to put into the array. The value should be a
* Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the
* JSONObject.NULL object.
* @return this.
* @throws JSONException If the index is negative or if the the value is
* an invalid number.
*/
public JSONArray put(int index, Object value) throws JSONException {
JSONObject.testValidity(value);
if (index < 0) {
throw new JSONException("JSONArray[" + index + "] not found.");
}
if (index < this.length()) {
this.myArrayList.set(index, value);
} else {
while (index != this.length()) {
this.put(JSONObject.NULL);
}
this.put(value);
}
return this;
}
/**
* Remove an index and close the hole.
* @param index The index of the element to be removed.
* @return The value that was associated with the index,
* or null if there was no value.
*/
public Object remove(int index) {
Object o = this.opt(index);
this.myArrayList.remove(index);
return o;
}
/**
* Produce a JSONObject by combining a JSONArray of names with the values
* of this JSONArray.
* @param names A JSONArray containing a list of key strings. These will be
* paired with the values.
* @return A JSONObject, or null if there are no names or if this JSONArray
* has no values.
* @throws JSONException If any of the names are null.
*/
public JSONObject toJSONObject(JSONArray names) throws JSONException {
if (names == null || names.length() == 0 || this.length() == 0) {
return null;
}
JSONObject jo = new JSONObject();
for (int i = 0; i < names.length(); i += 1) {
jo.put(names.getString(i), this.opt(i));
}
return jo;
}
/**
* Make a JSON text of this JSONArray. For compactness, no
* unnecessary whitespace is added. If it is not possible to produce a
* syntactically correct JSON text then null will be returned instead. This
* could occur if the array contains an invalid number.
* <p>
* Warning: This method assumes that the data structure is acyclical.
*
* @return a printable, displayable, transmittable
* representation of the array.
*/
public String toString() {
try {
return '[' + this.join(",") + ']';
} catch (Exception e) {
return null;
}
}
/**
* Make a prettyprinted JSON text of this JSONArray.
* Warning: This method assumes that the data structure is acyclical.
* @param indentFactor The number of spaces to add to each level of
* indentation.
* @return a printable, displayable, transmittable
* representation of the object, beginning
* with <code>[</code>&nbsp;<small>(left bracket)</small> and ending
* with <code>]</code>&nbsp;<small>(right bracket)</small>.
* @throws JSONException
*/
public String toString(int indentFactor) throws JSONException {
return this.toString(indentFactor, 0);
}
/**
* Make a prettyprinted JSON text of this JSONArray.
* Warning: This method assumes that the data structure is acyclical.
* @param indentFactor The number of spaces to add to each level of
* indentation.
* @param indent The indention of the top level.
* @return a printable, displayable, transmittable
* representation of the array.
* @throws JSONException
*/
String toString(int indentFactor, int indent) throws JSONException {
int len = this.length();
if (len == 0) {
return "[]";
}
int i;
StringBuffer sb = new StringBuffer("[");
if (len == 1) {
sb.append(JSONObject.valueToString(this.myArrayList.get(0),
indentFactor, indent));
} else {
int newindent = indent + indentFactor;
sb.append('\n');
for (i = 0; i < len; i += 1) {
if (i > 0) {
sb.append(",\n");
}
for (int j = 0; j < newindent; j += 1) {
sb.append(' ');
}
sb.append(JSONObject.valueToString(this.myArrayList.get(i),
indentFactor, newindent));
}
sb.append('\n');
for (i = 0; i < indent; i += 1) {
sb.append(' ');
}
}
sb.append(']');
return sb.toString();
}
/**
* Write the contents of the JSONArray as JSON text to a writer.
* For compactness, no whitespace is added.
* <p>
* Warning: This method assumes that the data structure is acyclical.
*
* @return The writer.
* @throws JSONException
*/
public Writer write(Writer writer) throws JSONException {
try {
boolean b = false;
int len = this.length();
writer.write('[');
for (int i = 0; i < len; i += 1) {
if (b) {
writer.write(',');
}
Object v = this.myArrayList.get(i);
if (v instanceof JSONObject) {
((JSONObject)v).write(writer);
} else if (v instanceof JSONArray) {
((JSONArray)v).write(writer);
} else {
writer.write(JSONObject.valueToString(v));
}
b = true;
}
writer.write(']');
return writer;
} catch (IOException e) {
throw new JSONException(e);
}
}
}
@@ -0,0 +1,28 @@
package org.json;
/**
* The JSONException is thrown by the JSON.org classes when things are amiss.
* @author JSON.org
* @version 2010-12-24
*/
public class JSONException extends Exception {
private static final long serialVersionUID = 0;
private Throwable cause;
/**
* Constructs a JSONException with an explanatory message.
* @param message Detail about the reason for the exception.
*/
public JSONException(String message) {
super(message);
}
public JSONException(Throwable cause) {
super(cause.getMessage());
this.cause = cause;
}
public Throwable getCause() {
return this.cause;
}
}
+465
View File
@@ -0,0 +1,465 @@
package org.json;
/*
Copyright (c) 2008 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import java.util.Iterator;
/**
* This provides static methods to convert an XML text into a JSONArray or
* JSONObject, and to covert a JSONArray or JSONObject into an XML text using
* the JsonML transform.
* @author JSON.org
* @version 2011-11-24
*/
public class JSONML {
/**
* Parse XML values and store them in a JSONArray.
* @param x The XMLTokener containing the source string.
* @param arrayForm true if array form, false if object form.
* @param ja The JSONArray that is containing the current tag or null
* if we are at the outermost level.
* @return A JSONArray if the value is the outermost tag, otherwise null.
* @throws JSONException
*/
private static Object parse(
XMLTokener x,
boolean arrayForm,
JSONArray ja
) throws JSONException {
String attribute;
char c;
String closeTag = null;
int i;
JSONArray newja = null;
JSONObject newjo = null;
Object token;
String tagName = null;
// Test for and skip past these forms:
// <!-- ... -->
// <![ ... ]]>
// <! ... >
// <? ... ?>
while (true) {
if (!x.more()) {
throw x.syntaxError("Bad XML");
}
token = x.nextContent();
if (token == XML.LT) {
token = x.nextToken();
if (token instanceof Character) {
if (token == XML.SLASH) {
// Close tag </
token = x.nextToken();
if (!(token instanceof String)) {
throw new JSONException(
"Expected a closing name instead of '" +
token + "'.");
}
if (x.nextToken() != XML.GT) {
throw x.syntaxError("Misshaped close tag");
}
return token;
} else if (token == XML.BANG) {
// <!
c = x.next();
if (c == '-') {
if (x.next() == '-') {
x.skipPast("-->");
}
x.back();
} else if (c == '[') {
token = x.nextToken();
if (token.equals("CDATA") && x.next() == '[') {
if (ja != null) {
ja.put(x.nextCDATA());
}
} else {
throw x.syntaxError("Expected 'CDATA['");
}
} else {
i = 1;
do {
token = x.nextMeta();
if (token == null) {
throw x.syntaxError("Missing '>' after '<!'.");
} else if (token == XML.LT) {
i += 1;
} else if (token == XML.GT) {
i -= 1;
}
} while (i > 0);
}
} else if (token == XML.QUEST) {
// <?
x.skipPast("?>");
} else {
throw x.syntaxError("Misshaped tag");
}
// Open tag <
} else {
if (!(token instanceof String)) {
throw x.syntaxError("Bad tagName '" + token + "'.");
}
tagName = (String)token;
newja = new JSONArray();
newjo = new JSONObject();
if (arrayForm) {
newja.put(tagName);
if (ja != null) {
ja.put(newja);
}
} else {
newjo.put("tagName", tagName);
if (ja != null) {
ja.put(newjo);
}
}
token = null;
for (;;) {
if (token == null) {
token = x.nextToken();
}
if (token == null) {
throw x.syntaxError("Misshaped tag");
}
if (!(token instanceof String)) {
break;
}
// attribute = value
attribute = (String)token;
if (!arrayForm && (attribute == "tagName" || attribute == "childNode")) {
throw x.syntaxError("Reserved attribute.");
}
token = x.nextToken();
if (token == XML.EQ) {
token = x.nextToken();
if (!(token instanceof String)) {
throw x.syntaxError("Missing value");
}
newjo.accumulate(attribute, XML.stringToValue((String)token));
token = null;
} else {
newjo.accumulate(attribute, "");
}
}
if (arrayForm && newjo.length() > 0) {
newja.put(newjo);
}
// Empty tag <.../>
if (token == XML.SLASH) {
if (x.nextToken() != XML.GT) {
throw x.syntaxError("Misshaped tag");
}
if (ja == null) {
if (arrayForm) {
return newja;
} else {
return newjo;
}
}
// Content, between <...> and </...>
} else {
if (token != XML.GT) {
throw x.syntaxError("Misshaped tag");
}
closeTag = (String)parse(x, arrayForm, newja);
if (closeTag != null) {
if (!closeTag.equals(tagName)) {
throw x.syntaxError("Mismatched '" + tagName +
"' and '" + closeTag + "'");
}
tagName = null;
if (!arrayForm && newja.length() > 0) {
newjo.put("childNodes", newja);
}
if (ja == null) {
if (arrayForm) {
return newja;
} else {
return newjo;
}
}
}
}
}
} else {
if (ja != null) {
ja.put(token instanceof String
? XML.stringToValue((String)token)
: token);
}
}
}
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONArray using the JsonML transform. Each XML tag is represented as
* a JSONArray in which the first element is the tag name. If the tag has
* attributes, then the second element will be JSONObject containing the
* name/value pairs. If the tag contains children, then strings and
* JSONArrays will represent the child tags.
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
* @param string The source string.
* @return A JSONArray containing the structured data from the XML string.
* @throws JSONException
*/
public static JSONArray toJSONArray(String string) throws JSONException {
return toJSONArray(new XMLTokener(string));
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONArray using the JsonML transform. Each XML tag is represented as
* a JSONArray in which the first element is the tag name. If the tag has
* attributes, then the second element will be JSONObject containing the
* name/value pairs. If the tag contains children, then strings and
* JSONArrays will represent the child content and tags.
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
* @param x An XMLTokener.
* @return A JSONArray containing the structured data from the XML string.
* @throws JSONException
*/
public static JSONArray toJSONArray(XMLTokener x) throws JSONException {
return (JSONArray)parse(x, true, null);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject using the JsonML transform. Each XML tag is represented as
* a JSONObject with a "tagName" property. If the tag has attributes, then
* the attributes will be in the JSONObject as properties. If the tag
* contains children, the object will have a "childNodes" property which
* will be an array of strings and JsonML JSONObjects.
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
* @param x An XMLTokener of the XML source text.
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException
*/
public static JSONObject toJSONObject(XMLTokener x) throws JSONException {
return (JSONObject)parse(x, false, null);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject using the JsonML transform. Each XML tag is represented as
* a JSONObject with a "tagName" property. If the tag has attributes, then
* the attributes will be in the JSONObject as properties. If the tag
* contains children, the object will have a "childNodes" property which
* will be an array of strings and JsonML JSONObjects.
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
* @param string The XML source text.
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException
*/
public static JSONObject toJSONObject(String string) throws JSONException {
return toJSONObject(new XMLTokener(string));
}
/**
* Reverse the JSONML transformation, making an XML text from a JSONArray.
* @param ja A JSONArray.
* @return An XML string.
* @throws JSONException
*/
public static String toString(JSONArray ja) throws JSONException {
int i;
JSONObject jo;
String key;
Iterator keys;
int length;
Object object;
StringBuffer sb = new StringBuffer();
String tagName;
String value;
// Emit <tagName
tagName = ja.getString(0);
XML.noSpace(tagName);
tagName = XML.escape(tagName);
sb.append('<');
sb.append(tagName);
object = ja.opt(1);
if (object instanceof JSONObject) {
i = 2;
jo = (JSONObject)object;
// Emit the attributes
keys = jo.keys();
while (keys.hasNext()) {
key = keys.next().toString();
XML.noSpace(key);
value = jo.optString(key);
if (value != null) {
sb.append(' ');
sb.append(XML.escape(key));
sb.append('=');
sb.append('"');
sb.append(XML.escape(value));
sb.append('"');
}
}
} else {
i = 1;
}
//Emit content in body
length = ja.length();
if (i >= length) {
sb.append('/');
sb.append('>');
} else {
sb.append('>');
do {
object = ja.get(i);
i += 1;
if (object != null) {
if (object instanceof String) {
sb.append(XML.escape(object.toString()));
} else if (object instanceof JSONObject) {
sb.append(toString((JSONObject)object));
} else if (object instanceof JSONArray) {
sb.append(toString((JSONArray)object));
}
}
} while (i < length);
sb.append('<');
sb.append('/');
sb.append(tagName);
sb.append('>');
}
return sb.toString();
}
/**
* Reverse the JSONML transformation, making an XML text from a JSONObject.
* The JSONObject must contain a "tagName" property. If it has children,
* then it must have a "childNodes" property containing an array of objects.
* The other properties are attributes with string values.
* @param jo A JSONObject.
* @return An XML string.
* @throws JSONException
*/
public static String toString(JSONObject jo) throws JSONException {
StringBuffer sb = new StringBuffer();
int i;
JSONArray ja;
String key;
Iterator keys;
int length;
Object object;
String tagName;
String value;
//Emit <tagName
tagName = jo.optString("tagName");
if (tagName == null) {
return XML.escape(jo.toString());
}
XML.noSpace(tagName);
tagName = XML.escape(tagName);
sb.append('<');
sb.append(tagName);
//Emit the attributes
keys = jo.keys();
while (keys.hasNext()) {
key = keys.next().toString();
if (!"tagName".equals(key) && !"childNodes".equals(key)) {
XML.noSpace(key);
value = jo.optString(key);
if (value != null) {
sb.append(' ');
sb.append(XML.escape(key));
sb.append('=');
sb.append('"');
sb.append(XML.escape(value));
sb.append('"');
}
}
}
//Emit content in body
ja = jo.optJSONArray("childNodes");
if (ja == null) {
sb.append('/');
sb.append('>');
} else {
sb.append('>');
length = ja.length();
for (i = 0; i < length; i += 1) {
object = ja.get(i);
if (object != null) {
if (object instanceof String) {
sb.append(XML.escape(object.toString()));
} else if (object instanceof JSONObject) {
sb.append(toString((JSONObject)object));
} else if (object instanceof JSONArray) {
sb.append(toString((JSONArray)object));
} else {
sb.append(object.toString());
}
}
}
sb.append('<');
sb.append('/');
sb.append(tagName);
sb.append('>');
}
return sb.toString();
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
package org.json;
/**
* The <code>JSONString</code> interface allows a <code>toJSONString()</code>
* method so that a class can change the behavior of
* <code>JSONObject.toString()</code>, <code>JSONArray.toString()</code>,
* and <code>JSONWriter.value(</code>Object<code>)</code>. The
* <code>toJSONString</code> method will be used instead of the default behavior
* of using the Object's <code>toString()</code> method and quoting the result.
*/
public interface JSONString {
/**
* The <code>toJSONString</code> method allows a class to produce its own JSON
* serialization.
*
* @return A strictly syntactically correct JSON text.
*/
public String toJSONString();
}
+78
View File
@@ -0,0 +1,78 @@
package org.json;
/*
Copyright (c) 2006 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import java.io.StringWriter;
/**
* JSONStringer provides a quick and convenient way of producing JSON text.
* The texts produced strictly conform to JSON syntax rules. No whitespace is
* added, so the results are ready for transmission or storage. Each instance of
* JSONStringer can produce one JSON text.
* <p>
* A JSONStringer instance provides a <code>value</code> method for appending
* values to the
* text, and a <code>key</code>
* method for adding keys before values in objects. There are <code>array</code>
* and <code>endArray</code> methods that make and bound array values, and
* <code>object</code> and <code>endObject</code> methods which make and bound
* object values. All of these methods return the JSONWriter instance,
* permitting cascade style. For example, <pre>
* myString = new JSONStringer()
* .object()
* .key("JSON")
* .value("Hello, World!")
* .endObject()
* .toString();</pre> which produces the string <pre>
* {"JSON":"Hello, World!"}</pre>
* <p>
* The first method called must be <code>array</code> or <code>object</code>.
* There are no methods for adding commas or colons. JSONStringer adds them for
* you. Objects and arrays can be nested up to 20 levels deep.
* <p>
* This can sometimes be easier than using a JSONObject to build a string.
* @author JSON.org
* @version 2008-09-18
*/
public class JSONStringer extends JSONWriter {
/**
* Make a fresh JSONStringer. It can be used to build one JSON text.
*/
public JSONStringer() {
super(new StringWriter());
}
/**
* Return the JSON text. This method is used to obtain the product of the
* JSONStringer instance. It will return <code>null</code> if there was a
* problem in the construction of the JSON text (such as the calls to
* <code>array</code> were not properly balanced with calls to
* <code>endArray</code>).
* @return The JSON text.
*/
public String toString() {
return this.mode == 'd' ? this.writer.toString() : null;
}
}
@@ -0,0 +1,446 @@
package org.json;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
/*
Copyright (c) 2002 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/**
* A JSONTokener takes a source string and extracts characters and tokens from
* it. It is used by the JSONObject and JSONArray constructors to parse
* JSON source strings.
* @author JSON.org
* @version 2011-11-24
*/
public class JSONTokener {
private int character;
private boolean eof;
private int index;
private int line;
private char previous;
private final Reader reader;
private boolean usePrevious;
/**
* Construct a JSONTokener from a Reader.
*
* @param reader A reader.
*/
public JSONTokener(Reader reader) {
this.reader = reader.markSupported()
? reader
: new BufferedReader(reader);
this.eof = false;
this.usePrevious = false;
this.previous = 0;
this.index = 0;
this.character = 1;
this.line = 1;
}
/**
* Construct a JSONTokener from an InputStream.
*/
public JSONTokener(InputStream inputStream) throws JSONException {
this(new InputStreamReader(inputStream));
}
/**
* Construct a JSONTokener from a string.
*
* @param s A source string.
*/
public JSONTokener(String s) {
this(new StringReader(s));
}
/**
* Back up one character. This provides a sort of lookahead capability,
* so that you can test for a digit or letter before attempting to parse
* the next number or identifier.
*/
public void back() throws JSONException {
if (this.usePrevious || this.index <= 0) {
throw new JSONException("Stepping back two steps is not supported");
}
this.index -= 1;
this.character -= 1;
this.usePrevious = true;
this.eof = false;
}
/**
* Get the hex value of a character (base16).
* @param c A character between '0' and '9' or between 'A' and 'F' or
* between 'a' and 'f'.
* @return An int between 0 and 15, or -1 if c was not a hex digit.
*/
public static int dehexchar(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'A' && c <= 'F') {
return c - ('A' - 10);
}
if (c >= 'a' && c <= 'f') {
return c - ('a' - 10);
}
return -1;
}
public boolean end() {
return this.eof && !this.usePrevious;
}
/**
* Determine if the source string still contains characters that next()
* can consume.
* @return true if not yet at the end of the source.
*/
public boolean more() throws JSONException {
this.next();
if (this.end()) {
return false;
}
this.back();
return true;
}
/**
* Get the next character in the source string.
*
* @return The next character, or 0 if past the end of the source string.
*/
public char next() throws JSONException {
int c;
if (this.usePrevious) {
this.usePrevious = false;
c = this.previous;
} else {
try {
c = this.reader.read();
} catch (IOException exception) {
throw new JSONException(exception);
}
if (c <= 0) { // End of stream
this.eof = true;
c = 0;
}
}
this.index += 1;
if (this.previous == '\r') {
this.line += 1;
this.character = c == '\n' ? 0 : 1;
} else if (c == '\n') {
this.line += 1;
this.character = 0;
} else {
this.character += 1;
}
this.previous = (char) c;
return this.previous;
}
/**
* Consume the next character, and check that it matches a specified
* character.
* @param c The character to match.
* @return The character.
* @throws JSONException if the character does not match.
*/
public char next(char c) throws JSONException {
char n = this.next();
if (n != c) {
throw this.syntaxError("Expected '" + c + "' and instead saw '" +
n + "'");
}
return n;
}
/**
* Get the next n characters.
*
* @param n The number of characters to take.
* @return A string of n characters.
* @throws JSONException
* Substring bounds error if there are not
* n characters remaining in the source string.
*/
public String next(int n) throws JSONException {
if (n == 0) {
return "";
}
char[] chars = new char[n];
int pos = 0;
while (pos < n) {
chars[pos] = this.next();
if (this.end()) {
throw this.syntaxError("Substring bounds error");
}
pos += 1;
}
return new String(chars);
}
/**
* Get the next char in the string, skipping whitespace.
* @throws JSONException
* @return A character, or 0 if there are no more characters.
*/
public char nextClean() throws JSONException {
for (;;) {
char c = this.next();
if (c == 0 || c > ' ') {
return c;
}
}
}
/**
* Return the characters up to the next close quote character.
* Backslash processing is done. The formal JSON format does not
* allow strings in single quotes, but an implementation is allowed to
* accept them.
* @param quote The quoting character, either
* <code>"</code>&nbsp;<small>(double quote)</small> or
* <code>'</code>&nbsp;<small>(single quote)</small>.
* @return A String.
* @throws JSONException Unterminated string.
*/
public String nextString(char quote) throws JSONException {
char c;
StringBuffer sb = new StringBuffer();
for (;;) {
c = this.next();
switch (c) {
case 0:
case '\n':
case '\r':
throw this.syntaxError("Unterminated string");
case '\\':
c = this.next();
switch (c) {
case 'b':
sb.append('\b');
break;
case 't':
sb.append('\t');
break;
case 'n':
sb.append('\n');
break;
case 'f':
sb.append('\f');
break;
case 'r':
sb.append('\r');
break;
case 'u':
sb.append((char)Integer.parseInt(this.next(4), 16));
break;
case '"':
case '\'':
case '\\':
case '/':
sb.append(c);
break;
default:
throw this.syntaxError("Illegal escape.");
}
break;
default:
if (c == quote) {
return sb.toString();
}
sb.append(c);
}
}
}
/**
* Get the text up but not including the specified character or the
* end of line, whichever comes first.
* @param delimiter A delimiter character.
* @return A string.
*/
public String nextTo(char delimiter) throws JSONException {
StringBuffer sb = new StringBuffer();
for (;;) {
char c = this.next();
if (c == delimiter || c == 0 || c == '\n' || c == '\r') {
if (c != 0) {
this.back();
}
return sb.toString().trim();
}
sb.append(c);
}
}
/**
* Get the text up but not including one of the specified delimiter
* characters or the end of line, whichever comes first.
* @param delimiters A set of delimiter characters.
* @return A string, trimmed.
*/
public String nextTo(String delimiters) throws JSONException {
char c;
StringBuffer sb = new StringBuffer();
for (;;) {
c = this.next();
if (delimiters.indexOf(c) >= 0 || c == 0 ||
c == '\n' || c == '\r') {
if (c != 0) {
this.back();
}
return sb.toString().trim();
}
sb.append(c);
}
}
/**
* Get the next value. The value can be a Boolean, Double, Integer,
* JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
* @throws JSONException If syntax error.
*
* @return An object.
*/
public Object nextValue() throws JSONException {
char c = this.nextClean();
String string;
switch (c) {
case '"':
case '\'':
return this.nextString(c);
case '{':
this.back();
return new JSONObject(this);
case '[':
this.back();
return new JSONArray(this);
}
/*
* Handle unquoted text. This could be the values true, false, or
* null, or it can be a number. An implementation (such as this one)
* is allowed to also accept non-standard forms.
*
* Accumulate characters until we reach the end of the text or a
* formatting character.
*/
StringBuffer sb = new StringBuffer();
while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
sb.append(c);
c = this.next();
}
this.back();
string = sb.toString().trim();
if ("".equals(string)) {
throw this.syntaxError("Missing value");
}
return JSONObject.stringToValue(string);
}
/**
* Skip characters until the next character is the requested character.
* If the requested character is not found, no characters are skipped.
* @param to A character to skip to.
* @return The requested character, or zero if the requested character
* is not found.
*/
public char skipTo(char to) throws JSONException {
char c;
try {
int startIndex = this.index;
int startCharacter = this.character;
int startLine = this.line;
this.reader.mark(Integer.MAX_VALUE);
do {
c = this.next();
if (c == 0) {
this.reader.reset();
this.index = startIndex;
this.character = startCharacter;
this.line = startLine;
return c;
}
} while (c != to);
} catch (IOException exc) {
throw new JSONException(exc);
}
this.back();
return c;
}
/**
* Make a JSONException to signal a syntax error.
*
* @param message The error message.
* @return A JSONException object, suitable for throwing
*/
public JSONException syntaxError(String message) {
return new JSONException(message + this.toString());
}
/**
* Make a printable string of this JSONTokener.
*
* @return " at {index} [character {character} line {line}]"
*/
public String toString() {
return " at " + this.index + " [character " + this.character + " line " +
this.line + "]";
}
}
+327
View File
@@ -0,0 +1,327 @@
package org.json;
import java.io.IOException;
import java.io.Writer;
/*
Copyright (c) 2006 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/**
* JSONWriter provides a quick and convenient way of producing JSON text.
* The texts produced strictly conform to JSON syntax rules. No whitespace is
* added, so the results are ready for transmission or storage. Each instance of
* JSONWriter can produce one JSON text.
* <p>
* A JSONWriter instance provides a <code>value</code> method for appending
* values to the
* text, and a <code>key</code>
* method for adding keys before values in objects. There are <code>array</code>
* and <code>endArray</code> methods that make and bound array values, and
* <code>object</code> and <code>endObject</code> methods which make and bound
* object values. All of these methods return the JSONWriter instance,
* permitting a cascade style. For example, <pre>
* new JSONWriter(myWriter)
* .object()
* .key("JSON")
* .value("Hello, World!")
* .endObject();</pre> which writes <pre>
* {"JSON":"Hello, World!"}</pre>
* <p>
* The first method called must be <code>array</code> or <code>object</code>.
* There are no methods for adding commas or colons. JSONWriter adds them for
* you. Objects and arrays can be nested up to 20 levels deep.
* <p>
* This can sometimes be easier than using a JSONObject to build a string.
* @author JSON.org
* @version 2011-11-24
*/
public class JSONWriter {
private static final int maxdepth = 200;
/**
* The comma flag determines if a comma should be output before the next
* value.
*/
private boolean comma;
/**
* The current mode. Values:
* 'a' (array),
* 'd' (done),
* 'i' (initial),
* 'k' (key),
* 'o' (object).
*/
protected char mode;
/**
* The object/array stack.
*/
private final JSONObject stack[];
/**
* The stack top index. A value of 0 indicates that the stack is empty.
*/
private int top;
/**
* The writer that will receive the output.
*/
protected Writer writer;
/**
* Make a fresh JSONWriter. It can be used to build one JSON text.
*/
public JSONWriter(Writer w) {
this.comma = false;
this.mode = 'i';
this.stack = new JSONObject[maxdepth];
this.top = 0;
this.writer = w;
}
/**
* Append a value.
* @param string A string value.
* @return this
* @throws JSONException If the value is out of sequence.
*/
private JSONWriter append(String string) throws JSONException {
if (string == null) {
throw new JSONException("Null pointer");
}
if (this.mode == 'o' || this.mode == 'a') {
try {
if (this.comma && this.mode == 'a') {
this.writer.write(',');
}
this.writer.write(string);
} catch (IOException e) {
throw new JSONException(e);
}
if (this.mode == 'o') {
this.mode = 'k';
}
this.comma = true;
return this;
}
throw new JSONException("Value out of sequence.");
}
/**
* Begin appending a new array. All values until the balancing
* <code>endArray</code> will be appended to this array. The
* <code>endArray</code> method must be called to mark the array's end.
* @return this
* @throws JSONException If the nesting is too deep, or if the object is
* started in the wrong place (for example as a key or after the end of the
* outermost array or object).
*/
public JSONWriter array() throws JSONException {
if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') {
this.push(null);
this.append("[");
this.comma = false;
return this;
}
throw new JSONException("Misplaced array.");
}
/**
* End something.
* @param mode Mode
* @param c Closing character
* @return this
* @throws JSONException If unbalanced.
*/
private JSONWriter end(char mode, char c) throws JSONException {
if (this.mode != mode) {
throw new JSONException(mode == 'a'
? "Misplaced endArray."
: "Misplaced endObject.");
}
this.pop(mode);
try {
this.writer.write(c);
} catch (IOException e) {
throw new JSONException(e);
}
this.comma = true;
return this;
}
/**
* End an array. This method most be called to balance calls to
* <code>array</code>.
* @return this
* @throws JSONException If incorrectly nested.
*/
public JSONWriter endArray() throws JSONException {
return this.end('a', ']');
}
/**
* End an object. This method most be called to balance calls to
* <code>object</code>.
* @return this
* @throws JSONException If incorrectly nested.
*/
public JSONWriter endObject() throws JSONException {
return this.end('k', '}');
}
/**
* Append a key. The key will be associated with the next value. In an
* object, every value must be preceded by a key.
* @param string A key string.
* @return this
* @throws JSONException If the key is out of place. For example, keys
* do not belong in arrays or if the key is null.
*/
public JSONWriter key(String string) throws JSONException {
if (string == null) {
throw new JSONException("Null key.");
}
if (this.mode == 'k') {
try {
this.stack[this.top - 1].putOnce(string, Boolean.TRUE);
if (this.comma) {
this.writer.write(',');
}
this.writer.write(JSONObject.quote(string));
this.writer.write(':');
this.comma = false;
this.mode = 'o';
return this;
} catch (IOException e) {
throw new JSONException(e);
}
}
throw new JSONException("Misplaced key.");
}
/**
* Begin appending a new object. All keys and values until the balancing
* <code>endObject</code> will be appended to this object. The
* <code>endObject</code> method must be called to mark the object's end.
* @return this
* @throws JSONException If the nesting is too deep, or if the object is
* started in the wrong place (for example as a key or after the end of the
* outermost array or object).
*/
public JSONWriter object() throws JSONException {
if (this.mode == 'i') {
this.mode = 'o';
}
if (this.mode == 'o' || this.mode == 'a') {
this.append("{");
this.push(new JSONObject());
this.comma = false;
return this;
}
throw new JSONException("Misplaced object.");
}
/**
* Pop an array or object scope.
* @param c The scope to close.
* @throws JSONException If nesting is wrong.
*/
private void pop(char c) throws JSONException {
if (this.top <= 0) {
throw new JSONException("Nesting error.");
}
char m = this.stack[this.top - 1] == null ? 'a' : 'k';
if (m != c) {
throw new JSONException("Nesting error.");
}
this.top -= 1;
this.mode = this.top == 0
? 'd'
: this.stack[this.top - 1] == null
? 'a'
: 'k';
}
/**
* Push an array or object scope.
* @param c The scope to open.
* @throws JSONException If nesting is too deep.
*/
private void push(JSONObject jo) throws JSONException {
if (this.top >= maxdepth) {
throw new JSONException("Nesting too deep.");
}
this.stack[this.top] = jo;
this.mode = jo == null ? 'a' : 'k';
this.top += 1;
}
/**
* Append either the value <code>true</code> or the value
* <code>false</code>.
* @param b A boolean.
* @return this
* @throws JSONException
*/
public JSONWriter value(boolean b) throws JSONException {
return this.append(b ? "true" : "false");
}
/**
* Append a double value.
* @param d A double.
* @return this
* @throws JSONException If the number is not finite.
*/
public JSONWriter value(double d) throws JSONException {
return this.value(new Double(d));
}
/**
* Append a long value.
* @param l A long.
* @return this
* @throws JSONException
*/
public JSONWriter value(long l) throws JSONException {
return this.append(Long.toString(l));
}
/**
* Append an object value.
* @param object The object to append. It can be null, or a Boolean, Number,
* String, JSONObject, or JSONArray, or an object that implements JSONString.
* @return this
* @throws JSONException If the value is out of sequence.
*/
public JSONWriter value(Object object) throws JSONException {
return this.append(JSONObject.valueToString(object));
}
}
@@ -0,0 +1,508 @@
package org.json;
/*
Copyright (c) 2002 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import java.util.Iterator;
/**
* This provides static methods to convert an XML text into a JSONObject,
* and to covert a JSONObject into an XML text.
* @author JSON.org
* @version 2011-02-11
*/
public class XML {
/** The Character '&'. */
public static final Character AMP = new Character('&');
/** The Character '''. */
public static final Character APOS = new Character('\'');
/** The Character '!'. */
public static final Character BANG = new Character('!');
/** The Character '='. */
public static final Character EQ = new Character('=');
/** The Character '>'. */
public static final Character GT = new Character('>');
/** The Character '<'. */
public static final Character LT = new Character('<');
/** The Character '?'. */
public static final Character QUEST = new Character('?');
/** The Character '"'. */
public static final Character QUOT = new Character('"');
/** The Character '/'. */
public static final Character SLASH = new Character('/');
/**
* Replace special characters with XML escapes:
* <pre>
* &amp; <small>(ampersand)</small> is replaced by &amp;amp;
* &lt; <small>(less than)</small> is replaced by &amp;lt;
* &gt; <small>(greater than)</small> is replaced by &amp;gt;
* &quot; <small>(double quote)</small> is replaced by &amp;quot;
* </pre>
* @param string The string to be escaped.
* @return The escaped string.
*/
public static String escape(String string) {
StringBuffer sb = new StringBuffer();
for (int i = 0, length = string.length(); i < length; i++) {
char c = string.charAt(i);
switch (c) {
case '&':
sb.append("&amp;");
break;
case '<':
sb.append("&lt;");
break;
case '>':
sb.append("&gt;");
break;
case '"':
sb.append("&quot;");
break;
case '\'':
sb.append("&apos;");
break;
default:
sb.append(c);
}
}
return sb.toString();
}
/**
* Throw an exception if the string contains whitespace.
* Whitespace is not allowed in tagNames and attributes.
* @param string
* @throws JSONException
*/
public static void noSpace(String string) throws JSONException {
int i, length = string.length();
if (length == 0) {
throw new JSONException("Empty string.");
}
for (i = 0; i < length; i += 1) {
if (Character.isWhitespace(string.charAt(i))) {
throw new JSONException("'" + string +
"' contains a space character.");
}
}
}
/**
* Scan the content following the named tag, attaching it to the context.
* @param x The XMLTokener containing the source string.
* @param context The JSONObject that will include the new material.
* @param name The tag name.
* @return true if the close tag is processed.
* @throws JSONException
*/
private static boolean parse(XMLTokener x, JSONObject context,
String name) throws JSONException {
char c;
int i;
JSONObject jsonobject = null;
String string;
String tagName;
Object token;
// Test for and skip past these forms:
// <!-- ... -->
// <! ... >
// <![ ... ]]>
// <? ... ?>
// Report errors for these forms:
// <>
// <=
// <<
token = x.nextToken();
// <!
if (token == BANG) {
c = x.next();
if (c == '-') {
if (x.next() == '-') {
x.skipPast("-->");
return false;
}
x.back();
} else if (c == '[') {
token = x.nextToken();
if ("CDATA".equals(token)) {
if (x.next() == '[') {
string = x.nextCDATA();
if (string.length() > 0) {
context.accumulate("content", string);
}
return false;
}
}
throw x.syntaxError("Expected 'CDATA['");
}
i = 1;
do {
token = x.nextMeta();
if (token == null) {
throw x.syntaxError("Missing '>' after '<!'.");
} else if (token == LT) {
i += 1;
} else if (token == GT) {
i -= 1;
}
} while (i > 0);
return false;
} else if (token == QUEST) {
// <?
x.skipPast("?>");
return false;
} else if (token == SLASH) {
// Close tag </
token = x.nextToken();
if (name == null) {
throw x.syntaxError("Mismatched close tag " + token);
}
if (!token.equals(name)) {
throw x.syntaxError("Mismatched " + name + " and " + token);
}
if (x.nextToken() != GT) {
throw x.syntaxError("Misshaped close tag");
}
return true;
} else if (token instanceof Character) {
throw x.syntaxError("Misshaped tag");
// Open tag <
} else {
tagName = (String)token;
token = null;
jsonobject = new JSONObject();
for (;;) {
if (token == null) {
token = x.nextToken();
}
// attribute = value
if (token instanceof String) {
string = (String)token;
token = x.nextToken();
if (token == EQ) {
token = x.nextToken();
if (!(token instanceof String)) {
throw x.syntaxError("Missing value");
}
jsonobject.accumulate(string,
XML.stringToValue((String)token));
token = null;
} else {
jsonobject.accumulate(string, "");
}
// Empty tag <.../>
} else if (token == SLASH) {
if (x.nextToken() != GT) {
throw x.syntaxError("Misshaped tag");
}
if (jsonobject.length() > 0) {
context.accumulate(tagName, jsonobject);
} else {
context.accumulate(tagName, "");
}
return false;
// Content, between <...> and </...>
} else if (token == GT) {
for (;;) {
token = x.nextContent();
if (token == null) {
if (tagName != null) {
throw x.syntaxError("Unclosed tag " + tagName);
}
return false;
} else if (token instanceof String) {
string = (String)token;
if (string.length() > 0) {
jsonobject.accumulate("content",
XML.stringToValue(string));
}
// Nested element
} else if (token == LT) {
if (parse(x, jsonobject, tagName)) {
if (jsonobject.length() == 0) {
context.accumulate(tagName, "");
} else if (jsonobject.length() == 1 &&
jsonobject.opt("content") != null) {
context.accumulate(tagName,
jsonobject.opt("content"));
} else {
context.accumulate(tagName, jsonobject);
}
return false;
}
}
}
} else {
throw x.syntaxError("Misshaped tag");
}
}
}
}
/**
* Try to convert a string into a number, boolean, or null. If the string
* can't be converted, return the string. This is much less ambitious than
* JSONObject.stringToValue, especially because it does not attempt to
* convert plus forms, octal forms, hex forms, or E forms lacking decimal
* points.
* @param string A String.
* @return A simple JSON value.
*/
public static Object stringToValue(String string) {
if ("".equals(string)) {
return string;
}
if ("true".equalsIgnoreCase(string)) {
return Boolean.TRUE;
}
if ("false".equalsIgnoreCase(string)) {
return Boolean.FALSE;
}
if ("null".equalsIgnoreCase(string)) {
return JSONObject.NULL;
}
if ("0".equals(string)) {
return new Integer(0);
}
// If it might be a number, try converting it. If that doesn't work,
// return the string.
try {
char initial = string.charAt(0);
boolean negative = false;
if (initial == '-') {
initial = string.charAt(1);
negative = true;
}
if (initial == '0' && string.charAt(negative ? 2 : 1) == '0') {
return string;
}
if ((initial >= '0' && initial <= '9')) {
if (string.indexOf('.') >= 0) {
return Double.valueOf(string);
} else if (string.indexOf('e') < 0 && string.indexOf('E') < 0) {
Long myLong = new Long(string);
if (myLong.longValue() == myLong.intValue()) {
return new Integer(myLong.intValue());
} else {
return myLong;
}
}
}
} catch (Exception ignore) {
}
return string;
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject. Some information may be lost in this transformation
* because JSON is a data format and XML is a document format. XML uses
* elements, attributes, and content text, while JSON uses unordered
* collections of name/value pairs and arrays of values. JSON does not
* does not like to distinguish between elements and attributes.
* Sequences of similar elements are represented as JSONArrays. Content
* text may be placed in a "content" member. Comments, prologs, DTDs, and
* <code>&lt;[ [ ]]></code> are ignored.
* @param string The source string.
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException
*/
public static JSONObject toJSONObject(String string) throws JSONException {
JSONObject jo = new JSONObject();
XMLTokener x = new XMLTokener(string);
while (x.more() && x.skipPast("<")) {
parse(x, jo, null);
}
return jo;
}
/**
* Convert a JSONObject into a well-formed, element-normal XML string.
* @param object A JSONObject.
* @return A string.
* @throws JSONException
*/
public static String toString(Object object) throws JSONException {
return toString(object, null);
}
/**
* Convert a JSONObject into a well-formed, element-normal XML string.
* @param object A JSONObject.
* @param tagName The optional name of the enclosing tag.
* @return A string.
* @throws JSONException
*/
public static String toString(Object object, String tagName)
throws JSONException {
StringBuffer sb = new StringBuffer();
int i;
JSONArray ja;
JSONObject jo;
String key;
Iterator keys;
int length;
String string;
Object value;
if (object instanceof JSONObject) {
// Emit <tagName>
if (tagName != null) {
sb.append('<');
sb.append(tagName);
sb.append('>');
}
// Loop thru the keys.
jo = (JSONObject)object;
keys = jo.keys();
while (keys.hasNext()) {
key = keys.next().toString();
value = jo.opt(key);
if (value == null) {
value = "";
}
if (value instanceof String) {
string = (String)value;
} else {
string = null;
}
// Emit content in body
if ("content".equals(key)) {
if (value instanceof JSONArray) {
ja = (JSONArray)value;
length = ja.length();
for (i = 0; i < length; i += 1) {
if (i > 0) {
sb.append('\n');
}
sb.append(escape(ja.get(i).toString()));
}
} else {
sb.append(escape(value.toString()));
}
// Emit an array of similar keys
} else if (value instanceof JSONArray) {
ja = (JSONArray)value;
length = ja.length();
for (i = 0; i < length; i += 1) {
value = ja.get(i);
if (value instanceof JSONArray) {
sb.append('<');
sb.append(key);
sb.append('>');
sb.append(toString(value));
sb.append("</");
sb.append(key);
sb.append('>');
} else {
sb.append(toString(value, key));
}
}
} else if ("".equals(value)) {
sb.append('<');
sb.append(key);
sb.append("/>");
// Emit a new tag <k>
} else {
sb.append(toString(value, key));
}
}
if (tagName != null) {
// Emit the </tagname> close tag
sb.append("</");
sb.append(tagName);
sb.append('>');
}
return sb.toString();
// XML does not have good support for arrays. If an array appears in a place
// where XML is lacking, synthesize an <array> element.
} else {
if (object.getClass().isArray()) {
object = new JSONArray(object);
}
if (object instanceof JSONArray) {
ja = (JSONArray)object;
length = ja.length();
for (i = 0; i < length; i += 1) {
sb.append(toString(ja.opt(i), tagName == null ? "array" : tagName));
}
return sb.toString();
} else {
string = (object == null) ? "null" : escape(object.toString());
return (tagName == null) ? "\"" + string + "\"" :
(string.length() == 0) ? "<" + tagName + "/>" :
"<" + tagName + ">" + string + "</" + tagName + ">";
}
}
}
}
@@ -0,0 +1,365 @@
package org.json;
/*
Copyright (c) 2002 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/**
* The XMLTokener extends the JSONTokener to provide additional methods
* for the parsing of XML texts.
* @author JSON.org
* @version 2010-12-24
*/
public class XMLTokener extends JSONTokener {
/** The table of entity values. It initially contains Character values for
* amp, apos, gt, lt, quot.
*/
public static final java.util.HashMap entity;
static {
entity = new java.util.HashMap(8);
entity.put("amp", XML.AMP);
entity.put("apos", XML.APOS);
entity.put("gt", XML.GT);
entity.put("lt", XML.LT);
entity.put("quot", XML.QUOT);
}
/**
* Construct an XMLTokener from a string.
* @param s A source string.
*/
public XMLTokener(String s) {
super(s);
}
/**
* Get the text in the CDATA block.
* @return The string up to the <code>]]&gt;</code>.
* @throws JSONException If the <code>]]&gt;</code> is not found.
*/
public String nextCDATA() throws JSONException {
char c;
int i;
StringBuffer sb = new StringBuffer();
for (;;) {
c = next();
if (end()) {
throw syntaxError("Unclosed CDATA");
}
sb.append(c);
i = sb.length() - 3;
if (i >= 0 && sb.charAt(i) == ']' &&
sb.charAt(i + 1) == ']' && sb.charAt(i + 2) == '>') {
sb.setLength(i);
return sb.toString();
}
}
}
/**
* Get the next XML outer token, trimming whitespace. There are two kinds
* of tokens: the '<' character which begins a markup tag, and the content
* text between markup tags.
*
* @return A string, or a '<' Character, or null if there is no more
* source text.
* @throws JSONException
*/
public Object nextContent() throws JSONException {
char c;
StringBuffer sb;
do {
c = next();
} while (Character.isWhitespace(c));
if (c == 0) {
return null;
}
if (c == '<') {
return XML.LT;
}
sb = new StringBuffer();
for (;;) {
if (c == '<' || c == 0) {
back();
return sb.toString().trim();
}
if (c == '&') {
sb.append(nextEntity(c));
} else {
sb.append(c);
}
c = next();
}
}
/**
* Return the next entity. These entities are translated to Characters:
* <code>&amp; &apos; &gt; &lt; &quot;</code>.
* @param ampersand An ampersand character.
* @return A Character or an entity String if the entity is not recognized.
* @throws JSONException If missing ';' in XML entity.
*/
public Object nextEntity(char ampersand) throws JSONException {
StringBuffer sb = new StringBuffer();
for (;;) {
char c = next();
if (Character.isLetterOrDigit(c) || c == '#') {
sb.append(Character.toLowerCase(c));
} else if (c == ';') {
break;
} else {
throw syntaxError("Missing ';' in XML entity: &" + sb);
}
}
String string = sb.toString();
Object object = entity.get(string);
return object != null ? object : ampersand + string + ";";
}
/**
* Returns the next XML meta token. This is used for skipping over <!...>
* and <?...?> structures.
* @return Syntax characters (<code>< > / = ! ?</code>) are returned as
* Character, and strings and names are returned as Boolean. We don't care
* what the values actually are.
* @throws JSONException If a string is not properly closed or if the XML
* is badly structured.
*/
public Object nextMeta() throws JSONException {
char c;
char q;
do {
c = next();
} while (Character.isWhitespace(c));
switch (c) {
case 0:
throw syntaxError("Misshaped meta tag");
case '<':
return XML.LT;
case '>':
return XML.GT;
case '/':
return XML.SLASH;
case '=':
return XML.EQ;
case '!':
return XML.BANG;
case '?':
return XML.QUEST;
case '"':
case '\'':
q = c;
for (;;) {
c = next();
if (c == 0) {
throw syntaxError("Unterminated string");
}
if (c == q) {
return Boolean.TRUE;
}
}
default:
for (;;) {
c = next();
if (Character.isWhitespace(c)) {
return Boolean.TRUE;
}
switch (c) {
case 0:
case '<':
case '>':
case '/':
case '=':
case '!':
case '?':
case '"':
case '\'':
back();
return Boolean.TRUE;
}
}
}
}
/**
* Get the next XML Token. These tokens are found inside of angle
* brackets. It may be one of these characters: <code>/ > = ! ?</code> or it
* may be a string wrapped in single quotes or double quotes, or it may be a
* name.
* @return a String or a Character.
* @throws JSONException If the XML is not well formed.
*/
public Object nextToken() throws JSONException {
char c;
char q;
StringBuffer sb;
do {
c = next();
} while (Character.isWhitespace(c));
switch (c) {
case 0:
throw syntaxError("Misshaped element");
case '<':
throw syntaxError("Misplaced '<'");
case '>':
return XML.GT;
case '/':
return XML.SLASH;
case '=':
return XML.EQ;
case '!':
return XML.BANG;
case '?':
return XML.QUEST;
// Quoted string
case '"':
case '\'':
q = c;
sb = new StringBuffer();
for (;;) {
c = next();
if (c == 0) {
throw syntaxError("Unterminated string");
}
if (c == q) {
return sb.toString();
}
if (c == '&') {
sb.append(nextEntity(c));
} else {
sb.append(c);
}
}
default:
// Name
sb = new StringBuffer();
for (;;) {
sb.append(c);
c = next();
if (Character.isWhitespace(c)) {
return sb.toString();
}
switch (c) {
case 0:
return sb.toString();
case '>':
case '/':
case '=':
case '!':
case '?':
case '[':
case ']':
back();
return sb.toString();
case '<':
case '"':
case '\'':
throw syntaxError("Bad character in a name");
}
}
}
}
/**
* Skip characters until past the requested string.
* If it is not found, we are left at the end of the source with a result of false.
* @param to A string to skip past.
* @throws JSONException
*/
public boolean skipPast(String to) throws JSONException {
boolean b;
char c;
int i;
int j;
int offset = 0;
int length = to.length();
char[] circle = new char[length];
/*
* First fill the circle buffer with as many characters as are in the
* to string. If we reach an early end, bail.
*/
for (i = 0; i < length; i += 1) {
c = next();
if (c == 0) {
return false;
}
circle[i] = c;
}
/*
* We will loop, possibly for all of the remaining characters.
*/
for (;;) {
j = offset;
b = true;
/*
* Compare the circle buffer with the to string.
*/
for (i = 0; i < length; i += 1) {
if (circle[j] != to.charAt(i)) {
b = false;
break;
}
j += 1;
if (j >= length) {
j -= length;
}
}
/*
* If we exit the loop with b intact, then victory is ours.
*/
if (b) {
return true;
}
/*
* Get the next character. If there isn't one, then defeat is ours.
*/
c = next();
if (c == 0) {
return false;
}
/*
* Shove the character in the circle buffer and advance the
* circle offset. The offset is mod n.
*/
circle[offset] = c;
offset += 1;
if (offset >= length) {
offset -= length;
}
}
}
}
@@ -0,0 +1,246 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player;
import org.libresonic.player.domain.Version;
import org.libresonic.player.service.*;
import org.libresonic.player.util.*;
import org.apache.commons.lang.exception.*;
import java.io.*;
import java.text.*;
import java.util.*;
/**
* Logger implementation which logs to SUBSONIC_HOME/libresonic.log.
* <br/>
* Note: Third party logging libraries (such as log4j and Commons logging) are intentionally not
* used. These libraries causes a lot of headache when deploying to some application servers
* (for instance Jetty and JBoss).
*
* @author Sindre Mehus
* @version $Revision: 1.1 $ $Date: 2005/05/09 19:58:26 $
*/
public class Logger {
private String category;
private static List<Entry> entries = Collections.synchronizedList(new BoundedList<Entry>(50));
private static PrintWriter writer;
private static Boolean debugEnabled;
/**
* Creates a logger for the given class.
* @param clazz The class.
* @return A logger for the class.
*/
public static Logger getLogger(Class clazz) {
return new Logger(clazz.getName());
}
/**
* Creates a logger for the given namee.
* @param name The name.
* @return A logger for the name.
*/
public static Logger getLogger(String name) {
return new Logger(name);
}
/**
* Returns the last few log entries.
* @return The last few log entries.
*/
public static Entry[] getLatestLogEntries() {
return entries.toArray(new Entry[entries.size()]);
}
private Logger(String name) {
int lastDot = name.lastIndexOf('.');
if (lastDot == -1) {
category = name;
} else {
category = name.substring(lastDot + 1);
}
}
/**
* Logs a debug message.
* @param message The log message.
*/
public void debug(Object message) {
debug(message, null);
}
/**
* Logs a debug message.
* @param message The message.
* @param error The optional exception.
*/
public void debug(Object message, Throwable error) {
if (isDebugEnabled()) {
add(Level.DEBUG, message, error);
}
}
private static boolean isDebugEnabled() {
if (debugEnabled == null) {
VersionService versionService = ServiceLocator.getVersionService();
if (versionService == null) {
return true; // versionService not yet available.
}
Version localVersion = versionService.getLocalVersion();
debugEnabled = localVersion == null || localVersion.getBeta() != 0;
}
return debugEnabled;
}
/**
* Logs an info message.
* @param message The message.
*/
public void info(Object message) {
info(message, null);
}
/**
* Logs an info message.
* @param message The message.
* @param error The optional exception.
*/
public void info(Object message, Throwable error) {
add(Level.INFO, message, error);
}
/**
* Logs a warning message.
* @param message The message.
*/
public void warn(Object message) {
warn(message, null);
}
/**
* Logs a warning message.
* @param message The message.
* @param error The optional exception.
*/
public void warn(Object message, Throwable error) {
add(Level.WARN, message, error);
}
/**
* Logs an error message.
* @param message The message.
*/
public void error(Object message) {
error(message, null);
}
/**
* Logs an error message.
* @param message The message.
* @param error The optional exception.
*/
public void error(Object message, Throwable error) {
add(Level.ERROR, message, error);
}
private void add(Level level, Object message, Throwable error) {
Entry entry = new Entry(category, level, message, error);
try {
getPrintWriter().println(entry);
} catch (IOException x) {
System.err.println("Failed to write to libresonic.log. " + x);
}
entries.add(entry);
}
private static synchronized PrintWriter getPrintWriter() throws IOException {
if (writer == null) {
writer = new PrintWriter(new FileWriter(getLogFile(), false), true);
}
return writer;
}
public static File getLogFile() {
File libresonicHome = SettingsService.getLibresonicHome();
return new File(libresonicHome, "libresonic.log");
}
/**
* Log level.
*/
public enum Level {
DEBUG, INFO, WARN, ERROR
}
/**
* Log entry.
*/
public static class Entry {
private String category;
private Date date;
private Level level;
private Object message;
private Throwable error;
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");
public Entry(String category, Level level, Object message, Throwable error) {
this.date = new Date();
this.category = category;
this.level = level;
this.message = message;
this.error = error;
}
public String getCategory() {
return category;
}
public Date getDate() {
return date;
}
public Level getLevel() {
return level;
}
public Object getMessage() {
return message;
}
public Throwable getError() {
return error;
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append('[').append(DATE_FORMAT.format(date)).append("] ");
builder.append(level).append(' ');
builder.append(category).append(" - ");
builder.append(message);
if (error != null) {
builder.append('\n').append(ExceptionUtils.getFullStackTrace(error));
}
return builder.toString();
}
}
}
@@ -0,0 +1,53 @@
/*
* This file is part of Libresonic.
*
* Libresonic 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.
*
* Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2014 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import java.util.List;
import org.libresonic.player.domain.ArtistBio;
/**
* @author Sindre Mehus
* @version $Id$
*/
public class ArtistInfo {
private final List<SimilarArtist> similarArtists;
private final ArtistBio artistBio;
private final List<TopSong> topSongs;
public ArtistInfo(List<SimilarArtist> similarArtists, ArtistBio artistBio, List<TopSong> topSongs) {
this.similarArtists = similarArtists;
this.artistBio = artistBio;
this.topSongs = topSongs;
}
public List<SimilarArtist> getSimilarArtists() {
return similarArtists;
}
public ArtistBio getArtistBio() {
return artistBio;
}
public List<TopSong> getTopSongs() {
return topSongs;
}
}
@@ -0,0 +1,163 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import org.libresonic.player.Logger;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.util.BoundedList;
import org.apache.commons.lang.StringUtils;
import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Provides AJAX-enabled services for the chatting.
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class ChatService {
private static final Logger LOG = Logger.getLogger(ChatService.class);
private static final String CACHE_KEY = "1";
private static final int MAX_MESSAGES = 10;
private static final long TTL_MILLIS = 3L * 24L * 60L * 60L * 1000L; // 3 days.
private final LinkedList<Message> messages = new BoundedList<Message>(MAX_MESSAGES);
private SecurityService securityService;
private long revision = System.identityHashCode(this);
/**
* Invoked by Spring.
*/
public void init() {
// Delete old messages every hour.
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
Runnable runnable = new Runnable() {
public void run() {
removeOldMessages();
}
};
executor.scheduleWithFixedDelay(runnable, 0L, 3600L, TimeUnit.SECONDS);
}
private synchronized void removeOldMessages() {
long now = System.currentTimeMillis();
for (Iterator<Message> iterator = messages.iterator(); iterator.hasNext();) {
Message message = iterator.next();
if (now - message.getDate().getTime() > TTL_MILLIS) {
iterator.remove();
revision++;
}
}
}
public synchronized void addMessage(String message) {
WebContext webContext = WebContextFactory.get();
doAddMessage(message, webContext.getHttpServletRequest());
}
public synchronized void doAddMessage(String message, HttpServletRequest request) {
String user = securityService.getCurrentUsername(request);
message = StringUtils.trimToNull(message);
if (message != null && user != null) {
messages.addFirst(new Message(message, user, new Date()));
revision++;
}
}
public synchronized void clearMessages() {
messages.clear();
revision++;
}
/**
* Returns all messages, but only if the given revision is different from the
* current revision.
*/
public synchronized Messages getMessages(long revision) {
if (this.revision != revision) {
return new Messages(new ArrayList<Message>(messages), this.revision);
}
return null;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public static class Messages implements Serializable {
private static final long serialVersionUID = -752602719879818165L;
private final List<Message> messages;
private final long revision;
public Messages(List<Message> messages, long revision) {
this.messages = messages;
this.revision = revision;
}
public List<Message> getMessages() {
return messages;
}
public long getRevision() {
return revision;
}
}
public static class Message implements Serializable {
private static final long serialVersionUID = -1907101191518133712L;
private final String content;
private final String username;
private final Date date;
public Message(String content, String username, Date date) {
this.content = content;
this.username = username;
this.date = date;
}
public String getContent() {
return content;
}
public String getUsername() {
return username;
}
public Date getDate() {
return date;
}
}
}
@@ -0,0 +1,43 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
/**
* Contains info about cover art images for an album.
*
* @author Sindre Mehus
*/
public class CoverArtInfo {
private final String imagePreviewUrl;
private final String imageDownloadUrl;
public CoverArtInfo(String imagePreviewUrl, String imageDownloadUrl) {
this.imagePreviewUrl = imagePreviewUrl;
this.imageDownloadUrl = imageDownloadUrl;
}
public String getImagePreviewUrl() {
return imagePreviewUrl;
}
public String getImageDownloadUrl() {
return imageDownloadUrl;
}
}
@@ -0,0 +1,164 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.libresonic.player.Logger;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.util.StringUtil;
/**
* Provides AJAX-enabled services for changing cover art images.
* <p/>
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class CoverArtService {
private static final Logger LOG = Logger.getLogger(CoverArtService.class);
private SecurityService securityService;
private MediaFileService mediaFileService;
/**
* Downloads and saves the cover art at the given URL.
*
* @param albumId ID of the album in question.
* @param url The image URL.
* @return The error string if something goes wrong, <code>null</code> otherwise.
*/
public String setCoverArtImage(int albumId, String url) {
try {
MediaFile mediaFile = mediaFileService.getMediaFile(albumId);
saveCoverArt(mediaFile.getPath(), url);
return null;
} catch (Exception x) {
LOG.warn("Failed to save cover art for album " + albumId, x);
return x.toString();
}
}
private void saveCoverArt(String path, String url) throws Exception {
InputStream input = null;
OutputStream output = null;
HttpClient client = new DefaultHttpClient();
try {
HttpConnectionParams.setConnectionTimeout(client.getParams(), 20 * 1000); // 20 seconds
HttpConnectionParams.setSoTimeout(client.getParams(), 20 * 1000); // 20 seconds
HttpGet method = new HttpGet(url);
HttpResponse response = client.execute(method);
input = response.getEntity().getContent();
// Attempt to resolve proper suffix.
String suffix = "jpg";
if (url.toLowerCase().endsWith(".gif")) {
suffix = "gif";
} else if (url.toLowerCase().endsWith(".png")) {
suffix = "png";
}
// Check permissions.
File newCoverFile = new File(path, "cover." + suffix);
if (!securityService.isWriteAllowed(newCoverFile)) {
throw new Exception("Permission denied: " + StringUtil.toHtml(newCoverFile.getPath()));
}
// If file exists, create a backup.
backup(newCoverFile, new File(path, "cover." + suffix + ".backup"));
// Write file.
output = new FileOutputStream(newCoverFile);
IOUtils.copy(input, output);
MediaFile dir = mediaFileService.getMediaFile(path);
// Refresh database.
mediaFileService.refreshMediaFile(dir);
dir = mediaFileService.getMediaFile(dir.getId());
// Rename existing cover files if new cover file is not the preferred.
try {
while (true) {
File coverFile = mediaFileService.getCoverArt(dir);
if (coverFile != null && !isMediaFile(coverFile) && !newCoverFile.equals(coverFile)) {
if (!coverFile.renameTo(new File(coverFile.getCanonicalPath() + ".old"))) {
LOG.warn("Unable to rename old image file " + coverFile);
break;
}
LOG.info("Renamed old image file " + coverFile);
// Must refresh again.
mediaFileService.refreshMediaFile(dir);
dir = mediaFileService.getMediaFile(dir.getId());
} else {
break;
}
}
} catch (Exception x) {
LOG.warn("Failed to rename existing cover file.", x);
}
} finally {
IOUtils.closeQuietly(input);
IOUtils.closeQuietly(output);
client.getConnectionManager().shutdown();
}
}
private boolean isMediaFile(File file) {
return !mediaFileService.filterMediaFiles(new File[]{file}).isEmpty();
}
private void backup(File newCoverFile, File backup) {
if (newCoverFile.exists()) {
if (backup.exists()) {
backup.delete();
}
if (newCoverFile.renameTo(backup)) {
LOG.info("Backed up old image file to " + backup);
} else {
LOG.warn("Failed to create image file backup " + backup);
}
}
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,62 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
/**
* Contains lyrics info for a song.
*
* @author Sindre Mehus
*/
public class LyricsInfo {
private final String lyrics;
private final String artist;
private final String title;
private boolean tryLater;
public LyricsInfo() {
this(null, null, null);
}
public LyricsInfo(String lyrics, String artist, String title) {
this.lyrics = lyrics;
this.artist = artist;
this.title = title;
}
public String getLyrics() {
return lyrics;
}
public String getArtist() {
return artist;
}
public String getTitle() {
return title;
}
public void setTryLater(boolean tryLater) {
this.tryLater = tryLater;
}
public boolean isTryLater() {
return tryLater;
}
}
@@ -0,0 +1,107 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import java.io.IOException;
import java.io.StringReader;
import java.net.SocketException;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.libresonic.player.Logger;
import org.libresonic.player.util.StringUtil;
/**
* Provides AJAX-enabled services for retrieving song lyrics from chartlyrics.com.
* <p/>
* See http://www.chartlyrics.com/api.aspx for details.
* <p/>
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class LyricsService {
private static final Logger LOG = Logger.getLogger(LyricsService.class);
/**
* Returns lyrics for the given song and artist.
*
* @param artist The artist.
* @param song The song.
* @return The lyrics, never <code>null</code> .
*/
public LyricsInfo getLyrics(String artist, String song) {
LyricsInfo lyrics = new LyricsInfo();
try {
artist = StringUtil.urlEncode(artist);
song = StringUtil.urlEncode(song);
String url = "http://api.chartlyrics.com/apiv1.asmx/SearchLyricDirect?artist=" + artist + "&song=" + song;
String xml = executeGetRequest(url);
lyrics = parseSearchResult(xml);
} catch (SocketException x) {
lyrics.setTryLater(true);
} catch (Exception x) {
LOG.warn("Failed to get lyrics for song '" + song + "'.", x);
}
return lyrics;
}
private LyricsInfo parseSearchResult(String xml) throws Exception {
SAXBuilder builder = new SAXBuilder();
Document document = builder.build(new StringReader(xml));
Element root = document.getRootElement();
Namespace ns = root.getNamespace();
String lyric = StringUtils.trimToNull(root.getChildText("Lyric", ns));
String song = root.getChildText("LyricSong", ns);
String artist = root.getChildText("LyricArtist", ns);
return new LyricsInfo(lyric, artist, song);
}
private String executeGetRequest(String url) throws IOException {
HttpClient client = new DefaultHttpClient();
HttpConnectionParams.setConnectionTimeout(client.getParams(), 15000);
HttpConnectionParams.setSoTimeout(client.getParams(), 15000);
HttpGet method = new HttpGet(url);
try {
ResponseHandler<String> responseHandler = new BasicResponseHandler();
return client.execute(method, responseHandler);
} finally {
client.getConnectionManager().shutdown();
}
}
}
@@ -0,0 +1,137 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.directwebremoting.WebContextFactory;
import org.libresonic.player.Logger;
import org.libresonic.player.domain.ArtistBio;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.LastFmService;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.NetworkService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
/**
* Provides miscellaneous AJAX-enabled services.
* <p/>
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class MultiService {
private static final Logger LOG = Logger.getLogger(MultiService.class);
private NetworkService networkService;
private MediaFileService mediaFileService;
private LastFmService lastFmService;
private SecurityService securityService;
private SettingsService settingsService;
/**
* Returns status for port forwarding and URL redirection.
*/
public NetworkStatus getNetworkStatus() {
NetworkService.Status portForwardingStatus = networkService.getPortForwardingStatus();
NetworkService.Status urlRedirectionStatus = networkService.getURLRedirecionStatus();
return new NetworkStatus(portForwardingStatus.getText(),
portForwardingStatus.getDate(),
urlRedirectionStatus.getText(),
urlRedirectionStatus.getDate());
}
public ArtistInfo getArtistInfo(int mediaFileId, int maxSimilarArtists, int maxTopSongs) {
MediaFile mediaFile = mediaFileService.getMediaFile(mediaFileId);
List<SimilarArtist> similarArtists = getSimilarArtists(mediaFileId, maxSimilarArtists);
ArtistBio artistBio = lastFmService.getArtistBio(mediaFile);
List<TopSong> topSongs = getTopSongs(mediaFile, maxTopSongs);
return new ArtistInfo(similarArtists, artistBio, topSongs);
}
private List<TopSong> getTopSongs(MediaFile mediaFile, int limit) {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
String username = securityService.getCurrentUsername(request);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<TopSong> result = new ArrayList<TopSong>();
List<MediaFile> files = lastFmService.getTopSongs(mediaFile, limit, musicFolders);
mediaFileService.populateStarredDate(files, username);
for (MediaFile file : files) {
result.add(new TopSong(file.getId(), file.getTitle(), file.getArtist(), file.getAlbumName(),
file.getDurationString(), file.getStarredDate() != null));
}
return result;
}
private List<SimilarArtist> getSimilarArtists(int mediaFileId, int limit) {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
String username = securityService.getCurrentUsername(request);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
MediaFile artist = mediaFileService.getMediaFile(mediaFileId);
List<MediaFile> similarArtists = lastFmService.getSimilarArtists(artist, limit, false, musicFolders);
SimilarArtist[] result = new SimilarArtist[similarArtists.size()];
for (int i = 0; i < result.length; i++) {
MediaFile similarArtist = similarArtists.get(i);
result[i] = new SimilarArtist(similarArtist.getId(), similarArtist.getName());
}
return Arrays.asList(result);
}
public void setShowSideBar(boolean show) {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
String username = securityService.getCurrentUsername(request);
UserSettings userSettings = settingsService.getUserSettings(username);
userSettings.setShowSideBar(show);
userSettings.setChanged(new Date());
settingsService.updateUserSettings(userSettings);
}
public void setNetworkService(NetworkService networkService) {
this.networkService = networkService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setLastFmService(LastFmService lastFmService) {
this.lastFmService = lastFmService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -0,0 +1,55 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import java.util.Date;
/**
* @author Sindre Mehus
*/
public class NetworkStatus {
private final String portForwardingStatusText;
private final Date portForwardingStatusDate;
private final String urlRedirectionStatusText;
private final Date urlRedirectionStatusDate;
public NetworkStatus(String portForwardingStatusText, Date portForwardingStatusDate,
String urlRedirectionStatusText, Date urlRedirectionStatusDate) {
this.portForwardingStatusText = portForwardingStatusText;
this.portForwardingStatusDate = portForwardingStatusDate;
this.urlRedirectionStatusText = urlRedirectionStatusText;
this.urlRedirectionStatusDate = urlRedirectionStatusDate;
}
public String getPortForwardingStatusText() {
return portForwardingStatusText;
}
public Date getPortForwardingStatusDate() {
return portForwardingStatusDate;
}
public String getUrlRedirectionStatusText() {
return urlRedirectionStatusText;
}
public Date getUrlRedirectionStatusDate() {
return urlRedirectionStatusDate;
}
}
@@ -0,0 +1,98 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
/**
* Details about what a user is currently listening to.
*
* @author Sindre Mehus
*/
public class NowPlayingInfo {
private final String playerId;
private final String username;
private final String artist;
private final String title;
private final String tooltip;
private final String streamUrl;
private final String albumUrl;
private final String lyricsUrl;
private final String coverArtUrl;
private final String avatarUrl;
private final int minutesAgo;
public NowPlayingInfo(String playerId, String user, String artist, String title, String tooltip, String streamUrl, String albumUrl,
String lyricsUrl, String coverArtUrl, String avatarUrl, int minutesAgo) {
this.playerId = playerId;
this.username = user;
this.artist = artist;
this.title = title;
this.tooltip = tooltip;
this.streamUrl = streamUrl;
this.albumUrl = albumUrl;
this.lyricsUrl = lyricsUrl;
this.coverArtUrl = coverArtUrl;
this.avatarUrl = avatarUrl;
this.minutesAgo = minutesAgo;
}
public String getPlayerId() {
return playerId;
}
public String getUsername() {
return username;
}
public String getArtist() {
return artist;
}
public String getTitle() {
return title;
}
public String getTooltip() {
return tooltip;
}
public String getStreamUrl() {
return streamUrl;
}
public String getAlbumUrl() {
return albumUrl;
}
public String getLyricsUrl() {
return lyricsUrl;
}
public String getCoverArtUrl() {
return coverArtUrl;
}
public String getAvatarUrl() {
return avatarUrl;
}
public int getMinutesAgo() {
return minutesAgo;
}
}
@@ -0,0 +1,176 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
import org.libresonic.player.Logger;
import org.libresonic.player.domain.AvatarScheme;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.PlayStatus;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.MediaScannerService;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.StatusService;
import org.libresonic.player.util.StringUtil;
/**
* Provides AJAX-enabled services for retrieving the currently playing file and directory.
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class NowPlayingService {
private static final Logger LOG = Logger.getLogger(NowPlayingService.class);
private PlayerService playerService;
private StatusService statusService;
private SettingsService settingsService;
private MediaScannerService mediaScannerService;
/**
* Returns details about what the current player is playing.
*
* @return Details about what the current player is playing, or <code>null</code> if not playing anything.
*/
public NowPlayingInfo getNowPlayingForCurrentPlayer() throws Exception {
WebContext webContext = WebContextFactory.get();
Player player = playerService.getPlayer(webContext.getHttpServletRequest(), webContext.getHttpServletResponse());
for (NowPlayingInfo info : getNowPlaying()) {
if (player.getId().equals(info.getPlayerId())) {
return info;
}
}
return null;
}
/**
* Returns details about what all users are currently playing.
*
* @return Details about what all users are currently playing.
*/
public List<NowPlayingInfo> getNowPlaying() throws Exception {
try {
return convert(statusService.getPlayStatuses());
} catch (Throwable x) {
LOG.error("Unexpected error in getNowPlaying: " + x, x);
return Collections.emptyList();
}
}
/**
* Returns media folder scanning status.
*/
public ScanInfo getScanningStatus() {
return new ScanInfo(mediaScannerService.isScanning(), mediaScannerService.getScanCount());
}
private List<NowPlayingInfo> convert(List<PlayStatus> playStatuses) {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
String url = request.getRequestURL().toString();
List<NowPlayingInfo> result = new ArrayList<NowPlayingInfo>();
for (PlayStatus status : playStatuses) {
Player player = status.getPlayer();
MediaFile mediaFile = status.getMediaFile();
String username = player.getUsername();
if (username == null) {
continue;
}
UserSettings userSettings = settingsService.getUserSettings(username);
if (!userSettings.isNowPlayingAllowed()) {
continue;
}
String artist = mediaFile.getArtist();
String title = mediaFile.getTitle();
String streamUrl = url.replaceFirst("/dwr/.*", "/stream?player=" + player.getId() + "&id=" + mediaFile.getId());
String albumUrl = url.replaceFirst("/dwr/.*", "/main.view?id=" + mediaFile.getId());
String lyricsUrl = null;
if (!mediaFile.isVideo()) {
lyricsUrl = url.replaceFirst("/dwr/.*", "/lyrics.view?artistUtf8Hex=" + StringUtil.utf8HexEncode(artist) +
"&songUtf8Hex=" + StringUtil.utf8HexEncode(title));
}
String coverArtUrl = url.replaceFirst("/dwr/.*", "/coverArt.view?size=60&id=" + mediaFile.getId());
String avatarUrl = null;
if (userSettings.getAvatarScheme() == AvatarScheme.SYSTEM) {
avatarUrl = url.replaceFirst("/dwr/.*", "/avatar.view?id=" + userSettings.getSystemAvatarId());
} else if (userSettings.getAvatarScheme() == AvatarScheme.CUSTOM && settingsService.getCustomAvatar(username) != null) {
avatarUrl = url.replaceFirst("/dwr/.*", "/avatar.view?usernameUtf8Hex=" + StringUtil.utf8HexEncode(username));
}
// Rewrite URLs in case we're behind a proxy.
if (settingsService.isRewriteUrlEnabled()) {
String referer = request.getHeader("referer");
streamUrl = StringUtil.rewriteUrl(streamUrl, referer);
albumUrl = StringUtil.rewriteUrl(albumUrl, referer);
lyricsUrl = StringUtil.rewriteUrl(lyricsUrl, referer);
coverArtUrl = StringUtil.rewriteUrl(coverArtUrl, referer);
avatarUrl = StringUtil.rewriteUrl(avatarUrl, referer);
}
String tooltip = StringUtil.toHtml(artist) + " &ndash; " + StringUtil.toHtml(title);
if (StringUtils.isNotBlank(player.getName())) {
username += "@" + player.getName();
}
artist = StringUtil.toHtml(StringUtils.abbreviate(artist, 25));
title = StringUtil.toHtml(StringUtils.abbreviate(title, 25));
username = StringUtil.toHtml(StringUtils.abbreviate(username, 25));
long minutesAgo = status.getMinutesAgo();
if (minutesAgo < 60) {
result.add(new NowPlayingInfo(player.getId(),username, artist, title, tooltip, streamUrl, albumUrl, lyricsUrl,
coverArtUrl, avatarUrl, (int) minutesAgo));
}
}
return result;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setStatusService(StatusService statusService) {
this.statusService = statusService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setMediaScannerService(MediaScannerService mediaScannerService) {
this.mediaScannerService = mediaScannerService;
}
}
@@ -0,0 +1,217 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import java.util.List;
import org.libresonic.player.util.StringUtil;
/**
* The playlist of a player.
*
* @author Sindre Mehus
*/
public class PlayQueueInfo {
private final List<Entry> entries;
private final boolean stopEnabled;
private final boolean repeatEnabled;
private final boolean sendM3U;
private final float gain;
private int startPlayerAt = -1;
private long startPlayerAtPosition; // millis
public PlayQueueInfo(List<Entry> entries, boolean stopEnabled, boolean repeatEnabled, boolean sendM3U, float gain) {
this.entries = entries;
this.stopEnabled = stopEnabled;
this.repeatEnabled = repeatEnabled;
this.sendM3U = sendM3U;
this.gain = gain;
}
public List<Entry> getEntries() {
return entries;
}
public String getDurationAsString() {
int durationSeconds = 0;
for (Entry entry : entries) {
if (entry.getDuration() != null) {
durationSeconds += entry.getDuration();
}
}
return StringUtil.formatDuration(durationSeconds);
}
public boolean isStopEnabled() {
return stopEnabled;
}
public boolean isSendM3U() {
return sendM3U;
}
public boolean isRepeatEnabled() {
return repeatEnabled;
}
public float getGain() {
return gain;
}
public int getStartPlayerAt() {
return startPlayerAt;
}
public PlayQueueInfo setStartPlayerAt(int startPlayerAt) {
this.startPlayerAt = startPlayerAt;
return this;
}
public long getStartPlayerAtPosition() {
return startPlayerAtPosition;
}
public PlayQueueInfo setStartPlayerAtPosition(long startPlayerAtPosition) {
this.startPlayerAtPosition = startPlayerAtPosition;
return this;
}
public static class Entry {
private final int id;
private final Integer trackNumber;
private final String title;
private final String artist;
private final String album;
private final String genre;
private final Integer year;
private final String bitRate;
private final Integer duration;
private final String durationAsString;
private final String format;
private final String contentType;
private final String fileSize;
private final boolean starred;
private final String albumUrl;
private final String streamUrl;
private final String remoteStreamUrl;
private final String coverArtUrl;
private final String remoteCoverArtUrl;
public Entry(int id, Integer trackNumber, String title, String artist, String album, String genre, Integer year,
String bitRate, Integer duration, String durationAsString, String format, String contentType, String fileSize,
boolean starred, String albumUrl, String streamUrl, String remoteStreamUrl, String coverArtUrl, String remoteCoverArtUrl) {
this.id = id;
this.trackNumber = trackNumber;
this.title = title;
this.artist = artist;
this.album = album;
this.genre = genre;
this.year = year;
this.bitRate = bitRate;
this.duration = duration;
this.durationAsString = durationAsString;
this.format = format;
this.contentType = contentType;
this.fileSize = fileSize;
this.starred = starred;
this.albumUrl = albumUrl;
this.streamUrl = streamUrl;
this.remoteStreamUrl = remoteStreamUrl;
this.coverArtUrl = coverArtUrl;
this.remoteCoverArtUrl = remoteCoverArtUrl;
}
public int getId() {
return id;
}
public Integer getTrackNumber() {
return trackNumber;
}
public String getTitle() {
return title;
}
public String getArtist() {
return artist;
}
public String getAlbum() {
return album;
}
public String getGenre() {
return genre;
}
public Integer getYear() {
return year;
}
public String getBitRate() {
return bitRate;
}
public String getDurationAsString() {
return durationAsString;
}
public Integer getDuration() {
return duration;
}
public String getFormat() {
return format;
}
public String getContentType() {
return contentType;
}
public String getFileSize() {
return fileSize;
}
public boolean isStarred() {
return starred;
}
public String getAlbumUrl() {
return albumUrl;
}
public String getStreamUrl() {
return streamUrl;
}
public String getRemoteStreamUrl() {
return remoteStreamUrl;
}
public String getCoverArtUrl() {
return coverArtUrl;
}
public String getRemoteCoverArtUrl() {
return remoteCoverArtUrl;
}
}
}
@@ -0,0 +1,737 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.directwebremoting.WebContextFactory;
import org.springframework.web.servlet.support.RequestContextUtils;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import org.libresonic.player.dao.MediaFileDao;
import org.libresonic.player.dao.PlayQueueDao;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.PlayQueue;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.PodcastEpisode;
import org.libresonic.player.domain.PodcastStatus;
import org.libresonic.player.domain.SavedPlayQueue;
import org.libresonic.player.domain.UrlRedirectType;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.JukeboxService;
import org.libresonic.player.service.LastFmService;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.service.PodcastService;
import org.libresonic.player.service.RatingService;
import org.libresonic.player.service.SearchService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.TranscodingService;
import org.libresonic.player.util.StringUtil;
/**
* Provides AJAX-enabled services for manipulating the play queue of a player.
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
@SuppressWarnings("UnusedDeclaration")
public class PlayQueueService {
private PlayerService playerService;
private JukeboxService jukeboxService;
private TranscodingService transcodingService;
private SettingsService settingsService;
private MediaFileService mediaFileService;
private LastFmService lastFmService;
private SecurityService securityService;
private SearchService searchService;
private RatingService ratingService;
private PodcastService podcastService;
private org.libresonic.player.service.PlaylistService playlistService;
private MediaFileDao mediaFileDao;
private PlayQueueDao playQueueDao;
/**
* Returns the play queue for the player of the current user.
*
* @return The play queue.
*/
public PlayQueueInfo getPlayQueue() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
return convert(request, player, false);
}
public PlayQueueInfo start() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doStart(request, response);
}
public PlayQueueInfo doStart(HttpServletRequest request, HttpServletResponse response) throws Exception {
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().setStatus(PlayQueue.Status.PLAYING);
return convert(request, player, true);
}
public PlayQueueInfo stop() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doStop(request, response);
}
public PlayQueueInfo doStop(HttpServletRequest request, HttpServletResponse response) throws Exception {
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().setStatus(PlayQueue.Status.STOPPED);
return convert(request, player, true);
}
public PlayQueueInfo skip(int index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doSkip(request, response, index, 0);
}
public PlayQueueInfo doSkip(HttpServletRequest request, HttpServletResponse response, int index, int offset) throws Exception {
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().setIndex(index);
boolean serverSidePlaylist = !player.isExternalWithPlaylist();
return convert(request, player, serverSidePlaylist, offset);
}
public void savePlayQueue(int currentSongIndex, long positionMillis) {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
String username = securityService.getCurrentUsername(request);
PlayQueue playQueue = player.getPlayQueue();
List<Integer> ids = MediaFile.toIdList(playQueue.getFiles());
Integer currentId = currentSongIndex == -1 ? null : playQueue.getFile(currentSongIndex).getId();
SavedPlayQueue savedPlayQueue = new SavedPlayQueue(null, username, ids, currentId, positionMillis, new Date(), "Libresonic");
playQueueDao.savePlayQueue(savedPlayQueue);
}
public PlayQueueInfo loadPlayQueue() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
String username = securityService.getCurrentUsername(request);
SavedPlayQueue savedPlayQueue = playQueueDao.getPlayQueue(username);
if (savedPlayQueue == null) {
return convert(request, player, false);
}
PlayQueue playQueue = player.getPlayQueue();
playQueue.clear();
for (Integer mediaFileId : savedPlayQueue.getMediaFileIds()) {
MediaFile mediaFile = mediaFileService.getMediaFile(mediaFileId);
if (mediaFile != null) {
playQueue.addFiles(true, mediaFile);
}
}
PlayQueueInfo result = convert(request, player, false);
Integer currentId = savedPlayQueue.getCurrentMediaFileId();
int currentIndex = -1;
long positionMillis = savedPlayQueue.getPositionMillis() == null ? 0L : savedPlayQueue.getPositionMillis();
if (currentId != null) {
MediaFile current = mediaFileService.getMediaFile(currentId);
currentIndex = playQueue.getFiles().indexOf(current);
if (currentIndex != -1) {
result.setStartPlayerAt(currentIndex);
result.setStartPlayerAtPosition(positionMillis);
}
}
boolean serverSidePlaylist = !player.isExternalWithPlaylist();
if (serverSidePlaylist && currentIndex != -1) {
doSkip(request, response, currentIndex, (int) (positionMillis / 1000L));
}
return result;
}
public PlayQueueInfo play(int id) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
MediaFile file = mediaFileService.getMediaFile(id);
if (file.isFile()) {
String username = securityService.getCurrentUsername(request);
boolean queueFollowingSongs = settingsService.getUserSettings(username).isQueueFollowingSongs();
List<MediaFile> songs;
if (queueFollowingSongs) {
MediaFile dir = mediaFileService.getParentOf(file);
songs = mediaFileService.getChildrenOf(dir, true, false, true);
if (!songs.isEmpty()) {
int index = songs.indexOf(file);
songs = songs.subList(index, songs.size());
}
} else {
songs = Arrays.asList(file);
}
return doPlay(request, player, songs).setStartPlayerAt(0);
} else {
List<MediaFile> songs = mediaFileService.getDescendantsOf(file, true);
return doPlay(request, player, songs).setStartPlayerAt(0);
}
}
/**
* @param index Start playing at this index, or play whole playlist if {@code null}.
*/
public PlayQueueInfo playPlaylist(int id, Integer index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
String username = securityService.getCurrentUsername(request);
boolean queueFollowingSongs = settingsService.getUserSettings(username).isQueueFollowingSongs();
List<MediaFile> files = playlistService.getFilesInPlaylist(id, true);
if (!files.isEmpty() && index != null) {
if (queueFollowingSongs) {
files = files.subList(index, files.size());
} else {
files = Arrays.asList(files.get(index));
}
}
// Remove non-present files
Iterator<MediaFile> iterator = files.iterator();
while (iterator.hasNext()) {
MediaFile file = iterator.next();
if (!file.isPresent()) {
iterator.remove();
}
}
Player player = getCurrentPlayer(request, response);
return doPlay(request, player, files).setStartPlayerAt(0);
}
/**
* @param index Start playing at this index, or play all top songs if {@code null}.
*/
public PlayQueueInfo playTopSong(int id, Integer index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
String username = securityService.getCurrentUsername(request);
boolean queueFollowingSongs = settingsService.getUserSettings(username).isQueueFollowingSongs();
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<MediaFile> files = lastFmService.getTopSongs(mediaFileService.getMediaFile(id), 50, musicFolders);
if (!files.isEmpty() && index != null) {
if (queueFollowingSongs) {
files = files.subList(index, files.size());
} else {
files = Arrays.asList(files.get(index));
}
}
Player player = getCurrentPlayer(request, response);
return doPlay(request, player, files).setStartPlayerAt(0);
}
public PlayQueueInfo playPodcastChannel(int id) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
List<PodcastEpisode> episodes = podcastService.getEpisodes(id);
List<MediaFile> files = new ArrayList<MediaFile>();
for (PodcastEpisode episode : episodes) {
if (episode.getStatus() == PodcastStatus.COMPLETED) {
MediaFile mediaFile = mediaFileService.getMediaFile(episode.getMediaFileId());
if (mediaFile != null && mediaFile.isPresent()) {
files.add(mediaFile);
}
}
}
Player player = getCurrentPlayer(request, response);
return doPlay(request, player, files).setStartPlayerAt(0);
}
public PlayQueueInfo playPodcastEpisode(int id) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
PodcastEpisode episode = podcastService.getEpisode(id, false);
List<PodcastEpisode> allEpisodes = podcastService.getEpisodes(episode.getChannelId());
List<MediaFile> files = new ArrayList<MediaFile>();
String username = securityService.getCurrentUsername(request);
boolean queueFollowingSongs = settingsService.getUserSettings(username).isQueueFollowingSongs();
for (PodcastEpisode ep : allEpisodes) {
if (ep.getStatus() == PodcastStatus.COMPLETED) {
MediaFile mediaFile = mediaFileService.getMediaFile(ep.getMediaFileId());
if (mediaFile != null && mediaFile.isPresent() &&
(ep.getId().equals(episode.getId()) || queueFollowingSongs && !files.isEmpty())) {
files.add(mediaFile);
}
}
}
Player player = getCurrentPlayer(request, response);
return doPlay(request, player, files).setStartPlayerAt(0);
}
public PlayQueueInfo playNewestPodcastEpisode(Integer index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
List<PodcastEpisode> episodes = podcastService.getNewestEpisodes(10);
List<MediaFile> files = Lists.transform(episodes, new Function<PodcastEpisode, MediaFile>() {
@Override
public MediaFile apply(PodcastEpisode episode) {
return mediaFileService.getMediaFile(episode.getMediaFileId());
}
});
String username = securityService.getCurrentUsername(request);
boolean queueFollowingSongs = settingsService.getUserSettings(username).isQueueFollowingSongs();
if (!files.isEmpty() && index != null) {
if (queueFollowingSongs) {
files = files.subList(index, files.size());
} else {
files = Arrays.asList(files.get(index));
}
}
Player player = getCurrentPlayer(request, response);
return doPlay(request, player, files).setStartPlayerAt(0);
}
public PlayQueueInfo playStarred() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
String username = securityService.getCurrentUsername(request);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<MediaFile> files = mediaFileDao.getStarredFiles(0, Integer.MAX_VALUE, username, musicFolders);
Player player = getCurrentPlayer(request, response);
return doPlay(request, player, files).setStartPlayerAt(0);
}
public PlayQueueInfo playShuffle(String albumListType, int offset, int count, String genre, String decade) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
String username = securityService.getCurrentUsername(request);
UserSettings userSettings = settingsService.getUserSettings(securityService.getCurrentUsername(request));
MusicFolder selectedMusicFolder = settingsService.getSelectedMusicFolder(username);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username,
selectedMusicFolder == null ? null : selectedMusicFolder.getId());
List<MediaFile> albums;
if ("highest".equals(albumListType)) {
albums = ratingService.getHighestRatedAlbums(offset, count, musicFolders);
} else if ("frequent".equals(albumListType)) {
albums = mediaFileService.getMostFrequentlyPlayedAlbums(offset, count, musicFolders);
} else if ("recent".equals(albumListType)) {
albums = mediaFileService.getMostRecentlyPlayedAlbums(offset, count, musicFolders);
} else if ("newest".equals(albumListType)) {
albums = mediaFileService.getNewestAlbums(offset, count, musicFolders);
} else if ("starred".equals(albumListType)) {
albums = mediaFileService.getStarredAlbums(offset, count, username, musicFolders);
} else if ("random".equals(albumListType)) {
albums = searchService.getRandomAlbums(count, musicFolders);
} else if ("alphabetical".equals(albumListType)) {
albums = mediaFileService.getAlphabeticalAlbums(offset, count, true, musicFolders);
} else if ("decade".equals(albumListType)) {
int fromYear = Integer.parseInt(decade);
int toYear = fromYear + 9;
albums = mediaFileService.getAlbumsByYear(offset, count, fromYear, toYear, musicFolders);
} else if ("genre".equals(albumListType)) {
albums = mediaFileService.getAlbumsByGenre(offset, count, genre, musicFolders);
} else {
albums = Collections.emptyList();
}
List<MediaFile> songs = new ArrayList<MediaFile>();
for (MediaFile album : albums) {
songs.addAll(mediaFileService.getChildrenOf(album, true, false, false));
}
Collections.shuffle(songs);
songs = songs.subList(0, Math.min(40, songs.size()));
Player player = getCurrentPlayer(request, response);
return doPlay(request, player, songs).setStartPlayerAt(0);
}
private PlayQueueInfo doPlay(HttpServletRequest request, Player player, List<MediaFile> files) throws Exception {
if (player.isWeb()) {
mediaFileService.removeVideoFiles(files);
}
player.getPlayQueue().addFiles(false, files);
player.getPlayQueue().setRandomSearchCriteria(null);
return convert(request, player, true);
}
public PlayQueueInfo playRandom(int id, int count) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
MediaFile file = mediaFileService.getMediaFile(id);
List<MediaFile> randomFiles = mediaFileService.getRandomSongsForParent(file, count);
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().addFiles(false, randomFiles);
player.getPlayQueue().setRandomSearchCriteria(null);
return convert(request, player, true).setStartPlayerAt(0);
}
public PlayQueueInfo playSimilar(int id, int count) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
MediaFile artist = mediaFileService.getMediaFile(id);
String username = securityService.getCurrentUsername(request);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<MediaFile> similarSongs = lastFmService.getSimilarSongs(artist, count, musicFolders);
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().addFiles(false, similarSongs);
return convert(request, player, true).setStartPlayerAt(0);
}
public PlayQueueInfo add(int id) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doAdd(request, response, new int[]{id}, null);
}
public PlayQueueInfo addAt(int id, int index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doAdd(request, response, new int[]{id}, index);
}
public PlayQueueInfo doAdd(HttpServletRequest request, HttpServletResponse response, int[] ids, Integer index) throws Exception {
Player player = getCurrentPlayer(request, response);
List<MediaFile> files = new ArrayList<MediaFile>(ids.length);
for (int id : ids) {
MediaFile ancestor = mediaFileService.getMediaFile(id);
files.addAll(mediaFileService.getDescendantsOf(ancestor, true));
}
if (player.isWeb()) {
mediaFileService.removeVideoFiles(files);
}
if (index != null) {
player.getPlayQueue().addFilesAt(files, index);
} else {
player.getPlayQueue().addFiles(true, files);
}
player.getPlayQueue().setRandomSearchCriteria(null);
return convert(request, player, false);
}
public PlayQueueInfo doSet(HttpServletRequest request, HttpServletResponse response, int[] ids) throws Exception {
Player player = getCurrentPlayer(request, response);
PlayQueue playQueue = player.getPlayQueue();
MediaFile currentFile = playQueue.getCurrentFile();
PlayQueue.Status status = playQueue.getStatus();
playQueue.clear();
PlayQueueInfo result = doAdd(request, response, ids, null);
int index = currentFile == null ? -1 : playQueue.getFiles().indexOf(currentFile);
playQueue.setIndex(index);
playQueue.setStatus(status);
return result;
}
public PlayQueueInfo clear() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doClear(request, response);
}
public PlayQueueInfo doClear(HttpServletRequest request, HttpServletResponse response) throws Exception {
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().clear();
boolean serverSidePlaylist = !player.isExternalWithPlaylist();
return convert(request, player, serverSidePlaylist);
}
public PlayQueueInfo shuffle() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doShuffle(request, response);
}
public PlayQueueInfo doShuffle(HttpServletRequest request, HttpServletResponse response) throws Exception {
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().shuffle();
return convert(request, player, false);
}
public PlayQueueInfo remove(int index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doRemove(request, response, index);
}
public PlayQueueInfo toggleStar(int index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
MediaFile file = player.getPlayQueue().getFile(index);
String username = securityService.getCurrentUsername(request);
boolean starred = mediaFileDao.getMediaFileStarredDate(file.getId(), username) != null;
if (starred) {
mediaFileDao.unstarMediaFile(file.getId(), username);
} else {
mediaFileDao.starMediaFile(file.getId(), username);
}
return convert(request, player, false);
}
public PlayQueueInfo doRemove(HttpServletRequest request, HttpServletResponse response, int index) throws Exception {
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().removeFileAt(index);
return convert(request, player, false);
}
public PlayQueueInfo removeMany(int[] indexes) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
for (int i = indexes.length - 1; i >= 0; i--) {
player.getPlayQueue().removeFileAt(indexes[i]);
}
return convert(request, player, false);
}
public PlayQueueInfo rearrange(int[] indexes) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().rearrange(indexes);
return convert(request, player, false);
}
public PlayQueueInfo up(int index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().moveUp(index);
return convert(request, player, false);
}
public PlayQueueInfo down(int index) throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().moveDown(index);
return convert(request, player, false);
}
public PlayQueueInfo toggleRepeat() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().setRepeatEnabled(!player.getPlayQueue().isRepeatEnabled());
return convert(request, player, false);
}
public PlayQueueInfo undo() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().undo();
boolean serverSidePlaylist = !player.isExternalWithPlaylist();
return convert(request, player, serverSidePlaylist);
}
public PlayQueueInfo sortByTrack() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().sort(PlayQueue.SortOrder.TRACK);
return convert(request, player, false);
}
public PlayQueueInfo sortByArtist() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().sort(PlayQueue.SortOrder.ARTIST);
return convert(request, player, false);
}
public PlayQueueInfo sortByAlbum() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response);
player.getPlayQueue().sort(PlayQueue.SortOrder.ALBUM);
return convert(request, player, false);
}
public void setGain(float gain) {
jukeboxService.setGain(gain);
}
private PlayQueueInfo convert(HttpServletRequest request, Player player, boolean serverSidePlaylist) throws Exception {
return convert(request, player, serverSidePlaylist, 0);
}
private PlayQueueInfo convert(HttpServletRequest request, Player player, boolean serverSidePlaylist, int offset) throws Exception {
String url = request.getRequestURL().toString();
if (serverSidePlaylist && player.isJukebox()) {
jukeboxService.updateJukebox(player, offset);
}
boolean isCurrentPlayer = player.getIpAddress() != null && player.getIpAddress().equals(request.getRemoteAddr());
boolean m3uSupported = player.isExternal() || player.isExternalWithPlaylist();
serverSidePlaylist = player.isAutoControlEnabled() && m3uSupported && isCurrentPlayer && serverSidePlaylist;
Locale locale = RequestContextUtils.getLocale(request);
List<PlayQueueInfo.Entry> entries = new ArrayList<PlayQueueInfo.Entry>();
PlayQueue playQueue = player.getPlayQueue();
for (MediaFile file : playQueue.getFiles()) {
String albumUrl = url.replaceFirst("/dwr/.*", "/main.view?id=" + file.getId());
String streamUrl = url.replaceFirst("/dwr/.*", "/stream?player=" + player.getId() + "&id=" + file.getId());
String coverArtUrl = url.replaceFirst("/dwr/.*", "/coverArt.view?id=" + file.getId());
// Rewrite URLs in case we're behind a proxy.
if (settingsService.isRewriteUrlEnabled()) {
String referer = request.getHeader("referer");
albumUrl = StringUtil.rewriteUrl(albumUrl, referer);
streamUrl = StringUtil.rewriteUrl(streamUrl, referer);
}
String remoteStreamUrl = settingsService.rewriteRemoteUrl(streamUrl);
String remoteCoverArtUrl = settingsService.rewriteRemoteUrl(coverArtUrl);
String format = formatFormat(player, file);
String username = securityService.getCurrentUsername(request);
boolean starred = mediaFileService.getMediaFileStarredDate(file.getId(), username) != null;
entries.add(new PlayQueueInfo.Entry(file.getId(), file.getTrackNumber(), file.getTitle(), file.getArtist(),
file.getAlbumName(), file.getGenre(), file.getYear(), formatBitRate(file),
file.getDurationSeconds(), file.getDurationString(), format, formatContentType(format),
formatFileSize(file.getFileSize(), locale), starred, albumUrl, streamUrl, remoteStreamUrl,
coverArtUrl, remoteCoverArtUrl));
}
boolean isStopEnabled = playQueue.getStatus() == PlayQueue.Status.PLAYING && !player.isExternalWithPlaylist();
float gain = jukeboxService.getGain();
return new PlayQueueInfo(entries, isStopEnabled, playQueue.isRepeatEnabled(), serverSidePlaylist, gain);
}
private String formatFileSize(Long fileSize, Locale locale) {
if (fileSize == null) {
return null;
}
return StringUtil.formatBytes(fileSize, locale);
}
private String formatFormat(Player player, MediaFile file) {
return transcodingService.getSuffix(player, file, null);
}
private String formatContentType(String format) {
return StringUtil.getMimeType(format);
}
private String formatBitRate(MediaFile mediaFile) {
if (mediaFile.getBitRate() == null) {
return null;
}
if (mediaFile.isVariableBitRate()) {
return mediaFile.getBitRate() + " Kbps vbr";
}
return mediaFile.getBitRate() + " Kbps";
}
private Player getCurrentPlayer(HttpServletRequest request, HttpServletResponse response) {
return playerService.getPlayer(request, response);
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setLastFmService(LastFmService lastFmService) {
this.lastFmService = lastFmService;
}
public void setJukeboxService(JukeboxService jukeboxService) {
this.jukeboxService = jukeboxService;
}
public void setTranscodingService(TranscodingService transcodingService) {
this.transcodingService = transcodingService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSearchService(SearchService searchService) {
this.searchService = searchService;
}
public void setRatingService(RatingService ratingService) {
this.ratingService = ratingService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPodcastService(PodcastService podcastService) {
this.podcastService = podcastService;
}
public void setMediaFileDao(MediaFileDao mediaFileDao) {
this.mediaFileDao = mediaFileDao;
}
public void setPlayQueueDao(PlayQueueDao playQueueDao) {
this.playQueueDao = playQueueDao;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
}
@@ -0,0 +1,96 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import java.util.List;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.Playlist;
/**
* The playlist of a player.
*
* @author Sindre Mehus
*/
public class PlaylistInfo {
private final Playlist playlist;
private final List<Entry> entries;
public PlaylistInfo(Playlist playlist, List<Entry> entries) {
this.playlist = playlist;
this.entries = entries;
}
public Playlist getPlaylist() {
return playlist;
}
public List<Entry> getEntries() {
return entries;
}
public static class Entry {
private final int id;
private final String title;
private final String artist;
private final String album;
private final String durationAsString;
private final boolean starred;
private final boolean present;
public Entry(int id, String title, String artist, String album, String durationAsString, boolean starred, boolean present) {
this.id = id;
this.title = title;
this.artist = artist;
this.album = album;
this.durationAsString = durationAsString;
this.starred = starred;
this.present = present;
}
public int getId() {
return id;
}
public String getTitle() {
return title;
}
public String getArtist() {
return artist;
}
public String getAlbum() {
return album;
}
public String getDurationAsString() {
return durationAsString;
}
public boolean isStarred() {
return starred;
}
public boolean isPresent() {
return present;
}
}
}
@@ -0,0 +1,266 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import org.libresonic.player.dao.MediaFileDao;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.Playlist;
import org.libresonic.player.i18n.LibresonicLocaleResolver;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.directwebremoting.WebContextFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
/**
* Provides AJAX-enabled services for manipulating playlists.
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class PlaylistService {
private MediaFileService mediaFileService;
private SecurityService securityService;
private org.libresonic.player.service.PlaylistService playlistService;
private MediaFileDao mediaFileDao;
private SettingsService settingsService;
private PlayerService playerService;
private LibresonicLocaleResolver localeResolver;
public List<Playlist> getReadablePlaylists() {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
String username = securityService.getCurrentUsername(request);
return playlistService.getReadablePlaylistsForUser(username);
}
public List<Playlist> getWritablePlaylists() {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
String username = securityService.getCurrentUsername(request);
return playlistService.getWritablePlaylistsForUser(username);
}
public PlaylistInfo getPlaylist(int id) {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
Playlist playlist = playlistService.getPlaylist(id);
List<MediaFile> files = playlistService.getFilesInPlaylist(id, true);
String username = securityService.getCurrentUsername(request);
mediaFileService.populateStarredDate(files, username);
populateAccess(files, username);
return new PlaylistInfo(playlist, createEntries(files));
}
private void populateAccess(List<MediaFile> files, String username) {
for (MediaFile file : files) {
if (!securityService.isFolderAccessAllowed(file, username)) {
file.setPresent(false);
}
}
}
public List<Playlist> createEmptyPlaylist() {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
Locale locale = localeResolver.resolveLocale(request);
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, locale);
Date now = new Date();
Playlist playlist = new Playlist();
playlist.setUsername(securityService.getCurrentUsername(request));
playlist.setCreated(now);
playlist.setChanged(now);
playlist.setShared(false);
playlist.setName(dateFormat.format(now));
playlistService.createPlaylist(playlist);
return getReadablePlaylists();
}
public int createPlaylistForPlayQueue() {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = playerService.getPlayer(request, response);
Locale locale = localeResolver.resolveLocale(request);
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, locale);
Date now = new Date();
Playlist playlist = new Playlist();
playlist.setUsername(securityService.getCurrentUsername(request));
playlist.setCreated(now);
playlist.setChanged(now);
playlist.setShared(false);
playlist.setName(dateFormat.format(now));
playlistService.createPlaylist(playlist);
playlistService.setFilesInPlaylist(playlist.getId(), player.getPlayQueue().getFiles());
return playlist.getId();
}
public int createPlaylistForStarredSongs() {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
Locale locale = localeResolver.resolveLocale(request);
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, locale);
Date now = new Date();
Playlist playlist = new Playlist();
String username = securityService.getCurrentUsername(request);
playlist.setUsername(username);
playlist.setCreated(now);
playlist.setChanged(now);
playlist.setShared(false);
ResourceBundle bundle = ResourceBundle.getBundle("org.libresonic.player.i18n.ResourceBundle", locale);
playlist.setName(bundle.getString("top.starred") + " " + dateFormat.format(now));
playlistService.createPlaylist(playlist);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<MediaFile> songs = mediaFileDao.getStarredFiles(0, Integer.MAX_VALUE, username, musicFolders);
playlistService.setFilesInPlaylist(playlist.getId(), songs);
return playlist.getId();
}
public void appendToPlaylist(int playlistId, List<Integer> mediaFileIds) {
List<MediaFile> files = playlistService.getFilesInPlaylist(playlistId, true);
for (Integer mediaFileId : mediaFileIds) {
MediaFile file = mediaFileService.getMediaFile(mediaFileId);
if (file != null) {
files.add(file);
}
}
playlistService.setFilesInPlaylist(playlistId, files);
}
private List<PlaylistInfo.Entry> createEntries(List<MediaFile> files) {
List<PlaylistInfo.Entry> result = new ArrayList<PlaylistInfo.Entry>();
for (MediaFile file : files) {
result.add(new PlaylistInfo.Entry(file.getId(), file.getTitle(), file.getArtist(), file.getAlbumName(),
file.getDurationString(), file.getStarredDate() != null, file.isPresent()));
}
return result;
}
public PlaylistInfo toggleStar(int id, int index) {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
String username = securityService.getCurrentUsername(request);
List<MediaFile> files = playlistService.getFilesInPlaylist(id, true);
MediaFile file = files.get(index);
boolean starred = mediaFileDao.getMediaFileStarredDate(file.getId(), username) != null;
if (starred) {
mediaFileDao.unstarMediaFile(file.getId(), username);
} else {
mediaFileDao.starMediaFile(file.getId(), username);
}
return getPlaylist(id);
}
public PlaylistInfo remove(int id, int index) {
List<MediaFile> files = playlistService.getFilesInPlaylist(id, true);
files.remove(index);
playlistService.setFilesInPlaylist(id, files);
return getPlaylist(id);
}
public PlaylistInfo up(int id, int index) {
List<MediaFile> files = playlistService.getFilesInPlaylist(id, true);
if (index > 0) {
MediaFile file = files.remove(index);
files.add(index - 1, file);
playlistService.setFilesInPlaylist(id, files);
}
return getPlaylist(id);
}
public PlaylistInfo rearrange(int id, int[] indexes) {
List<MediaFile> files = playlistService.getFilesInPlaylist(id, true);
MediaFile[] newFiles = new MediaFile[files.size()];
for (int i = 0; i < indexes.length; i++) {
newFiles[i] = files.get(indexes[i]);
}
playlistService.setFilesInPlaylist(id, Arrays.asList(newFiles));
return getPlaylist(id);
}
public PlaylistInfo down(int id, int index) {
List<MediaFile> files = playlistService.getFilesInPlaylist(id, true);
if (index < files.size() - 1) {
MediaFile file = files.remove(index);
files.add(index + 1, file);
playlistService.setFilesInPlaylist(id, files);
}
return getPlaylist(id);
}
public void deletePlaylist(int id) {
playlistService.deletePlaylist(id);
}
public PlaylistInfo updatePlaylist(int id, String name, String comment, boolean shared) {
Playlist playlist = playlistService.getPlaylist(id);
playlist.setName(name);
playlist.setComment(comment);
playlist.setShared(shared);
playlistService.updatePlaylist(playlist);
return getPlaylist(id);
}
public void setPlaylistService(org.libresonic.player.service.PlaylistService playlistService) {
this.playlistService = playlistService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setMediaFileDao(MediaFileDao mediaFileDao) {
this.mediaFileDao = mediaFileDao;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setLocaleResolver(LibresonicLocaleResolver localeResolver) {
this.localeResolver = localeResolver;
}
}
@@ -0,0 +1,43 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
/**
* Media folder scanning status.
*
* @author Sindre Mehus
*/
public class ScanInfo {
private final boolean scanning;
private final int count;
public ScanInfo(boolean scanning, int count) {
this.scanning = scanning;
this.count = count;
}
public boolean isScanning() {
return scanning;
}
public int getCount() {
return count;
}
}
@@ -0,0 +1,43 @@
/*
* This file is part of Libresonic.
*
* Libresonic 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.
*
* Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2014 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
/**
* Contains info about a similar artist.
*
* @author Sindre Mehus
*/
public class SimilarArtist {
private final int mediaFileId;
private final String artistName;
public SimilarArtist(int mediaFileId, String artistName) {
this.mediaFileId = mediaFileId;
this.artistName = artistName;
}
public int getMediaFileId() {
return mediaFileId;
}
public String getArtistName() {
return artistName;
}
}
@@ -0,0 +1,63 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import org.libresonic.player.Logger;
import org.libresonic.player.dao.MediaFileDao;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.SecurityService;
import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
/**
* Provides AJAX-enabled services for starring.
* <p/>
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class StarService {
private static final Logger LOG = Logger.getLogger(StarService.class);
private SecurityService securityService;
private MediaFileDao mediaFileDao;
public void star(int id) {
mediaFileDao.starMediaFile(id, getUser());
}
public void unstar(int id) {
mediaFileDao.unstarMediaFile(id, getUser());
}
private String getUser() {
WebContext webContext = WebContextFactory.get();
User user = securityService.getCurrentUser(webContext.getHttpServletRequest());
return user.getUsername();
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setMediaFileDao(MediaFileDao mediaFileDao) {
this.mediaFileDao = mediaFileDao;
}
}
@@ -0,0 +1,130 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.Logger;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.metadata.MetaData;
import org.libresonic.player.service.metadata.MetaDataParser;
import org.libresonic.player.service.metadata.MetaDataParserFactory;
/**
* Provides AJAX-enabled services for editing tags in music files.
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class TagService {
private static final Logger LOG = Logger.getLogger(TagService.class);
private MetaDataParserFactory metaDataParserFactory;
private MediaFileService mediaFileService;
/**
* Updated tags for a given music file.
*
* @param id The ID of the music file.
* @param track The track number.
* @param artist The artist name.
* @param album The album name.
* @param title The song title.
* @param year The release year.
* @param genre The musical genre.
* @return "UPDATED" if the new tags were updated, "SKIPPED" if no update was necessary.
* Otherwise the error message is returned.
*/
public String setTags(int id, String track, String artist, String album, String title, String year, String genre) {
track = StringUtils.trimToNull(track);
artist = StringUtils.trimToNull(artist);
album = StringUtils.trimToNull(album);
title = StringUtils.trimToNull(title);
year = StringUtils.trimToNull(year);
genre = StringUtils.trimToNull(genre);
Integer trackNumber = null;
if (track != null) {
try {
trackNumber = new Integer(track);
} catch (NumberFormatException x) {
LOG.warn("Illegal track number: " + track, x);
}
}
Integer yearNumber = null;
if (year != null) {
try {
yearNumber = new Integer(year);
} catch (NumberFormatException x) {
LOG.warn("Illegal year: " + year, x);
}
}
try {
MediaFile file = mediaFileService.getMediaFile(id);
MetaDataParser parser = metaDataParserFactory.getParser(file.getFile());
if (!parser.isEditingSupported()) {
return "Tag editing of " + FilenameUtils.getExtension(file.getPath()) + " files is not supported.";
}
if (StringUtils.equals(artist, file.getArtist()) &&
StringUtils.equals(album, file.getAlbumName()) &&
StringUtils.equals(title, file.getTitle()) &&
ObjectUtils.equals(yearNumber, file.getYear()) &&
StringUtils.equals(genre, file.getGenre()) &&
ObjectUtils.equals(trackNumber, file.getTrackNumber())) {
return "SKIPPED";
}
MetaData newMetaData = parser.getMetaData(file.getFile());
// Note: album artist is intentionally set, as it is not user-changeable.
newMetaData.setArtist(artist);
newMetaData.setAlbumName(album);
newMetaData.setTitle(title);
newMetaData.setYear(yearNumber);
newMetaData.setGenre(genre);
newMetaData.setTrackNumber(trackNumber);
parser.setMetaData(file, newMetaData);
mediaFileService.refreshMediaFile(file);
mediaFileService.refreshMediaFile(mediaFileService.getParentOf(file));
return "UPDATED";
} catch (Exception x) {
LOG.warn("Failed to update tags for " + id, x);
return x.getMessage();
}
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setMetaDataParserFactory(MetaDataParserFactory metaDataParserFactory) {
this.metaDataParserFactory = metaDataParserFactory;
}
}
@@ -0,0 +1,67 @@
/*
* This file is part of Libresonic.
*
* Libresonic 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.
*
* Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2015 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
/**
* See {@link ArtistInfo}.
*
* @author Sindre Mehus
*/
public class TopSong {
private final int id;
private final String title;
private final String artist;
private final String album;
private final String durationAsString;
private final boolean starred;
public TopSong(int id, String title, String artist, String album, String durationAsString, boolean starred) {
this.id = id;
this.title = title;
this.artist = artist;
this.album = album;
this.durationAsString = durationAsString;
this.starred = starred;
}
public int getId() {
return id;
}
public String getTitle() {
return title;
}
public String getArtist() {
return artist;
}
public String getAlbum() {
return album;
}
public String getDurationAsString() {
return durationAsString;
}
public boolean isStarred() {
return starred;
}
}
@@ -0,0 +1,49 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import org.libresonic.player.domain.*;
import org.libresonic.player.controller.*;
import org.directwebremoting.*;
import javax.servlet.http.*;
/**
* Provides AJAX-enabled services for retrieving the status of ongoing transfers.
* This class is used by the DWR framework (http://getahead.ltd.uk/dwr/).
*
* @author Sindre Mehus
*/
public class TransferService {
/**
* Returns info about any ongoing upload within the current session.
* @return Info about ongoing upload.
*/
public UploadInfo getUploadInfo() {
HttpSession session = WebContextFactory.get().getSession();
TransferStatus status = (TransferStatus) session.getAttribute(UploadController.UPLOAD_STATUS);
if (status != null) {
return new UploadInfo(status.getBytesTransfered(), status.getBytesTotal());
}
return new UploadInfo(0L, 0L);
}
}
@@ -0,0 +1,52 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
/**
* Contains status for a file upload.
*
* @author Sindre Mehus
*/
public class UploadInfo {
private long bytesUploaded;
private long bytesTotal;
public UploadInfo(long bytesUploaded, long bytesTotal) {
this.bytesUploaded = bytesUploaded;
this.bytesTotal = bytesTotal;
}
/**
* Returns the number of bytes uploaded.
* @return The number of bytes uploaded.
*/
public long getBytesUploaded() {
return bytesUploaded;
}
/**
* Returns the total number of bytes.
* @return The total number of bytes.
*/
public long getBytesTotal() {
return bytesTotal;
}
}
@@ -0,0 +1,58 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.cache;
import java.io.File;
import org.springframework.beans.factory.InitializingBean;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.config.Configuration;
import net.sf.ehcache.config.ConfigurationFactory;
import org.libresonic.player.Logger;
import org.libresonic.player.service.SettingsService;
/**
* Initializes Ehcache and creates caches.
*
* @author Sindre Mehus
* @version $Id$
*/
public class CacheFactory implements InitializingBean {
private static final Logger LOG = Logger.getLogger(CacheFactory.class);
private CacheManager cacheManager;
public void afterPropertiesSet() throws Exception {
Configuration configuration = ConfigurationFactory.parseConfiguration();
// Override configuration to make sure cache is stored in Libresonic home dir.
File cacheDir = new File(SettingsService.getLibresonicHome(), "cache");
configuration.getDiskStoreConfiguration().setPath(cacheDir.getPath());
cacheManager = CacheManager.create(configuration);
}
public Ehcache getCache(String name) {
return cacheManager.getCache(name);
}
}
@@ -0,0 +1,129 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.command;
import org.libresonic.player.controller.AdvancedSettingsController;
/**
* Command used in {@link AdvancedSettingsController}.
*
* @author Sindre Mehus
*/
public class AdvancedSettingsCommand {
private String downloadLimit;
private String uploadLimit;
private boolean ldapEnabled;
private String ldapUrl;
private String ldapSearchFilter;
private String ldapManagerDn;
private String ldapManagerPassword;
private boolean ldapAutoShadowing;
private String brand;
private boolean isReloadNeeded;
private boolean toast;
public String getDownloadLimit() {
return downloadLimit;
}
public void setDownloadLimit(String downloadLimit) {
this.downloadLimit = downloadLimit;
}
public String getUploadLimit() {
return uploadLimit;
}
public void setUploadLimit(String uploadLimit) {
this.uploadLimit = uploadLimit;
}
public boolean isLdapEnabled() {
return ldapEnabled;
}
public void setLdapEnabled(boolean ldapEnabled) {
this.ldapEnabled = ldapEnabled;
}
public String getLdapUrl() {
return ldapUrl;
}
public void setLdapUrl(String ldapUrl) {
this.ldapUrl = ldapUrl;
}
public String getLdapSearchFilter() {
return ldapSearchFilter;
}
public void setLdapSearchFilter(String ldapSearchFilter) {
this.ldapSearchFilter = ldapSearchFilter;
}
public String getLdapManagerDn() {
return ldapManagerDn;
}
public void setLdapManagerDn(String ldapManagerDn) {
this.ldapManagerDn = ldapManagerDn;
}
public String getLdapManagerPassword() {
return ldapManagerPassword;
}
public void setLdapManagerPassword(String ldapManagerPassword) {
this.ldapManagerPassword = ldapManagerPassword;
}
public boolean isLdapAutoShadowing() {
return ldapAutoShadowing;
}
public void setLdapAutoShadowing(boolean ldapAutoShadowing) {
this.ldapAutoShadowing = ldapAutoShadowing;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getBrand() {
return brand;
}
public void setReloadNeeded(boolean reloadNeeded) {
isReloadNeeded = reloadNeeded;
}
public boolean isReloadNeeded() {
return isReloadNeeded;
}
public boolean isToast() {
return toast;
}
public void setToast(boolean toast) {
this.toast = toast;
}
}
@@ -0,0 +1,42 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.command;
/**
* Holds the name and description of an enum value.
*
* @author Sindre Mehus
*/
public class EnumHolder {
private String name;
private String description;
public EnumHolder(String name, String description) {
this.name = name;
this.description = description;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
}
@@ -0,0 +1,202 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.command;
import org.libresonic.player.controller.GeneralSettingsController;
import org.libresonic.player.domain.Theme;
/**
* Command used in {@link GeneralSettingsController}.
*
* @author Sindre Mehus
*/
public class GeneralSettingsCommand {
private String playlistFolder;
private String musicFileTypes;
private String videoFileTypes;
private String coverArtFileTypes;
private String index;
private String ignoredArticles;
private String shortcuts;
private boolean sortAlbumsByYear;
private boolean gettingStartedEnabled;
private String welcomeTitle;
private String welcomeSubtitle;
private String welcomeMessage;
private String loginMessage;
private String localeIndex;
private String[] locales;
private String themeIndex;
private Theme[] themes;
private boolean isReloadNeeded;
private boolean toast;
public String getPlaylistFolder() {
return playlistFolder;
}
public void setPlaylistFolder(String playlistFolder) {
this.playlistFolder = playlistFolder;
}
public String getMusicFileTypes() {
return musicFileTypes;
}
public void setMusicFileTypes(String musicFileTypes) {
this.musicFileTypes = musicFileTypes;
}
public String getVideoFileTypes() {
return videoFileTypes;
}
public void setVideoFileTypes(String videoFileTypes) {
this.videoFileTypes = videoFileTypes;
}
public String getCoverArtFileTypes() {
return coverArtFileTypes;
}
public void setCoverArtFileTypes(String coverArtFileTypes) {
this.coverArtFileTypes = coverArtFileTypes;
}
public String getIndex() {
return index;
}
public void setIndex(String index) {
this.index = index;
}
public String getIgnoredArticles() {
return ignoredArticles;
}
public void setIgnoredArticles(String ignoredArticles) {
this.ignoredArticles = ignoredArticles;
}
public String getShortcuts() {
return shortcuts;
}
public void setShortcuts(String shortcuts) {
this.shortcuts = shortcuts;
}
public String getWelcomeTitle() {
return welcomeTitle;
}
public void setWelcomeTitle(String welcomeTitle) {
this.welcomeTitle = welcomeTitle;
}
public String getWelcomeSubtitle() {
return welcomeSubtitle;
}
public void setWelcomeSubtitle(String welcomeSubtitle) {
this.welcomeSubtitle = welcomeSubtitle;
}
public String getWelcomeMessage() {
return welcomeMessage;
}
public void setWelcomeMessage(String welcomeMessage) {
this.welcomeMessage = welcomeMessage;
}
public String getLoginMessage() {
return loginMessage;
}
public void setLoginMessage(String loginMessage) {
this.loginMessage = loginMessage;
}
public String getLocaleIndex() {
return localeIndex;
}
public void setLocaleIndex(String localeIndex) {
this.localeIndex = localeIndex;
}
public String[] getLocales() {
return locales;
}
public void setLocales(String[] locales) {
this.locales = locales;
}
public String getThemeIndex() {
return themeIndex;
}
public void setThemeIndex(String themeIndex) {
this.themeIndex = themeIndex;
}
public Theme[] getThemes() {
return themes;
}
public void setThemes(Theme[] themes) {
this.themes = themes;
}
public boolean isReloadNeeded() {
return isReloadNeeded;
}
public void setReloadNeeded(boolean reloadNeeded) {
isReloadNeeded = reloadNeeded;
}
public boolean isSortAlbumsByYear() {
return sortAlbumsByYear;
}
public void setSortAlbumsByYear(boolean sortAlbumsByYear) {
this.sortAlbumsByYear = sortAlbumsByYear;
}
public boolean isGettingStartedEnabled() {
return gettingStartedEnabled;
}
public void setGettingStartedEnabled(boolean gettingStartedEnabled) {
this.gettingStartedEnabled = gettingStartedEnabled;
}
public boolean isToast() {
return toast;
}
public void setToast(boolean toast) {
this.toast = toast;
}
}
@@ -0,0 +1,187 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.command;
import java.io.File;
import java.util.Date;
import java.util.List;
import org.libresonic.player.controller.MusicFolderSettingsController;
import org.libresonic.player.domain.MusicFolder;
import org.apache.commons.lang.StringUtils;
/**
* Command used in {@link MusicFolderSettingsController}.
*
* @author Sindre Mehus
*/
public class MusicFolderSettingsCommand {
private String interval;
private String hour;
private boolean scanning;
private boolean fastCache;
private boolean organizeByFolderStructure;
private List<MusicFolderInfo> musicFolders;
private MusicFolderInfo newMusicFolder;
private boolean reload;
public String getInterval() {
return interval;
}
public void setInterval(String interval) {
this.interval = interval;
}
public String getHour() {
return hour;
}
public void setHour(String hour) {
this.hour = hour;
}
public boolean isScanning() {
return scanning;
}
public void setScanning(boolean scanning) {
this.scanning = scanning;
}
public boolean isFastCache() {
return fastCache;
}
public List<MusicFolderInfo> getMusicFolders() {
return musicFolders;
}
public void setMusicFolders(List<MusicFolderInfo> musicFolders) {
this.musicFolders = musicFolders;
}
public void setFastCache(boolean fastCache) {
this.fastCache = fastCache;
}
public MusicFolderInfo getNewMusicFolder() {
return newMusicFolder;
}
public void setNewMusicFolder(MusicFolderInfo newMusicFolder) {
this.newMusicFolder = newMusicFolder;
}
public void setReload(boolean reload) {
this.reload = reload;
}
public boolean isReload() {
return reload;
}
public boolean isOrganizeByFolderStructure() {
return organizeByFolderStructure;
}
public void setOrganizeByFolderStructure(boolean organizeByFolderStructure) {
this.organizeByFolderStructure = organizeByFolderStructure;
}
public static class MusicFolderInfo {
private Integer id;
private String path;
private String name;
private boolean enabled;
private boolean delete;
private boolean existing;
public MusicFolderInfo(MusicFolder musicFolder) {
id = musicFolder.getId();
path = musicFolder.getPath().getPath();
name = musicFolder.getName();
enabled = musicFolder.isEnabled();
existing = musicFolder.getPath().exists() && musicFolder.getPath().isDirectory();
}
public MusicFolderInfo() {
enabled = true;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isDelete() {
return delete;
}
public void setDelete(boolean delete) {
this.delete = delete;
}
public MusicFolder toMusicFolder() {
String path = StringUtils.trimToNull(this.path);
if (path == null) {
return null;
}
File file = new File(path);
String name = StringUtils.trimToNull(this.name);
if (name == null) {
name = file.getName();
}
return new MusicFolder(id, new File(path), name, enabled, new Date());
}
public boolean isExisting() {
return existing;
}
}
}
@@ -0,0 +1,100 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.command;
import org.libresonic.player.domain.LicenseInfo;
/**
* @author Sindre Mehus
* @version $Id$
*/
public class NetworkSettingsCommand {
private boolean portForwardingEnabled;
private boolean urlRedirectionEnabled;
private String urlRedirectFrom;
private String urlRedirectCustomUrl;
private String urlRedirectType;
private int port;
private boolean toast;
private LicenseInfo licenseInfo;
public void setPortForwardingEnabled(boolean portForwardingEnabled) {
this.portForwardingEnabled = portForwardingEnabled;
}
public boolean isPortForwardingEnabled() {
return portForwardingEnabled;
}
public boolean isUrlRedirectionEnabled() {
return urlRedirectionEnabled;
}
public void setUrlRedirectionEnabled(boolean urlRedirectionEnabled) {
this.urlRedirectionEnabled = urlRedirectionEnabled;
}
public String getUrlRedirectFrom() {
return urlRedirectFrom;
}
public void setUrlRedirectFrom(String urlRedirectFrom) {
this.urlRedirectFrom = urlRedirectFrom;
}
public String getUrlRedirectCustomUrl() {
return urlRedirectCustomUrl;
}
public void setUrlRedirectCustomUrl(String urlRedirectCustomUrl) {
this.urlRedirectCustomUrl = urlRedirectCustomUrl;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public boolean isToast() {
return toast;
}
public void setToast(boolean toast) {
this.toast = toast;
}
public void setLicenseInfo(LicenseInfo licenseInfo) {
this.licenseInfo = licenseInfo;
}
public LicenseInfo getLicenseInfo() {
return licenseInfo;
}
public String getUrlRedirectType() {
return urlRedirectType;
}
public void setUrlRedirectType(String urlRedirectType) {
this.urlRedirectType = urlRedirectType;
}
}
@@ -0,0 +1,74 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.command;
import org.libresonic.player.controller.*;
/**
* Command used in {@link PasswordSettingsController}.
*
* @author Sindre Mehus
*/
public class PasswordSettingsCommand {
private String username;
private String password;
private String confirmPassword;
private boolean ldapAuthenticated;
private boolean toast;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getConfirmPassword() {
return confirmPassword;
}
public void setConfirmPassword(String confirmPassword) {
this.confirmPassword = confirmPassword;
}
public boolean isLdapAuthenticated() {
return ldapAuthenticated;
}
public void setLdapAuthenticated(boolean ldapAuthenticated) {
this.ldapAuthenticated = ldapAuthenticated;
}
public boolean isToast() {
return toast;
}
public void setToast(boolean toast) {
this.toast = toast;
}
}
@@ -0,0 +1,270 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.command;
import java.util.List;
import org.libresonic.player.controller.PersonalSettingsController;
import org.libresonic.player.domain.AlbumListType;
import org.libresonic.player.domain.Avatar;
import org.libresonic.player.domain.Theme;
import org.libresonic.player.domain.User;
import org.libresonic.player.domain.UserSettings;
/**
* Command used in {@link PersonalSettingsController}.
*
* @author Sindre Mehus
*/
public class PersonalSettingsCommand {
private User user;
private String localeIndex;
private String[] locales;
private String themeIndex;
private Theme[] themes;
private String albumListId;
private AlbumListType[] albumLists;
private int avatarId;
private List<Avatar> avatars;
private Avatar customAvatar;
private UserSettings.Visibility mainVisibility;
private UserSettings.Visibility playlistVisibility;
private boolean partyModeEnabled;
private boolean showNowPlayingEnabled;
private boolean showChatEnabled;
private boolean showArtistInfoEnabled;
private boolean nowPlayingAllowed;
private boolean autoHidePlayQueue;
private boolean finalVersionNotificationEnabled;
private boolean betaVersionNotificationEnabled;
private boolean songNotificationEnabled;
private boolean queueFollowingSongs;
private boolean lastFmEnabled;
private String lastFmUsername;
private String lastFmPassword;
private boolean isReloadNeeded;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getLocaleIndex() {
return localeIndex;
}
public void setLocaleIndex(String localeIndex) {
this.localeIndex = localeIndex;
}
public String[] getLocales() {
return locales;
}
public void setLocales(String[] locales) {
this.locales = locales;
}
public String getThemeIndex() {
return themeIndex;
}
public void setThemeIndex(String themeIndex) {
this.themeIndex = themeIndex;
}
public Theme[] getThemes() {
return themes;
}
public void setThemes(Theme[] themes) {
this.themes = themes;
}
public String getAlbumListId() {
return albumListId;
}
public void setAlbumListId(String albumListId) {
this.albumListId = albumListId;
}
public AlbumListType[] getAlbumLists() {
return albumLists;
}
public void setAlbumLists(AlbumListType[] albumLists) {
this.albumLists = albumLists;
}
public int getAvatarId() {
return avatarId;
}
public void setAvatarId(int avatarId) {
this.avatarId = avatarId;
}
public List<Avatar> getAvatars() {
return avatars;
}
public void setAvatars(List<Avatar> avatars) {
this.avatars = avatars;
}
public Avatar getCustomAvatar() {
return customAvatar;
}
public void setCustomAvatar(Avatar customAvatar) {
this.customAvatar = customAvatar;
}
public UserSettings.Visibility getMainVisibility() {
return mainVisibility;
}
public void setMainVisibility(UserSettings.Visibility mainVisibility) {
this.mainVisibility = mainVisibility;
}
public UserSettings.Visibility getPlaylistVisibility() {
return playlistVisibility;
}
public void setPlaylistVisibility(UserSettings.Visibility playlistVisibility) {
this.playlistVisibility = playlistVisibility;
}
public boolean isPartyModeEnabled() {
return partyModeEnabled;
}
public void setPartyModeEnabled(boolean partyModeEnabled) {
this.partyModeEnabled = partyModeEnabled;
}
public boolean isShowNowPlayingEnabled() {
return showNowPlayingEnabled;
}
public void setShowNowPlayingEnabled(boolean showNowPlayingEnabled) {
this.showNowPlayingEnabled = showNowPlayingEnabled;
}
public boolean isShowChatEnabled() {
return showChatEnabled;
}
public void setShowChatEnabled(boolean showChatEnabled) {
this.showChatEnabled = showChatEnabled;
}
public boolean isShowArtistInfoEnabled() {
return showArtistInfoEnabled;
}
public void setShowArtistInfoEnabled(boolean showArtistInfoEnabled) {
this.showArtistInfoEnabled = showArtistInfoEnabled;
}
public boolean isNowPlayingAllowed() {
return nowPlayingAllowed;
}
public void setNowPlayingAllowed(boolean nowPlayingAllowed) {
this.nowPlayingAllowed = nowPlayingAllowed;
}
public boolean isFinalVersionNotificationEnabled() {
return finalVersionNotificationEnabled;
}
public void setFinalVersionNotificationEnabled(boolean finalVersionNotificationEnabled) {
this.finalVersionNotificationEnabled = finalVersionNotificationEnabled;
}
public boolean isBetaVersionNotificationEnabled() {
return betaVersionNotificationEnabled;
}
public void setBetaVersionNotificationEnabled(boolean betaVersionNotificationEnabled) {
this.betaVersionNotificationEnabled = betaVersionNotificationEnabled;
}
public void setSongNotificationEnabled(boolean songNotificationEnabled) {
this.songNotificationEnabled = songNotificationEnabled;
}
public boolean isSongNotificationEnabled() {
return songNotificationEnabled;
}
public boolean isAutoHidePlayQueue() {
return autoHidePlayQueue;
}
public void setAutoHidePlayQueue(boolean autoHidePlayQueue) {
this.autoHidePlayQueue = autoHidePlayQueue;
}
public boolean isLastFmEnabled() {
return lastFmEnabled;
}
public void setLastFmEnabled(boolean lastFmEnabled) {
this.lastFmEnabled = lastFmEnabled;
}
public String getLastFmUsername() {
return lastFmUsername;
}
public void setLastFmUsername(String lastFmUsername) {
this.lastFmUsername = lastFmUsername;
}
public String getLastFmPassword() {
return lastFmPassword;
}
public void setLastFmPassword(String lastFmPassword) {
this.lastFmPassword = lastFmPassword;
}
public boolean isReloadNeeded() {
return isReloadNeeded;
}
public void setReloadNeeded(boolean reloadNeeded) {
isReloadNeeded = reloadNeeded;
}
public boolean isQueueFollowingSongs() {
return queueFollowingSongs;
}
public void setQueueFollowingSongs(boolean queueFollowingSongs) {
this.queueFollowingSongs = queueFollowingSongs;
}
}
@@ -0,0 +1,227 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.command;
import java.util.Date;
import java.util.List;
import org.libresonic.player.controller.PlayerSettingsController;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.PlayerTechnology;
import org.libresonic.player.domain.TranscodeScheme;
import org.libresonic.player.domain.Transcoding;
/**
* Command used in {@link PlayerSettingsController}.
*
* @author Sindre Mehus
*/
public class PlayerSettingsCommand {
private String playerId;
private String name;
private String description;
private String type;
private Date lastSeen;
private boolean isDynamicIp;
private boolean isAutoControlEnabled;
private String technologyName;
private String transcodeSchemeName;
private boolean transcodingSupported;
private String transcodeDirectory;
private List<Transcoding> allTranscodings;
private int[] activeTranscodingIds;
private EnumHolder[] technologyHolders;
private EnumHolder[] transcodeSchemeHolders;
private Player[] players;
private boolean isAdmin;
private boolean isReloadNeeded;
public String getPlayerId() {
return playerId;
}
public void setPlayerId(String playerId) {
this.playerId = playerId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Date getLastSeen() {
return lastSeen;
}
public void setLastSeen(Date lastSeen) {
this.lastSeen = lastSeen;
}
public boolean isDynamicIp() {
return isDynamicIp;
}
public void setDynamicIp(boolean dynamicIp) {
isDynamicIp = dynamicIp;
}
public boolean isAutoControlEnabled() {
return isAutoControlEnabled;
}
public void setAutoControlEnabled(boolean autoControlEnabled) {
isAutoControlEnabled = autoControlEnabled;
}
public String getTranscodeSchemeName() {
return transcodeSchemeName;
}
public void setTranscodeSchemeName(String transcodeSchemeName) {
this.transcodeSchemeName = transcodeSchemeName;
}
public boolean isTranscodingSupported() {
return transcodingSupported;
}
public void setTranscodingSupported(boolean transcodingSupported) {
this.transcodingSupported = transcodingSupported;
}
public String getTranscodeDirectory() {
return transcodeDirectory;
}
public void setTranscodeDirectory(String transcodeDirectory) {
this.transcodeDirectory = transcodeDirectory;
}
public List<Transcoding> getAllTranscodings() {
return allTranscodings;
}
public void setAllTranscodings(List<Transcoding> allTranscodings) {
this.allTranscodings = allTranscodings;
}
public int[] getActiveTranscodingIds() {
return activeTranscodingIds;
}
public void setActiveTranscodingIds(int[] activeTranscodingIds) {
this.activeTranscodingIds = activeTranscodingIds;
}
public EnumHolder[] getTechnologyHolders() {
return technologyHolders;
}
public void setTechnologies(PlayerTechnology[] technologies) {
technologyHolders = new EnumHolder[technologies.length];
for (int i = 0; i < technologies.length; i++) {
PlayerTechnology technology = technologies[i];
technologyHolders[i] = new EnumHolder(technology.name(), technology.toString());
}
}
public EnumHolder[] getTranscodeSchemeHolders() {
return transcodeSchemeHolders;
}
public void setTranscodeSchemes(TranscodeScheme[] transcodeSchemes) {
transcodeSchemeHolders = new EnumHolder[transcodeSchemes.length];
for (int i = 0; i < transcodeSchemes.length; i++) {
TranscodeScheme scheme = transcodeSchemes[i];
transcodeSchemeHolders[i] = new EnumHolder(scheme.name(), scheme.toString());
}
}
public String getTechnologyName() {
return technologyName;
}
public void setTechnologyName(String technologyName) {
this.technologyName = technologyName;
}
public Player[] getPlayers() {
return players;
}
public void setPlayers(Player[] players) {
this.players = players;
}
public boolean isAdmin() {
return isAdmin;
}
public void setAdmin(boolean admin) {
isAdmin = admin;
}
public boolean isReloadNeeded() {
return isReloadNeeded;
}
public void setReloadNeeded(boolean reloadNeeded) {
isReloadNeeded = reloadNeeded;
}
/**
* Holds the transcoding and whether it is active for the given player.
*/
public static class TranscodingHolder {
private Transcoding transcoding;
private boolean isActive;
public TranscodingHolder(Transcoding transcoding, boolean isActive) {
this.transcoding = transcoding;
this.isActive = isActive;
}
public Transcoding getTranscoding() {
return transcoding;
}
public boolean isActive() {
return isActive;
}
}
}
@@ -0,0 +1,75 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.command;
import org.libresonic.player.controller.PodcastSettingsController;
/**
* Command used in {@link PodcastSettingsController}.
*
* @author Sindre Mehus
*/
public class PodcastSettingsCommand {
private String interval;
private String folder;
private String episodeRetentionCount;
private String episodeDownloadCount;
private boolean toast;
public String getInterval() {
return interval;
}
public void setInterval(String interval) {
this.interval = interval;
}
public String getFolder() {
return folder;
}
public void setFolder(String folder) {
this.folder = folder;
}
public String getEpisodeRetentionCount() {
return episodeRetentionCount;
}
public void setEpisodeRetentionCount(String episodeRetentionCount) {
this.episodeRetentionCount = episodeRetentionCount;
}
public String getEpisodeDownloadCount() {
return episodeDownloadCount;
}
public void setEpisodeDownloadCount(String episodeDownloadCount) {
this.episodeDownloadCount = episodeDownloadCount;
}
public boolean isToast() {
return toast;
}
public void setToast(boolean toast) {
this.toast = toast;
}
}
@@ -0,0 +1,106 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.command;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.controller.PremiumSettingsController;
import org.libresonic.player.domain.LicenseInfo;
import org.libresonic.player.domain.User;
/**
* Command used in {@link PremiumSettingsController}.
*
* @author Sindre Mehus
*/
public class PremiumSettingsCommand {
private String path;
private String brand;
private LicenseInfo licenseInfo;
private String licenseCode;
private boolean forceChange;
private boolean submissionError;
private User user;
private boolean toast;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public LicenseInfo getLicenseInfo() {
return licenseInfo;
}
public String getLicenseCode() {
return licenseCode;
}
public void setLicenseCode(String licenseCode) {
this.licenseCode = StringUtils.trimToNull(licenseCode);
}
public void setLicenseInfo(LicenseInfo licenseInfo) {
this.licenseInfo = licenseInfo;
}
public boolean isForceChange() {
return forceChange;
}
public void setForceChange(boolean forceChange) {
this.forceChange = forceChange;
}
public boolean isSubmissionError() {
return submissionError;
}
public void setSubmissionError(boolean submissionError) {
this.submissionError = submissionError;
}
public void setUser(User user) {
this.user = user;
}
public User getUser() {
return user;
}
public void setToast(boolean toast) {
this.toast = toast;
}
public boolean isToast() {
return toast;
}
}
@@ -0,0 +1,135 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.command;
import org.libresonic.player.domain.*;
import org.libresonic.player.controller.*;
import java.util.*;
/**
* Command used in {@link SearchController}.
*
* @author Sindre Mehus
*/
public class SearchCommand {
private String query;
private List<MediaFile> artists;
private List<MediaFile> albums;
private List<MediaFile> songs;
private boolean isIndexBeingCreated;
private User user;
private boolean partyModeEnabled;
private Player player;
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
public boolean isIndexBeingCreated() {
return isIndexBeingCreated;
}
public void setIndexBeingCreated(boolean indexBeingCreated) {
isIndexBeingCreated = indexBeingCreated;
}
public List<MediaFile> getArtists() {
return artists;
}
public void setArtists(List<MediaFile> artists) {
this.artists = artists;
}
public List<MediaFile> getAlbums() {
return albums;
}
public void setAlbums(List<MediaFile> albums) {
this.albums = albums;
}
public List<MediaFile> getSongs() {
return songs;
}
public void setSongs(List<MediaFile> songs) {
this.songs = songs;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public boolean isPartyModeEnabled() {
return partyModeEnabled;
}
public void setPartyModeEnabled(boolean partyModeEnabled) {
this.partyModeEnabled = partyModeEnabled;
}
public Player getPlayer() {
return player;
}
public void setPlayer(Player player) {
this.player = player;
}
public static class Match {
private MediaFile mediaFile;
private String title;
private String album;
private String artist;
public Match(MediaFile mediaFile, String title, String album, String artist) {
this.mediaFile = mediaFile;
this.title = title;
this.album = album;
this.artist = artist;
}
public MediaFile getMediaFile() {
return mediaFile;
}
public String getTitle() {
return title;
}
public String getAlbum() {
return album;
}
public String getArtist() {
return artist;
}
}
}
@@ -0,0 +1,316 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.command;
import org.libresonic.player.controller.UserSettingsController;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.TranscodeScheme;
import org.libresonic.player.domain.User;
import java.util.List;
/**
* Command used in {@link UserSettingsController}.
*
* @author Sindre Mehus
*/
public class UserSettingsCommand {
private String username;
private boolean isAdminRole;
private boolean isDownloadRole;
private boolean isUploadRole;
private boolean isCoverArtRole;
private boolean isCommentRole;
private boolean isPodcastRole;
private boolean isStreamRole;
private boolean isJukeboxRole;
private boolean isSettingsRole;
private boolean isShareRole;
private List<User> users;
private boolean isAdmin;
private boolean isPasswordChange;
private boolean isNewUser;
private boolean isDeleteUser;
private String password;
private String confirmPassword;
private String email;
private boolean isLdapAuthenticated;
private boolean isLdapEnabled;
private List<MusicFolder> allMusicFolders;
private int[] allowedMusicFolderIds;
private String transcodeSchemeName;
private EnumHolder[] transcodeSchemeHolders;
private boolean transcodingSupported;
private String transcodeDirectory;
private boolean toast;
private boolean reload;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public boolean isAdminRole() {
return isAdminRole;
}
public void setAdminRole(boolean adminRole) {
isAdminRole = adminRole;
}
public boolean isDownloadRole() {
return isDownloadRole;
}
public void setDownloadRole(boolean downloadRole) {
isDownloadRole = downloadRole;
}
public boolean isUploadRole() {
return isUploadRole;
}
public void setUploadRole(boolean uploadRole) {
isUploadRole = uploadRole;
}
public boolean isCoverArtRole() {
return isCoverArtRole;
}
public void setCoverArtRole(boolean coverArtRole) {
isCoverArtRole = coverArtRole;
}
public boolean isCommentRole() {
return isCommentRole;
}
public void setCommentRole(boolean commentRole) {
isCommentRole = commentRole;
}
public boolean isPodcastRole() {
return isPodcastRole;
}
public void setPodcastRole(boolean podcastRole) {
isPodcastRole = podcastRole;
}
public boolean isStreamRole() {
return isStreamRole;
}
public void setStreamRole(boolean streamRole) {
isStreamRole = streamRole;
}
public boolean isJukeboxRole() {
return isJukeboxRole;
}
public void setJukeboxRole(boolean jukeboxRole) {
isJukeboxRole = jukeboxRole;
}
public boolean isSettingsRole() {
return isSettingsRole;
}
public void setSettingsRole(boolean settingsRole) {
isSettingsRole = settingsRole;
}
public boolean isShareRole() {
return isShareRole;
}
public void setShareRole(boolean shareRole) {
isShareRole = shareRole;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
public boolean isAdmin() {
return isAdmin;
}
public void setAdmin(boolean admin) {
isAdmin = admin;
}
public boolean isPasswordChange() {
return isPasswordChange;
}
public void setPasswordChange(boolean passwordChange) {
isPasswordChange = passwordChange;
}
public boolean isNewUser() {
return isNewUser;
}
public void setNewUser(boolean isNewUser) {
this.isNewUser = isNewUser;
}
public boolean isDeleteUser() {
return isDeleteUser;
}
public void setDeleteUser(boolean deleteUser) {
this.isDeleteUser = deleteUser;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getConfirmPassword() {
return confirmPassword;
}
public void setConfirmPassword(String confirmPassword) {
this.confirmPassword = confirmPassword;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public boolean isLdapAuthenticated() {
return isLdapAuthenticated;
}
public void setLdapAuthenticated(boolean ldapAuthenticated) {
isLdapAuthenticated = ldapAuthenticated;
}
public boolean isLdapEnabled() {
return isLdapEnabled;
}
public void setLdapEnabled(boolean ldapEnabled) {
isLdapEnabled = ldapEnabled;
}
public List<MusicFolder> getAllMusicFolders() {
return allMusicFolders;
}
public void setAllMusicFolders(List<MusicFolder> allMusicFolders) {
this.allMusicFolders = allMusicFolders;
}
public int[] getAllowedMusicFolderIds() {
return allowedMusicFolderIds;
}
public void setAllowedMusicFolderIds(int[] allowedMusicFolderIds) {
this.allowedMusicFolderIds = allowedMusicFolderIds;
}
public String getTranscodeSchemeName() {
return transcodeSchemeName;
}
public void setTranscodeSchemeName(String transcodeSchemeName) {
this.transcodeSchemeName = transcodeSchemeName;
}
public EnumHolder[] getTranscodeSchemeHolders() {
return transcodeSchemeHolders;
}
public void setTranscodeSchemes(TranscodeScheme[] transcodeSchemes) {
transcodeSchemeHolders = new EnumHolder[transcodeSchemes.length];
for (int i = 0; i < transcodeSchemes.length; i++) {
TranscodeScheme scheme = transcodeSchemes[i];
transcodeSchemeHolders[i] = new EnumHolder(scheme.name(), scheme.toString());
}
}
public boolean isTranscodingSupported() {
return transcodingSupported;
}
public void setTranscodingSupported(boolean transcodingSupported) {
this.transcodingSupported = transcodingSupported;
}
public String getTranscodeDirectory() {
return transcodeDirectory;
}
public void setTranscodeDirectory(String transcodeDirectory) {
this.transcodeDirectory = transcodeDirectory;
}
public void setUser(User user) {
username = user == null ? null : user.getUsername();
isAdminRole = user != null && user.isAdminRole();
isDownloadRole = user != null && user.isDownloadRole();
isUploadRole = user != null && user.isUploadRole();
isCoverArtRole = user != null && user.isCoverArtRole();
isCommentRole = user != null && user.isCommentRole();
isPodcastRole = user != null && user.isPodcastRole();
isStreamRole = user != null && user.isStreamRole();
isJukeboxRole = user != null && user.isJukeboxRole();
isSettingsRole = user != null && user.isSettingsRole();
isShareRole = user != null && user.isShareRole();
isLdapAuthenticated = user != null && user.isLdapAuthenticated();
}
public void setToast(boolean toast) {
this.toast = toast;
}
public boolean isToast() {
return toast;
}
public boolean isReload() {
return reload;
}
public void setReload(boolean reload) {
this.reload = reload;
}
}
@@ -0,0 +1,60 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.springframework.web.servlet.support.*;
import org.springframework.web.servlet.mvc.*;
import org.springframework.ui.context.*;
import javax.servlet.http.*;
import java.awt.*;
import java.util.*;
/**
* Abstract super class for controllers which generate charts.
*
* @author Sindre Mehus
*/
public abstract class AbstractChartController implements Controller {
/**
* Returns the chart background color for the current theme.
* @param request The servlet request.
* @return The chart background color.
*/
protected Color getBackground(HttpServletRequest request) {
return getColor("backgroundColor", request);
}
/**
* Returns the chart foreground color for the current theme.
* @param request The servlet request.
* @return The chart foreground color.
*/
protected Color getForeground(HttpServletRequest request) {
return getColor("textColor", request);
}
private Color getColor(String code, HttpServletRequest request) {
Theme theme = RequestContextUtils.getTheme(request);
Locale locale = RequestContextUtils.getLocale(request);
String colorHex = theme.getMessageSource().getMessage(code, new Object[0], locale);
return new Color(Integer.parseInt(colorHex, 16));
}
}
@@ -0,0 +1,82 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.command.AdvancedSettingsCommand;
import org.libresonic.player.service.SettingsService;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.apache.commons.lang.StringUtils;
import javax.servlet.http.HttpServletRequest;
/**
* Controller for the page used to administrate advanced settings.
*
* @author Sindre Mehus
*/
public class AdvancedSettingsController extends SimpleFormController {
private SettingsService settingsService;
@Override
protected Object formBackingObject(HttpServletRequest request) throws Exception {
AdvancedSettingsCommand command = new AdvancedSettingsCommand();
command.setDownloadLimit(String.valueOf(settingsService.getDownloadBitrateLimit()));
command.setUploadLimit(String.valueOf(settingsService.getUploadBitrateLimit()));
command.setLdapEnabled(settingsService.isLdapEnabled());
command.setLdapUrl(settingsService.getLdapUrl());
command.setLdapSearchFilter(settingsService.getLdapSearchFilter());
command.setLdapManagerDn(settingsService.getLdapManagerDn());
command.setLdapAutoShadowing(settingsService.isLdapAutoShadowing());
command.setBrand(settingsService.getBrand());
return command;
}
@Override
protected void doSubmitAction(Object comm) throws Exception {
AdvancedSettingsCommand command = (AdvancedSettingsCommand) comm;
command.setToast(true);
command.setReloadNeeded(false);
try {
settingsService.setDownloadBitrateLimit(Long.parseLong(command.getDownloadLimit()));
} catch (NumberFormatException x) { /* Intentionally ignored. */ }
try {
settingsService.setUploadBitrateLimit(Long.parseLong(command.getUploadLimit()));
} catch (NumberFormatException x) { /* Intentionally ignored. */ }
settingsService.setLdapEnabled(command.isLdapEnabled());
settingsService.setLdapUrl(command.getLdapUrl());
settingsService.setLdapSearchFilter(command.getLdapSearchFilter());
settingsService.setLdapManagerDn(command.getLdapManagerDn());
settingsService.setLdapAutoShadowing(command.isLdapAutoShadowing());
if (StringUtils.isNotEmpty(command.getLdapManagerPassword())) {
settingsService.setLdapManagerPassword(command.getLdapManagerPassword());
}
settingsService.save();
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -0,0 +1,38 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.springframework.web.servlet.*;
import org.springframework.web.servlet.mvc.*;
import javax.servlet.http.*;
/**
* Controller for the page which forwards to allmusic.com.
*
* @author Sindre Mehus
*/
public class AllmusicController extends ParameterizableViewController {
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("album", request.getParameter("album"));
return result;
}
}
@@ -0,0 +1,71 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2013 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.io.IOException;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import org.apache.commons.lang.RandomStringUtils;
/**
* @author Sindre Mehus
* @version $Id$
*/
public class AutoCoverDemo {
public static void main(String[] args) throws IOException {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
panel.add(new AlbumComponent(110, 110));
panel.add(new AlbumComponent(150, 150));
panel.add(new AlbumComponent(200, 200));
panel.add(new AlbumComponent(300, 300));
panel.add(new AlbumComponent(400, 240));
panel.add(new AlbumComponent(240, 400));
panel.setBackground(Color.LIGHT_GRAY);
frame.add(panel);
frame.setSize(1000, 800);
frame.setVisible(true);
}
private static class AlbumComponent extends JComponent {
private final int width;
private final int height;
public AlbumComponent(int width, int height) {
this.width = width;
this.height = height;
setPreferredSize(new Dimension(width, height));
}
@Override
protected void paintComponent(Graphics g) {
String key = RandomStringUtils.random(5);
new CoverArtController.AutoCover((Graphics2D) g, key, "Artist with a very long name", "Album", width, height).paintCover();
}
}
}
@@ -0,0 +1,92 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.servlet.mvc.LastModified;
import org.libresonic.player.domain.Avatar;
import org.libresonic.player.domain.AvatarScheme;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.SettingsService;
/**
* Controller which produces avatar images.
*
* @author Sindre Mehus
*/
public class AvatarController implements Controller, LastModified {
private SettingsService settingsService;
public long getLastModified(HttpServletRequest request) {
Avatar avatar = getAvatar(request);
long result = avatar == null ? -1L : avatar.getCreatedDate().getTime();
String username = request.getParameter("username");
if (username != null) {
UserSettings userSettings = settingsService.getUserSettings(username);
result = Math.max(result, userSettings.getChanged().getTime());
}
return result;
}
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
Avatar avatar = getAvatar(request);
if (avatar == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
}
response.setContentType(avatar.getMimeType());
response.getOutputStream().write(avatar.getData());
return null;
}
private Avatar getAvatar(HttpServletRequest request) {
String id = request.getParameter("id");
boolean forceCustom = ServletRequestUtils.getBooleanParameter(request, "forceCustom", false);
if (id != null) {
return settingsService.getSystemAvatar(Integer.parseInt(id));
}
String username = request.getParameter("username");
if (username == null) {
return null;
}
UserSettings userSettings = settingsService.getUserSettings(username);
if (userSettings.getAvatarScheme() == AvatarScheme.CUSTOM || forceCustom) {
return settingsService.getCustomAvatar(username);
}
return settingsService.getSystemAvatar(userSettings.getSystemAvatarId());
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -0,0 +1,141 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.Logger;
import org.libresonic.player.domain.Avatar;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.util.StringUtil;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller which receives uploaded avatar images.
*
* @author Sindre Mehus
*/
public class AvatarUploadController extends ParameterizableViewController {
private static final Logger LOG = Logger.getLogger(AvatarUploadController.class);
private static final int MAX_AVATAR_SIZE = 64;
private SettingsService settingsService;
private SecurityService securityService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
String username = securityService.getCurrentUsername(request);
// Check that we have a file upload request.
if (!ServletFileUpload.isMultipartContent(request)) {
throw new Exception("Illegal request.");
}
Map<String, Object> map = new HashMap<String, Object>();
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<?> items = upload.parseRequest(request);
// Look for file items.
for (Object o : items) {
FileItem item = (FileItem) o;
if (!item.isFormField()) {
String fileName = item.getName();
byte[] data = item.get();
if (StringUtils.isNotBlank(fileName) && data.length > 0) {
createAvatar(fileName, data, username, map);
} else {
map.put("error", new Exception("Missing file."));
LOG.warn("Failed to upload personal image. No file specified.");
}
break;
}
}
map.put("username", username);
map.put("avatar", settingsService.getCustomAvatar(username));
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
private void createAvatar(String fileName, byte[] data, String username, Map<String, Object> map) throws IOException {
BufferedImage image;
try {
image = ImageIO.read(new ByteArrayInputStream(data));
if (image == null) {
throw new Exception("Failed to decode incoming image: " + fileName + " (" + data.length + " bytes).");
}
int width = image.getWidth();
int height = image.getHeight();
String mimeType = StringUtil.getMimeType(FilenameUtils.getExtension(fileName));
// Scale down image if necessary.
if (width > MAX_AVATAR_SIZE || height > MAX_AVATAR_SIZE) {
double scaleFactor = (double) MAX_AVATAR_SIZE / (double) Math.max(width, height);
height = (int) (height * scaleFactor);
width = (int) (width * scaleFactor);
image = CoverArtController.scale(image, width, height);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(image, "jpeg", out);
data = out.toByteArray();
mimeType = StringUtil.getMimeType("jpeg");
map.put("resized", true);
}
Avatar avatar = new Avatar(0, fileName, new Date(), mimeType, width, height, data);
settingsService.setCustomAvatar(avatar, username);
LOG.info("Created avatar '" + fileName + "' (" + data.length + " bytes) for user " + username);
} catch (Exception x) {
LOG.warn("Failed to upload personal image: " + x, x);
map.put("error", x);
}
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,72 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.service.MediaFileService;
/**
* Controller for changing cover art.
*
* @author Sindre Mehus
*/
public class ChangeCoverArtController extends ParameterizableViewController {
private MediaFileService mediaFileService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
String artist = request.getParameter("artist");
String album = request.getParameter("album");
MediaFile dir = mediaFileService.getMediaFile(id);
if (artist == null) {
artist = dir.getArtist();
}
if (album == null) {
album = dir.getAlbumName();
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("id", id);
map.put("artist", artist);
map.put("album", album);
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,731 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.awt.Color;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Semaphore;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.servlet.mvc.LastModified;
import org.libresonic.player.Logger;
import org.libresonic.player.dao.AlbumDao;
import org.libresonic.player.dao.ArtistDao;
import org.libresonic.player.domain.Album;
import org.libresonic.player.domain.Artist;
import org.libresonic.player.domain.CoverArtScheme;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.Playlist;
import org.libresonic.player.domain.PodcastChannel;
import org.libresonic.player.domain.Transcoding;
import org.libresonic.player.domain.VideoTranscodingSettings;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.service.PodcastService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.TranscodingService;
import org.libresonic.player.service.metadata.JaudiotaggerParser;
import org.libresonic.player.util.StringUtil;
/**
* Controller which produces cover art images.
*
* @author Sindre Mehus
*/
public class CoverArtController implements Controller, LastModified {
public static final String ALBUM_COVERART_PREFIX = "al-";
public static final String ARTIST_COVERART_PREFIX = "ar-";
public static final String PLAYLIST_COVERART_PREFIX = "pl-";
public static final String PODCAST_COVERART_PREFIX = "pod-";
private static final Logger LOG = Logger.getLogger(CoverArtController.class);
private MediaFileService mediaFileService;
private TranscodingService transcodingService;
private SettingsService settingsService;
private PlaylistService playlistService;
private PodcastService podcastService;
private ArtistDao artistDao;
private AlbumDao albumDao;
private Semaphore semaphore;
public void init() {
semaphore = new Semaphore(settingsService.getCoverArtConcurrency());
}
public long getLastModified(HttpServletRequest request) {
CoverArtRequest coverArtRequest = createCoverArtRequest(request);
long result = coverArtRequest.lastModified();
// LOG.info("getLastModified - " + coverArtRequest + ": " + new Date(result));
return result;
}
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
CoverArtRequest coverArtRequest = createCoverArtRequest(request);
// LOG.info("handleRequest - " + coverArtRequest);
Integer size = ServletRequestUtils.getIntParameter(request, "size");
// Send fallback image if no ID is given. (No need to cache it, since it will be cached in browser.)
if (coverArtRequest == null) {
sendFallback(size, response);
return null;
}
// Optimize if no scaling is required.
if (size == null && coverArtRequest.getCoverArt() != null) {
// LOG.info("sendUnscaled - " + coverArtRequest);
sendUnscaled(coverArtRequest, response);
return null;
}
// Send cached image, creating it if necessary.
if (size == null) {
size = CoverArtScheme.LARGE.getSize() * 2;
}
try {
File cachedImage = getCachedImage(coverArtRequest, size);
sendImage(cachedImage, response);
} catch (IOException e) {
sendFallback(size, response);
}
return null;
}
private CoverArtRequest createCoverArtRequest(HttpServletRequest request) {
String id = request.getParameter("id");
if (id == null) {
return null;
}
if (id.startsWith(ALBUM_COVERART_PREFIX)) {
return createAlbumCoverArtRequest(Integer.valueOf(id.replace(ALBUM_COVERART_PREFIX, "")));
}
if (id.startsWith(ARTIST_COVERART_PREFIX)) {
return createArtistCoverArtRequest(Integer.valueOf(id.replace(ARTIST_COVERART_PREFIX, "")));
}
if (id.startsWith(PLAYLIST_COVERART_PREFIX)) {
return createPlaylistCoverArtRequest(Integer.valueOf(id.replace(PLAYLIST_COVERART_PREFIX, "")));
}
if (id.startsWith(PODCAST_COVERART_PREFIX)) {
return createPodcastCoverArtRequest(Integer.valueOf(id.replace(PODCAST_COVERART_PREFIX, "")), request);
}
return createMediaFileCoverArtRequest(Integer.valueOf(id), request);
}
private CoverArtRequest createAlbumCoverArtRequest(int id) {
Album album = albumDao.getAlbum(id);
return album == null ? null : new AlbumCoverArtRequest(album);
}
private CoverArtRequest createArtistCoverArtRequest(int id) {
Artist artist = artistDao.getArtist(id);
return artist == null ? null : new ArtistCoverArtRequest(artist);
}
private PlaylistCoverArtRequest createPlaylistCoverArtRequest(int id) {
Playlist playlist = playlistService.getPlaylist(id);
return playlist == null ? null : new PlaylistCoverArtRequest(playlist);
}
private CoverArtRequest createPodcastCoverArtRequest(int id, HttpServletRequest request) {
PodcastChannel channel = podcastService.getChannel(id);
if (channel == null) {
return null;
}
if (channel.getMediaFileId() == null) {
return new PodcastCoverArtRequest(channel);
}
return createMediaFileCoverArtRequest(channel.getMediaFileId(), request);
}
private CoverArtRequest createMediaFileCoverArtRequest(int id, HttpServletRequest request) {
MediaFile mediaFile = mediaFileService.getMediaFile(id);
if (mediaFile == null) {
return null;
}
if (mediaFile.isVideo()) {
int offset = ServletRequestUtils.getIntParameter(request, "offset", 60);
return new VideoCoverArtRequest(mediaFile, offset);
}
return new MediaFileCoverArtRequest(mediaFile);
}
private void sendImage(File file, HttpServletResponse response) throws IOException {
response.setContentType(StringUtil.getMimeType(FilenameUtils.getExtension(file.getName())));
InputStream in = new FileInputStream(file);
try {
IOUtils.copy(in, response.getOutputStream());
} finally {
IOUtils.closeQuietly(in);
}
}
private void sendFallback(Integer size, HttpServletResponse response) throws IOException {
if (response.getContentType() == null) {
response.setContentType(StringUtil.getMimeType("jpeg"));
}
InputStream in = null;
try {
in = getClass().getResourceAsStream("default_cover.jpg");
BufferedImage image = ImageIO.read(in);
if (size != null) {
image = scale(image, size, size);
}
ImageIO.write(image, "jpeg", response.getOutputStream());
} finally {
IOUtils.closeQuietly(in);
}
}
private void sendUnscaled(CoverArtRequest coverArtRequest, HttpServletResponse response) throws IOException {
File file = coverArtRequest.getCoverArt();
JaudiotaggerParser parser = new JaudiotaggerParser();
if (!parser.isApplicable(file)) {
response.setContentType(StringUtil.getMimeType(FilenameUtils.getExtension(file.getName())));
}
InputStream in = null;
try {
in = getImageInputStream(file);
IOUtils.copy(in, response.getOutputStream());
} finally {
IOUtils.closeQuietly(in);
}
}
private File getCachedImage(CoverArtRequest request, int size) throws IOException {
String hash = DigestUtils.md5Hex(request.getKey());
String encoding = request.getCoverArt() != null ? "jpeg" : "png";
File cachedImage = new File(getImageCacheDirectory(size), hash + "." + encoding);
// Synchronize to avoid concurrent writing to the same file.
synchronized (hash.intern()) {
// Is cache missing or obsolete?
if (!cachedImage.exists() || request.lastModified() > cachedImage.lastModified()) {
// LOG.info("Cache MISS - " + request + " (" + size + ")");
OutputStream out = null;
try {
semaphore.acquire();
BufferedImage image = request.createImage(size);
if (image == null) {
throw new Exception("Unable to decode image.");
}
out = new FileOutputStream(cachedImage);
ImageIO.write(image, encoding, out);
} catch (Throwable x) {
// Delete corrupt (probably empty) thumbnail cache.
LOG.warn("Failed to create thumbnail for " + request, x);
IOUtils.closeQuietly(out);
cachedImage.delete();
throw new IOException("Failed to create thumbnail for " + request + ". " + x.getMessage());
} finally {
semaphore.release();
IOUtils.closeQuietly(out);
}
} else {
// LOG.info("Cache HIT - " + request + " (" + size + ")");
}
return cachedImage;
}
}
/**
* Returns an input stream to the image in the given file. If the file is an audio file,
* the embedded album art is returned.
*/
private InputStream getImageInputStream(File file) throws IOException {
JaudiotaggerParser parser = new JaudiotaggerParser();
if (parser.isApplicable(file)) {
MediaFile mediaFile = mediaFileService.getMediaFile(file);
return new ByteArrayInputStream(parser.getImageData(mediaFile));
} else {
return new FileInputStream(file);
}
}
private InputStream getImageInputStreamForVideo(MediaFile mediaFile, int width, int height, int offset) throws Exception {
VideoTranscodingSettings videoSettings = new VideoTranscodingSettings(width, height, offset, 0, false);
TranscodingService.Parameters parameters = new TranscodingService.Parameters(mediaFile, videoSettings);
String command = settingsService.getVideoImageCommand();
parameters.setTranscoding(new Transcoding(null, null, null, null, command, null, null, false));
return transcodingService.getTranscodedInputStream(parameters);
}
private synchronized File getImageCacheDirectory(int size) {
File dir = new File(SettingsService.getLibresonicHome(), "thumbs");
dir = new File(dir, String.valueOf(size));
if (!dir.exists()) {
if (dir.mkdirs()) {
LOG.info("Created thumbnail cache " + dir);
} else {
LOG.error("Failed to create thumbnail cache " + dir);
}
}
return dir;
}
public static BufferedImage scale(BufferedImage image, int width, int height) {
int w = image.getWidth();
int h = image.getHeight();
BufferedImage thumb = image;
// For optimal results, use step by step bilinear resampling - halfing the size at each step.
do {
w /= 2;
h /= 2;
if (w < width) {
w = width;
}
if (h < height) {
h = height;
}
BufferedImage temp = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
thumb = temp;
} while (w != width);
return thumb;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setArtistDao(ArtistDao artistDao) {
this.artistDao = artistDao;
}
public void setAlbumDao(AlbumDao albumDao) {
this.albumDao = albumDao;
}
public void setTranscodingService(TranscodingService transcodingService) {
this.transcodingService = transcodingService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
public void setPodcastService(PodcastService podcastService) {
this.podcastService = podcastService;
}
private abstract class CoverArtRequest {
protected File coverArt;
private CoverArtRequest() {
}
private CoverArtRequest(String coverArtPath) {
this.coverArt = coverArtPath == null ? null : new File(coverArtPath);
}
private File getCoverArt() {
return coverArt;
}
public abstract String getKey();
public abstract long lastModified();
public BufferedImage createImage(int size) {
if (coverArt != null) {
InputStream in = null;
try {
in = getImageInputStream(coverArt);
return scale(ImageIO.read(in), size, size);
} catch (Throwable x) {
LOG.warn("Failed to process cover art " + coverArt + ": " + x, x);
} finally {
IOUtils.closeQuietly(in);
}
}
return createAutoCover(size, size);
}
protected BufferedImage createAutoCover(int width, int height) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = image.createGraphics();
AutoCover autoCover = new AutoCover(graphics, getKey(), getArtist(), getAlbum(), width, height);
autoCover.paintCover();
graphics.dispose();
return image;
}
public abstract String getAlbum();
public abstract String getArtist();
}
private class ArtistCoverArtRequest extends CoverArtRequest {
private final Artist artist;
private ArtistCoverArtRequest(Artist artist) {
super(artist.getCoverArtPath());
this.artist = artist;
}
@Override
public String getKey() {
return artist.getCoverArtPath() != null ? artist.getCoverArtPath() : (ARTIST_COVERART_PREFIX + artist.getId());
}
@Override
public long lastModified() {
return coverArt != null ? coverArt.lastModified() : artist.getLastScanned().getTime();
}
@Override
public String getAlbum() {
return null;
}
@Override
public String getArtist() {
return artist.getName();
}
@Override
public String toString() {
return "Artist " + artist.getId() + " - " + artist.getName();
}
}
private class AlbumCoverArtRequest extends CoverArtRequest {
private final Album album;
private AlbumCoverArtRequest(Album album) {
super(album.getCoverArtPath());
this.album = album;
}
@Override
public String getKey() {
return album.getCoverArtPath() != null ? album.getCoverArtPath() : (ALBUM_COVERART_PREFIX + album.getId());
}
@Override
public long lastModified() {
return coverArt != null ? coverArt.lastModified() : album.getLastScanned().getTime();
}
@Override
public String getAlbum() {
return album.getName();
}
@Override
public String getArtist() {
return album.getArtist();
}
@Override
public String toString() {
return "Album " + album.getId() + " - " + album.getName();
}
}
private class PlaylistCoverArtRequest extends CoverArtRequest {
private final Playlist playlist;
private PlaylistCoverArtRequest(Playlist playlist) {
super(null);
this.playlist = playlist;
}
@Override
public String getKey() {
return PLAYLIST_COVERART_PREFIX + playlist.getId();
}
@Override
public long lastModified() {
return playlist.getChanged().getTime();
}
@Override
public String getAlbum() {
return null;
}
@Override
public String getArtist() {
return playlist.getName();
}
@Override
public String toString() {
return "Playlist " + playlist.getId() + " - " + playlist.getName();
}
@Override
public BufferedImage createImage(int size) {
List<MediaFile> albums = getRepresentativeAlbums();
if (albums.isEmpty()) {
return createAutoCover(size, size);
}
if (albums.size() < 4) {
return new MediaFileCoverArtRequest(albums.get(0)).createImage(size);
}
BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = image.createGraphics();
int half = size / 2;
graphics.drawImage(new MediaFileCoverArtRequest(albums.get(0)).createImage(half), null, 0, 0);
graphics.drawImage(new MediaFileCoverArtRequest(albums.get(1)).createImage(half), null, half, 0);
graphics.drawImage(new MediaFileCoverArtRequest(albums.get(2)).createImage(half), null, 0, half);
graphics.drawImage(new MediaFileCoverArtRequest(albums.get(3)).createImage(half), null, half, half);
graphics.dispose();
return image;
}
private List<MediaFile> getRepresentativeAlbums() {
Set<MediaFile> albums = new LinkedHashSet<MediaFile>();
for (MediaFile song : playlistService.getFilesInPlaylist(playlist.getId())) {
MediaFile album = mediaFileService.getParentOf(song);
if (album != null && !mediaFileService.isRoot(album)) {
albums.add(album);
}
}
return new ArrayList<MediaFile>(albums);
}
}
private class PodcastCoverArtRequest extends CoverArtRequest {
private final PodcastChannel channel;
public PodcastCoverArtRequest(PodcastChannel channel) {
this.channel = channel;
}
@Override
public String getKey() {
return PODCAST_COVERART_PREFIX + channel.getId();
}
@Override
public long lastModified() {
return -1;
}
@Override
public String getAlbum() {
return null;
}
@Override
public String getArtist() {
return channel.getTitle() != null ? channel.getTitle() : channel.getUrl();
}
}
private class MediaFileCoverArtRequest extends CoverArtRequest {
private final MediaFile mediaFile;
private final MediaFile dir;
private MediaFileCoverArtRequest(MediaFile mediaFile) {
this.mediaFile = mediaFile;
dir = mediaFile.isDirectory() ? mediaFile : mediaFileService.getParentOf(mediaFile);
coverArt = mediaFileService.getCoverArt(mediaFile);
}
@Override
public String getKey() {
return coverArt != null ? coverArt.getPath() : dir.getPath();
}
@Override
public long lastModified() {
return coverArt != null ? coverArt.lastModified() : dir.getChanged().getTime();
}
@Override
public String getAlbum() {
return dir.getName();
}
@Override
public String getArtist() {
return dir.getAlbumArtist() != null ? dir.getAlbumArtist() : dir.getArtist();
}
@Override
public String toString() {
return "Media file " + mediaFile.getId() + " - " + mediaFile;
}
}
private class VideoCoverArtRequest extends CoverArtRequest {
private final MediaFile mediaFile;
private final int offset;
private VideoCoverArtRequest(MediaFile mediaFile, int offset) {
this.mediaFile = mediaFile;
this.offset = offset;
}
@Override
public BufferedImage createImage(int size) {
int height = size;
int width = height * 16 / 9;
InputStream in = null;
try {
in = getImageInputStreamForVideo(mediaFile, width, height, offset);
BufferedImage result = ImageIO.read(in);
if (result == null) {
throw new NullPointerException();
}
return result;
} catch (Throwable x) {
LOG.warn("Failed to process cover art for " + mediaFile + ": " + x, x);
} finally {
IOUtils.closeQuietly(in);
}
return createAutoCover(width, height);
}
@Override
public String getKey() {
return mediaFile.getPath() + "/" + offset;
}
@Override
public long lastModified() {
return mediaFile.getChanged().getTime();
}
@Override
public String getAlbum() {
return null;
}
@Override
public String getArtist() {
return mediaFile.getName();
}
@Override
public String toString() {
return "Video file " + mediaFile.getId() + " - " + mediaFile;
}
}
static class AutoCover {
private final static int[] COLORS = {0x33B5E5, 0xAA66CC, 0x99CC00, 0xFFBB33, 0xFF4444};
private final Graphics2D graphics;
private final String artist;
private final String album;
private final int width;
private final int height;
private final Color color;
public AutoCover(Graphics2D graphics, String key, String artist, String album, int width, int height) {
this.graphics = graphics;
this.artist = artist;
this.album = album;
this.width = width;
this.height = height;
int hash = key.hashCode();
int rgb = COLORS[Math.abs(hash) % COLORS.length];
this.color = new Color(rgb);
}
public void paintCover() {
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
graphics.setPaint(color);
graphics.fillRect(0, 0, width, height);
int y = height * 2 / 3;
graphics.setPaint(new GradientPaint(0, y, new Color(82, 82, 82), 0, height, Color.BLACK));
graphics.fillRect(0, y, width, height / 3);
graphics.setPaint(Color.WHITE);
float fontSize = 3.0f + height * 0.07f;
Font font = new Font(Font.SANS_SERIF, Font.BOLD, (int) fontSize);
graphics.setFont(font);
if (album != null) {
graphics.drawString(album, width * 0.05f, height * 0.6f);
}
if (artist != null) {
graphics.drawString(artist, width * 0.05f, height * 0.8f);
}
int borderWidth = height / 50;
graphics.fillRect(0, 0, borderWidth, height);
graphics.fillRect(width - borderWidth, 0, height - borderWidth, height);
graphics.fillRect(0, 0, width, borderWidth);
graphics.fillRect(0, height - borderWidth, width, height);
}
}
}
@@ -0,0 +1,66 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.dao.DaoHelper;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller for the DB admin page.
*
* @author Sindre Mehus
*/
public class DBController extends ParameterizableViewController {
private DaoHelper daoHelper;
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
String query = request.getParameter("query");
if (query != null) {
map.put("query", query);
try {
List<?> result = daoHelper.getJdbcTemplate().query(query, new ColumnMapRowMapper());
map.put("result", result);
} catch (DataAccessException x) {
map.put("error", ExceptionUtils.getRootCause(x).getMessage());
}
}
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public void setDaoHelper(DaoHelper daoHelper) {
this.daoHelper = daoHelper;
}
}
@@ -0,0 +1,95 @@
/*
* This file is part of Libresonic.
*
* Libresonic 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.
*
* Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2013 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.UPnPService;
/**
* Controller for the page used to administrate the UPnP/DLNA server settings.
*
* @author Sindre Mehus
*/
public class DLNASettingsController extends ParameterizableViewController {
private UPnPService upnpService;
private SettingsService settingsService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
if (isFormSubmission(request)) {
handleParameters(request);
map.put("toast", true);
}
ModelAndView result = super.handleRequestInternal(request, response);
map.put("dlnaEnabled", settingsService.isDlnaEnabled());
map.put("dlnaServerName", settingsService.getDlnaServerName());
map.put("licenseInfo", settingsService.getLicenseInfo());
result.addObject("model", map);
return result;
}
/**
* Determine if the given request represents a form submission.
*
* @param request current HTTP request
* @return if the request represents a form submission
*/
private boolean isFormSubmission(HttpServletRequest request) {
return "POST".equals(request.getMethod());
}
private void handleParameters(HttpServletRequest request) {
boolean dlnaEnabled = ServletRequestUtils.getBooleanParameter(request, "dlnaEnabled", false);
String dlnaServerName = StringUtils.trimToNull(request.getParameter("dlnaServerName"));
if (dlnaServerName == null) {
dlnaServerName = "Libresonic";
}
upnpService.setMediaServerEnabled(false);
settingsService.setDlnaEnabled(dlnaEnabled);
settingsService.setDlnaServerName(dlnaServerName);
settingsService.save();
upnpService.setMediaServerEnabled(dlnaEnabled);
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setUpnpService(UPnPService upnpService) {
this.upnpService = upnpService;
}
}
@@ -0,0 +1,414 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.servlet.mvc.LastModified;
import org.libresonic.player.Logger;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.PlayQueue;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.Playlist;
import org.libresonic.player.domain.TransferStatus;
import org.libresonic.player.domain.User;
import org.libresonic.player.io.RangeOutputStream;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.StatusService;
import org.libresonic.player.util.FileUtil;
import org.libresonic.player.util.HttpRange;
import org.libresonic.player.util.Util;
/**
* A controller used for downloading files to a remote client. If the requested path refers to a file, the
* given file is downloaded. If the requested path refers to a directory, the entire directory (including
* sub-directories) are downloaded as an uncompressed zip-file.
*
* @author Sindre Mehus
*/
public class DownloadController implements Controller, LastModified {
private static final Logger LOG = Logger.getLogger(DownloadController.class);
private PlayerService playerService;
private StatusService statusService;
private SecurityService securityService;
private PlaylistService playlistService;
private SettingsService settingsService;
private MediaFileService mediaFileService;
public long getLastModified(HttpServletRequest request) {
try {
MediaFile mediaFile = getMediaFile(request);
if (mediaFile == null || mediaFile.isDirectory() || mediaFile.getChanged() == null) {
return -1;
}
return mediaFile.getChanged().getTime();
} catch (ServletRequestBindingException e) {
return -1;
}
}
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
User user = securityService.getCurrentUser(request);
TransferStatus status = null;
try {
status = statusService.createDownloadStatus(playerService.getPlayer(request, response, false, false));
MediaFile mediaFile = getMediaFile(request);
Integer playlistId = ServletRequestUtils.getIntParameter(request, "playlist");
String playerId = request.getParameter("player");
int[] indexes = request.getParameter("i") == null ? null : ServletRequestUtils.getIntParameters(request, "i");
if (mediaFile != null) {
response.setIntHeader("ETag", mediaFile.getId());
response.setHeader("Accept-Ranges", "bytes");
}
HttpRange range = HttpRange.valueOf(request.getHeader("Range"));
if (range != null) {
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
LOG.info("Got HTTP range: " + range);
}
if (mediaFile != null) {
if (!securityService.isFolderAccessAllowed(mediaFile, user.getUsername())) {
response.sendError(HttpServletResponse.SC_FORBIDDEN,
"Access to file " + mediaFile.getId() + " is forbidden for user " + user.getUsername());
return null;
}
if (mediaFile.isFile()) {
downloadFile(response, status, mediaFile.getFile(), range);
} else {
List<MediaFile> children = mediaFileService.getChildrenOf(mediaFile, true, false, true);
String zipFileName = FilenameUtils.getBaseName(mediaFile.getPath()) + ".zip";
File coverArtFile = indexes == null ? mediaFile.getCoverArtFile() : null;
downloadFiles(response, status, children, indexes, coverArtFile, range, zipFileName);
}
} else if (playlistId != null) {
List<MediaFile> songs = playlistService.getFilesInPlaylist(playlistId);
Playlist playlist = playlistService.getPlaylist(playlistId);
downloadFiles(response, status, songs, null, null, range, playlist.getName() + ".zip");
} else if (playerId != null) {
Player player = playerService.getPlayerById(playerId);
PlayQueue playQueue = player.getPlayQueue();
playQueue.setName("Playlist");
downloadFiles(response, status, playQueue.getFiles(), indexes, null, range, "download.zip");
}
} finally {
if (status != null) {
statusService.removeDownloadStatus(status);
securityService.updateUserByteCounts(user, 0L, status.getBytesTransfered(), 0L);
}
}
return null;
}
private MediaFile getMediaFile(HttpServletRequest request) throws ServletRequestBindingException {
Integer id = ServletRequestUtils.getIntParameter(request, "id");
return id == null ? null : mediaFileService.getMediaFile(id);
}
/**
* Downloads a single file.
*
*
* @param response The HTTP response.
* @param status The download status.
* @param file The file to download.
* @param range The byte range, may be <code>null</code>.
* @throws IOException If an I/O error occurs.
*/
private void downloadFile(HttpServletResponse response, TransferStatus status, File file, HttpRange range) throws IOException {
LOG.info("Starting to download '" + FileUtil.getShortPath(file) + "' to " + status.getPlayer());
status.setFile(file);
response.setContentType("application/x-download");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodeAsRFC5987(file.getName()));
if (range == null) {
Util.setContentLength(response, file.length());
}
copyFileToStream(file, RangeOutputStream.wrap(response.getOutputStream(), range), status, range);
LOG.info("Downloaded '" + FileUtil.getShortPath(file) + "' to " + status.getPlayer());
}
private String encodeAsRFC5987(String string) throws UnsupportedEncodingException {
byte[] stringAsByteArray = string.getBytes("UTF-8");
char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
byte[] attrChar = {'!', '#', '$', '&', '+', '-', '.', '^', '_', '`', '|', '~', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
StringBuilder sb = new StringBuilder();
for (byte b : stringAsByteArray) {
if (Arrays.binarySearch(attrChar, b) >= 0) {
sb.append((char) b);
} else {
sb.append('%');
sb.append(digits[0x0f & (b >>> 4)]);
sb.append(digits[b & 0x0f]);
}
}
return sb.toString();
}
/**
* Downloads the given files. The files are packed together in an
* uncompressed zip-file.
*
*
* @param response The HTTP response.
* @param status The download status.
* @param files The files to download.
* @param indexes Only download songs at these indexes. May be <code>null</code>.
* @param coverArtFile The cover art file to include, may be {@code null}.
*@param range The byte range, may be <code>null</code>.
* @param zipFileName The name of the resulting zip file. @throws IOException If an I/O error occurs.
*/
private void downloadFiles(HttpServletResponse response, TransferStatus status, List<MediaFile> files, int[] indexes, File coverArtFile, HttpRange range, String zipFileName) throws IOException {
if (indexes != null && indexes.length == 1) {
downloadFile(response, status, files.get(indexes[0]).getFile(), range);
return;
}
LOG.info("Starting to download '" + zipFileName + "' to " + status.getPlayer());
response.setContentType("application/x-download");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodeAsRFC5987(zipFileName));
ZipOutputStream out = new ZipOutputStream(RangeOutputStream.wrap(response.getOutputStream(), range));
out.setMethod(ZipOutputStream.STORED); // No compression.
List<MediaFile> filesToDownload = new ArrayList<MediaFile>();
if (indexes == null) {
filesToDownload.addAll(files);
} else {
for (int index : indexes) {
try {
filesToDownload.add(files.get(index));
} catch (IndexOutOfBoundsException x) { /* Ignored */}
}
}
for (MediaFile mediaFile : filesToDownload) {
zip(out, mediaFile.getParentFile(), mediaFile.getFile(), status, range);
}
if (coverArtFile != null && coverArtFile.exists()) {
zip(out, coverArtFile.getParentFile(), coverArtFile, status, range);
}
out.close();
LOG.info("Downloaded '" + zipFileName + "' to " + status.getPlayer());
}
/**
* Utility method for writing the content of a given file to a given output stream.
*
*
* @param file The file to copy.
* @param out The output stream to write to.
* @param status The download status.
* @param range The byte range, may be <code>null</code>.
* @throws IOException If an I/O error occurs.
*/
private void copyFileToStream(File file, OutputStream out, TransferStatus status, HttpRange range) throws IOException {
LOG.info("Downloading '" + FileUtil.getShortPath(file) + "' to " + status.getPlayer());
final int bufferSize = 16 * 1024; // 16 Kbit
InputStream in = new BufferedInputStream(new FileInputStream(file), bufferSize);
try {
byte[] buf = new byte[bufferSize];
long bitrateLimit = 0;
long lastLimitCheck = 0;
while (true) {
long before = System.currentTimeMillis();
int n = in.read(buf);
if (n == -1) {
break;
}
out.write(buf, 0, n);
// Don't sleep if outside range.
if (range != null && !range.contains(status.getBytesSkipped() + status.getBytesTransfered())) {
status.addBytesSkipped(n);
continue;
}
status.addBytesTransfered(n);
long after = System.currentTimeMillis();
// Calculate bitrate limit every 5 seconds.
if (after - lastLimitCheck > 5000) {
bitrateLimit = 1024L * settingsService.getDownloadBitrateLimit() /
Math.max(1, statusService.getAllDownloadStatuses().size());
lastLimitCheck = after;
}
// Sleep for a while to throttle bitrate.
if (bitrateLimit != 0) {
long sleepTime = 8L * 1000 * bufferSize / bitrateLimit - (after - before);
if (sleepTime > 0L) {
try {
Thread.sleep(sleepTime);
} catch (Exception x) {
LOG.warn("Failed to sleep.", x);
}
}
}
}
} finally {
out.flush();
IOUtils.closeQuietly(in);
}
}
/**
* Writes a file or a directory structure to a zip output stream. File entries in the zip file are relative
* to the given root.
*
*
* @param out The zip output stream.
* @param root The root of the directory structure. Used to create path information in the zip file.
* @param file The file or directory to zip.
* @param status The download status.
* @param range The byte range, may be <code>null</code>.
* @throws IOException If an I/O error occurs.
*/
private void zip(ZipOutputStream out, File root, File file, TransferStatus status, HttpRange range) throws IOException {
// Exclude all hidden files starting with a "."
if (file.getName().startsWith(".")) {
return;
}
String zipName = file.getCanonicalPath().substring(root.getCanonicalPath().length() + 1);
if (file.isFile()) {
status.setFile(file);
ZipEntry zipEntry = new ZipEntry(zipName);
zipEntry.setSize(file.length());
zipEntry.setCompressedSize(file.length());
zipEntry.setCrc(computeCrc(file));
out.putNextEntry(zipEntry);
copyFileToStream(file, out, status, range);
out.closeEntry();
} else {
ZipEntry zipEntry = new ZipEntry(zipName + '/');
zipEntry.setSize(0);
zipEntry.setCompressedSize(0);
zipEntry.setCrc(0);
out.putNextEntry(zipEntry);
out.closeEntry();
File[] children = FileUtil.listFiles(file);
for (File child : children) {
zip(out, root, child, status, range);
}
}
}
/**
* Computes the CRC checksum for the given file.
*
* @param file The file to compute checksum for.
* @return A CRC32 checksum.
* @throws IOException If an I/O error occurs.
*/
private long computeCrc(File file) throws IOException {
CRC32 crc = new CRC32();
InputStream in = new FileInputStream(file);
try {
byte[] buf = new byte[8192];
int n = in.read(buf);
while (n != -1) {
crc.update(buf, 0, n);
n = in.read(buf);
}
} finally {
in.close();
}
return crc.getValue();
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setStatusService(StatusService statusService) {
this.statusService = statusService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,197 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.FilenameUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.metadata.JaudiotaggerParser;
import org.libresonic.player.service.metadata.MetaDataParser;
import org.libresonic.player.service.metadata.MetaDataParserFactory;
/**
* Controller for the page used to edit MP3 tags.
*
* @author Sindre Mehus
*/
public class EditTagsController extends ParameterizableViewController {
private MetaDataParserFactory metaDataParserFactory;
private MediaFileService mediaFileService;
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
MediaFile dir = mediaFileService.getMediaFile(id);
List<MediaFile> files = mediaFileService.getChildrenOf(dir, true, false, true, false);
Map<String, Object> map = new HashMap<String, Object>();
if (!files.isEmpty()) {
map.put("defaultArtist", files.get(0).getArtist());
map.put("defaultAlbum", files.get(0).getAlbumName());
map.put("defaultYear", files.get(0).getYear());
map.put("defaultGenre", files.get(0).getGenre());
}
map.put("allGenres", JaudiotaggerParser.getID3V1Genres());
List<Song> songs = new ArrayList<Song>();
for (int i = 0; i < files.size(); i++) {
songs.add(createSong(files.get(i), i));
}
map.put("id", id);
map.put("songs", songs);
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
private Song createSong(MediaFile file, int index) {
MetaDataParser parser = metaDataParserFactory.getParser(file.getFile());
Song song = new Song();
song.setId(file.getId());
song.setFileName(FilenameUtils.getBaseName(file.getPath()));
song.setTrack(file.getTrackNumber());
song.setSuggestedTrack(index + 1);
song.setTitle(file.getTitle());
song.setSuggestedTitle(parser.guessTitle(file.getFile()));
song.setArtist(file.getArtist());
song.setAlbum(file.getAlbumName());
song.setYear(file.getYear());
song.setGenre(file.getGenre());
return song;
}
public void setMetaDataParserFactory(MetaDataParserFactory metaDataParserFactory) {
this.metaDataParserFactory = metaDataParserFactory;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
/**
* Contains information about a single song.
*/
public static class Song {
private int id;
private String fileName;
private Integer suggestedTrack;
private Integer track;
private String suggestedTitle;
private String title;
private String artist;
private String album;
private Integer year;
private String genre;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public Integer getSuggestedTrack() {
return suggestedTrack;
}
public void setSuggestedTrack(Integer suggestedTrack) {
this.suggestedTrack = suggestedTrack;
}
public Integer getTrack() {
return track;
}
public void setTrack(Integer track) {
this.track = track;
}
public String getSuggestedTitle() {
return suggestedTitle;
}
public void setSuggestedTitle(String suggestedTitle) {
this.suggestedTitle = suggestedTitle;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
public String getAlbum() {
return album;
}
public void setAlbum(String album) {
this.album = album;
}
public Integer getYear() {
return year;
}
public void setYear(Integer year) {
this.year = year;
}
public String getGenre() {
return genre;
}
public void setGenre(String genre) {
this.genre = genre;
}
}
}
@@ -0,0 +1,125 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.Share;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.ShareService;
/**
* Controller for the page used to play shared music (Twitter, Facebook etc).
*
* @author Sindre Mehus
*/
public class ExternalPlayerController extends ParameterizableViewController {
private SettingsService settingsService;
private PlayerService playerService;
private ShareService shareService;
private MediaFileService mediaFileService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
String pathInfo = request.getPathInfo();
if (pathInfo == null || !pathInfo.startsWith("/")) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
}
Share share = shareService.getShareByName(pathInfo.substring(1));
if (share != null && share.getExpires() != null && share.getExpires().before(new Date())) {
share = null;
}
if (share != null) {
share.setLastVisited(new Date());
share.setVisitCount(share.getVisitCount() + 1);
shareService.updateShare(share);
}
Player player = playerService.getGuestPlayer(request);
map.put("share", share);
map.put("songs", getSongs(share, player.getUsername()));
map.put("redirectUrl", settingsService.getUrlRedirectUrl());
map.put("player", player.getId());
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
private List<MediaFile> getSongs(Share share, String username) throws IOException {
List<MediaFile> result = new ArrayList<MediaFile>();
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
if (share != null) {
for (MediaFile file : shareService.getSharedFiles(share.getId(), musicFolders)) {
if (file.getFile().exists()) {
if (file.isDirectory()) {
result.addAll(mediaFileService.getChildrenOf(file, true, false, true));
} else {
result.add(file);
}
}
}
}
return result;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setShareService(ShareService shareService) {
this.shareService = shareService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,119 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.libresonic.player.command.GeneralSettingsCommand;
import org.libresonic.player.domain.Theme;
import org.libresonic.player.service.SettingsService;
/**
* Controller for the page used to administrate general settings.
*
* @author Sindre Mehus
*/
public class GeneralSettingsController extends SimpleFormController {
private SettingsService settingsService;
protected Object formBackingObject(HttpServletRequest request) throws Exception {
GeneralSettingsCommand command = new GeneralSettingsCommand();
command.setCoverArtFileTypes(settingsService.getCoverArtFileTypes());
command.setIgnoredArticles(settingsService.getIgnoredArticles());
command.setShortcuts(settingsService.getShortcuts());
command.setIndex(settingsService.getIndexString());
command.setPlaylistFolder(settingsService.getPlaylistFolder());
command.setMusicFileTypes(settingsService.getMusicFileTypes());
command.setVideoFileTypes(settingsService.getVideoFileTypes());
command.setSortAlbumsByYear(settingsService.isSortAlbumsByYear());
command.setGettingStartedEnabled(settingsService.isGettingStartedEnabled());
command.setWelcomeTitle(settingsService.getWelcomeTitle());
command.setWelcomeSubtitle(settingsService.getWelcomeSubtitle());
command.setWelcomeMessage(settingsService.getWelcomeMessage());
command.setLoginMessage(settingsService.getLoginMessage());
Theme[] themes = settingsService.getAvailableThemes();
command.setThemes(themes);
String currentThemeId = settingsService.getThemeId();
for (int i = 0; i < themes.length; i++) {
if (currentThemeId.equals(themes[i].getId())) {
command.setThemeIndex(String.valueOf(i));
break;
}
}
Locale currentLocale = settingsService.getLocale();
Locale[] locales = settingsService.getAvailableLocales();
String[] localeStrings = new String[locales.length];
for (int i = 0; i < locales.length; i++) {
localeStrings[i] = locales[i].getDisplayName(locales[i]);
if (currentLocale.equals(locales[i])) {
command.setLocaleIndex(String.valueOf(i));
}
}
command.setLocales(localeStrings);
return command;
}
protected void doSubmitAction(Object comm) throws Exception {
GeneralSettingsCommand command = (GeneralSettingsCommand) comm;
int themeIndex = Integer.parseInt(command.getThemeIndex());
Theme theme = settingsService.getAvailableThemes()[themeIndex];
int localeIndex = Integer.parseInt(command.getLocaleIndex());
Locale locale = settingsService.getAvailableLocales()[localeIndex];
command.setToast(true);
command.setReloadNeeded(!settingsService.getIndexString().equals(command.getIndex()) ||
!settingsService.getIgnoredArticles().equals(command.getIgnoredArticles()) ||
!settingsService.getShortcuts().equals(command.getShortcuts()) ||
!settingsService.getThemeId().equals(theme.getId()) ||
!settingsService.getLocale().equals(locale));
settingsService.setIndexString(command.getIndex());
settingsService.setIgnoredArticles(command.getIgnoredArticles());
settingsService.setShortcuts(command.getShortcuts());
settingsService.setPlaylistFolder(command.getPlaylistFolder());
settingsService.setMusicFileTypes(command.getMusicFileTypes());
settingsService.setVideoFileTypes(command.getVideoFileTypes());
settingsService.setCoverArtFileTypes(command.getCoverArtFileTypes());
settingsService.setSortAlbumsByYear(command.isSortAlbumsByYear());
settingsService.setGettingStartedEnabled(command.isGettingStartedEnabled());
settingsService.setWelcomeTitle(command.getWelcomeTitle());
settingsService.setWelcomeSubtitle(command.getWelcomeSubtitle());
settingsService.setWelcomeMessage(command.getWelcomeMessage());
settingsService.setLoginMessage(command.getLoginMessage());
settingsService.setThemeId(theme.getId());
settingsService.setLocale(locale);
settingsService.save();
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -0,0 +1,201 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.awt.Dimension;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.Player;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.util.Pair;
import org.libresonic.player.util.StringUtil;
/**
* Controller which produces the HLS (Http Live Streaming) playlist.
*
* @author Sindre Mehus
*/
public class HLSController implements Controller {
private static final int SEGMENT_DURATION = 10;
private static final Pattern BITRATE_PATTERN = Pattern.compile("(\\d+)(@(\\d+)x(\\d+))?");
private PlayerService playerService;
private MediaFileService mediaFileService;
private SecurityService securityService;
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setHeader("Access-Control-Allow-Origin", "*");
int id = ServletRequestUtils.getIntParameter(request, "id");
MediaFile mediaFile = mediaFileService.getMediaFile(id);
Player player = playerService.getPlayer(request, response);
String username = player.getUsername();
if (mediaFile == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Media file not found: " + id);
return null;
}
if (username != null && !securityService.isFolderAccessAllowed(mediaFile, username)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN,
"Access to file " + mediaFile.getId() + " is forbidden for user " + username);
return null;
}
Integer duration = mediaFile.getDurationSeconds();
if (duration == null || duration == 0) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unknown duration for media file: " + id);
return null;
}
response.setContentType("application/vnd.apple.mpegurl");
response.setCharacterEncoding(StringUtil.ENCODING_UTF8);
List<Pair<Integer, Dimension>> bitRates = parseBitRates(request);
PrintWriter writer = response.getWriter();
if (bitRates.size() > 1) {
generateVariantPlaylist(request, id, player, bitRates, writer);
} else {
generateNormalPlaylist(request, id, player, bitRates.size() == 1 ? bitRates.get(0) : null, duration, writer);
}
return null;
}
private List<Pair<Integer, Dimension>> parseBitRates(HttpServletRequest request) throws IllegalArgumentException {
List<Pair<Integer, Dimension>> result = new ArrayList<Pair<Integer, Dimension>>();
String[] bitRates = request.getParameterValues("bitRate");
if (bitRates != null) {
for (String bitRate : bitRates) {
result.add(parseBitRate(bitRate));
}
}
return result;
}
/**
* Parses a string containing the bitrate and an optional width/height, e.g., 1200@640x480
*/
protected Pair<Integer, Dimension> parseBitRate(String bitRate) throws IllegalArgumentException {
Matcher matcher = BITRATE_PATTERN.matcher(bitRate);
if (!matcher.matches()) {
throw new IllegalArgumentException("Invalid bitrate specification: " + bitRate);
}
int kbps = Integer.parseInt(matcher.group(1));
if (matcher.group(3) == null) {
return new Pair<Integer, Dimension>(kbps, null);
} else {
int width = Integer.parseInt(matcher.group(3));
int height = Integer.parseInt(matcher.group(4));
return new Pair<Integer, Dimension>(kbps, new Dimension(width, height));
}
}
private void generateVariantPlaylist(HttpServletRequest request, int id, Player player, List<Pair<Integer, Dimension>> bitRates, PrintWriter writer) {
writer.println("#EXTM3U");
writer.println("#EXT-X-VERSION:1");
// writer.println("#EXT-X-TARGETDURATION:" + SEGMENT_DURATION);
String contextPath = getContextPath(request);
for (Pair<Integer, Dimension> bitRate : bitRates) {
Integer kbps = bitRate.getFirst();
writer.println("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + kbps * 1000L);
writer.print(contextPath + "hls/hls.m3u8?id=" + id + "&player=" + player.getId() + "&bitRate=" + kbps);
Dimension dimension = bitRate.getSecond();
if (dimension != null) {
writer.print("@" + dimension.width + "x" + dimension.height);
}
writer.println();
}
// writer.println("#EXT-X-ENDLIST");
}
private void generateNormalPlaylist(HttpServletRequest request, int id, Player player, Pair<Integer, Dimension> bitRate, int totalDuration, PrintWriter writer) {
writer.println("#EXTM3U");
writer.println("#EXT-X-VERSION:1");
writer.println("#EXT-X-TARGETDURATION:" + SEGMENT_DURATION);
for (int i = 0; i < totalDuration / SEGMENT_DURATION; i++) {
int offset = i * SEGMENT_DURATION;
writer.println("#EXTINF:" + SEGMENT_DURATION + ",");
writer.println(createStreamUrl(request, player, id, offset, SEGMENT_DURATION, bitRate));
}
int remainder = totalDuration % SEGMENT_DURATION;
if (remainder > 0) {
writer.println("#EXTINF:" + remainder + ",");
int offset = totalDuration - remainder;
writer.println(createStreamUrl(request, player, id, offset, remainder, bitRate));
}
writer.println("#EXT-X-ENDLIST");
}
private String createStreamUrl(HttpServletRequest request, Player player, int id, int offset, int duration, Pair<Integer, Dimension> bitRate) {
StringBuilder builder = new StringBuilder();
builder.append(getContextPath(request)).append("stream/stream.ts?id=").append(id).append("&hls=true&timeOffset=").append(offset)
.append("&player=").append(player.getId()).append("&duration=").append(duration);
if (bitRate != null) {
builder.append("&maxBitRate=").append(bitRate.getFirst());
Dimension dimension = bitRate.getSecond();
if (dimension != null) {
builder.append("&size=").append(dimension.width).append("x").append(dimension.height);
}
}
return builder.toString();
}
private String getContextPath(HttpServletRequest request) {
String contextPath = request.getContextPath();
if (StringUtils.isEmpty(contextPath)) {
contextPath = "/";
} else {
contextPath += "/";
}
return contextPath;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,93 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.libresonic.player.Logger;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.VersionService;
/**
* Controller for the help page.
*
* @author Sindre Mehus
*/
public class HelpController extends ParameterizableViewController {
private VersionService versionService;
private SettingsService settingsService;
private SecurityService securityService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
if (versionService.isNewFinalVersionAvailable()) {
map.put("newVersionAvailable", true);
map.put("latestVersion", versionService.getLatestFinalVersion());
} else if (versionService.isNewBetaVersionAvailable()) {
map.put("newVersionAvailable", true);
map.put("latestVersion", versionService.getLatestBetaVersion());
}
long totalMemory = Runtime.getRuntime().totalMemory();
long freeMemory = Runtime.getRuntime().freeMemory();
String serverInfo = request.getSession().getServletContext().getServerInfo() +
", java " + System.getProperty("java.version") +
", " + System.getProperty("os.name");
map.put("licenseInfo", settingsService.getLicenseInfo());
map.put("user", securityService.getCurrentUser(request));
map.put("brand", settingsService.getBrand());
map.put("localVersion", versionService.getLocalVersion());
map.put("buildDate", versionService.getLocalBuildDate());
map.put("buildNumber", versionService.getLocalBuildNumber());
map.put("serverInfo", serverInfo);
map.put("usedMemory", totalMemory - freeMemory);
map.put("totalMemory", totalMemory);
map.put("logEntries", Logger.getLatestLogEntries());
map.put("logFile", Logger.getLogFile());
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public void setVersionService(VersionService versionService) {
this.versionService = versionService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,372 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.view.RedirectView;
import org.libresonic.player.domain.AlbumListType;
import org.libresonic.player.domain.CoverArtScheme;
import org.libresonic.player.domain.Genre;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.User;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.MediaScannerService;
import org.libresonic.player.service.RatingService;
import org.libresonic.player.service.SearchService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import static org.springframework.web.bind.ServletRequestUtils.getIntParameter;
import static org.springframework.web.bind.ServletRequestUtils.getStringParameter;
/**
* Controller for the home page.
*
* @author Sindre Mehus
*/
public class HomeController extends ParameterizableViewController {
private static final int LIST_SIZE = 40;
private SettingsService settingsService;
private MediaScannerService mediaScannerService;
private RatingService ratingService;
private SecurityService securityService;
private MediaFileService mediaFileService;
private SearchService searchService;
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
User user = securityService.getCurrentUser(request);
if (user.isAdminRole() && settingsService.isGettingStartedEnabled()) {
return new ModelAndView(new RedirectView("gettingStarted.view"));
}
int listOffset = getIntParameter(request, "listOffset", 0);
AlbumListType listType = AlbumListType.fromId(getStringParameter(request, "listType"));
if (listType == null) {
UserSettings userSettings = settingsService.getUserSettings(user.getUsername());
listType = userSettings.getDefaultAlbumList();
}
MusicFolder selectedMusicFolder = settingsService.getSelectedMusicFolder(user.getUsername());
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(user.getUsername(),
selectedMusicFolder == null ? null : selectedMusicFolder.getId());
Map<String, Object> map = new HashMap<String, Object>();
List<Album> albums = Collections.emptyList();
switch (listType) {
case HIGHEST:
albums = getHighestRated(listOffset, LIST_SIZE, musicFolders);
break;
case FREQUENT:
albums = getMostFrequent(listOffset, LIST_SIZE, musicFolders);
break;
case RECENT:
albums = getMostRecent(listOffset, LIST_SIZE, musicFolders);
break;
case NEWEST:
albums = getNewest(listOffset, LIST_SIZE, musicFolders);
break;
case STARRED:
albums = getStarred(listOffset, LIST_SIZE, user.getUsername(), musicFolders);
break;
case RANDOM:
albums = getRandom(LIST_SIZE, musicFolders);
break;
case ALPHABETICAL:
albums = getAlphabetical(listOffset, LIST_SIZE, true, musicFolders);
break;
case DECADE:
List<Integer> decades = createDecades();
map.put("decades", decades);
int decade = getIntParameter(request, "decade", decades.get(0));
map.put("decade", decade);
albums = getByYear(listOffset, LIST_SIZE, decade, decade + 9, musicFolders);
break;
case GENRE:
List<Genre> genres = mediaFileService.getGenres(true);
map.put("genres", genres);
if (!genres.isEmpty()) {
String genre = getStringParameter(request, "genre", genres.get(0).getName());
map.put("genre", genre);
albums = getByGenre(listOffset, LIST_SIZE, genre, musicFolders);
}
break;
default:
break;
}
map.put("albums", albums);
map.put("welcomeTitle", settingsService.getWelcomeTitle());
map.put("welcomeSubtitle", settingsService.getWelcomeSubtitle());
map.put("welcomeMessage", settingsService.getWelcomeMessage());
map.put("isIndexBeingCreated", mediaScannerService.isScanning());
map.put("musicFoldersExist", !settingsService.getAllMusicFolders().isEmpty());
map.put("listType", listType.getId());
map.put("listSize", LIST_SIZE);
map.put("coverArtSize", CoverArtScheme.MEDIUM.getSize());
map.put("listOffset", listOffset);
map.put("musicFolder", selectedMusicFolder);
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
private List<Album> getHighestRated(int offset, int count, List<MusicFolder> musicFolders) {
List<Album> result = new ArrayList<Album>();
for (MediaFile mediaFile : ratingService.getHighestRatedAlbums(offset, count, musicFolders)) {
Album album = createAlbum(mediaFile);
album.setRating((int) Math.round(ratingService.getAverageRating(mediaFile) * 10.0D));
result.add(album);
}
return result;
}
private List<Album> getMostFrequent(int offset, int count, List<MusicFolder> musicFolders) {
List<Album> result = new ArrayList<Album>();
for (MediaFile mediaFile : mediaFileService.getMostFrequentlyPlayedAlbums(offset, count, musicFolders)) {
Album album = createAlbum(mediaFile);
album.setPlayCount(mediaFile.getPlayCount());
result.add(album);
}
return result;
}
private List<Album> getMostRecent(int offset, int count, List<MusicFolder> musicFolders) {
List<Album> result = new ArrayList<Album>();
for (MediaFile mediaFile : mediaFileService.getMostRecentlyPlayedAlbums(offset, count, musicFolders)) {
Album album = createAlbum(mediaFile);
album.setLastPlayed(mediaFile.getLastPlayed());
result.add(album);
}
return result;
}
private List<Album> getNewest(int offset, int count, List<MusicFolder> musicFolders) throws IOException {
List<Album> result = new ArrayList<Album>();
for (MediaFile file : mediaFileService.getNewestAlbums(offset, count, musicFolders)) {
Album album = createAlbum(file);
Date created = file.getCreated();
if (created == null) {
created = file.getChanged();
}
album.setCreated(created);
result.add(album);
}
return result;
}
private List<Album> getStarred(int offset, int count, String username, List<MusicFolder> musicFolders) throws IOException {
List<Album> result = new ArrayList<Album>();
for (MediaFile file : mediaFileService.getStarredAlbums(offset, count, username, musicFolders)) {
result.add(createAlbum(file));
}
return result;
}
private List<Album> getRandom(int count, List<MusicFolder> musicFolders) throws IOException {
List<Album> result = new ArrayList<Album>();
for (MediaFile file : searchService.getRandomAlbums(count, musicFolders)) {
result.add(createAlbum(file));
}
return result;
}
private List<Album> getAlphabetical(int offset, int count, boolean byArtist, List<MusicFolder> musicFolders) throws IOException {
List<Album> result = new ArrayList<Album>();
for (MediaFile file : mediaFileService.getAlphabeticalAlbums(offset, count, byArtist, musicFolders)) {
result.add(createAlbum(file));
}
return result;
}
private List<Album> getByYear(int offset, int count, int fromYear, int toYear, List<MusicFolder> musicFolders) {
List<Album> result = new ArrayList<Album>();
for (MediaFile file : mediaFileService.getAlbumsByYear(offset, count, fromYear, toYear, musicFolders)) {
Album album = createAlbum(file);
album.setYear(file.getYear());
result.add(album);
}
return result;
}
private List<Integer> createDecades() {
List<Integer> result = new ArrayList<Integer>();
int decade = Calendar.getInstance().get(Calendar.YEAR) / 10;
for (int i = 0; i < 10; i++) {
result.add((decade - i) * 10);
}
return result;
}
private List<Album> getByGenre(int offset, int count, String genre, List<MusicFolder> musicFolders) {
List<Album> result = new ArrayList<Album>();
for (MediaFile file : mediaFileService.getAlbumsByGenre(offset, count, genre, musicFolders)) {
result.add(createAlbum(file));
}
return result;
}
private Album createAlbum(MediaFile file) {
Album album = new Album();
album.setId(file.getId());
album.setPath(file.getPath());
album.setArtist(file.getArtist());
album.setAlbumTitle(file.getAlbumName());
album.setCoverArtPath(file.getCoverArtPath());
return album;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setMediaScannerService(MediaScannerService mediaScannerService) {
this.mediaScannerService = mediaScannerService;
}
public void setRatingService(RatingService ratingService) {
this.ratingService = ratingService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
public void setSearchService(SearchService searchService) {
this.searchService = searchService;
}
/**
* Contains info for a single album.
*/
public static class Album {
private String path;
private String coverArtPath;
private String artist;
private String albumTitle;
private Date created;
private Date lastPlayed;
private Integer playCount;
private Integer rating;
private int id;
private Integer year;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getCoverArtPath() {
return coverArtPath;
}
public void setCoverArtPath(String coverArtPath) {
this.coverArtPath = coverArtPath;
}
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
public String getAlbumTitle() {
return albumTitle;
}
public void setAlbumTitle(String albumTitle) {
this.albumTitle = albumTitle;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getLastPlayed() {
return lastPlayed;
}
public void setLastPlayed(Date lastPlayed) {
this.lastPlayed = lastPlayed;
}
public Integer getPlayCount() {
return playCount;
}
public void setPlayCount(Integer playCount) {
this.playCount = playCount;
}
public Integer getRating() {
return rating;
}
public void setRating(Integer rating) {
this.rating = rating;
}
public void setYear(Integer year) {
this.year = year;
}
public Integer getYear() {
return year;
}
}
}
@@ -0,0 +1,93 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.libresonic.player.domain.Playlist;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.service.SecurityService;
/**
* @author Sindre Mehus
*/
public class ImportPlaylistController extends ParameterizableViewController {
private static final long MAX_PLAYLIST_SIZE_MB = 5L;
private SecurityService securityService;
private PlaylistService playlistService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
try {
if (ServletFileUpload.isMultipartContent(request)) {
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<?> items = upload.parseRequest(request);
for (Object o : items) {
FileItem item = (FileItem) o;
if ("file".equals(item.getFieldName()) && !StringUtils.isBlank(item.getName())) {
if (item.getSize() > MAX_PLAYLIST_SIZE_MB * 1024L * 1024L) {
throw new Exception("The playlist file is too large. Max file size is " + MAX_PLAYLIST_SIZE_MB + " MB.");
}
String playlistName = FilenameUtils.getBaseName(item.getName());
String fileName = FilenameUtils.getName(item.getName());
String format = StringUtils.lowerCase(FilenameUtils.getExtension(item.getName()));
String username = securityService.getCurrentUsername(request);
Playlist playlist = playlistService.importPlaylist(username, playlistName, fileName, format, item.getInputStream(), null);
map.put("playlist", playlist);
}
}
}
} catch (Exception e) {
map.put("error", e.getMessage());
}
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
}
@@ -0,0 +1,122 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.InternetRadio;
import org.libresonic.player.service.SettingsService;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller for the page used to administrate the set of internet radio/tv stations.
*
* @author Sindre Mehus
*/
public class InternetRadioSettingsController extends ParameterizableViewController {
private SettingsService settingsService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
if (isFormSubmission(request)) {
String error = handleParameters(request);
map.put("error", error);
if (error == null) {
map.put("reload", true);
}
}
ModelAndView result = super.handleRequestInternal(request, response);
map.put("internetRadios", settingsService.getAllInternetRadios(true));
result.addObject("model", map);
return result;
}
/**
* Determine if the given request represents a form submission.
*
* @param request current HTTP request
* @return if the request represents a form submission
*/
private boolean isFormSubmission(HttpServletRequest request) {
return "POST".equals(request.getMethod());
}
private String handleParameters(HttpServletRequest request) {
List<InternetRadio> radios = settingsService.getAllInternetRadios(true);
for (InternetRadio radio : radios) {
Integer id = radio.getId();
String streamUrl = getParameter(request, "streamUrl", id);
String homepageUrl = getParameter(request, "homepageUrl", id);
String name = getParameter(request, "name", id);
boolean enabled = getParameter(request, "enabled", id) != null;
boolean delete = getParameter(request, "delete", id) != null;
if (delete) {
settingsService.deleteInternetRadio(id);
} else {
if (name == null) {
return "internetradiosettings.noname";
}
if (streamUrl == null) {
return "internetradiosettings.nourl";
}
settingsService.updateInternetRadio(new InternetRadio(id, name, streamUrl, homepageUrl, enabled, new Date()));
}
}
String name = StringUtils.trimToNull(request.getParameter("name"));
String streamUrl = StringUtils.trimToNull(request.getParameter("streamUrl"));
String homepageUrl = StringUtils.trimToNull(request.getParameter("homepageUrl"));
boolean enabled = StringUtils.trimToNull(request.getParameter("enabled")) != null;
if (name != null || streamUrl != null || homepageUrl != null) {
if (name == null) {
return "internetradiosettings.noname";
}
if (streamUrl == null) {
return "internetradiosettings.nourl";
}
settingsService.createInternetRadio(new InternetRadio(name, streamUrl, homepageUrl, enabled, new Date()));
}
return null;
}
private String getParameter(HttpServletRequest request, String name, Integer id) {
return StringUtils.trimToNull(request.getParameter(name + "[" + id + "]"));
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -0,0 +1,166 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.Date;
import java.util.GregorianCalendar;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import org.apache.commons.io.IOUtils;
import org.eclipse.persistence.jaxb.JAXBContext;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.input.SAXBuilder;
import org.libresonic.restapi.Error;
import org.libresonic.restapi.ObjectFactory;
import org.libresonic.restapi.Response;
import org.libresonic.restapi.ResponseStatus;
import org.libresonic.player.Logger;
import org.libresonic.player.util.StringUtil;
import static org.springframework.web.bind.ServletRequestUtils.getStringParameter;
/**
* @author Sindre Mehus
* @version $Id$
*/
public class JAXBWriter {
private static final Logger LOG = Logger.getLogger(JAXBWriter.class);
private final javax.xml.bind.JAXBContext jaxbContext;
private final DatatypeFactory datatypeFactory;
private final String restProtocolVersion;
public JAXBWriter() {
try {
jaxbContext = JAXBContext.newInstance(Response.class);
datatypeFactory = DatatypeFactory.newInstance();
restProtocolVersion = getRESTProtocolVersion();
} catch (Exception x) {
throw new RuntimeException(x);
}
}
private Marshaller createXmlMarshaller() throws JAXBException {
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, StringUtil.ENCODING_UTF8);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
return marshaller;
}
private Marshaller createJsonMarshaller() throws JAXBException {
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, StringUtil.ENCODING_UTF8);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, true);
return marshaller;
}
private String getRESTProtocolVersion() throws Exception {
InputStream in = null;
try {
in = StringUtil.class.getResourceAsStream("/libresonic-rest-api.xsd");
Document document = new SAXBuilder().build(in);
Attribute version = document.getRootElement().getAttribute("version");
return version.getValue();
} finally {
IOUtils.closeQuietly(in);
}
}
public String getRestProtocolVersion() {
return restProtocolVersion;
}
public Response createResponse(boolean ok) {
Response response = new ObjectFactory().createResponse();
response.setStatus(ok ? ResponseStatus.OK : ResponseStatus.FAILED);
response.setVersion(restProtocolVersion);
return response;
}
public void writeResponse(HttpServletRequest request, HttpServletResponse httpResponse, Response jaxbResponse) throws Exception {
String format = getStringParameter(request, "f", "xml");
String jsonpCallback = request.getParameter("callback");
boolean json = "json".equals(format);
boolean jsonp = "jsonp".equals(format) && jsonpCallback != null;
Marshaller marshaller;
if (json) {
marshaller = createJsonMarshaller();
httpResponse.setContentType("application/json");
} else if (jsonp) {
marshaller = createJsonMarshaller();
httpResponse.setContentType("text/javascript");
} else {
marshaller = createXmlMarshaller();
httpResponse.setContentType("text/xml");
}
httpResponse.setCharacterEncoding(StringUtil.ENCODING_UTF8);
try {
StringWriter writer = new StringWriter();
if (jsonp) {
writer.append(jsonpCallback).append('(');
}
marshaller.marshal(new ObjectFactory().createLibresonicResponse(jaxbResponse), writer);
if (jsonp) {
writer.append(");");
}
httpResponse.getWriter().append(writer.getBuffer());
} catch (Exception x) {
LOG.error("Failed to marshal JAXB", x);
throw x;
}
}
public void writeErrorResponse(HttpServletRequest request, HttpServletResponse response,
RESTController.ErrorCode code, String message) throws Exception {
Response res = createResponse(false);
Error error = new Error();
res.setError(error);
error.setCode(code.getCode());
error.setMessage(message);
writeResponse(request, response, res);
}
public XMLGregorianCalendar convertDate(Date date) {
if (date == null) {
return null;
}
GregorianCalendar c = new GregorianCalendar();
c.setTime(date);
return datatypeFactory.newXMLGregorianCalendar(c).normalize();
}
}
@@ -0,0 +1,200 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.io.File;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.libresonic.player.domain.InternetRadio;
import org.libresonic.player.domain.MediaLibraryStatistics;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.MusicFolderContent;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.MediaScannerService;
import org.libresonic.player.service.MusicIndexService;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.util.FileUtil;
import org.libresonic.player.util.StringUtil;
/**
* Controller for the left index frame.
*
* @author Sindre Mehus
*/
public class LeftController extends ParameterizableViewController {
// Update this time if you want to force a refresh in clients.
private static final Calendar LAST_COMPATIBILITY_TIME = Calendar.getInstance();
static {
LAST_COMPATIBILITY_TIME.set(2012, Calendar.MARCH, 6, 0, 0, 0);
LAST_COMPATIBILITY_TIME.set(Calendar.MILLISECOND, 0);
}
private MediaScannerService mediaScannerService;
private SettingsService settingsService;
private SecurityService securityService;
private MusicIndexService musicIndexService;
private PlayerService playerService;
/**
* Note: This class intentionally does not implement org.springframework.web.servlet.mvc.LastModified
* as we don't need browser-side caching of left.jsp. This method is only used by RESTController.
*/
public long getLastModified(HttpServletRequest request) {
saveSelectedMusicFolder(request);
if (mediaScannerService.isScanning()) {
return -1L;
}
long lastModified = LAST_COMPATIBILITY_TIME.getTimeInMillis();
String username = securityService.getCurrentUsername(request);
// When was settings last changed?
lastModified = Math.max(lastModified, settingsService.getSettingsChanged());
// When was music folder(s) on disk last changed?
List<MusicFolder> allMusicFolders = settingsService.getMusicFoldersForUser(username);
MusicFolder selectedMusicFolder = settingsService.getSelectedMusicFolder(username);
if (selectedMusicFolder != null) {
File file = selectedMusicFolder.getPath();
lastModified = Math.max(lastModified, FileUtil.lastModified(file));
} else {
for (MusicFolder musicFolder : allMusicFolders) {
File file = musicFolder.getPath();
lastModified = Math.max(lastModified, FileUtil.lastModified(file));
}
}
// When was music folder table last changed?
for (MusicFolder musicFolder : allMusicFolders) {
lastModified = Math.max(lastModified, musicFolder.getChanged().getTime());
}
// When was internet radio table last changed?
for (InternetRadio internetRadio : settingsService.getAllInternetRadios()) {
lastModified = Math.max(lastModified, internetRadio.getChanged().getTime());
}
// When was user settings last changed?
UserSettings userSettings = settingsService.getUserSettings(username);
lastModified = Math.max(lastModified, userSettings.getChanged().getTime());
return lastModified;
}
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
boolean musicFolderChanged = saveSelectedMusicFolder(request);
Map<String, Object> map = new HashMap<String, Object>();
MediaLibraryStatistics statistics = mediaScannerService.getStatistics();
Locale locale = RequestContextUtils.getLocale(request);
boolean refresh = ServletRequestUtils.getBooleanParameter(request, "refresh", false);
if (refresh) {
settingsService.clearMusicFolderCache();
}
String username = securityService.getCurrentUsername(request);
List<MusicFolder> allMusicFolders = settingsService.getMusicFoldersForUser(username);
MusicFolder selectedMusicFolder = settingsService.getSelectedMusicFolder(username);
List<MusicFolder> musicFoldersToUse = selectedMusicFolder == null ? allMusicFolders : Arrays.asList(selectedMusicFolder);
UserSettings userSettings = settingsService.getUserSettings(username);
MusicFolderContent musicFolderContent = musicIndexService.getMusicFolderContent(musicFoldersToUse, refresh);
map.put("player", playerService.getPlayer(request, response));
map.put("scanning", mediaScannerService.isScanning());
map.put("musicFolders", allMusicFolders);
map.put("selectedMusicFolder", selectedMusicFolder);
map.put("radios", settingsService.getAllInternetRadios());
map.put("shortcuts", musicIndexService.getShortcuts(musicFoldersToUse));
map.put("partyMode", userSettings.isPartyModeEnabled());
map.put("organizeByFolderStructure", settingsService.isOrganizeByFolderStructure());
map.put("musicFolderChanged", musicFolderChanged);
if (statistics != null) {
map.put("statistics", statistics);
long bytes = statistics.getTotalLengthInBytes();
long hours = statistics.getTotalDurationInSeconds() / 3600L;
map.put("hours", hours);
map.put("bytes", StringUtil.formatBytes(bytes, locale));
}
map.put("indexedArtists", musicFolderContent.getIndexedArtists());
map.put("singleSongs", musicFolderContent.getSingleSongs());
map.put("indexes", musicFolderContent.getIndexedArtists().keySet());
map.put("user", securityService.getCurrentUser(request));
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
private boolean saveSelectedMusicFolder(HttpServletRequest request) {
if (request.getParameter("musicFolderId") == null) {
return false;
}
int musicFolderId = Integer.parseInt(request.getParameter("musicFolderId"));
// Note: UserSettings.setChanged() is intentionally not called. This would break browser caching
// of the left frame.
UserSettings settings = settingsService.getUserSettings(securityService.getCurrentUsername(request));
settings.setSelectedMusicFolderId(musicFolderId);
settingsService.updateUserSettings(settings);
return true;
}
public void setMediaScannerService(MediaScannerService mediaScannerService) {
this.mediaScannerService = mediaScannerService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setMusicIndexService(MusicIndexService musicIndexService) {
this.musicIndexService = musicIndexService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
}
@@ -0,0 +1,46 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.HashMap;
/**
* Controller for the lyrics popup.
*
* @author Sindre Mehus
*/
public class LyricsController extends ParameterizableViewController {
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put("artist", request.getParameter("artist"));
map.put("song", request.getParameter("song"));
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
}
@@ -0,0 +1,122 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.PlayQueue;
import org.libresonic.player.domain.Player;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.TranscodingService;
import org.libresonic.player.util.StringUtil;
/**
* Controller which produces the M3U playlist.
*
* @author Sindre Mehus
*/
public class M3UController implements Controller {
private PlayerService playerService;
private SettingsService settingsService;
private TranscodingService transcodingService;
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setContentType("audio/x-mpegurl");
response.setCharacterEncoding(StringUtil.ENCODING_UTF8);
Player player = playerService.getPlayer(request, response);
String url = request.getRequestURL().toString();
url = url.replaceFirst("play.m3u.*", "stream?");
// Rewrite URLs in case we're behind a proxy.
if (settingsService.isRewriteUrlEnabled()) {
String referer = request.getHeader("referer");
url = StringUtil.rewriteUrl(url, referer);
}
url = settingsService.rewriteRemoteUrl(url);
if (player.isExternalWithPlaylist()) {
createClientSidePlaylist(response.getWriter(), player, url);
} else {
createServerSidePlaylist(response.getWriter(), player, url);
}
return null;
}
private void createClientSidePlaylist(PrintWriter out, Player player, String url) throws Exception {
out.println("#EXTM3U");
List<MediaFile> result;
synchronized (player.getPlayQueue()) {
result = player.getPlayQueue().getFiles();
}
for (MediaFile mediaFile : result) {
Integer duration = mediaFile.getDurationSeconds();
if (duration == null) {
duration = -1;
}
out.println("#EXTINF:" + duration + "," + mediaFile.getArtist() + " - " + mediaFile.getTitle());
out.println(url + "player=" + player.getId() + "&id=" + mediaFile.getId() + "&suffix=." + transcodingService.getSuffix(player, mediaFile, null));
}
}
private void createServerSidePlaylist(PrintWriter out, Player player, String url) throws IOException {
url += "player=" + player.getId();
// Get suffix of current file, e.g., ".mp3".
String suffix = getSuffix(player);
if (suffix != null) {
url += "&suffix=." + suffix;
}
out.println("#EXTM3U");
out.println("#EXTINF:-1,Libresonic");
out.println(url);
}
private String getSuffix(Player player) {
PlayQueue playQueue = player.getPlayQueue();
return playQueue.isEmpty() ? null : transcodingService.getSuffix(player, playQueue.getFile(0), null);
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setTranscodingService(TranscodingService transcodingService) {
this.transcodingService = transcodingService;
}
}
@@ -0,0 +1,288 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import org.springframework.web.servlet.view.RedirectView;
import org.libresonic.player.domain.CoverArtScheme;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.MediaFileComparator;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.AdService;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.RatingService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
/**
* Controller for the main page.
*
* @author Sindre Mehus
*/
public class MainController extends AbstractController {
private SecurityService securityService;
private PlayerService playerService;
private SettingsService settingsService;
private RatingService ratingService;
private MediaFileService mediaFileService;
private AdService adService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
Player player = playerService.getPlayer(request, response);
List<MediaFile> mediaFiles = getMediaFiles(request);
if (mediaFiles.isEmpty()) {
return new ModelAndView(new RedirectView("notFound.view"));
}
MediaFile dir = mediaFiles.get(0);
if (dir.isFile()) {
dir = mediaFileService.getParentOf(dir);
}
// Redirect if root directory.
if (mediaFileService.isRoot(dir)) {
return new ModelAndView(new RedirectView("home.view?"));
}
String username = securityService.getCurrentUsername(request);
if (!securityService.isFolderAccessAllowed(dir, username)) {
return new ModelAndView(new RedirectView("accessDenied.view"));
}
List<MediaFile> children = mediaFiles.size() == 1 ? mediaFileService.getChildrenOf(dir, true, true, true) : getMultiFolderChildren(mediaFiles);
List<MediaFile> files = new ArrayList<MediaFile>();
List<MediaFile> subDirs = new ArrayList<MediaFile>();
for (MediaFile child : children) {
if (child.isFile()) {
files.add(child);
} else {
subDirs.add(child);
}
}
UserSettings userSettings = settingsService.getUserSettings(username);
mediaFileService.populateStarredDate(dir, username);
mediaFileService.populateStarredDate(children, username);
map.put("dir", dir);
map.put("files", files);
map.put("subDirs", subDirs);
map.put("ancestors", getAncestors(dir));
map.put("coverArtSizeMedium", CoverArtScheme.MEDIUM.getSize());
map.put("coverArtSizeLarge", CoverArtScheme.LARGE.getSize());
map.put("player", player);
map.put("user", securityService.getCurrentUser(request));
map.put("visibility", userSettings.getMainVisibility());
map.put("showAlbumYear", settingsService.isSortAlbumsByYear());
map.put("showArtistInfo", userSettings.isShowArtistInfoEnabled());
map.put("partyMode", userSettings.isPartyModeEnabled());
map.put("brand", settingsService.getBrand());
map.put("showAd", !settingsService.isLicenseValid() && adService.showAd());
map.put("viewAsList", isViewAsList(request, userSettings));
if (dir.isAlbum()) {
map.put("sieblingAlbums", getSieblingAlbums(dir));
map.put("artist", guessArtist(children));
map.put("album", guessAlbum(children));
}
try {
MediaFile parent = mediaFileService.getParentOf(dir);
map.put("parent", parent);
map.put("navigateUpAllowed", !mediaFileService.isRoot(parent));
} catch (SecurityException x) {
// Happens if Podcast directory is outside music folder.
}
Integer userRating = ratingService.getRatingForUser(username, dir);
Double averageRating = ratingService.getAverageRating(dir);
if (userRating == null) {
userRating = 0;
}
if (averageRating == null) {
averageRating = 0.0D;
}
map.put("userRating", 10 * userRating);
map.put("averageRating", Math.round(10.0D * averageRating));
map.put("starred", mediaFileService.getMediaFileStarredDate(dir.getId(), username) != null);
String view;
if (isVideoOnly(children)) {
view = "videoMain";
} else if (dir.isAlbum()) {
view = "albumMain";
} else {
view = "artistMain";
}
ModelAndView result = new ModelAndView(view);
result.addObject("model", map);
return result;
}
private boolean isViewAsList(HttpServletRequest request, UserSettings userSettings) {
boolean viewAsList = ServletRequestUtils.getBooleanParameter(request, "viewAsList", userSettings.isViewAsList());
if (viewAsList != userSettings.isViewAsList()) {
userSettings.setViewAsList(viewAsList);
userSettings.setChanged(new Date());
settingsService.updateUserSettings(userSettings);
}
return viewAsList;
}
private boolean isVideoOnly(List<MediaFile> children) {
boolean videoFound = false;
for (MediaFile child : children) {
if (child.isAudio()) {
return false;
}
if (child.isVideo()) {
videoFound = true;
}
}
return videoFound;
}
private List<MediaFile> getMediaFiles(HttpServletRequest request) {
List<MediaFile> mediaFiles = new ArrayList<MediaFile>();
for (String path : ServletRequestUtils.getStringParameters(request, "path")) {
MediaFile mediaFile = mediaFileService.getMediaFile(path);
if (mediaFile != null) {
mediaFiles.add(mediaFile);
}
}
for (int id : ServletRequestUtils.getIntParameters(request, "id")) {
MediaFile mediaFile = mediaFileService.getMediaFile(id);
if (mediaFile != null) {
mediaFiles.add(mediaFile);
}
}
return mediaFiles;
}
private String guessArtist(List<MediaFile> children) {
for (MediaFile child : children) {
if (child.isFile() && child.getArtist() != null) {
return child.getArtist();
}
}
return null;
}
private String guessAlbum(List<MediaFile> children) {
for (MediaFile child : children) {
if (child.isFile() && child.getArtist() != null) {
return child.getAlbumName();
}
}
return null;
}
private List<MediaFile> getMultiFolderChildren(List<MediaFile> mediaFiles) throws IOException {
SortedSet<MediaFile> result = new TreeSet<MediaFile>(new MediaFileComparator(settingsService.isSortAlbumsByYear()));
for (MediaFile mediaFile : mediaFiles) {
if (mediaFile.isFile()) {
mediaFile = mediaFileService.getParentOf(mediaFile);
}
result.addAll(mediaFileService.getChildrenOf(mediaFile, true, true, true));
}
return new ArrayList<MediaFile>(result);
}
private List<MediaFile> getAncestors(MediaFile dir) throws IOException {
LinkedList<MediaFile> result = new LinkedList<MediaFile>();
try {
MediaFile parent = mediaFileService.getParentOf(dir);
while (parent != null && !mediaFileService.isRoot(parent)) {
result.addFirst(parent);
parent = mediaFileService.getParentOf(parent);
}
} catch (SecurityException x) {
// Happens if Podcast directory is outside music folder.
}
return result;
}
private List<MediaFile> getSieblingAlbums(MediaFile dir) {
List<MediaFile> result = new ArrayList<MediaFile>();
MediaFile parent = mediaFileService.getParentOf(dir);
if (!mediaFileService.isRoot(parent)) {
List<MediaFile> sieblings = mediaFileService.getChildrenOf(parent, false, true, true);
for (MediaFile siebling : sieblings) {
if (siebling.isAlbum() && !siebling.equals(dir)) {
result.add(siebling);
}
}
}
return result;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setRatingService(RatingService ratingService) {
this.ratingService = ratingService;
}
public void setAdService(AdService adService) {
this.adService = adService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,102 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.io.File;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.util.StringUtil;
/**
* Controller for the "more" page.
*
* @author Sindre Mehus
*/
public class MoreController extends ParameterizableViewController {
private SettingsService settingsService;
private SecurityService securityService;
private PlayerService playerService;
private MediaFileService mediaFileService;
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
User user = securityService.getCurrentUser(request);
String uploadDirectory = null;
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(user.getUsername());
if (musicFolders.size() > 0) {
uploadDirectory = new File(musicFolders.get(0).getPath(), "Incoming").getPath();
}
StringBuilder jamstashUrl = new StringBuilder("http://jamstash.com/#/settings?u=" + StringUtil.urlEncode(user.getUsername()) + "&url=");
if (settingsService.isUrlRedirectionEnabled()) {
jamstashUrl.append(StringUtil.urlEncode(settingsService.getUrlRedirectUrl()));
} else {
jamstashUrl.append(StringUtil.urlEncode(request.getRequestURL().toString().replaceAll("/more.view.*", "")));
}
Player player = playerService.getPlayer(request, response);
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
map.put("user", user);
map.put("uploadDirectory", uploadDirectory);
map.put("genres", mediaFileService.getGenres(false));
map.put("currentYear", Calendar.getInstance().get(Calendar.YEAR));
map.put("musicFolders", musicFolders);
map.put("clientSidePlaylist", player.isExternalWithPlaylist() || player.isWeb());
map.put("brand", settingsService.getBrand());
map.put("jamstashUrl", jamstashUrl);
return result;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,265 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpConnectionParams;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
import org.springframework.web.servlet.view.RedirectView;
import org.libresonic.player.Logger;
import org.libresonic.player.domain.Playlist;
import org.libresonic.player.domain.User;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.util.StringUtil;
import net.tanesha.recaptcha.ReCaptcha;
import net.tanesha.recaptcha.ReCaptchaFactory;
import net.tanesha.recaptcha.ReCaptchaResponse;
/**
* Multi-controller used for simple pages.
*
* @author Sindre Mehus
*/
public class MultiController extends MultiActionController {
private static final Logger LOG = Logger.getLogger(MultiController.class);
private SecurityService securityService;
private SettingsService settingsService;
private PlaylistService playlistService;
public ModelAndView login(HttpServletRequest request, HttpServletResponse response) throws Exception {
// Auto-login if "user" and "password" parameters are given.
String username = request.getParameter("user");
String password = request.getParameter("password");
if (username != null && password != null) {
username = StringUtil.urlEncode(username);
password = StringUtil.urlEncode(password);
return new ModelAndView(new RedirectView("j_acegi_security_check?j_username=" + username +
"&j_password=" + password + "&_acegi_security_remember_me=checked"));
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("logout", request.getParameter("logout") != null);
map.put("error", request.getParameter("error") != null);
map.put("brand", settingsService.getBrand());
map.put("loginMessage", settingsService.getLoginMessage());
User admin = securityService.getUserByName(User.USERNAME_ADMIN);
if (User.USERNAME_ADMIN.equals(admin.getPassword())) {
map.put("insecure", true);
}
return new ModelAndView("login", "model", map);
}
public ModelAndView recover(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
String usernameOrEmail = StringUtils.trimToNull(request.getParameter("usernameOrEmail"));
ReCaptcha captcha = ReCaptchaFactory.newSecureReCaptcha("6LcZ3OMSAAAAANkKMdFdaNopWu9iS03V-nLOuoiH",
"6LcZ3OMSAAAAAPaFg89mEzs-Ft0fIu7wxfKtkwmQ", false);
boolean showCaptcha = true;
if (usernameOrEmail != null) {
map.put("usernameOrEmail", usernameOrEmail);
User user = getUserByUsernameOrEmail(usernameOrEmail);
String challenge = request.getParameter("recaptcha_challenge_field");
String uresponse = request.getParameter("recaptcha_response_field");
ReCaptchaResponse captchaResponse = captcha.checkAnswer(request.getRemoteAddr(), challenge, uresponse);
if (!captchaResponse.isValid()) {
map.put("error", "recover.error.invalidcaptcha");
} else if (user == null) {
map.put("error", "recover.error.usernotfound");
} else if (user.getEmail() == null) {
map.put("error", "recover.error.noemail");
} else {
String password = RandomStringUtils.randomAlphanumeric(8);
if (emailPassword(password, user.getUsername(), user.getEmail())) {
map.put("sentTo", user.getEmail());
user.setLdapAuthenticated(false);
user.setPassword(password);
securityService.updateUser(user);
showCaptcha = false;
} else {
map.put("error", "recover.error.sendfailed");
}
}
}
if (showCaptcha) {
map.put("captcha", captcha.createRecaptchaHtml(null, null));
}
return new ModelAndView("recover", "model", map);
}
private boolean emailPassword(String password, String username, String email) {
HttpClient client = new DefaultHttpClient();
try {
HttpConnectionParams.setConnectionTimeout(client.getParams(), 10000);
HttpConnectionParams.setSoTimeout(client.getParams(), 10000);
HttpPost method = new HttpPost("http://libresonic.org/backend/sendMail.view");
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("from", "noreply@libresonic.org"));
params.add(new BasicNameValuePair("to", email));
params.add(new BasicNameValuePair("subject", "Libresonic Password"));
params.add(new BasicNameValuePair("text",
"Hi there!\n\n" +
"You have requested to reset your Libresonic password. Please find your new login details below.\n\n" +
"Username: " + username + "\n" +
"Password: " + password + "\n\n" +
"--\n" +
"The Libresonic Team\n" +
"libresonic.org"));
method.setEntity(new UrlEncodedFormEntity(params, StringUtil.ENCODING_UTF8));
client.execute(method);
return true;
} catch (Exception x) {
LOG.warn("Failed to send email.", x);
return false;
} finally {
client.getConnectionManager().shutdown();
}
}
private User getUserByUsernameOrEmail(String usernameOrEmail) {
if (usernameOrEmail != null) {
User user = securityService.getUserByName(usernameOrEmail);
if (user != null) {
return user;
}
return securityService.getUserByEmail(usernameOrEmail);
}
return null;
}
public ModelAndView accessDenied(HttpServletRequest request, HttpServletResponse response) {
return new ModelAndView("accessDenied");
}
public ModelAndView notFound(HttpServletRequest request, HttpServletResponse response) {
return new ModelAndView("notFound");
}
public ModelAndView gettingStarted(HttpServletRequest request, HttpServletResponse response) {
updatePortAndContextPath(request);
if (request.getParameter("hide") != null) {
settingsService.setGettingStartedEnabled(false);
settingsService.save();
return new ModelAndView(new RedirectView("home.view"));
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("runningAsRoot", "root".equals(System.getProperty("user.name")));
return new ModelAndView("gettingStarted", "model", map);
}
public ModelAndView index(HttpServletRequest request, HttpServletResponse response) {
updatePortAndContextPath(request);
UserSettings userSettings = settingsService.getUserSettings(securityService.getCurrentUsername(request));
Map<String, Object> map = new HashMap<String, Object>();
map.put("showRight", userSettings.isShowNowPlayingEnabled() || userSettings.isShowChatEnabled());
map.put("autoHidePlayQueue", userSettings.isAutoHidePlayQueue());
map.put("showSideBar", userSettings.isShowSideBar());
map.put("brand", settingsService.getBrand());
return new ModelAndView("index", "model", map);
}
public ModelAndView exportPlaylist(HttpServletRequest request, HttpServletResponse response) throws Exception {
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
Playlist playlist = playlistService.getPlaylist(id);
if (!playlistService.isReadAllowed(playlist, securityService.getCurrentUsername(request))) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return null;
}
response.setContentType("application/x-download");
response.setHeader("Content-Disposition", "attachment; filename=\"" + StringUtil.fileSystemSafe(playlist.getName()) + ".m3u8\"");
playlistService.exportPlaylist(id, response.getOutputStream());
return null;
}
private void updatePortAndContextPath(HttpServletRequest request) {
int port = Integer.parseInt(System.getProperty("libresonic.port", String.valueOf(request.getLocalPort())));
int httpsPort = Integer.parseInt(System.getProperty("libresonic.httpsPort", "0"));
String contextPath = request.getContextPath().replace("/", "");
if (settingsService.getPort() != port) {
settingsService.setPort(port);
settingsService.save();
}
if (settingsService.getHttpsPort() != httpsPort) {
settingsService.setHttpsPort(httpsPort);
settingsService.save();
}
if (!ObjectUtils.equals(settingsService.getUrlRedirectContextPath(), contextPath)) {
settingsService.setUrlRedirectContextPath(contextPath);
settingsService.save();
}
}
public ModelAndView test(HttpServletRequest request, HttpServletResponse response) {
return new ModelAndView("test");
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
}
@@ -0,0 +1,135 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.command.MusicFolderSettingsCommand;
import org.libresonic.player.dao.AlbumDao;
import org.libresonic.player.dao.ArtistDao;
import org.libresonic.player.dao.MediaFileDao;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.service.MediaScannerService;
import org.libresonic.player.service.SettingsService;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
/**
* Controller for the page used to administrate the set of music folders.
*
* @author Sindre Mehus
*/
public class MusicFolderSettingsController extends SimpleFormController {
private SettingsService settingsService;
private MediaScannerService mediaScannerService;
private ArtistDao artistDao;
private AlbumDao albumDao;
private MediaFileDao mediaFileDao;
protected Object formBackingObject(HttpServletRequest request) throws Exception {
MusicFolderSettingsCommand command = new MusicFolderSettingsCommand();
if (request.getParameter("scanNow") != null) {
settingsService.clearMusicFolderCache();
mediaScannerService.scanLibrary();
}
if (request.getParameter("expunge") != null) {
expunge();
}
command.setInterval(String.valueOf(settingsService.getIndexCreationInterval()));
command.setHour(String.valueOf(settingsService.getIndexCreationHour()));
command.setFastCache(settingsService.isFastCacheEnabled());
command.setOrganizeByFolderStructure(settingsService.isOrganizeByFolderStructure());
command.setScanning(mediaScannerService.isScanning());
command.setMusicFolders(wrap(settingsService.getAllMusicFolders(true, true)));
command.setNewMusicFolder(new MusicFolderSettingsCommand.MusicFolderInfo());
command.setReload(request.getParameter("reload") != null || request.getParameter("scanNow") != null);
return command;
}
private void expunge() {
artistDao.expunge();
albumDao.expunge();
mediaFileDao.expunge();
}
private List<MusicFolderSettingsCommand.MusicFolderInfo> wrap(List<MusicFolder> musicFolders) {
ArrayList<MusicFolderSettingsCommand.MusicFolderInfo> result = new ArrayList<MusicFolderSettingsCommand.MusicFolderInfo>();
for (MusicFolder musicFolder : musicFolders) {
result.add(new MusicFolderSettingsCommand.MusicFolderInfo(musicFolder));
}
return result;
}
@Override
protected ModelAndView onSubmit(Object comm) throws Exception {
MusicFolderSettingsCommand command = (MusicFolderSettingsCommand) comm;
for (MusicFolderSettingsCommand.MusicFolderInfo musicFolderInfo : command.getMusicFolders()) {
if (musicFolderInfo.isDelete()) {
settingsService.deleteMusicFolder(musicFolderInfo.getId());
} else {
MusicFolder musicFolder = musicFolderInfo.toMusicFolder();
if (musicFolder != null) {
settingsService.updateMusicFolder(musicFolder);
}
}
}
MusicFolder newMusicFolder = command.getNewMusicFolder().toMusicFolder();
if (newMusicFolder != null) {
settingsService.createMusicFolder(newMusicFolder);
}
settingsService.setIndexCreationInterval(Integer.parseInt(command.getInterval()));
settingsService.setIndexCreationHour(Integer.parseInt(command.getHour()));
settingsService.setFastCacheEnabled(command.isFastCache());
settingsService.setOrganizeByFolderStructure(command.isOrganizeByFolderStructure());
settingsService.save();
mediaScannerService.schedule();
return new ModelAndView(new RedirectView(getSuccessView() + ".view?reload"));
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setMediaScannerService(MediaScannerService mediaScannerService) {
this.mediaScannerService = mediaScannerService;
}
public void setArtistDao(ArtistDao artistDao) {
this.artistDao = artistDao;
}
public void setAlbumDao(AlbumDao albumDao) {
this.albumDao = albumDao;
}
public void setMediaFileDao(MediaFileDao mediaFileDao) {
this.mediaFileDao = mediaFileDao;
}
}
@@ -0,0 +1,83 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.Random;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.libresonic.player.command.NetworkSettingsCommand;
import org.libresonic.player.domain.UrlRedirectType;
import org.libresonic.player.service.NetworkService;
import org.libresonic.player.service.SettingsService;
/**
* Controller for the page used to change the network settings.
*
* @author Sindre Mehus
*/
public class NetworkSettingsController extends SimpleFormController {
private SettingsService settingsService;
private NetworkService networkService;
protected Object formBackingObject(HttpServletRequest request) throws Exception {
NetworkSettingsCommand command = new NetworkSettingsCommand();
command.setPortForwardingEnabled(settingsService.isPortForwardingEnabled());
command.setUrlRedirectionEnabled(settingsService.isUrlRedirectionEnabled());
command.setUrlRedirectType(settingsService.getUrlRedirectType().name());
command.setUrlRedirectFrom(settingsService.getUrlRedirectFrom());
command.setUrlRedirectCustomUrl(settingsService.getUrlRedirectCustomUrl());
command.setPort(settingsService.getPort());
command.setLicenseInfo(settingsService.getLicenseInfo());
return command;
}
protected void doSubmitAction(Object cmd) throws Exception {
NetworkSettingsCommand command = (NetworkSettingsCommand) cmd;
command.setToast(true);
settingsService.setPortForwardingEnabled(command.isPortForwardingEnabled());
settingsService.setUrlRedirectionEnabled(command.isUrlRedirectionEnabled());
settingsService.setUrlRedirectType(UrlRedirectType.valueOf(command.getUrlRedirectType()));
settingsService.setUrlRedirectFrom(StringUtils.lowerCase(command.getUrlRedirectFrom()));
settingsService.setUrlRedirectCustomUrl(StringUtils.trimToEmpty(command.getUrlRedirectCustomUrl()));
if (settingsService.getServerId() == null) {
Random rand = new Random(System.currentTimeMillis());
settingsService.setServerId(String.valueOf(Math.abs(rand.nextLong())));
}
settingsService.save();
networkService.initPortForwarding(0);
networkService.initUrlRedirection(true);
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setNetworkService(NetworkService networkService) {
this.networkService = networkService;
}
}
@@ -0,0 +1,78 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import org.springframework.web.servlet.view.RedirectView;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.TransferStatus;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.StatusService;
/**
* Controller for showing what's currently playing.
*
* @author Sindre Mehus
*/
public class NowPlayingController extends AbstractController {
private PlayerService playerService;
private StatusService statusService;
private MediaFileService mediaFileService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Player player = playerService.getPlayer(request, response);
List<TransferStatus> statuses = statusService.getStreamStatusesForPlayer(player);
MediaFile current = statuses.isEmpty() ? null : mediaFileService.getMediaFile(statuses.get(0).getFile());
MediaFile dir = current == null ? null : mediaFileService.getParentOf(current);
String url;
if (dir != null && !mediaFileService.isRoot(dir)) {
url = "main.view?id=" + dir.getId();
} else {
url = "home.view";
}
return new ModelAndView(new RedirectView(url));
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setStatusService(StatusService statusService) {
this.statusService = statusService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,59 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.springframework.web.servlet.mvc.*;
import org.libresonic.player.service.*;
import org.libresonic.player.command.*;
import org.libresonic.player.domain.*;
import javax.servlet.http.*;
/**
* Controller for the page used to change password.
*
* @author Sindre Mehus
*/
public class PasswordSettingsController extends SimpleFormController {
private SecurityService securityService;
protected Object formBackingObject(HttpServletRequest request) throws Exception {
PasswordSettingsCommand command = new PasswordSettingsCommand();
User user = securityService.getCurrentUser(request);
command.setUsername(user.getUsername());
command.setLdapAuthenticated(user.isLdapAuthenticated());
return command;
}
protected void doSubmitAction(Object comm) throws Exception {
PasswordSettingsCommand command = (PasswordSettingsCommand) comm;
User user = securityService.getUserByName(command.getUsername());
user.setPassword(command.getPassword());
securityService.updateUser(user);
command.setPassword(null);
command.setConfirmPassword(null);
command.setToast(true);
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,183 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.Date;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.libresonic.player.command.PersonalSettingsCommand;
import org.libresonic.player.domain.AlbumListType;
import org.libresonic.player.domain.AvatarScheme;
import org.libresonic.player.domain.Theme;
import org.libresonic.player.domain.User;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
/**
* Controller for the page used to administrate per-user settings.
*
* @author Sindre Mehus
*/
public class PersonalSettingsController extends SimpleFormController {
private SettingsService settingsService;
private SecurityService securityService;
@Override
protected Object formBackingObject(HttpServletRequest request) throws Exception {
PersonalSettingsCommand command = new PersonalSettingsCommand();
User user = securityService.getCurrentUser(request);
UserSettings userSettings = settingsService.getUserSettings(user.getUsername());
command.setUser(user);
command.setLocaleIndex("-1");
command.setThemeIndex("-1");
command.setAlbumLists(AlbumListType.values());
command.setAlbumListId(userSettings.getDefaultAlbumList().getId());
command.setAvatars(settingsService.getAllSystemAvatars());
command.setCustomAvatar(settingsService.getCustomAvatar(user.getUsername()));
command.setAvatarId(getAvatarId(userSettings));
command.setPartyModeEnabled(userSettings.isPartyModeEnabled());
command.setQueueFollowingSongs(userSettings.isQueueFollowingSongs());
command.setShowNowPlayingEnabled(userSettings.isShowNowPlayingEnabled());
command.setShowChatEnabled(userSettings.isShowChatEnabled());
command.setShowArtistInfoEnabled(userSettings.isShowArtistInfoEnabled());
command.setNowPlayingAllowed(userSettings.isNowPlayingAllowed());
command.setMainVisibility(userSettings.getMainVisibility());
command.setPlaylistVisibility(userSettings.getPlaylistVisibility());
command.setFinalVersionNotificationEnabled(userSettings.isFinalVersionNotificationEnabled());
command.setBetaVersionNotificationEnabled(userSettings.isBetaVersionNotificationEnabled());
command.setSongNotificationEnabled(userSettings.isSongNotificationEnabled());
command.setAutoHidePlayQueue(userSettings.isAutoHidePlayQueue());
command.setLastFmEnabled(userSettings.isLastFmEnabled());
command.setLastFmUsername(userSettings.getLastFmUsername());
command.setLastFmPassword(userSettings.getLastFmPassword());
Locale currentLocale = userSettings.getLocale();
Locale[] locales = settingsService.getAvailableLocales();
String[] localeStrings = new String[locales.length];
for (int i = 0; i < locales.length; i++) {
localeStrings[i] = locales[i].getDisplayName(locales[i]);
if (locales[i].equals(currentLocale)) {
command.setLocaleIndex(String.valueOf(i));
}
}
command.setLocales(localeStrings);
String currentThemeId = userSettings.getThemeId();
Theme[] themes = settingsService.getAvailableThemes();
command.setThemes(themes);
for (int i = 0; i < themes.length; i++) {
if (themes[i].getId().equals(currentThemeId)) {
command.setThemeIndex(String.valueOf(i));
break;
}
}
return command;
}
@Override
protected void doSubmitAction(Object comm) throws Exception {
PersonalSettingsCommand command = (PersonalSettingsCommand) comm;
int localeIndex = Integer.parseInt(command.getLocaleIndex());
Locale locale = null;
if (localeIndex != -1) {
locale = settingsService.getAvailableLocales()[localeIndex];
}
int themeIndex = Integer.parseInt(command.getThemeIndex());
String themeId = null;
if (themeIndex != -1) {
themeId = settingsService.getAvailableThemes()[themeIndex].getId();
}
String username = command.getUser().getUsername();
UserSettings settings = settingsService.getUserSettings(username);
settings.setLocale(locale);
settings.setThemeId(themeId);
settings.setDefaultAlbumList(AlbumListType.fromId(command.getAlbumListId()));
settings.setPartyModeEnabled(command.isPartyModeEnabled());
settings.setQueueFollowingSongs(command.isQueueFollowingSongs());
settings.setShowNowPlayingEnabled(command.isShowNowPlayingEnabled());
settings.setShowChatEnabled(command.isShowChatEnabled());
settings.setShowArtistInfoEnabled(command.isShowArtistInfoEnabled());
settings.setNowPlayingAllowed(command.isNowPlayingAllowed());
settings.setMainVisibility(command.getMainVisibility());
settings.setPlaylistVisibility(command.getPlaylistVisibility());
settings.setFinalVersionNotificationEnabled(command.isFinalVersionNotificationEnabled());
settings.setBetaVersionNotificationEnabled(command.isBetaVersionNotificationEnabled());
settings.setSongNotificationEnabled(command.isSongNotificationEnabled());
settings.setAutoHidePlayQueue(command.isAutoHidePlayQueue());
settings.setLastFmEnabled(command.isLastFmEnabled());
settings.setLastFmUsername(command.getLastFmUsername());
settings.setSystemAvatarId(getSystemAvatarId(command));
settings.setAvatarScheme(getAvatarScheme(command));
if (StringUtils.isNotBlank(command.getLastFmPassword())) {
settings.setLastFmPassword(command.getLastFmPassword());
}
settings.setChanged(new Date());
settingsService.updateUserSettings(settings);
command.setReloadNeeded(true);
}
private int getAvatarId(UserSettings userSettings) {
AvatarScheme avatarScheme = userSettings.getAvatarScheme();
return avatarScheme == AvatarScheme.SYSTEM ? userSettings.getSystemAvatarId() : avatarScheme.getCode();
}
private AvatarScheme getAvatarScheme(PersonalSettingsCommand command) {
if (command.getAvatarId() == AvatarScheme.NONE.getCode()) {
return AvatarScheme.NONE;
}
if (command.getAvatarId() == AvatarScheme.CUSTOM.getCode()) {
return AvatarScheme.CUSTOM;
}
return AvatarScheme.SYSTEM;
}
private Integer getSystemAvatarId(PersonalSettingsCommand command) {
int avatarId = command.getAvatarId();
if (avatarId == AvatarScheme.NONE.getCode() ||
avatarId == AvatarScheme.CUSTOM.getCode()) {
return null;
}
return avatarId;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,80 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.User;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
/**
* Controller for the playlist frame.
*
* @author Sindre Mehus
*/
public class PlayQueueController extends ParameterizableViewController {
private PlayerService playerService;
private SecurityService securityService;
private SettingsService settingsService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
User user = securityService.getCurrentUser(request);
UserSettings userSettings = settingsService.getUserSettings(user.getUsername());
Player player = playerService.getPlayer(request, response);
Map<String, Object> map = new HashMap<String, Object>();
map.put("user", user);
map.put("player", player);
map.put("players", playerService.getPlayersForUserAndClientId(user.getUsername(), null));
map.put("visibility", userSettings.getPlaylistVisibility());
map.put("partyMode", userSettings.isPartyModeEnabled());
map.put("notify", userSettings.isSongNotificationEnabled());
map.put("autoHide", userSettings.isAutoHidePlayQueue());
map.put("licenseInfo", settingsService.getLicenseInfo());
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -0,0 +1,146 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.libresonic.player.command.PlayerSettingsCommand;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.PlayerTechnology;
import org.libresonic.player.domain.TranscodeScheme;
import org.libresonic.player.domain.Transcoding;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.TranscodingService;
/**
* Controller for the player settings page.
*
* @author Sindre Mehus
*/
public class PlayerSettingsController extends SimpleFormController {
private PlayerService playerService;
private SecurityService securityService;
private TranscodingService transcodingService;
@Override
protected Object formBackingObject(HttpServletRequest request) throws Exception {
handleRequestParameters(request);
List<Player> players = getPlayers(request);
User user = securityService.getCurrentUser(request);
PlayerSettingsCommand command = new PlayerSettingsCommand();
Player player = null;
String playerId = request.getParameter("id");
if (playerId != null) {
player = playerService.getPlayerById(playerId);
} else if (!players.isEmpty()) {
player = players.get(0);
}
if (player != null) {
command.setPlayerId(player.getId());
command.setName(player.getName());
command.setDescription(player.toString());
command.setType(player.getType());
command.setLastSeen(player.getLastSeen());
command.setDynamicIp(player.isDynamicIp());
command.setAutoControlEnabled(player.isAutoControlEnabled());
command.setTranscodeSchemeName(player.getTranscodeScheme().name());
command.setTechnologyName(player.getTechnology().name());
command.setAllTranscodings(transcodingService.getAllTranscodings());
List<Transcoding> activeTranscodings = transcodingService.getTranscodingsForPlayer(player);
int[] activeTranscodingIds = new int[activeTranscodings.size()];
for (int i = 0; i < activeTranscodings.size(); i++) {
activeTranscodingIds[i] = activeTranscodings.get(i).getId();
}
command.setActiveTranscodingIds(activeTranscodingIds);
}
command.setTranscodingSupported(transcodingService.isDownsamplingSupported(null));
command.setTranscodeDirectory(transcodingService.getTranscodeDirectory().getPath());
command.setTranscodeSchemes(TranscodeScheme.values());
command.setTechnologies(PlayerTechnology.values());
command.setPlayers(players.toArray(new Player[players.size()]));
command.setAdmin(user.isAdminRole());
return command;
}
@Override
protected void doSubmitAction(Object comm) throws Exception {
PlayerSettingsCommand command = (PlayerSettingsCommand) comm;
Player player = playerService.getPlayerById(command.getPlayerId());
player.setAutoControlEnabled(command.isAutoControlEnabled());
player.setDynamicIp(command.isDynamicIp());
player.setName(StringUtils.trimToNull(command.getName()));
player.setTranscodeScheme(TranscodeScheme.valueOf(command.getTranscodeSchemeName()));
player.setTechnology(PlayerTechnology.valueOf(command.getTechnologyName()));
playerService.updatePlayer(player);
transcodingService.setTranscodingsForPlayer(player, command.getActiveTranscodingIds());
command.setReloadNeeded(true);
}
private List<Player> getPlayers(HttpServletRequest request) {
User user = securityService.getCurrentUser(request);
String username = user.getUsername();
List<Player> players = playerService.getAllPlayers();
List<Player> authorizedPlayers = new ArrayList<Player>();
for (Player player : players) {
// Only display authorized players.
if (user.isAdminRole() || username.equals(player.getUsername())) {
authorizedPlayers.add(player);
}
}
return authorizedPlayers;
}
private void handleRequestParameters(HttpServletRequest request) {
if (request.getParameter("delete") != null) {
playerService.removePlayerById(request.getParameter("delete"));
} else if (request.getParameter("clone") != null) {
playerService.clonePlayer(request.getParameter("clone"));
}
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setTranscodingService(TranscodingService transcodingService) {
this.transcodingService = transcodingService;
}
}
@@ -0,0 +1,91 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.Playlist;
import org.libresonic.player.domain.User;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for the playlist page.
*
* @author Sindre Mehus
*/
public class PlaylistController extends ParameterizableViewController {
private SecurityService securityService;
private PlaylistService playlistService;
private SettingsService settingsService;
private PlayerService playerService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
User user = securityService.getCurrentUser(request);
String username = user.getUsername();
UserSettings userSettings = settingsService.getUserSettings(username);
Player player = playerService.getPlayer(request, response);
Playlist playlist = playlistService.getPlaylist(id);
if (playlist == null) {
return new ModelAndView(new RedirectView("notFound.view"));
}
map.put("playlist", playlist);
map.put("user", user);
map.put("player", player);
map.put("editAllowed", username.equals(playlist.getUsername()) || securityService.isAdmin(username));
map.put("partyMode", userSettings.isPartyModeEnabled());
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
}
@@ -0,0 +1,72 @@
/*
* This file is part of Libresonic.
*
* Libresonic 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.
*
* Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2014 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.view.RedirectView;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.Playlist;
import org.libresonic.player.domain.User;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
/**
* Controller for the playlists page.
*
* @author Sindre Mehus
*/
public class PlaylistsController extends ParameterizableViewController {
private SecurityService securityService;
private PlaylistService playlistService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
User user = securityService.getCurrentUser(request);
List<Playlist> playlists = playlistService.getReadablePlaylistsForUser(user.getUsername());
map.put("playlists", playlists);
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
}
@@ -0,0 +1,67 @@
/*
* This file is part of Libresonic.
*
* Libresonic 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.
*
* Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2015 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.libresonic.player.service.PodcastService;
import org.libresonic.player.service.SecurityService;
/**
* Controller for the "Podcast channel" page.
*
* @author Sindre Mehus
*/
public class PodcastChannelController extends ParameterizableViewController {
private PodcastService podcastService;
private SecurityService securityService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
int channelId = ServletRequestUtils.getRequiredIntParameter(request, "id");
map.put("user", securityService.getCurrentUser(request));
map.put("channel", podcastService.getChannel(channelId));
map.put("episodes", podcastService.getEpisodes(channelId));
return result;
}
public void setPodcastService(PodcastService podcastService) {
this.podcastService = podcastService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,82 @@
/*
* This file is part of Libresonic.
*
* Libresonic 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.
*
* Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2015 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.libresonic.player.domain.PodcastChannel;
import org.libresonic.player.domain.PodcastEpisode;
import org.libresonic.player.service.PodcastService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
/**
* Controller for the "Podcast channels" page.
*
* @author Sindre Mehus
*/
public class PodcastChannelsController extends ParameterizableViewController {
private PodcastService podcastService;
private SecurityService securityService;
private SettingsService settingsService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
Map<PodcastChannel, List<PodcastEpisode>> channels = new LinkedHashMap<PodcastChannel, List<PodcastEpisode>>();
Map<Integer, PodcastChannel> channelMap = new HashMap<Integer, PodcastChannel>();
for (PodcastChannel channel : podcastService.getAllChannels()) {
channels.put(channel, podcastService.getEpisodes(channel.getId()));
channelMap.put(channel.getId(), channel);
}
map.put("user", securityService.getCurrentUser(request));
map.put("channels", channels);
map.put("channelMap", channelMap);
map.put("newestEpisodes", podcastService.getNewestEpisodes(10));
map.put("licenseInfo", settingsService.getLicenseInfo());
return result;
}
public void setPodcastService(PodcastService podcastService) {
this.podcastService = podcastService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -0,0 +1,147 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.Playlist;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.util.StringUtil;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* Controller for the page used to generate the Podcast XML file.
*
* @author Sindre Mehus
*/
public class PodcastController extends ParameterizableViewController {
private static final DateFormat RSS_DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
private PlaylistService playlistService;
private SettingsService settingsService;
private SecurityService securityService;
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
String url = request.getRequestURL().toString();
String username = securityService.getCurrentUsername(request);
List<Playlist> playlists = playlistService.getReadablePlaylistsForUser(username);
List<Podcast> podcasts = new ArrayList<Podcast>();
for (Playlist playlist : playlists) {
List<MediaFile> songs = playlistService.getFilesInPlaylist(playlist.getId());
if (songs.isEmpty()) {
continue;
}
long length = 0L;
for (MediaFile song : songs) {
length += song.getFileSize();
}
String publishDate = RSS_DATE_FORMAT.format(playlist.getCreated());
// Resolve content type.
String suffix = songs.get(0).getFormat();
String type = StringUtil.getMimeType(suffix);
// Rewrite URLs in case we're behind a proxy.
if (settingsService.isRewriteUrlEnabled()) {
String referer = request.getHeader("referer");
url = StringUtil.rewriteUrl(url, referer);
}
String enclosureUrl = url.replaceFirst("/podcast.*", "/stream?playlist=" + playlist.getId());
enclosureUrl = settingsService.rewriteRemoteUrl(enclosureUrl);
podcasts.add(new Podcast(playlist.getName(), publishDate, enclosureUrl, length, type));
}
Map<String, Object> map = new HashMap<String, Object>();
ModelAndView result = super.handleRequestInternal(request, response);
map.put("url", url);
map.put("podcasts", podcasts);
result.addObject("model", map);
return result;
}
public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
/**
* Contains information about a single Podcast.
*/
public static class Podcast {
private String name;
private String publishDate;
private String enclosureUrl;
private long length;
private String type;
public Podcast(String name, String publishDate, String enclosureUrl, long length, String type) {
this.name = name;
this.publishDate = publishDate;
this.enclosureUrl = enclosureUrl;
this.length = length;
this.type = type;
}
public String getName() {
return name;
}
public String getPublishDate() {
return publishDate;
}
public String getEnclosureUrl() {
return enclosureUrl;
}
public long getLength() {
return length;
}
public String getType() {
return type;
}
}
}
@@ -0,0 +1,98 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.PodcastEpisode;
import org.libresonic.player.domain.PodcastStatus;
import org.libresonic.player.service.PodcastService;
import org.libresonic.player.util.StringUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Controller for the "Podcast receiver" page.
*
* @author Sindre Mehus
*/
public class PodcastReceiverAdminController extends AbstractController {
private PodcastService podcastService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Integer channelId = ServletRequestUtils.getIntParameter(request, "channelId");
if (request.getParameter("add") != null) {
String url = StringUtils.trim(request.getParameter("add"));
podcastService.createChannel(url);
return new ModelAndView(new RedirectView("podcastChannels.view"));
}
if (request.getParameter("downloadEpisode") != null) {
download(StringUtil.parseInts(request.getParameter("downloadEpisode")));
return new ModelAndView(new RedirectView("podcastChannel.view?id=" + channelId));
}
if (request.getParameter("deleteChannel") != null) {
podcastService.deleteChannel(channelId);
return new ModelAndView(new RedirectView("podcastChannels.view"));
}
if (request.getParameter("deleteEpisode") != null) {
for (int episodeId : StringUtil.parseInts(request.getParameter("deleteEpisode"))) {
podcastService.deleteEpisode(episodeId, true);
}
return new ModelAndView(new RedirectView("podcastChannel.view?id=" + channelId));
}
if (request.getParameter("refresh") != null) {
if (channelId != null) {
podcastService.refreshChannel(channelId, true);
return new ModelAndView(new RedirectView("podcastChannel.view?id=" + channelId));
} else {
podcastService.refreshAllChannels(true);
return new ModelAndView(new RedirectView("podcastChannels.view"));
}
}
return new ModelAndView(new RedirectView("podcastChannels.view"));
}
private void download(int[] episodeIds) {
for (Integer episodeId : episodeIds) {
PodcastEpisode episode = podcastService.getEpisode(episodeId, false);
if (episode != null && episode.getUrl() != null &&
(episode.getStatus() == PodcastStatus.NEW ||
episode.getStatus() == PodcastStatus.ERROR ||
episode.getStatus() == PodcastStatus.SKIPPED)) {
podcastService.downloadEpisode(episode);
}
}
}
public void setPodcastService(PodcastService podcastService) {
this.podcastService = podcastService;
}
}
@@ -0,0 +1,68 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.PodcastService;
import org.libresonic.player.command.PodcastSettingsCommand;
import javax.servlet.http.HttpServletRequest;
/**
* Controller for the page used to administrate the Podcast receiver.
*
* @author Sindre Mehus
*/
public class PodcastSettingsController extends SimpleFormController {
private SettingsService settingsService;
private PodcastService podcastService;
protected Object formBackingObject(HttpServletRequest request) throws Exception {
PodcastSettingsCommand command = new PodcastSettingsCommand();
command.setInterval(String.valueOf(settingsService.getPodcastUpdateInterval()));
command.setEpisodeRetentionCount(String.valueOf(settingsService.getPodcastEpisodeRetentionCount()));
command.setEpisodeDownloadCount(String.valueOf(settingsService.getPodcastEpisodeDownloadCount()));
command.setFolder(settingsService.getPodcastFolder());
return command;
}
protected void doSubmitAction(Object comm) throws Exception {
PodcastSettingsCommand command = (PodcastSettingsCommand) comm;
command.setToast(true);
settingsService.setPodcastUpdateInterval(Integer.parseInt(command.getInterval()));
settingsService.setPodcastEpisodeRetentionCount(Integer.parseInt(command.getEpisodeRetentionCount()));
settingsService.setPodcastEpisodeDownloadCount(Integer.parseInt(command.getEpisodeDownloadCount()));
settingsService.setPodcastFolder(command.getFolder());
settingsService.save();
podcastService.schedule();
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setPodcastService(PodcastService podcastService) {
this.podcastService = podcastService;
}
}
@@ -0,0 +1,79 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.libresonic.player.command.PremiumSettingsCommand;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
/**
* Controller for the Subsonic Premium page.
*
* @author Sindre Mehus
*/
public class PremiumSettingsController extends SimpleFormController {
private SettingsService settingsService;
private SecurityService securityService;
protected Object formBackingObject(HttpServletRequest request) throws Exception {
PremiumSettingsCommand command = new PremiumSettingsCommand();
command.setPath(request.getParameter("path"));
command.setForceChange(request.getParameter("change") != null);
command.setLicenseInfo(settingsService.getLicenseInfo());
command.setBrand(settingsService.getBrand());
command.setUser(securityService.getCurrentUser(request));
return command;
}
protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object com, BindException errors)
throws Exception {
PremiumSettingsCommand command = (PremiumSettingsCommand) com;
Date now = new Date();
settingsService.setLicenseCode(command.getLicenseCode());
settingsService.setLicenseEmail(command.getLicenseInfo().getLicenseEmail());
settingsService.setLicenseDate(now);
settingsService.save();
settingsService.scheduleLicenseValidation();
// Reflect changes in view. The validator will validate the license asynchronously.
command.setLicenseInfo(settingsService.getLicenseInfo());
command.setToast(true);
return new ModelAndView(getSuccessView(), errors.getModel());
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
@@ -0,0 +1,68 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.io.InputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
/**
* A proxy for external HTTP requests.
*
* @author Sindre Mehus
*/
public class ProxyController implements Controller {
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
String url = ServletRequestUtils.getRequiredStringParameter(request, "url");
HttpClient client = new DefaultHttpClient();
HttpConnectionParams.setConnectionTimeout(client.getParams(), 15000);
HttpConnectionParams.setSoTimeout(client.getParams(), 15000);
HttpGet method = new HttpGet(url);
InputStream in = null;
try {
HttpResponse resp = client.execute(method);
int statusCode = resp.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
response.sendError(statusCode);
} else {
in = resp.getEntity().getContent();
IOUtils.copy(in, response.getOutputStream());
}
} finally {
IOUtils.closeQuietly(in);
client.getConnectionManager().shutdown();
}
return null;
}
}
@@ -0,0 +1,123 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.PlayQueue;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.RandomSearchCriteria;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SearchService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
/**
* Controller for the creating a random play queue.
*
* @author Sindre Mehus
*/
public class RandomPlayQueueController extends ParameterizableViewController {
private PlayerService playerService;
private List<ReloadFrame> reloadFrames;
private SearchService searchService;
private SecurityService securityService;
private SettingsService settingsService;
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
int size = ServletRequestUtils.getRequiredIntParameter(request, "size");
String genre = request.getParameter("genre");
if (StringUtils.equalsIgnoreCase("any", genre)) {
genre = null;
}
Integer fromYear = null;
Integer toYear = null;
String year = request.getParameter("year");
if (!StringUtils.equalsIgnoreCase("any", year)) {
String[] tmp = StringUtils.split(year);
fromYear = Integer.parseInt(tmp[0]);
toYear = Integer.parseInt(tmp[1]);
}
List<MusicFolder> musicFolders = getMusicFolders(request);
Player player = playerService.getPlayer(request, response);
PlayQueue playQueue = player.getPlayQueue();
RandomSearchCriteria criteria = new RandomSearchCriteria(size, genre, fromYear, toYear, musicFolders);
playQueue.addFiles(false, searchService.getRandomSongs(criteria));
if (request.getParameter("autoRandom") != null) {
playQueue.setRandomSearchCriteria(criteria);
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("reloadFrames", reloadFrames);
ModelAndView result = super.handleRequestInternal(request, response);
result.addObject("model", map);
return result;
}
private List<MusicFolder> getMusicFolders(HttpServletRequest request) throws ServletRequestBindingException {
String username = securityService.getCurrentUsername(request);
Integer selectedMusicFolderId = ServletRequestUtils.getRequiredIntParameter(request, "musicFolderId");
if (selectedMusicFolderId == -1) {
selectedMusicFolderId = null;
}
return settingsService.getMusicFoldersForUser(username, selectedMusicFolderId);
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setReloadFrames(List<ReloadFrame> reloadFrames) {
this.reloadFrames = reloadFrames;
}
public void setSearchService(SearchService searchService) {
this.searchService = searchService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
}
@@ -0,0 +1,52 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
/**
* Used in libresonic-servlet.xml to specify frame reloading.
*
* @author Sindre Mehus
*/
public class ReloadFrame {
private String frame;
private String view;
public ReloadFrame() {}
public ReloadFrame(String frame, String view) {
this.frame = frame;
this.view = view;
}
public String getFrame() {
return frame;
}
public void setFrame(String frame) {
this.frame = frame;
}
public String getView() {
return view;
}
public void setView(String view) {
this.view = view;
}
}
@@ -0,0 +1,82 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.VersionService;
/**
* Controller for the right frame.
*
* @author Sindre Mehus
*/
public class RightController extends ParameterizableViewController {
private SettingsService settingsService;
private SecurityService securityService;
private VersionService versionService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
ModelAndView result = super.handleRequestInternal(request, response);
UserSettings userSettings = settingsService.getUserSettings(securityService.getCurrentUsername(request));
if (userSettings.isFinalVersionNotificationEnabled() && versionService.isNewFinalVersionAvailable()) {
map.put("newVersionAvailable", true);
map.put("latestVersion", versionService.getLatestFinalVersion());
} else if (userSettings.isBetaVersionNotificationEnabled() && versionService.isNewBetaVersionAvailable()) {
map.put("newVersionAvailable", true);
map.put("latestVersion", versionService.getLatestBetaVersion());
}
map.put("brand", settingsService.getBrand());
map.put("showNowPlaying", userSettings.isShowNowPlayingEnabled());
map.put("showChat", userSettings.isShowChatEnabled());
map.put("user", securityService.getCurrentUser(request));
map.put("licenseInfo", settingsService.getLicenseInfo());
result.addObject("model", map);
return result;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setVersionService(VersionService versionService) {
this.versionService = versionService;
}
}
@@ -0,0 +1,110 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.libresonic.player.command.SearchCommand;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.SearchCriteria;
import org.libresonic.player.domain.SearchResult;
import org.libresonic.player.domain.User;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SearchService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
/**
* Controller for the search page.
*
* @author Sindre Mehus
*/
public class SearchController extends SimpleFormController {
private static final int MATCH_COUNT = 25;
private SecurityService securityService;
private SettingsService settingsService;
private PlayerService playerService;
private SearchService searchService;
@Override
protected Object formBackingObject(HttpServletRequest request) throws Exception {
return new SearchCommand();
}
@Override
protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object com, BindException errors)
throws Exception {
SearchCommand command = (SearchCommand) com;
User user = securityService.getCurrentUser(request);
UserSettings userSettings = settingsService.getUserSettings(user.getUsername());
command.setUser(user);
command.setPartyModeEnabled(userSettings.isPartyModeEnabled());
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(user.getUsername());
String query = StringUtils.trimToNull(command.getQuery());
if (query != null) {
SearchCriteria criteria = new SearchCriteria();
criteria.setCount(MATCH_COUNT);
criteria.setQuery(query);
SearchResult artists = searchService.search(criteria, musicFolders, SearchService.IndexType.ARTIST);
command.setArtists(artists.getMediaFiles());
SearchResult albums = searchService.search(criteria, musicFolders, SearchService.IndexType.ALBUM);
command.setAlbums(albums.getMediaFiles());
SearchResult songs = searchService.search(criteria, musicFolders, SearchService.IndexType.SONG);
command.setSongs(songs.getMediaFiles());
command.setPlayer(playerService.getPlayer(request, response));
}
return new ModelAndView(getSuccessView(), errors.getModel());
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setPlayerService(PlayerService playerService) {
this.playerService = playerService;
}
public void setSearchService(SearchService searchService) {
this.searchService = searchService;
}
}
@@ -0,0 +1,60 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import org.springframework.web.servlet.view.RedirectView;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.util.StringUtil;
/**
* Controller for updating music file metadata.
*
* @author Sindre Mehus
*/
public class SetMusicFileInfoController extends AbstractController {
private MediaFileService mediaFileService;
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
String action = request.getParameter("action");
MediaFile mediaFile = mediaFileService.getMediaFile(id);
if ("comment".equals(action)) {
mediaFile.setComment(StringUtil.toHtml(request.getParameter("comment")));
mediaFileService.updateMediaFile(mediaFile);
}
String url = "main.view?id=" + id;
return new ModelAndView(new RedirectView(url));
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,70 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import org.springframework.web.servlet.view.RedirectView;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.RatingService;
import org.libresonic.player.service.SecurityService;
/**
* Controller for updating music file ratings.
*
* @author Sindre Mehus
*/
public class SetRatingController extends AbstractController {
private RatingService ratingService;
private SecurityService securityService;
private MediaFileService mediaFileService;
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
Integer rating = ServletRequestUtils.getIntParameter(request, "rating");
if (rating == 0) {
rating = null;
}
MediaFile mediaFile = mediaFileService.getMediaFile(id);
String username = securityService.getCurrentUsername(request);
ratingService.setRatingForUser(username, mediaFile, rating);
return new ModelAndView(new RedirectView("main.view?id=" + id));
}
public void setRatingService(RatingService ratingService) {
this.ratingService = ratingService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
public void setMediaFileService(MediaFileService mediaFileService) {
this.mediaFileService = mediaFileService;
}
}
@@ -0,0 +1,52 @@
/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.*;
import org.springframework.web.servlet.*;
import org.springframework.web.servlet.view.*;
import org.springframework.web.servlet.mvc.*;
import javax.servlet.http.*;
/**
* Controller for the main settings page.
*
* @author Sindre Mehus
*/
public class SettingsController extends AbstractController {
private SecurityService securityService;
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
User user = securityService.getCurrentUser(request);
// Redirect to music folder settings if admin.
String view = user.isAdminRole() ? "musicFolderSettings.view" : "personalSettings.view";
return new ModelAndView(new RedirectView(view));
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}

Some files were not shown because too many files have changed in this diff Show More