File System Renames (No content changes)

Signed-off-by: Andrew DeMaria <lostonamountain@gmail.com>
This commit is contained in:
Andrew DeMaria
2017-07-13 21:34:19 -06:00
parent d8bd94b7ad
commit 260e04c8ea
1378 changed files with 0 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
<html>
<head>
<meta http-equiv="refresh" content="0;URL=http://libresonic.org/pages/getting-started.jsp">
</head>
<body>
</body>
</html>
+674
View File
@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+102
View File
@@ -0,0 +1,102 @@
Dreamplug
===========
The Access Point uap0 is set to 192.168.1.1. If your router already uses this set of IP address,
you will run into a conflict and the internet connection will go.
Two things you can do.
change the default ip address in /root/init_setup.sh
and you can take down the AP if you are purely not interested in it.
ifconfig uap0 down
and comment out the call to the init_setup.sh in /etc/rc.local
http://www.newit.co.uk/forum/index.php?topic=1078.15
http://plugcomputer.org/plugwiki/index.php/New_Plugger_How_To
sdf
Login : root
Password: nosoup4u
type wlan.sh command to switch from WLAN AP to client mode.
-----
root@ubuntu:~# apt-get install openjdk-6-jre
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following extra packages will be installed:
libgif4 libice6 libpulse0 libsm6 libxi6 libxrender1 libxtst6
Suggested packages:
pulseaudio icedtea6-plugin
The following NEW packages will be installed:
libgif4 libice6 libpulse0 libsm6 libxi6 libxrender1 libxtst6 openjdk-6-jre
0 upgraded, 8 newly installed, 0 to remove and 35 not upgraded.
Need to get 615kB of archives.
After this operation, 1978kB of additional disk space will be used.
Do you want to continue [Y/n]?
Get:1 http://ports.ubuntu.com jaunty/main libgif4 4.1.6-6 [39.2kB]
Get:2 http://ports.ubuntu.com jaunty/main libice6 2:1.0.4-1 [44.3kB]
Get:3 http://ports.ubuntu.com jaunty/main libsm6 2:1.1.0-1 [21.6kB]
Get:4 http://ports.ubuntu.com jaunty-updates/main libpulse0 1:0.9.14-0ubuntu20.3 [178kB]
Get:5 http://ports.ubuntu.com jaunty-updates/main libxi6 2:1.2.0-1ubuntu1.1 [26.5kB]
Get:6 http://ports.ubuntu.com jaunty/main libxrender1 1:0.9.4-2 [28.2kB]
Get:7 http://ports.ubuntu.com jaunty/main libxtst6 2:1.0.3-1ubuntu2 [12.8kB]
Get:8 http://ports.ubuntu.com jaunty-updates/main openjdk-6-jre 6b18-1.8.2-4ubuntu1~9.04.1 [264kB]
Fetched 615kB in 0s (726kB/s)
Selecting previously deselected package libgif4.
(Reading database ... 21520 files and directories currently installed.)
Unpacking libgif4 (from .../libgif4_4.1.6-6_armel.deb) ...
Selecting previously deselected package libice6.
Unpacking libice6 (from .../libice6_2%3a1.0.4-1_armel.deb) ...
Selecting previously deselected package libsm6.
Unpacking libsm6 (from .../libsm6_2%3a1.1.0-1_armel.deb) ...
Selecting previously deselected package libpulse0.
Unpacking libpulse0 (from .../libpulse0_1%3a0.9.14-0ubuntu20.3_armel.deb) ...
Selecting previously deselected package libxi6.
Unpacking libxi6 (from .../libxi6_2%3a1.2.0-1ubuntu1.1_armel.deb) ...
Selecting previously deselected package libxrender1.
Unpacking libxrender1 (from .../libxrender1_1%3a0.9.4-2_armel.deb) ...
Selecting previously deselected package libxtst6.
Unpacking libxtst6 (from .../libxtst6_2%3a1.0.3-1ubuntu2_armel.deb) ...
Selecting previously deselected package openjdk-6-jre.
Unpacking openjdk-6-jre (from .../openjdk-6-jre_6b18-1.8.2-4ubuntu1~9.04.1_armel.deb) ...
Setting up libgif4 (4.1.6-6) ...
Setting up libice6 (2:1.0.4-1) ...
Setting up libsm6 (2:1.1.0-1) ...
Setting up libpulse0 (1:0.9.14-0ubuntu20.3) ...
Setting up libxi6 (2:1.2.0-1ubuntu1.1) ...
Setting up libxrender1 (1:0.9.4-2) ...
Setting up libxtst6 (2:1.0.3-1ubuntu2) ...
Setting up openjdk-6-jre (6b18-1.8.2-4ubuntu1~9.04.1) ...
Processing triggers for libc6 ...
ldconfig deferred processing now taking place
root@ubuntu:~#
root@ubuntu:~# java -version
java version "1.6.0_18"
OpenJDK Runtime Environment (IcedTea6 1.8.2) (6b18-1.8.2-4ubuntu1~9.04.1)
OpenJDK Zero VM (build 14.0-b16, mixed mode)
root@ubuntu:/tmp# dpkg -i libresonic-4.4.deb
Selecting previously deselected package libresonic.
(Reading database ... 21622 files and directories currently installed.)
Unpacking libresonic (from libresonic-4.4.deb) ...
Setting up libresonic (4.4) ...
Adding system startup for /etc/init.d/libresonic ...
/etc/rc0.d/K99libresonic -> ../init.d/libresonic
/etc/rc1.d/K99libresonic -> ../init.d/libresonic
/etc/rc6.d/K99libresonic -> ../init.d/libresonic
/etc/rc2.d/S99libresonic -> ../init.d/libresonic
/etc/rc3.d/S99libresonic -> ../init.d/libresonic
/etc/rc4.d/S99libresonic -> ../init.d/libresonic
/etc/rc5.d/S99libresonic -> ../init.d/libresonic
Started Libresonic [PID 2596, /var/libresonic/libresonic_sh.log]
http://192.168.0.100:4040
+560
View File
@@ -0,0 +1,560 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>libresonic-main</artifactId>
<packaging>war</packaging>
<name>Libresonic Main</name>
<parent>
<groupId>org.libresonic.player</groupId>
<artifactId>libresonic</artifactId>
<version>6.3-SNAPSHOT</version>
</parent>
<properties>
<metrics.version>3.1.0</metrics.version>
<chameleon.version>1.2.0-RELEASE</chameleon.version>
</properties>
<dependencies>
<dependency>
<groupId>org.libresonic.player</groupId>
<artifactId>subsonic-rest-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.libresonic.player</groupId>
<artifactId>libresonic-sonos-api</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Metrics
Metrics is a cool framework used here
to compute musures and statistics during automated testing
-->
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>${metrics.version}</version>
</dependency>
<!-- END Metrics -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.4.1</version>
<scope>test</scope>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
</dependency>
<!-- END Spring -->
<!-- taglibs -->
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-impl</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>string</artifactId>
<version>1.1.0</version>
<scope>runtime</scope>
</dependency>
<!-- END taglibs -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.5</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>1.8.0.7</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>radeox</groupId>
<artifactId>radeox</artifactId>
<version>1.0-b2</version>
</dependency>
<dependency>
<groupId>org.directwebremoting</groupId>
<artifactId>dwr</artifactId>
<version>3.0.rc1</version>
</dependency>
<!--Needed by dwr-->
<dependency>
<groupId>com.yahoo.platform.yui</groupId>
<artifactId>yuicompressor</artifactId>
<version>2.3.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>ant-zip</groupId>
<artifactId>ant-zip</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>net.jthink</groupId>
<artifactId>jaudiotagger</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.0.11</version>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
<exclusion>
<groupId>gnujaxp</groupId>
<artifactId>gnujaxp</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>jdom</groupId>
<artifactId>jdom</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.moxy</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>javax.mail-api</artifactId>
<version>1.5.5</version>
</dependency>
<dependency>
<groupId>com.hoodcomputing</groupId>
<artifactId>natpmp</artifactId>
<version>0.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.5.7</version>
<scope>test</scope>
</dependency>
<!-- UPNP / DLNA -->
<dependency>
<groupId>org.fourthline.cling</groupId>
<artifactId>cling-core</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.fourthline.cling</groupId>
<artifactId>cling-support</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.seamless</groupId>
<artifactId>seamless-util</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>net.tanesha.recaptcha4j</groupId>
<artifactId>recaptcha4j</artifactId>
<version>0.0.8</version>
</dependency>
<dependency>
<groupId>de.u-mass</groupId>
<artifactId>lastfm-java</artifactId>
<version>0.1.2</version>
</dependency>
<!-- SONOS API / WSDL SUPPORT -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>${cxf.version}</version>
<exclusions>
<exclusion>
<groupId> org.springframework</groupId>
<artifactId>spring-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-core</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-bindings-soap</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-databinding-jaxb</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-configuration2</artifactId>
<version>2.1.1</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Fixes liquibase not using slf4j -->
<dependency>
<groupId>com.mattbertolini</groupId>
<artifactId>liquibase-slf4j</artifactId>
<version>1.2.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
<!-- commons-configuration2 requires during runtime -->
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<!-- Cannot use 3.5.3/2 due to https://liquibase.jira.com/browse/CORE-2944 -->
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
<version>3.3.9</version>
</dependency>
<dependency>
<groupId>com.github.muff1nman.chameleon</groupId>
<artifactId>core</artifactId>
<version>${chameleon.version}</version>
</dependency>
<dependency>
<groupId>com.github.muff1nman.chameleon</groupId>
<artifactId>playlist-xspf</artifactId>
<version>${chameleon.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.muff1nman.chameleon</groupId>
<artifactId>playlist-all</artifactId>
<version>${chameleon.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.24</version>
</dependency>
<!-- Embedded tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!-- Embedded Jetty -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>apache-jsp</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.7</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.7</version>
</dependency>
</dependencies>
<build>
<finalName>libresonic</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.5.2.RELEASE</version>
<configuration>
<mainClass>org.libresonic.player.boot.Application</mainClass>
<layout>WAR</layout>
<excludeGroupIds>${boot.group.excludes}</excludeGroupIds>
<excludeArtifactIds>${boot.artifact.excludes}</excludeArtifactIds>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
<configuration>
<reuseForks>false</reuseForks>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>create</goal>
</goals>
</execution>
</executions>
<configuration>
<doCheck>false</doCheck>
<doUpdate>false</doUpdate>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>generate-resources</phase>
<configuration>
<target>
<tstamp/>
<copy file="${basedir}/src/main/resources/org/libresonic/player/i18n/ResourceBundle_en.properties"
tofile="${project.build.directory}/classes/org/libresonic/player/i18n/ResourceBundle.properties"/>
<echo file="${project.build.directory}/classes/build_number.txt">${buildNumber}</echo>
<echo file="${project.build.directory}/classes/build_date.txt">${DSTAMP}</echo>
<echo file="${project.build.directory}/classes/version.txt">${project.version}</echo>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>tomcat-embed</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<boot.group.excludes>org.eclipse.jetty,org.eclipse.jetty.websocket,org.mortbay.jasper,org.eclipse.jetty.toolchain,org.ow2.asm</boot.group.excludes>
<boot.artifact.excludes>spring-boot-starter-jetty</boot.artifact.excludes>
</properties>
</profile>
<profile>
<id>jetty-embed</id>
<properties>
<boot.group.excludes>org.apache.tomcat.embed</boot.group.excludes>
<boot.artifact.excludes>spring-boot-starter-tomcat</boot.artifact.excludes>
</properties>
</profile>
</profiles>
</project>
@@ -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 org.libresonic.player.domain.ArtistBio;
import java.util.List;
/**
* @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,44 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, 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,177 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.libresonic.player.domain.LastFmCoverArt;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.service.LastFmService;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
/**
* 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 = LoggerFactory.getLogger(CoverArtService.class);
private SecurityService securityService;
private MediaFileService mediaFileService;
private LastFmService lastFmService;
public List<LastFmCoverArt> searchCoverArt(String artist, String album) {
return lastFmService.searchCoverArt(artist, album);
}
/**
* 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;
try (CloseableHttpClient client = HttpClients.createDefault()) {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(20 * 1000) // 20 seconds
.setSocketTimeout(20 * 1000) // 20 seconds
.build();
HttpGet method = new HttpGet(url);
method.setConfig(requestConfig);
try (CloseableHttpResponse 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);
}
}
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;
}
public void setLastFmService(LastFmService lastFmService) {
this.lastFmService = lastFmService;
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, 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,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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.libresonic.player.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.StringReader;
import java.net.SocketException;
/**
* 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 = LoggerFactory.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 {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(15000)
.setSocketTimeout(15000)
.build();
HttpGet method = new HttpGet(url);
method.setConfig(requestConfig);
try (CloseableHttpClient client = HttpClients.createDefault()) {
ResponseHandler<String> responseHandler = new BasicResponseHandler();
return client.execute(method, responseHandler);
}
}
}
@@ -0,0 +1,120 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import org.directwebremoting.WebContextFactory;
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.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* 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 = LoggerFactory.getLogger(MultiService.class);
private MediaFileService mediaFileService;
private LastFmService lastFmService;
private SecurityService securityService;
private SettingsService settingsService;
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 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,99 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, 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,159 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import org.apache.commons.lang.StringUtils;
import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.*;
import org.libresonic.player.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 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 = LoggerFactory.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 = NetworkService.getBaseUrl(request);
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 + "/stream?player=" + player.getId() + "&id=" + mediaFile.getId();
String albumUrl = url + "/main.view?id=" + mediaFile.getId();
String lyricsUrl = null;
if (!mediaFile.isVideo()) {
lyricsUrl = url + "/lyrics.view?artistUtf8Hex=" + StringUtil.utf8HexEncode(artist) +
"&songUtf8Hex=" + StringUtil.utf8HexEncode(title);
}
String coverArtUrl = url + "/coverArt.view?size=60&id=" + mediaFile.getId();
String avatarUrl = null;
if (userSettings.getAvatarScheme() == AvatarScheme.SYSTEM) {
avatarUrl = url + "/avatar.view?id=" + userSettings.getSystemAvatarId();
} else if (userSettings.getAvatarScheme() == AvatarScheme.CUSTOM && settingsService.getCustomAvatar(username) != null) {
avatarUrl = url + "/avatar.view?usernameUtf8Hex=" + StringUtil.utf8HexEncode(username);
}
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,224 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import org.libresonic.player.util.StringUtil;
import java.util.List;
/**
* 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 radioEnabled;
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 radioEnabled, boolean sendM3U, float gain) {
this.entries = entries;
this.stopEnabled = stopEnabled;
this.repeatEnabled = repeatEnabled;
this.radioEnabled = radioEnabled;
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 boolean isRadioEnabled() {
return radioEnabled;
}
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,745 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import org.directwebremoting.WebContextFactory;
import org.libresonic.player.dao.MediaFileDao;
import org.libresonic.player.dao.PlayQueueDao;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.*;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.util.StringUtil;
import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
/**
* 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;
private JWTSecurityService jwtSecurityService;
/**
* 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 toggleStartStop() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
return doToggleStartStop(request, response);
}
public PlayQueueInfo doToggleStartStop(HttpServletRequest request, HttpServletResponse response) throws Exception {
Player player = getCurrentPlayer(request, response);
if (player.getPlayQueue().getStatus() == PlayQueue.Status.STOPPED) {
player.getPlayQueue().setStatus(PlayQueue.Status.PLAYING);
} else if (player.getPlayQueue().getStatus() == PlayQueue.Status.PLAYING) {
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 PlayQueueInfo reloadSearchCriteria() throws Exception {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
String username = securityService.getCurrentUsername(request);
Player player = getCurrentPlayer(request, response);
PlayQueue playQueue = player.getPlayQueue();
if (playQueue.getRandomSearchCriteria() != null) {
playQueue.addFiles(true, mediaFileService.getRandomSongs(playQueue.getRandomSearchCriteria(), username));
}
return convert(request, player, false);
}
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);
PlayQueue playQueue = player.getPlayQueue();
if (playQueue.isRadioEnabled()) {
playQueue.setRandomSearchCriteria(null);
playQueue.setRepeatEnabled(false);
} else {
playQueue.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 = NetworkService.getBaseUrl(request);
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 + "/main.view?id=" + file.getId();
String streamUrl = url + "/stream?player=" + player.getId() + "&id=" + file.getId();
String coverArtUrl = url + "/coverArt.view?id=" + file.getId();
String remoteStreamUrl = jwtSecurityService.addJWTToken(url + "/ext/stream?player=" + player.getId() + "&id=" + file.getId());
String remoteCoverArtUrl = jwtSecurityService.addJWTToken(url + "/ext/coverArt.view?id=" + file.getId());
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(), playQueue.isRadioEnabled(), 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 file.getFormat();
}
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;
}
public void setJwtSecurityService(JWTSecurityService jwtSecurityService) {
this.jwtSecurityService = jwtSecurityService;
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import org.libresonic.player.domain.Playlist;
import java.util.List;
/**
* 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,263 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import org.directwebremoting.WebContextFactory;
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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.DateFormat;
import java.util.*;
/**
* 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,44 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, 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,65 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
import org.libresonic.player.dao.MediaFileDao;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.SecurityService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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 = LoggerFactory.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,131 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, 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.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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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 = LoggerFactory.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,50 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.ajax;
import org.directwebremoting.WebContextFactory;
import org.libresonic.player.controller.UploadController;
import org.libresonic.player.domain.TransferStatus;
import javax.servlet.http.HttpSession;
/**
* 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,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 2016 (C) Libresonic Authors
Based upon Subsonic, 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,222 @@
package org.libresonic.player.boot;
import net.sf.ehcache.constructs.web.ShutdownListener;
import org.directwebremoting.servlet.DwrServlet;
import org.libresonic.player.filter.*;
import org.libresonic.player.spring.LibresonicPropertySourceConfigurer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.util.ReflectionUtils;
import javax.servlet.Filter;
import javax.servlet.ServletContextListener;
import java.lang.reflect.Method;
@SpringBootApplication(exclude = {
JmxAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class,
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
MultipartAutoConfiguration.class, // TODO: update to use spring boot builtin multipart support
LiquibaseAutoConfiguration.class})
@Configuration
@ImportResource(value = {"classpath:/applicationContext-service.xml",
"classpath:/applicationContext-cache.xml",
"classpath:/applicationContext-sonos.xml",
"classpath:/libresonic-servlet.xml"})
public class Application extends SpringBootServletInitializer implements EmbeddedServletContainerCustomizer {
private static final Logger LOG = LoggerFactory.getLogger(Application.class);
/**
* Registers the DWR servlet.
*
* @return a registration bean.
*/
@Bean
public ServletRegistrationBean dwrServletRegistrationBean() {
ServletRegistrationBean servlet = new ServletRegistrationBean(new DwrServlet(), "/dwr/*");
servlet.addInitParameter("crossDomainSessionSecurity","false");
return servlet;
}
@Bean
public ServletRegistrationBean cxfServletBean() {
return new ServletRegistrationBean(new org.apache.cxf.transport.servlet.CXFServlet(), "/ws/*");
}
@Bean
public ServletContextListener ehCacheShutdownListener() {
return new ShutdownListener();
}
@Bean
public FilterRegistrationBean bootstrapVerificationFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(bootstrapVerificationFiler());
registration.addUrlPatterns("/*");
registration.setName("BootstrapVerificationFilter");
registration.setOrder(1);
return registration;
}
@Bean
public Filter bootstrapVerificationFiler() {
return new BootstrapVerificationFilter();
}
@Bean
public FilterRegistrationBean parameterDecodingFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(parameterDecodingFilter());
registration.addUrlPatterns("/*");
registration.setName("ParameterDecodingFilter");
registration.setOrder(2);
return registration;
}
@Bean
public Filter parameterDecodingFilter() {
return new ParameterDecodingFilter();
}
@Bean
public FilterRegistrationBean restFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(restFilter());
registration.addUrlPatterns("/rest/*");
registration.setName("RESTFilter");
registration.setOrder(3);
return registration;
}
@Bean
public Filter restFilter() {
return new RESTFilter();
}
@Bean
public FilterRegistrationBean requestEncodingFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(requestEncodingFilter());
registration.addUrlPatterns("/*");
registration.addInitParameter("encoding", "UTF-8");
registration.setName("RequestEncodingFilter");
registration.setOrder(4);
return registration;
}
@Bean
public Filter requestEncodingFilter() {
return new RequestEncodingFilter();
}
@Bean
public FilterRegistrationBean cacheFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(cacheFilter());
registration.addUrlPatterns("/icons/*", "/style/*", "/script/*", "/dwr/*", "/icons/*", "/coverArt.view", "/avatar.view");
registration.addInitParameter("Cache-Control", "max-age=36000");
registration.setName("CacheFilter");
registration.setOrder(5);
return registration;
}
@Bean
public Filter cacheFilter() {
return new ResponseHeaderFilter();
}
@Bean
public FilterRegistrationBean noCacheFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(noCacheFilter());
registration.addUrlPatterns("/statusChart.view", "/userChart.view", "/playQueue.view", "/podcastChannels.view", "/podcastChannel.view", "/help.view", "/top.view", "/home.view");
registration.addInitParameter("Cache-Control", "no-cache, post-check=0, pre-check=0");
registration.addInitParameter("Pragma", "no-cache");
registration.addInitParameter("Expires", "Thu, 01 Dec 1994 16:00:00 GMT");
registration.setName("NoCacheFilter");
registration.setOrder(6);
return registration;
}
@Bean
public Filter metricsFilter() {
return new MetricsFilter();
}
@Bean
public FilterRegistrationBean metricsFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(metricsFilter());
registration.setOrder(7);
return registration;
}
@Bean
public Filter noCacheFilter() {
return new ResponseHeaderFilter();
}
private static SpringApplicationBuilder doConfigure(SpringApplicationBuilder application) {
// Customize the application or call application.sources(...) to add sources
// Since our example is itself a @Configuration class (via @SpringBootApplication)
// we actually don't need to override this method.
return application.sources(Application.class).web(true).initializers(new LibresonicPropertySourceConfigurer());
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return doConfigure(application);
}
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
// Yes, there is a good reason we do this.
// We cannot count on the tomcat classes being on the classpath which will
// happen if the war is deployed to another app server like Jetty. So, we
// ensure this class does not have any direct dependencies on any Tomcat
// specific classes.
try {
Class<?> tomcatESCF = Class.forName("org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory");
if(tomcatESCF.isInstance(container)) {
LOG.debug("Attempting to optimize tomcat");
Object tomcatESCFInstance = tomcatESCF.cast(container);
Class<?> tomcatApplicationClass = Class.forName("org.libresonic.player.boot.TomcatApplication");
Method configure = ReflectionUtils.findMethod(tomcatApplicationClass, "configure", tomcatESCF);
configure.invoke(null, tomcatESCFInstance);
LOG.debug("Tomcat optimizations complete");
} else {
LOG.debug("Skipping tomcat optimization as we are not running on tomcat");
}
} catch (NoClassDefFoundError | ClassNotFoundException e) {
LOG.debug("Skipping tomcat optimization as the tomcat classes are not available");
} catch (Exception e) {
LOG.warn("An error happened while trying to optimize tomcat", e);
}
}
public static void main(String[] args) {
SpringApplicationBuilder builder = new SpringApplicationBuilder();
doConfigure(builder).run(args);
}
}
@@ -0,0 +1,40 @@
package org.libresonic.player.boot;
import org.apache.catalina.Container;
import org.apache.catalina.Wrapper;
import org.apache.catalina.webresources.StandardRoot;
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
public class TomcatApplication {
public static void configure(TomcatEmbeddedServletContainerFactory tomcatFactory) {
tomcatFactory.addContextCustomizers((TomcatContextCustomizer) context -> {
boolean development = (System.getProperty("libresonic.development") != null);
// Increase the size and time before eviction of the Tomcat
// cache so that resources aren't uncompressed too often.
// See https://github.com/jhipster/generator-jhipster/issues/3995
StandardRoot resources = new StandardRoot();
if (development) {
resources.setCachingAllowed(false);
} else {
resources.setCacheMaxSize(100000);
resources.setCacheObjectMaxSize(4000);
resources.setCacheTtl(24 * 3600 * 1000); // 1 day, in milliseconds
}
context.setResources(resources);
// Put Jasper in production mode so that JSP aren't recompiled
// on each request.
// See http://stackoverflow.com/questions/29653326/spring-boot-application-slow-because-of-jsp-compilation
Container jsp = context.findChild("jsp");
if (jsp instanceof Wrapper) {
((Wrapper) jsp).addInitParameter("development", Boolean.toString(development));
}
});
}
}
@@ -0,0 +1,57 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.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.service.SettingsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import java.io.File;
/**
* Initializes Ehcache and creates caches.
*
* @author Sindre Mehus
* @version $Id$
*/
public class CacheFactory implements InitializingBean {
private static final Logger LOG = LoggerFactory.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,170 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, 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 String smtpServer;
private String smtpEncryption;
private String smtpPort;
private String smtpUser;
private String smtpPassword;
private String smtpFrom;
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) {
}
public String getSmtpServer() {
return smtpServer;
}
public void setSmtpServer(String smtpServer) {
this.smtpServer = smtpServer;
}
public String getSmtpEncryption() {
return smtpEncryption;
}
public void setSmtpEncryption(String smtpEncryption) {
this.smtpEncryption = smtpEncryption;
}
public String getSmtpPort() {
return smtpPort;
}
public void setSmtpPort(String smtpPort) {
this.smtpPort = smtpPort;
}
public String getSmtpUser() {
return smtpUser;
}
public void setSmtpUser(String smtpUser) {
this.smtpUser = smtpUser;
}
public String getSmtpPassword() {
return smtpPassword;
}
public void setSmtpPassword(String smtpPassword) {
this.smtpPassword = smtpPassword;
}
public String getSmtpFrom() {
return smtpFrom;
}
public void setSmtpFrom(String smtpFrom) {
this.smtpFrom = smtpFrom;
}
}
@@ -0,0 +1,82 @@
package org.libresonic.player.command;
import org.libresonic.player.spring.DataSourceConfigType;
import javax.validation.constraints.NotNull;
public class DatabaseSettingsCommand {
@NotNull
private DataSourceConfigType configType;
private String embedDriver;
private String embedPassword;
private String embedUrl;
private String embedUsername;
private String JNDIName;
private int mysqlVarcharMaxlength;
private String usertableQuote;
public DataSourceConfigType getConfigType() {
return configType;
}
public void setConfigType(DataSourceConfigType configType) {
this.configType = configType;
}
public String getEmbedDriver() {
return embedDriver;
}
public void setEmbedDriver(String embedDriver) {
this.embedDriver = embedDriver;
}
public String getEmbedPassword() {
return embedPassword;
}
public void setEmbedPassword(String embedPassword) {
this.embedPassword = embedPassword;
}
public String getEmbedUrl() {
return embedUrl;
}
public void setEmbedUrl(String embedUrl) {
this.embedUrl = embedUrl;
}
public String getEmbedUsername() {
return embedUsername;
}
public void setEmbedUsername(String embedUsername) {
this.embedUsername = embedUsername;
}
public String getJNDIName() {
return JNDIName;
}
public void setJNDIName(String JNDIName) {
this.JNDIName = JNDIName;
}
public int getMysqlVarcharMaxlength() {
return mysqlVarcharMaxlength;
}
public void setMysqlVarcharMaxlength(int mysqlVarcharMaxlength) {
this.mysqlVarcharMaxlength = mysqlVarcharMaxlength;
}
public String getUsertableQuote() {
return usertableQuote;
}
public void setUsertableQuote(String usertableQuote) {
this.usertableQuote = usertableQuote;
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, 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,189 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, 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;
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 void setReloadNeeded(boolean 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;
}
}
@@ -0,0 +1,179 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.command;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.controller.MusicFolderSettingsController;
import org.libresonic.player.domain.MusicFolder;
import java.io.File;
import java.util.Date;
import java.util.List;
/**
* 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;
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 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,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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.command;
import org.libresonic.player.controller.PasswordSettingsController;
/**
* Command used in {@link PasswordSettingsController}.
*
* @author Sindre Mehus
*/
public class PasswordSettingsCommand {
private String username;
private String password;
private String confirmPassword;
private boolean ldapAuthenticated;
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;
}
}
@@ -0,0 +1,276 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.command;
import org.libresonic.player.controller.PersonalSettingsController;
import org.libresonic.player.domain.*;
import java.util.List;
/**
* 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 showArtistInfoEnabled;
private boolean nowPlayingAllowed;
private boolean autoHidePlayQueue;
private boolean keyboardShortcutsEnabled;
private boolean finalVersionNotificationEnabled;
private boolean betaVersionNotificationEnabled;
private boolean songNotificationEnabled;
private boolean queueFollowingSongs;
private boolean lastFmEnabled;
private int listReloadDelay;
private int paginationSize;
private String lastFmUsername;
private String lastFmPassword;
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 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 isKeyboardShortcutsEnabled() {
return keyboardShortcutsEnabled;
}
public void setKeyboardShortcutsEnabled(boolean keyboardShortcutsEnabled) {
this.keyboardShortcutsEnabled = keyboardShortcutsEnabled;
}
public boolean isLastFmEnabled() {
return lastFmEnabled;
}
public void setLastFmEnabled(boolean lastFmEnabled) {
this.lastFmEnabled = lastFmEnabled;
}
public int getListReloadDelay() {
return listReloadDelay;
}
public void setListReloadDelay(int listReloadDelay) {
this.listReloadDelay = listReloadDelay;
}
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 isQueueFollowingSongs() {
return queueFollowingSongs;
}
public void setQueueFollowingSongs(boolean queueFollowingSongs) {
this.queueFollowingSongs = queueFollowingSongs;
}
public int getPaginationSize() {
return paginationSize;
}
public void setPaginationSize(int paginationSize) {
this.paginationSize = paginationSize;
}
}
@@ -0,0 +1,232 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.command;
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;
import java.util.Date;
import java.util.List;
/**
* 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 boolean isM3uBomEnabled;
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;
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 boolean isM3uBomEnabled() {
return isM3uBomEnabled;
}
public void setM3uBomEnabled(boolean m3uBomEnabled) {
isM3uBomEnabled = m3uBomEnabled;
}
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 void setReloadNeeded(boolean 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,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 2016 (C) Libresonic Authors
Based upon Subsonic, 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;
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;
}
}
@@ -0,0 +1,138 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.command;
import org.libresonic.player.controller.SearchController;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.User;
import java.util.List;
/**
* 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,301 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, 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;
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();
setNewUser(false);
}
}
@@ -0,0 +1,61 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.springframework.ui.context.Theme;
import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.http.HttpServletRequest;
import java.awt.*;
import java.util.Locale;
/**
* Abstract super class for controllers which generate charts.
*
* @author Sindre Mehus
*/
public abstract class AbstractChartController {
/**
* 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,29 @@
package org.libresonic.player.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Spring MVC Controller that serves the login page.
*/
@Controller
@RequestMapping("/accessDenied")
public class AccessDeniedController {
private static final Logger LOG = LoggerFactory.getLogger(AccessDeniedController.class);
@RequestMapping(method = {RequestMethod.GET})
public ModelAndView accessDenied(HttpServletRequest request, HttpServletResponse response) {
return new ModelAndView("accessDenied");
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.command.AdvancedSettingsCommand;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
/**
* Controller for the page used to administrate advanced settings.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/advancedSettings")
public class AdvancedSettingsController {
@Autowired
private SettingsService settingsService;
// TODO replace with @GetMapping in Spring 4
@RequestMapping(method = RequestMethod.GET)
protected String formBackingObject(Model model) 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());
command.setSmtpServer(settingsService.getSmtpServer());
command.setSmtpEncryption(settingsService.getSmtpEncryption());
command.setSmtpPort(settingsService.getSmtpPort());
command.setSmtpUser(settingsService.getSmtpUser());
command.setSmtpFrom(settingsService.getSmtpFrom());
model.addAttribute("command", command);
return "advancedSettings";
}
@RequestMapping(method = RequestMethod.POST)
protected String doSubmitAction(@ModelAttribute AdvancedSettingsCommand command, RedirectAttributes redirectAttributes) throws Exception {
redirectAttributes.addFlashAttribute("settings_reload", false);
redirectAttributes.addFlashAttribute("settings_toast", true);
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.setSmtpServer(command.getSmtpServer());
settingsService.setSmtpEncryption(command.getSmtpEncryption());
settingsService.setSmtpPort(command.getSmtpPort());
settingsService.setSmtpUser(command.getSmtpUser());
settingsService.setSmtpFrom(command.getSmtpFrom());
if (StringUtils.isNotEmpty(command.getSmtpPassword())) {
settingsService.setSmtpPassword(command.getSmtpPassword());
}
settingsService.save();
return "redirect:advancedSettings.view";
}
}
@@ -0,0 +1,45 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Controller for the page which forwards to allmusic.com.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/allmusic")
public class AllmusicController {
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView result = new ModelAndView();
result.addObject("album", request.getParameter("album"));
return result;
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2013 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang.RandomStringUtils;
import javax.swing.*;
import java.awt.*;
import java.io.IOException;
/**
* @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,99 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.Avatar;
import org.libresonic.player.domain.AvatarScheme;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.LastModified;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Controller which produces avatar images.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/avatar")
public class AvatarController implements LastModified {
@Autowired
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;
}
@RequestMapping(method = RequestMethod.GET)
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);
}
if(userSettings.getAvatarScheme() == AvatarScheme.NONE) {
return null;
}
return settingsService.getSystemAvatar(userSettings.getSystemAvatarId());
}
}
@@ -0,0 +1,142 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
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.lang3.StringUtils;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
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
*/
@Controller
@RequestMapping("/avatarUpload")
public class AvatarUploadController {
private static final Logger LOG = LoggerFactory.getLogger(AvatarUploadController.class);
private static final int MAX_AVATAR_SIZE = 64;
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@RequestMapping(method = RequestMethod.POST)
protected ModelAndView handleRequestInternal(HttpServletRequest request) 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));
return new ModelAndView("avatarUploadResult","model",map);
}
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);
}
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.service.MediaFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for changing cover art.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/changeCoverArt")
public class ChangeCoverArtController {
@Autowired
private MediaFileService mediaFileService;
@RequestMapping(method = RequestMethod.GET)
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 (StringUtils.isBlank(artist)) {
artist = dir.getArtist();
}
if (StringUtils.isBlank(album)) {
album = dir.getAlbumName();
}
Map<String, Object> map = new HashMap<>();
map.put("id", id);
map.put("artist", artist);
map.put("album", album);
return new ModelAndView("changeCoverArt","model",map);
}
}
@@ -0,0 +1,25 @@
package org.libresonic.player.controller;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.HandlerMapping;
import javax.servlet.http.HttpServletRequest;
/**
* This class has been created to refactor code previously present
* in the MultiController.
*/
public class ControllerUtils {
public static String extractMatched(final HttpServletRequest request){
String path = (String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
String bestMatchPattern = (String ) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
AntPathMatcher apm = new AntPathMatcher();
return apm.extractPathWithinPattern(bestMatchPattern, path);
}
}
@@ -0,0 +1,699 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.libresonic.player.dao.AlbumDao;
import org.libresonic.player.dao.ArtistDao;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.*;
import org.libresonic.player.service.metadata.JaudiotaggerParser;
import org.libresonic.player.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.LastModified;
import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Semaphore;
/**
* Controller which produces cover art images.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping(value = {"/coverArt", "/ext/coverArt"})
public class CoverArtController implements 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 = LoggerFactory.getLogger(CoverArtController.class);
@Autowired
private MediaFileService mediaFileService;
@Autowired
private TranscodingService transcodingService;
@Autowired
private SettingsService settingsService;
@Autowired
private PlaylistService playlistService;
@Autowired
private PodcastService podcastService;
@Autowired
private ArtistDao artistDao;
@Autowired
private AlbumDao albumDao;
private Semaphore semaphore;
@PostConstruct
public void init() {
semaphore = new Semaphore(settingsService.getCoverArtConcurrency());
}
public long getLastModified(HttpServletRequest request) {
CoverArtRequest coverArtRequest = createCoverArtRequest(request);
// LOG.info("getLastModified - " + coverArtRequest + ": " + new Date(result));
return coverArtRequest.lastModified();
}
@RequestMapping(method = RequestMethod.GET)
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;
}
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<>(albums);
}
}
private class PodcastCoverArtRequest extends CoverArtRequest {
private final PodcastChannel channel;
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;
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;
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,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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.libresonic.player.dao.DaoHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
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
*/
@Controller
@RequestMapping("/db")
public class DBController {
@Autowired
private DaoHelper daoHelper;
@RequestMapping(method = { RequestMethod.GET, RequestMethod.POST })
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());
}
}
return new ModelAndView("db","model",map);
}
}
@@ -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 org.apache.commons.lang.StringUtils;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.UPnPService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for the page used to administrate the UPnP/DLNA server settings.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/dlnaSettings")
public class DLNASettingsController {
@Autowired
private UPnPService upnpService;
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.GET)
public String handleGet(Model model) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put("dlnaEnabled", settingsService.isDlnaEnabled());
map.put("dlnaServerName", settingsService.getDlnaServerName());
map.put("dlnaBaseLANURL", settingsService.getDlnaBaseLANURL());
model.addAttribute("model", map);
return "dlnaSettings";
}
@RequestMapping(method = RequestMethod.POST)
public String handlePost(HttpServletRequest request, RedirectAttributes redirectAttributes) throws Exception {
handleParameters(request);
redirectAttributes.addFlashAttribute("settings_toast", true);
return "redirect:dlnaSettings.view";
}
private void handleParameters(HttpServletRequest request) {
boolean dlnaEnabled = ServletRequestUtils.getBooleanParameter(request, "dlnaEnabled", false);
String dlnaServerName = StringUtils.trimToNull(request.getParameter("dlnaServerName"));
String dlnaBaseLANURL = StringUtils.trimToNull(request.getParameter("dlnaBaseLANURL"));
if (dlnaServerName == null) {
dlnaServerName = "Libresonic";
}
upnpService.setMediaServerEnabled(false);
settingsService.setDlnaEnabled(dlnaEnabled);
settingsService.setDlnaServerName(dlnaServerName);
settingsService.setDlnaBaseLANURL(dlnaBaseLANURL);
settingsService.save();
upnpService.setMediaServerEnabled(dlnaEnabled);
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setUpnpService(UPnPService upnpService) {
this.upnpService = upnpService;
}
}
@@ -0,0 +1,94 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.command.DatabaseSettingsCommand;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.spring.DataSourceConfigType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
@RequestMapping("/databaseSettings")
public class DatabaseSettingsController {
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.GET)
protected String displayForm() throws Exception {
return "databaseSettings";
}
@ModelAttribute
protected void formBackingObject(Model model) throws Exception {
DatabaseSettingsCommand command = new DatabaseSettingsCommand();
command.setConfigType(settingsService.getDatabaseConfigType());
command.setEmbedDriver(settingsService.getDatabaseConfigEmbedDriver());
command.setEmbedPassword(settingsService.getDatabaseConfigEmbedPassword());
command.setEmbedUrl(settingsService.getDatabaseConfigEmbedUrl());
command.setEmbedUsername(settingsService.getDatabaseConfigEmbedUsername());
command.setJNDIName(settingsService.getDatabaseConfigJNDIName());
command.setMysqlVarcharMaxlength(settingsService.getDatabaseMysqlVarcharMaxlength());
command.setUsertableQuote(settingsService.getDatabaseUsertableQuote());
model.addAttribute("command", command);
}
@RequestMapping(method = RequestMethod.POST)
protected String onSubmit(@ModelAttribute("command") @Validated DatabaseSettingsCommand command,
BindingResult bindingResult,
RedirectAttributes redirectAttributes) throws Exception {
if (!bindingResult.hasErrors()) {
settingsService.resetDatabaseToDefault();
settingsService.setDatabaseConfigType(command.getConfigType());
switch(command.getConfigType()) {
case EMBED:
settingsService.setDatabaseConfigEmbedDriver(command.getEmbedDriver());
settingsService.setDatabaseConfigEmbedPassword(command.getEmbedPassword());
settingsService.setDatabaseConfigEmbedUrl(command.getEmbedUrl());
settingsService.setDatabaseConfigEmbedUsername(command.getEmbedUsername());
break;
case JNDI:
settingsService.setDatabaseConfigJNDIName(command.getJNDIName());
break;
case LEGACY:
default:
break;
}
if(command.getConfigType() != DataSourceConfigType.LEGACY) {
settingsService.setDatabaseMysqlVarcharMaxlength(command.getMysqlVarcharMaxlength());
settingsService.setDatabaseUsertableQuote(command.getUsertableQuote());
}
redirectAttributes.addFlashAttribute("settings_toast", true);
settingsService.save();
return "redirect:databaseSettings.view";
} else {
return "databaseSettings.view";
}
}
}
@@ -0,0 +1,385 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.libresonic.player.domain.*;
import org.libresonic.player.io.RangeOutputStream;
import org.libresonic.player.service.*;
import org.libresonic.player.util.FileUtil;
import org.libresonic.player.util.HttpRange;
import org.libresonic.player.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.LastModified;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* 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
*/
@Controller
@RequestMapping("/download")
public class DownloadController implements LastModified {
private static final Logger LOG = LoggerFactory.getLogger(DownloadController.class);
@Autowired
private PlayerService playerService;
@Autowired
private StatusService statusService;
@Autowired
private SecurityService securityService;
@Autowired
private PlaylistService playlistService;
@Autowired
private SettingsService settingsService;
@Autowired
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;
}
}
@RequestMapping(method = RequestMethod.GET)
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.
Set<MediaFile> filesToDownload = new HashSet<>();
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();
}
}
@@ -0,0 +1,196 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.io.FilenameUtils;
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;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller for the page used to edit MP3 tags.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/editTags")
public class EditTagsController {
@Autowired
private MetaDataParserFactory metaDataParserFactory;
@Autowired
private MediaFileService mediaFileService;
@RequestMapping(method = RequestMethod.GET)
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);
return new ModelAndView("editTags","model",map);
}
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;
}
/**
* 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,52 @@
package org.libresonic.player.controller;
import org.libresonic.player.domain.Playlist;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Spring MVC Controller that serves the login page.
*/
@Controller
@RequestMapping("/exportPlaylist")
public class ExportPlayListController {
private static final Logger LOG = LoggerFactory.getLogger(ExportPlayListController.class);
@Autowired
private PlaylistService playlistService;
@Autowired
private SecurityService securityService;
@RequestMapping(method = { RequestMethod.GET })
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;
}
}
@@ -0,0 +1,152 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.commons.lang3.StringUtils;
import org.libresonic.player.domain.*;
import org.libresonic.player.security.JWTAuthenticationToken;
import org.libresonic.player.service.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
/**
* Controller for the page used to play shared music (Twitter, Facebook etc).
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping(value = {"/ext/share/**"})
public class ExternalPlayerController {
private static final Logger LOG = LoggerFactory.getLogger(ExternalPlayerController.class);
@Autowired
private SettingsService settingsService;
@Autowired
private PlayerService playerService;
@Autowired
private ShareService shareService;
@Autowired
private MediaFileService mediaFileService;
@Autowired
private JWTSecurityService jwtSecurityService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
String shareName = ControllerUtils.extractMatched(request);
LOG.debug("Share name is {}", shareName);
if(StringUtils.isBlank(shareName)) {
LOG.warn("Could not find share with shareName " + shareName);
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
}
Share share = shareService.getShareByName(shareName);
if (share != null && share.getExpires() != null && share.getExpires().before(new Date())) {
LOG.warn("Share " + shareName + " is expired");
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(request, share, player));
return new ModelAndView("externalPlayer", "model", map);
}
private List<MediaFileWithUrlInfo> getSongs(HttpServletRequest request, Share share, Player player) throws IOException {
Date expires = null;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(authentication instanceof JWTAuthenticationToken) {
DecodedJWT token = jwtSecurityService.verify((String) authentication.getCredentials());
expires = token.getExpiresAt();
}
Date finalExpires = expires;
List<MediaFileWithUrlInfo> result = new ArrayList<>();
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(player.getUsername());
if (share != null) {
for (MediaFile file : shareService.getSharedFiles(share.getId(), musicFolders)) {
if (file.getFile().exists()) {
if (file.isDirectory()) {
List<MediaFile> childrenOf = mediaFileService.getChildrenOf(file, true, false, true);
result.addAll(childrenOf.stream().map(mf -> addUrlInfo(request, player, mf, finalExpires)).collect(Collectors.toList()));
} else {
result.add(addUrlInfo(request, player, file, finalExpires));
}
}
}
}
return result;
}
public MediaFileWithUrlInfo addUrlInfo(HttpServletRequest request, Player player, MediaFile mediaFile, Date expires) {
String prefix = "/ext";
String streamUrl = jwtSecurityService.addJWTToken(
UriComponentsBuilder
.fromHttpUrl(NetworkService.getBaseUrl(request) + prefix + "/stream")
.queryParam("id", mediaFile.getId())
.queryParam("player", player.getId())
.queryParam("maxBitRate", "1200"),
expires)
.build()
.toUriString();
String coverArtUrl = jwtSecurityService.addJWTToken(
UriComponentsBuilder
.fromHttpUrl(NetworkService.getBaseUrl(request) + prefix + "/coverArt.view")
.queryParam("id", mediaFile.getId())
.queryParam("size", "500"),
expires)
.build()
.toUriString();
return new MediaFileWithUrlInfo(mediaFile, coverArtUrl, streamUrl);
}
}
@@ -0,0 +1,132 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.command.GeneralSettingsCommand;
import org.libresonic.player.domain.Theme;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.Locale;
/**
* Controller for the page used to administrate general settings.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/generalSettings")
public class GeneralSettingsController {
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.GET)
protected String displayForm() throws Exception {
return "generalSettings";
}
@ModelAttribute
protected void formBackingObject(Model model) 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);
model.addAttribute("command",command);
}
@RequestMapping(method = RequestMethod.POST)
protected String doSubmitAction(@ModelAttribute("command") GeneralSettingsCommand command, RedirectAttributes redirectAttributes) throws Exception {
int themeIndex = Integer.parseInt(command.getThemeIndex());
Theme theme = settingsService.getAvailableThemes()[themeIndex];
int localeIndex = Integer.parseInt(command.getLocaleIndex());
Locale locale = settingsService.getAvailableLocales()[localeIndex];
redirectAttributes.addFlashAttribute("settings_toast", true);
redirectAttributes.addFlashAttribute(
"settings_reload",
!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();
return "redirect:generalSettings.view";
}
}
@@ -0,0 +1,56 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequestMapping("/gettingStarted")
public class GettingStartedController {
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.GET)
public ModelAndView gettingStarted(HttpServletRequest request) {
if (request.getParameter("hide") != null) {
settingsService.setGettingStartedEnabled(false);
settingsService.save();
return new ModelAndView(new RedirectView("home.view"));
}
Map<String, Object> map = new HashMap<>();;
map.put("runningAsRoot", "root".equals(System.getProperty("user.name")));
return new ModelAndView("gettingStarted", "model", map);
}
}
@@ -0,0 +1,213 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.Player;
import org.libresonic.player.service.JWTSecurityService;
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;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Controller which produces the HLS (Http Live Streaming) playlist.
*
* @author Sindre Mehus
*/
@Controller(value = "hlsController")
@RequestMapping(value = {"/hls/**", "/ext/hls/**"})
public class HLSController {
private static final int SEGMENT_DURATION = 10;
private static final Pattern BITRATE_PATTERN = Pattern.compile("(\\d+)(@(\\d+)x(\\d+))?");
@Autowired
private PlayerService playerService;
@Autowired
private MediaFileService mediaFileService;
@Autowired
private SecurityService securityService;
@Autowired
private JWTSecurityService jwtSecurityService;
@RequestMapping(method = RequestMethod.GET)
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);
UriComponentsBuilder url = (UriComponentsBuilder.fromUriString(contextPath + "ext/hls/hls.m3u8")
.queryParam("id", id)
.queryParam("player", player.getId())
.queryParam("bitRate", kbps));
jwtSecurityService.addJWTToken(url);
writer.print(url.toUriString());
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) {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(getContextPath(request) + "ext/stream/stream.ts");
builder.queryParam("id", id);
builder.queryParam("hls", "true");
builder.queryParam("timeOffset", offset);
builder.queryParam("player", player.getId());
builder.queryParam("duration", duration);
if (bitRate != null) {
builder.queryParam("maxBitRate", bitRate.getFirst());
Dimension dimension = bitRate.getSecond();
if (dimension != null) {
builder.queryParam("size", dimension.width);
builder.queryParam("x", dimension.height);
}
}
jwtSecurityService.addJWTToken(builder);
return builder.toUriString();
}
private String getContextPath(HttpServletRequest request) {
String contextPath = request.getContextPath();
if (StringUtils.isEmpty(contextPath)) {
contextPath = "/";
} else {
contextPath += "/";
}
return contextPath;
}
}
@@ -0,0 +1,115 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.io.input.ReversedLinesFileReader;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.VersionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller for the help page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/help")
public class HelpController {
private static final Logger logger = LoggerFactory.getLogger(HelpController.class);
private static final int LOG_LINES_TO_SHOW = 50;
@Autowired
private VersionService versionService;
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
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("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);
File logFile = SettingsService.getLogFile();
List<String> latestLogEntries = getLatestLogEntries(logFile);
map.put("logEntries", latestLogEntries);
map.put("logFile", logFile);
return new ModelAndView("help","model",map);
}
private static List<String> getLatestLogEntries(File logFile) {
try {
List<String> lines = new ArrayList<>(LOG_LINES_TO_SHOW);
ReversedLinesFileReader reader = new ReversedLinesFileReader(logFile, Charset.defaultCharset());
String current;
while((current = reader.readLine()) != null && lines.size() < LOG_LINES_TO_SHOW) {
lines.add(0, current);
}
return lines;
} catch (Exception e) {
logger.warn("Could not open log file " + logFile, e);
return null;
}
}
}
@@ -0,0 +1,341 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;
import static org.springframework.web.bind.ServletRequestUtils.getIntParameter;
import static org.springframework.web.bind.ServletRequestUtils.getStringParameter;
/**
* Controller for the home page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/home")
public class HomeController {
private static final int LIST_SIZE = 40;
@Autowired
private SettingsService settingsService;
@Autowired
private MediaScannerService mediaScannerService;
@Autowired
private RatingService ratingService;
@Autowired
private SecurityService securityService;
@Autowired
private MediaFileService mediaFileService;
@Autowired
private SearchService searchService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request) 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"));
UserSettings userSettings = settingsService.getUserSettings(user.getUsername());
if (listType == null) {
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<>();
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);
map.put("listReloadDelay", userSettings.getListReloadDelay());
return new ModelAndView("home","model",map);
}
private List<Album> getHighestRated(int offset, int count, List<MusicFolder> musicFolders) {
List<Album> result = new ArrayList<>();
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<>();
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<>();
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<>();
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<>();
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<>();
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<>();
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<>();
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<>();
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<>();
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;
}
/**
* 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,99 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
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.libresonic.player.domain.Playlist;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.service.SecurityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/importPlaylist")
public class ImportPlaylistController {
private static final long MAX_PLAYLIST_SIZE_MB = 5L;
@Autowired
private SecurityService securityService;
@Autowired
private PlaylistService playlistService;
@RequestMapping(method = RequestMethod.POST)
protected String handlePost(RedirectAttributes redirectAttributes,
HttpServletRequest request
) 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,
item.getInputStream(), null);
map.put("playlist", playlist);
}
}
}
} catch (Exception e) {
map.put("error", e.getMessage());
}
redirectAttributes.addFlashAttribute("model", map);
return "redirect:importPlaylist";
}
@RequestMapping(method = RequestMethod.GET)
public String handleGet() {
return "importPlaylist";
}
}
@@ -0,0 +1,46 @@
package org.libresonic.player.controller;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequestMapping("/index")
public class IndexController {
private static final Logger LOG = LoggerFactory.getLogger(IndexController.class);
@Autowired
private SecurityService securityService;
@Autowired
private SettingsService settingsService;
@RequestMapping(method = { RequestMethod.GET})
public ModelAndView index(HttpServletRequest request) {
UserSettings userSettings = settingsService.getUserSettings(securityService.getCurrentUsername(request));
Map<String, Object> map = new HashMap<String, Object>();
map.put("showRight", userSettings.isShowNowPlayingEnabled());
map.put("autoHidePlayQueue", userSettings.isAutoHidePlayQueue());
map.put("listReloadDelay", userSettings.getListReloadDelay());
map.put("keyboardShortcutsEnabled", userSettings.isKeyboardShortcutsEnabled());
map.put("showSideBar", userSettings.isShowSideBar());
map.put("brand", settingsService.getBrand());
return new ModelAndView("index", "model", map);
}
}
@@ -0,0 +1,120 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.domain.InternetRadio;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
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
*/
@Controller
@RequestMapping("/internetRadioSettings")
public class InternetRadioSettingsController {
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.GET)
public String doGet(Model model) throws Exception {
Map<String, Object> map = new HashMap<>();
map.put("internetRadios", settingsService.getAllInternetRadios(true));
model.addAttribute("model", map);
return "internetRadioSettings";
}
@RequestMapping(method = RequestMethod.POST)
public String doPost(HttpServletRequest request, RedirectAttributes redirectAttributes) throws Exception {
String error = handleParameters(request);
Map<String, Object> map = new HashMap<>();
if(error == null) {
redirectAttributes.addFlashAttribute("settings_toast", true);
redirectAttributes.addFlashAttribute("settings_reload", true);
}
redirectAttributes.addFlashAttribute("error", error);
return "redirect:internetRadioSettings.view";
}
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 + "]"));
}
}
@@ -0,0 +1,178 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
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.player.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.subsonic.restapi.Error;
import org.subsonic.restapi.ObjectFactory;
import org.subsonic.restapi.Response;
import org.subsonic.restapi.ResponseStatus;
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 java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.Date;
import java.util.GregorianCalendar;
import static org.springframework.web.bind.ServletRequestUtils.getStringParameter;
/**
* @author Sindre Mehus
* @version $Id$
*/
public class JAXBWriter {
private static final Logger LOG = LoggerFactory.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() {
Marshaller marshaller = null;
try {
marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, StringUtil.ENCODING_UTF8);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
return marshaller;
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
private Marshaller createJsonMarshaller() {
try {
Marshaller 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;
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
private String getRESTProtocolVersion() throws Exception {
InputStream in = null;
try {
in = StringUtil.class.getResourceAsStream("/subsonic-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) {
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().createSubsonicResponse(jaxbResponse), writer);
if (jsonp) {
writer.append(");");
}
httpResponse.getWriter().append(writer.getBuffer());
} catch (JAXBException | IOException x) {
LOG.error("Failed to marshal JAXB", x);
throw new RuntimeException(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,174 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.*;
import org.libresonic.player.util.FileUtil;
import org.libresonic.player.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.util.*;
/**
* Controller for the left index frame.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/left")
public class LeftController {
// 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);
}
@Autowired
private MediaScannerService mediaScannerService;
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@Autowired
private MusicIndexService musicIndexService;
@Autowired
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.
*/
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;
}
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
boolean musicFolderChanged = saveSelectedMusicFolder(request);
Map<String, Object> map = new HashMap<>();
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 : Collections.singletonList(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));
return new ModelAndView("left","model",map);
}
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;
}
}
@@ -0,0 +1,66 @@
package org.libresonic.player.controller;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
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;
/**
* Spring MVC Controller that serves the login page.
*/
@Controller
public class LoginController {
private static final Logger LOG = LoggerFactory.getLogger(LoginController.class);
@Autowired
private SecurityService securityService;
@Autowired
private SettingsService settingsService;
@RequestMapping(value = "/login", method = RequestMethod.GET)
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("/login?"+
UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY+"=" + username +
"&"+UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY+"=" + password
));
}
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);
}
}
@@ -0,0 +1,51 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for the lyrics popup.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/lyrics")
public class LyricsController {
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
map.put("artist", request.getParameter("artist"));
map.put("song", request.getParameter("song"));
return new ModelAndView("lyrics","model",map);
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.PlayQueue;
import org.libresonic.player.domain.Player;
import org.libresonic.player.service.JWTSecurityService;
import org.libresonic.player.service.NetworkService;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.TranscodingService;
import org.libresonic.player.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
/**
* Controller which produces the M3U playlist.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/play.m3u")
public class M3UController {
@Autowired
private PlayerService playerService;
@Autowired
private TranscodingService transcodingService;
@Autowired
private JWTSecurityService jwtSecurityService;
@RequestMapping(method = RequestMethod.GET)
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 = NetworkService.getBaseUrl(request);
url = url + "ext/stream?";
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 {
if (player.isM3uBomEnabled()) {
out.print("\ufeff");
}
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());
String urlNoAuth = url + "player=" + player.getId() + "&id=" + mediaFile.getId() + "&suffix=." +
transcodingService.getSuffix(player, mediaFile, null);
String urlWithAuth = jwtSecurityService.addJWTToken(urlNoAuth);
out.println(urlWithAuth);
}
}
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;
}
if (player.isM3uBomEnabled()) {
out.print("\ufeff");
}
out.println("#EXTM3U");
out.println("#EXTINF:-1,Libresonic");
out.println(jwtSecurityService.addJWTToken(url));
}
private String getSuffix(Player player) {
PlayQueue playQueue = player.getPlayQueue();
return playQueue.isEmpty() ? null : transcodingService.getSuffix(player, playQueue.getFile(0), null);
}
}
@@ -0,0 +1,281 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang3.BooleanUtils;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
/**
* Controller for the main page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/main")
public class MainController {
@Autowired
private SecurityService securityService;
@Autowired
private PlayerService playerService;
@Autowired
private SettingsService settingsService;
@Autowired
private RatingService ratingService;
@Autowired
private MediaFileService mediaFileService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(@RequestParam(name = "showAll", required = false) Boolean showAll,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
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"));
}
UserSettings userSettings = settingsService.getUserSettings(username);
List<MediaFile> children = mediaFiles.size() == 1 ? mediaFileService.getChildrenOf(dir,
true,
true,
true) : getMultiFolderChildren(mediaFiles);
List<MediaFile> files = new ArrayList<>();
List<MediaFile> subDirs = new ArrayList<>();
for (MediaFile child : children) {
if (child.isFile()) {
files.add(child);
} else {
subDirs.add(child);
}
}
int userPaginationPreference = userSettings.getPaginationSize();
if(userPaginationPreference <= 0) {
showAll = true;
}
boolean thereIsMoreSubDirs = trimToSize(showAll, subDirs, userPaginationPreference);
boolean thereIsMoreSAlbums = false;
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("viewAsList", isViewAsList(request, userSettings));
if (dir.isAlbum()) {
List<MediaFile> sieblingAlbums = getSieblingAlbums(dir);
thereIsMoreSAlbums = trimToSize(showAll, sieblingAlbums, userPaginationPreference);
map.put("sieblingAlbums", sieblingAlbums);
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.
}
map.put("thereIsMore", (thereIsMoreSubDirs || thereIsMoreSAlbums) && !BooleanUtils.isTrue(showAll));
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";
}
return new ModelAndView(view, "model", map);
}
private <T> boolean trimToSize(Boolean showAll, List<T> list, int userPaginationPreference) {
boolean trimmed = false;
if(!BooleanUtils.isTrue(showAll)) {
if(list.size() > userPaginationPreference) {
trimmed = true;
list.subList(userPaginationPreference, list.size()).clear();
}
}
return trimmed;
}
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<>();
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<>(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<>(result);
}
private List<MediaFile> getAncestors(MediaFile dir) throws IOException {
LinkedList<MediaFile> result = new LinkedList<>();
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 parent = mediaFileService.getParentOf(dir);
if (!mediaFileService.isRoot(parent)) {
List<MediaFile> sieblings = mediaFileService.getChildrenOf(parent, false, true, true);
result.addAll(sieblings.stream().filter(siebling -> siebling.isAlbum() && !siebling.equals(dir)).collect(Collectors.toList()));
}
return result;
}
}
@@ -0,0 +1,85 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller for the "more" page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/more")
public class MoreController {
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@Autowired
private PlayerService playerService;
@Autowired
private MediaFileService mediaFileService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
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();
}
Player player = playerService.getPlayer(request, response);
ModelAndView result = new ModelAndView();
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());
return result;
}
}
@@ -0,0 +1,136 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* Controller for the page used to administrate the set of music folders.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/musicFolderSettings")
public class MusicFolderSettingsController {
@Autowired
private SettingsService settingsService;
@Autowired
private MediaScannerService mediaScannerService;
@Autowired
private ArtistDao artistDao;
@Autowired
private AlbumDao albumDao;
@Autowired
private MediaFileDao mediaFileDao;
@RequestMapping(method = RequestMethod.GET)
protected String displayForm() throws Exception {
return "musicFolderSettings";
}
@ModelAttribute
protected void formBackingObject(@RequestParam(value = "scanNow",required = false) String scanNow,
@RequestParam(value = "expunge",required = false) String expunge,
Model model) throws Exception {
MusicFolderSettingsCommand command = new MusicFolderSettingsCommand();
if (scanNow != null) {
settingsService.clearMusicFolderCache();
mediaScannerService.scanLibrary();
}
if (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());
model.addAttribute("command",command);
}
private void expunge() {
artistDao.expunge();
albumDao.expunge();
mediaFileDao.expunge();
}
private List<MusicFolderSettingsCommand.MusicFolderInfo> wrap(List<MusicFolder> musicFolders) {
return musicFolders.stream().map(MusicFolderSettingsCommand.MusicFolderInfo::new).collect(Collectors.toCollection(ArrayList::new));
}
@RequestMapping(method = RequestMethod.POST)
protected String onSubmit(@ModelAttribute("command") MusicFolderSettingsCommand command, RedirectAttributes redirectAttributes) throws Exception {
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();
redirectAttributes.addFlashAttribute("settings_toast", true);
redirectAttributes.addFlashAttribute("settings_reload", true);
mediaScannerService.schedule();
return "redirect:musicFolderSettings.view";
}
}
@@ -0,0 +1,29 @@
package org.libresonic.player.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Spring MVC Controller that serves the login page.
*/
@Controller
@RequestMapping("/notFound")
public class NotFoundController {
private static final Logger LOG = LoggerFactory.getLogger(NotFoundController.class);
@RequestMapping(method = {RequestMethod.GET})
public ModelAndView notFound(HttpServletRequest request, HttpServletResponse response) {
return new ModelAndView("notFound");
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
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;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* Controller for showing what's currently playing.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/nowPlaying")
public class NowPlayingController {
@Autowired
private PlayerService playerService;
@Autowired
private StatusService statusService;
@Autowired
private MediaFileService mediaFileService;
@RequestMapping(method = RequestMethod.GET)
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));
}
}
@@ -0,0 +1,84 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.command.PasswordSettingsCommand;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.validator.PasswordSettingsValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* Controller for the page used to change password.
*
* @author Sindre Mehus
*/
@org.springframework.stereotype.Controller
@RequestMapping("/passwordSettings")
public class PasswordSettingsController {
@Autowired
private SecurityService securityService;
@Autowired
private PasswordSettingsValidator passwordSettingsValidator;
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(passwordSettingsValidator);
}
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView displayForm(HttpServletRequest request) throws Exception {
PasswordSettingsCommand command = new PasswordSettingsCommand();
User user = securityService.getCurrentUser(request);
command.setUsername(user.getUsername());
command.setLdapAuthenticated(user.isLdapAuthenticated());
return new ModelAndView("passwordSettings","command",command);
}
@RequestMapping(method = RequestMethod.POST)
protected String doSubmitAction(@ModelAttribute("command") @Validated PasswordSettingsCommand command, BindingResult bindingResult, RedirectAttributes redirectAttributes) throws Exception {
if (!bindingResult.hasErrors()) {
User user = securityService.getUserByName(command.getUsername());
user.setPassword(command.getPassword());
securityService.updateUser(user);
command.setPassword(null);
command.setConfirmPassword(null);
redirectAttributes.addFlashAttribute("settings_toast", true);
return "redirect:passwordSettings.view";
} else {
return "passwordSettings";
}
}
}
@@ -0,0 +1,193 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.command.PersonalSettingsCommand;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.Locale;
/**
* Controller for the page used to administrate per-user settings.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/personalSettings")
public class PersonalSettingsController {
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@ModelAttribute
protected void formBackingObject(HttpServletRequest request,Model model) 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.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.setListReloadDelay(userSettings.getListReloadDelay());
command.setKeyboardShortcutsEnabled(userSettings.isKeyboardShortcutsEnabled());
command.setLastFmEnabled(userSettings.isLastFmEnabled());
command.setLastFmUsername(userSettings.getLastFmUsername());
command.setLastFmPassword(userSettings.getLastFmPassword());
command.setPaginationSize(userSettings.getPaginationSize());
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;
}
}
model.addAttribute("command",command);
}
@RequestMapping(method = RequestMethod.GET)
protected String displayForm() throws Exception {
return "personalSettings";
}
@RequestMapping(method = RequestMethod.POST)
protected String doSubmitAction(@ModelAttribute("command") PersonalSettingsCommand command, RedirectAttributes redirectAttributes) throws Exception {
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.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.setListReloadDelay(command.getListReloadDelay());
settings.setKeyboardShortcutsEnabled(command.isKeyboardShortcutsEnabled());
settings.setLastFmEnabled(command.isLastFmEnabled());
settings.setLastFmUsername(command.getLastFmUsername());
settings.setSystemAvatarId(getSystemAvatarId(command));
settings.setAvatarScheme(getAvatarScheme(command));
settings.setPaginationSize(command.getPaginationSize());
if (StringUtils.isNotBlank(command.getLastFmPassword())) {
settings.setLastFmPassword(command.getLastFmPassword());
}
settings.setChanged(new Date());
settingsService.updateUserSettings(settings);
redirectAttributes.addFlashAttribute("settings_reload", true);
redirectAttributes.addFlashAttribute("settings_toast", true);
return "redirect:personalSettings.view";
}
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;
}
}
@@ -0,0 +1,85 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
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;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for the playlist frame.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/playQueue")
public class PlayQueueController {
@Autowired
private PlayerService playerService;
@Autowired
private SecurityService securityService;
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.GET)
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<>();
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());
return new ModelAndView("playQueue","model",map);
}
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,150 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.command.PlayerSettingsCommand;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.TranscodingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
/**
* Controller for the player settings page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/playerSettings")
public class PlayerSettingsController {
@Autowired
private PlayerService playerService;
@Autowired
private SecurityService securityService;
@Autowired
private TranscodingService transcodingService;
@RequestMapping(method = RequestMethod.GET)
protected String displayForm() throws Exception {
return "playerSettings";
}
@ModelAttribute
protected void formBackingObject(HttpServletRequest request, Model model) 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.setM3uBomEnabled(player.isM3uBomEnabled());
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());
model.addAttribute("command",command);
}
@RequestMapping(method = RequestMethod.POST)
protected String doSubmitAction(@ModelAttribute("command") PlayerSettingsCommand command, RedirectAttributes redirectAttributes) throws Exception {
Player player = playerService.getPlayerById(command.getPlayerId());
player.setAutoControlEnabled(command.isAutoControlEnabled());
player.setM3uBomEnabled(command.isM3uBomEnabled());
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());
redirectAttributes.addFlashAttribute("settings_reload", true);
redirectAttributes.addFlashAttribute("settings_toast", true);
return "redirect:playerSettings.view";
}
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"));
}
}
}
@@ -0,0 +1,88 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
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
*/
@Controller
@RequestMapping("/playlist")
public class PlaylistController {
@Autowired
private SecurityService securityService;
@Autowired
private PlaylistService playlistService;
@Autowired
private SettingsService settingsService;
@Autowired
private PlayerService playerService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
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"));
}
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());
return new ModelAndView("playlist","model",map);
}
}
@@ -0,0 +1,64 @@
/*
* 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 org.libresonic.player.domain.Playlist;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.PlaylistService;
import org.libresonic.player.service.SecurityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller for the playlists page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/playlists")
public class PlaylistsController {
@Autowired
private SecurityService securityService;
@Autowired
private PlaylistService playlistService;
@RequestMapping(method = RequestMethod.GET)
public String doGet(HttpServletRequest request, Model model) throws Exception {
Map<String, Object> map = new HashMap<>();
User user = securityService.getCurrentUser(request);
List<Playlist> playlists = playlistService.getReadablePlaylistsForUser(user.getUsername());
map.put("playlists", playlists);
model.addAttribute("model", map);
return "playlists";
}
}
@@ -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 2015 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.service.PodcastService;
import org.libresonic.player.service.SecurityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for the "Podcast channel" page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/podcastChannel")
public class PodcastChannelController {
@Autowired
private PodcastService podcastService;
@Autowired
private SecurityService securityService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
ModelAndView result = new ModelAndView();
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;
}
}
@@ -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 2015 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
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;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Controller for the "Podcast channels" page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/podcastChannels")
public class PodcastChannelsController {
@Autowired
private PodcastService podcastService;
@Autowired
private SecurityService securityService;
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
ModelAndView result = new ModelAndView();
result.addObject("model", map);
Map<PodcastChannel, List<PodcastEpisode>> channels = new LinkedHashMap<>();
Map<Integer, PodcastChannel> channelMap = new HashMap<>();
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));
return result;
}
}
@@ -0,0 +1,136 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* Controller for the page used to generate the Podcast XML file.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/podcast")
public class PodcastController {
private static final DateFormat RSS_DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
@Autowired
private PlaylistService playlistService;
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@RequestMapping(method = RequestMethod.GET)
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<>();
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);
String enclosureUrl = url + "/stream?playlist=" + playlist.getId();
podcasts.add(new Podcast(playlist.getName(), publishDate, enclosureUrl, length, type));
}
Map<String, Object> map = new HashMap<>();
map.put("url", url);
map.put("podcasts", podcasts);
return new ModelAndView("podcast","model",map);
}
/**
* 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,99 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang.StringUtils;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Controller for the "Podcast receiver" page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/podcastReceiverAdmin")
public class PodcastReceiverAdminController {
@Autowired
private PodcastService podcastService;
@RequestMapping(method = { RequestMethod.POST, RequestMethod.GET })
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);
}
}
}
}
@@ -0,0 +1,73 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.command.PodcastSettingsCommand;
import org.libresonic.player.service.PodcastService;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
/**
* Controller for the page used to administrate the Podcast receiver.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/podcastSettings")
public class PodcastSettingsController {
@Autowired
private SettingsService settingsService;
@Autowired
private PodcastService podcastService;
@RequestMapping(method = RequestMethod.GET)
protected String formBackingObject(Model model) 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());
model.addAttribute("command",command);
return "podcastSettings";
}
@RequestMapping(method = RequestMethod.POST)
protected String doSubmitAction(@ModelAttribute PodcastSettingsCommand command, RedirectAttributes redirectAttributes) throws Exception {
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();
redirectAttributes.addFlashAttribute("settings_toast", true);
return "redirect:podcastSettings.view";
}
}
@@ -0,0 +1,76 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
/**
* A proxy for external HTTP requests.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/proxy")
public class ProxyController {
@RequestMapping(method = RequestMethod.GET)
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
String url = ServletRequestUtils.getRequiredStringParameter(request, "url");
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(15000)
.setSocketTimeout(15000)
.build();
HttpGet method = new HttpGet(url);
method.setConfig(requestConfig);
InputStream in = null;
try (CloseableHttpClient client = HttpClients.createDefault()) {
try (CloseableHttpResponse 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);
}
return null;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,258 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.domain.*;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
/**
* Controller for the creating a random play queue.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/randomPlayQueue.view")
public class RandomPlayQueueController {
@Autowired
private PlayerService playerService;
@Autowired
private MediaFileService mediaFileService;
@Autowired
private SecurityService securityService;
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.POST)
protected String handleRandomPlayQueue(
ModelMap model,
HttpServletRequest request,
HttpServletResponse response,
@RequestParam(value = "size") int size,
@RequestParam(value = "genre", required = false) String genre,
@RequestParam(value = "year", required = false) String year,
@RequestParam(value = "songRating", required = false) String songRating,
@RequestParam(value = "lastPlayedValue", required = false) String lastPlayedValue,
@RequestParam(value = "lastPlayedComp", required = false) String lastPlayedComp,
@RequestParam(value = "albumRatingValue", required = false) Integer albumRatingValue,
@RequestParam(value = "albumRatingComp", required = false) String albumRatingComp,
@RequestParam(value = "playCountValue", required = false) Integer playCountValue,
@RequestParam(value = "playCountComp", required = false) String playCountComp,
@RequestParam(value = "format", required = false) String format,
@RequestParam(value = "autoRandom", required = false) String autoRandom
) throws Exception {
Integer fromYear = null;
Integer toYear = null;
Integer minAlbumRating = null;
Integer maxAlbumRating = null;
Integer minPlayCount = null;
Integer maxPlayCount = null;
Date minLastPlayedDate = null;
Date maxLastPlayedDate = null;
boolean doesShowStarredSongs = false;
boolean doesShowUnstarredSongs = false;
// Handle the genre filter
if (StringUtils.equalsIgnoreCase("any", genre)) {
genre = null;
}
// Handle the release year filter
if (!StringUtils.equalsIgnoreCase("any", year)) {
String[] tmp = StringUtils.split(year);
fromYear = Integer.parseInt(tmp[0]);
toYear = Integer.parseInt(tmp[1]);
}
// Handle the song rating filter
if (StringUtils.equalsIgnoreCase("any", songRating)) {
doesShowStarredSongs = true;
doesShowUnstarredSongs = true;
} else if (StringUtils.equalsIgnoreCase("starred", songRating)) {
doesShowStarredSongs = true;
doesShowUnstarredSongs = false;
} else if (StringUtils.equalsIgnoreCase("unstarred", songRating)) {
doesShowStarredSongs = false;
doesShowUnstarredSongs = true;
}
// Handle the last played date filter
Calendar lastPlayed = Calendar.getInstance();
lastPlayed.setTime(new Date());
switch (lastPlayedValue) {
case "any":
lastPlayed = null;
break;
case "1day":
lastPlayed.add(Calendar.DAY_OF_YEAR, -1);
break;
case "1week":
lastPlayed.add(Calendar.WEEK_OF_YEAR, -1);
break;
case "1month":
lastPlayed.add(Calendar.MONTH, -1);
break;
case "3months":
lastPlayed.add(Calendar.MONTH, -3);
break;
case "6months":
lastPlayed.add(Calendar.MONTH, -6);
break;
case "1year":
lastPlayed.add(Calendar.YEAR, -1);
break;
}
if (lastPlayed != null) {
switch (lastPlayedComp) {
case "lt":
minLastPlayedDate = null;
maxLastPlayedDate = lastPlayed.getTime();
break;
case "gt":
minLastPlayedDate = lastPlayed.getTime();
maxLastPlayedDate = null;
break;
}
}
// Handle the album rating filter
if (albumRatingValue != null) {
switch (albumRatingComp) {
case "lt":
minAlbumRating = null;
maxAlbumRating = albumRatingValue - 1;
break;
case "gt":
minAlbumRating = albumRatingValue + 1;
maxAlbumRating = null;
break;
case "le":
minAlbumRating = null;
maxAlbumRating = albumRatingValue;
break;
case "ge":
minAlbumRating = albumRatingValue;
maxAlbumRating = null;
break;
case "eq":
minAlbumRating = albumRatingValue;
maxAlbumRating = albumRatingValue;
break;
}
}
// Handle the play count filter
if (playCountValue != null) {
switch (playCountComp) {
case "lt":
minPlayCount = null;
maxPlayCount = playCountValue - 1;
break;
case "gt":
minPlayCount = playCountValue + 1;
maxPlayCount = null;
break;
case "le":
minPlayCount = null;
maxPlayCount = playCountValue;
break;
case "ge":
minPlayCount = playCountValue;
maxPlayCount = null;
break;
case "eq":
minPlayCount = playCountValue;
maxPlayCount = playCountValue;
break;
}
}
// Handle the format filter
if (StringUtils.equalsIgnoreCase(format, "any")) format = null;
// Handle the music folder filter
List<MusicFolder> musicFolders = getMusicFolders(request);
// Do we add to the current playlist or do we replace it?
boolean shouldAddToPlayList = request.getParameter("addToPlaylist") != null;
// Search the database using these criteria
RandomSearchCriteria criteria = new RandomSearchCriteria(
size,
genre,
fromYear,
toYear,
musicFolders,
minLastPlayedDate,
maxLastPlayedDate,
minAlbumRating,
maxAlbumRating,
minPlayCount,
maxPlayCount,
doesShowStarredSongs,
doesShowUnstarredSongs,
format
);
User user = securityService.getCurrentUser(request);
Player player = playerService.getPlayer(request, response);
PlayQueue playQueue = player.getPlayQueue();
playQueue.addFiles(shouldAddToPlayList, mediaFileService.getRandomSongs(criteria, user.getUsername()));
if (autoRandom != null) {
playQueue.setRandomSearchCriteria(criteria);
}
// Render the 'reload' view to reload the play queue and the main page
List<ReloadFrame> reloadFrames = new ArrayList<>();
reloadFrames.add(new ReloadFrame("playQueue", "playQueue.view?"));
reloadFrames.add(new ReloadFrame("main", "more.view"));
Map<String, Object> map = new HashMap<>();
map.put("reloadFrames", reloadFrames);
model.addAttribute("model", map);
return "reload";
}
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);
}
}
@@ -0,0 +1,164 @@
package org.libresonic.player.controller;
import net.tanesha.recaptcha.ReCaptcha;
import net.tanesha.recaptcha.ReCaptchaFactory;
import net.tanesha.recaptcha.ReCaptchaResponse;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* Spring MVC Controller that serves the login page.
*/
@Controller
@RequestMapping("/recover")
public class RecoverController {
private static final Logger LOG = LoggerFactory.getLogger(RecoverController.class);
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@RequestMapping(method = {RequestMethod.GET, RequestMethod.POST})
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 User getUserByUsernameOrEmail(String usernameOrEmail) {
if (usernameOrEmail != null) {
User user = securityService.getUserByName(usernameOrEmail);
if (user != null) {
return user;
}
return securityService.getUserByEmail(usernameOrEmail);
}
return null;
}
/*
* e-mail user new password via configured Smtp server
*/
private boolean emailPassword(String password, String username, String email) {
/* Default to protocol smtp when SmtpEncryption is set to "None" */
String prot = "smtp";
if (settingsService.getSmtpServer() == null || settingsService.getSmtpServer().isEmpty()) {
LOG.warn("Can not send email; no Smtp server configured.");
return false;
}
Properties props = new Properties();
if (settingsService.getSmtpEncryption().equals("SSL/TLS")) {
prot = "smtps";
props.put("mail." + prot + ".ssl.enable", "true");
} else if (settingsService.getSmtpEncryption().equals("STARTTLS")) {
prot = "smtp";
props.put("mail." + prot + ".starttls.enable", "true");
}
props.put("mail." + prot + ".host", settingsService.getSmtpServer());
props.put("mail." + prot + ".port", settingsService.getSmtpPort());
/* use authentication when SmtpUser is configured */
if (settingsService.getSmtpUser() != null && !settingsService.getSmtpUser().isEmpty()) {
props.put("mail." + prot + ".auth", "true");
}
Session session = Session.getInstance(props, null);
try {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(settingsService.getSmtpFrom()));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(email));
message.setSubject("Libresonic Password");
message.setText("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" +
"Your Libresonic server\n" +
"libresonic.org");
message.setSentDate(new Date());
Transport trans = session.getTransport(prot);
try {
if (props.get("mail." + prot + ".auth") != null && props.get("mail." + prot + ".auth").equals("true")) {
trans.connect(settingsService.getSmtpServer(), settingsService.getSmtpUser(), settingsService.getSmtpPassword());
} else {
trans.connect();
}
trans.sendMessage(message, message.getAllRecipients());
} finally {
trans.close();
}
return true;
} catch (Exception x) {
LOG.warn("Failed to send email.", x);
return false;
}
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, 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,76 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.UserSettings;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.VersionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for the right frame.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/right")
public class RightController {
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@Autowired
private VersionService versionService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request) throws Exception {
Map<String, Object> map = new HashMap<>();
ModelAndView result = new ModelAndView("right");
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("user", securityService.getCurrentUser(request));
result.addObject("model", map);
return result;
}
}
@@ -0,0 +1,104 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.command.SearchCommand;
import org.libresonic.player.domain.*;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SearchService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* Controller for the search page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/search")
public class SearchController {
private static final int MATCH_COUNT = 25;
@Autowired
private SecurityService securityService;
@Autowired
private SettingsService settingsService;
@Autowired
private PlayerService playerService;
@Autowired
private SearchService searchService;
@RequestMapping(method = RequestMethod.GET)
protected String displayForm() throws Exception {
return "search";
}
@ModelAttribute
protected void formBackingObject(HttpServletRequest request, Model model) throws Exception {
model.addAttribute("command",new SearchCommand());
}
@RequestMapping(method = RequestMethod.POST)
protected String onSubmit(HttpServletRequest request, HttpServletResponse response,@ModelAttribute("command") SearchCommand command, Model model)
throws Exception {
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 "search";
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
/**
* Controller for updating music file metadata.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/setMusicFileInfo")
public class SetMusicFileInfoController {
@Autowired
private MediaFileService mediaFileService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request) 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));
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.RatingService;
import org.libresonic.player.service.SecurityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
/**
* Controller for updating music file ratings.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/setRating")
public class SetRatingController {
@Autowired
private RatingService ratingService;
@Autowired
private SecurityService securityService;
@Autowired
private MediaFileService mediaFileService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request) 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));
}
}
@@ -0,0 +1,56 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.SecurityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
/**
* Controller for the main settings page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/settings")
public class SettingsController {
@Autowired
private SecurityService securityService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request) throws Exception {
User user = securityService.getCurrentUser(request);
// Redirect to music folder settings if admin.
String view = user.isAdminRole() ? "musicFolderSettings" : "personalSettings";
return new ModelAndView(new RedirectView(view));
}
}
@@ -0,0 +1,127 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.PlayQueue;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.Share;
import org.libresonic.player.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
/**
* Controller for sharing music on Twitter, Facebook etc.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/createShare")
public class ShareManagementController {
@Autowired
private MediaFileService mediaFileService;
@Autowired
private SettingsService settingsService;
@Autowired
private ShareService shareService;
@Autowired
private PlayerService playerService;
@Autowired
private PlaylistService playlistService;
@Autowired
private SecurityService securityService;
@RequestMapping(method = RequestMethod.GET)
public ModelAndView createShare(HttpServletRequest request, HttpServletResponse response) throws Exception {
List<MediaFile> files = getMediaFiles(request);
MediaFile dir = null;
if (!files.isEmpty()) {
dir = files.get(0);
if (!dir.isAlbum()) {
dir = mediaFileService.getParentOf(dir);
}
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("dir", dir);
map.put("user", securityService.getCurrentUser(request));
Share share = shareService.createShare(request, files);
String description = getDescription(request);
if (description != null) {
share.setDescription(description);
shareService.updateShare(share);
}
map.put("playUrl", shareService.getShareUrl(request, share));
return new ModelAndView("createShare", "model", map);
}
private String getDescription(HttpServletRequest request) throws ServletRequestBindingException {
Integer playlistId = ServletRequestUtils.getIntParameter(request, "playlist");
return playlistId == null ? null : playlistService.getPlaylist(playlistId).getName();
}
private List<MediaFile> getMediaFiles(HttpServletRequest request) throws Exception {
Integer id = ServletRequestUtils.getIntParameter(request, "id");
String playerId = request.getParameter("player");
Integer playlistId = ServletRequestUtils.getIntParameter(request, "playlist");
List<MediaFile> result = new ArrayList<>();
if (id != null) {
MediaFile album = mediaFileService.getMediaFile(id);
int[] indexes = ServletRequestUtils.getIntParameters(request, "i");
if (indexes.length == 0) {
return Arrays.asList(album);
}
List<MediaFile> children = mediaFileService.getChildrenOf(album, true, false, true);
for (int index : indexes) {
result.add(children.get(index));
}
}
else if (playerId != null) {
Player player = playerService.getPlayerById(playerId);
PlayQueue playQueue = player.getPlayQueue();
result = playQueue.getFiles();
}
else if (playlistId != null) {
result = playlistService.getFilesInPlaylist(playlistId);
}
return result;
}
}
@@ -0,0 +1,170 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.domain.MediaFile;
import org.libresonic.player.domain.MusicFolder;
import org.libresonic.player.domain.Share;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.MediaFileService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.ShareService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
* Controller for the page used to administrate the set of shared media.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/shareSettings")
public class ShareSettingsController {
@Autowired
private ShareService shareService;
@Autowired
private SecurityService securityService;
@Autowired
private MediaFileService mediaFileService;
@Autowired
private SettingsService settingsService;
@RequestMapping(method = RequestMethod.GET)
public String doGet(HttpServletRequest request, Model model) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put("shareInfos", getShareInfos(request));
map.put("user", securityService.getCurrentUser(request));
model.addAttribute("model", map);
return "shareSettings";
}
@RequestMapping(method = RequestMethod.POST)
public String doPost(HttpServletRequest request, RedirectAttributes redirectAttributes) throws Exception {
handleParameters(request);
redirectAttributes.addFlashAttribute("settings_toast", true);
return "redirect:shareSettings.view";
}
private void handleParameters(HttpServletRequest request) {
User user = securityService.getCurrentUser(request);
for (Share share : shareService.getSharesForUser(user)) {
int id = share.getId();
String description = getParameter(request, "description", id);
boolean delete = getParameter(request, "delete", id) != null;
String expireIn = getParameter(request, "expireIn", id);
if (delete) {
shareService.deleteShare(id);
} else {
if (expireIn != null) {
share.setExpires(parseExpireIn(expireIn));
}
share.setDescription(description);
shareService.updateShare(share);
}
}
boolean deleteExpired = ServletRequestUtils.getBooleanParameter(request, "deleteExpired", false);
if (deleteExpired) {
Date now = new Date();
for (Share share : shareService.getSharesForUser(user)) {
Date expires = share.getExpires();
if (expires != null && expires.before(now)) {
shareService.deleteShare(share.getId());
}
}
}
}
private List<ShareInfo> getShareInfos(HttpServletRequest request) {
List<ShareInfo> result = new ArrayList<ShareInfo>();
User user = securityService.getCurrentUser(request);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(user.getUsername());
for (Share share : shareService.getSharesForUser(user)) {
List<MediaFile> files = shareService.getSharedFiles(share.getId(), musicFolders);
if (!files.isEmpty()) {
MediaFile file = files.get(0);
result.add(new ShareInfo(shareService.getShareUrl(request, share), share, file.isDirectory() ? file :
mediaFileService
.getParentOf(file)));
}
}
return result;
}
private String getParameter(HttpServletRequest request, String name, int id) {
return StringUtils.trimToNull(request.getParameter(name + "[" + id + "]"));
}
private Date parseExpireIn(String expireIn) {
int days = Integer.parseInt(expireIn);
if (days == 0) {
return null;
}
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_YEAR, days);
return calendar.getTime();
}
public static class ShareInfo {
private final String shareUrl;
private final Share share;
private final MediaFile dir;
public ShareInfo(String shareUrl, Share share, MediaFile dir) {
this.shareUrl = shareUrl;
this.share = share;
this.dir = dir;
}
public Share getShare() {
return share;
}
public String getShareUrl() {
return shareUrl;
}
public MediaFile getDir() {
return dir;
}
}
}
@@ -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 2015 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.lang.StringUtils;
import org.libresonic.player.service.NetworkService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.SonosService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* Controller for the page used to administrate the Sonos music service settings.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/sonosSettings")
public class SonosSettingsController {
@Autowired
private SettingsService settingsService;
@Autowired
private SonosService sonosService;
@RequestMapping(method = RequestMethod.GET)
public String doGet(Model model) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put("sonosEnabled", settingsService.isSonosEnabled());
map.put("sonosServiceName", settingsService.getSonosServiceName());
model.addAttribute("model", map);
return "sonosSettings";
}
@RequestMapping(method = RequestMethod.POST)
public String doPost(HttpServletRequest request, RedirectAttributes redirectAttributes) throws Exception {
handleParameters(request);
redirectAttributes.addFlashAttribute("settings_toast", true);
return "redirect:sonosSettings.view";
}
private void handleParameters(HttpServletRequest request) {
boolean sonosEnabled = ServletRequestUtils.getBooleanParameter(request, "sonosEnabled", false);
String sonosServiceName = StringUtils.trimToNull(request.getParameter("sonosServiceName"));
if (sonosServiceName == null) {
sonosServiceName = "Libresonic";
}
settingsService.setSonosEnabled(sonosEnabled);
settingsService.setSonosServiceName(sonosServiceName);
settingsService.save();
sonosService.setMusicServiceEnabled(false, NetworkService.getBaseUrl(request));
sonosService.setMusicServiceEnabled(sonosEnabled, NetworkService.getBaseUrl(request));
}
public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
}
public void setSonosService(SonosService sonosService) {
this.sonosService = sonosService;
}
}
@@ -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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.dao.MediaFileDao;
import org.libresonic.player.domain.*;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller for showing a user's starred items.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/starred")
public class StarredController {
@Autowired
private PlayerService playerService;
@Autowired
private MediaFileDao mediaFileDao;
@Autowired
private SecurityService securityService;
@Autowired
private SettingsService settingsService;
@Autowired
private MediaFileService mediaFileService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
User user = securityService.getCurrentUser(request);
String username = user.getUsername();
UserSettings userSettings = settingsService.getUserSettings(username);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<MediaFile> artists = mediaFileDao.getStarredDirectories(0, Integer.MAX_VALUE, username, musicFolders);
List<MediaFile> albums = mediaFileDao.getStarredAlbums(0, Integer.MAX_VALUE, username, musicFolders);
List<MediaFile> files = mediaFileDao.getStarredFiles(0, Integer.MAX_VALUE, username, musicFolders);
mediaFileService.populateStarredDate(artists, username);
mediaFileService.populateStarredDate(albums, username);
mediaFileService.populateStarredDate(files, username);
List<MediaFile> songs = new ArrayList<>();
List<MediaFile> videos = new ArrayList<>();
for (MediaFile file : files) {
(file.isVideo() ? videos : songs).add(file);
}
map.put("user", user);
map.put("partyModeEnabled", userSettings.isPartyModeEnabled());
map.put("player", playerService.getPlayer(request, response));
map.put("coverArtSize", CoverArtScheme.MEDIUM.getSize());
map.put("artists", artists);
map.put("albums", albums);
map.put("songs", songs);
map.put("videos", videos);
return new ModelAndView("starred","model",map);
}
}
@@ -0,0 +1,161 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.data.Range;
import org.jfree.data.time.*;
import org.libresonic.player.domain.TransferStatus;
import org.libresonic.player.service.StatusService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
* Controller for generating a chart showing bitrate vs time.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/statusChart")
public class StatusChartController extends AbstractChartController {
@Autowired
private StatusService statusService;
public static final int IMAGE_WIDTH = 350;
public static final int IMAGE_HEIGHT = 150;
@RequestMapping(method = RequestMethod.GET)
public synchronized ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
String type = request.getParameter("type");
int index = Integer.parseInt(request.getParameter("index"));
List<TransferStatus> statuses = Collections.emptyList();
if ("stream".equals(type)) {
statuses = statusService.getAllStreamStatuses();
} else if ("download".equals(type)) {
statuses = statusService.getAllDownloadStatuses();
} else if ("upload".equals(type)) {
statuses = statusService.getAllUploadStatuses();
}
if (index < 0 || index >= statuses.size()) {
return null;
}
TransferStatus status = statuses.get(index);
TimeSeries series = new TimeSeries("Kbps", Millisecond.class);
TransferStatus.SampleHistory history = status.getHistory();
long to = System.currentTimeMillis();
long from = to - status.getHistoryLengthMillis();
Range range = new DateRange(from, to);
if (!history.isEmpty()) {
TransferStatus.Sample previous = history.get(0);
for (int i = 1; i < history.size(); i++) {
TransferStatus.Sample sample = history.get(i);
long elapsedTimeMilis = sample.getTimestamp() - previous.getTimestamp();
long bytesStreamed = Math.max(0L, sample.getBytesTransfered() - previous.getBytesTransfered());
double kbps = (8.0 * bytesStreamed / 1024.0) / (elapsedTimeMilis / 1000.0);
series.addOrUpdate(new Millisecond(new Date(sample.getTimestamp())), kbps);
previous = sample;
}
}
// Compute moving average.
series = MovingAverage.createMovingAverage(series, "Kbps", 20000, 5000);
// Find min and max values.
double min = 100;
double max = 250;
for (Object obj : series.getItems()) {
TimeSeriesDataItem item = (TimeSeriesDataItem) obj;
double value = item.getValue().doubleValue();
if (item.getPeriod().getFirstMillisecond() > from) {
min = Math.min(min, value);
max = Math.max(max, value);
}
}
// Add 10% to max value.
max *= 1.1D;
// Subtract 10% from min value.
min *= 0.9D;
TimeSeriesCollection dataset = new TimeSeriesCollection();
dataset.addSeries(series);
JFreeChart chart = ChartFactory.createTimeSeriesChart(null, null, null, dataset, false, false, false);
XYPlot plot = (XYPlot) chart.getPlot();
plot.setRangeAxisLocation(AxisLocation.BOTTOM_OR_RIGHT);
Paint background = new GradientPaint(0, 0, Color.lightGray, 0, IMAGE_HEIGHT, Color.white);
plot.setBackgroundPaint(background);
XYItemRenderer renderer = plot.getRendererForDataset(dataset);
renderer.setSeriesPaint(0, Color.blue.darker());
renderer.setSeriesStroke(0, new BasicStroke(2f));
// Set theme-specific colors.
Color bgColor = getBackground(request);
Color fgColor = getForeground(request);
chart.setBackgroundPaint(bgColor);
ValueAxis domainAxis = plot.getDomainAxis();
domainAxis.setRange(range);
domainAxis.setTickLabelPaint(fgColor);
domainAxis.setTickMarkPaint(fgColor);
domainAxis.setAxisLinePaint(fgColor);
ValueAxis rangeAxis = plot.getRangeAxis();
rangeAxis.setRange(new Range(min, max));
rangeAxis.setTickLabelPaint(fgColor);
rangeAxis.setTickMarkPaint(fgColor);
rangeAxis.setAxisLinePaint(fgColor);
ChartUtilities.writeChartAsPNG(response.getOutputStream(), chart, IMAGE_WIDTH, IMAGE_HEIGHT);
return null;
}
}
@@ -0,0 +1,140 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.libresonic.player.domain.Player;
import org.libresonic.player.domain.TransferStatus;
import org.libresonic.player.service.StatusService;
import org.libresonic.player.util.FileUtil;
import org.libresonic.player.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
/**
* Controller for the status page.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping("/status")
public class StatusController {
@Autowired
private StatusService statusService;
@RequestMapping(method = RequestMethod.GET)
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
List<TransferStatus> streamStatuses = statusService.getAllStreamStatuses();
List<TransferStatus> downloadStatuses = statusService.getAllDownloadStatuses();
List<TransferStatus> uploadStatuses = statusService.getAllUploadStatuses();
Locale locale = RequestContextUtils.getLocale(request);
List<TransferStatusHolder> transferStatuses = new ArrayList<>();
for (int i = 0; i < streamStatuses.size(); i++) {
long minutesAgo = streamStatuses.get(i).getMillisSinceLastUpdate() / 1000L / 60L;
if (minutesAgo < 60L) {
transferStatuses.add(new TransferStatusHolder(streamStatuses.get(i), true, false, false, i, locale));
}
}
for (int i = 0; i < downloadStatuses.size(); i++) {
transferStatuses.add(new TransferStatusHolder(downloadStatuses.get(i), false, true, false, i, locale));
}
for (int i = 0; i < uploadStatuses.size(); i++) {
transferStatuses.add(new TransferStatusHolder(uploadStatuses.get(i), false, false, true, i, locale));
}
map.put("transferStatuses", transferStatuses);
map.put("chartWidth", StatusChartController.IMAGE_WIDTH);
map.put("chartHeight", StatusChartController.IMAGE_HEIGHT);
return new ModelAndView("status","model",map);
}
public static class TransferStatusHolder {
private TransferStatus transferStatus;
private boolean isStream;
private boolean isDownload;
private boolean isUpload;
private int index;
private Locale locale;
TransferStatusHolder(TransferStatus transferStatus, boolean isStream, boolean isDownload, boolean isUpload,
int index, Locale locale) {
this.transferStatus = transferStatus;
this.isStream = isStream;
this.isDownload = isDownload;
this.isUpload = isUpload;
this.index = index;
this.locale = locale;
}
public boolean isStream() {
return isStream;
}
public boolean isDownload() {
return isDownload;
}
public boolean isUpload() {
return isUpload;
}
public int getIndex() {
return index;
}
public Player getPlayer() {
return transferStatus.getPlayer();
}
public String getPlayerType() {
Player player = transferStatus.getPlayer();
return player == null ? null : player.getType();
}
public String getUsername() {
Player player = transferStatus.getPlayer();
return player == null ? null : player.getUsername();
}
public String getPath() {
return FileUtil.getShortPath(transferStatus.getFile());
}
public String getBytes() {
return StringUtil.formatBytes(transferStatus.getBytesTransfered(), locale);
}
}
}
@@ -0,0 +1,428 @@
/*
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 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.io.IOUtils;
import org.libresonic.player.domain.*;
import org.libresonic.player.io.PlayQueueInputStream;
import org.libresonic.player.io.RangeOutputStream;
import org.libresonic.player.io.ShoutCastOutputStream;
import org.libresonic.player.security.JWTAuthenticationToken;
import org.libresonic.player.service.*;
import org.libresonic.player.service.sonos.SonosHelper;
import org.libresonic.player.util.HttpRange;
import org.libresonic.player.util.StringUtil;
import org.libresonic.player.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A controller which streams the content of a {@link org.libresonic.player.domain.PlayQueue} to a remote
* {@link Player}.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping(value = {"/stream/**", "/ext/stream/**"})
public class StreamController {
private static final Logger LOG = LoggerFactory.getLogger(StreamController.class);
@Autowired
private StatusService statusService;
@Autowired
private PlayerService playerService;
@Autowired
private PlaylistService playlistService;
@Autowired
private SecurityService securityService;
@Autowired
private SettingsService settingsService;
@Autowired
private TranscodingService transcodingService;
@Autowired
private AudioScrobblerService audioScrobblerService;
@Autowired
private MediaFileService mediaFileService;
@Autowired
private SearchService searchService;
@RequestMapping(method = RequestMethod.GET)
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
TransferStatus status = null;
PlayQueueInputStream in = null;
Player player = playerService.getPlayer(request, response, false, true);
User user = securityService.getUserByName(player.getUsername());
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
try {
if (!(authentication instanceof JWTAuthenticationToken) && !user.isStreamRole()) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Streaming is forbidden for user " + user.getUsername());
return null;
}
// If "playlist" request parameter is set, this is a Podcast request. In that case, create a separate
// play queue (in order to support multiple parallel Podcast streams).
Integer playlistId = ServletRequestUtils.getIntParameter(request, "playlist");
boolean isPodcast = playlistId != null;
if (isPodcast) {
PlayQueue playQueue = new PlayQueue();
playQueue.addFiles(false, playlistService.getFilesInPlaylist(playlistId));
player.setPlayQueue(playQueue);
Util.setContentLength(response, playQueue.length());
LOG.info("Incoming Podcast request for playlist " + playlistId);
}
response.setHeader("Access-Control-Allow-Origin", "*");
String contentType = StringUtil.getMimeType(request.getParameter("suffix"));
response.setContentType(contentType);
String preferredTargetFormat = request.getParameter("format");
Integer maxBitRate = ServletRequestUtils.getIntParameter(request, "maxBitRate");
if (Integer.valueOf(0).equals(maxBitRate)) {
maxBitRate = null;
}
VideoTranscodingSettings videoTranscodingSettings = null;
// Is this a request for a single file (typically from the embedded Flash player)?
// In that case, create a separate playlist (in order to support multiple parallel streams).
// Also, enable partial download (HTTP byte range).
MediaFile file = getSingleFile(request);
boolean isSingleFile = file != null;
HttpRange range = null;
if (isSingleFile) {
if (!(authentication instanceof JWTAuthenticationToken) && !securityService.isFolderAccessAllowed(file, user.getUsername())) {
response.sendError(HttpServletResponse.SC_FORBIDDEN,
"Access to file " + file.getId() + " is forbidden for user " + user.getUsername());
return null;
}
// Update the index of the currently playing media file. At
// this point we haven't yet modified the play queue to support
// multiple streams, so the current play queue is the real one.
int currentIndex = player.getPlayQueue().getFiles().indexOf(file);
player.getPlayQueue().setIndex(currentIndex);
// Create a new, fake play queue that only contains the
// currently playing media file, in case multiple streams want
// to use the same player.
PlayQueue playQueue = new PlayQueue();
playQueue.addFiles(true, file);
player.setPlayQueue(playQueue);
if (!file.isVideo()) {
response.setIntHeader("ETag", file.getId());
response.setHeader("Accept-Ranges", "bytes");
}
TranscodingService.Parameters parameters = transcodingService.getParameters(file, player, maxBitRate, preferredTargetFormat, null);
long fileLength = getFileLength(parameters);
boolean isConversion = parameters.isDownsample() || parameters.isTranscode();
boolean estimateContentLength = ServletRequestUtils.getBooleanParameter(request, "estimateContentLength", false);
boolean isHls = ServletRequestUtils.getBooleanParameter(request, "hls", false);
range = getRange(request, file);
if (range != null && !file.isVideo()) {
LOG.info("Got HTTP range: " + range);
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
Util.setContentLength(response, range.isClosed() ? range.size() : fileLength - range.getFirstBytePos());
long lastBytePos = range.getLastBytePos() != null ? range.getLastBytePos() : fileLength - 1;
response.setHeader("Content-Range", "bytes " + range.getFirstBytePos() + "-" + lastBytePos + "/" + fileLength);
} else if (!isHls && (!isConversion || estimateContentLength)) {
Util.setContentLength(response, fileLength);
}
if (isHls) {
response.setContentType(StringUtil.getMimeType("ts")); // HLS is always MPEG TS.
} else {
String transcodedSuffix = transcodingService.getSuffix(player, file, preferredTargetFormat);
boolean sonos = SonosHelper.LIBRESONIC_CLIENT_ID.equals(player.getClientId());
response.setContentType(StringUtil.getMimeType(transcodedSuffix, sonos));
setContentDuration(response, file);
}
if (file.isVideo() || isHls) {
videoTranscodingSettings = createVideoTranscodingSettings(file, request);
}
}
if (request.getMethod().equals("HEAD")) {
return null;
}
// Terminate any other streams to this player.
if (!isPodcast && !isSingleFile) {
for (TransferStatus streamStatus : statusService.getStreamStatusesForPlayer(player)) {
if (streamStatus.isActive()) {
streamStatus.terminate();
}
}
}
status = statusService.createStreamStatus(player);
in = new PlayQueueInputStream(player, status, maxBitRate, preferredTargetFormat, videoTranscodingSettings, transcodingService,
audioScrobblerService, mediaFileService, searchService);
OutputStream out = RangeOutputStream.wrap(response.getOutputStream(), range);
// Enabled SHOUTcast, if requested.
boolean isShoutCastRequested = "1".equals(request.getHeader("icy-metadata"));
if (isShoutCastRequested && !isSingleFile) {
response.setHeader("icy-metaint", "" + ShoutCastOutputStream.META_DATA_INTERVAL);
response.setHeader("icy-notice1", "This stream is served using Libresonic");
response.setHeader("icy-notice2", "Libresonic - Free media streamer - libresonic.org");
response.setHeader("icy-name", "Libresonic");
response.setHeader("icy-genre", "Mixed");
response.setHeader("icy-url", "http://libresonic.org/");
out = new ShoutCastOutputStream(out, player.getPlayQueue(), settingsService);
}
final int BUFFER_SIZE = 2048;
byte[] buf = new byte[BUFFER_SIZE];
while (true) {
// Check if stream has been terminated.
if (status.terminated()) {
return null;
}
if (player.getPlayQueue().getStatus() == PlayQueue.Status.STOPPED) {
if (isPodcast || isSingleFile) {
break;
} else {
sendDummy(buf, out);
}
} else {
int n = in.read(buf);
if (n == -1) {
if (isPodcast || isSingleFile) {
break;
} else {
sendDummy(buf, out);
}
} else {
out.write(buf, 0, n);
}
}
}
} finally {
if (status != null) {
securityService.updateUserByteCounts(user, status.getBytesTransfered(), 0L, 0L);
statusService.removeStreamStatus(status);
}
IOUtils.closeQuietly(in);
}
return null;
}
private void setContentDuration(HttpServletResponse response, MediaFile file) {
if (file.getDurationSeconds() != null) {
response.setHeader("X-Content-Duration", String.format("%.1f", file.getDurationSeconds().doubleValue()));
}
}
private MediaFile getSingleFile(HttpServletRequest request) throws ServletRequestBindingException {
String path = request.getParameter("path");
if (path != null) {
return mediaFileService.getMediaFile(path);
}
Integer id = ServletRequestUtils.getIntParameter(request, "id");
if (id != null) {
return mediaFileService.getMediaFile(id);
}
return null;
}
private long getFileLength(TranscodingService.Parameters parameters) {
MediaFile file = parameters.getMediaFile();
if (!parameters.isDownsample() && !parameters.isTranscode()) {
return file.getFileSize();
}
Integer duration = file.getDurationSeconds();
Integer maxBitRate = parameters.getMaxBitRate();
if (duration == null) {
LOG.warn("Unknown duration for " + file + ". Unable to estimate transcoded size.");
return file.getFileSize();
}
if (maxBitRate == null) {
LOG.error("Unknown bit rate for " + file + ". Unable to estimate transcoded size.");
return file.getFileSize();
}
return duration * maxBitRate * 1000L / 8L;
}
private HttpRange getRange(HttpServletRequest request, MediaFile file) {
// First, look for "Range" HTTP header.
HttpRange range = HttpRange.valueOf(request.getHeader("Range"));
if (range != null) {
return range;
}
// Second, look for "offsetSeconds" request parameter.
String offsetSeconds = request.getParameter("offsetSeconds");
range = parseAndConvertOffsetSeconds(offsetSeconds, file);
if (range != null) {
return range;
}
return null;
}
private HttpRange parseAndConvertOffsetSeconds(String offsetSeconds, MediaFile file) {
if (offsetSeconds == null) {
return null;
}
try {
Integer duration = file.getDurationSeconds();
Long fileSize = file.getFileSize();
if (duration == null || fileSize == null) {
return null;
}
float offset = Float.parseFloat(offsetSeconds);
// Convert from time offset to byte offset.
long byteOffset = (long) (fileSize * (offset / duration));
return new HttpRange(byteOffset, null);
} catch (Exception x) {
LOG.error("Failed to parse and convert time offset: " + offsetSeconds, x);
return null;
}
}
private VideoTranscodingSettings createVideoTranscodingSettings(MediaFile file, HttpServletRequest request) throws ServletRequestBindingException {
Integer existingWidth = file.getWidth();
Integer existingHeight = file.getHeight();
Integer maxBitRate = ServletRequestUtils.getIntParameter(request, "maxBitRate");
int timeOffset = ServletRequestUtils.getIntParameter(request, "timeOffset", 0);
int defaultDuration = file.getDurationSeconds() == null ? Integer.MAX_VALUE : file.getDurationSeconds() - timeOffset;
int duration = ServletRequestUtils.getIntParameter(request, "duration", defaultDuration);
boolean hls = ServletRequestUtils.getBooleanParameter(request, "hls", false);
Dimension dim = getRequestedVideoSize(request.getParameter("size"));
if (dim == null) {
dim = getSuitableVideoSize(existingWidth, existingHeight, maxBitRate);
}
return new VideoTranscodingSettings(dim.width, dim.height, timeOffset, duration, hls);
}
protected Dimension getRequestedVideoSize(String sizeSpec) {
if (sizeSpec == null) {
return null;
}
Pattern pattern = Pattern.compile("^(\\d+)x(\\d+)$");
Matcher matcher = pattern.matcher(sizeSpec);
if (matcher.find()) {
int w = Integer.parseInt(matcher.group(1));
int h = Integer.parseInt(matcher.group(2));
if (w >= 0 && h >= 0 && w <= 2000 && h <= 2000) {
return new Dimension(w, h);
}
}
return null;
}
protected Dimension getSuitableVideoSize(Integer existingWidth, Integer existingHeight, Integer maxBitRate) {
if (maxBitRate == null) {
return new Dimension(400, 224);
}
int w;
if (maxBitRate < 400) {
w = 400;
} else if (maxBitRate < 600) {
w = 480;
} else if (maxBitRate < 1800) {
w = 640;
} else {
w = 960;
}
int h = even(w * 9 / 16);
if (existingWidth == null || existingHeight == null) {
return new Dimension(w, h);
}
if (existingWidth < w || existingHeight < h) {
return new Dimension(even(existingWidth), even(existingHeight));
}
double aspectRate = existingWidth.doubleValue() / existingHeight.doubleValue();
h = (int) Math.round(w / aspectRate);
return new Dimension(even(w), even(h));
}
// Make sure width and height are multiples of two, as some versions of ffmpeg require it.
private int even(int size) {
return size + (size % 2);
}
/**
* Feed the other end with some dummy data to keep it from reconnecting.
*/
private void sendDummy(byte[] buf, OutputStream out) throws IOException {
try {
Thread.sleep(2000);
} catch (InterruptedException x) {
LOG.warn("Interrupted in sleep.", x);
}
Arrays.fill(buf, (byte) 0xFF);
out.write(buf);
out.flush();
}
}

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