aboutsummaryrefslogtreecommitdiff
path: root/subsonic-main/src/main/webapp/WEB-INF
diff options
context:
space:
mode:
authorScott Jackson <daneren2005@gmail.com>2012-07-02 21:24:02 -0700
committerScott Jackson <daneren2005@gmail.com>2012-07-02 21:24:02 -0700
commita1a18f77a50804e0127dfa4b0f5240c49c541184 (patch)
tree19a38880afe505beddb5590379a8134d7730a277 /subsonic-main/src/main/webapp/WEB-INF
parentb61d787706979e7e20f4c3c4f93c1f129d92273f (diff)
downloaddsub-a1a18f77a50804e0127dfa4b0f5240c49c541184.tar.gz
dsub-a1a18f77a50804e0127dfa4b0f5240c49c541184.tar.bz2
dsub-a1a18f77a50804e0127dfa4b0f5240c49c541184.zip
Initial Commit
Diffstat (limited to 'subsonic-main/src/main/webapp/WEB-INF')
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/applicationContext-cache.xml22
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/applicationContext-security.xml234
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/applicationContext-service.xml245
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/dwr.xml62
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/accessDenied.jsp22
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/advancedSettings.jsp142
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/allmusic.jsp16
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/avatarUploadResult.jsp35
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/changeCoverArt.jsp206
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/coverArt.jsp86
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/createShare.jsp51
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/db.jsp45
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/donate.jsp147
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/editTags.jsp164
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/externalPlayer.jsp99
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/generalSettings.jsp165
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/gettingStarted.jsp53
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/head.jsp11
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/help.jsp70
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/helpToolTip.jsp18
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/home.jsp189
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/importPlaylist.jsp37
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/include.jsp8
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/index.jsp26
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/internetRadioSettings.jsp62
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/jquery.jsp3
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/left.jsp168
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/login.jsp64
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/lyrics.jsp79
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/main.jsp479
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/more.jsp159
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/musicFolderSettings.jsp114
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/networkSettings.jsp107
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/notFound.jsp21
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/passwordSettings.jsp45
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/personalSettings.jsp228
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/playAddDownload.jsp64
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/playQueue.jsp617
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/playerSettings.jsp177
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/playlist.jsp235
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/podcast.jsp26
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/podcastReceiver.jsp269
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/podcastSettings.jsp88
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/rating.jsp51
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/recover.jsp34
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/reload.jsp11
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/rest/videoPlayer.jsp142
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/right.jsp191
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/search.jsp150
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/settingsHeader.jsp32
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/shareSettings.jsp72
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/starred.jsp131
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/status.jsp93
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/test.jsp20
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/top.jsp98
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/transcodingSettings.jsp70
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/upload.jsp29
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/userSettings.jsp201
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/videoPlayer.jsp190
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/wap/browse.jsp56
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/wap/head.jsp10
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/wap/index.jsp62
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/wap/loadPlaylist.jsp23
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/wap/playlist.jsp56
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/wap/search.jsp19
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/wap/searchResult.jsp30
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/wap/settings.jsp47
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/jsp/xspfPlaylist.jsp30
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/sub.tld114
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/subsonic-servlet.xml479
-rw-r--r--subsonic-main/src/main/webapp/WEB-INF/web.xml207
71 files changed, 7806 insertions, 0 deletions
diff --git a/subsonic-main/src/main/webapp/WEB-INF/applicationContext-cache.xml b/subsonic-main/src/main/webapp/WEB-INF/applicationContext-cache.xml
new file mode 100644
index 00000000..a8692d08
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/applicationContext-cache.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+
+ <bean id="cacheFactory" class="net.sourceforge.subsonic.cache.CacheFactory"/>
+
+ <bean id="userCache" factory-bean="cacheFactory" factory-method="getCache">
+ <constructor-arg value="userCache"/>
+ </bean>
+
+ <bean id="mediaFileMemoryCache" factory-bean="cacheFactory" factory-method="getCache">
+ <constructor-arg value="mediaFileMemoryCache"/>
+ </bean>
+
+ <bean id="musicFileMemoryCache" factory-bean="cacheFactory" factory-method="getCache">
+ <constructor-arg value="musicFileMemoryCache"/>
+ </bean>
+
+</beans>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/applicationContext-security.xml b/subsonic-main/src/main/webapp/WEB-INF/applicationContext-security.xml
new file mode 100644
index 00000000..8cf5e9c2
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/applicationContext-security.xml
@@ -0,0 +1,234 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
+ <property name="filterInvocationDefinitionSource">
+ <value>
+ PATTERN_TYPE_APACHE_ANT
+ /wap**=httpSessionContextIntegrationFilter,logoutFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,basicExceptionTranslationFilter,filterInvocationInterceptor
+ /podcastReceiver**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
+ /podcast**=httpSessionContextIntegrationFilter,logoutFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,basicExceptionTranslationFilter,filterInvocationInterceptor
+ /rest/**=httpSessionContextIntegrationFilter,logoutFilter,basicProcessingFilter,restRequestParameterProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,basicExceptionTranslationFilter,filterInvocationInterceptor
+ /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
+ </value>
+ </property>
+ </bean>
+
+ <bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>
+
+ <bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
+ <constructor-arg value="/login.view?logout"/>
+ <!-- URL redirected to after logout -->
+ <constructor-arg>
+ <list>
+ <ref bean="rememberMeServices"/>
+ <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ <bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
+ <property name="authenticationManager" ref="authenticationManager"/>
+ <property name="authenticationFailureUrl" value="/login.view?error"/>
+ <property name="defaultTargetUrl" value="/"/>
+ <property name="alwaysUseDefaultTargetUrl" value="true"/>
+ <property name="filterProcessesUrl" value="/j_acegi_security_check"/>
+ <property name="rememberMeServices" ref="rememberMeServices"/>
+ </bean>
+
+ <bean id="basicProcessingFilter" class="org.acegisecurity.ui.basicauth.BasicProcessingFilter">
+ <property name="authenticationManager" ref="authenticationManager"/>
+ <property name="authenticationEntryPoint" ref="basicProcessingFilterEntryPoint"/>
+ </bean>
+
+ <bean id="restRequestParameterProcessingFilter" class="net.sourceforge.subsonic.security.RESTRequestParameterProcessingFilter">
+ <property name="authenticationManager" ref="authenticationManager"/>
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+
+ <bean id="basicProcessingFilterEntryPoint" class="org.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
+ <property name="realmName" value="Subsonic"/>
+ </bean>
+
+ <bean id="securityContextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/>
+
+ <bean id="rememberMeProcessingFilter" class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
+ <property name="authenticationManager" ref="authenticationManager"/>
+ <property name="rememberMeServices" ref="rememberMeServices"/>
+ </bean>
+
+ <bean id="anonymousProcessingFilter" class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
+ <property name="key" value="subsonic"/>
+ <property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
+ </bean>
+
+ <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
+ <property name="authenticationEntryPoint">
+ <bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
+ <property name="loginFormUrl" value="/login.view?"/>
+ <property name="forceHttps" value="false"/>
+ </bean>
+ </property>
+ <property name="accessDeniedHandler">
+ <bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
+ <property name="errorPage" value="/accessDenied.view"/>
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="basicExceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
+ <property name="authenticationEntryPoint">
+ <bean class="org.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
+ <property name="realmName" value="Subsonic"/>
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
+ <property name="authenticationManager" ref="authenticationManager"/>
+ <property name="alwaysReauthenticate" value="true"/>
+ <property name="accessDecisionManager" ref="accessDecisionManager"/>
+ <property name="objectDefinitionSource">
+ <value>
+ PATTERN_TYPE_APACHE_ANT
+
+ /login.view=IS_AUTHENTICATED_ANONYMOUSLY
+ /recover.view=IS_AUTHENTICATED_ANONYMOUSLY
+ /accessDenied.view=IS_AUTHENTICATED_ANONYMOUSLY
+ /videoPlayer.view=IS_AUTHENTICATED_ANONYMOUSLY
+ /coverArt.view=IS_AUTHENTICATED_ANONYMOUSLY
+ /stream/**=IS_AUTHENTICATED_ANONYMOUSLY
+ /share/**=IS_AUTHENTICATED_ANONYMOUSLY
+ /style/**=IS_AUTHENTICATED_ANONYMOUSLY
+ /icons/**=IS_AUTHENTICATED_ANONYMOUSLY
+ /flash/**=IS_AUTHENTICATED_ANONYMOUSLY
+ /script/**=IS_AUTHENTICATED_ANONYMOUSLY
+ /crossdomain.xml=IS_AUTHENTICATED_ANONYMOUSLY
+
+ /personalSettings.view=ROLE_SETTINGS
+ /passwordSettings.view=ROLE_SETTINGS
+ /playerSettings.view=ROLE_SETTINGS
+ /shareSettings.view=ROLE_SETTINGS
+
+ /generalSettings.view=ROLE_ADMIN
+ /advancedSettings.view=ROLE_ADMIN
+ /userSettings.view=ROLE_ADMIN
+ /musicFolderSettings.view=ROLE_ADMIN
+ /networkSettings.view=ROLE_ADMIN
+ /transcodingSettings.view=ROLE_ADMIN
+ /internetRadioSettings.view=ROLE_ADMIN
+ /podcastSettings.view=ROLE_ADMIN
+ /db.view=ROLE_ADMIN
+
+ /deletePlaylist.view=ROLE_PLAYLIST
+ /savePlaylist.view=ROLE_PLAYLIST
+
+ /download.view=ROLE_DOWNLOAD
+
+ /upload.view=ROLE_UPLOAD
+
+ /createShare.view=ROLE_SHARE
+
+ /changeCoverArt.view=ROLE_COVERART
+ /editTags.view=ROLE_COVERART
+
+ /setMusicFileInfo.view=ROLE_COMMENT
+
+ /podcastReceiverAdmin.view=ROLE_PODCAST
+
+ /**=IS_AUTHENTICATED_REMEMBERED
+ </value>
+ </property>
+ </bean>
+
+ <bean id="accessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
+ <property name="allowIfAllAbstainDecisions" value="false"/>
+ <property name="decisionVoters">
+ <list>
+ <bean class="org.acegisecurity.vote.RoleVoter"/>
+ <bean class="org.acegisecurity.vote.AuthenticatedVoter"/>
+ </list>
+ </property>
+ </bean>
+
+ <bean id="rememberMeServices" class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
+ <property name="userDetailsService" ref="securityService"/>
+ <property name="tokenValiditySeconds" value="31536000"/>
+ <!-- One year -->
+ <property name="key" value="subsonic"/>
+ </bean>
+
+ <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
+ <property name="providers">
+ <list>
+ <ref local="daoAuthenticationProvider"/>
+ <ref local="ldapAuthenticationProvider"/>
+ <bean class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
+ <property name="key" value="subsonic"/>
+ </bean>
+ <bean class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
+ <property name="key" value="subsonic"/>
+ </bean>
+ </list>
+ </property>
+ </bean>
+
+ <bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
+ <property name="userDetailsService" ref="securityService"/>
+ <property name="userCache" ref="userCacheWrapper"/>
+ </bean>
+
+ <bean id="userCacheWrapper" class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
+ <property name="cache" ref="userCache"/>
+ </bean>
+
+ <bean id="ldapAuthenticationProvider" class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider">
+ <constructor-arg ref="bindAuthenticator"/>
+ <constructor-arg ref="userDetailsServiceBasedAuthoritiesPopulator"/>
+ <property name="userCache" ref="userCacheWrapper"/>
+ </bean>
+
+ <bean id="bindAuthenticator" class="net.sourceforge.subsonic.ldap.SubsonicLdapBindAuthenticator">
+ <property name="securityService" ref="securityService"/>
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+
+ <bean id="userDetailsServiceBasedAuthoritiesPopulator"
+ class="net.sourceforge.subsonic.ldap.UserDetailsServiceBasedAuthoritiesPopulator">
+ <property name="userDetailsService" ref="securityService"/>
+ </bean>
+
+ <!-- Authorization of AJAX services. -->
+ <bean id="ajaxServiceInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
+ <property name="authenticationManager" ref="authenticationManager"/>
+ <property name="accessDecisionManager" ref="accessDecisionManager"/>
+ <property name="objectDefinitionSource">
+ <value>
+ net.sourceforge.subsonic.ajax.TagService.setTags=ROLE_COVERART
+ net.sourceforge.subsonic.ajax.TransferService.getUploadInfo=ROLE_UPLOAD
+ </value>
+ </property>
+ </bean>
+
+ <bean id="ajaxTagServiceSecure" class="org.springframework.aop.framework.ProxyFactoryBean">
+ <property name="target" ref="ajaxTagService"/>
+ <property name="interceptorNames">
+ <list>
+ <idref local="ajaxServiceInterceptor"/>
+ </list>
+ </property>
+ </bean>
+
+ <bean id="ajaxTransferServiceSecure" class="org.springframework.aop.framework.ProxyFactoryBean">
+ <property name="target" ref="ajaxTransferService"/>
+ <property name="interceptorNames">
+ <list>
+ <idref local="ajaxServiceInterceptor"/>
+ </list>
+ </property>
+ </bean>
+
+</beans> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/applicationContext-service.xml b/subsonic-main/src/main/webapp/WEB-INF/applicationContext-service.xml
new file mode 100644
index 00000000..95f76359
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/applicationContext-service.xml
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <!-- DAO's -->
+
+ <bean id="playerDao" class="net.sourceforge.subsonic.dao.PlayerDao">
+ <property name="daoHelper" ref="daoHelper"/>
+ </bean>
+
+ <bean id="mediaFileDao" class="net.sourceforge.subsonic.dao.MediaFileDao">
+ <property name="daoHelper" ref="daoHelper"/>
+ </bean>
+
+ <bean id="artistDao" class="net.sourceforge.subsonic.dao.ArtistDao">
+ <property name="daoHelper" ref="daoHelper"/>
+ </bean>
+
+ <bean id="albumDao" class="net.sourceforge.subsonic.dao.AlbumDao">
+ <property name="daoHelper" ref="daoHelper"/>
+ </bean>
+
+ <bean id="playlistDao" class="net.sourceforge.subsonic.dao.PlaylistDao">
+ <property name="daoHelper" ref="daoHelper"/>
+ </bean>
+
+ <bean id="internetRadioDao" class="net.sourceforge.subsonic.dao.InternetRadioDao">
+ <property name="daoHelper" ref="daoHelper"/>
+ </bean>
+
+ <bean id="musicFileInfoDao" class="net.sourceforge.subsonic.dao.RatingDao">
+ <property name="daoHelper" ref="daoHelper"/>
+ </bean>
+
+ <bean id="musicFolderDao" class="net.sourceforge.subsonic.dao.MusicFolderDao">
+ <property name="daoHelper" ref="daoHelper"/>
+ </bean>
+
+ <bean id="userDao" class="net.sourceforge.subsonic.dao.UserDao">
+ <property name="daoHelper" ref="daoHelper"/>
+ </bean>
+
+ <bean id="transcodingDao" class="net.sourceforge.subsonic.dao.TranscodingDao">
+ <property name="daoHelper" ref="daoHelper"/>
+ </bean>
+
+ <bean id="podcastDao" class="net.sourceforge.subsonic.dao.PodcastDao">
+ <property name="daoHelper" ref="daoHelper"/>
+ </bean>
+
+ <bean id="avatarDao" class="net.sourceforge.subsonic.dao.AvatarDao">
+ <property name="daoHelper" ref="daoHelper"/>
+ </bean>
+
+ <bean id="shareDao" class="net.sourceforge.subsonic.dao.ShareDao">
+ <property name="daoHelper" ref="daoHelper"/>
+ </bean>
+
+ <bean id="daoHelper" class="net.sourceforge.subsonic.dao.DaoHelper"/>
+
+
+ <!-- Services -->
+
+ <bean id="mediaFileService" class="net.sourceforge.subsonic.service.MediaFileService">
+ <property name="securityService" ref="securityService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="mediaFileMemoryCache" ref="mediaFileMemoryCache"/>
+ <property name="mediaFileDao" ref="mediaFileDao"/>
+ <property name="albumDao" ref="albumDao"/>
+ <property name="metaDataParserFactory" ref="metaDataParserFactory"/>
+ </bean>
+
+ <bean id="securityService" class="net.sourceforge.subsonic.service.SecurityService">
+ <property name="settingsService" ref="settingsService"/>
+ <property name="userDao" ref="userDao"/>
+ <property name="userCache" ref="userCache"/>
+ </bean>
+
+ <bean id="settingsService" class="net.sourceforge.subsonic.service.SettingsService" init-method="init">
+ <property name="internetRadioDao" ref="internetRadioDao"/>
+ <property name="musicFolderDao" ref="musicFolderDao"/>
+ <property name="userDao" ref="userDao"/>
+ <property name="avatarDao" ref="avatarDao"/>
+ <property name="versionService" ref="versionService"/>
+ </bean>
+
+ <bean id="mediaScannerService" class="net.sourceforge.subsonic.service.MediaScannerService" init-method="init" depends-on="metaDataParserFactory">
+ <property name="settingsService" ref="settingsService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="mediaFileDao" ref="mediaFileDao"/>
+ <property name="artistDao" ref="artistDao"/>
+ <property name="albumDao" ref="albumDao"/>
+ <property name="searchService" ref="searchService"/>
+ </bean>
+
+ <bean id="searchService" class="net.sourceforge.subsonic.service.SearchService">
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="artistDao" ref="artistDao"/>
+ <property name="albumDao" ref="albumDao"/>
+ </bean>
+
+ <bean id="networkService" class="net.sourceforge.subsonic.service.NetworkService" init-method="init">
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+
+ <bean id="playerService" class="net.sourceforge.subsonic.service.PlayerService" init-method="init">
+ <property name="playerDao" ref="playerDao"/>
+ <property name="statusService" ref="statusService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="transcodingService" ref="transcodingService"/>
+ </bean>
+
+ <bean id="playlistService" class="net.sourceforge.subsonic.service.PlaylistService" init-method="init">
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="mediaFileDao" ref="mediaFileDao"/>
+ <property name="playlistDao" ref="playlistDao"/>
+ </bean>
+
+ <bean id="versionService" class="net.sourceforge.subsonic.service.VersionService"/>
+
+ <bean id="statusService" class="net.sourceforge.subsonic.service.StatusService"/>
+
+ <bean id="musicInfoService" class="net.sourceforge.subsonic.service.RatingService">
+ <property name="ratingDao" ref="musicFileInfoDao"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="securityService" ref="securityService"/>
+ </bean>
+
+ <bean id="musicIndexService" class="net.sourceforge.subsonic.service.MusicIndexService">
+ <property name="settingsService" ref="settingsService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="mediaFileDao" ref="mediaFileDao"/>
+ </bean>
+
+ <bean id="audioScrobblerService" class="net.sourceforge.subsonic.service.AudioScrobblerService">
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+
+ <bean id="transcodingService" class="net.sourceforge.subsonic.service.TranscodingService">
+ <property name="transcodingDao" ref="transcodingDao"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="playerService" ref="playerService"/>
+ </bean>
+
+ <bean id="shareService" class="net.sourceforge.subsonic.service.ShareService">
+ <property name="shareDao" ref="shareDao"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ </bean>
+
+ <bean id="podcastService" class="net.sourceforge.subsonic.service.PodcastService" init-method="init">
+ <property name="podcastDao" ref="podcastDao"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ </bean>
+
+ <bean id="adService" class="net.sourceforge.subsonic.service.AdService">
+ <property name="adInterval" value="4"/>
+ </bean>
+
+ <bean id="jukeboxService" class="net.sourceforge.subsonic.service.JukeboxService">
+ <property name="statusService" ref="statusService"/>
+ <property name="transcodingService" ref="transcodingService"/>
+ <property name="audioScrobblerService" ref="audioScrobblerService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="securityService" ref="securityService"/>
+ </bean>
+
+ <bean id="metaDataParserFactory" class="net.sourceforge.subsonic.service.metadata.MetaDataParserFactory">
+ <property name="parsers">
+ <list>
+ <bean class="net.sourceforge.subsonic.service.metadata.JaudiotaggerParser"/>
+ <bean class="net.sourceforge.subsonic.service.metadata.FFmpegParser">
+ <property name="transcodingService" ref="transcodingService"/>
+ </bean>
+ <bean class="net.sourceforge.subsonic.service.metadata.DefaultMetaDataParser"/>
+ </list>
+ </property>
+ </bean>
+
+ <!-- AJAX services -->
+
+ <bean id="ajaxMultiService" class="net.sourceforge.subsonic.ajax.MultiService">
+ <property name="networkService" ref="networkService"/>
+ </bean>
+
+ <bean id="ajaxNowPlayingService" class="net.sourceforge.subsonic.ajax.NowPlayingService">
+ <property name="playerService" ref="playerService"/>
+ <property name="statusService" ref="statusService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="mediaScannerService" ref="mediaScannerService"/>
+ </bean>
+
+ <bean id="ajaxPlayQueueService" class="net.sourceforge.subsonic.ajax.PlayQueueService">
+ <property name="playerService" ref="playerService"/>
+ <property name="playlistService" ref="playlistService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="mediaFileDao" ref="mediaFileDao"/>
+ <property name="jukeboxService" ref="jukeboxService"/>
+ <property name="transcodingService" ref="transcodingService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="securityService" ref="securityService"/>
+ </bean>
+
+ <bean id="ajaxPlaylistService" class="net.sourceforge.subsonic.ajax.PlaylistService">
+ <property name="playlistService" ref="playlistService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="mediaFileDao" ref="mediaFileDao"/>
+ </bean>
+
+ <bean id="ajaxLyricsService" class="net.sourceforge.subsonic.ajax.LyricsService"/>
+
+ <bean id="ajaxCoverArtService" class="net.sourceforge.subsonic.ajax.CoverArtService">
+ <property name="securityService" ref="securityService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ </bean>
+
+ <bean id="ajaxStarService" class="net.sourceforge.subsonic.ajax.StarService">
+ <property name="securityService" ref="securityService"/>
+ <property name="mediaFileDao" ref="mediaFileDao"/>
+ </bean>
+
+ <bean id="ajaxTagService" class="net.sourceforge.subsonic.ajax.TagService">
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="metaDataParserFactory" ref="metaDataParserFactory"/>
+ </bean>
+
+ <bean id="ajaxTransferService" class="net.sourceforge.subsonic.ajax.TransferService"/>
+
+ <bean id="ajaxChatService" class="net.sourceforge.subsonic.ajax.ChatService" init-method="init">
+ <property name="securityService" ref="securityService"/>
+ </bean>
+
+</beans>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/dwr.xml b/subsonic-main/src/main/webapp/WEB-INF/dwr.xml
new file mode 100644
index 00000000..e7ea3187
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/dwr.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 3.0//EN" "http://getahead.org/dwr/dwr30.dtd">
+
+<dwr>
+ <allow>
+
+ <create creator="spring" javascript="multiService">
+ <param name="beanName" value="ajaxMultiService"/>
+ </create>
+
+ <create creator="spring" javascript="nowPlayingService">
+ <param name="beanName" value="ajaxNowPlayingService"/>
+ </create>
+
+ <create creator="spring" javascript="playQueueService">
+ <param name="beanName" value="ajaxPlayQueueService"/>
+ </create>
+
+ <create creator="spring" javascript="playlistService">
+ <param name="beanName" value="ajaxPlaylistService"/>
+ </create>
+
+ <create creator="spring" javascript="lyricsService">
+ <param name="beanName" value="ajaxLyricsService"/>
+ </create>
+
+ <create creator="spring" javascript="coverArtService">
+ <param name="beanName" value="ajaxCoverArtService"/>
+ </create>
+
+ <create creator="spring" javascript="starService">
+ <param name="beanName" value="ajaxStarService"/>
+ </create>
+
+ <create creator="spring" javascript="tagService">
+ <param name="beanName" value="ajaxTagService"/>
+ </create>
+
+ <create creator="spring" javascript="transferService">
+ <param name="beanName" value="ajaxTransferService"/>
+ </create>
+
+ <create creator="spring" javascript="chatService">
+ <param name="beanName" value="ajaxChatService"/>
+ </create>
+
+ <convert converter="bean" match="net.sourceforge.subsonic.ajax.NetworkStatus"/>
+ <convert converter="bean" match="net.sourceforge.subsonic.ajax.NowPlayingInfo"/>
+ <convert converter="bean" match="net.sourceforge.subsonic.ajax.ScanInfo"/>
+ <convert converter="bean" match="net.sourceforge.subsonic.ajax.PlayQueueInfo"/>
+ <convert converter="bean" match="net.sourceforge.subsonic.ajax.PlayQueueInfo$Entry"/>
+ <convert converter="bean" match="net.sourceforge.subsonic.ajax.PlaylistInfo"/>
+ <convert converter="bean" match="net.sourceforge.subsonic.ajax.PlaylistInfo$Entry"/>
+ <convert converter="bean" match="net.sourceforge.subsonic.domain.Playlist"/>
+ <convert converter="bean" match="net.sourceforge.subsonic.ajax.UploadInfo"/>
+ <convert converter="bean" match="net.sourceforge.subsonic.ajax.LyricsInfo"/>
+ <convert converter="bean" match="net.sourceforge.subsonic.ajax.CoverArtInfo"/>
+ <convert converter="bean" match="net.sourceforge.subsonic.ajax.ChatService$Message"/>
+ <convert converter="bean" match="net.sourceforge.subsonic.ajax.ChatService$Messages"/>
+
+ </allow>
+</dwr> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/accessDenied.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/accessDenied.jsp
new file mode 100644
index 00000000..78d2b910
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/accessDenied.jsp
@@ -0,0 +1,22 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+
+<html>
+<head>
+ <%@ include file="head.jsp" %>
+</head>
+
+<body class="mainframe bgcolor1">
+
+<h1>
+ <img src="<spring:theme code="errorImage"/>" alt=""/>
+ <fmt:message key="accessDenied.title"/>
+</h1>
+
+<p>
+ <fmt:message key="accessDenied.text"/>
+</p>
+
+<div class="back"><a href="javascript:history.go(-1)"><fmt:message key="common.back"/></a></div>
+
+</body>
+</html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/advancedSettings.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/advancedSettings.jsp
new file mode 100644
index 00000000..9b3a95b1
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/advancedSettings.jsp
@@ -0,0 +1,142 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <script type="text/javascript" src="<c:url value="/script/scripts.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/prototype.js"/>"></script>
+ <script type="text/javascript" language="javascript">
+ function enableLdapFields() {
+ var checkbox = $("ldap");
+ var table = $("ldapTable");
+
+ if (checkbox && checkbox.checked) {
+ table.show();
+ } else {
+ table.hide();
+ }
+ }
+ </script>
+</head>
+
+<body class="mainframe bgcolor1" onload="enableLdapFields()">
+<script type="text/javascript" src="<c:url value="/script/wz_tooltip.js"/>"></script>
+<script type="text/javascript" src="<c:url value="/script/tip_balloon.js"/>"></script>
+
+<c:import url="settingsHeader.jsp">
+ <c:param name="cat" value="advanced"/>
+</c:import>
+
+<form:form method="post" action="advancedSettings.view" commandName="command">
+
+ <table style="white-space:nowrap" class="indent">
+
+ <tr>
+ <td><fmt:message key="advancedsettings.downsamplecommand"/></td>
+ <td>
+ <form:input path="downsampleCommand" size="70"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="downsamplecommand"/></c:import>
+ </td>
+ </tr>
+
+ <tr><td colspan="2">&nbsp;</td></tr>
+
+ <tr>
+ <td><fmt:message key="advancedsettings.coverartlimit"/></td>
+ <td>
+ <form:input path="coverArtLimit" size="8"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="coverartlimit"/></c:import>
+ </td>
+ </tr>
+
+ <tr>
+ <td><fmt:message key="advancedsettings.downloadlimit"/></td>
+ <td>
+ <form:input path="downloadLimit" size="8"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="downloadlimit"/></c:import>
+ </td>
+ </tr>
+
+ <tr>
+ <td><fmt:message key="advancedsettings.uploadlimit"/></td>
+ <td>
+ <form:input path="uploadLimit" size="8"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="uploadlimit"/></c:import>
+ </td>
+ </tr>
+
+ <tr>
+ <td><fmt:message key="advancedsettings.streamport"/></td>
+ <td>
+ <form:input path="streamPort" size="8"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="streamport"/></c:import>
+ </td>
+ </tr>
+
+ <tr><td colspan="2">&nbsp;</td></tr>
+
+ <tr>
+ <td colspan="2">
+ <form:checkbox path="ldapEnabled" id="ldap" cssClass="checkbox" onclick="javascript:enableLdapFields()"/>
+ <label for="ldap"><fmt:message key="advancedsettings.ldapenabled"/></label>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="ldap"/></c:import>
+ </td>
+ </tr>
+
+ <tr><td colspan="2">
+ <table class="indent" id="ldapTable" style="padding-left:2em">
+ <tr>
+ <td><fmt:message key="advancedsettings.ldapurl"/></td>
+ <td colspan="3">
+ <form:input path="ldapUrl" size="70"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="ldapurl"/></c:import>
+ </td>
+ </tr>
+
+ <tr>
+ <td><fmt:message key="advancedsettings.ldapsearchfilter"/></td>
+ <td colspan="3">
+ <form:input path="ldapSearchFilter" size="70"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="ldapsearchfilter"/></c:import>
+ </td>
+ </tr>
+
+ <tr>
+ <td><fmt:message key="advancedsettings.ldapmanagerdn"/></td>
+ <td>
+ <form:input path="ldapManagerDn" size="20"/>
+ </td>
+ <td><fmt:message key="advancedsettings.ldapmanagerpassword"/></td>
+ <td>
+ <form:password path="ldapManagerPassword" size="20"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="ldapmanagerdn"/></c:import>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="5">
+ <form:checkbox path="ldapAutoShadowing" id="ldapAutoShadowing" cssClass="checkbox"/>
+ <label for="ldapAutoShadowing"><fmt:message key="advancedsettings.ldapautoshadowing"><fmt:param value="${command.brand}"/></fmt:message></label>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="ldapautoshadowing"/></c:import>
+ </td>
+ </tr>
+ </table>
+ </td></tr>
+
+ <tr>
+ <td colspan="2" style="padding-top:1.5em">
+ <input type="submit" value="<fmt:message key="common.save"/>" style="margin-right:0.3em">
+ <input type="button" value="<fmt:message key="common.cancel"/>" onclick="location.href='nowPlaying.view'">
+ </td>
+ </tr>
+
+ </table>
+</form:form>
+
+<c:if test="${command.reloadNeeded}">
+ <script language="javascript" type="text/javascript">
+ parent.frames.left.location.href="left.view?";
+ parent.frames.playQueue.location.href="playQueue.view?";
+ </script>
+</c:if>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/allmusic.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/allmusic.jsp
new file mode 100644
index 00000000..1af611fb
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/allmusic.jsp
@@ -0,0 +1,16 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+</head>
+
+<body onload="document.allmusic.submit();" class="mainframe bgcolor1">
+<h2><fmt:message key="allmusic.text"><fmt:param value="${album}"/></fmt:message></h2>
+
+<form name="allmusic" action="http://www.allmusic.com/search" method="POST" accept-charset="iso-8859-1">
+ <input type="hidden" name="search_term" value="${album}"/>
+ <input type="hidden" name="search_type" value="album"/>
+</form>
+
+</body>
+</html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/avatarUploadResult.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/avatarUploadResult.jsp
new file mode 100644
index 00000000..6c3b1010
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/avatarUploadResult.jsp
@@ -0,0 +1,35 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+
+<html>
+<head>
+ <%@ include file="head.jsp" %>
+</head>
+<body class="mainframe bgcolor1">
+
+<h1>
+ <img src="<spring:theme code="settingsImage"/>" alt=""/>
+ <fmt:message key="avataruploadresult.title"/>
+</h1>
+
+<c:choose>
+ <c:when test="${empty model.error}">
+ <p>
+ <fmt:message key="avataruploadresult.success"><fmt:param value="${model.avatar.name}"/></fmt:message>
+ <sub:url value="avatar.view" var="avatarUrl">
+ <sub:param name="username" value="${model.username}"/>
+ </sub:url>
+ <img src="${avatarUrl}" alt="${model.avatar.name}" width="${model.avatar.width}"
+ height="${model.avatar.height}" style="padding-left:2em"/>
+ </p>
+ </c:when>
+ <c:otherwise>
+ <p class="warning">
+ <fmt:message key="avataruploadresult.failure"/>
+ </p>
+ </c:otherwise>
+</c:choose>
+
+<div class="back"><a href="personalSettings.view?"><fmt:message key="common.back"/></a></div>
+
+</body>
+</html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/changeCoverArt.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/changeCoverArt.jsp
new file mode 100644
index 00000000..598f12ca
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/changeCoverArt.jsp
@@ -0,0 +1,206 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <script type="text/javascript" src="<c:url value="/dwr/interface/coverArtService.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/engine.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/util.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/prototype.js"/>"></script>
+ <script type="text/javascript" src="https://www.google.com/jsapi"></script>
+
+ <script type="text/javascript" language="javascript">
+
+ dwr.engine.setErrorHandler(null);
+ google.load('search', '1');
+ var imageSearch;
+
+ function setImage(imageUrl) {
+ $("wait").show();
+ $("result").hide();
+ $("success").hide();
+ $("error").hide();
+ $("errorDetails").hide();
+ $("noImagesFound").hide();
+ var id = dwr.util.getValue("id");
+ coverArtService.setCoverArtImage(id, imageUrl, setImageComplete);
+ }
+
+ function setImageComplete(errorDetails) {
+ $("wait").hide();
+ if (errorDetails != null) {
+ dwr.util.setValue("errorDetails", "<br/>" + errorDetails, { escapeHtml:false });
+ $("error").show();
+ $("errorDetails").show();
+ } else {
+ $("success").show();
+ }
+ }
+
+ function searchComplete() {
+
+ $("wait").hide();
+
+ if (imageSearch.results && imageSearch.results.length > 0) {
+
+ var images = $("images");
+ images.innerHTML = "";
+
+ var results = imageSearch.results;
+ for (var i = 0; i < results.length; i++) {
+ var result = results[i];
+ var node = $("template").cloneNode(true);
+
+ var link = node.getElementsByClassName("search-result-link")[0];
+ link.href = "javascript:setImage('" + result.url + "');";
+
+ var thumbnail = node.getElementsByClassName("search-result-thumbnail")[0];
+ thumbnail.src = result.tbUrl;
+
+ var title = node.getElementsByClassName("search-result-title")[0];
+ title.innerHTML = result.contentNoFormatting.truncate(30);
+
+ var dimension = node.getElementsByClassName("search-result-dimension")[0];
+ dimension.innerHTML = result.width + " × " + result.height;
+
+ var url = node.getElementsByClassName("search-result-url")[0];
+ url.innerHTML = result.visibleUrl;
+
+ node.show();
+ images.appendChild(node);
+ }
+
+ $("result").show();
+
+ addPaginationLinks(imageSearch);
+
+ } else {
+ $("noImagesFound").show();
+ }
+ }
+
+ function addPaginationLinks() {
+
+ // To paginate search results, use the cursor function.
+ var cursor = imageSearch.cursor;
+ var curPage = cursor.currentPageIndex; // check what page the app is on
+ var pagesDiv = document.createElement("div");
+ for (var i = 0; i < cursor.pages.length; i++) {
+ var page = cursor.pages[i];
+ var label;
+ if (curPage == i) {
+ // If we are on the current page, then don"t make a link.
+ label = document.createElement("b");
+ } else {
+
+ // Create links to other pages using gotoPage() on the searcher.
+ label = document.createElement("a");
+ label.href = "javascript:imageSearch.gotoPage(" + i + ");";
+ }
+ label.innerHTML = page.label;
+ label.style.marginRight = "1em";
+ pagesDiv.appendChild(label);
+ }
+
+ // Create link to next page.
+ if (curPage < cursor.pages.length - 1) {
+ var next = document.createElement("a");
+ next.href = "javascript:imageSearch.gotoPage(" + (curPage + 1) + ");";
+ next.innerHTML = "<fmt:message key="common.next"/>";
+ next.style.marginLeft = "1em";
+ pagesDiv.appendChild(next);
+ }
+
+ var pages = $("pages");
+ pages.innerHTML = "";
+ pages.appendChild(pagesDiv);
+ }
+
+ function search() {
+
+ $("wait").show();
+ $("result").hide();
+ $("success").hide();
+ $("error").hide();
+ $("errorDetails").hide();
+ $("noImagesFound").hide();
+
+ var query = dwr.util.getValue("query");
+ imageSearch.execute(query);
+ }
+
+ function onLoad() {
+
+ imageSearch = new google.search.ImageSearch();
+ imageSearch.setSearchCompleteCallback(this, searchComplete, null);
+ imageSearch.setNoHtmlGeneration();
+ imageSearch.setResultSetSize(8);
+
+ google.search.Search.getBranding("branding");
+
+ $("template").hide();
+
+ search();
+ }
+ google.setOnLoadCallback(onLoad);
+
+
+ </script>
+</head>
+<body class="mainframe bgcolor1">
+<h1><fmt:message key="changecoverart.title"/></h1>
+<form action="javascript:search()">
+ <table class="indent"><tr>
+ <td><input id="query" name="query" size="70" type="text" value="${model.artist} ${model.album}" onclick="select()"/></td>
+ <td style="padding-left:0.5em"><input type="submit" value="<fmt:message key="changecoverart.search"/>"/></td>
+ </tr></table>
+</form>
+
+<form action="javascript:setImage(dwr.util.getValue('url'))">
+ <table><tr>
+ <input id="id" type="hidden" name="id" value="${model.id}"/>
+ <td><label for="url"><fmt:message key="changecoverart.address"/></label></td>
+ <td style="padding-left:0.5em"><input type="text" name="url" size="50" id="url" value="http://" onclick="select()"/></td>
+ <td style="padding-left:0.5em"><input type="submit" value="<fmt:message key="common.ok"/>"></td>
+ </tr></table>
+</form>
+<sub:url value="main.view" var="backUrl"><sub:param name="id" value="${model.id}"/></sub:url>
+<div style="padding-top:0.5em;padding-bottom:0.5em">
+ <div class="back"><a href="${backUrl}"><fmt:message key="common.back"/></a></div>
+</div>
+
+<h2 id="wait" style="display:none"><fmt:message key="changecoverart.wait"/></h2>
+<h2 id="noImagesFound" style="display:none"><fmt:message key="changecoverart.noimagesfound"/></h2>
+<h2 id="success" style="display:none"><fmt:message key="changecoverart.success"/></h2>
+<h2 id="error" style="display:none"><fmt:message key="changecoverart.error"/></h2>
+<div id="errorDetails" class="warning" style="display:none">
+</div>
+
+<div id="result">
+
+ <div id="pages" style="float:left;padding-left:0.5em;padding-top:0.5em">
+ </div>
+
+ <div id="branding" style="float:right;padding-right:1em;padding-top:0.5em">
+ </div>
+
+ <div style="clear:both;">
+ </div>
+
+ <div id="images" style="width:100%;padding-bottom:2em">
+ </div>
+
+ <div style="clear:both;">
+ </div>
+
+</div>
+
+<div id="template" style="float:left; height:190px; width:220px;padding:0.5em;position:relative">
+ <div style="position:absolute;bottom:0">
+ <a class="search-result-link"><img class="search-result-thumbnail" style="padding:1px; border:1px solid #021a40; background-color:white;"></a>
+ <div class="search-result-title"></div>
+ <div class="search-result-dimension detail"></div>
+ <div class="search-result-url detail"></div>
+ </div>
+</div>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/coverArt.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/coverArt.jsp
new file mode 100644
index 00000000..a5030499
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/coverArt.jsp
@@ -0,0 +1,86 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+<%@ include file="include.jsp" %>
+
+<%--
+PARAMETERS
+ albumId: ID of album.
+ coverArtSize: Height and width of cover art.
+ albumName: Album name to display as caption and img alt.
+ showLink: Whether to make the cover art image link to the album page.
+ showZoom: Whether to display a link for zooming the cover art.
+ showChange: Whether to display a link for changing the cover art.
+ showCaption: Whether to display the album name as a caption below the image.
+ appearAfter: Fade in after this many milliseconds, or nil if no fading in should happen.
+--%>
+<c:choose>
+ <c:when test="${empty param.coverArtSize}">
+ <c:set var="size" value="auto"/>
+ </c:when>
+ <c:otherwise>
+ <c:set var="size" value="${param.coverArtSize + 8}px"/>
+ </c:otherwise>
+</c:choose>
+
+<c:set var="opacity" value="${empty param.appearAfter ? 1 : 0}"/>
+
+<div style="width:${size}; max-width:${size}; height:${size}; max-height:${size}" title="${param.albumName}">
+ <sub:url value="main.view" var="mainUrl">
+ <sub:param name="id" value="${param.albumId}"/>
+ </sub:url>
+
+ <sub:url value="/coverArt.view" var="coverArtUrl">
+ <c:if test="${not empty param.coverArtSize}">
+ <sub:param name="size" value="${param.coverArtSize}"/>
+ </c:if>
+ <sub:param name="id" value="${param.albumId}"/>
+ </sub:url>
+ <sub:url value="/coverArt.view" var="zoomCoverArtUrl">
+ <sub:param name="id" value="${param.albumId}"/>
+ </sub:url>
+
+ <str:randomString count="5" type="alphabet" var="divId"/>
+ <div class="outerpair1" id="${divId}" style="display:none">
+ <div class="outerpair2">
+ <div class="shadowbox">
+ <div class="innerbox">
+ <c:choose>
+ <c:when test="${param.showLink}"><a href="${mainUrl}" title="${param.albumName}"></c:when>
+ <c:when test="${param.showZoom}"><a href="${zoomCoverArtUrl}" rel="zoom" title="${param.albumName}"></c:when>
+ </c:choose>
+ <img src="${coverArtUrl}" alt="${param.albumName}">
+ <c:if test="${param.showLink or param.showZoom}"></a></c:if>
+ </div>
+ </div>
+ </div>
+ </div>
+ <c:if test="${not empty param.appearAfter}">
+ <script type="text/javascript">
+ if (window.addEventListener) {
+ window.addEventListener('load', function() {
+ setTimeout("$('#${divId}').fadeIn(500)", ${param.appearAfter});
+ }, false);
+ }
+ </script>
+ </c:if>
+</div>
+
+<div style="text-align:right; padding-right: 8px;">
+ <c:if test="${param.showChange}">
+ <sub:url value="/changeCoverArt.view" var="changeCoverArtUrl">
+ <sub:param name="id" value="${param.albumId}"/>
+ </sub:url>
+ <a class="detail" href="${changeCoverArtUrl}"><fmt:message key="coverart.change"/></a>
+ </c:if>
+
+ <c:if test="${param.showZoom and param.showChange}">
+ |
+ </c:if>
+
+ <c:if test="${param.showZoom}">
+ <a class="detail" rel="zoom" title="${param.albumName}" href="${zoomCoverArtUrl}"><fmt:message key="coverart.zoom"/></a>
+ </c:if>
+
+ <c:if test="${not param.showZoom and not param.showChange and param.showCaption}">
+ <span class="detail"><str:truncateNicely upper="17">${param.albumName}</str:truncateNicely></span>
+ </c:if>
+</div> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/createShare.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/createShare.jsp
new file mode 100644
index 00000000..3f739077
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/createShare.jsp
@@ -0,0 +1,51 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+
+<html>
+<head>
+ <%@ include file="head.jsp" %>
+ <script type="text/javascript" src="https://apis.google.com/js/plusone.js"></script>
+</head>
+<body class="mainframe bgcolor1">
+
+<h1><fmt:message key="share.title"/></h1>
+
+<c:choose>
+ <c:when test="${model.urlRedirectionEnabled}">
+ <fmt:message key="share.warning"/>
+ <p>
+ <a href="http://www.facebook.com/sharer.php?u=${model.playUrl}" target="_blank"><img src="<spring:theme code="shareFacebookImage"/>" alt=""></a>&nbsp;
+ <a href="http://www.facebook.com/sharer.php?u=${model.playUrl}" target="_blank"><fmt:message key="share.facebook"/></a>
+ </p>
+
+ <p>
+ <a href="http://twitter.com/?status=Listening to ${model.playUrl}" target="_blank"><img src="<spring:theme code="shareTwitterImage"/>" alt=""></a>&nbsp;
+ <a href="http://twitter.com/?status=Listening to ${model.playUrl}" target="_blank"><fmt:message key="share.twitter"/></a>
+ </p>
+ <p>
+ <g:plusone size="small" annotation="none" href="${model.playUrl}"></g:plusone>&nbsp;<fmt:message key="share.googleplus"/>
+ </p>
+ <p>
+ <fmt:message key="share.link">
+ <fmt:param>${model.playUrl}</fmt:param>
+ </fmt:message>
+ </p>
+ </c:when>
+ <c:otherwise>
+ <p>
+ <fmt:message key="share.disabled"/>
+ </p>
+ </c:otherwise>
+</c:choose>
+
+
+<div style="padding-top:1em">
+ <c:if test="${not empty model.dir}">
+ <sub:url value="main.view" var="backUrl"><sub:param name="path" value="${model.dir.path}"/></sub:url>
+ <div class="back" style="float:left;padding-right:10pt"><a href="${backUrl}"><fmt:message key="common.back"/></a></div>
+ </c:if>
+ <c:if test="${model.user.settingsRole}">
+ <div class="forward" style="float:left"><a href="shareSettings.view"><fmt:message key="share.manage"/></a></div>
+ </c:if>
+</div>
+</body>
+</html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/db.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/db.jsp
new file mode 100644
index 00000000..52918ab1
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/db.jsp
@@ -0,0 +1,45 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+</head><body class="mainframe bgcolor1">
+
+<h1>Database query</h1>
+
+<form method="post" action="db.view">
+ <textarea rows="10" cols="80" name="query" style="margin-top:1em">${model.query}</textarea>
+ <input type="submit" value="<fmt:message key="common.ok"/>">
+</form>
+
+<c:if test="${not empty model.result}">
+ <h1 style="margin-top:2em">Result</h1>
+
+ <table class="indent ruleTable">
+ <c:forEach items="${model.result}" var="row" varStatus="loopStatus">
+
+ <c:if test="${loopStatus.count == 1}">
+ <tr>
+ <c:forEach items="${row}" var="entry">
+ <td class="ruleTableHeader">${entry.key}</td>
+ </c:forEach>
+ </tr>
+ </c:if>
+ <tr>
+ <c:forEach items="${row}" var="entry">
+ <td class="ruleTableCell">${entry.value}</td>
+ </c:forEach>
+ </tr>
+ </c:forEach>
+
+ </table>
+</c:if>
+
+<c:if test="${not empty model.error}">
+ <h1 style="margin-top:2em">Error</h1>
+
+ <p class="warning">
+ ${model.error}
+ </p>
+</c:if>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/donate.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/donate.jsp
new file mode 100644
index 00000000..77906cb1
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/donate.jsp
@@ -0,0 +1,147 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+<%--@elvariable id="command" type="net.sourceforge.subsonic.command.DonateCommand"--%>
+<html>
+<head>
+ <%@ include file="head.jsp" %>
+</head>
+<body class="mainframe bgcolor1">
+
+<h1>
+ <img src="<spring:theme code="donateImage"/>" alt=""/>
+ <fmt:message key="donate.title"/>
+</h1>
+<c:if test="${not empty command.path}">
+ <sub:url value="main.view" var="backUrl">
+ <sub:param name="path" value="${command.path}"/>
+ </sub:url>
+ <div class="back"><a href="${backUrl}">
+ <fmt:message key="common.back"/>
+ </a></div>
+ <br/>
+</c:if>
+
+<c:url value="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=E5RNJMDJ7C862" var="donate10Url"/>
+<c:url value="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CKRS9A4J99TFN" var="donate15Url"/>
+<c:url value="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=H79PAZLVHFT6E" var="donate20Url"/>
+<c:url value="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=2TGXFN7AVREEN" var="donate25Url"/>
+<c:url value="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BXJVAQALLFREC" var="donate30Url"/>
+<c:url value="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=M5PX55AC4ER9Y" var="donate50Url"/>
+
+<div style="width:50em; max-width:50em">
+
+<fmt:message key="donate.textbefore"><fmt:param value="${command.brand}"/></fmt:message>
+
+<table cellpadding="10">
+ <tr>
+ <td>
+ <table>
+ <tr>
+ <td><a href="${donate10Url}" target="_blank"><img src="<spring:theme code="paypalImage"/>" alt=""/></a> </td>
+ </tr>
+ <tr>
+ <td class="detail" style="text-align:center;"><fmt:message key="donate.amount"><fmt:param value="&euro;10"/></fmt:message></td>
+ </tr>
+ </table>
+ </td>
+ <td>
+ <table>
+ <tr>
+ <td><a href="${donate15Url}" target="_blank"><img src="<spring:theme code="paypalImage"/>" alt=""/></a> </td>
+ </tr>
+ <tr>
+ <td class="detail" style="text-align:center;"><fmt:message key="donate.amount"><fmt:param value="&euro;15"/></fmt:message></td>
+ </tr>
+ </table>
+ </td>
+ <td>
+ <table>
+ <tr>
+ <td><a href="${donate20Url}" target="_blank"><img src="<spring:theme code="paypalImage"/>" alt=""/></a> </td>
+ </tr>
+ <tr>
+ <td class="detail" style="text-align:center;"><fmt:message key="donate.amount"><fmt:param value="&euro;20"/></fmt:message></td>
+ </tr>
+ </table>
+ </td>
+ <td>
+ <table>
+ <tr>
+ <td><a href="${donate25Url}" target="_blank"><img src="<spring:theme code="paypalImage"/>" alt=""/></a> </td>
+ </tr>
+ <tr>
+ <td class="detail" style="text-align:center;"><fmt:message key="donate.amount"><fmt:param value="&euro;25"/></fmt:message></td>
+ </tr>
+ </table>
+ </td>
+ <td>
+ <table>
+ <tr>
+ <td><a href="${donate30Url}" target="_blank"><img src="<spring:theme code="paypalImage"/>" alt=""/></a> </td>
+ </tr>
+ <tr>
+ <td class="detail" style="text-align:center;"><fmt:message key="donate.amount"><fmt:param value="&euro;30"/></fmt:message></td>
+ </tr>
+ </table>
+ </td>
+ <td>
+ <table>
+ <tr>
+ <td><a href="${donate50Url}" target="_blank"><img src="<spring:theme code="paypalImage"/>" alt=""/></a> </td>
+ </tr>
+ <tr>
+ <td class="detail" style="text-align:center;"><fmt:message key="donate.amount"><fmt:param value="&euro;50"/></fmt:message></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+</table>
+
+<fmt:message key="donate.textafter"/>
+
+<c:choose>
+ <c:when test="${command.licenseValid}">
+ <p>
+ <b>
+ <fmt:formatDate value="${command.licenseDate}" dateStyle="long" var="licenseDate"/>
+ <fmt:message key="donate.licensed">
+ <fmt:param value="${command.emailAddress}"/>
+ <fmt:param value="${licenseDate}"/>
+ <fmt:param value="${command.brand}"/>
+ </fmt:message>
+ </p>
+ </c:when>
+ <c:otherwise>
+
+ <p><fmt:message key="donate.register"/></p>
+
+ <form:form commandName="command" method="post" action="donate.view">
+ <form:hidden path="path"/>
+ <table>
+ <tr>
+ <td><fmt:message key="donate.register.email"/></td>
+ <td>
+ <form:input path="emailAddress" size="40"/>
+ </td>
+ </tr>
+ <tr>
+ <td><fmt:message key="donate.register.license"/></td>
+ <td>
+ <form:input path="license" size="40"/>
+ </td>
+ <td><input type="submit" value="<fmt:message key="common.ok"/>"/></td>
+ </tr>
+ <tr>
+ <td/>
+ <td class="warning"><form:errors path="license"/></td>
+ </tr>
+ </table>
+ </form:form>
+
+ <p><fmt:message key="donate.resend"/></p>
+
+ </c:otherwise>
+</c:choose>
+
+</div>
+</body>
+</html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/editTags.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/editTags.jsp
new file mode 100644
index 00000000..7d95edd9
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/editTags.jsp
@@ -0,0 +1,164 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <script type="text/javascript" src="<c:url value="/dwr/interface/tagService.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/engine.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/util.js"/>"></script>
+</head>
+<body class="mainframe bgcolor1">
+
+<script type="text/javascript" language="javascript">
+ var index = 0;
+ var fileCount = ${fn:length(model.songs)};
+ function setArtist() {
+ var artist = dwr.util.getValue("artistAll");
+ for (i = 0; i < fileCount; i++) {
+ dwr.util.setValue("artist" + i, artist);
+ }
+ }
+ function setAlbum() {
+ var album = dwr.util.getValue("albumAll");
+ for (i = 0; i < fileCount; i++) {
+ dwr.util.setValue("album" + i, album);
+ }
+ }
+ function setYear() {
+ var year = dwr.util.getValue("yearAll");
+ for (i = 0; i < fileCount; i++) {
+ dwr.util.setValue("year" + i, year);
+ }
+ }
+ function setGenre() {
+ var genre = dwr.util.getValue("genreAll");
+ for (i = 0; i < fileCount; i++) {
+ dwr.util.setValue("genre" + i, genre);
+ }
+ }
+ function suggestTitle() {
+ for (i = 0; i < fileCount; i++) {
+ var title = dwr.util.getValue("suggestedTitle" + i);
+ dwr.util.setValue("title" + i, title);
+ }
+ }
+ function resetTitle() {
+ for (i = 0; i < fileCount; i++) {
+ var title = dwr.util.getValue("originalTitle" + i);
+ dwr.util.setValue("title" + i, title);
+ }
+ }
+ function suggestTrack() {
+ for (i = 0; i < fileCount; i++) {
+ var track = dwr.util.getValue("suggestedTrack" + i);
+ dwr.util.setValue("track" + i, track);
+ }
+ }
+ function resetTrack() {
+ for (i = 0; i < fileCount; i++) {
+ var track = dwr.util.getValue("originalTrack" + i);
+ dwr.util.setValue("track" + i, track);
+ }
+ }
+ function updateTags() {
+ document.getElementById("save").disabled = true;
+ index = 0;
+ dwr.util.setValue("errors", "");
+ for (i = 0; i < fileCount; i++) {
+ dwr.util.setValue("status" + i, "");
+ }
+ updateNextTag();
+ }
+ function updateNextTag() {
+ var id = dwr.util.getValue("id" + index);
+ var artist = dwr.util.getValue("artist" + index);
+ var track = dwr.util.getValue("track" + index);
+ var album = dwr.util.getValue("album" + index);
+ var title = dwr.util.getValue("title" + index);
+ var year = dwr.util.getValue("year" + index);
+ var genre = dwr.util.getValue("genre" + index);
+ dwr.util.setValue("status" + index, "<fmt:message key="edittags.working"/>");
+ tagService.setTags(id, track, artist, album, title, year, genre, setTagsCallback);
+ }
+ function setTagsCallback(result) {
+ var message;
+ if (result == "SKIPPED") {
+ message = "<fmt:message key="edittags.skipped"/>";
+ } else if (result == "UPDATED") {
+ message = "<b><fmt:message key="edittags.updated"/></b>";
+ } else {
+ message = "<div class='warning'><fmt:message key="edittags.error"/></div>"
+ var errors = dwr.util.getValue("errors");
+ errors += result + "<br/>";
+ dwr.util.setValue("errors", errors, { escapeHtml:false });
+ }
+ dwr.util.setValue("status" + index, message, { escapeHtml:false });
+ index++;
+ if (index < fileCount) {
+ updateNextTag();
+ } else {
+ document.getElementById("save").disabled = false;
+ }
+ }
+</script>
+
+<h1><fmt:message key="edittags.title"/></h1>
+<sub:url value="main.view" var="backUrl"><sub:param name="id" value="${model.id}"/></sub:url>
+<div class="back"><a href="${backUrl}"><fmt:message key="common.back"/></a></div>
+
+<table class="ruleTable indent">
+ <tr>
+ <th class="ruleTableHeader"><fmt:message key="edittags.file"/></th>
+ <th class="ruleTableHeader"><fmt:message key="edittags.track"/></th>
+ <th class="ruleTableHeader"><fmt:message key="edittags.songtitle"/></th>
+ <th class="ruleTableHeader"><fmt:message key="edittags.artist"/></th>
+ <th class="ruleTableHeader"><fmt:message key="edittags.album"/></th>
+ <th class="ruleTableHeader"><fmt:message key="edittags.year"/></th>
+ <th class="ruleTableHeader"><fmt:message key="edittags.genre"/></th>
+ <th class="ruleTableHeader" width="60pt"><fmt:message key="edittags.status"/></th>
+ </tr>
+ <tr>
+ <th class="ruleTableHeader"/>
+ <th class="ruleTableHeader"><a href="javascript:suggestTrack()"><fmt:message key="edittags.suggest.short"/></a> |
+ <a href="javascript:resetTrack()"><fmt:message key="edittags.reset.short"/></a></th>
+ <th class="ruleTableHeader"><a href="javascript:suggestTitle()"><fmt:message key="edittags.suggest"/></a> |
+ <a href="javascript:resetTitle()"><fmt:message key="edittags.reset"/></a></th>
+ <th class="ruleTableHeader" style="white-space: nowrap"><input type="text" name="artistAll" size="15" onkeypress="dwr.util.onReturn(event, setArtist)" value="${model.defaultArtist}"/>&nbsp;<a href="javascript:setArtist()"><fmt:message key="edittags.set"/></a></th>
+ <th class="ruleTableHeader" style="white-space: nowrap"><input type="text" name="albumAll" size="15" onkeypress="dwr.util.onReturn(event, setAlbum)" value="${model.defaultAlbum}"/>&nbsp;<a href="javascript:setAlbum()"><fmt:message key="edittags.set"/></a></th>
+ <th class="ruleTableHeader" style="white-space: nowrap"><input type="text" name="yearAll" size="5" onkeypress="dwr.util.onReturn(event, setYear)" value="${model.defaultYear}"/>&nbsp;<a href="javascript:setYear()"><fmt:message key="edittags.set"/></a></th>
+ <th class="ruleTableHeader" style="white-space: nowrap">
+ <select name="genreAll" style="width:7em">
+ <option value=""/>
+ <c:forEach items="${model.allGenres}" var="genre">
+ <option ${genre eq model.defaultGenre ? "selected" : ""} value="${genre}">${genre}</option>
+ </c:forEach>
+ </select>
+
+ <a href="javascript:setGenre()"><fmt:message key="edittags.set"/></a>
+ </th>
+ <th class="ruleTableHeader"/>
+ </tr>
+
+ <c:forEach items="${model.songs}" var="song" varStatus="loopStatus">
+ <tr>
+ <str:truncateNicely lower="25" upper="25" var="fileName">${song.fileName}</str:truncateNicely>
+ <input type="hidden" name="id${loopStatus.count - 1}" value="${song.id}"/>
+ <input type="hidden" name="suggestedTitle${loopStatus.count - 1}" value="${song.suggestedTitle}"/>
+ <input type="hidden" name="originalTitle${loopStatus.count - 1}" value="${song.title}"/>
+ <input type="hidden" name="suggestedTrack${loopStatus.count - 1}" value="${song.suggestedTrack}"/>
+ <input type="hidden" name="originalTrack${loopStatus.count - 1}" value="${song.track}"/>
+ <td class="ruleTableCell" title="${song.fileName}">${fileName}</td>
+ <td class="ruleTableCell"><input type="text" size="5" name="track${loopStatus.count - 1}" value="${song.track}"/></td>
+ <td class="ruleTableCell"><input type="text" size="30" name="title${loopStatus.count - 1}" value="${song.title}"/></td>
+ <td class="ruleTableCell"><input type="text" size="15" name="artist${loopStatus.count - 1}" value="${song.artist}"/></td>
+ <td class="ruleTableCell"><input type="text" size="15" name="album${loopStatus.count - 1}" value="${song.album}"/></td>
+ <td class="ruleTableCell"><input type="text" size="5" name="year${loopStatus.count - 1}" value="${song.year}"/></td>
+ <td class="ruleTableCell"><input type="text" name="genre${loopStatus.count - 1}" value="${song.genre}" style="width:7em"/></td>
+ <td class="ruleTableCell"><div id="status${loopStatus.count - 1}"/></td>
+ </tr>
+ </c:forEach>
+
+</table>
+
+<p><input type="submit" id="save" value="<fmt:message key="common.save"/>" onclick="javascript:updateTags()"/></p>
+<div class="warning" id="errors"/>
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/externalPlayer.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/externalPlayer.jsp
new file mode 100644
index 00000000..31077d85
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/externalPlayer.jsp
@@ -0,0 +1,99 @@
+<%--@elvariable id="model" type="java.util.Map"--%>
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+
+<html>
+<head>
+ <%@ include file="head.jsp" %>
+ <script type="text/javascript" src="<c:url value="/script/swfobject.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/prototype.js"/>"></script>
+
+ <sub:url value="/coverArt.view" var="coverArtUrl">
+ <c:if test="${not empty model.coverArt}">
+ <sub:param name="path" value="${model.coverArt.path}"/>
+ </c:if>
+ <sub:param name="size" value="200"/>
+ </sub:url>
+
+ <meta name="og:title" content="${fn:escapeXml(model.songs[0].artist)} &mdash; ${fn:escapeXml(model.songs[0].albumName)}"/>
+ <meta name="og:type" content="album"/>
+ <meta name="og:image" content="http://${model.redirectFrom}.subsonic.org${coverArtUrl}"/>
+
+ <script type="text/javascript">
+ function init() {
+ var flashvars = {
+ id:"player1",
+ screencolor:"000000",
+ frontcolor:"<spring:theme code="textColor"/>",
+ backcolor:"<spring:theme code="backgroundColor"/>",
+ stretching: "fill",
+ "playlist.position": "bottom",
+ "playlist.size": 200
+ };
+ var params = {
+ allowfullscreen:"true",
+ allowscriptaccess:"always"
+ };
+ var attributes = {
+ id:"player1",
+ name:"player1"
+ };
+ swfobject.embedSWF("<c:url value="/flash/jw-player-5.6.swf"/>", "placeholder", "500", "500", "9.0.0", false, flashvars, params, attributes);
+ }
+
+ function playerReady(thePlayer) {
+ var player = $("player1");
+ var list = new Array();
+
+ <c:forEach items="${model.songs}" var="song" varStatus="loopStatus">
+ <%--@elvariable id="song" type="net.sourceforge.subsonic.domain.MediaFile"--%>
+ <sub:url value="/stream" var="streamUrl">
+ <sub:param name="path" value="${song.path}"/>
+ <sub:param name="player" value="${model.player}"/>
+ </sub:url>
+ <sub:url value="/coverArt.view" var="coverUrl">
+ <sub:param name="size" value="500"/>
+ <c:if test="${not empty model.coverArts[loopStatus.count - 1]}">
+ <sub:param name="path" value="${model.coverArts[loopStatus.count - 1].path}"/>
+ </c:if>
+ </sub:url>
+
+ <!-- TODO: Use video provider for aac, m4a -->
+ list[${loopStatus.count - 1}] = {
+ file: "${streamUrl}",
+ image: "${coverUrl}",
+ title: "${fn:escapeXml(song.title)}",
+ provider: "${song.video ? "video" : "sound"}",
+ description: "${fn:escapeXml(song.artist)}"
+ };
+
+ <c:if test="${not empty song.durationSeconds}">
+ list[${loopStatus.count-1}].duration = ${song.durationSeconds};
+ </c:if>
+
+ </c:forEach>
+
+ player.sendEvent("LOAD", list);
+ player.sendEvent("PLAY");
+ }
+
+ </script>
+</head>
+
+<body class="mainframe bgcolor1" style="padding-top:2em" onload="init();">
+
+<div style="margin:auto;width:500px">
+ <h1 >${model.songs[0].artist}</h1>
+ <div style="float:left;padding-right:1.5em">
+ <h2 style="margin:0;">${model.songs[0].albumName}</h2>
+ </div>
+ <div class="detail" style="float:right">Streaming by <a href="http://subsonic.org/" target="_blank"><b>Subsonic</b></a></div>
+
+ <div style="clear:both;padding-top:1em">
+ <div id="placeholder">
+ <a href="http://www.adobe.com/go/getflashplayer" target="_blank"><fmt:message key="playlist.getflash"/></a>
+ </div>
+ </div>
+ <div style="padding-top: 2em">${fn:escapeXml(model.share.description)}</div>
+</div>
+</body>
+</html>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/generalSettings.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/generalSettings.jsp
new file mode 100644
index 00000000..f5cffe35
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/generalSettings.jsp
@@ -0,0 +1,165 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+<%--@elvariable id="command" type="net.sourceforge.subsonic.command.GeneralSettingsCommand"--%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <script type="text/javascript" src="<c:url value="/script/scripts.js"/>"></script>
+</head>
+
+<body class="mainframe bgcolor1">
+<script type="text/javascript" src="<c:url value="/script/wz_tooltip.js"/>"></script>
+<script type="text/javascript" src="<c:url value="/script/tip_balloon.js"/>"></script>
+
+<c:import url="settingsHeader.jsp">
+ <c:param name="cat" value="general"/>
+</c:import>
+
+<form:form method="post" action="generalSettings.view" commandName="command">
+
+ <table style="white-space:nowrap" class="indent">
+
+ <tr>
+ <td><fmt:message key="generalsettings.musicmask"/></td>
+ <td>
+ <form:input path="musicFileTypes" size="70"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="musicmask"/></c:import>
+ </td>
+ </tr>
+
+ <tr>
+ <td><fmt:message key="generalsettings.videomask"/></td>
+ <td>
+ <form:input path="videoFileTypes" size="70"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="videomask"/></c:import>
+ </td>
+ </tr>
+
+ <tr>
+ <td><fmt:message key="generalsettings.coverartmask"/></td>
+ <td>
+ <form:input path="coverArtFileTypes" size="70"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="coverartmask"/></c:import>
+ </td>
+ </tr>
+
+ <tr><td colspan="2">&nbsp;</td></tr>
+
+ <tr>
+ <td><fmt:message key="generalsettings.index"/></td>
+ <td>
+ <form:input path="index" size="70"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="index"/></c:import>
+ </td>
+ </tr>
+
+ <tr>
+ <td><fmt:message key="generalsettings.ignoredarticles"/></td>
+ <td>
+ <form:input path="ignoredArticles" size="70"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="ignoredarticles"/></c:import>
+ </td>
+ </tr>
+
+ <tr>
+ <td><fmt:message key="generalsettings.shortcuts"/></td>
+ <td>
+ <form:input path="shortcuts" size="70"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="shortcuts"/></c:import>
+ </td>
+ </tr>
+
+ <tr><td colspan="2">&nbsp;</td></tr>
+
+ <tr>
+ <td><fmt:message key="generalsettings.language"/></td>
+ <td>
+ <form:select path="localeIndex" cssStyle="width:15em">
+ <c:forEach items="${command.locales}" var="locale" varStatus="loopStatus">
+ <form:option value="${loopStatus.count - 1}" label="${locale}"/>
+ </c:forEach>
+ </form:select>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="language"/></c:import>
+ </td>
+ </tr>
+
+ <tr>
+ <td><fmt:message key="generalsettings.theme"/></td>
+ <td>
+ <form:select path="themeIndex" cssStyle="width:15em">
+ <c:forEach items="${command.themes}" var="theme" varStatus="loopStatus">
+ <form:option value="${loopStatus.count - 1}" label="${theme.name}"/>
+ </c:forEach>
+ </form:select>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="theme"/></c:import>
+ </td>
+ </tr>
+
+ <tr><td colspan="2">&nbsp;</td></tr>
+
+ <tr>
+ <td>
+ </td>
+ <td>
+ <form:checkbox path="sortAlbumsByYear" id="sortAlbumsByYear"/>
+ <label for="sortAlbumsByYear"><fmt:message key="generalsettings.sortalbumsbyyear"/></label>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ </td>
+ <td>
+ <form:checkbox path="gettingStartedEnabled" id="gettingStartedEnabled"/>
+ <label for="gettingStartedEnabled"><fmt:message key="generalsettings.showgettingstarted"/></label>
+ </td>
+ </tr>
+
+ <tr><td colspan="2">&nbsp;</td></tr>
+
+ <tr>
+ <td><fmt:message key="generalsettings.welcometitle"/></td>
+ <td>
+ <form:input path="welcomeTitle" size="70"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="welcomemessage"/></c:import>
+ </td>
+ </tr>
+ <tr>
+ <td><fmt:message key="generalsettings.welcomesubtitle"/></td>
+ <td>
+ <form:input path="welcomeSubtitle" size="70"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="welcomemessage"/></c:import>
+ </td>
+ </tr>
+ <tr>
+ <td style="vertical-align:top;"><fmt:message key="generalsettings.welcomemessage"/></td>
+ <td>
+ <form:textarea path="welcomeMessage" rows="5" cols="70"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="welcomemessage"/></c:import>
+ </td>
+ </tr>
+ <tr>
+ <td style="vertical-align:top;"><fmt:message key="generalsettings.loginmessage"/></td>
+ <td>
+ <form:textarea path="loginMessage" rows="5" cols="70"/>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="loginmessage"/></c:import>
+ <fmt:message key="main.wiki"/>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2" style="padding-top:1.5em">
+ <input type="submit" value="<fmt:message key="common.save"/>" style="margin-right:0.3em">
+ <input type="button" value="<fmt:message key="common.cancel"/>" onclick="location.href='nowPlaying.view'">
+ </td>
+ </tr>
+
+ </table>
+</form:form>
+
+<c:if test="${command.reloadNeeded}">
+ <script language="javascript" type="text/javascript">
+ parent.frames.left.location.href="left.view?";
+ parent.frames.playQueue.location.href="playQueue.view?";
+ </script>
+</c:if>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/gettingStarted.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/gettingStarted.jsp
new file mode 100644
index 00000000..ad7b2a77
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/gettingStarted.jsp
@@ -0,0 +1,53 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <script type="text/javascript" language="javascript">
+ function hideGettingStarted() {
+ alert("<fmt:message key="gettingStarted.hidealert"/>");
+ location.href = "gettingStarted.view?hide";
+ }
+ </script>
+</head>
+<body class="mainframe bgcolor1">
+
+<h1 style="padding-bottom:0.5em">
+ <img src="<spring:theme code="homeImage"/>" alt="">
+ <fmt:message key="gettingStarted.title"/>
+</h1>
+
+<fmt:message key="gettingStarted.text"/>
+
+<c:if test="${model.runningAsRoot}">
+ <h2 class="warning"><fmt:message key="gettingStarted.root"/></h2>
+</c:if>
+
+<table style="padding-top:1em;padding-bottom:2em;width:60%">
+ <tr>
+ <td style="font-size:26pt;padding:20pt">1</td>
+ <td>
+ <div style="font-size:14pt"><a href="userSettings.view?userIndex=0"><fmt:message key="gettingStarted.step1.title"/></a></div>
+ <div style="padding-top:5pt"><fmt:message key="gettingStarted.step1.text"/></div>
+ </td>
+ </tr>
+ <tr>
+ <td style="font-size:26pt;padding:20pt">2</td>
+ <td>
+ <div style="font-size:14pt"><a href="musicFolderSettings.view"><fmt:message key="gettingStarted.step2.title"/></a></div>
+ <div style="padding-top:5pt"><fmt:message key="gettingStarted.step2.text"/></div>
+ </td>
+ </tr>
+ <tr>
+ <td style="font-size:26pt;padding:20pt">3</td>
+ <td>
+ <div style="font-size:14pt"><a href="networkSettings.view"><fmt:message key="gettingStarted.step3.title"/></a></div>
+ <div style="padding-top:5pt"><fmt:message key="gettingStarted.step3.text"/></div>
+ </td>
+ </tr>
+
+</table>
+
+<div class="forward"><a href="javascript:hideGettingStarted()"><fmt:message key="gettingStarted.hide"/></a></div>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/head.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/head.jsp
new file mode 100644
index 00000000..cedadd8d
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/head.jsp
@@ -0,0 +1,11 @@
+<%@ include file="include.jsp" %>
+
+<!--[if lt IE 7.]>
+<script defer type="text/javascript" src="<c:url value="/script/pngfix.js"/>"></script>
+<![endif]-->
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<c:set var="styleSheet"><spring:theme code="styleSheet"/></c:set>
+<c:set var="faviconImage"><spring:theme code="faviconImage"/></c:set>
+<link rel="stylesheet" href="<c:url value="/${styleSheet}"/>" type="text/css">
+<link rel="shortcut icon" href="<c:url value="/${faviconImage}"/>" type="text/css">
+<title>Subsonic</title>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/help.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/help.jsp
new file mode 100644
index 00000000..58421828
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/help.jsp
@@ -0,0 +1,70 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <script type="text/javascript" src="<c:url value="/script/scripts.js"/>"></script>
+</head>
+<body class="mainframe bgcolor1">
+
+<c:choose>
+ <c:when test="${empty model.buildDate}">
+ <fmt:message key="common.unknown" var="buildDateString"/>
+ </c:when>
+ <c:otherwise>
+ <fmt:formatDate value="${model.buildDate}" dateStyle="long" var="buildDateString"/>
+ </c:otherwise>
+</c:choose>
+
+<c:choose>
+ <c:when test="${empty model.localVersion}">
+ <fmt:message key="common.unknown" var="versionString"/>
+ </c:when>
+ <c:otherwise>
+ <c:set var="versionString" value="${model.localVersion} (build ${model.buildNumber})"/>
+ </c:otherwise>
+</c:choose>
+
+<h1>
+ <img src="<spring:theme code="helpImage"/>" alt="">
+ <fmt:message key="help.title"><fmt:param value="${model.brand}"/></fmt:message>
+</h1>
+
+<c:if test="${model.newVersionAvailable}">
+ <p class="warning"><fmt:message key="help.upgrade"><fmt:param value="${model.brand}"/><fmt:param value="${model.latestVersion}"/></fmt:message></p>
+</c:if>
+
+<table width="75%" class="ruleTable indent">
+ <tr><td class="ruleTableHeader"><fmt:message key="help.version.title"/></td><td class="ruleTableCell">${versionString} &ndash; ${buildDateString}</td></tr>
+ <tr><td class="ruleTableHeader"><fmt:message key="help.server.title"/></td><td class="ruleTableCell">${model.serverInfo} (<sub:formatBytes bytes="${model.usedMemory}"/> / <sub:formatBytes bytes="${model.totalMemory}"/>)</td></tr>
+ <tr><td class="ruleTableHeader"><fmt:message key="help.license.title"/></td><td class="ruleTableCell">
+ <a href="http://www.gnu.org/copyleft/gpl.html" target="_blank"><img style="float:right;margin-left: 10px" alt="GPL 3.0" src="<c:url value="/icons/gpl.png"/>"></a>
+ <fmt:message key="help.license.text"><fmt:param value="${model.brand}"/></fmt:message></td></tr>
+ <tr><td class="ruleTableHeader"><fmt:message key="help.homepage.title"/></td><td class="ruleTableCell"><a target="_blank" href="http://www.subsonic.org/">subsonic.org</a></td></tr>
+ <tr><td class="ruleTableHeader"><fmt:message key="help.forum.title"/></td><td class="ruleTableCell"><a target="_blank" href="http://forum.subsonic.org/">forum.subsonic.org</a></td></tr>
+ <tr><td class="ruleTableHeader"><fmt:message key="help.contact.title"/></td><td class="ruleTableCell"><fmt:message key="help.contact.text"><fmt:param value="${model.brand}"/></fmt:message></td></tr>
+</table>
+
+<p></p>
+
+<table width="75%"><tr>
+ <td><a href="<c:url value="/donate.view"/>"><img src="<spring:theme code="paypalImage"/>" alt=""></a></td>
+ <td><fmt:message key="help.donate"><fmt:param value="${model.brand}"/></fmt:message></td>
+</tr></table>
+
+<h2><img src="<spring:theme code="logImage"/>" alt="">&nbsp;<fmt:message key="help.log"/></h2>
+
+<table cellpadding="2" class="log indent">
+ <c:forEach items="${model.logEntries}" var="entry">
+ <tr>
+ <td>[<fmt:formatDate value="${entry.date}" dateStyle="short" timeStyle="long" type="both"/>]</td>
+ <td>${entry.level}</td><td>${entry.category}</td><td>${entry.message}</td>
+ </tr>
+ </c:forEach>
+</table>
+
+<p><fmt:message key="help.logfile"><fmt:param value="${model.logFile}"/></fmt:message> </p>
+
+<div class="forward"><a href="help.view?"><fmt:message key="common.refresh"/></a></div>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/helpToolTip.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/helpToolTip.jsp
new file mode 100644
index 00000000..04de8ad0
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/helpToolTip.jsp
@@ -0,0 +1,18 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+<%@ include file="include.jsp" %>
+
+<%--
+ Shows online help as a balloon tool tip.
+
+PARAMETERS
+ topic: Refers to a key in the resource bundle containing the text to display in the tool tip.
+--%>
+
+<spring:theme code="helpPopupImage" var="imageUrl"/>
+<fmt:message key="common.help" var="help"/>
+
+<div id="placeholder-${param.topic}" style="display:none">
+ <div style="font-weight:bold;"><fmt:message key="helppopup.${param.topic}.title"><fmt:param value="Subsonic"/></fmt:message></div>
+ <div><fmt:message key="helppopup.${param.topic}.text"><fmt:param value="Subsonic"/></fmt:message></div>
+</div>
+<img src="${imageUrl}" alt="${help}" title="${help}" onmouseover="TagToTip('placeholder-${param.topic}', BALLOON, true, ABOVE, true, OFFSETX, -17, PADDING, 8, WIDTH, -240, CLICKSTICKY, true, CLICKCLOSE, true)" onmouseout="UnTip()"/>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/home.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/home.jsp
new file mode 100644
index 00000000..8792d60d
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/home.jsp
@@ -0,0 +1,189 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <%@ include file="jquery.jsp" %>
+ <link href="<c:url value="/style/shadow.css"/>" rel="stylesheet">
+
+ <script type="text/javascript" language="javascript">
+ function init() {
+ <c:if test="${model.listType eq 'random'}">
+ setTimeout("refresh()", 20000);
+ </c:if>
+ }
+
+ function refresh() {
+ top.main.location.href = top.main.location.href;
+ }
+ </script>
+</head>
+<body class="mainframe bgcolor1" onload="init();">
+<h1>
+ <img src="<spring:theme code="homeImage"/>" alt="">
+ ${model.welcomeTitle}
+</h1>
+
+<c:if test="${not empty model.welcomeSubtitle}">
+ <h2>${model.welcomeSubtitle}</h2>
+</c:if>
+
+<h2>
+ <c:forTokens items="random newest starred highest frequent recent alphabetical users" delims=" " var="cat" varStatus="loopStatus">
+ <c:if test="${loopStatus.count > 1}">&nbsp;|&nbsp;</c:if>
+ <sub:url var="url" value="home.view">
+ <sub:param name="listSize" value="${model.listSize}"/>
+ <sub:param name="listType" value="${cat}"/>
+ </sub:url>
+
+ <c:choose>
+ <c:when test="${model.listType eq cat}">
+ <span class="headerSelected"><fmt:message key="home.${cat}.title"/></span>
+ </c:when>
+ <c:otherwise>
+ <a href="${url}"><fmt:message key="home.${cat}.title"/></a>
+ </c:otherwise>
+ </c:choose>
+
+ </c:forTokens>
+</h2>
+
+<c:if test="${model.isIndexBeingCreated}">
+ <p class="warning"><fmt:message key="home.scan"/></p>
+</c:if>
+
+<h2><fmt:message key="home.${model.listType}.text"/></h2>
+
+<table width="100%">
+ <tr>
+ <td style="vertical-align:top;">
+<c:choose>
+<c:when test="${model.listType eq 'users'}">
+ <table>
+ <tr>
+ <th><fmt:message key="home.chart.total"/></th>
+ <th><fmt:message key="home.chart.stream"/></th>
+ </tr>
+ <tr>
+ <td><img src="<c:url value="/userChart.view"><c:param name="type" value="total"/></c:url>" alt=""></td>
+ <td><img src="<c:url value="/userChart.view"><c:param name="type" value="stream"/></c:url>" alt=""></td>
+ </tr>
+ <tr>
+ <th><fmt:message key="home.chart.download"/></th>
+ <th><fmt:message key="home.chart.upload"/></th>
+ </tr>
+ <tr>
+ <td><img src="<c:url value="/userChart.view"><c:param name="type" value="download"/></c:url>" alt=""></td>
+ <td><img src="<c:url value="/userChart.view"><c:param name="type" value="upload"/></c:url>" alt=""></td>
+ </tr>
+</table>
+
+</c:when>
+<c:otherwise>
+
+ <table>
+ <c:forEach items="${model.albums}" var="album" varStatus="loopStatus">
+ <c:if test="${loopStatus.count % 5 == 1}">
+ <tr>
+ </c:if>
+
+ <td style="vertical-align:top">
+ <table>
+ <tr><td>
+ <c:import url="coverArt.jsp">
+ <c:param name="albumId" value="${album.id}"/>
+ <c:param name="albumName" value="${album.albumTitle}"/>
+ <c:param name="coverArtSize" value="110"/>
+ <c:param name="showLink" value="true"/>
+ <c:param name="showZoom" value="false"/>
+ <c:param name="showChange" value="false"/>
+ <c:param name="appearAfter" value="${loopStatus.count * 30}"/>
+ </c:import>
+
+ <div class="detail">
+ <c:if test="${not empty album.playCount}">
+ <fmt:message key="home.playcount"><fmt:param value="${album.playCount}"/></fmt:message>
+ </c:if>
+ <c:if test="${not empty album.lastPlayed}">
+ <fmt:formatDate value="${album.lastPlayed}" dateStyle="short" var="lastPlayedDate"/>
+ <fmt:message key="home.lastplayed"><fmt:param value="${lastPlayedDate}"/></fmt:message>
+ </c:if>
+ <c:if test="${not empty album.created}">
+ <fmt:formatDate value="${album.created}" dateStyle="short" var="creationDate"/>
+ <fmt:message key="home.created"><fmt:param value="${creationDate}"/></fmt:message>
+ </c:if>
+ <c:if test="${not empty album.rating}">
+ <c:import url="rating.jsp">
+ <c:param name="readonly" value="true"/>
+ <c:param name="rating" value="${album.rating}"/>
+ </c:import>
+ </c:if>
+ </div>
+
+ <c:choose>
+ <c:when test="${empty album.artist and empty album.albumTitle}">
+ <div class="detail"><fmt:message key="common.unknown"/></div>
+ </c:when>
+ <c:otherwise>
+ <div class="detail"><em><str:truncateNicely lower="15" upper="15">${album.artist}</str:truncateNicely></em></div>
+ <div class="detail"><str:truncateNicely lower="15" upper="15">${album.albumTitle}</str:truncateNicely></div>
+ </c:otherwise>
+ </c:choose>
+
+ </td></tr>
+ </table>
+ </td>
+ <c:if test="${loopStatus.count % 5 == 0}">
+ </tr>
+ </c:if>
+ </c:forEach>
+ </table>
+
+<table>
+ <tr>
+ <td style="padding-right:1.5em">
+ <select name="listSize" onchange="location='home.view?listType=${model.listType}&amp;listOffset=${model.listOffset}&amp;listSize=' + options[selectedIndex].value;">
+ <c:forTokens items="5 10 15 20 30 40 50" delims=" " var="size">
+ <option ${size eq model.listSize ? "selected" : ""} value="${size}"><fmt:message key="home.listsize"><fmt:param value="${size}"/></fmt:message></option>
+ </c:forTokens>
+ </select>
+ </td>
+
+ <c:choose>
+ <c:when test="${model.listType eq 'random'}">
+ <td><div class="forward"><a href="home.view?listType=random&amp;listSize=${model.listSize}"><fmt:message key="common.more"/></a></div></td>
+ </c:when>
+
+ <c:otherwise>
+ <sub:url value="home.view" var="previousUrl">
+ <sub:param name="listType" value="${model.listType}"/>
+ <sub:param name="listOffset" value="${model.listOffset - model.listSize}"/>
+ <sub:param name="listSize" value="${model.listSize}"/>
+ </sub:url>
+ <sub:url value="home.view" var="nextUrl">
+ <sub:param name="listType" value="${model.listType}"/>
+ <sub:param name="listOffset" value="${model.listOffset + model.listSize}"/>
+ <sub:param name="listSize" value="${model.listSize}"/>
+ </sub:url>
+
+ <td style="padding-right:1.5em"><fmt:message key="home.albums"><fmt:param value="${model.listOffset + 1}"/><fmt:param value="${model.listOffset + model.listSize}"/></fmt:message></td>
+ <td style="padding-right:1.5em"><div class="back"><a href="${previousUrl}"><fmt:message key="common.previous"/></a></div></td>
+ <td><div class="forward"><a href="${nextUrl}"><fmt:message key="common.next"/></a></div></td>
+ </c:otherwise>
+ </c:choose>
+ </tr>
+ </table>
+</c:otherwise>
+</c:choose>
+ </td>
+ <c:if test="${not empty model.welcomeMessage}">
+ <td style="vertical-align:top;width:20em">
+ <div style="padding:0 1em 0 1em;border-left:1px solid #<spring:theme code="detailColor"/>">
+ <sub:wiki text="${model.welcomeMessage}"/>
+ </div>
+ </td>
+ </c:if>
+ </tr>
+ </table>
+
+</body></html>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/importPlaylist.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/importPlaylist.jsp
new file mode 100644
index 00000000..4bc09b88
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/importPlaylist.jsp
@@ -0,0 +1,37 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html><head>
+ <%@ include file="head.jsp" %>
+</head>
+<body class="mainframe bgcolor1">
+
+<h1 style="padding-bottom:0.5em">
+ <fmt:message key="importPlaylist.title"/>
+</h1>
+
+<c:if test="${not empty model.playlist}">
+ <p>
+ <fmt:message key="importPlaylist.success"><fmt:param value="${model.playlist.name}"/></fmt:message>
+ <script type="text/javascript" language="javascript">
+ top.left.updatePlaylists();
+ </script>
+ </p>
+</c:if>
+
+<c:if test="${not empty model.error}">
+ <p class="warning">
+ <fmt:message key="importPlaylist.error"><fmt:param value="${model.error}"/></fmt:message>
+ </p>
+</c:if>
+
+<div style="padding-bottom: 0.25em">
+ <fmt:message key="importPlaylist.text"/>
+</div>
+<form method="post" enctype="multipart/form-data" action="importPlaylist.view">
+ <input type="file" id="file" name="file" size="40"/>
+ <input type="submit" value="<fmt:message key="common.ok"/>"/>
+</form>
+
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/include.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/include.jsp
new file mode 100644
index 00000000..41c0aa2d
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/include.jsp
@@ -0,0 +1,8 @@
+<%@ page session="false"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
+<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
+<%@ taglib prefix="sub" uri="http://subsonic.org/taglib/sub" %>
+<%@ taglib prefix="str" uri="http://jakarta.apache.org/taglibs/string-1.1" %>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/index.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/index.jsp
new file mode 100644
index 00000000..4ec7e2b0
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/index.jsp
@@ -0,0 +1,26 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <link rel="alternate" type="application/rss+xml" title="Subsonic Podcast" href="podcast.view?suffix=.rss">
+</head>
+
+<frameset rows="70,*,0" border="0" framespacing="0" frameborder="0">
+ <frame name="upper" src="top.view?">
+ <frameset cols="15%,85%" border="0" framespacing="0" frameborder="0">
+ <frame name="left" src="left.view?" marginwidth="0" marginheight="0">
+
+ <frameset rows="70%,30%" border="0" framespacing="0" frameborder="0">
+ <frameset cols="*,${model.showRight ? 230 : 0}" border="0" framespacing="0" frameborder="0">
+ <frame name="main" src="nowPlaying.view?" marginwidth="0" marginheight="0">
+ <frame name="right" src="right.view?">
+ </frameset>
+ <frame name="playQueue" src="playQueue.view?">
+ </frameset>
+ </frameset>
+ <frame name="hidden" frameborder="0" noresize="noresize">
+
+</frameset>
+
+</html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/internetRadioSettings.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/internetRadioSettings.jsp
new file mode 100644
index 00000000..ba3fbe1b
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/internetRadioSettings.jsp
@@ -0,0 +1,62 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+</head>
+<body class="mainframe bgcolor1">
+
+<c:import url="settingsHeader.jsp">
+ <c:param name="cat" value="internetRadio"/>
+</c:import>
+
+<form method="post" action="internetRadioSettings.view">
+<table class="indent">
+ <tr>
+ <th><fmt:message key="internetradiosettings.name"/></th>
+ <th><fmt:message key="internetradiosettings.streamurl"/></th>
+ <th><fmt:message key="internetradiosettings.homepageurl"/></th>
+ <th style="padding-left:1em"><fmt:message key="internetradiosettings.enabled"/></th>
+ <th style="padding-left:1em"><fmt:message key="common.delete"/></th>
+ </tr>
+
+ <c:forEach items="${model.internetRadios}" var="radio">
+ <tr>
+ <td><input type="text" name="name[${radio.id}]" size="20" value="${radio.name}"/></td>
+ <td><input type="text" name="streamUrl[${radio.id}]" size="40" value="${radio.streamUrl}"/></td>
+ <td><input type="text" name="homepageUrl[${radio.id}]" size="40" value="${radio.homepageUrl}"/></td>
+ <td align="center" style="padding-left:1em"><input type="checkbox" ${radio.enabled ? "checked" : ""} name="enabled[${radio.id}]" class="checkbox"/></td>
+ <td align="center" style="padding-left:1em"><input type="checkbox" name="delete[${radio.id}]" class="checkbox"/></td>
+ </tr>
+ </c:forEach>
+
+ <tr>
+ <th colspan="5" align="left" style="padding-top:1em"><fmt:message key="internetradiosettings.add"/></th>
+ </tr>
+
+ <tr>
+ <td><input type="text" name="name" size="20"/></td>
+ <td><input type="text" name="streamUrl" size="40"/></td>
+ <td><input type="text" name="homepageUrl" size="40"/></td>
+ <td align="center" style="padding-left:1em"><input name="enabled" checked type="checkbox" class="checkbox"/></td>
+ <td/>
+ </tr>
+
+ <tr>
+ <td style="padding-top:1.5em" colspan="5">
+ <input type="submit" value="<fmt:message key="common.save"/>" style="margin-right:0.3em">
+ <input type="button" value="<fmt:message key="common.cancel"/>" onclick="location.href='nowPlaying.view'">
+ </td>
+ </tr>
+</table>
+</form>
+
+
+<c:if test="${not empty model.error}">
+ <p class="warning"><fmt:message key="${model.error}"/></p>
+</c:if>
+
+<c:if test="${model.reload}">
+ <script language="javascript" type="text/javascript">parent.frames.left.location.href="left.view?"</script>
+</c:if>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/jquery.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/jquery.jsp
new file mode 100644
index 00000000..094b9bcc
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/jquery.jsp
@@ -0,0 +1,3 @@
+<link rel="stylesheet" href="<c:url value="/style/smoothness/jquery-ui-1.8.18.custom.css"/>" type="text/css">
+<script type="text/javascript" src="<c:url value='/script/jquery-1.7.1.min.js'/>"></script>
+<script type="text/javascript" src="<c:url value='/script/jquery-ui-1.8.18.custom.min.js'/>"></script>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/left.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/left.jsp
new file mode 100644
index 00000000..d47c25f6
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/left.jsp
@@ -0,0 +1,168 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html><head>
+ <%@ include file="head.jsp" %>
+ <%@ include file="jquery.jsp" %>
+ <script type="text/javascript" src="<c:url value="/script/scripts.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/smooth-scroll.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/engine.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/interface/playlistService.js"/>"></script>
+ <script type="text/javascript" language="javascript">
+
+ var playlists;
+
+ function init() {
+ dwr.engine.setErrorHandler(null);
+ updatePlaylists();
+ }
+
+ function updatePlaylists() {
+ playlistService.getReadablePlaylists(playlistCallback);
+ }
+
+ function createEmptyPlaylist() {
+ playlistService.createEmptyPlaylist(playlistCallback);
+ }
+
+ function playlistCallback(playlists) {
+ this.playlists = playlists;
+
+ $("#playlists").empty();
+ for (var i = 0; i < playlists.length; i++) {
+ var playlist = playlists[i];
+ $("<p class='dense'><a target='main' href='playlist.view?id=" +
+ playlist.id + "'>" + playlist.name + "&nbsp;(" + playlist.fileCount + ")</a></p>").appendTo("#playlists");
+ }
+ }
+ </script>
+</head>
+
+<body class="bgcolor2 leftframe" onload="init()">
+<a name="top"></a>
+
+<c:if test="${model.scanning}">
+ <div style="padding-bottom:1.0em">
+ <div class="warning"><fmt:message key="left.scanning"/></div>
+ <div class="forward"><a href="left.view"><fmt:message key="common.refresh"/></a></div>
+ </div>
+</c:if>
+
+<div style="padding-bottom:0.5em">
+ <c:forEach items="${model.indexes}" var="index">
+ <a href="#${index.index}" accesskey="${index.index}">${index.index}</a>
+ </c:forEach>
+</div>
+
+<c:if test="${model.statistics.songCount gt 0}">
+ <div class="detail">
+ <fmt:message key="left.statistics">
+ <fmt:param value="${model.statistics.artistCount}"/>
+ <fmt:param value="${model.statistics.albumCount}"/>
+ <fmt:param value="${model.statistics.songCount}"/>
+ <fmt:param value="${model.bytes}"/>
+ <fmt:param value="${model.hours}"/>
+ </fmt:message>
+ </div>
+</c:if>
+
+<c:if test="${fn:length(model.musicFolders) > 1}">
+ <div style="padding-top:1em">
+ <select name="musicFolderId" style="width:100%" onchange="location='left.view?musicFolderId=' + options[selectedIndex].value;" >
+ <option value="-1"><fmt:message key="left.allfolders"/></option>
+ <c:forEach items="${model.musicFolders}" var="musicFolder">
+ <option ${model.selectedMusicFolder.id == musicFolder.id ? "selected" : ""} value="${musicFolder.id}">${musicFolder.name}</option>
+ </c:forEach>
+ </select>
+ </div>
+</c:if>
+
+<c:if test="${not empty model.shortcuts}">
+ <h2 class="bgcolor1"><fmt:message key="left.shortcut"/></h2>
+ <c:forEach items="${model.shortcuts}" var="shortcut">
+ <p class="dense" style="padding-left:0.5em">
+ <sub:url value="main.view" var="mainUrl">
+ <sub:param name="id" value="${shortcut.id}"/>
+ </sub:url>
+ <a target="main" href="${mainUrl}">${shortcut.name}</a>
+ </p>
+ </c:forEach>
+</c:if>
+
+<h2 class="bgcolor1"><fmt:message key="left.playlists"/></h2>
+<div style='padding-left:0.5em'>
+ <div id="playlists"></div>
+ <div style="margin-top: 0.3em"><a href="javascript:noop()" onclick="createEmptyPlaylist()"><fmt:message key="left.createplaylist"/></a></div>
+ <div><a href="importPlaylist.view" target="main"><fmt:message key="left.importplaylist"/></a></div>
+</div>
+
+<c:if test="${not empty model.radios}">
+ <h2 class="bgcolor1"><fmt:message key="left.radio"/></h2>
+ <c:forEach items="${model.radios}" var="radio">
+ <p class="dense">
+ <a target="hidden" href="${radio.streamUrl}">
+ <img src="<spring:theme code="playImage"/>" alt="<fmt:message key="common.play"/>" title="<fmt:message key="common.play"/>"></a>
+ <c:choose>
+ <c:when test="${empty radio.homepageUrl}">
+ ${radio.name}
+ </c:when>
+ <c:otherwise>
+ <a target="main" href="${radio.homepageUrl}">${radio.name}</a>
+ </c:otherwise>
+ </c:choose>
+ </p>
+ </c:forEach>
+</c:if>
+
+<c:forEach items="${model.indexedArtists}" var="entry">
+ <table class="bgcolor1" style="width:100%;padding:0;margin:1em 0 0 0;border:0">
+ <tr style="padding:0;margin:0;border:0">
+ <th style="text-align:left;padding:0;margin:0;border:0"><a name="${entry.key.index}"></a>
+ <h2 style="padding:0;margin:0;border:0">${entry.key.index}</h2>
+ </th>
+ <th style="text-align:right;">
+ <a href="#top"><img src="<spring:theme code="upImage"/>" alt=""></a>
+ </th>
+ </tr>
+ </table>
+
+ <c:forEach items="${entry.value}" var="artist">
+ <p class="dense" style="padding-left:0.5em">
+ <span title="${artist.name}">
+ <sub:url value="main.view" var="mainUrl">
+ <c:forEach items="${artist.mediaFiles}" var="mediaFile">
+ <sub:param name="id" value="${mediaFile.id}"/>
+ </c:forEach>
+ </sub:url>
+ <a target="main" href="${mainUrl}"><str:truncateNicely upper="${model.captionCutoff}">${artist.name}</str:truncateNicely></a>
+ </span>
+ </p>
+ </c:forEach>
+</c:forEach>
+
+<div style="padding-top:1em"></div>
+
+<c:forEach items="${model.singleSongs}" var="song">
+ <p class="dense" style="padding-left:0.5em">
+ <span title="${song.title}">
+ <c:import url="playAddDownload.jsp">
+ <c:param name="id" value="${song.id}"/>
+ <c:param name="playEnabled" value="${model.user.streamRole and not model.partyMode}"/>
+ <c:param name="addEnabled" value="${model.user.streamRole}"/>
+ <c:param name="downloadEnabled" value="${model.user.downloadRole and not model.partyMode}"/>
+ <c:param name="video" value="${song.video and model.player.web}"/>
+ </c:import>
+ <str:truncateNicely upper="${model.captionCutoff}">${song.title}</str:truncateNicely>
+ </span>
+ </p>
+</c:forEach>
+
+<div style="height:5em"></div>
+
+<div class="bgcolor2" style="opacity: 1.0; clear: both; position: fixed; bottom: 0; right: 0; left: 0;
+ padding: 0.25em 0.75em 0.25em 0.75em; border-top:1px solid black; max-width: 850px;">
+ <c:forEach items="${model.indexes}" var="index">
+ <a href="#${index.index}">${index.index}</a>
+ </c:forEach>
+</div>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/login.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/login.jsp
new file mode 100644
index 00000000..0173ca8c
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/login.jsp
@@ -0,0 +1,64 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <script type="text/javascript">
+ if (window != window.top) {
+ top.location.href = location.href;
+ }
+ </script>
+
+</head>
+<body class="mainframe bgcolor1" onload="document.getElementById('j_username').focus()">
+
+<form action="<c:url value="/j_acegi_security_check"/>" method="POST">
+ <div class="bgcolor2" align="center" style="border:1px solid black; padding:20px 50px 20px 50px; margin-top:100px">
+
+ <div style="margin-bottom:1em;max-width:50em;text-align:left;"><sub:wiki text="${model.loginMessage}"/></div>
+
+ <table>
+ <tr>
+ <td colspan="2" align="left" style="padding-bottom:10px">
+ <img src="<spring:theme code="logoImage"/>" alt="">
+ </td>
+ </tr>
+ <tr>
+ <td align="left" style="padding-right:10px"><fmt:message key="login.username"/></td>
+ <td align="left"><input type="text" id="j_username" name="j_username" style="width:12em" tabindex="1"></td>
+ </tr>
+
+ <tr>
+ <td align="left" style="padding-bottom:10px"><fmt:message key="login.password"/></td>
+ <td align="left" style="padding-bottom:10px"><input type="password" name="j_password" style="width:12em" tabindex="2"></td>
+ </tr>
+
+ <tr>
+ <td align="left"><input name="submit" type="submit" value="<fmt:message key="login.login"/>" tabindex="4"></td>
+ <td align="left" class="detail">
+ <input type="checkbox" name="_acegi_security_remember_me" id="remember" class="checkbox" tabindex="3">
+ <label for="remember"><fmt:message key="login.remember"/></label>
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td align="left" class="detail"><a href="recover.view"><fmt:message key="login.recover"/></a></td>
+ </tr>
+
+ <c:if test="${model.logout}">
+ <tr><td colspan="2" style="padding-top:10px"><b><fmt:message key="login.logout"/></b></td></tr>
+ </c:if>
+ <c:if test="${model.error}">
+ <tr><td colspan="2" style="padding-top:10px"><b class="warning"><fmt:message key="login.error"/></b></td></tr>
+ </c:if>
+
+ </table>
+
+ <c:if test="${model.insecure}">
+ <p><b class="warning"><fmt:message key="login.insecure"><fmt:param value="${model.brand}"/></fmt:message></b></p>
+ </c:if>
+
+ </div>
+</form>
+</body>
+</html>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/lyrics.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/lyrics.jsp
new file mode 100644
index 00000000..82d5ce37
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/lyrics.jsp
@@ -0,0 +1,79 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <title><fmt:message key="lyrics.title"/></title>
+ <script type="text/javascript" src="<c:url value="/dwr/interface/lyricsService.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/engine.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/util.js"/>"></script>
+
+ <script type="text/javascript" language="javascript">
+
+ dwr.engine.setErrorHandler(null);
+
+ function init() {
+ getLyrics('${model.artist}', '${model.song}');
+ }
+
+ function getLyrics(artist, song) {
+ $("wait").style.display = "inline";
+ $("lyrics").style.display = "none";
+ $("noLyricsFound").style.display = "none";
+ lyricsService.getLyrics(artist, song, getLyricsCallback);
+ }
+
+ function getLyricsCallback(lyricsInfo) {
+ dwr.util.setValue("lyricsHeader", lyricsInfo.artist + " - " + lyricsInfo.title);
+ var lyrics;
+ if (lyricsInfo.lyrics != null) {
+ lyrics = lyricsInfo.lyrics.replace(/\n/g, "<br>");
+ }
+ dwr.util.setValue("lyricsText", lyrics, { escapeHtml:false });
+ $("wait").style.display = "none";
+ if (lyrics != null) {
+ $("lyrics").style.display = "inline";
+ } else {
+ $("noLyricsFound").style.display = "inline";
+ }
+ }
+ </script>
+
+</head>
+<body class="mainframe bgcolor1" onload="init();">
+
+<table>
+ <tr>
+ <td><fmt:message key="lyrics.artist"/></td>
+ <td style="padding-left:0.50em"><input id="artist" type="text" size="40" value="${model.artist}" tabindex="1"/></td>
+ <td style="padding-left:0.75em"><input type="submit" value="<fmt:message key="lyrics.search"/>" style="width:6em"
+ onclick="getLyrics(dwr.util.getValue('artist'), dwr.util.getValue('song'))" tabindex="3"/></td>
+ </tr>
+ <tr>
+ <td><fmt:message key="lyrics.song"/></td>
+ <td style="padding-left:0.50em"><input id="song" type="text" size="40" value="${model.song}" tabindex="2"/></td>
+ <td style="padding-left:0.75em"><input type="submit" value="<fmt:message key="common.close"/>" style="width:6em"
+ onclick="self.close()" tabindex="4"/></td>
+ </tr>
+</table>
+
+<hr/>
+<h2 id="wait"><fmt:message key="lyrics.wait"/></h2>
+<h2 id="noLyricsFound" style="display:none"><fmt:message key="lyrics.nolyricsfound"/></h2>
+
+<div id="lyrics" style="display:none;">
+ <h2 id="lyricsHeader" style="text-align:center;margin-bottom:1em"></h2>
+
+ <div id="lyricsText"></div>
+
+ <p class="detail" style="text-align:right">
+ <fmt:message key="lyrics.courtesy"/>
+ </p>
+</div>
+
+<hr/>
+<p style="text-align:center">
+ <a href="javascript:self.close()">[<fmt:message key="common.close"/>]</a>
+</p>
+
+</body>
+</html>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/main.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/main.jsp
new file mode 100644
index 00000000..fbbd553d
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/main.jsp
@@ -0,0 +1,479 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<%--@elvariable id="model" type="java.util.Map"--%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <%@ include file="jquery.jsp" %>
+ <link href="<c:url value="/style/shadow.css"/>" rel="stylesheet">
+ <c:if test="${not model.updateNowPlaying}">
+ <meta http-equiv="refresh" content="180;URL=nowPlaying.view?">
+ </c:if>
+ <script type="text/javascript" src="<c:url value="/dwr/engine.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/interface/starService.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/interface/playlistService.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/fancyzoom/FancyZoom.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/fancyzoom/FancyZoomHTML.js"/>"></script>
+</head><body class="mainframe bgcolor1" onload="init();">
+
+<sub:url value="createShare.view" var="shareUrl">
+ <sub:param name="dir" value="${model.dir.path}"/>
+</sub:url>
+<sub:url value="download.view" var="downloadUrl">
+ <sub:param name="dir" value="${model.dir.path}"/>
+</sub:url>
+<sub:url value="appendPlaylist.view" var="appendPlaylistUrl">
+ <sub:param name="dir" value="${model.dir.path}"/>
+</sub:url>
+
+<script type="text/javascript" language="javascript">
+ function init() {
+ setupZoom('<c:url value="/"/>');
+
+ $("#dialog-select-playlist").dialog({resizable: true, height: 220, position: 'top', modal: true, autoOpen: false,
+ buttons: {
+ "<fmt:message key="common.cancel"/>": function() {
+ $(this).dialog("close");
+ }
+ }});
+ }
+
+ <!-- actionSelected() is invoked when the users selects from the "More actions..." combo box. -->
+ function actionSelected(id) {
+
+ if (id == "top") {
+ return;
+ } else if (id == "selectAll") {
+ selectAll(true);
+ } else if (id == "selectNone") {
+ selectAll(false);
+ } else if (id == "share") {
+ parent.frames.main.location.href = "${shareUrl}&" + getSelectedIndexes();
+ } else if (id == "download") {
+ location.href = "${downloadUrl}&" + getSelectedIndexes();
+ } else if (id == "appendPlaylist") {
+ onAppendPlaylist();
+ }
+ $("#moreActions").prop("selectedIndex", 0);
+ }
+
+ function getSelectedIndexes() {
+ var result = "";
+ for (var i = 0; i < ${fn:length(model.children)}; i++) {
+ var checkbox = $("#songIndex" + i);
+ if (checkbox != null && checkbox.is(":checked")) {
+ result += "i=" + i + "&";
+ }
+ }
+ return result;
+ }
+
+ function selectAll(b) {
+ for (var i = 0; i < ${fn:length(model.children)}; i++) {
+ var checkbox = $("#songIndex" + i);
+ if (checkbox != null) {
+ if (b) {
+ checkbox.attr("checked", "checked");
+ } else {
+ checkbox.removeAttr("checked");
+ }
+ }
+ }
+ }
+
+ function toggleStar(mediaFileId, imageId) {
+ if ($(imageId).attr("src").indexOf("<spring:theme code="ratingOnImage"/>") != -1) {
+ $(imageId).attr("src", "<spring:theme code="ratingOffImage"/>");
+ starService.unstar(mediaFileId);
+ }
+ else if ($(imageId).attr("src").indexOf("<spring:theme code="ratingOffImage"/>") != -1) {
+ $(imageId).attr("src", "<spring:theme code="ratingOnImage"/>");
+ starService.star(mediaFileId);
+ }
+ }
+
+ function onAppendPlaylist() {
+ playlistService.getWritablePlaylists(playlistCallback);
+ }
+ function playlistCallback(playlists) {
+ $("#dialog-select-playlist-list").empty();
+ for (var i = 0; i < playlists.length; i++) {
+ var playlist = playlists[i];
+ $("<p class='dense'><b><a href='#' onclick='appendPlaylist(" + playlist.id + ")'>" + playlist.name + "</a></b></p>").appendTo("#dialog-select-playlist-list");
+ }
+ $("#dialog-select-playlist").dialog("open");
+ }
+ function appendPlaylist(playlistId) {
+ $("#dialog-select-playlist").dialog("close");
+
+ var mediaFileIds = new Array();
+ for (var i = 0; i < ${fn:length(model.children)}; i++) {
+ var checkbox = $("#songIndex" + i);
+ if (checkbox && checkbox.is(":checked")) {
+ mediaFileIds.push($("#songId" + i).html());
+ }
+ }
+ playlistService.appendToPlaylist(playlistId, mediaFileIds, function (){top.left.updatePlaylists();});
+ }
+
+</script>
+
+<c:if test="${model.updateNowPlaying}">
+
+ <script type="text/javascript" language="javascript">
+ // Variable used by javascript in playlist.jsp
+ var updateNowPlaying = true;
+ </script>
+</c:if>
+
+<h1>
+ <a href="#" onclick="toggleStar(${model.dir.id}, '#starImage'); return false;">
+ <c:choose>
+ <c:when test="${not empty model.dir.starredDate}">
+ <img id="starImage" src="<spring:theme code="ratingOnImage"/>" alt="">
+ </c:when>
+ <c:otherwise>
+ <img id="starImage" src="<spring:theme code="ratingOffImage"/>" alt="">
+ </c:otherwise>
+ </c:choose>
+ </a>
+
+ <c:forEach items="${model.ancestors}" var="ancestor">
+ <sub:url value="main.view" var="ancestorUrl">
+ <sub:param name="id" value="${ancestor.id}"/>
+ </sub:url>
+ <a href="${ancestorUrl}">${ancestor.name}</a> &raquo;
+ </c:forEach>
+ ${model.dir.name}
+
+ <c:if test="${model.dir.album and model.averageRating gt 0}">
+ &nbsp;&nbsp;
+ <c:import url="rating.jsp">
+ <c:param name="path" value="${model.dir.path}"/>
+ <c:param name="readonly" value="true"/>
+ <c:param name="rating" value="${model.averageRating}"/>
+ </c:import>
+ </c:if>
+</h1>
+
+<c:if test="${not model.partyMode}">
+<h2>
+ <c:if test="${model.navigateUpAllowed}">
+ <sub:url value="main.view" var="upUrl">
+ <sub:param name="id" value="${model.parent.id}"/>
+ </sub:url>
+ <a href="${upUrl}"><fmt:message key="main.up"/></a>
+ <c:set var="needSep" value="true"/>
+ </c:if>
+
+ <c:if test="${model.user.streamRole}">
+ <c:if test="${needSep}">|</c:if>
+ <a href="#" onclick="top.playQueue.onPlay(${model.dir.id});"><fmt:message key="main.playall"/></a> |
+ <a href="#" onclick="top.playQueue.onPlayRandom(${model.dir.id}, 10);"><fmt:message key="main.playrandom"/></a> |
+ <a href="#" onclick="top.playQueue.onAdd(${model.dir.id});"><fmt:message key="main.addall"/></a>
+ <c:set var="needSep" value="true"/>
+ </c:if>
+
+ <c:if test="${model.dir.album}">
+
+ <c:if test="${model.user.downloadRole}">
+ <sub:url value="download.view" var="downloadUrl">
+ <sub:param name="id" value="${model.dir.id}"/>
+ </sub:url>
+ <c:if test="${needSep}">|</c:if>
+ <a href="${downloadUrl}"><fmt:message key="common.download"/></a>
+ <c:set var="needSep" value="true"/>
+ </c:if>
+
+ <c:if test="${model.user.coverArtRole}">
+ <sub:url value="editTags.view" var="editTagsUrl">
+ <sub:param name="id" value="${model.dir.id}"/>
+ </sub:url>
+ <c:if test="${needSep}">|</c:if>
+ <a href="${editTagsUrl}"><fmt:message key="main.tags"/></a>
+ <c:set var="needSep" value="true"/>
+ </c:if>
+
+ </c:if>
+
+ <c:if test="${model.user.commentRole}">
+ <c:if test="${needSep}">|</c:if>
+ <a href="javascript:toggleComment()"><fmt:message key="main.comment"/></a>
+ </c:if>
+</h2>
+</c:if>
+
+<c:if test="${model.dir.album}">
+
+ <div class="detail">
+ <c:if test="${model.user.commentRole}">
+ <c:import url="rating.jsp">
+ <c:param name="path" value="${model.dir.path}"/>
+ <c:param name="readonly" value="false"/>
+ <c:param name="rating" value="${model.userRating}"/>
+ </c:import>
+ </c:if>
+
+ <c:if test="${model.user.shareRole}">
+ <a href="${shareUrl}"><img src="<spring:theme code="shareFacebookImage"/>" alt=""></a>
+ <a href="${shareUrl}"><img src="<spring:theme code="shareTwitterImage"/>" alt=""></a>
+ <a href="${shareUrl}"><img src="<spring:theme code="shareGooglePlusImage"/>" alt=""></a>
+ <a href="${shareUrl}"><span class="detail"><fmt:message key="main.sharealbum"/></span></a> |
+ </c:if>
+
+ <c:if test="${not empty model.artist and not empty model.album}">
+ <sub:url value="http://www.google.com/search" var="googleUrl" encoding="UTF-8">
+ <sub:param name="q" value="\"${model.artist}\" \"${model.album}\""/>
+ </sub:url>
+ <sub:url value="http://en.wikipedia.org/wiki/Special:Search" var="wikipediaUrl" encoding="UTF-8">
+ <sub:param name="search" value="\"${model.album}\""/>
+ <sub:param name="go" value="Go"/>
+ </sub:url>
+ <sub:url value="allmusic.view" var="allmusicUrl">
+ <sub:param name="album" value="${model.album}"/>
+ </sub:url>
+ <sub:url value="http://www.last.fm/search" var="lastFmUrl" encoding="UTF-8">
+ <sub:param name="q" value="\"${model.artist}\" \"${model.album}\""/>
+ <sub:param name="type" value="album"/>
+ </sub:url>
+ <fmt:message key="top.search"/> <a target="_blank" href="${googleUrl}">Google</a> |
+ <a target="_blank" href="${wikipediaUrl}">Wikipedia</a> |
+ <a target="_blank" href="${allmusicUrl}">allmusic</a> |
+ <a target="_blank" href="${lastFmUrl}">Last.fm</a>
+ </c:if>
+ </div>
+ <div class="detail" style="padding-top:0.2em">
+ <fmt:message key="main.playcount"><fmt:param value="${model.dir.playCount}"/></fmt:message>
+ <c:if test="${not empty model.dir.lastPlayed}">
+ <fmt:message key="main.lastplayed">
+ <fmt:param><fmt:formatDate type="date" dateStyle="long" value="${model.dir.lastPlayed}"/></fmt:param>
+ </fmt:message>
+ </c:if>
+ </div>
+
+</c:if>
+
+<div id="comment" class="albumComment"><sub:wiki text="${model.dir.comment}"/></div>
+
+<div id="commentForm" style="display:none">
+ <form method="post" action="setMusicFileInfo.view">
+ <input type="hidden" name="action" value="comment">
+ <input type="hidden" name="path" value="${model.dir.path}">
+ <textarea name="comment" rows="6" cols="70">${model.dir.comment}</textarea>
+ <input type="submit" value="<fmt:message key="common.save"/>">
+ </form>
+ <fmt:message key="main.wiki"/>
+</div>
+
+<script type='text/javascript'>
+ function toggleComment() {
+ $("#commentForm").toggle();
+ $("#comment").toggle();
+ }
+</script>
+
+
+<table cellpadding="10" style="width:100%">
+<tr style="vertical-align:top;">
+ <td style="vertical-align:top;">
+ <table style="border-collapse:collapse;white-space:nowrap">
+ <c:set var="cutoff" value="${model.visibility.captionCutoff}"/>
+ <c:forEach items="${model.children}" var="child" varStatus="loopStatus">
+ <%--@elvariable id="child" type="net.sourceforge.subsonic.domain.MediaFile"--%>
+ <c:choose>
+ <c:when test="${loopStatus.count % 2 == 1}">
+ <c:set var="class" value="class='bgcolor2'"/>
+ </c:when>
+ <c:otherwise>
+ <c:set var="class" value=""/>
+ </c:otherwise>
+ </c:choose>
+
+ <tr style="margin:0;padding:0;border:0">
+ <c:import url="playAddDownload.jsp">
+ <c:param name="id" value="${child.id}"/>
+ <c:param name="video" value="${child.video and model.player.web}"/>
+ <c:param name="playEnabled" value="${model.user.streamRole and not model.partyMode}"/>
+ <c:param name="addEnabled" value="${model.user.streamRole and (not model.partyMode or not child.directory)}"/>
+ <c:param name="downloadEnabled" value="${model.user.downloadRole and not model.partyMode}"/>
+ <c:param name="starEnabled" value="true"/>
+ <c:param name="starred" value="${not empty child.starredDate}"/>
+ <c:param name="asTable" value="true"/>
+ </c:import>
+
+ <c:choose>
+ <c:when test="${child.directory}">
+ <sub:url value="main.view" var="childUrl">
+ <sub:param name="id" value="${child.id}"/>
+ </sub:url>
+ <td style="padding-left:0.25em" colspan="3">
+ <a href="${childUrl}" title="${child.name}"><span style="white-space:nowrap;"><str:truncateNicely upper="${cutoff}">${child.name}</str:truncateNicely></span></a>
+ </td>
+ <td style="padding-left:1.25em"><c:if test="${model.showAlbumYear and not empty child.year}"><span class="detail">${child.year}</span></c:if></td>
+ </c:when>
+
+ <c:otherwise>
+ <td ${class} style="padding-left:0.25em"><input type="checkbox" class="checkbox" id="songIndex${loopStatus.count - 1}">
+ <span id="songId${loopStatus.count - 1}" style="display: none">${child.id}</span></td>
+
+ <c:if test="${model.visibility.trackNumberVisible}">
+ <td ${class} style="padding-right:0.5em;text-align:right">
+ <span class="detail">${child.trackNumber}</span>
+ </td>
+ </c:if>
+
+ <td ${class} style="padding-right:1.25em;white-space:nowrap">
+ <span title="${child.title}"><str:truncateNicely upper="${cutoff}">${fn:escapeXml(child.title)}</str:truncateNicely></span>
+ </td>
+
+ <c:if test="${model.visibility.albumVisible}">
+ <td ${class} style="padding-right:1.25em;white-space:nowrap">
+ <span class="detail" title="${child.albumName}"><str:truncateNicely upper="${cutoff}">${fn:escapeXml(child.albumName)}</str:truncateNicely></span>
+ </td>
+ </c:if>
+
+ <c:if test="${model.visibility.artistVisible and model.multipleArtists}">
+ <td ${class} style="padding-right:1.25em;white-space:nowrap">
+ <span class="detail" title="${child.artist}"><str:truncateNicely upper="${cutoff}">${fn:escapeXml(child.artist)}</str:truncateNicely></span>
+ </td>
+ </c:if>
+
+ <c:if test="${model.visibility.genreVisible}">
+ <td ${class} style="padding-right:1.25em;white-space:nowrap">
+ <span class="detail">${child.genre}</span>
+ </td>
+ </c:if>
+
+ <c:if test="${model.visibility.yearVisible}">
+ <td ${class} style="padding-right:1.25em">
+ <span class="detail">${child.year}</span>
+ </td>
+ </c:if>
+
+ <c:if test="${model.visibility.formatVisible}">
+ <td ${class} style="padding-right:1.25em">
+ <span class="detail">${fn:toLowerCase(child.format)}</span>
+ </td>
+ </c:if>
+
+ <c:if test="${model.visibility.fileSizeVisible}">
+ <td ${class} style="padding-right:1.25em;text-align:right">
+ <span class="detail"><sub:formatBytes bytes="${child.fileSize}"/></span>
+ </td>
+ </c:if>
+
+ <c:if test="${model.visibility.durationVisible}">
+ <td ${class} style="padding-right:1.25em;text-align:right">
+ <span class="detail">${child.durationString}</span>
+ </td>
+ </c:if>
+
+ <c:if test="${model.visibility.bitRateVisible}">
+ <td ${class} style="padding-right:0.25em">
+ <span class="detail">
+ <c:if test="${not empty child.bitRate}">
+ ${child.bitRate} Kbps ${child.variableBitRate ? "vbr" : ""}
+ </c:if>
+ <c:if test="${child.video and not empty child.width and not empty child.height}">
+ (${child.width}x${child.height})
+ </c:if>
+ </span>
+ </td>
+ </c:if>
+
+
+ </c:otherwise>
+ </c:choose>
+ </tr>
+ </c:forEach>
+ </table>
+ </td>
+
+ <td style="vertical-align:top;width:100%">
+ <c:forEach items="${model.coverArts}" var="coverArt" varStatus="loopStatus">
+ <div style="float:left; padding:5px">
+ <c:import url="coverArt.jsp">
+ <c:param name="albumId" value="${coverArt.id}"/>
+ <c:param name="albumName" value="${coverArt.name}"/>
+ <c:param name="coverArtSize" value="${model.coverArtSize}"/>
+ <c:param name="showLink" value="${coverArt ne model.dir}"/>
+ <c:param name="showZoom" value="${coverArt eq model.dir}"/>
+ <c:param name="showChange" value="${(coverArt eq model.dir) and model.user.coverArtRole}"/>
+ <c:param name="showCaption" value="true"/>
+ <c:param name="appearAfter" value="${loopStatus.count * 30}"/>
+ </c:import>
+ </div>
+ </c:forEach>
+
+ <c:if test="${model.showGenericCoverArt}">
+ <div style="float:left; padding:5px">
+ <c:import url="coverArt.jsp">
+ <c:param name="albumId" value="${model.dir.id}"/>
+ <c:param name="coverArtSize" value="${model.coverArtSize}"/>
+ <c:param name="showLink" value="false"/>
+ <c:param name="showZoom" value="false"/>
+ <c:param name="showChange" value="${model.user.coverArtRole}"/>
+ <c:param name="appearAfter" value="0"/>
+ </c:import>
+ </div>
+ </c:if>
+ </td>
+
+ <td style="vertical-align:top;">
+ <div style="padding:0 1em 0 1em;">
+ <c:if test="${not empty model.ad}">
+ <div class="detail" style="text-align:center">
+ ${model.ad}
+ <br/>
+ <br/>
+ <sub:url value="donate.view" var="donateUrl">
+ <sub:param name="path" value="${model.dir.path}"/>
+ </sub:url>
+ <fmt:message key="main.donate"><fmt:param value="${donateUrl}"/><fmt:param value="${model.brand}"/></fmt:message>
+ </div>
+ </c:if>
+ </div>
+ </td>
+</tr>
+</table>
+
+<select id="moreActions" onchange="actionSelected(this.options[selectedIndex].id);" style="margin-bottom:1.0em">
+ <option id="top" selected="selected"><fmt:message key="main.more"/></option>
+ <option style="color:blue;"><fmt:message key="main.more.selection"/></option>
+ <option id="selectAll">&nbsp;&nbsp;&nbsp;&nbsp;<fmt:message key="playlist.more.selectall"/></option>
+ <option id="selectNone">&nbsp;&nbsp;&nbsp;&nbsp;<fmt:message key="playlist.more.selectnone"/></option>
+ <c:if test="${model.user.shareRole}">
+ <option id="share">&nbsp;&nbsp;&nbsp;&nbsp;<fmt:message key="main.more.share"/></option>
+ </c:if>
+ <c:if test="${model.user.downloadRole}">
+ <option id="download">&nbsp;&nbsp;&nbsp;&nbsp;<fmt:message key="common.download"/></option>
+ </c:if>
+ <option id="appendPlaylist">&nbsp;&nbsp;&nbsp;&nbsp;<fmt:message key="playlist.append"/></option>
+</select>
+
+<div style="padding-bottom: 1em">
+ <c:if test="${not empty model.previousAlbum}">
+ <sub:url value="main.view" var="previousUrl">
+ <sub:param name="id" value="${model.previousAlbum.id}"/>
+ </sub:url>
+ <div class="back" style="float:left;padding-right:10pt"><a href="${previousUrl}" title="${model.previousAlbum.name}">
+ <str:truncateNicely upper="30">${fn:escapeXml(model.previousAlbum.name)}</str:truncateNicely>
+ </a></div>
+ </c:if>
+ <c:if test="${not empty model.nextAlbum}">
+ <sub:url value="main.view" var="nextUrl">
+ <sub:param name="id" value="${model.nextAlbum.id}"/>
+ </sub:url>
+ <div class="forward" style="float:left"><a href="${nextUrl}" title="${model.nextAlbum.name}">
+ <str:truncateNicely upper="30">${fn:escapeXml(model.nextAlbum.name)}</str:truncateNicely>
+ </a></div>
+ </c:if>
+</div>
+
+<div id="dialog-select-playlist" title="<fmt:message key="main.addtoplaylist.title"/>" style="display: none;">
+ <p><fmt:message key="main.addtoplaylist.text"/></p>
+ <div id="dialog-select-playlist-list"></div>
+</div>
+
+</body>
+</html>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/more.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/more.jsp
new file mode 100644
index 00000000..18be91fe
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/more.jsp
@@ -0,0 +1,159 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <style type="text/css">
+ #progressBar {width: 350px; height: 10px; border: 1px solid black; display:none;}
+ #progressBarContent {width: 0; height: 10px; background: url("<c:url value="/icons/progress.png"/>") repeat;}
+ </style>
+ <script type="text/javascript" src="<c:url value="/dwr/interface/transferService.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/engine.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/util.js"/>"></script>
+
+ <script type="text/javascript">
+ function refreshProgress() {
+ transferService.getUploadInfo(updateProgress);
+ }
+
+ function updateProgress(uploadInfo) {
+
+ var progressBar = document.getElementById("progressBar");
+ var progressBarContent = document.getElementById("progressBarContent");
+ var progressText = document.getElementById("progressText");
+
+
+ if (uploadInfo.bytesTotal > 0) {
+ var percent = Math.ceil((uploadInfo.bytesUploaded / uploadInfo.bytesTotal) * 100);
+ var width = parseInt(percent * 3.5) + 'px';
+ progressBarContent.style.width = width;
+ progressText.innerHTML = percent + "<fmt:message key="more.upload.progress"/>";
+ progressBar.style.display = "block";
+ progressText.style.display = "block";
+ window.setTimeout("refreshProgress()", 1000);
+ } else {
+ progressBar.style.display = "none";
+ progressText.style.display = "none";
+ window.setTimeout("refreshProgress()", 5000);
+ }
+ }
+ </script>
+
+</head>
+<body class="mainframe bgcolor1" onload="${model.user.uploadRole ? "refreshProgress()" : ""}">
+
+<h1>
+ <img src="<spring:theme code="moreImage"/>" alt=""/>
+ <fmt:message key="more.title"/>
+</h1>
+
+<c:if test="${model.user.streamRole}">
+ <h2><img src="<spring:theme code="randomImage"/>" alt=""/>&nbsp;<fmt:message key="more.random.title"/></h2>
+
+ <form method="post" action="randomPlayQueue.view?">
+ <table>
+ <tr>
+ <td><fmt:message key="more.random.text"/></td>
+ <td>
+ <select name="size">
+ <option value="5"><fmt:message key="more.random.songs"><fmt:param value="5"/></fmt:message></option>
+ <option value="10" selected="true"><fmt:message key="more.random.songs"><fmt:param value="10"/></fmt:message></option>
+ <option value="20"><fmt:message key="more.random.songs"><fmt:param value="20"/></fmt:message></option>
+ <option value="50"><fmt:message key="more.random.songs"><fmt:param value="50"/></fmt:message></option>
+ </select>
+ </td>
+ <td><fmt:message key="more.random.genre"/></td>
+ <td>
+ <select name="genre">
+ <option value="any"><fmt:message key="more.random.anygenre"/></option>
+ <c:forEach items="${model.genres}" var="genre">
+ <option value="${genre}"><str:truncateNicely upper="20">${genre}</str:truncateNicely></option>
+ </c:forEach>
+ </select>
+ </td>
+ <td><fmt:message key="more.random.year"/></td>
+ <td>
+ <select name="year">
+ <option value="any"><fmt:message key="more.random.anyyear"/></option>
+
+ <c:forEach begin="0" end="${model.currentYear - 2006}" var="yearOffset">
+ <c:set var="year" value="${model.currentYear - yearOffset}"/>
+ <option value="${year} ${year}">${year}</option>
+ </c:forEach>
+
+ <option value="2005 2010">2005 &ndash; 2010</option>
+ <option value="2000 2005">2000 &ndash; 2005</option>
+ <option value="1990 2000">1990 &ndash; 2000</option>
+ <option value="1980 1990">1980 &ndash; 1990</option>
+ <option value="1970 1980">1970 &ndash; 1980</option>
+ <option value="1960 1970">1960 &ndash; 1970</option>
+ <option value="1950 1960">1950 &ndash; 1960</option>
+ <option value="0 1949">&lt; 1950</option>
+ </select>
+ </td>
+ <td><fmt:message key="more.random.folder"/></td>
+ <td>
+ <select name="musicFolderId">
+ <option value="-1"><fmt:message key="more.random.anyfolder"/></option>
+ <c:forEach items="${model.musicFolders}" var="musicFolder">
+ <option value="${musicFolder.id}">${musicFolder.name}</option>
+ </c:forEach>
+ </select>
+ </td>
+ <td>
+ <input type="submit" value="<fmt:message key="more.random.ok"/>">
+ </td>
+ </tr>
+ <c:if test="${not model.clientSidePlaylist}">
+ <tr>
+ <td colspan="9">
+ <input type="checkbox" name="autoRandom" id="autoRandom" class="checkbox"/>
+ <label for="autoRandom"><fmt:message key="more.random.auto"/></label>
+ </td>
+ </tr>
+ </c:if>
+ </table>
+ </form>
+</c:if>
+<h2><img src="<spring:theme code="androidImage"/>" alt=""/>&nbsp;<fmt:message key="more.apps.title"/></h2>
+<fmt:message key="more.apps.text"/>
+
+<h2><img src="<spring:theme code="wapImage"/>" alt=""/>&nbsp;<fmt:message key="more.mobile.title"/></h2>
+<fmt:message key="more.mobile.text"><fmt:param value="${model.brand}"/></fmt:message>
+
+<h2><img src="<spring:theme code="podcastImage"/>" alt=""/>&nbsp;<fmt:message key="more.podcast.title"/></h2>
+<fmt:message key="more.podcast.text"/>
+
+<c:if test="${model.user.uploadRole}">
+
+ <h2><img src="<spring:theme code="uploadImage"/>" alt=""/>&nbsp;<fmt:message key="more.upload.title"/></h2>
+
+ <form method="post" enctype="multipart/form-data" action="upload.view">
+ <table>
+ <tr>
+ <td><fmt:message key="more.upload.source"/></td>
+ <td colspan="2"><input type="file" id="file" name="file" size="40"/></td>
+ </tr>
+ <tr>
+ <td><fmt:message key="more.upload.target"/></td>
+ <td><input type="text" id="dir" name="dir" size="37" value="${model.uploadDirectory}"/></td>
+ <td><input type="submit" value="<fmt:message key="more.upload.ok"/>"/></td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <input type="checkbox" checked name="unzip" id="unzip" class="checkbox"/>
+ <label for="unzip"><fmt:message key="more.upload.unzip"/></label>
+ </td>
+ </tr>
+ </table>
+ </form>
+
+
+ <p class="detail" id="progressText"/>
+
+ <div id="progressBar">
+ <div id="progressBarContent"/>
+ </div>
+
+</c:if>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/musicFolderSettings.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/musicFolderSettings.jsp
new file mode 100644
index 00000000..283e6878
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/musicFolderSettings.jsp
@@ -0,0 +1,114 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+<%--@elvariable id="command" type="net.sourceforge.subsonic.command.MusicFolderSettingsCommand"--%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+</head>
+<body class="mainframe bgcolor1">
+
+<c:import url="settingsHeader.jsp">
+ <c:param name="cat" value="musicFolder"/>
+</c:import>
+
+<form:form commandName="command" action="musicFolderSettings.view" method="post">
+
+<table class="indent">
+ <tr>
+ <th><fmt:message key="musicfoldersettings.name"/></th>
+ <th><fmt:message key="musicfoldersettings.path"/></th>
+ <th style="padding-left:1em"><fmt:message key="musicfoldersettings.enabled"/></th>
+ <th style="padding-left:1em"><fmt:message key="common.delete"/></th>
+ <th></th>
+ </tr>
+
+ <c:forEach items="${command.musicFolders}" var="folder" varStatus="loopStatus">
+ <tr>
+ <td><form:input path="musicFolders[${loopStatus.count-1}].name" size="20"/></td>
+ <td><form:input path="musicFolders[${loopStatus.count-1}].path" size="40"/></td>
+ <td align="center" style="padding-left:1em"><form:checkbox path="musicFolders[${loopStatus.count-1}].enabled" cssClass="checkbox"/></td>
+ <td align="center" style="padding-left:1em"><form:checkbox path="musicFolders[${loopStatus.count-1}].delete" cssClass="checkbox"/></td>
+ <td><c:if test="${not folder.existing}"><span class="warning"><fmt:message key="musicfoldersettings.notfound"/></span></c:if></td>
+ </tr>
+ </c:forEach>
+
+ <tr>
+ <th colspan="4" align="left" style="padding-top:1em"><fmt:message key="musicfoldersettings.add"/></th>
+ </tr>
+
+ <tr>
+ <td><form:input path="newMusicFolder.name" size="20"/></td>
+ <td><form:input path="newMusicFolder.path" size="40"/></td>
+ <td align="center" style="padding-left:1em"><form:checkbox path="newMusicFolder.enabled" cssClass="checkbox"/></td>
+ <td></td>
+ </tr>
+
+</table>
+
+ <div style="padding-top: 1.2em;padding-bottom: 0.3em">
+ <span style="white-space: nowrap">
+ <fmt:message key="musicfoldersettings.scan"/>
+ <form:select path="interval">
+ <fmt:message key="musicfoldersettings.interval.never" var="never"/>
+ <fmt:message key="musicfoldersettings.interval.one" var="one"/>
+ <form:option value="-1" label="${never}"/>
+ <form:option value="1" label="${one}"/>
+
+ <c:forTokens items="2 3 7 14 30 60" delims=" " var="interval">
+ <fmt:message key="musicfoldersettings.interval.many" var="many"><fmt:param value="${interval}"/></fmt:message>
+ <form:option value="${interval}" label="${many}"/>
+ </c:forTokens>
+ </form:select>
+ <form:select path="hour">
+ <c:forEach begin="0" end="23" var="hour">
+ <fmt:message key="musicfoldersettings.hour" var="hourLabel"><fmt:param value="${hour}"/></fmt:message>
+ <form:option value="${hour}" label="${hourLabel}"/>
+ </c:forEach>
+ </form:select>
+ </span>
+ </div>
+
+ <p class="forward"><a href="musicFolderSettings.view?scanNow"><fmt:message key="musicfoldersettings.scannow"/></a></p>
+
+ <c:if test="${command.scanning}">
+ <p style="width:60%"><b><fmt:message key="musicfoldersettings.nowscanning"/></b></p>
+ </c:if>
+
+ <div>
+ <form:checkbox path="fastCache" cssClass="checkbox" id="fastCache"/>
+ <form:label path="fastCache"><fmt:message key="musicfoldersettings.fastcache"/></form:label>
+ </div>
+
+ <p class="detail" style="width:60%;white-space:normal;">
+ <fmt:message key="musicfoldersettings.fastcache.description"/>
+ </p>
+
+ <p class="forward"><a href="musicFolderSettings.view?expunge"><fmt:message key="musicfoldersettings.expunge"/></a></p>
+ <p class="detail" style="width:60%;white-space:normal;margin-top:-10px;">
+ <fmt:message key="musicfoldersettings.expunge.description"/>
+ </p>
+
+ <%--<div>--%>
+ <%--<form:checkbox path="organizeByFolderStructure" cssClass="checkbox" id="organizeByFolderStructure"/>--%>
+ <%--<form:label path="organizeByFolderStructure"><fmt:message key="musicfoldersettings.organizebyfolderstructure"/></form:label>--%>
+ <%--</div>--%>
+
+ <%--<p class="detail" style="width:60%;white-space:normal;">--%>
+ <%--<fmt:message key="musicfoldersettings.organizebyfolderstructure.description"/>--%>
+ <%--</p>--%>
+
+ <p >
+ <input type="submit" value="<fmt:message key="common.save"/>" style="margin-right:0.3em">
+ <input type="button" value="<fmt:message key="common.cancel"/>" onclick="location.href='nowPlaying.view'">
+ </p>
+
+</form:form>
+
+<c:if test="${command.reload}">
+ <script type="text/javascript">
+ parent.frames.upper.location.href="top.view?";
+ parent.frames.left.location.href="left.view?";
+ parent.frames.right.location.href="right.view?";
+ </script>
+</c:if>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/networkSettings.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/networkSettings.jsp
new file mode 100644
index 00000000..c59c16c9
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/networkSettings.jsp
@@ -0,0 +1,107 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+<%--@elvariable id="command" type="net.sourceforge.subsonic.command.NetworkSettingsCommand"--%>
+
+<html>
+<head>
+ <%@ include file="head.jsp" %>
+ <script type="text/javascript" src="<c:url value="/script/prototype.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/interface/multiService.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/engine.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/util.js"/>"></script>
+ <script type="text/javascript" language="javascript">
+
+ function init() {
+ enableUrlRedirectionFields();
+ refreshStatus();
+ }
+
+ function refreshStatus() {
+ multiService.getNetworkStatus(updateStatus);
+ }
+
+ function updateStatus(networkStatus) {
+ dwr.util.setValue("portForwardingStatus", networkStatus.portForwardingStatusText);
+ dwr.util.setValue("urlRedirectionStatus", networkStatus.urlRedirectionStatusText);
+ window.setTimeout("refreshStatus()", 1000);
+ }
+
+ function enableUrlRedirectionFields() {
+ var checkbox = $("urlRedirectionEnabled");
+ var field = $("urlRedirectFrom");
+
+ if (checkbox && checkbox.checked) {
+ field.enable();
+ } else {
+ field.disable();
+ }
+ }
+
+ </script>
+</head>
+<body class="mainframe bgcolor1" onload="init()">
+<script type="text/javascript" src="<c:url value="/script/wz_tooltip.js"/>"></script>
+<script type="text/javascript" src="<c:url value="/script/tip_balloon.js"/>"></script>
+
+<c:import url="settingsHeader.jsp">
+ <c:param name="cat" value="network"/>
+</c:import>
+
+<p style="padding-top:1em"><fmt:message key="networksettings.text"/></p>
+
+<form:form commandName="command" action="networkSettings.view" method="post">
+ <p style="padding-top:1em">
+ <form:checkbox id="portForwardingEnabled" path="portForwardingEnabled"/>
+ <label for="portForwardingEnabled"><fmt:message key="networksettings.portforwardingenabled"/></label>
+ </p>
+
+ <div style="padding-left:2em;max-width:60em">
+ <p>
+ <fmt:message key="networksettings.portforwardinghelp"><fmt:param>${command.port}</fmt:param></fmt:message>
+ </p>
+
+ <p class="detail">
+ <fmt:message key="networksettings.status"/>
+ <span id="portForwardingStatus" style="margin-left:0.25em"></span>
+ </p>
+ </div>
+
+ <p style="padding-top:1em"><form:checkbox id="urlRedirectionEnabled" path="urlRedirectionEnabled"
+ onclick="enableUrlRedirectionFields()"/>
+ <label for="urlRedirectionEnabled"><fmt:message key="networksettings.urlredirectionenabled"/></label>
+ </p>
+
+ <div style="padding-left:2em">
+
+ <p>http://<form:input id="urlRedirectFrom" path="urlRedirectFrom" size="16" cssStyle="margin-left:0.25em"/>.subsonic.org</p>
+
+ <p class="detail">
+ <fmt:message key="networksettings.status"/>
+ <span id="urlRedirectionStatus" style="margin-left:0.25em"></span>
+ <span id="urlRedirectionTestStatus" style="margin-left:0.25em"></span>
+ </p>
+ </div>
+
+ <c:if test="${command.trial}">
+ <fmt:formatDate value="${command.trialExpires}" dateStyle="long" var="expiryDate"/>
+
+ <p class="warning" style="padding-top:1em">
+ <c:choose>
+ <c:when test="${command.trialExpired}">
+ <fmt:message key="networksettings.trialexpired"><fmt:param>${expiryDate}</fmt:param></fmt:message>
+ </c:when>
+ <c:otherwise>
+ <fmt:message
+ key="networksettings.trialnotexpired"><fmt:param>${expiryDate}</fmt:param></fmt:message>
+ </c:otherwise>
+ </c:choose>
+ </p>
+ </c:if>
+
+ <p style="padding-top:1em">
+ <input type="submit" value="<fmt:message key="common.save"/>" style="margin-right:0.3em">
+ <input type="button" value="<fmt:message key="common.cancel"/>" onclick="location.href='nowPlaying.view'">
+ </p>
+
+</form:form>
+</body>
+</html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/notFound.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/notFound.jsp
new file mode 100644
index 00000000..84e666af
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/notFound.jsp
@@ -0,0 +1,21 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+
+<html>
+<head>
+ <%@ include file="head.jsp" %>
+</head>
+
+<body class="mainframe bgcolor1">
+
+<h1>
+ <img src="<spring:theme code="errorImage"/>" alt=""/>
+ <fmt:message key="notFound.title"/>
+</h1>
+
+<fmt:message key="notFound.text"/>
+
+<div class="forward" style="float:left;padding-right:10pt"><a href="javascript:top.location.reload(true)"><fmt:message key="notFound.reload"/></a></div>
+<div class="forward" style="float:left"><a href="musicFolderSettings.view"><fmt:message key="notFound.scan"/></a></div>
+
+</body>
+</html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/passwordSettings.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/passwordSettings.jsp
new file mode 100644
index 00000000..75fd3e01
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/passwordSettings.jsp
@@ -0,0 +1,45 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+</head>
+<body class="mainframe bgcolor1">
+
+<c:import url="settingsHeader.jsp">
+ <c:param name="cat" value="password"/>
+ <c:param name="restricted" value="true"/>
+</c:import>
+
+<c:choose>
+
+ <c:when test="${command.ldapAuthenticated}">
+ <p><fmt:message key="usersettings.passwordnotsupportedforldap"/></p>
+ </c:when>
+
+ <c:otherwise>
+ <h2><fmt:message key="passwordsettings.title"><fmt:param>${command.username}</fmt:param></fmt:message></h2>
+ <form:form method="post" action="passwordSettings.view" commandName="command">
+ <table class="indent">
+ <tr>
+ <td><fmt:message key="usersettings.newpassword"/></td>
+ <td><form:password path="password"/></td>
+ <td class="warning"><form:errors path="password"/></td>
+ </tr>
+ <tr>
+ <td><fmt:message key="usersettings.confirmpassword"/></td>
+ <td><form:password path="confirmPassword"/></td>
+ <td/>
+ </tr>
+ <tr>
+ <td colspan="3" style="padding-top:1.5em">
+ <input type="submit" value="<fmt:message key="common.save"/>" style="margin-right:0.3em">
+ <input type="button" value="<fmt:message key="common.cancel"/>" onclick="location.href='nowPlaying.view'">
+ </td>
+ </tr>
+
+ </table>
+ </form:form>
+ </c:otherwise>
+</c:choose>
+
+</body></html>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/personalSettings.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/personalSettings.jsp
new file mode 100644
index 00000000..2942d195
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/personalSettings.jsp
@@ -0,0 +1,228 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+<%--@elvariable id="command" type="net.sourceforge.subsonic.command.PersonalSettingsCommand"--%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <script type="text/javascript" src="<c:url value="/script/scripts.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/prototype.js"/>"></script>
+
+ <script type="text/javascript" language="javascript">
+ function enableLastFmFields() {
+ var checkbox = $("lastFm");
+ var table = $("lastFmTable");
+
+ if (checkbox && checkbox.checked) {
+ table.show();
+ } else {
+ table.hide();
+ }
+ }
+ </script>
+</head>
+
+<body class="mainframe bgcolor1" onload="enableLastFmFields()">
+<script type="text/javascript" src="<c:url value="/script/wz_tooltip.js"/>"></script>
+<script type="text/javascript" src="<c:url value="/script/tip_balloon.js"/>"></script>
+
+<c:import url="settingsHeader.jsp">
+ <c:param name="cat" value="personal"/>
+ <c:param name="restricted" value="${not command.user.adminRole}"/>
+</c:import>
+
+<h2><fmt:message key="personalsettings.title"><fmt:param>${command.user.username}</fmt:param></fmt:message></h2>
+
+<fmt:message key="common.default" var="default"/>
+<form:form method="post" action="personalSettings.view" commandName="command">
+
+ <table style="white-space:nowrap" class="indent">
+
+ <tr>
+ <td><fmt:message key="personalsettings.language"/></td>
+ <td>
+ <form:select path="localeIndex" cssStyle="width:15em">
+ <form:option value="-1" label="${default}"/>
+ <c:forEach items="${command.locales}" var="locale" varStatus="loopStatus">
+ <form:option value="${loopStatus.count - 1}" label="${locale}"/>
+ </c:forEach>
+ </form:select>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="language"/></c:import>
+ </td>
+ </tr>
+
+ <tr>
+ <td><fmt:message key="personalsettings.theme"/></td>
+ <td>
+ <form:select path="themeIndex" cssStyle="width:15em">
+ <form:option value="-1" label="${default}"/>
+ <c:forEach items="${command.themes}" var="theme" varStatus="loopStatus">
+ <form:option value="${loopStatus.count - 1}" label="${theme.name}"/>
+ </c:forEach>
+ </form:select>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="theme"/></c:import>
+ </td>
+ </tr>
+ </table>
+
+ <table class="indent">
+ <tr>
+ <th style="padding:0 0.5em 0.5em 0;text-align:left;"><fmt:message key="personalsettings.display"/></th>
+ <th style="padding:0 0.5em 0.5em 0.5em;text-align:center;"><fmt:message key="personalsettings.browse"/></th>
+ <th style="padding:0 0 0.5em 0.5em;text-align:center;"><fmt:message key="personalsettings.playlist"/></th>
+ <th style="padding:0 0 0.5em 0.5em">
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="visibility"/></c:import>
+ </th>
+ </tr>
+ <tr>
+ <td><fmt:message key="personalsettings.tracknumber"/></td>
+ <td style="text-align:center"><form:checkbox path="mainVisibility.trackNumberVisible" cssClass="checkbox"/></td>
+ <td style="text-align:center"><form:checkbox path="playlistVisibility.trackNumberVisible" cssClass="checkbox"/></td>
+ </tr>
+ <tr>
+ <td><fmt:message key="personalsettings.artist"/></td>
+ <td style="text-align:center"><form:checkbox path="mainVisibility.artistVisible" cssClass="checkbox"/></td>
+ <td style="text-align:center"><form:checkbox path="playlistVisibility.artistVisible" cssClass="checkbox"/></td>
+ </tr>
+ <tr>
+ <td><fmt:message key="personalsettings.album"/></td>
+ <td style="text-align:center"><form:checkbox path="mainVisibility.albumVisible" cssClass="checkbox"/></td>
+ <td style="text-align:center"><form:checkbox path="playlistVisibility.albumVisible" cssClass="checkbox"/></td>
+ </tr>
+ <tr>
+ <td><fmt:message key="personalsettings.genre"/></td>
+ <td style="text-align:center"><form:checkbox path="mainVisibility.genreVisible" cssClass="checkbox"/></td>
+ <td style="text-align:center"><form:checkbox path="playlistVisibility.genreVisible" cssClass="checkbox"/></td>
+ </tr>
+ <tr>
+ <td><fmt:message key="personalsettings.year"/></td>
+ <td style="text-align:center"><form:checkbox path="mainVisibility.yearVisible" cssClass="checkbox"/></td>
+ <td style="text-align:center"><form:checkbox path="playlistVisibility.yearVisible" cssClass="checkbox"/></td>
+ </tr>
+ <tr>
+ <td><fmt:message key="personalsettings.bitrate"/></td>
+ <td style="text-align:center"><form:checkbox path="mainVisibility.bitRateVisible" cssClass="checkbox"/></td>
+ <td style="text-align:center"><form:checkbox path="playlistVisibility.bitRateVisible" cssClass="checkbox"/></td>
+ </tr>
+ <tr>
+ <td><fmt:message key="personalsettings.duration"/></td>
+ <td style="text-align:center"><form:checkbox path="mainVisibility.durationVisible" cssClass="checkbox"/></td>
+ <td style="text-align:center"><form:checkbox path="playlistVisibility.durationVisible" cssClass="checkbox"/></td>
+ </tr>
+ <tr>
+ <td><fmt:message key="personalsettings.format"/></td>
+ <td style="text-align:center"><form:checkbox path="mainVisibility.formatVisible" cssClass="checkbox"/></td>
+ <td style="text-align:center"><form:checkbox path="playlistVisibility.formatVisible" cssClass="checkbox"/></td>
+ </tr>
+ <tr>
+ <td><fmt:message key="personalsettings.filesize"/></td>
+ <td style="text-align:center"><form:checkbox path="mainVisibility.fileSizeVisible" cssClass="checkbox"/></td>
+ <td style="text-align:center"><form:checkbox path="playlistVisibility.fileSizeVisible" cssClass="checkbox"/></td>
+ </tr>
+ <tr>
+ <td><fmt:message key="personalsettings.captioncutoff"/></td>
+ <td style="text-align:center"><form:input path="mainVisibility.captionCutoff" size="3"/></td>
+ <td style="text-align:center"><form:input path="playlistVisibility.captionCutoff" size="3"/></td>
+ </tr>
+ </table>
+
+ <table class="indent">
+ <tr>
+ <td><form:checkbox path="showNowPlayingEnabled" id="nowPlaying" cssClass="checkbox"/></td>
+ <td><label for="nowPlaying"><fmt:message key="personalsettings.shownowplaying"/></label></td>
+ <td style="padding-left:2em"><form:checkbox path="showChatEnabled" id="chat" cssClass="checkbox"/></td>
+ <td><label for="chat"><fmt:message key="personalsettings.showchat"/></label></td>
+ </tr>
+ <tr>
+ <td><form:checkbox path="nowPlayingAllowed" id="nowPlayingAllowed" cssClass="checkbox"/></td>
+ <td><label for="nowPlayingAllowed"><fmt:message key="personalsettings.nowplayingallowed"/></label></td>
+ <td style="padding-left:2em"><form:checkbox path="partyModeEnabled" id="partyModeEnabled" cssClass="checkbox"/></td>
+ <td><label for="partyModeEnabled"><fmt:message key="personalsettings.partymode"/></label>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="partymode"/></c:import>
+ </td>
+ </tr>
+ </table>
+
+ <table class="indent">
+ <tr>
+ <td><form:checkbox path="finalVersionNotificationEnabled" id="final" cssClass="checkbox"/></td>
+ <td><label for="final"><fmt:message key="personalsettings.finalversionnotification"/></label></td>
+ </tr>
+ <tr>
+ <td><form:checkbox path="betaVersionNotificationEnabled" id="beta" cssClass="checkbox"/></td>
+ <td><label for="beta"><fmt:message key="personalsettings.betaversionnotification"/></label></td>
+ </tr>
+ </table>
+
+ <table class="indent">
+ <tr>
+ <td><form:checkbox path="lastFmEnabled" id="lastFm" cssClass="checkbox" onclick="javascript:enableLastFmFields()"/></td>
+ <td><label for="lastFm"><fmt:message key="personalsettings.lastfmenabled"/></label></td>
+ </tr>
+ </table>
+
+ <table id="lastFmTable" style="padding-left:2em">
+ <tr>
+ <td><fmt:message key="personalsettings.lastfmusername"/></td>
+ <td><form:input path="lastFmUsername" size="24"/></td>
+ </tr>
+ <tr>
+ <td><fmt:message key="personalsettings.lastfmpassword"/></td>
+ <td><form:password path="lastFmPassword" size="24"/></td>
+ </tr>
+ </table>
+
+ <p style="padding-top:1em;padding-bottom:1em">
+ <input type="submit" value="<fmt:message key="common.save"/>" style="margin-right:0.3em"/>
+ <input type="button" value="<fmt:message key="common.cancel"/>" onclick="location.href='nowPlaying.view'">
+ </p>
+
+ <h2><fmt:message key="personalsettings.avatar.title"/></h2>
+
+ <p style="padding-top:1em">
+ <c:forEach items="${command.avatars}" var="avatar">
+ <c:url value="avatar.view" var="avatarUrl">
+ <c:param name="id" value="${avatar.id}"/>
+ </c:url>
+ <span style="white-space:nowrap;">
+ <form:radiobutton id="avatar-${avatar.id}" path="avatarId" value="${avatar.id}"/>
+ <label for="avatar-${avatar.id}"><img src="${avatarUrl}" alt="${avatar.name}" width="${avatar.width}" height="${avatar.height}" style="padding-right:2em;padding-bottom:1em"/></label>
+ </span>
+ </c:forEach>
+ </p>
+ <p>
+ <form:radiobutton id="noAvatar" path="avatarId" value="-1"/>
+ <label for="noAvatar"><fmt:message key="personalsettings.avatar.none"/></label>
+ </p>
+ <p>
+ <form:radiobutton id="customAvatar" path="avatarId" value="-2"/>
+ <label for="customAvatar"><fmt:message key="personalsettings.avatar.custom"/>
+ <c:if test="${not empty command.customAvatar}">
+ <sub:url value="avatar.view" var="avatarUrl">
+ <sub:param name="username" value="${command.user.username}"/>
+ </sub:url>
+ <img src="${avatarUrl}" alt="${command.customAvatar.name}" width="${command.customAvatar.width}" height="${command.customAvatar.height}" style="padding-right:2em"/>
+ </c:if>
+ </label>
+ </p>
+</form:form>
+
+<form method="post" enctype="multipart/form-data" action="avatarUpload.view">
+ <table>
+ <tr>
+ <td style="padding-right:1em"><fmt:message key="personalsettings.avatar.changecustom"/></td>
+ <td style="padding-right:1em"><input type="file" id="file" name="file" size="40"/></td>
+ <td style="padding-right:1em"><input type="submit" value="<fmt:message key="personalsettings.avatar.upload"/>"/></td>
+ </tr>
+ </table>
+</form>
+
+<p class="detail" style="text-align:right">
+ <fmt:message key="personalsettings.avatar.courtesy"/>
+</p>
+
+<c:if test="${command.reloadNeeded}">
+ <script language="javascript" type="text/javascript">
+ parent.location.href="index.view?";
+ </script>
+</c:if>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/playAddDownload.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/playAddDownload.jsp
new file mode 100644
index 00000000..e0852182
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/playAddDownload.jsp
@@ -0,0 +1,64 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+<%@ include file="include.jsp" %>
+
+<%--
+PARAMETERS
+ id: ID of file.
+ video: Whether the file is a video (default false).
+ playEnabled: Whether the current user is allowed to play songs (default true).
+ addEnabled: Whether the current user is allowed to add songs to the playlist (default true).
+ downloadEnabled: Whether the current user is allowed to download songs (default false).
+ starEnabled: Whether to show star/unstar controls (default false).
+ starred: Whether the file is currently starred.
+ asTable: Whether to put the images in td tags.
+--%>
+
+<sub:url value="/download.view" var="downloadUrl">
+ <sub:param name="id" value="${param.id}"/>
+</sub:url>
+<c:if test="${param.starEnabled}">
+ <c:if test="${param.asTable}"><td></c:if>
+ <a href="#" onclick="toggleStar(${param.id}, '#starImage${param.id}'); return false;">
+ <c:choose>
+ <c:when test="${param.starred}">
+ <img id="starImage${param.id}" src="<spring:theme code="ratingOnImage"/>" alt="">
+ </c:when>
+ <c:otherwise>
+ <img id="starImage${param.id}" src="<spring:theme code="ratingOffImage"/>" alt="">
+ </c:otherwise>
+ </c:choose>
+ </a>
+ <c:if test="${param.asTable}"></td></c:if>
+</c:if>
+
+<c:if test="${param.asTable}"><td></c:if>
+<c:if test="${empty param.playEnabled or param.playEnabled}">
+ <c:choose>
+ <c:when test="${param.video}">
+ <sub:url value="/videoPlayer.view" var="videoUrl">
+ <sub:param name="id" value="${param.id}"/>
+ </sub:url>
+ <a href="${videoUrl}" target="main">
+ <img src="<spring:theme code="playImage"/>" alt="<fmt:message key="common.play"/>" title="<fmt:message key="common.play"/>"></a>
+ </c:when>
+ <c:otherwise>
+ <a href="javascript:noop()" onclick="top.playQueue.onPlay(${param.id});">
+ <img src="<spring:theme code="playImage"/>" alt="<fmt:message key="common.play"/>" title="<fmt:message key="common.play"/>"></a>
+ </c:otherwise>
+ </c:choose>
+</c:if>
+<c:if test="${param.asTable}"></td></c:if>
+
+<c:if test="${param.asTable}"><td></c:if>
+<c:if test="${(empty param.addEnabled or param.addEnabled) and not param.video}">
+ <a href="javascript:noop()" onclick="top.playQueue.onAdd(${param.id});">
+ <img src="<spring:theme code="addImage"/>" alt="<fmt:message key="common.add"/>" title="<fmt:message key="common.add"/>"></a>
+</c:if>
+<c:if test="${param.asTable}"></td></c:if>
+
+<c:if test="${param.asTable}"><td></c:if>
+<c:if test="${param.downloadEnabled}">
+ <a href="${downloadUrl}">
+ <img src="<spring:theme code="downloadImage"/>" alt="<fmt:message key="common.download"/>" title="<fmt:message key="common.download"/>"></a>
+</c:if>
+<c:if test="${param.asTable}"></td></c:if>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/playQueue.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/playQueue.jsp
new file mode 100644
index 00000000..5ac4ac46
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/playQueue.jsp
@@ -0,0 +1,617 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html><head>
+ <%@ include file="head.jsp" %>
+ <%@ include file="jquery.jsp" %>
+ <script type="text/javascript" src="<c:url value="/dwr/interface/nowPlayingService.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/interface/playQueueService.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/interface/playlistService.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/engine.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/util.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/swfobject.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/webfx/range.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/webfx/timer.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/webfx/slider.js"/>"></script>
+ <link type="text/css" rel="stylesheet" href="<c:url value="/script/webfx/luna.css"/>">
+</head>
+
+<body class="bgcolor2 playlistframe" onload="init()">
+
+<script type="text/javascript" language="javascript">
+ var player = null;
+ var songs = null;
+ var currentAlbumUrl = null;
+ var currentStreamUrl = null;
+ var startPlayer = false;
+ var repeatEnabled = false;
+ var slider = null;
+
+ function init() {
+ dwr.engine.setErrorHandler(null);
+ startTimer();
+
+ $("#dialog-select-playlist").dialog({resizable: true, height: 220, position: 'top', modal: true, autoOpen: false,
+ buttons: {
+ "<fmt:message key="common.cancel"/>": function() {
+ $(this).dialog("close");
+ }
+ }});
+
+ <c:choose>
+ <c:when test="${model.player.web}">
+ createPlayer();
+ </c:when>
+ <c:otherwise>
+ getPlayQueue();
+ </c:otherwise>
+ </c:choose>
+ }
+
+ function startTimer() {
+ <!-- Periodically check if the current song has changed. -->
+ nowPlayingService.getNowPlayingForCurrentPlayer(nowPlayingCallback);
+ setTimeout("startTimer()", 10000);
+ }
+
+ function nowPlayingCallback(nowPlayingInfo) {
+ if (nowPlayingInfo != null && nowPlayingInfo.streamUrl != currentStreamUrl) {
+ getPlayQueue();
+ if (currentAlbumUrl != nowPlayingInfo.albumUrl && top.main.updateNowPlaying) {
+ top.main.location.replace("nowPlaying.view?");
+ currentAlbumUrl = nowPlayingInfo.albumUrl;
+ }
+ <c:if test="${not model.player.web}">
+ currentStreamUrl = nowPlayingInfo.streamUrl;
+ updateCurrentImage();
+ </c:if>
+ }
+ }
+
+ function createPlayer() {
+ var flashvars = {
+ backcolor:"<spring:theme code="backgroundColor"/>",
+ frontcolor:"<spring:theme code="textColor"/>",
+ id:"player1"
+ };
+ var params = {
+ allowfullscreen:"true",
+ allowscriptaccess:"always"
+ };
+ var attributes = {
+ id:"player1",
+ name:"player1"
+ };
+ swfobject.embedSWF("<c:url value="/flash/jw-player-5.6.swf"/>", "placeholder", "340", "24", "9.0.0", false, flashvars, params, attributes);
+ }
+
+ function playerReady(thePlayer) {
+ player = document.getElementById("player1");
+ player.addModelListener("STATE", "stateListener");
+ getPlayQueue();
+ }
+
+ function stateListener(obj) { // IDLE, BUFFERING, PLAYING, PAUSED, COMPLETED
+ if (obj.newstate == "COMPLETED") {
+ onNext(repeatEnabled);
+ }
+ }
+
+ function getPlayQueue() {
+ playQueueService.getPlayQueue(playQueueCallback);
+ }
+
+ function onClear() {
+ var ok = true;
+ <c:if test="${model.partyMode}">
+ ok = confirm("<fmt:message key="playlist.confirmclear"/>");
+ </c:if>
+ if (ok) {
+ playQueueService.clear(playQueueCallback);
+ }
+ }
+ function onStart() {
+ playQueueService.start(playQueueCallback);
+ }
+ function onStop() {
+ playQueueService.stop(playQueueCallback);
+ }
+ function onGain(gain) {
+ playQueueService.setGain(gain);
+ }
+ function onSkip(index) {
+ <c:choose>
+ <c:when test="${model.player.web}">
+ skip(index);
+ </c:when>
+ <c:otherwise>
+ currentStreamUrl = songs[index].streamUrl;
+ playQueueService.skip(index, playQueueCallback);
+ </c:otherwise>
+ </c:choose>
+ }
+ function onNext(wrap) {
+ var index = parseInt(getCurrentSongIndex()) + 1;
+ if (wrap) {
+ index = index % songs.length;
+ }
+ skip(index);
+ }
+ function onPrevious() {
+ skip(parseInt(getCurrentSongIndex()) - 1);
+ }
+ function onPlay(id) {
+ startPlayer = true;
+ playQueueService.play(id, playQueueCallback);
+ }
+ function onPlayPlaylist(id) {
+ startPlayer = true;
+ playQueueService.playPlaylist(id, playQueueCallback);
+ }
+ function onPlayRandom(id, count) {
+ startPlayer = true;
+ playQueueService.playRandom(id, count, playQueueCallback);
+ }
+ function onAdd(id) {
+ startPlayer = false;
+ playQueueService.add(id, playQueueCallback);
+ }
+ function onShuffle() {
+ playQueueService.shuffle(playQueueCallback);
+ }
+ function onStar(index) {
+ playQueueService.toggleStar(index, playQueueCallback);
+ }
+ function onRemove(index) {
+ playQueueService.remove(index, playQueueCallback);
+ }
+ function onRemoveSelected() {
+ var indexes = new Array();
+ var counter = 0;
+ for (var i = 0; i < songs.length; i++) {
+ var index = i + 1;
+ if ($("#songIndex" + index).is(":checked")) {
+ indexes[counter++] = i;
+ }
+ }
+ playQueueService.removeMany(indexes, playQueueCallback);
+ }
+
+ function onUp(index) {
+ playQueueService.up(index, playQueueCallback);
+ }
+ function onDown(index) {
+ playQueueService.down(index, playQueueCallback);
+ }
+ function onToggleRepeat() {
+ playQueueService.toggleRepeat(playQueueCallback);
+ }
+ function onUndo() {
+ playQueueService.undo(playQueueCallback);
+ }
+ function onSortByTrack() {
+ playQueueService.sortByTrack(playQueueCallback);
+ }
+ function onSortByArtist() {
+ playQueueService.sortByArtist(playQueueCallback);
+ }
+ function onSortByAlbum() {
+ playQueueService.sortByAlbum(playQueueCallback);
+ }
+ function onSavePlaylist() {
+ playQueueService.savePlaylist(function () {top.left.updatePlaylists();});
+ }
+ function onAppendPlaylist() {
+ playlistService.getWritablePlaylists(playlistCallback);
+ }
+ function playlistCallback(playlists) {
+ $("#dialog-select-playlist-list").empty();
+ for (var i = 0; i < playlists.length; i++) {
+ var playlist = playlists[i];
+ $("<p class='dense'><b><a href='#' onclick='appendPlaylist(" + playlist.id + ")'>" + playlist.name + "</a></b></p>").appendTo("#dialog-select-playlist-list");
+ }
+ $("#dialog-select-playlist").dialog("open");
+ }
+ function appendPlaylist(playlistId) {
+ $("#dialog-select-playlist").dialog("close");
+
+ var mediaFileIds = new Array();
+ for (var i = 0; i < songs.length; i++) {
+ if ($("#songIndex" + (i + 1)).is(":checked")) {
+ mediaFileIds.push(songs[i].id);
+ }
+ }
+ playlistService.appendToPlaylist(playlistId, mediaFileIds, function (){top.left.updatePlaylists();});
+ }
+
+ function playQueueCallback(playQueue) {
+ songs = playQueue.entries;
+ repeatEnabled = playQueue.repeatEnabled;
+ if ($("#start")) {
+ if (playQueue.stopEnabled) {
+ $("#start").hide();
+ $("#stop").show();
+ } else {
+ $("#start").show();
+ $("#stop").hide();
+ }
+ }
+
+ if ($("#toggleRepeat")) {
+ var text = repeatEnabled ? "<fmt:message key="playlist.repeat_on"/>" : "<fmt:message key="playlist.repeat_off"/>";
+ $("#toggleRepeat").html(text);
+ }
+
+ if (songs.length == 0) {
+ $("#empty").show();
+ } else {
+ $("#empty").hide();
+ }
+
+ // Delete all the rows except for the "pattern" row
+ dwr.util.removeAllRows("playlistBody", { filter:function(tr) {
+ return (tr.id != "pattern");
+ }});
+
+ // Create a new set cloned from the pattern row
+ for (var i = 0; i < songs.length; i++) {
+ var song = songs[i];
+ var id = i + 1;
+ dwr.util.cloneNode("pattern", { idSuffix:id });
+ if ($("#trackNumber" + id)) {
+ $("#trackNumber" + id).html(song.trackNumber);
+ }
+ if (song.starred) {
+ $("#starSong" + id).attr("src", "<spring:theme code='ratingOnImage'/>");
+ } else {
+ $("#starSong" + id).attr("src", "<spring:theme code='ratingOffImage'/>");
+ }
+ if ($("#currentImage" + id) && song.streamUrl == currentStreamUrl) {
+ $("#currentImage" + id).show();
+ }
+ if ($("#title" + id)) {
+ $("#title" + id).html(truncate(song.title));
+ $("#title" + id).attr("title", song.title);
+ }
+ if ($("#titleUrl" + id)) {
+ $("#titleUrl" + id).html(truncate(song.title));
+ $("#titleUrl" + id).attr("title", song.title);
+ $("#titleUrl" + id).click(function () {onSkip(this.id.substring(8) - 1)});
+ }
+ if ($("#album" + id)) {
+ $("#album" + id).html(truncate(song.album));
+ $("#album" + id).attr("title", song.album);
+ $("#albumUrl" + id).attr("href", song.albumUrl);
+ }
+ if ($("#artist" + id)) {
+ $("#artist" + id).html(truncate(song.artist));
+ $("#artist" + id).attr("title", song.artist);
+ }
+ if ($("#genre" + id)) {
+ $("#genre" + id).html(song.genre);
+ }
+ if ($("#year" + id)) {
+ $("#year" + id).html(song.year);
+ }
+ if ($("#bitRate" + id)) {
+ $("#bitRate" + id).html(song.bitRate);
+ }
+ if ($("#duration" + id)) {
+ $("#duration" + id).html(song.durationAsString);
+ }
+ if ($("#format" + id)) {
+ $("#format" + id).html(song.format);
+ }
+ if ($("#fileSize" + id)) {
+ $("#fileSize" + id).html(song.fileSize);
+ }
+
+ $("#pattern" + id).show();
+ $("#pattern" + id).addClass((i % 2 == 0) ? "bgcolor1" : "bgcolor2");
+ }
+
+ if (playQueue.sendM3U) {
+ parent.frames.main.location.href="play.m3u?";
+ }
+
+ if (slider) {
+ slider.setValue(playQueue.gain * 100);
+ }
+
+ <c:if test="${model.player.web}">
+ triggerPlayer();
+ </c:if>
+ }
+
+ function triggerPlayer() {
+ if (startPlayer) {
+ startPlayer = false;
+ if (songs.length > 0) {
+ skip(0);
+ }
+ }
+ updateCurrentImage();
+ if (songs.length == 0) {
+ player.sendEvent("LOAD", new Array());
+ player.sendEvent("STOP");
+ }
+ }
+
+ function skip(index) {
+ if (index < 0 || index >= songs.length) {
+ return;
+ }
+
+ var song = songs[index];
+ currentStreamUrl = song.streamUrl;
+ updateCurrentImage();
+ var list = new Array();
+ list[0] = {
+ file:song.streamUrl,
+ title:song.title,
+ provider:"sound"
+ };
+
+ if (song.duration != null) {
+ list[0].duration = song.duration;
+ }
+ if (song.format == "aac" || song.format == "m4a") {
+ list[0].provider = "video";
+ }
+
+ player.sendEvent("LOAD", list);
+ player.sendEvent("PLAY");
+ }
+
+ function updateCurrentImage() {
+ for (var i = 0; i < songs.length; i++) {
+ var song = songs[i];
+ var id = i + 1;
+ var image = $("#currentImage" + id);
+
+ if (image) {
+ if (song.streamUrl == currentStreamUrl) {
+ image.show();
+ } else {
+ image.hide();
+ }
+ }
+ }
+ }
+
+ function getCurrentSongIndex() {
+ for (var i = 0; i < songs.length; i++) {
+ if (songs[i].streamUrl == currentStreamUrl) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ function truncate(s) {
+ if (s == null) {
+ return s;
+ }
+ var cutoff = ${model.visibility.captionCutoff};
+
+ if (s.length > cutoff) {
+ return s.substring(0, cutoff) + "...";
+ }
+ return s;
+ }
+
+ <!-- actionSelected() is invoked when the users selects from the "More actions..." combo box. -->
+ function actionSelected(id) {
+ if (id == "top") {
+ return;
+ } else if (id == "savePlaylist") {
+ onSavePlaylist();
+ } else if (id == "downloadPlaylist") {
+ location.href = "download.view?player=${model.player.id}";
+ } else if (id == "sharePlaylist") {
+ parent.frames.main.location.href = "createShare.view?player=${model.player.id}&" + getSelectedIndexes();
+ } else if (id == "sortByTrack") {
+ onSortByTrack();
+ } else if (id == "sortByArtist") {
+ onSortByArtist();
+ } else if (id == "sortByAlbum") {
+ onSortByAlbum();
+ } else if (id == "selectAll") {
+ selectAll(true);
+ } else if (id == "selectNone") {
+ selectAll(false);
+ } else if (id == "removeSelected") {
+ onRemoveSelected();
+ } else if (id == "download") {
+ location.href = "download.view?player=${model.player.id}&" + getSelectedIndexes();
+ } else if (id == "appendPlaylist") {
+ onAppendPlaylist();
+ }
+ $("#moreActions").prop("selectedIndex", 0);
+ }
+
+ function getSelectedIndexes() {
+ var result = "";
+ for (var i = 0; i < songs.length; i++) {
+ if ($("#songIndex" + (i + 1)).is(":checked")) {
+ result += "i=" + i + "&";
+ }
+ }
+ return result;
+ }
+
+ function selectAll(b) {
+ for (var i = 0; i < songs.length; i++) {
+ if (b) {
+ $("#songIndex" + (i + 1)).attr("checked", "checked");
+ } else {
+ $("#songIndex" + (i + 1)).removeAttr("checked");
+ }
+ }
+ }
+
+</script>
+
+<div class="bgcolor2" style="position:fixed; top:0; width:100%;padding-top:0.5em">
+ <table style="white-space:nowrap;">
+ <tr style="white-space:nowrap;">
+ <c:if test="${model.user.settingsRole}">
+ <td><select name="player" onchange="location='playQueue.view?player=' + options[selectedIndex].value;">
+ <c:forEach items="${model.players}" var="player">
+ <option ${player.id eq model.player.id ? "selected" : ""} value="${player.id}">${player.shortDescription}</option>
+ </c:forEach>
+ </select></td>
+ </c:if>
+ <c:if test="${model.player.web}">
+ <td style="width:340px; height:24px;padding-left:10px;padding-right:10px"><div id="placeholder">
+ <a href="http://www.adobe.com/go/getflashplayer" target="_blank"><fmt:message key="playlist.getflash"/></a>
+ </div></td>
+ </c:if>
+
+ <c:if test="${model.user.streamRole and not model.player.web}">
+ <td style="white-space:nowrap;" id="stop"><b><a href="#" onclick="onStop()"><fmt:message key="playlist.stop"/></a></b> | </td>
+ <td style="white-space:nowrap;" id="start"><b><a href="#" onclick="onStart()"><fmt:message key="playlist.start"/></a></b> | </td>
+ </c:if>
+
+ <c:if test="${model.player.jukebox}">
+ <td style="white-space:nowrap;">
+ <img src="<spring:theme code="volumeImage"/>" alt="">
+ </td>
+ <td style="white-space:nowrap;">
+ <div class="slider bgcolor2" id="slider-1" style="width:90px">
+ <input class="slider-input" id="slider-input-1" name="slider-input-1">
+ </div>
+ <script type="text/javascript">
+
+ var updateGainTimeoutId = 0;
+ slider = new Slider(document.getElementById("slider-1"), document.getElementById("slider-input-1"));
+ slider.onchange = function () {
+ clearTimeout(updateGainTimeoutId);
+ updateGainTimeoutId = setTimeout("updateGain()", 250);
+ };
+
+ function updateGain() {
+ var gain = slider.getValue() / 100.0;
+ onGain(gain);
+ }
+ </script>
+ </td>
+ </c:if>
+
+ <c:if test="${model.player.web}">
+ <td style="white-space:nowrap;"><a href="#" onclick="onPrevious()"><b>&laquo;</b></a></td>
+ <td style="white-space:nowrap;"><a href="#" onclick="onNext(false)"><b>&raquo;</b></a> |</td>
+ </c:if>
+
+ <td style="white-space:nowrap;"><a href="#" onclick="onClear()"><fmt:message key="playlist.clear"/></a> |</td>
+ <td style="white-space:nowrap;"><a href="#" onclick="onShuffle()"><fmt:message key="playlist.shuffle"/></a> |</td>
+
+ <c:if test="${model.player.web or model.player.jukebox or model.player.external}">
+ <td style="white-space:nowrap;"><a href="#" onclick="onToggleRepeat()"><span id="toggleRepeat"><fmt:message key="playlist.repeat_on"/></span></a> |</td>
+ </c:if>
+
+ <td style="white-space:nowrap;"><a href="#" onclick="onUndo()"><fmt:message key="playlist.undo"/></a> |</td>
+
+ <c:if test="${model.user.settingsRole}">
+ <td style="white-space:nowrap;"><a href="playerSettings.view?id=${model.player.id}" target="main"><fmt:message key="playlist.settings"/></a> |</td>
+ </c:if>
+
+ <td style="white-space:nowrap;"><select id="moreActions" onchange="actionSelected(this.options[selectedIndex].id)">
+ <option id="top" selected="selected"><fmt:message key="playlist.more"/></option>
+ <option style="color:blue;"><fmt:message key="playlist.more.playlist"/></option>
+ <option id="savePlaylist">&nbsp;&nbsp;&nbsp;&nbsp;<fmt:message key="playlist.save"/></option>
+ <c:if test="${model.user.downloadRole}">
+ <option id="downloadPlaylist">&nbsp;&nbsp;&nbsp;&nbsp;<fmt:message key="common.download"/></option>
+ </c:if>
+ <c:if test="${model.user.shareRole}">
+ <option id="sharePlaylist">&nbsp;&nbsp;&nbsp;&nbsp;<fmt:message key="main.more.share"/></option>
+ </c:if>
+ <option id="sortByTrack">&nbsp;&nbsp;&nbsp;&nbsp;<fmt:message key="playlist.more.sortbytrack"/></option>
+ <option id="sortByAlbum">&nbsp;&nbsp;&nbsp;&nbsp;<fmt:message key="playlist.more.sortbyalbum"/></option>
+ <option id="sortByArtist">&nbsp;&nbsp;&nbsp;&nbsp;<fmt:message key="playlist.more.sortbyartist"/></option>
+ <option style="color:blue;"><fmt:message key="playlist.more.selection"/></option>
+ <option id="selectAll">&nbsp;&nbsp;&nbsp;&nbsp;<fmt:message key="playlist.more.selectall"/></option>
+ <option id="selectNone">&nbsp;&nbsp;&nbsp;&nbsp;<fmt:message key="playlist.more.selectnone"/></option>
+ <option id="removeSelected">&nbsp;&nbsp;&nbsp;&nbsp;<fmt:message key="playlist.remove"/></option>
+ <c:if test="${model.user.downloadRole}">
+ <option id="download">&nbsp;&nbsp;&nbsp;&nbsp;<fmt:message key="common.download"/></option>
+ </c:if>
+ <option id="appendPlaylist">&nbsp;&nbsp;&nbsp;&nbsp;<fmt:message key="playlist.append"/></option>
+ </select>
+ </td>
+
+ </tr></table>
+</div>
+
+<div style="height:3.2em"></div>
+
+<p id="empty"><em><fmt:message key="playlist.empty"/></em></p>
+
+<table style="border-collapse:collapse;white-space:nowrap;">
+ <tbody id="playlistBody">
+ <tr id="pattern" style="display:none;margin:0;padding:0;border:0">
+ <td class="bgcolor2"><a href="#">
+ <img id="starSong" onclick="onStar(this.id.substring(8) - 1)" src="<spring:theme code="ratingOffImage"/>"
+ alt="" title=""></a></td>
+ <td class="bgcolor2"><a href="#">
+ <img id="removeSong" onclick="onRemove(this.id.substring(10) - 1)" src="<spring:theme code="removeImage"/>"
+ alt="<fmt:message key="playlist.remove"/>" title="<fmt:message key="playlist.remove"/>"></a></td>
+ <td class="bgcolor2"><a href="#">
+ <img id="up" onclick="onUp(this.id.substring(2) - 1)" src="<spring:theme code="upImage"/>"
+ alt="<fmt:message key="playlist.up"/>" title="<fmt:message key="playlist.up"/>"></a></td>
+ <td class="bgcolor2"><a href="#">
+ <img id="down" onclick="onDown(this.id.substring(4) - 1)" src="<spring:theme code="downImage"/>"
+ alt="<fmt:message key="playlist.down"/>" title="<fmt:message key="playlist.down"/>"></a></td>
+
+ <td class="bgcolor2" style="padding-left: 0.1em"><input type="checkbox" class="checkbox" id="songIndex"></td>
+ <td style="padding-right:0.25em"></td>
+
+ <c:if test="${model.visibility.trackNumberVisible}">
+ <td style="padding-right:0.5em;text-align:right"><span class="detail" id="trackNumber">1</span></td>
+ </c:if>
+
+ <td style="padding-right:1.25em">
+ <img id="currentImage" src="<spring:theme code="currentImage"/>" alt="" style="display:none">
+ <c:choose>
+ <c:when test="${model.player.externalWithPlaylist}">
+ <span id="title">Title</span>
+ </c:when>
+ <c:otherwise>
+ <a id="titleUrl" href="#">Title</a>
+ </c:otherwise>
+ </c:choose>
+ </td>
+
+ <c:if test="${model.visibility.albumVisible}">
+ <td style="padding-right:1.25em"><a id="albumUrl" target="main"><span id="album" class="detail">Album</span></a></td>
+ </c:if>
+ <c:if test="${model.visibility.artistVisible}">
+ <td style="padding-right:1.25em"><span id="artist" class="detail">Artist</span></td>
+ </c:if>
+ <c:if test="${model.visibility.genreVisible}">
+ <td style="padding-right:1.25em"><span id="genre" class="detail">Genre</span></td>
+ </c:if>
+ <c:if test="${model.visibility.yearVisible}">
+ <td style="padding-right:1.25em"><span id="year" class="detail">Year</span></td>
+ </c:if>
+ <c:if test="${model.visibility.formatVisible}">
+ <td style="padding-right:1.25em"><span id="format" class="detail">Format</span></td>
+ </c:if>
+ <c:if test="${model.visibility.fileSizeVisible}">
+ <td style="padding-right:1.25em;text-align:right;"><span id="fileSize" class="detail">Format</span></td>
+ </c:if>
+ <c:if test="${model.visibility.durationVisible}">
+ <td style="padding-right:1.25em;text-align:right;"><span id="duration" class="detail">Duration</span></td>
+ </c:if>
+ <c:if test="${model.visibility.bitRateVisible}">
+ <td style="padding-right:0.25em"><span id="bitRate" class="detail">Bit Rate</span></td>
+ </c:if>
+ </tr>
+ </tbody>
+</table>
+
+<div id="dialog-select-playlist" title="<fmt:message key="main.addtoplaylist.title"/>" style="display: none;">
+ <p><fmt:message key="main.addtoplaylist.text"/></p>
+ <div id="dialog-select-playlist-list"></div>
+</div>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/playerSettings.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/playerSettings.jsp
new file mode 100644
index 00000000..3381c3a8
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/playerSettings.jsp
@@ -0,0 +1,177 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+<%--@elvariable id="command" type="net.sourceforge.subsonic.command.PlayerSettingsCommand"--%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <script type="text/javascript" src="<c:url value="/script/scripts.js"/>"></script>
+</head>
+<body class="mainframe bgcolor1">
+<script type="text/javascript" src="<c:url value="/script/wz_tooltip.js"/>"></script>
+<script type="text/javascript" src="<c:url value="/script/tip_balloon.js"/>"></script>
+
+<c:import url="settingsHeader.jsp">
+ <c:param name="cat" value="player"/>
+ <c:param name="restricted" value="${not command.admin}"/>
+</c:import>
+
+<fmt:message key="common.unknown" var="unknown"/>
+
+<c:choose>
+<c:when test="${empty command.players}">
+ <p><fmt:message key="playersettings.noplayers"/></p>
+</c:when>
+<c:otherwise>
+
+<c:url value="playerSettings.view" var="deleteUrl">
+ <c:param name="delete" value="${command.playerId}"/>
+</c:url>
+<c:url value="playerSettings.view" var="cloneUrl">
+ <c:param name="clone" value="${command.playerId}"/>
+</c:url>
+
+<table class="indent">
+ <tr>
+ <td><b><fmt:message key="playersettings.title"/></b></td>
+ <td>
+ <select name="player" onchange="location='playerSettings.view?id=' + options[selectedIndex].value;">
+ <c:forEach items="${command.players}" var="player">
+ <option ${player.id eq command.playerId ? "selected" : ""}
+ value="${player.id}">${player.description}</option>
+ </c:forEach>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td style="padding-right:1em"><div class="forward"><a href="${deleteUrl}"><fmt:message key="playersettings.forget"/></a></div></td>
+ <td><div class="forward"><a href="${cloneUrl}"><fmt:message key="playersettings.clone"/></a></div></td>
+ </tr>
+</table>
+
+<form:form commandName="command" method="post" action="playerSettings.view">
+<form:hidden path="playerId"/>
+
+<table class="ruleTable indent">
+ <c:forEach items="${command.technologyHolders}" var="technologyHolder">
+ <c:set var="technologyName">
+ <fmt:message key="playersettings.technology.${fn:toLowerCase(technologyHolder.name)}.title"/>
+ </c:set>
+
+ <tr>
+ <td class="ruleTableHeader">
+ <form:radiobutton id="radio-${technologyName}" path="technologyName" value="${technologyHolder.name}"/>
+ <b><label for="radio-${technologyName}">${technologyName}</label></b>
+ </td>
+ <td class="ruleTableCell" style="width:40em">
+ <fmt:message key="playersettings.technology.${fn:toLowerCase(technologyHolder.name)}.text"/>
+ </td>
+ </tr>
+ </c:forEach>
+</table>
+
+
+<table class="indent" style="border-spacing:3pt">
+ <tr>
+ <td><fmt:message key="playersettings.type"/></td>
+ <td colspan="3">
+ <c:choose>
+ <c:when test="${empty command.type}">${unknown}</c:when>
+ <c:otherwise>${command.type}</c:otherwise>
+ </c:choose>
+ </td>
+ </tr>
+ <tr>
+ <td><fmt:message key="playersettings.lastseen"/></td>
+ <td colspan="3"><fmt:formatDate value="${command.lastSeen}" type="both" dateStyle="long" timeStyle="medium"/></td>
+ </tr>
+
+ <tr>
+ <td colspan="4">&nbsp;</td>
+ </tr>
+
+ <tr>
+ <td><fmt:message key="playersettings.name"/></td>
+ <td><form:input path="name" size="16"/></td>
+ <td colspan="2"><c:import url="helpToolTip.jsp"><c:param name="topic" value="playername"/></c:import></td>
+ </tr>
+
+ <tr>
+ <td><fmt:message key="playersettings.coverartsize"/></td>
+ <td>
+ <form:select path="coverArtSchemeName" cssStyle="width:8em">
+ <c:forEach items="${command.coverArtSchemeHolders}" var="coverArtSchemeHolder">
+ <c:set var="coverArtSchemeName">
+ <fmt:message key="playersettings.coverart.${fn:toLowerCase(coverArtSchemeHolder.name)}"/>
+ </c:set>
+ <form:option value="${coverArtSchemeHolder.name}" label="${coverArtSchemeName}"/>
+ </c:forEach>
+ </form:select>
+ </td>
+ <td colspan="2"><c:import url="helpToolTip.jsp"><c:param name="topic" value="cover"/></c:import></td>
+ </tr>
+
+ <tr>
+ <td><fmt:message key="playersettings.maxbitrate"/></td>
+ <td>
+ <form:select path="transcodeSchemeName" cssStyle="width:8em">
+ <c:forEach items="${command.transcodeSchemeHolders}" var="transcodeSchemeHolder">
+ <form:option value="${transcodeSchemeHolder.name}" label="${transcodeSchemeHolder.description}"/>
+ </c:forEach>
+ </form:select>
+ </td>
+ <td>
+ <c:import url="helpToolTip.jsp"><c:param name="topic" value="transcode"/></c:import>
+ </td>
+ <td class="warning">
+ <c:if test="${not command.transcodingSupported}">
+ <fmt:message key="playersettings.nolame"/>
+ </c:if>
+ </td>
+ </tr>
+
+</table>
+
+<table class="indent" style="border-spacing:3pt">
+
+ <tr>
+ <td>
+ <form:checkbox path="dynamicIp" id="dynamicIp" cssClass="checkbox"/>
+ <label for="dynamicIp"><fmt:message key="playersettings.dynamicip"/></label>
+ </td>
+ <td><c:import url="helpToolTip.jsp"><c:param name="topic" value="dynamicip"/></c:import></td>
+ </tr>
+
+ <tr>
+ <td>
+ <form:checkbox path="autoControlEnabled" id="autoControlEnabled" cssClass="checkbox"/>
+ <label for="autoControlEnabled"><fmt:message key="playersettings.autocontrol"/></label>
+ </td>
+ <td><c:import url="helpToolTip.jsp"><c:param name="topic" value="autocontrol"/></c:import></td>
+ </tr>
+</table>
+
+ <c:if test="${not empty command.allTranscodings}">
+ <table class="indent">
+ <tr><td><b><fmt:message key="playersettings.transcodings"/></b></td></tr>
+ <c:forEach items="${command.allTranscodings}" var="transcoding" varStatus="loopStatus">
+ <c:if test="${loopStatus.count % 3 == 1}"><tr></c:if>
+ <td style="padding-right:2em">
+ <form:checkbox path="activeTranscodingIds" id="transcoding${transcoding.id}" value="${transcoding.id}" cssClass="checkbox"/>
+ <label for="transcoding${transcoding.id}">${transcoding.name}</label>
+ </td>
+ <c:if test="${loopStatus.count % 3 == 0 or loopStatus.count eq fn:length(command.allTranscodings)}"></tr></c:if>
+ </c:forEach>
+ </table>
+ </c:if>
+
+ <input type="submit" value="<fmt:message key="common.save"/>" style="margin-top:1em;margin-right:0.3em">
+ <input type="button" value="<fmt:message key="common.cancel"/>" style="margin-top:1em" onclick="location.href='nowPlaying.view'">
+</form:form>
+
+</c:otherwise>
+</c:choose>
+
+<c:if test="${command.reloadNeeded}">
+ <script language="javascript" type="text/javascript">parent.frames.playQueue.location.href="playQueue.view?"</script>
+</c:if>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/playlist.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/playlist.jsp
new file mode 100644
index 00000000..b0cb1f74
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/playlist.jsp
@@ -0,0 +1,235 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <%@ include file="jquery.jsp" %>
+ <script type="text/javascript" src="<c:url value='/dwr/util.js'/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/engine.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/interface/playlistService.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/interface/starService.js"/>"></script>
+ <script type="text/javascript" language="javascript">
+
+ var playlist;
+ var songs;
+
+ function init() {
+ dwr.engine.setErrorHandler(null);
+ $("#dialog-edit").dialog({resizable: true, width:400, position: 'top', modal: true, autoOpen: false,
+ buttons: {
+ "<fmt:message key="common.save"/>": function() {
+ $(this).dialog("close");
+ var name = $("#newName").val();
+ var comment = $("#newComment").val();
+ var isPublic = $("#newPublic").is(":checked");
+ $("#name").html(name);
+ $("#comment").html(comment);
+ playlistService.updatePlaylist(playlist.id, name, comment, isPublic, function (playlistInfo){playlistCallback(playlistInfo); top.left.updatePlaylists()});
+ },
+ "<fmt:message key="common.cancel"/>": function() {
+ $(this).dialog("close");
+ }
+ }});
+
+ $("#dialog-delete").dialog({resizable: false, height: 170, position: 'top', modal: true, autoOpen: false,
+ buttons: {
+ "<fmt:message key="common.delete"/>": function() {
+ $(this).dialog("close");
+ playlistService.deletePlaylist(playlist.id, function (){top.left.updatePlaylists(); location = "home.view";});
+ },
+ "<fmt:message key="common.cancel"/>": function() {
+ $(this).dialog("close");
+ }
+ }});
+ getPlaylist();
+ }
+
+ function getPlaylist() {
+ playlistService.getPlaylist(${model.playlist.id}, playlistCallback);
+ }
+
+ function playlistCallback(playlistInfo) {
+ this.playlist = playlistInfo.playlist;
+ this.songs = playlistInfo.entries;
+
+ if (songs.length == 0) {
+ $("#empty").show();
+ } else {
+ $("#empty").hide();
+ }
+
+
+ $("#songCount").html(playlist.fileCount);
+ $("#duration").html(playlist.durationAsString);
+
+ if (playlist.public) {
+ $("#shared").html("<fmt:message key="playlist2.shared"/>");
+ } else {
+ $("#shared").html("<fmt:message key="playlist2.notshared"/>");
+ }
+
+ // Delete all the rows except for the "pattern" row
+ dwr.util.removeAllRows("playlistBody", { filter:function(tr) {
+ return (tr.id != "pattern");
+ }});
+
+ // Create a new set cloned from the pattern row
+ for (var i = 0; i < songs.length; i++) {
+ var song = songs[i];
+ var id = i + 1;
+ dwr.util.cloneNode("pattern", { idSuffix:id });
+ if (song.starred) {
+ $("#starSong" + id).attr("src", "<spring:theme code='ratingOnImage'/>");
+ } else {
+ $("#starSong" + id).attr("src", "<spring:theme code='ratingOffImage'/>");
+ }
+ if ($("#title" + id)) {
+ $("#title" + id).html(truncate(song.title));
+ $("#title" + id).attr("title", song.title);
+ }
+ if ($("#album" + id)) {
+ $("#album" + id).html(truncate(song.album));
+ $("#album" + id).attr("title", song.album);
+ $("#albumUrl" + id).attr("href", "main.view?id=" + song.id);
+ }
+ if ($("#artist" + id)) {
+ $("#artist" + id).html(truncate(song.artist));
+ $("#artist" + id).attr("title", song.artist);
+ }
+ if ($("#duration" + id)) {
+ $("#duration" + id).html(song.durationAsString);
+ }
+
+ $("#pattern" + id).addClass((i % 2 == 0) ? "bgcolor2" : "bgcolor1");
+ $("#pattern" + id).show();
+ }
+ }
+
+ function truncate(s) {
+ if (s == null) {
+ return s;
+ }
+ var cutoff = 30;
+
+ if (s.length > cutoff) {
+ return s.substring(0, cutoff) + "...";
+ }
+ return s;
+ }
+
+ function onPlay(index) {
+ top.playQueue.onPlay(songs[index].id);
+ }
+ function onPlayAll() {
+ top.playQueue.onPlayPlaylist(playlist.id);
+ }
+ function onAdd(index) {
+ top.playQueue.onAdd(songs[index].id);
+ }
+ function onStar(index) {
+ playlistService.toggleStar(playlist.id, index, playlistCallback);
+ }
+ function onRemove(index) {
+ playlistService.remove(playlist.id, index, function (playlistInfo){playlistCallback(playlistInfo); top.left.updatePlaylists()});
+ }
+ function onUp(index) {
+ playlistService.up(playlist.id, index, playlistCallback);
+ }
+ function onDown(index) {
+ playlistService.down(playlist.id, index, playlistCallback);
+ }
+ function onEditPlaylist() {
+ $("#dialog-edit").dialog("open");
+ }
+ function onDeletePlaylist() {
+ $("#dialog-delete").dialog("open");
+ }
+
+ </script>
+</head>
+<body class="mainframe bgcolor1" onload="init()">
+
+<h1 id="name">${model.playlist.name}</h1>
+<h2>
+ <a href="#" onclick="onPlayAll();"><fmt:message key="common.play"/></a>
+
+ <c:if test="${model.user.downloadRole}">
+ <c:url value="download.view" var="downloadUrl"><c:param name="playlist" value="${model.playlist.id}"/></c:url>
+ | <a href="${downloadUrl}"><fmt:message key="common.download"/></a>
+ </c:if>
+ <c:if test="${model.editAllowed}">
+ | <a href="#" onclick="onEditPlaylist();"><fmt:message key="common.edit"/></a>
+ | <a href="#" onclick="onDeletePlaylist();"><fmt:message key="common.delete"/></a>
+ </c:if>
+ <c:url value="exportPlaylist.view" var="exportUrl"><c:param name="id" value="${model.playlist.id}"/></c:url>
+ | <a href="${exportUrl}"><fmt:message key="playlist2.export"/></a>
+
+</h2>
+
+<div id="comment" class="detail" style="padding-top:0.2em">${model.playlist.comment}</div>
+
+<div class="detail" style="padding-top:0.2em">
+ <fmt:message key="playlist2.created">
+ <fmt:param>${model.playlist.username}</fmt:param>
+ <fmt:param><fmt:formatDate type="date" dateStyle="long" value="${model.playlist.created}"/></fmt:param>
+ </fmt:message>.
+ <span id="shared"></span>.
+ <span id="songCount"></span> <fmt:message key="playlist2.songs"/> (<span id="duration"></span>)
+</div>
+
+<div style="height:0.7em"></div>
+
+<p id="empty" style="display: none;"><em><fmt:message key="playlist2.empty"/></em></p>
+
+<table style="border-collapse:collapse;white-space:nowrap">
+ <tbody id="playlistBody">
+ <tr id="pattern" style="display:none;margin:0;padding:0;border:0">
+ <td class="bgcolor1"><a href="#">
+ <img id="starSong" onclick="onStar(this.id.substring(8) - 1)" src="<spring:theme code="ratingOffImage"/>" alt="" title=""></a></td>
+ <td class="bgcolor1"><a href="#">
+ <img id="play" src="<spring:theme code="playImage"/>" alt="<fmt:message key="common.play"/>" title="<fmt:message key="common.play"/>"
+ onclick="onPlay(this.id.substring(4) - 1)"></a></td>
+ <td class="bgcolor1"><a href="#">
+ <img id="add" src="<spring:theme code="addImage"/>" alt="<fmt:message key="common.add"/>" title="<fmt:message key="common.add"/>"
+ onclick="onAdd(this.id.substring(3) - 1)"></a></td>
+
+ <td style="padding-right:0.25em"></td>
+ <td style="padding-right:1.25em"><span id="title">Title</span></td>
+ <td style="padding-right:1.25em"><a id="albumUrl" target="main"><span id="album" class="detail">Album</span></a></td>
+ <td style="padding-right:1.25em"><span id="artist" class="detail">Artist</span></td>
+ <td style="padding-right:1.25em;text-align:right;"><span id="duration" class="detail">Duration</span></td>
+
+ <c:if test="${model.editAllowed}">
+ <td class="bgcolor1"><a href="#">
+ <img id="removeSong" onclick="onRemove(this.id.substring(10) - 1)" src="<spring:theme code="removeImage"/>"
+ alt="<fmt:message key="playlist.remove"/>" title="<fmt:message key="playlist.remove"/>"></a></td>
+ <td class="bgcolor1"><a href="#">
+ <img id="up" onclick="onUp(this.id.substring(2) - 1)" src="<spring:theme code="upImage"/>"
+ alt="<fmt:message key="playlist.up"/>" title="<fmt:message key="playlist.up"/>"></a></td>
+ <td class="bgcolor1"><a href="#">
+ <img id="down" onclick="onDown(this.id.substring(4) - 1)" src="<spring:theme code="downImage"/>"
+ alt="<fmt:message key="playlist.down"/>" title="<fmt:message key="playlist.down"/>"></a></td>
+ </c:if>
+
+ </tr>
+ </tbody>
+</table>
+
+<div id="dialog-delete" title="<fmt:message key="common.confirm"/>" style="display: none;">
+ <p><span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span>
+ <fmt:message key="playlist2.confirmdelete"/></p>
+</div>
+
+<div id="dialog-edit" title="<fmt:message key="common.edit"/>" style="display: none;">
+ <form>
+ <label for="newName" style="display:block;"><fmt:message key="playlist2.name"/></label>
+ <input type="text" name="newName" id="newName" value="${model.playlist.name}" class="ui-widget-content"
+ style="display:block;width:95%;"/>
+ <label for="newComment" style="display:block;margin-top:1em"><fmt:message key="playlist2.comment"/></label>
+ <input type="text" name="newComment" id="newComment" value="${model.playlist.comment}" class="ui-widget-content"
+ style="display:block;width:95%;"/>
+ <input type="checkbox" name="newPublic" id="newPublic" ${model.playlist.public ? "checked='checked'" : ""} style="margin-top:1.5em" class="ui-widget-content"/>
+ <label for="newPublic"><fmt:message key="playlist2.public"/></label>
+ </form>
+</div>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/podcast.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/podcast.jsp
new file mode 100644
index 00000000..6f2b88d8
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/podcast.jsp
@@ -0,0 +1,26 @@
+<%@ include file="include.jsp" %>
+<%@ page language="java" contentType="text/xml; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<rss version="2.0">
+ <channel>
+ <title>Subsonic Podcast</title>
+ <link>${model.url}</link>
+ <description>Subsonic Podcast</description>
+ <language>en-us</language>
+ <image>
+ <url>http://subsonic.org/pages/inc/img/subsonic.png</url>
+ <title>Subsonic Podcast</title>
+ </image>
+
+ <c:forEach var="podcast" items="${model.podcasts}">
+ <item>
+ <title>${fn:escapeXml(podcast.name)}</title>
+ <link>${model.url}</link>
+ <description>Subsonic playlist "${fn:escapeXml(podcast.name)}"</description>
+ <pubDate>${podcast.publishDate}</pubDate>
+ <enclosure url="${podcast.enclosureUrl}" length="${podcast.length}" type="${podcast.type}"/>
+ </item>
+ </c:forEach>
+
+ </channel>
+</rss>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/podcastReceiver.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/podcastReceiver.jsp
new file mode 100644
index 00000000..35a0ffdb
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/podcastReceiver.jsp
@@ -0,0 +1,269 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <script type="text/javascript" src="<c:url value="/script/scripts.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/prototype.js"/>"></script>
+</head>
+<body class="mainframe bgcolor1">
+
+<script type="text/javascript" language="javascript">
+ var channelCount = ${fn:length(model.channels)};
+
+ function downloadSelected() {
+ location.href = "podcastReceiverAdmin.view?downloadChannel=" + getSelectedChannels() +
+ "&downloadEpisode=" + getSelectedEpisodes() +
+ "&expandedChannels=" + getExpandedChannels();
+ }
+
+ function deleteSelected() {
+ if (confirm("<fmt:message key="podcastreceiver.confirmdelete"/>")) {
+ location.href = "podcastReceiverAdmin.view?deleteChannel=" + getSelectedChannels() +
+ "&deleteEpisode=" + getSelectedEpisodes() +
+ "&expandedChannels=" + getExpandedChannels();
+ }
+ }
+
+ function refreshChannels() {
+ location.href = "podcastReceiverAdmin.view?refresh=" +
+ "&expandedChannels=" + getExpandedChannels();
+ }
+
+ function refreshPage() {
+ location.href = "podcastReceiver.view?expandedChannels=" + getExpandedChannels();
+ }
+
+ function toggleEpisodes(channelIndex) {
+ for (var i = 0; i < episodeCount; i++) {
+ var row = $("episodeRow" + i);
+ if (row.title == "channel" + channelIndex) {
+ row.toggle();
+ $("channelExpanded" + channelIndex).checked = row.visible() ? "checked" : "";
+ }
+ }
+ }
+
+ function toggleAllEpisodes(visible) {
+ for (var i = 0; i < episodeCount; i++) {
+ var row = $("episodeRow" + i);
+ if (visible) {
+ row.show();
+ } else {
+ row.hide();
+ }
+ }
+ for (i = 0; i < channelCount; i++) {
+ $("channelExpanded" + i).checked = visible ? "checked" : "";
+ }
+ }
+
+ function getExpandedChannels() {
+ var result = "";
+ for (var i = 0; i < channelCount; i++) {
+ var checkbox = $("channelExpanded" + i);
+ if (checkbox.checked) {
+ result += (checkbox.value + " ");
+ }
+ }
+ return result;
+ }
+
+ function getSelectedChannels() {
+ var result = "";
+ for (var i = 0; i < channelCount; i++) {
+ var checkbox = $("channel" + i);
+ if (checkbox.checked) {
+ result += (checkbox.value + " ");
+ }
+ }
+ return result;
+ }
+
+ function getSelectedEpisodes() {
+ var result = "";
+ for (var i = 0; i < episodeCount; i++) {
+ var checkbox = $("episode" + i);
+ if (checkbox.checked) {
+ result += (checkbox.value + " ");
+ }
+ }
+ return result;
+ }
+</script>
+
+<h1>
+ <img src="<spring:theme code="podcastLargeImage"/>" alt=""/>
+ <fmt:message key="podcastreceiver.title"/>
+</h1>
+
+<table><tr>
+ <td style="padding-right:2em"><div class="forward"><a href="javascript:toggleAllEpisodes(true)"><fmt:message key="podcastreceiver.expandall"/></a></div></td>
+ <td style="padding-right:2em"><div class="forward"><a href="javascript:toggleAllEpisodes(false)"><fmt:message key="podcastreceiver.collapseall"/></a></div></td>
+</tr></table>
+
+<table style="border-collapse:collapse;white-space:nowrap">
+
+ <c:set var="episodeCount" value="0"/>
+
+ <c:forEach items="${model.channels}" var="channel" varStatus="i">
+
+ <c:set var="title" value="${channel.key.title}"/>
+ <c:if test="${empty title}">
+ <c:set var="title" value="${channel.key.url}"/>
+ </c:if>
+
+ <c:set var="channelExpanded" value="false"/>
+ <c:forEach items="${model.expandedChannels}" var="expandedChannelId">
+ <c:if test="${expandedChannelId eq channel.key.id}">
+ <c:set var="channelExpanded" value="true"/>
+ </c:if>
+ </c:forEach>
+
+ <tr style="margin:0;padding:0;border:0">
+ <td style="padding-top:1em">
+ <input type="checkbox" class="checkbox" id="channel${i.index}" value="${channel.key.id}"/>
+ <input type="checkbox" class="checkbox" id="channelExpanded${i.index}" value="${channel.key.id}" style="display:none"
+ <c:if test="${channelExpanded}">checked="checked"</c:if>/>
+ </td>
+ <td colspan="6" style="padding-left:0.25em;padding-top:1em">
+ <a href="javascript:toggleEpisodes(${i.index})">
+ <span title="${title}"><b><str:truncateNicely upper="40">${title}</str:truncateNicely></b></span>
+ (${fn:length(channel.value)})
+ </a>
+ </td>
+ <td style="padding-left:1.5em;padding-top:1em;text-align:center;">
+ <span class="detail"><fmt:message key="podcastreceiver.status.${fn:toLowerCase(channel.key.status)}"/></span>
+ </td>
+ <td style="padding-left:1.5em;padding-top:1em">
+ <c:choose>
+ <c:when test="${channel.key.status eq 'ERROR'}">
+ <span class="detail warning" title="${channel.key.errorMessage}"><str:truncateNicely upper="100">${channel.key.errorMessage}</str:truncateNicely></span>
+ </c:when>
+ <c:otherwise>
+ <span class="detail" title="${channel.key.description}"><str:truncateNicely upper="100">${channel.key.description}</str:truncateNicely></span>
+ </c:otherwise>
+ </c:choose>
+ </td>
+ </tr>
+
+ <c:set var="class" value=""/>
+
+ <c:forEach items="${channel.value}" var="episode" varStatus="j">
+
+ <c:choose>
+ <c:when test="${empty class}">
+ <c:set var="class" value="class='bgcolor2'"/>
+ </c:when>
+ <c:otherwise>
+ <c:set var="class" value=""/>
+ </c:otherwise>
+ </c:choose>
+ <tr title="channel${i.index}" id="episodeRow${episodeCount}" style="margin:0;padding:0;border:0;display:${channelExpanded ? "table-row" : "none"}">
+
+ <td><input type="checkbox" class="checkbox" id="episode${episodeCount}" value="${episode.id}"/></td>
+
+ <c:choose>
+ <c:when test="${empty episode.path}">
+ <td ${class} colspan="3"/>
+ </c:when>
+ <c:otherwise>
+ <c:import url="playAddDownload.jsp">
+ <c:param name="id" value="${episode.mediaFileId}"/>
+ <c:param name="playEnabled" value="${model.user.streamRole and not model.partyMode}"/>
+ <c:param name="addEnabled" value="${model.user.streamRole and not model.partyMode}"/>
+ <c:param name="downloadEnabled" value="false"/>
+ <c:param name="asTable" value="true"/>
+ </c:import>
+ </c:otherwise>
+ </c:choose>
+
+ <c:set var="episodeCount" value="${episodeCount + 1}"/>
+
+
+ <sub:url value="main.view" var="mainUrl">
+ <sub:param name="path" value="${episode.path}"/>
+ </sub:url>
+
+
+ <td ${class} style="padding-left:0.6em">
+ <span title="${episode.title}">
+ <c:choose>
+ <c:when test="${empty episode.path}">
+ <str:truncateNicely upper="40">${episode.title}</str:truncateNicely>
+ </c:when>
+ <c:otherwise>
+ <a target="main" href="${mainUrl}"><str:truncateNicely upper="40">${episode.title}</str:truncateNicely></a>
+ </c:otherwise>
+ </c:choose>
+ </span>
+ </td>
+
+ <td ${class} style="padding-left:1.5em">
+ <span class="detail">${episode.duration}</span>
+ </td>
+
+ <td ${class} style="padding-left:1.5em">
+ <span class="detail"><fmt:formatDate value="${episode.publishDate}" dateStyle="medium"/></span>
+ </td>
+
+ <td ${class} style="padding-left:1.5em;text-align:center">
+ <span class="detail">
+ <c:choose>
+ <c:when test="${episode.status eq 'DOWNLOADING'}">
+ <fmt:formatNumber type="percent" value="${episode.completionRate}"/>
+ </c:when>
+ <c:otherwise>
+ <fmt:message key="podcastreceiver.status.${fn:toLowerCase(episode.status)}"/>
+ </c:otherwise>
+ </c:choose>
+ </span>
+ </td>
+
+ <td ${class} style="padding-left:1.5em">
+ <c:choose>
+ <c:when test="${episode.status eq 'ERROR'}">
+ <span class="detail warning" title="${episode.errorMessage}"><str:truncateNicely upper="100">${episode.errorMessage}</str:truncateNicely></span>
+ </c:when>
+ <c:otherwise>
+ <span class="detail" title="${episode.description}"><str:truncateNicely upper="100">${episode.description}</str:truncateNicely></span>
+ </c:otherwise>
+ </c:choose>
+ </td>
+
+ </tr>
+ </c:forEach>
+ </c:forEach>
+</table>
+
+<script type="text/javascript" language="javascript">
+ var episodeCount = ${episodeCount};
+</script>
+
+<table style="padding-top:1em"><tr>
+ <c:if test="${model.user.podcastRole}">
+ <td style="padding-right:2em"><div class="forward"><a href="javascript:downloadSelected()"><fmt:message key="podcastreceiver.downloadselected"/></a></div></td>
+ <td style="padding-right:2em"><div class="forward"><a href="javascript:deleteSelected()"><fmt:message key="podcastreceiver.deleteselected"/></a></div></td>
+ <td style="padding-right:2em"><div class="forward"><a href="javascript:refreshChannels()"><fmt:message key="podcastreceiver.check"/></a></div></td>
+ </c:if>
+ <td style="padding-right:2em"><div class="forward"><a href="javascript:refreshPage()"><fmt:message key="podcastreceiver.refresh"/></a></div></td>
+ <c:if test="${model.user.adminRole}">
+ <td style="padding-right:2em"><div class="forward"><a href="podcastSettings.view?"><fmt:message key="podcastreceiver.settings"/></a></div></td>
+ </c:if>
+</tr></table>
+
+<c:if test="${model.user.podcastRole}">
+ <form method="post" action="podcastReceiverAdmin.view?">
+ <input type="hidden" name="expandedChannels" value=""/>
+ <table>
+ <tr>
+ <td><fmt:message key="podcastreceiver.subscribe"/></td>
+ <td><input type="text" name="add" value="http://" style="width:30em" onclick="select()"/></td>
+ <td><input type="submit" value="<fmt:message key="common.ok"/>"/></td>
+ </tr>
+ </table>
+ </form>
+</c:if>
+
+
+</body>
+</html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/podcastSettings.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/podcastSettings.jsp
new file mode 100644
index 00000000..07d99e28
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/podcastSettings.jsp
@@ -0,0 +1,88 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+</head>
+<body class="mainframe bgcolor1">
+
+<c:import url="settingsHeader.jsp">
+ <c:param name="cat" value="podcast"/>
+</c:import>
+
+<form:form commandName="command" action="podcastSettings.view" method="post">
+
+<table class="indent">
+ <tr>
+ <td><fmt:message key="podcastsettings.update"/></td>
+ <td>
+ <form:select path="interval" cssStyle="width:20em">
+ <fmt:message key="podcastsettings.interval.manually" var="never"/>
+ <fmt:message key="podcastsettings.interval.hourly" var="hourly"/>
+ <fmt:message key="podcastsettings.interval.daily" var="daily"/>
+ <fmt:message key="podcastsettings.interval.weekly" var="weekly"/>
+
+ <form:option value="-1" label="${never}"/>
+ <form:option value="1" label="${hourly}"/>
+ <form:option value="24" label="${daily}"/>
+ <form:option value="168" label="${weekly}"/>
+ </form:select>
+ </td>
+ </tr>
+
+ <tr>
+ <td><fmt:message key="podcastsettings.keep"/></td>
+ <td>
+ <form:select path="episodeRetentionCount" cssStyle="width:20em">
+ <fmt:message key="podcastsettings.keep.all" var="all"/>
+ <fmt:message key="podcastsettings.keep.one" var="one"/>
+
+ <form:option value="-1" label="${all}"/>
+ <form:option value="1" label="${one}"/>
+
+ <c:forTokens items="2 3 4 5 10 20 30 50" delims=" " var="count">
+ <fmt:message key="podcastsettings.keep.many" var="many"><fmt:param value="${count}"/></fmt:message>
+ <form:option value="${count}" label="${many}"/>
+ </c:forTokens>
+
+ </form:select>
+ </td>
+ </tr>
+
+ <tr>
+ <td><fmt:message key="podcastsettings.download"/></td>
+ <td>
+ <form:select path="episodeDownloadCount" cssStyle="width:20em">
+ <fmt:message key="podcastsettings.download.all" var="all"/>
+ <fmt:message key="podcastsettings.download.one" var="one"/>
+ <fmt:message key="podcastsettings.download.none" var="none"/>
+
+ <form:option value="-1" label="${all}"/>
+ <form:option value="1" label="${one}"/>
+
+ <c:forTokens items="2 3 4 5 10" delims=" " var="count">
+ <fmt:message key="podcastsettings.download.many" var="many"><fmt:param value="${count}"/></fmt:message>
+ <form:option value="${count}" label="${many}"/>
+ </c:forTokens>
+ <form:option value="0" label="${none}"/>
+
+ </form:select>
+ </td>
+ </tr>
+
+ <tr>
+ <td><fmt:message key="podcastsettings.folder"/></td>
+ <td><form:input path="folder" cssStyle="width:20em"/></td>
+ </tr>
+
+ <tr>
+ <td style="padding-top:1.5em" colspan="2">
+ <input type="submit" value="<fmt:message key="common.save"/>" style="margin-right:0.3em">
+ <input type="button" value="<fmt:message key="common.cancel"/>" onclick="location.href='nowPlaying.view'">
+ </td>
+ </tr>
+
+</table>
+
+</form:form>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/rating.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/rating.jsp
new file mode 100644
index 00000000..9e956de3
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/rating.jsp
@@ -0,0 +1,51 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+<%@ include file="include.jsp" %>
+
+<%--
+Creates HTML for displaying the rating stars.
+PARAMETERS
+ path: Album path. May be null if readonly.
+ readonly: Whether rating can be changed.
+ rating: The rating, an integer from 0 (no rating), through 10 (lowest rating), to 50 (highest rating).
+--%>
+
+<c:forEach var="i" begin="1" end="5">
+
+ <sub:url value="setRating.view" var="ratingUrl">
+ <sub:param name="path" value="${param.path}"/>
+ <sub:param name="action" value="rating"/>
+ <sub:param name="rating" value="${i}"/>
+ </sub:url>
+
+ <c:choose>
+ <c:when test="${param.rating ge i * 10}">
+ <spring:theme code="ratingOnImage" var="imageUrl"/>
+ </c:when>
+ <c:when test="${param.rating ge i*10 - 7 and param.rating le i*10 - 3}">
+ <spring:theme code="ratingHalfImage" var="imageUrl"/>
+ </c:when>
+ <c:otherwise>
+ <spring:theme code="ratingOffImage" var="imageUrl"/>
+ </c:otherwise>
+ </c:choose>
+
+ <c:choose>
+ <c:when test="${param.readonly}">
+ <img src="${imageUrl}" style="margin-right:-3px" alt="" title="<fmt:message key="rating.rating"/> ${param.rating/10}">
+ </c:when>
+ <c:otherwise>
+ <a href="${ratingUrl}"><img src="${imageUrl}" style="margin-right:-3px" alt="" title="<fmt:message key="rating.rating"/> ${i}"></a>
+ </c:otherwise>
+ </c:choose>
+
+</c:forEach>
+
+<sub:url value="setRating.view" var="clearRatingUrl">
+ <sub:param name="path" value="${param.path}"/>
+ <sub:param name="action" value="rating"/>
+ <sub:param name="rating" value="0"/>
+</sub:url>
+
+<c:if test="${not param.readonly}">
+ | <a href="${clearRatingUrl}"><img src="<spring:theme code="clearRatingImage"/>" alt="" title="<fmt:message key="rating.clearrating"/>" style="margin-left:-3px; margin-right:5px"></a>
+</c:if>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/recover.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/recover.jsp
new file mode 100644
index 00000000..ff206d14
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/recover.jsp
@@ -0,0 +1,34 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+<head>
+ <%@ include file="head.jsp" %>
+</head>
+<body class="mainframe bgcolor1" onload="document.getElementById('usernameOrEmail').focus()">
+
+<form action="recover.view" method="POST">
+ <div class="bgcolor2" style="border:1px solid black; padding:20px 50px 20px 50px; margin-top:100px">
+
+ <div style="margin-left: auto; margin-right: auto; width: 45em">
+
+ <h1><fmt:message key="recover.title"/></h1>
+ <p style="padding-top: 1em; padding-bottom: 0.5em"><fmt:message key="recover.text"/></p>
+ <input type="text" id="usernameOrEmail" name="usernameOrEmail" style="width:18em;margin-right: 1em">
+ <input name="submit" type="submit" value="<fmt:message key="recover.send"/>">
+
+ <c:if test="${not empty model.sentTo}">
+ <p style="padding-top: 1em"><fmt:message key="recover.success"><fmt:param value="${model.sentTo}"/></fmt:message></p>
+ </c:if>
+
+ <c:if test="${not empty model.error}">
+ <p style="padding-top: 1em" class="warning"><fmt:message key="${model.error}"/></p>
+ </c:if>
+
+ <div class="back" style="margin-top: 1.5em"><a href="login.view"><fmt:message key="common.back"/></a></div>
+
+ </div>
+ </div>
+</form>
+</body>
+</html>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/reload.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/reload.jsp
new file mode 100644
index 00000000..1b8384a7
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/reload.jsp
@@ -0,0 +1,11 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+</head><body>
+
+<c:forEach items="${model.reloadFrames}" var="reloadFrame">
+ <script language="javascript" type="text/javascript">parent.frames.${reloadFrame.frame}.location.href="${reloadFrame.view}"</script>
+</c:forEach>
+
+</body></html>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/rest/videoPlayer.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/rest/videoPlayer.jsp
new file mode 100644
index 00000000..ccd18a93
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/rest/videoPlayer.jsp
@@ -0,0 +1,142 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+
+<html>
+<head>
+ <%@ include file="../include.jsp" %>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" href="../<spring:theme code="styleSheet"/>" type="text/css">
+ <link rel="shortcut icon" href="../<spring:theme code="faviconImage"/>">
+
+ <c:url value="/rest/stream.view" var="streamUrl">
+ <c:param name="c" value="${model.c}"/>
+ <c:param name="v" value="${model.v}"/>
+ <c:param name="id" value="${model.id}"/>
+ </c:url>
+
+ <script type="text/javascript" src="<c:url value="/script/swfobject.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/prototype.js"/>"></script>
+ <script type="text/javascript" language="javascript">
+
+ var player;
+ var position;
+ var maxBitRate = ${model.maxBitRate};
+ var timeOffset = ${model.timeOffset};
+
+ function init() {
+ var flashvars = {
+ id:"player1",
+ skin:"<c:url value="/flash/whotube.zip"/>",
+ screencolor:"000000",
+ autostart:false,
+ bufferlength:4,
+ backcolor:"<spring:theme code="backgroundColor"/>",
+ frontcolor:"<spring:theme code="textColor"/>",
+ provider:"video"
+ };
+ var params = {
+ allowfullscreen:"true",
+ allowscriptaccess:"always"
+ };
+ var attributes = {
+ id:"player1",
+ name:"player1"
+ };
+
+ swfobject.embedSWF("<c:url value="/flash/jw-player-5.6.swf"/>", "placeholder1", "360", "240", "9.0.0", false, flashvars, params, attributes);
+ }
+
+ function playerReady(thePlayer) {
+ player = $("player1");
+ player.addModelListener("TIME", "timeListener");
+
+ <c:if test="${model.autoplay}">
+ play();
+ </c:if>
+ }
+
+ function play() {
+ var list = new Array();
+ list[0] = {
+ file:"${streamUrl}&maxBitRate=" + maxBitRate + "&timeOffset=" + timeOffset + "&p=${model.p}" + "&u=${model.u}",
+ duration:${model.duration} - timeOffset,
+ provider:"video"
+ };
+ player.sendEvent("LOAD", list);
+ player.sendEvent("PLAY");
+ }
+
+ function timeListener(obj) {
+ var newPosition = Math.round(obj.position);
+ if (newPosition != position) {
+ position = newPosition;
+ updatePosition();
+ }
+ }
+
+ function updatePosition() {
+ var pos = parseInt(timeOffset) + parseInt(position);
+
+ var minutes = Math.round(pos / 60);
+ var seconds = pos % 60;
+
+ var result = minutes + ":";
+ if (seconds < 10) {
+ result += "0";
+ }
+ result += seconds;
+ $("position").innerHTML = result;
+ }
+
+ function changeTimeOffset() {
+ timeOffset = $("timeOffset").getValue();
+ play();
+ }
+
+ function changeBitRate() {
+ maxBitRate = $("maxBitRate").getValue();
+ timeOffset = parseInt(timeOffset) + parseInt(position);
+ play();
+ }
+
+ </script>
+</head>
+
+<body class="mainframe bgcolor1" onload="init();">
+<h1>${model.video.title}</h1>
+
+<div id="wrapper" style="padding-top:1em">
+ <div id="placeholder1"><span class="warning"><a href="http://www.adobe.com/go/getflashplayer"><fmt:message key="playlist.getflash"/></a></span></div>
+</div>
+
+<div style="padding-top:1.3em;padding-bottom:0.7em;font-size:16px">
+
+ <span id="position" style="padding-right:0.5em">0:00</span>
+ <select id="timeOffset" onchange="changeTimeOffset();" style="padding-left:0.25em;padding-right:0.25em;margin-right:0.5em;font-size:16px">
+ <c:forEach items="${model.skipOffsets}" var="skipOffset">
+ <c:choose>
+ <c:when test="${skipOffset.value eq model.timeOffset}">
+ <option selected="selected" value="${skipOffset.value}">${skipOffset.key}</option>
+ </c:when>
+ <c:otherwise>
+ <option value="${skipOffset.value}">${skipOffset.key}</option>
+ </c:otherwise>
+ </c:choose>
+ </c:forEach>
+ </select>
+
+ <select id="maxBitRate" onchange="changeBitRate();" style="padding-left:0.25em;padding-right:0.25em;margin-right:0.5em;font-size:16px">
+ <c:forEach items="${model.bitRates}" var="bitRate">
+ <c:choose>
+ <c:when test="${bitRate eq model.maxBitRate}">
+ <option selected="selected" value="${bitRate}">${bitRate} Kbps</option>
+ </c:when>
+ <c:otherwise>
+ <option value="${bitRate}">${bitRate} Kbps</option>
+ </c:otherwise>
+ </c:choose>
+ </c:forEach>
+ </select>
+</div>
+
+</body>
+</html>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/right.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/right.jsp
new file mode 100644
index 00000000..ada7385f
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/right.jsp
@@ -0,0 +1,191 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+
+<html>
+<head>
+ <%@ include file="head.jsp" %>
+ <script type="text/javascript" src="<c:url value="/dwr/engine.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/util.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/interface/chatService.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/interface/nowPlayingService.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/prototype.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/scripts.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/fancyzoom/FancyZoom.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/fancyzoom/FancyZoomHTML.js"/>"></script>
+</head>
+<body class="bgcolor1 rightframe" style="padding-top:2em" onload="init()">
+
+<script type="text/javascript">
+ function init() {
+ setupZoom('<c:url value="/"/>');
+ dwr.engine.setErrorHandler(null);
+ <c:if test="${model.showChat}">
+ chatService.addMessage(null);
+ </c:if>
+ }
+</script>
+
+<div id="scanningStatus" style="display: none;" class="warning">
+ <img src="<spring:theme code="scanningImage"/>" title="" alt=""> <fmt:message key="main.scanning"/> <span id="scanCount"></span>
+</div>
+
+<c:if test="${model.showNowPlaying}">
+
+ <!-- This script uses AJAX to periodically retrieve what all users are playing. -->
+ <script type="text/javascript" language="javascript">
+
+ startGetNowPlayingTimer();
+
+ function startGetNowPlayingTimer() {
+ nowPlayingService.getNowPlaying(getNowPlayingCallback);
+ setTimeout("startGetNowPlayingTimer()", 10000);
+ }
+
+ function getNowPlayingCallback(nowPlaying) {
+ var html = nowPlaying.length == 0 ? "" : "<h2><fmt:message key="main.nowplaying"/></h2><table>";
+ for (var i = 0; i < nowPlaying.length; i++) {
+ html += "<tr><td colspan='2' class='detail' style='padding-top:1em;white-space:nowrap'>";
+
+ if (nowPlaying[i].avatarUrl != null) {
+ html += "<img src='" + nowPlaying[i].avatarUrl + "' style='padding-right:5pt'>";
+ }
+ html += "<b>" + nowPlaying[i].username + "</b></td></tr>"
+
+ html += "<tr><td class='detail' style='padding-right:1em'>" +
+ "<a title='" + nowPlaying[i].tooltip + "' target='main' href='" + nowPlaying[i].albumUrl + "'>";
+
+ if (nowPlaying[i].artist != null) {
+ html += "<em>" + nowPlaying[i].artist + "</em><br/>";
+ }
+
+ html += nowPlaying[i].title + "</a><br/>" +
+ "<span class='forward'><a href='" + nowPlaying[i].lyricsUrl + "' onclick=\"return popupSize(this, 'lyrics', 430, 550)\">" +
+ "<fmt:message key="main.lyrics"/>" + "</a></span></td><td style='padding-top:1em'>";
+
+ if (nowPlaying[i].coverArtUrl != null) {
+ html += "<a title='" + nowPlaying[i].tooltip + "' rel='zoom' href='" + nowPlaying[i].coverArtZoomUrl + "'>" +
+ "<img src='" + nowPlaying[i].coverArtUrl + "' width='48' height='48'></a>";
+ }
+ html += "</td></tr>";
+
+ var minutesAgo = nowPlaying[i].minutesAgo;
+ if (minutesAgo > 4) {
+ html += "<tr><td class='detail' colspan='2'>" + minutesAgo + " <fmt:message key="main.minutesago"/></td></tr>";
+ }
+ }
+ html += "</table>";
+ $('nowPlaying').innerHTML = html;
+ prepZooms();
+ }
+ </script>
+
+ <div id="nowPlaying">
+ </div>
+
+</c:if>
+
+<c:if test="${model.showChat}">
+ <script type="text/javascript">
+
+ var revision = 0;
+ startGetMessagesTimer();
+
+ function startGetMessagesTimer() {
+ chatService.getMessages(revision, getMessagesCallback);
+ setTimeout("startGetMessagesTimer()", 10000);
+ }
+
+ function addMessage() {
+ chatService.addMessage($("message").value);
+ dwr.util.setValue("message", null);
+ setTimeout("startGetMessagesTimer()", 500);
+ }
+ function clearMessages() {
+ chatService.clearMessages();
+ setTimeout("startGetMessagesTimer()", 500);
+ }
+ function getMessagesCallback(messages) {
+
+ if (messages == null) {
+ return;
+ }
+ revision = messages.revision;
+
+ // Delete all the rows except for the "pattern" row
+ dwr.util.removeAllRows("chatlog", { filter:function(div) {
+ return (div.id != "pattern");
+ }});
+
+ // Create a new set cloned from the pattern row
+ for (var i = 0; i < messages.messages.length; i++) {
+ var message = messages.messages[i];
+ var id = i + 1;
+ dwr.util.cloneNode("pattern", { idSuffix:id });
+ dwr.util.setValue("user" + id, message.username);
+ dwr.util.setValue("date" + id, " [" + formatDate(message.date) + "]");
+ dwr.util.setValue("content" + id, message.content);
+ $("pattern" + id).show();
+ }
+
+ var clearDiv = $("clearDiv");
+ if (clearDiv) {
+ if (messages.messages.length == 0) {
+ clearDiv.hide();
+ } else {
+ clearDiv.show();
+ }
+ }
+ }
+ function formatDate(date) {
+ var hours = date.getHours();
+ var minutes = date.getMinutes();
+ var result = hours < 10 ? "0" : "";
+ result += hours;
+ result += ":";
+ if (minutes < 10) {
+ result += "0";
+ }
+ result += minutes;
+ return result;
+ }
+ </script>
+
+ <script type="text/javascript">
+
+ startGetScanningStatusTimer();
+
+ function startGetScanningStatusTimer() {
+ nowPlayingService.getScanningStatus(getScanningStatusCallback);
+ }
+
+ function getScanningStatusCallback(scanInfo) {
+ dwr.util.setValue("scanCount", scanInfo.count);
+ if (scanInfo.scanning) {
+ $("scanningStatus").show();
+ setTimeout("startGetScanningStatusTimer()", 1000);
+ } else {
+ $("scanningStatus").hide();
+ setTimeout("startGetScanningStatusTimer()", 15000);
+ }
+ }
+ </script>
+
+ <h2><fmt:message key="main.chat"/></h2>
+ <div style="padding-top:0.3em;padding-bottom:0.3em">
+ <input id="message" value=" <fmt:message key="main.message"/>" style="width:90%" onclick="dwr.util.setValue('message', null);" onkeypress="dwr.util.onReturn(event, addMessage)"/>
+ </div>
+
+ <table>
+ <tbody id="chatlog">
+ <tr id="pattern" style="display:none;margin:0;padding:0 0 0.15em 0;border:0"><td>
+ <span id="user" class="detail" style="font-weight:bold"></span>&nbsp;<span id="date" class="detail"></span> <span id="content"></span></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <c:if test="${model.user.adminRole}">
+ <div id="clearDiv" style="display:none;" class="forward"><a href="#" onclick="clearMessages(); return false;"> <fmt:message key="main.clearchat"/></a></div>
+ </c:if>
+</c:if>
+
+</body>
+</html>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/search.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/search.jsp
new file mode 100644
index 00000000..a01f7afe
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/search.jsp
@@ -0,0 +1,150 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+<%--@elvariable id="command" type="net.sourceforge.subsonic.command.SearchCommand"--%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <script type="text/javascript" src="<c:url value='/script/scripts.js'/>"></script>
+ <script type="text/javascript" src="<c:url value='/script/prototype.js'/>"></script>
+ <script type="text/javascript" src="<c:url value='/dwr/util.js'/>"></script>
+
+ <script type="text/javascript">
+ function more(rowSelector, moreId) {
+ var rows = $$(rowSelector);
+ for (var i = 0; i < rows.length; i++) {
+ rows[i].show();
+ }
+ $(moreId).hide();
+ }
+ </script>
+
+</head>
+<body class="mainframe bgcolor1">
+
+<h1>
+ <img src="<spring:theme code="searchImage"/>" alt=""/>
+ <fmt:message key="search.title"/>
+</h1>
+
+<form:form commandName="command" method="post" action="search.view" name="searchForm">
+ <table>
+ <tr>
+ <td><fmt:message key="search.query"/></td>
+ <td style="padding-left:0.25em"><form:input path="query" size="35"/></td>
+ <td style="padding-left:0.25em"><input type="submit" onclick="search(0)" value="<fmt:message key="search.search"/>"/></td>
+ </tr>
+ </table>
+
+</form:form>
+
+<c:if test="${command.indexBeingCreated}">
+ <p class="warning"><fmt:message key="search.index"/></p>
+</c:if>
+
+<c:if test="${not command.indexBeingCreated and empty command.artists and empty command.albums and empty command.songs}">
+ <p class="warning"><fmt:message key="search.hits.none"/></p>
+</c:if>
+
+<c:if test="${not empty command.artists}">
+ <h2><fmt:message key="search.hits.artists"/></h2>
+ <table style="border-collapse:collapse">
+ <c:forEach items="${command.artists}" var="match" varStatus="loopStatus">
+
+ <sub:url value="/main.view" var="mainUrl">
+ <sub:param name="path" value="${match.path}"/>
+ </sub:url>
+
+ <tr class="artistRow" ${loopStatus.count > 5 ? "style='display:none'" : ""}>
+ <c:import url="playAddDownload.jsp">
+ <c:param name="id" value="${match.id}"/>
+ <c:param name="playEnabled" value="${command.user.streamRole and not command.partyModeEnabled}"/>
+ <c:param name="addEnabled" value="${command.user.streamRole and (not command.partyModeEnabled or not match.directory)}"/>
+ <c:param name="downloadEnabled" value="${command.user.downloadRole and not command.partyModeEnabled}"/>
+ <c:param name="asTable" value="true"/>
+ </c:import>
+ <td ${loopStatus.count % 2 == 1 ? "class='bgcolor2'" : ""} style="padding-left:0.25em;padding-right:1.25em">
+ <a href="${mainUrl}">${match.name}</a>
+ </td>
+ </tr>
+
+ </c:forEach>
+ </table>
+ <c:if test="${fn:length(command.artists) gt 5}">
+ <div id="moreArtists" class="forward"><a href="javascript:noop()" onclick="more('tr.artistRow', 'moreArtists')"><fmt:message key="search.hits.more"/></a></div>
+ </c:if>
+</c:if>
+
+<c:if test="${not empty command.albums}">
+ <h2><fmt:message key="search.hits.albums"/></h2>
+ <table style="border-collapse:collapse">
+ <c:forEach items="${command.albums}" var="match" varStatus="loopStatus">
+
+ <sub:url value="/main.view" var="mainUrl">
+ <sub:param name="path" value="${match.path}"/>
+ </sub:url>
+
+ <tr class="albumRow" ${loopStatus.count > 5 ? "style='display:none'" : ""}>
+ <c:import url="playAddDownload.jsp">
+ <c:param name="id" value="${match.id}"/>
+ <c:param name="playEnabled" value="${command.user.streamRole and not command.partyModeEnabled}"/>
+ <c:param name="addEnabled" value="${command.user.streamRole and (not command.partyModeEnabled or not match.directory)}"/>
+ <c:param name="downloadEnabled" value="${command.user.downloadRole and not command.partyModeEnabled}"/>
+ <c:param name="asTable" value="true"/>
+ </c:import>
+
+ <td ${loopStatus.count % 2 == 1 ? "class='bgcolor2'" : ""} style="padding-left:0.25em;padding-right:1.25em">
+ <a href="${mainUrl}">${match.albumName}</a>
+ </td>
+
+ <td ${loopStatus.count % 2 == 1 ? "class='bgcolor2'" : ""} style="padding-right:0.25em">
+ <span class="detail">${match.artist}</span>
+ </td>
+ </tr>
+
+ </c:forEach>
+ </table>
+ <c:if test="${fn:length(command.albums) gt 5}">
+ <div id="moreAlbums" class="forward"><a href="javascript:noop()" onclick="more('tr.albumRow', 'moreAlbums')"><fmt:message key="search.hits.more"/></a></div>
+ </c:if>
+</c:if>
+
+
+<c:if test="${not empty command.songs}">
+ <h2><fmt:message key="search.hits.songs"/></h2>
+ <table style="border-collapse:collapse">
+ <c:forEach items="${command.songs}" var="match" varStatus="loopStatus">
+
+ <sub:url value="/main.view" var="mainUrl">
+ <sub:param name="path" value="${match.parentPath}"/>
+ </sub:url>
+
+ <tr class="songRow" ${loopStatus.count > 15 ? "style='display:none'" : ""}>
+ <c:import url="playAddDownload.jsp">
+ <c:param name="id" value="${match.id}"/>
+ <c:param name="playEnabled" value="${command.user.streamRole and not command.partyModeEnabled}"/>
+ <c:param name="addEnabled" value="${command.user.streamRole and (not command.partyModeEnabled or not match.directory)}"/>
+ <c:param name="downloadEnabled" value="${command.user.downloadRole and not command.partyModeEnabled}"/>
+ <c:param name="video" value="${match.video and command.player.web}"/>
+ <c:param name="asTable" value="true"/>
+ </c:import>
+
+ <td ${loopStatus.count % 2 == 1 ? "class='bgcolor2'" : ""} style="padding-left:0.25em;padding-right:1.25em">
+ ${match.title}
+ </td>
+
+ <td ${loopStatus.count % 2 == 1 ? "class='bgcolor2'" : ""} style="padding-right:1.25em">
+ <a href="${mainUrl}"><span class="detail">${match.albumName}</span></a>
+ </td>
+
+ <td ${loopStatus.count % 2 == 1 ? "class='bgcolor2'" : ""} style="padding-right:0.25em">
+ <span class="detail">${match.artist}</span>
+ </td>
+ </tr>
+
+ </c:forEach>
+ </table>
+<c:if test="${fn:length(command.songs) gt 15}">
+ <div id="moreSongs" class="forward"><a href="javascript:noop()" onclick="more('tr.songRow', 'moreSongs')"><fmt:message key="search.hits.more"/></a></div>
+</c:if>
+</c:if>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/settingsHeader.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/settingsHeader.jsp
new file mode 100644
index 00000000..6f7f240c
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/settingsHeader.jsp
@@ -0,0 +1,32 @@
+
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+<%@ include file="include.jsp" %>
+
+<c:set var="categories" value="${param.restricted ? 'personal password player share' : 'musicFolder general advanced personal user player share network transcoding internetRadio podcast'}"/>
+<h1>
+ <img src="<spring:theme code="settingsImage"/>" alt=""/>
+ <fmt:message key="settingsheader.title"/>
+</h1>
+
+<h2>
+<c:forTokens items="${categories}" delims=" " var="cat" varStatus="loopStatus">
+ <c:choose>
+ <c:when test="${loopStatus.count > 1 and (loopStatus.count - 1) % 6 != 0}">&nbsp;|&nbsp;</c:when>
+ <c:otherwise></h2><h2></c:otherwise>
+ </c:choose>
+
+ <c:url var="url" value="${cat}Settings.view?"/>
+
+ <c:choose>
+ <c:when test="${param.cat eq cat}">
+ <span class="headerSelected"><fmt:message key="settingsheader.${cat}"/></span>
+ </c:when>
+ <c:otherwise>
+ <a href="${url}"><fmt:message key="settingsheader.${cat}"/></a>
+ </c:otherwise>
+ </c:choose>
+
+</c:forTokens>
+</h2>
+
+<p></p>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/shareSettings.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/shareSettings.jsp
new file mode 100644
index 00000000..448f4741
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/shareSettings.jsp
@@ -0,0 +1,72 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+<%--@elvariable id="model" type="Map"--%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+</head>
+<body class="mainframe bgcolor1">
+
+<c:import url="settingsHeader.jsp">
+ <c:param name="cat" value="share"/>
+ <c:param name="restricted" value="${not model.user.adminRole}"/>
+</c:import>
+
+<form method="post" action="shareSettings.view">
+
+ <table class="indent" style="border-collapse:collapse;white-space:nowrap">
+ <tr>
+ <th style="padding-left:1em"><fmt:message key="sharesettings.name"/></th>
+ <th style="padding-left:1em"><fmt:message key="sharesettings.owner"/></th>
+ <th style="padding-left:1em"><fmt:message key="sharesettings.description"/></th>
+ <th style="padding-left:1em"><fmt:message key="sharesettings.expires"/></th>
+ <th style="padding-left:1em"><fmt:message key="sharesettings.lastvisited"/></th>
+ <th style="padding-left:1em"><fmt:message key="sharesettings.visits"/></th>
+ <th style="padding-left:1em"><fmt:message key="sharesettings.files"/></th>
+ <th style="padding-left:1em"><fmt:message key="sharesettings.expirein"/></th>
+ <th style="padding-left:1em"><fmt:message key="common.delete"/></th>
+ </tr>
+
+ <c:forEach items="${model.shareInfos}" var="shareInfo" varStatus="loopStatus">
+ <c:set var="share" value="${shareInfo.share}"/>
+ <c:choose>
+ <c:when test="${loopStatus.count % 2 == 1}">
+ <c:set var="class" value="class='bgcolor2'"/>
+ </c:when>
+ <c:otherwise>
+ <c:set var="class" value=""/>
+ </c:otherwise>
+ </c:choose>
+
+ <sub:url value="main.view" var="albumUrl">
+ <sub:param name="path" value="${shareInfo.dir.path}"/>
+ </sub:url>
+
+ <tr>
+ <td ${class} style="padding-left:1em"><a href="${model.shareBaseUrl}${share.name}" target="_blank">${share.name}</a></td>
+ <td ${class} style="padding-left:1em">${share.username}</td>
+ <td ${class} style="padding-left:1em"><input type="text" name="description[${share.id}]" size="40" value="${share.description}"/></td>
+ <td ${class} style="padding-left:1em"><fmt:formatDate value="${share.expires}" type="date" dateStyle="medium"/></td>
+ <td ${class} style="padding-left:1em"><fmt:formatDate value="${share.lastVisited}" type="date" dateStyle="medium"/></td>
+ <td ${class} style="padding-left:1em; text-align:right">${share.visitCount}</td>
+ <td ${class} style="padding-left:1em"><a href="${albumUrl}" title="${shareInfo.dir.name}"><str:truncateNicely upper="30">${fn:escapeXml(shareInfo.dir.name)}</str:truncateNicely></a></td>
+ <td ${class} style="padding-left:1em">
+ <label><input type="radio" name="expireIn[${share.id}]" value="7"><fmt:message key="sharesettings.expirein.week"/></label>
+ <label><input type="radio" name="expireIn[${share.id}]" value="30"><fmt:message key="sharesettings.expirein.month"/></label>
+ <label><input type="radio" name="expireIn[${share.id}]" value="365"><fmt:message key="sharesettings.expirein.year"/></label>
+ <label><input type="radio" name="expireIn[${share.id}]" value="0"><fmt:message key="sharesettings.expirein.never"/></label>
+ </td>
+ <td ${class} style="padding-left:1em" align="center" style="padding-left:1em"><input type="checkbox" name="delete[${share.id}]" class="checkbox"/></td>
+ </tr>
+ </c:forEach>
+
+ <tr>
+ <td colspan="4" style="padding-top:1.5em">
+ <input type="submit" value="<fmt:message key="common.save"/>" style="margin-right:0.3em">
+ <input type="button" value="<fmt:message key="common.cancel"/>" onclick="location.href='nowPlaying.view'">
+ </td>
+ </tr>
+
+ </table>
+</form>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/starred.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/starred.jsp
new file mode 100644
index 00000000..967fc7b7
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/starred.jsp
@@ -0,0 +1,131 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <%@ include file="jquery.jsp" %>
+ <script type="text/javascript" src="<c:url value='/dwr/util.js'/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/engine.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/dwr/interface/starService.js"/>"></script>
+ <script type="text/javascript" language="javascript">
+
+ function toggleStar(mediaFileId, imageId) {
+ if ($(imageId).attr("src").indexOf("<spring:theme code="ratingOnImage"/>") != -1) {
+ $(imageId).attr("src", "<spring:theme code="ratingOffImage"/>");
+ starService.unstar(mediaFileId);
+ }
+ else if ($(imageId).attr("src").indexOf("<spring:theme code="ratingOffImage"/>") != -1) {
+ $(imageId).attr("src", "<spring:theme code="ratingOnImage"/>");
+ starService.star(mediaFileId);
+ }
+ }
+ </script>
+</head>
+<body class="mainframe bgcolor1">
+
+<h1>
+ <fmt:message key="starred.title"/>
+</h1>
+
+<c:if test="${empty model.artists and empty model.albums and empty model.songs}">
+ <p style="padding-top: 1em"><em><fmt:message key="starred.empty"/></em></p>
+</c:if>
+
+<c:if test="${not empty model.artists}">
+ <h2><fmt:message key="search.hits.artists"/></h2>
+ <table style="border-collapse:collapse">
+ <c:forEach items="${model.artists}" var="artist" varStatus="loopStatus">
+
+ <sub:url value="/main.view" var="mainUrl">
+ <sub:param name="path" value="${artist.path}"/>
+ </sub:url>
+
+ <tr>
+ <c:import url="playAddDownload.jsp">
+ <c:param name="id" value="${artist.id}"/>
+ <c:param name="playEnabled" value="${model.user.streamRole and not model.partyModeEnabled}"/>
+ <c:param name="addEnabled" value="${model.user.streamRole and (not model.partyModeEnabled or not artist.directory)}"/>
+ <c:param name="downloadEnabled" value="${model.user.downloadRole and not model.partyModeEnabled}"/>
+ <c:param name="starEnabled" value="true"/>
+ <c:param name="starred" value="${not empty artist.starredDate}"/>
+ <c:param name="asTable" value="true"/>
+ </c:import>
+ <td ${loopStatus.count % 2 == 1 ? "class='bgcolor2'" : ""} style="padding-left:0.25em;padding-right:1.25em">
+ <a href="${mainUrl}">${artist.name}</a>
+ </td>
+ </tr>
+ </c:forEach>
+ </table>
+</c:if>
+
+<c:if test="${not empty model.albums}">
+ <h2><fmt:message key="search.hits.albums"/></h2>
+ <table style="border-collapse:collapse">
+ <c:forEach items="${model.albums}" var="album" varStatus="loopStatus">
+
+ <sub:url value="/main.view" var="mainUrl">
+ <sub:param name="path" value="${album.path}"/>
+ </sub:url>
+
+ <tr>
+ <c:import url="playAddDownload.jsp">
+ <c:param name="id" value="${album.id}"/>
+ <c:param name="playEnabled" value="${model.user.streamRole and not model.partyModeEnabled}"/>
+ <c:param name="addEnabled" value="${model.user.streamRole and (not model.partyModeEnabled or not album.directory)}"/>
+ <c:param name="downloadEnabled" value="${model.user.downloadRole and not model.partyModeEnabled}"/>
+ <c:param name="starEnabled" value="true"/>
+ <c:param name="starred" value="${not empty album.starredDate}"/>
+ <c:param name="asTable" value="true"/>
+ </c:import>
+
+ <td ${loopStatus.count % 2 == 1 ? "class='bgcolor2'" : ""} style="padding-left:0.25em;padding-right:1.25em">
+ <a href="${mainUrl}">${album.albumName}</a>
+ </td>
+
+ <td ${loopStatus.count % 2 == 1 ? "class='bgcolor2'" : ""} style="padding-right:0.25em">
+ <span class="detail">${album.artist}</span>
+ </td>
+ </tr>
+
+ </c:forEach>
+ </table>
+</c:if>
+
+<c:if test="${not empty model.songs}">
+ <h2><fmt:message key="search.hits.songs"/></h2>
+ <table style="border-collapse:collapse">
+ <c:forEach items="${model.songs}" var="song" varStatus="loopStatus">
+
+ <sub:url value="/main.view" var="mainUrl">
+ <sub:param name="path" value="${song.parentPath}"/>
+ </sub:url>
+
+ <tr>
+ <c:import url="playAddDownload.jsp">
+ <c:param name="id" value="${song.id}"/>
+ <c:param name="playEnabled" value="${model.user.streamRole and not model.partyModeEnabled}"/>
+ <c:param name="addEnabled" value="${model.user.streamRole and (not model.partyModeEnabled or not song.directory)}"/>
+ <c:param name="downloadEnabled" value="${model.user.downloadRole and not model.partyModeEnabled}"/>
+ <c:param name="starEnabled" value="true"/>
+ <c:param name="starred" value="${not empty song.starredDate}"/>
+ <c:param name="video" value="${song.video and model.player.web}"/>
+ <c:param name="asTable" value="true"/>
+ </c:import>
+
+ <td ${loopStatus.count % 2 == 1 ? "class='bgcolor2'" : ""} style="padding-left:0.25em;padding-right:1.25em">
+ ${song.title}
+ </td>
+
+ <td ${loopStatus.count % 2 == 1 ? "class='bgcolor2'" : ""} style="padding-right:1.25em">
+ <a href="${mainUrl}"><span class="detail">${song.albumName}</span></a>
+ </td>
+
+ <td ${loopStatus.count % 2 == 1 ? "class='bgcolor2'" : ""} style="padding-right:0.25em">
+ <span class="detail">${song.artist}</span>
+ </td>
+ </tr>
+
+ </c:forEach>
+ </table>
+</c:if>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/status.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/status.jsp
new file mode 100644
index 00000000..2861022d
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/status.jsp
@@ -0,0 +1,93 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <meta http-equiv="CACHE-CONTROL" content="NO-CACHE">
+ <meta http-equiv="REFRESH" content="20;URL=status.view">
+</head>
+<body class="mainframe bgcolor1">
+
+<h1>
+ <img src="<spring:theme code="statusImage"/>" alt="">
+ <fmt:message key="status.title"/>
+</h1>
+
+<table width="100%" class="ruleTable indent">
+ <tr>
+ <th class="ruleTableHeader"><fmt:message key="status.type"/></th>
+ <th class="ruleTableHeader"><fmt:message key="status.player"/></th>
+ <th class="ruleTableHeader"><fmt:message key="status.user"/></th>
+ <th class="ruleTableHeader"><fmt:message key="status.current"/></th>
+ <th class="ruleTableHeader"><fmt:message key="status.transmitted"/></th>
+ <th class="ruleTableHeader"><fmt:message key="status.bitrate"/></th>
+ </tr>
+
+ <c:forEach items="${model.transferStatuses}" var="status">
+
+ <c:choose>
+ <c:when test="${empty status.playerType}">
+ <fmt:message key="common.unknown" var="type"/>
+ </c:when>
+ <c:otherwise>
+ <c:set var="type" value="(${status.playerType})"/>
+ </c:otherwise>
+ </c:choose>
+
+ <c:choose>
+ <c:when test="${status.stream}">
+ <fmt:message key="status.stream" var="transferType"/>
+ </c:when>
+ <c:when test="${status.download}">
+ <fmt:message key="status.download" var="transferType"/>
+ </c:when>
+ <c:when test="${status.upload}">
+ <fmt:message key="status.upload" var="transferType"/>
+ </c:when>
+ </c:choose>
+
+ <c:choose>
+ <c:when test="${empty status.username}">
+ <fmt:message key="common.unknown" var="user"/>
+ </c:when>
+ <c:otherwise>
+ <c:set var="user" value="${status.username}"/>
+ </c:otherwise>
+ </c:choose>
+
+ <c:choose>
+ <c:when test="${empty status.path}">
+ <fmt:message key="common.unknown" var="current"/>
+ </c:when>
+ <c:otherwise>
+ <c:set var="current" value="${status.path}"/>
+ </c:otherwise>
+ </c:choose>
+
+ <sub:url value="/statusChart.view" var="chartUrl">
+ <c:if test="${status.stream}">
+ <sub:param name="type" value="stream"/>
+ </c:if>
+ <c:if test="${status.download}">
+ <sub:param name="type" value="download"/>
+ </c:if>
+ <c:if test="${status.upload}">
+ <sub:param name="type" value="upload"/>
+ </c:if>
+ <sub:param name="index" value="${status.index}"/>
+ </sub:url>
+
+ <tr>
+ <td class="ruleTableCell">${transferType}</td>
+ <td class="ruleTableCell">${status.player}<br>${type}</td>
+ <td class="ruleTableCell">${user}</td>
+ <td class="ruleTableCell">${current}</td>
+ <td class="ruleTableCell">${status.bytes}</td>
+ <td class="ruleTableCell" width="${model.chartWidth}"><img width="${model.chartWidth}" height="${model.chartHeight}" src="${chartUrl}" alt=""></td>
+ </tr>
+ </c:forEach>
+</table>
+
+<div class="forward"><a href="status.view?"><fmt:message key="common.refresh"/></a></div>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/test.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/test.jsp
new file mode 100644
index 00000000..64f7c792
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/test.jsp
@@ -0,0 +1,20 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+
+<html>
+<head>
+ <%@ include file="head.jsp" %>
+ <script type="text/javascript" src="<c:url value="/script/prototype.js"/>"></script>
+</head>
+<body>
+
+<div id="tFfBc" style="opacity:0;">
+ <img src="/coverArt.view?size=200" alt="">
+</div>
+
+<ul>
+ <li><a href="#" onclick="new Effect.Opacity('tFfBc', { from: 1, to: 0 }); return false;">Hide this box</a></li>
+ <li><a href="#" onclick="new Effect.Opacity('tFfBc', { from: 0, to: 1 }); return false;">Show this box</a></li>
+</ul>
+
+</body>
+</html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/top.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/top.jsp
new file mode 100644
index 00000000..18acb053
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/top.jsp
@@ -0,0 +1,98 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html><head>
+ <%@ include file="head.jsp" %>
+</head>
+
+<body class="bgcolor2 topframe" style="margin:0.4em 1em 0.4em 1em">
+
+<fmt:message key="top.home" var="home"/>
+<fmt:message key="top.now_playing" var="nowPlaying"/>
+<fmt:message key="top.starred" var="starred"/>
+<fmt:message key="top.settings" var="settings"/>
+<fmt:message key="top.status" var="status"/>
+<fmt:message key="top.podcast" var="podcast"/>
+<fmt:message key="top.more" var="more"/>
+<fmt:message key="top.help" var="help"/>
+<fmt:message key="top.search" var="search"/>
+
+<table style="margin:0"><tr valign="middle">
+ <td class="logo" style="padding-right:2em"><a href="help.view?" target="main"><img src="<spring:theme code="logoImage"/>" title="${help}" alt=""></a></td>
+
+ <c:if test="${not model.musicFoldersExist}">
+ <td style="padding-right:2em">
+ <p class="warning"><fmt:message key="top.missing"/></p>
+ </td>
+ </c:if>
+
+ <td>
+ <table><tr align="center">
+ <td style="min-width:4em;padding-right:1.5em">
+ <a href="home.view?" target="main"><img src="<spring:theme code="homeImage"/>" title="${home}" alt="${home}"></a><br>
+ <a href="home.view?" target="main">${home}</a>
+ </td>
+ <td style="min-width:4em;padding-right:1.5em">
+ <a href="nowPlaying.view?" target="main"><img src="<spring:theme code="nowPlayingImage"/>" title="${nowPlaying}" alt="${nowPlaying}"></a><br>
+ <a href="nowPlaying.view?" target="main">${nowPlaying}</a>
+ </td>
+ <td style="min-width:4em;padding-right:1.5em">
+ <a href="starred.view?" target="main"><img src="<spring:theme code="starredImage"/>" title="${starred}" alt="${starred}"></a><br>
+ <a href="starred.view?" target="main">${starred}</a>
+ </td>
+ <td style="min-width:4em;padding-right:1.5em">
+ <a href="podcastReceiver.view?" target="main"><img src="<spring:theme code="podcastLargeImage"/>" title="${podcast}" alt="${podcast}"></a><br>
+ <a href="podcastReceiver.view?" target="main">${podcast}</a>
+ </td>
+ <c:if test="${model.user.settingsRole}">
+ <td style="min-width:4em;padding-right:1.5em">
+ <a href="settings.view?" target="main"><img src="<spring:theme code="settingsImage"/>" title="${settings}" alt="${settings}"></a><br>
+ <a href="settings.view?" target="main">${settings}</a>
+ </td>
+ </c:if>
+ <td style="min-width:4em;padding-right:1.5em">
+ <a href="status.view?" target="main"><img src="<spring:theme code="statusImage"/>" title="${status}" alt="${status}"></a><br>
+ <a href="status.view?" target="main">${status}</a>
+ </td>
+ <td style="min-width:4em;padding-right:1.5em">
+ <a href="more.view?" target="main"><img src="<spring:theme code="moreImage"/>" title="${more}" alt="${more}"></a><br>
+ <a href="more.view?" target="main">${more}</a>
+ </td>
+ <td style="min-width:4em;padding-right:1.5em">
+ <a href="help.view?" target="main"><img src="<spring:theme code="helpImage"/>" title="${help}" alt="${help}"></a><br>
+ <a href="help.view?" target="main">${help}</a>
+ </td>
+
+ <td style="padding-left:1em">
+ <form method="post" action="search.view" target="main" name="searchForm">
+ <table><tr>
+ <td><input type="text" name="query" id="query" size="28" value="${search}" onclick="select();"></td>
+ <td><a href="javascript:document.searchForm.submit()"><img src="<spring:theme code="searchImage"/>" alt="${search}" title="${search}"></a></td>
+ </tr></table>
+ </form>
+ </td>
+
+ <td style="padding-left:15pt;text-align:center;">
+ <p class="detail" style="line-height:1.5">
+ <a href="j_acegi_logout" target="_top"><fmt:message key="top.logout"><fmt:param value="${model.user.username}"/></fmt:message></a>
+ <c:if test="${not model.licensed}">
+ <br>
+ <a href="donate.view" target="main"><img src="<spring:theme code="donateSmallImage"/>" alt=""></a>
+ <a href="donate.view" target="main"><fmt:message key="donate.title"/></a>
+ </c:if>
+ </p>
+ </td>
+
+ <c:if test="${model.newVersionAvailable}">
+ <td style="padding-left:15pt">
+ <p class="warning">
+ <fmt:message key="top.upgrade"><fmt:param value="${model.brand}"/><fmt:param value="${model.latestVersion}"/></fmt:message>
+ </p>
+ </td>
+ </c:if>
+ </tr></table>
+ </td>
+
+</tr></table>
+
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/transcodingSettings.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/transcodingSettings.jsp
new file mode 100644
index 00000000..a641cb53
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/transcodingSettings.jsp
@@ -0,0 +1,70 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+</head>
+<body class="mainframe bgcolor1">
+
+<c:import url="settingsHeader.jsp">
+ <c:param name="cat" value="transcoding"/>
+</c:import>
+
+<form method="post" action="transcodingSettings.view">
+<table class="indent">
+ <tr>
+ <th><fmt:message key="transcodingsettings.name"/></th>
+ <th><fmt:message key="transcodingsettings.sourceformat"/></th>
+ <th><fmt:message key="transcodingsettings.targetformat"/></th>
+ <th><fmt:message key="transcodingsettings.step1"/></th>
+ <th><fmt:message key="transcodingsettings.step2"/></th>
+ <th style="padding-left:1em"><fmt:message key="common.delete"/></th>
+ </tr>
+
+ <c:forEach items="${model.transcodings}" var="transcoding">
+ <tr>
+ <td><input style="font-family:monospace" type="text" name="name[${transcoding.id}]" size="10" value="${transcoding.name}"/></td>
+ <td><input style="font-family:monospace" type="text" name="sourceFormats[${transcoding.id}]" size="36" value="${transcoding.sourceFormats}"/></td>
+ <td><input style="font-family:monospace" type="text" name="targetFormat[${transcoding.id}]" size="10" value="${transcoding.targetFormat}"/></td>
+ <td><input style="font-family:monospace" type="text" name="step1[${transcoding.id}]" size="60" value="${transcoding.step1}"/></td>
+ <td><input style="font-family:monospace" type="text" name="step2[${transcoding.id}]" size="22" value="${transcoding.step2}"/></td>
+ <td align="center" style="padding-left:1em"><input type="checkbox" name="delete[${transcoding.id}]" class="checkbox"/></td>
+ </tr>
+ </c:forEach>
+
+ <tr>
+ <th colspan="6" align="left" style="padding-top:1em"><fmt:message key="transcodingsettings.add"/></th>
+ </tr>
+
+ <tr>
+ <td><input style="font-family:monospace" type="text" name="name" size="10" value="${model.newTranscoding.name}"/></td>
+ <td><input style="font-family:monospace" type="text" name="sourceFormats" size="36" value="${model.newTranscoding.sourceFormats}"/></td>
+ <td><input style="font-family:monospace" type="text" name="targetFormat" size="10" value="${model.newTranscoding.targetFormat}"/></td>
+ <td><input style="font-family:monospace" type="text" name="step1" size="60" value="${model.newTranscoding.step1}"/></td>
+ <td><input style="font-family:monospace" type="text" name="step2" size="22" value="${model.newTranscoding.step2}"/></td>
+ <td/>
+ </tr>
+
+ <tr>
+ <td colspan="6" style="padding-top:0.1em">
+ <input type="checkbox" id="defaultActive" name="defaultActive" class="checkbox" checked/>
+ <label for="defaultActive"><fmt:message key="transcodingsettings.defaultactive"/></label>
+ </td>
+ </tr>
+</table>
+
+ <p style="padding-top:0.75em">
+ <input type="submit" value="<fmt:message key="common.save"/>" style="margin-right:0.3em">
+ <input type="button" value="<fmt:message key="common.cancel"/>" onclick="location.href='nowPlaying.view'" style="margin-right:1.3em">
+ <a href="http://www.subsonic.org/pages/transcoding.jsp" target="_blank"><fmt:message key="transcodingsettings.recommended"/></a>
+ </p>
+
+</form>
+
+<c:if test="${not empty model.error}">
+ <p class="warning"><fmt:message key="${model.error}"/></p>
+</c:if>
+
+<div style="width:60%">
+ <fmt:message key="transcodingsettings.info"><fmt:param value="${model.transcodeDirectory}"/><fmt:param value="${model.brand}"/></fmt:message>
+</div>
+</body></html> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/upload.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/upload.jsp
new file mode 100644
index 00000000..eb79d17c
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/upload.jsp
@@ -0,0 +1,29 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+</head><body>
+
+<h1><fmt:message key="upload.title"/></h1>
+
+<c:forEach items="${model.uploadedFiles}" var="file">
+ <p><fmt:message key="upload.success"><fmt:param value="${file.path}"/></fmt:message></p>
+</c:forEach>
+
+<c:forEach items="${model.unzippedFiles}" var="file">
+ <fmt:message key="upload.unzipped"><fmt:param value="${file.path}"/></fmt:message><br/>
+</c:forEach>
+
+<c:choose>
+ <c:when test="${not empty model.exception}">
+ <p><fmt:message key="upload.failed"><fmt:param value="${model.exception.message}"/></fmt:message></p>
+ </c:when>
+ <c:when test="${empty model.uploadedFiles}">
+ <p><fmt:message key="upload.empty"/></p>
+ </c:when>
+</c:choose>
+
+<div class="back"><a href="more.view?"><fmt:message key="common.back"/></a></div>
+</body></html>
+
+
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/userSettings.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/userSettings.jsp
new file mode 100644
index 00000000..a26c9113
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/userSettings.jsp
@@ -0,0 +1,201 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<html><head>
+ <%@ include file="head.jsp" %>
+ <script type="text/javascript" src="<c:url value="/script/scripts.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/prototype.js"/>"></script>
+</head>
+
+<body class="mainframe bgcolor1" onload="enablePasswordChangeFields();">
+<script type="text/javascript" src="<c:url value="/script/wz_tooltip.js"/>"></script>
+<script type="text/javascript" src="<c:url value="/script/tip_balloon.js"/>"></script>
+
+<c:import url="settingsHeader.jsp">
+ <c:param name="cat" value="user"/>
+</c:import>
+
+<script type="text/javascript" language="javascript">
+ function enablePasswordChangeFields() {
+ var changePasswordCheckbox = $("passwordChange");
+ var ldapCheckbox = $("ldapAuthenticated");
+ var passwordChangeTable = $("passwordChangeTable");
+ var passwordChangeCheckboxTable = $("passwordChangeCheckboxTable");
+
+ if (changePasswordCheckbox && changePasswordCheckbox.checked && (ldapCheckbox == null || !ldapCheckbox.checked)) {
+ passwordChangeTable.show();
+ } else {
+ passwordChangeTable.hide();
+ }
+
+ if (changePasswordCheckbox) {
+ if (ldapCheckbox && ldapCheckbox.checked) {
+ passwordChangeCheckboxTable.hide();
+ } else {
+ passwordChangeCheckboxTable.show();
+ }
+ }
+ }
+</script>
+
+<table class="indent">
+ <tr>
+ <td><b><fmt:message key="usersettings.title"/></b></td>
+ <td>
+ <select name="username" onchange="location='userSettings.view?userIndex=' + (selectedIndex - 1);">
+ <option value="">-- <fmt:message key="usersettings.newuser"/> --</option>
+ <c:forEach items="${command.users}" var="user">
+ <option ${user.username eq command.username ? "selected" : ""}
+ value="${user.username}">${user.username}</option>
+ </c:forEach>
+ </select>
+ </td>
+ </tr>
+</table>
+
+<p/>
+
+<form:form method="post" action="userSettings.view" commandName="command">
+ <c:if test="${not command.admin}">
+ <table>
+ <tr>
+ <td><form:checkbox path="adminRole" id="admin" cssClass="checkbox"/></td>
+ <td><label for="admin"><fmt:message key="usersettings.admin"/></label></td>
+ </tr>
+ <tr>
+ <td><form:checkbox path="settingsRole" id="settings" cssClass="checkbox"/></td>
+ <td><label for="settings"><fmt:message key="usersettings.settings"/></label></td>
+ </tr>
+ <tr>
+ <td style="padding-top:1em"><form:checkbox path="streamRole" id="stream" cssClass="checkbox"/></td>
+ <td style="padding-top:1em"><label for="stream"><fmt:message key="usersettings.stream"/></label></td>
+ </tr>
+ <tr>
+ <td><form:checkbox path="jukeboxRole" id="jukebox" cssClass="checkbox"/></td>
+ <td><label for="jukebox"><fmt:message key="usersettings.jukebox"/></label></td>
+ </tr>
+ <tr>
+ <td><form:checkbox path="downloadRole" id="download" cssClass="checkbox"/></td>
+ <td><label for="download"><fmt:message key="usersettings.download"/></label></td>
+ </tr>
+ <tr>
+ <td><form:checkbox path="uploadRole" id="upload" cssClass="checkbox"/></td>
+ <td><label for="upload"><fmt:message key="usersettings.upload"/></label></td>
+ </tr>
+ <tr>
+ <td><form:checkbox path="shareRole" id="share" cssClass="checkbox"/></td>
+ <td><label for="share"><fmt:message key="usersettings.share"/></label></td>
+ </tr>
+ <tr>
+ <td><form:checkbox path="coverArtRole" id="coverArt" cssClass="checkbox"/></td>
+ <td><label for="coverArt"><fmt:message key="usersettings.coverart"/></label></td>
+ </tr>
+ <tr>
+ <td><form:checkbox path="commentRole" id="comment" cssClass="checkbox"/></td>
+ <td><label for="comment"><fmt:message key="usersettings.comment"/></label></td>
+ </tr>
+ <tr>
+ <td><form:checkbox path="podcastRole" id="podcast" cssClass="checkbox"/></td>
+ <td><label for="podcast"><fmt:message key="usersettings.podcast"/></label></td>
+ </tr>
+ </table>
+ </c:if>
+
+ <table class="indent">
+ <tr>
+ <td><fmt:message key="playersettings.maxbitrate"/></td>
+ <td>
+ <form:select path="transcodeSchemeName" cssStyle="width:8em">
+ <c:forEach items="${command.transcodeSchemeHolders}" var="transcodeSchemeHolder">
+ <form:option value="${transcodeSchemeHolder.name}" label="${transcodeSchemeHolder.description}"/>
+ </c:forEach>
+ </form:select>
+ </td>
+ <td><c:import url="helpToolTip.jsp"><c:param name="topic" value="transcode"/></c:import></td>
+ <c:if test="${not command.transcodingSupported}">
+ <td class="warning"><fmt:message key="playersettings.nolame"/></td>
+ </c:if>
+ </tr>
+ </table>
+
+ <c:if test="${not command.new and not command.admin}">
+ <table class="indent">
+ <tr>
+ <td><form:checkbox path="delete" id="delete" cssClass="checkbox"/></td>
+ <td><label for="delete"><fmt:message key="usersettings.delete"/></label></td>
+ </tr>
+ </table>
+ </c:if>
+
+ <c:if test="${command.ldapEnabled and not command.admin}">
+ <table>
+ <tr>
+ <td><form:checkbox path="ldapAuthenticated" id="ldapAuthenticated" cssClass="checkbox" onclick="javascript:enablePasswordChangeFields()"/></td>
+ <td><label for="ldapAuthenticated"><fmt:message key="usersettings.ldap"/></label></td>
+ <td><c:import url="helpToolTip.jsp"><c:param name="topic" value="ldap"/></c:import></td>
+ </tr>
+ </table>
+ </c:if>
+
+ <c:choose>
+ <c:when test="${command.new}">
+
+ <table class="indent">
+ <tr>
+ <td><fmt:message key="usersettings.username"/></td>
+ <td><form:input path="username"/></td>
+ <td class="warning"><form:errors path="username"/></td>
+ </tr>
+ <tr>
+ <td><fmt:message key="usersettings.email"/></td>
+ <td><form:input path="email"/></td>
+ <td class="warning"><form:errors path="email"/></td>
+ </tr>
+ <tr>
+ <td><fmt:message key="usersettings.password"/></td>
+ <td><form:password path="password"/></td>
+ <td class="warning"><form:errors path="password"/></td>
+ </tr>
+ <tr>
+ <td><fmt:message key="usersettings.confirmpassword"/></td>
+ <td><form:password path="confirmPassword"/></td>
+ <td/>
+ </tr>
+ </table>
+ </c:when>
+
+ <c:otherwise>
+ <table id="passwordChangeCheckboxTable">
+ <tr>
+ <td><form:checkbox path="passwordChange" id="passwordChange" onclick="enablePasswordChangeFields();" cssClass="checkbox"/></td>
+ <td><label for="passwordChange"><fmt:message key="usersettings.changepassword"/></label></td>
+ </tr>
+ </table>
+
+ <table id="passwordChangeTable" style="display:none">
+ <tr>
+ <td><fmt:message key="usersettings.newpassword"/></td>
+ <td><form:password path="password" id="path"/></td>
+ <td class="warning"><form:errors path="password"/></td>
+ </tr>
+ <tr>
+ <td><fmt:message key="usersettings.confirmpassword"/></td>
+ <td><form:password path="confirmPassword" id="confirmPassword"/></td>
+ <td/>
+ </tr>
+ </table>
+
+ <table>
+ <tr>
+ <td><fmt:message key="usersettings.email"/></td>
+ <td><form:input path="email"/></td>
+ <td class="warning"><form:errors path="email"/></td>
+ </tr>
+ </table>
+ </c:otherwise>
+ </c:choose>
+
+ <input type="submit" value="<fmt:message key="common.save"/>" style="margin-top:1.5em;margin-right:0.3em">
+ <input type="button" value="<fmt:message key="common.cancel"/>" onclick="location.href='nowPlaying.view'" style="margin-top:1.5em">
+</form:form>
+
+</body></html>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/videoPlayer.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/videoPlayer.jsp
new file mode 100644
index 00000000..681a41a8
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/videoPlayer.jsp
@@ -0,0 +1,190 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="iso-8859-1" %>
+
+<html>
+<head>
+ <%@ include file="head.jsp" %>
+
+ <sub:url value="videoPlayer.view" var="baseUrl"><sub:param name="id" value="${model.video.id}"/></sub:url>
+ <sub:url value="main.view" var="backUrl"><sub:param name="id" value="${model.video.id}"/></sub:url>
+
+ <sub:url value="/stream" var="streamUrl">
+ <sub:param name="id" value="${model.video.id}"/>
+ </sub:url>
+
+ <script type="text/javascript" src="<c:url value="/script/swfobject.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/prototype.js"/>"></script>
+ <script type="text/javascript" src="<c:url value="/script/scripts.js"/>"></script>
+ <script type="text/javascript" language="javascript">
+
+ var player;
+ var position;
+ var maxBitRate = ${model.maxBitRate};
+ var timeOffset = ${model.timeOffset};
+
+ function init() {
+
+ var flashvars = {
+ id:"player1",
+ skin:"<c:url value="/flash/whotube.zip"/>",
+// plugins:"metaviewer-1",
+ screencolor:"000000",
+ controlbar:"over",
+ autostart:"false",
+ bufferlength:3,
+ backcolor:"<spring:theme code="backgroundColor"/>",
+ frontcolor:"<spring:theme code="textColor"/>",
+ provider:"video"
+ };
+ var params = {
+ allowfullscreen:"true",
+ allowscriptaccess:"always"
+ };
+ var attributes = {
+ id:"player1",
+ name:"player1"
+ };
+
+ var width = "${model.popout ? '100%' : '600'}";
+ var height = "${model.popout ? '85%' : '360'}";
+ swfobject.embedSWF("<c:url value="/flash/jw-player-5.6.swf"/>", "placeholder1", width, height, "9.0.0", false, flashvars, params, attributes);
+ }
+
+ function playerReady(thePlayer) {
+ player = $("player1");
+ player.addModelListener("TIME", "timeListener");
+
+ <c:if test="${not (model.trial and model.trialExpired)}">
+ play();
+ </c:if>
+ }
+
+ function play() {
+ var list = new Array();
+ list[0] = {
+ file:"${streamUrl}&maxBitRate=" + maxBitRate + "&timeOffset=" + timeOffset + "&player=${model.player}",
+ duration:${model.duration} - timeOffset,
+ provider:"video"
+ };
+ player.sendEvent("LOAD", list);
+ player.sendEvent("PLAY");
+ }
+
+ function timeListener(obj) {
+ var newPosition = Math.round(obj.position);
+ if (newPosition != position) {
+ position = newPosition;
+ updatePosition();
+ }
+ }
+
+ function updatePosition() {
+ var pos = getPosition();
+
+ var minutes = Math.round(pos / 60);
+ var seconds = pos % 60;
+
+ var result = minutes + ":";
+ if (seconds < 10) {
+ result += "0";
+ }
+ result += seconds;
+ $("position").innerHTML = result;
+ }
+
+ function changeTimeOffset() {
+ timeOffset = $("timeOffset").getValue();
+ play();
+ }
+
+ function changeBitRate() {
+ maxBitRate = $("maxBitRate").getValue();
+ timeOffset = getPosition();
+ play();
+ }
+
+ function popout() {
+ var url = "${baseUrl}&maxBitRate=" + maxBitRate + "&timeOffset=" + getPosition() + "&popout=true";
+ popupSize(url, "video", 600, 400);
+ window.location.href = "${backUrl}";
+ }
+
+ function popin() {
+ window.opener.location.href = "${baseUrl}&maxBitRate=" + maxBitRate + "&timeOffset=" + getPosition();
+ window.close();
+ }
+
+ function getPosition() {
+ return parseInt(timeOffset) + parseInt(position);
+ }
+
+ </script>
+</head>
+
+<body class="mainframe bgcolor1" style="padding-bottom:0.5em" onload="init();">
+<c:if test="${not model.popout}">
+ <h1>${model.video.title}</h1>
+</c:if>
+
+<c:if test="${model.trial}">
+ <fmt:formatDate value="${model.trialExpires}" dateStyle="long" var="expiryDate"/>
+
+ <p class="warning" style="padding-top:1em">
+ <c:choose>
+ <c:when test="${model.trialExpired}">
+ <fmt:message key="networksettings.trialexpired"><fmt:param>${expiryDate}</fmt:param></fmt:message>
+ </c:when>
+ <c:otherwise>
+ <fmt:message
+ key="networksettings.trialnotexpired"><fmt:param>${expiryDate}</fmt:param></fmt:message>
+ </c:otherwise>
+ </c:choose>
+ </p>
+</c:if>
+
+
+<div id="wrapper" style="padding-top:1em">
+ <div id="placeholder1"><a href="http://www.adobe.com/go/getflashplayer" target="_blank"><fmt:message key="playlist.getflash"/></a></div>
+</div>
+
+<div style="padding-top:0.7em;padding-bottom:0.7em">
+
+ <span id="position" style="padding-right:0.5em">0:00</span>
+ <select id="timeOffset" onchange="changeTimeOffset();" style="padding-left:0.25em;padding-right:0.25em;margin-right:0.5em">
+ <c:forEach items="${model.skipOffsets}" var="skipOffset">
+ <c:choose>
+ <c:when test="${skipOffset.value - skipOffset.value mod 60 eq model.timeOffset - model.timeOffset mod 60}">
+ <option selected="selected" value="${skipOffset.value}">${skipOffset.key}</option>
+ </c:when>
+ <c:otherwise>
+ <option value="${skipOffset.value}">${skipOffset.key}</option>
+ </c:otherwise>
+ </c:choose>
+ </c:forEach>
+ </select>
+
+ <select id="maxBitRate" onchange="changeBitRate();" style="padding-left:0.25em;padding-right:0.25em;margin-right:0.5em">
+ <c:forEach items="${model.bitRates}" var="bitRate">
+ <c:choose>
+ <c:when test="${bitRate eq model.maxBitRate}">
+ <option selected="selected" value="${bitRate}">${bitRate} Kbps</option>
+ </c:when>
+ <c:otherwise>
+ <option value="${bitRate}">${bitRate} Kbps</option>
+ </c:otherwise>
+ </c:choose>
+ </c:forEach>
+ </select>
+</div>
+
+<c:choose>
+ <c:when test="${model.popout}">
+ <div class="back"><a href="javascript:popin();"><fmt:message key="common.back"/></a></div>
+ </c:when>
+ <c:otherwise>
+ <div class="back" style="float:left;padding-right:2em"><a href="${backUrl}"><fmt:message key="common.back"/></a></div>
+ <div class="forward" style="float:left;"><a href="javascript:popout();"><fmt:message key="videoPlayer.popout"/></a></div>
+ </c:otherwise>
+</c:choose>
+
+</body>
+</html>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/browse.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/browse.jsp
new file mode 100644
index 00000000..ac1ce096
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/browse.jsp
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml">
+
+<%@ page language="java" contentType="text/vnd.wap.wml; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<wml>
+
+ <%@ include file="head.jsp" %>
+
+ <card id="main" title="Subsonic" newcontext="false">
+
+ <p><small><b>
+
+ <sub:url value="/wap/playlist.view" var="playUrl">
+ <sub:param name="play" value="${model.parent.path}"/>
+ </sub:url>
+ <sub:url value="/wap/playlist.view" var="addUrl">
+ <sub:param name="add" value="${model.parent.path}"/>
+ </sub:url>
+ <sub:url value="/wap/download.view" var="downloadUrl">
+ <sub:param name="path" value="${model.parent.path}"/>
+ </sub:url>
+
+ <c:choose>
+ <c:when test="${fn:length(model.children) eq 1 and model.children[0].file}">
+ <a href="${playUrl}">[<fmt:message key="wap.browse.playone"/>]</a><br/>
+ <a href="${addUrl}">[<fmt:message key="wap.browse.addone"/>]</a><br/>
+ <c:if test="${model.user.downloadRole}">
+ <a href="${downloadUrl}">[<fmt:message key="wap.browse.downloadone"/>]</a><br/>
+ </c:if>
+ </c:when>
+ <c:otherwise>
+ <a href="${playUrl}">[<fmt:message key="wap.browse.playall"/>]</a><br/>
+ <a href="${addUrl}">[<fmt:message key="wap.browse.addall"/>]</a><br/>
+ <c:if test="${model.user.downloadRole}">
+ <a href="${downloadUrl}">[<fmt:message key="wap.browse.downloadall"/>]</a><br/>
+ </c:if>
+ </c:otherwise>
+ </c:choose>
+
+ <a href="<c:url value="/wap/index.view"/>">[<fmt:message key="common.home"/>]</a><br/>
+ </b></small></p>
+
+ <p><small>
+
+ <c:forEach items="${model.children}" var="child">
+ <sub:url value="/wap/browse.view" var="browseUrl">
+ <sub:param name="path" value="${child.path}"/>
+ </sub:url>
+ <a href="${browseUrl}">${fn:escapeXml(child.title)}</a><br/>
+ </c:forEach>
+
+ </small></p>
+ </card>
+</wml>
+
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/head.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/head.jsp
new file mode 100644
index 00000000..d902de1d
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/head.jsp
@@ -0,0 +1,10 @@
+<%@ include file="../include.jsp" %>
+
+<head>
+ <meta http-equiv="Cache-Control" content="max-age=0" forua="true"/>
+ <meta http-equiv="Cache-Control" content="must-revalidate" forua="true"/>
+</head>
+
+<template>
+ <do type="prev" name="back" label="<fmt:message key="common.back"/>"><prev/></do>
+</template>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/index.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/index.jsp
new file mode 100644
index 00000000..2773a55e
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/index.jsp
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml">
+
+<%@ page language="java" contentType="text/vnd.wap.wml; charset=utf-8" pageEncoding="iso-8859-1" %>
+
+<wml>
+
+ <%@ include file="head.jsp" %>
+
+ <card id="main" title="Subsonic" newcontext="false">
+ <p>
+ <small>
+
+ <c:choose>
+ <c:when test="${empty model.artists}">
+
+ <b>
+ <a href="<c:url value="/wap/playlist.view"/>">[<fmt:message key="wap.index.playlist"/>]</a>
+ </b>
+ <br/>
+ <b>
+ <a href="<c:url value="/wap/search.view"/>">[<fmt:message key="wap.index.search"/>]</a>
+ </b>
+ <br/>
+ <b>
+ <a href="<c:url value="/wap/settings.view"/>">[<fmt:message key="wap.index.settings"/>]</a>
+ </b>
+ <br/>
+ </small>
+ </p>
+ <p>
+ <small>
+ <c:forEach items="${model.indexes}" var="index">
+ <sub:url var="url" value="/wap/index.view">
+ <sub:param name="index" value="${index.index}"/>
+ </sub:url>
+ <a href="${url}">${index.index}</a>
+ </c:forEach>
+ </c:when>
+
+ <c:otherwise>
+ <c:forEach items="${model.artists}" var="artist">
+ <c:forEach items="${artist.musicFiles}" var="mediaFile">
+ <sub:url var="url" value="/wap/browse.view">
+ <sub:param name="path" value="${mediaFile.path}"/>
+ </sub:url>
+ <a href="${url}">${fn:escapeXml(mediaFile.title)}</a>
+ <br/>
+ </c:forEach>
+ </c:forEach>
+ </c:otherwise>
+ </c:choose>
+
+ <c:if test="${model.noMusic}">
+ <fmt:message key="wap.index.missing"/>
+ </c:if>
+
+ </small>
+ </p>
+ </card>
+</wml>
+
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/loadPlaylist.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/loadPlaylist.jsp
new file mode 100644
index 00000000..2640cce0
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/loadPlaylist.jsp
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml">
+
+<%@ page language="java" contentType="text/vnd.wap.wml; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<wml>
+
+ <%@ include file="head.jsp" %>
+
+ <card id="main" title="Subsonic" newcontext="false">
+ <p><small>
+
+ <c:forEach items="${model.playlists}" var="playlist">
+ <sub:url var="url" value="/wap/playlist.view">
+ <sub:param name="load" value="${playlist.id}"/>
+ </sub:url>
+ <b><a href="${url}">${fn:escapeXml(playlist.name)}</a></b><br/>
+ </c:forEach>
+ </small></p>
+
+ </card>
+</wml>
+
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/playlist.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/playlist.jsp
new file mode 100644
index 00000000..481e00d3
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/playlist.jsp
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml">
+
+<%@ page language="java" contentType="text/vnd.wap.wml; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<wml>
+
+ <%@ include file="head.jsp" %>
+
+ <c:if test="${fn:length(model.players) eq 1}">
+ <c:choose>
+ <c:when test="${empty model.players[0].name}">
+ <c:set var="playerName" value=" - Player ${model.players[0].id}"/>
+ </c:when>
+ <c:otherwise>
+ <c:set var="playerName" value=" - ${model.players[0].name}"/>
+ </c:otherwise>
+ </c:choose>
+ </c:if>
+
+
+ <card id="main" title="Subsonic" newcontext="false">
+ <p><small><b><fmt:message key="wap.playlist.title"/>${playerName}</b></small></p>
+ <p><small>
+
+ <c:choose>
+ <c:when test="${empty model.players}">
+ <fmt:message key="wap.playlist.noplayer"/>
+ </c:when>
+ <c:otherwise>
+ <b><a href="<c:url value="/wap/index.view"/>">[<fmt:message key="common.home"/>]</a></b><br/>
+ <b><a href="<c:url value="/wap/loadPlaylist.view"/>">[<fmt:message key="wap.playlist.load"/>]</a></b><br/>
+ <b><a href="<c:url value="/wap/playlist.view?random"/>">[<fmt:message key="wap.playlist.random"/>]</a></b><br/>
+
+ <c:set var="playlist" value="${model.players[0].playlist}"/>
+
+ <c:if test="${not empty playlist.files}">
+ <b><a href="<c:url value="/play.m3u"/>">[<fmt:message key="wap.playlist.play"/>]</a></b><br/>
+ <b><a href="<c:url value="/wap/playlist.view?clear"/>">[<fmt:message key="wap.playlist.clear"/>]</a></b><br/>
+ </small></p>
+ <p><small>
+
+ <c:forEach items="${playlist.files}" var="file" varStatus="loopStatus">
+ <c:set var="isCurrent" value="${(file eq playlist.currentFile) and (loopStatus.count - 1 eq playlist.index)}"/>
+ ${isCurrent ? "<b>" : ""}
+ <a href="<c:url value="/wap/playlist.view?skip=${loopStatus.count - 1}"/>">${fn:escapeXml(file.title)}</a>
+ ${isCurrent ? "</b>" : ""}
+ <br/>
+ </c:forEach>
+ </c:if>
+ </c:otherwise>
+ </c:choose>
+ </small></p>
+ </card>
+</wml>
+
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/search.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/search.jsp
new file mode 100644
index 00000000..b35b04a3
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/search.jsp
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml">
+
+<%@ page language="java" contentType="text/vnd.wap.wml; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<wml>
+ <%@ include file="head.jsp" %>
+ <card id="main" title="Subsonic" newcontext="false">
+ <p>
+ <input name="query" value="" size="10"/>
+ <anchor><fmt:message key="wap.search.title"/>
+ <go href="<c:url value="/wap/searchResult.view"/>" method="get">
+ <postfield name="query" value="$query"/>
+ </go>
+ </anchor>
+ </p>
+ </card>
+</wml>
+
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/searchResult.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/searchResult.jsp
new file mode 100644
index 00000000..2267c069
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/searchResult.jsp
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml">
+
+<%@ page language="java" contentType="text/vnd.wap.wml; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<wml>
+
+ <%@ include file="head.jsp" %>
+ <card id="main" title="Subsonic" newcontext="false">
+ <p><small>
+
+ <c:choose>
+ <c:when test="${model.creatingIndex}">
+ <fmt:message key="wap.searchresult.index"/>
+ </c:when>
+
+ <c:otherwise>
+ <c:forEach items="${model.hits}" var="hit">
+ <sub:url var="url" value="/wap/browse.view">
+ <sub:param name="path" value="${hit.path}"/>
+ </sub:url>
+ <a href="${url}">${fn:escapeXml(hit.title)}</a><br/>
+ </c:forEach>
+ </c:otherwise>
+ </c:choose>
+ </small></p>
+ </card>
+
+</wml>
+
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/settings.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/settings.jsp
new file mode 100644
index 00000000..5c44e87d
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/wap/settings.jsp
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml">
+
+<%@ page language="java" contentType="text/vnd.wap.wml; charset=utf-8" pageEncoding="iso-8859-1"%>
+
+<wml>
+
+ <%@ include file="head.jsp" %>
+ <card id="main" title="Subsonic" newcontext="false">
+ <p><small>
+ <b><a href="<c:url value="/wap/index.view"/>">[<fmt:message key="common.home"/>]</a><br/></b>
+ <b><a href="#player">[<fmt:message key="wap.settings.selectplayer"/>]</a></b>
+ </small></p>
+ </card>
+
+ <card id="player" title="Subsonic" newcontext="false">
+ <p><small>
+
+ <b><a href="<c:url value="/wap/index.view"/>">[<fmt:message key="common.home"/>]</a><br/></b>
+ </small></p><p><small>
+
+ <c:choose>
+ <c:when test="${empty model.playerId}">
+ <fmt:message key="wap.settings.allplayers"/>
+ </c:when>
+ <c:otherwise>
+ <a href="<c:url value="/wap/selectPlayer.view"/>"><fmt:message key="wap.settings.allplayers"/></a>
+ </c:otherwise>
+ </c:choose>
+ <br/>
+
+ <c:forEach items="${model.players}" var="player">
+ <c:choose>
+ <c:when test="${player.id eq model.playerId}">
+ ${player}
+ </c:when>
+ <c:otherwise>
+ <a href="<c:url value="/wap/selectPlayer.view?playerId=${player.id}"/>">${player}</a>
+ </c:otherwise>
+ </c:choose>
+ <br/>
+ </c:forEach>
+ </small></p>
+ </card>
+
+</wml>
+
diff --git a/subsonic-main/src/main/webapp/WEB-INF/jsp/xspfPlaylist.jsp b/subsonic-main/src/main/webapp/WEB-INF/jsp/xspfPlaylist.jsp
new file mode 100644
index 00000000..4ee8fb46
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/jsp/xspfPlaylist.jsp
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<%@ include file="include.jsp" %>
+<%@ page language="java" contentType="text/xml; charset=utf-8" pageEncoding="iso-8859-1" %>
+
+<playlist version="0" xmlns="http://xspf.org/ns/0/">
+ <trackList>
+
+<c:forEach var="song" items="${model.songs}">
+
+ <sub:url value="/stream" var="streamUrl">
+ <sub:param name="path" value="${song.musicFile.path}"/>
+ </sub:url>
+
+ <sub:url value="coverArt.view" var="coverArtUrl">
+ <sub:param name="size" value="200"/>
+ <c:if test="${not empty song.coverArtFile}">
+ <sub:param name="path" value="${song.coverArtFile.path}"/>
+ </c:if>
+ </sub:url>
+
+ <track>
+ <location>${streamUrl}</location>
+ <image>${coverArtUrl}</image>
+ <annotation>${song.musicFile.metaData.artist} - ${song.musicFile.title}</annotation>
+ </track>
+
+</c:forEach>
+
+ </trackList>
+</playlist> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/sub.tld b/subsonic-main/src/main/webapp/WEB-INF/sub.tld
new file mode 100644
index 00000000..ef712968
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/sub.tld
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+
+<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
+ version="2.0">
+
+ <description>Subsonic tag library</description>
+ <display-name>Subsonic tag library</display-name>
+ <tlib-version>1.1</tlib-version>
+ <short-name>sub</short-name>
+ <uri>http://subsonic.org/taglib/sub</uri>
+
+ <tag>
+ <description>
+ Creates a URL with optional query parameters. Similar to 'c:url', but
+ you may specify which character encoding to use for the URL query
+ parameters. If no encoding is specified, the following steps are performed:
+ a) Parameter values are encoded as the hexadecimal representation of the UTF-8 bytes of the original string.
+ b) Parameter names are prepended with the suffix "Utf8Hex"
+ </description>
+ <name>url</name>
+ <tag-class>net.sourceforge.subsonic.taglib.UrlTag</tag-class>
+ <body-content>JSP</body-content>
+ <attribute>
+ <description>
+ Name of the exported scoped variable for the
+ processed url. The type of the scoped variable is
+ String.
+ </description>
+ <name>var</name>
+ <required>false</required>
+ <rtexprvalue>false</rtexprvalue>
+ </attribute>
+ <attribute>
+ <description>URL to be processed.</description>
+ <name>value</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <description>The encoding to use. Default is ISO-8859-1.</description>
+ <name>encoding</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ </tag>
+
+ <tag>
+ <description>Adds a parameter to a containing 'url' tag.</description>
+ <name>param</name>
+ <tag-class>net.sourceforge.subsonic.taglib.ParamTag</tag-class>
+ <body-content>empty</body-content>
+ <attribute>
+ <description>Name of the query string parameter.</description>
+ <name>name</name>
+ <required>true</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <description>Value of the parameter.</description>
+ <name>value</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ </tag>
+
+ <tag>
+ <description>
+ Converts a byte-count to a formatted string suitable for display to the user, with respect
+ to the current locale.
+ </description>
+ <name>formatBytes</name>
+ <tag-class>net.sourceforge.subsonic.taglib.FormatBytesTag</tag-class>
+ <body-content>JSP</body-content>
+ <attribute>
+ <description>The byte count.</description>
+ <name>bytes</name>
+ <required>true</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ </tag>
+
+ <tag>
+ <description>
+ Renders a Wiki text with markup to HTML, using the Radeox render engine.
+ </description>
+ <name>wiki</name>
+ <tag-class>net.sourceforge.subsonic.taglib.WikiTag</tag-class>
+ <body-content>JSP</body-content>
+ <attribute>
+ <description>The Wiki markup text.</description>
+ <name>text</name>
+ <required>true</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ </tag>
+
+ <tag>
+ <description>
+ Escapes the characters in a string using JavaScript rules.
+ </description>
+ <name>escapeJavaScript</name>
+ <tag-class>net.sourceforge.subsonic.taglib.EscapeJavaScriptTag</tag-class>
+ <body-content>JSP</body-content>
+ <attribute>
+ <description>The string to escape.</description>
+ <name>string</name>
+ <required>true</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ </tag>
+
+</taglib> \ No newline at end of file
diff --git a/subsonic-main/src/main/webapp/WEB-INF/subsonic-servlet.xml b/subsonic-main/src/main/webapp/WEB-INF/subsonic-servlet.xml
new file mode 100644
index 00000000..b36bb0c8
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/subsonic-servlet.xml
@@ -0,0 +1,479 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="leftController" class="net.sourceforge.subsonic.controller.LeftController">
+ <property name="viewName" value="left"/>
+ <property name="mediaScannerService" ref="mediaScannerService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="musicIndexService" ref="musicIndexService"/>
+ <property name="playerService" ref="playerService"/>
+ <property name="playlistService" ref="playlistService"/>
+ </bean>
+ <bean id="rightController" class="net.sourceforge.subsonic.controller.RightController">
+ <property name="viewName" value="right"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+ <bean id="statusController" class="net.sourceforge.subsonic.controller.StatusController">
+ <property name="viewName" value="status"/>
+ <property name="statusService" ref="statusService"/>
+ </bean>
+ <bean id="mainController" class="net.sourceforge.subsonic.controller.MainController">
+ <property name="viewName" value="main"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="playerService" ref="playerService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="ratingService" ref="musicInfoService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="adService" ref="adService"/>
+ </bean>
+ <bean id="playlistController" class="net.sourceforge.subsonic.controller.PlaylistController">
+ <property name="viewName" value="playlist"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="playlistService" ref="playlistService"/>
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+ <bean id="importPlaylistController" class="net.sourceforge.subsonic.controller.ImportPlaylistController">
+ <property name="viewName" value="importPlaylist"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="playlistService" ref="playlistService"/>
+ </bean>
+ <bean id="topController" class="net.sourceforge.subsonic.controller.TopController">
+ <property name="viewName" value="top"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="versionService" ref="versionService"/>
+ <property name="securityService" ref="securityService"/>
+ </bean>
+ <bean id="helpController" class="net.sourceforge.subsonic.controller.HelpController">
+ <property name="viewName" value="help"/>
+ <property name="versionService" ref="versionService"/>
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+ <bean id="moreController" class="net.sourceforge.subsonic.controller.MoreController">
+ <property name="viewName" value="more"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="playerService" ref="playerService"/>
+ </bean>
+ <bean id="uploadController" class="net.sourceforge.subsonic.controller.UploadController">
+ <property name="viewName" value="upload"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="statusService" ref="statusService"/>
+ <property name="playerService" ref="playerService"/>
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+ <bean id="lyricsController" class="net.sourceforge.subsonic.controller.LyricsController">
+ <property name="viewName" value="lyrics"/>
+ </bean>
+ <bean id="allmusicController" class="net.sourceforge.subsonic.controller.AllmusicController">
+ <property name="viewName" value="allmusic"/>
+ </bean>
+ <bean id="podcastController" class="net.sourceforge.subsonic.controller.PodcastController">
+ <property name="viewName" value="podcast"/>
+ <property name="playlistService" ref="playlistService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="securityService" ref="securityService"/>
+ </bean>
+ <bean id="podcastReceiverController" class="net.sourceforge.subsonic.controller.PodcastReceiverController">
+ <property name="viewName" value="podcastReceiver"/>
+ <property name="podcastService" ref="podcastService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+ <bean id="podcastReceiverAdminController"
+ class="net.sourceforge.subsonic.controller.PodcastReceiverAdminController">
+ <property name="podcastService" ref="podcastService"/>
+ </bean>
+ <bean id="setMusicFileInfoController" class="net.sourceforge.subsonic.controller.SetMusicFileInfoController">
+ <property name="mediaFileService" ref="mediaFileService"/>
+ </bean>
+ <bean id="shareManagementController" class="net.sourceforge.subsonic.controller.ShareManagementController">
+ <property name="settingsService" ref="settingsService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="shareService" ref="shareService"/>
+ <property name="playerService" ref="playerService"/>
+ <property name="securityService" ref="securityService"/>
+ </bean>
+ <bean id="setRatingController" class="net.sourceforge.subsonic.controller.SetRatingController">
+ <property name="ratingService" ref="musicInfoService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="securityService" ref="securityService"/>
+ </bean>
+ <bean id="randomPlayQueueController" class="net.sourceforge.subsonic.controller.RandomPlayQueueController">
+ <property name="viewName" value="reload"/>
+ <property name="playerService" ref="playerService"/>
+ <property name="searchService" ref="searchService"/>
+ <property name="reloadFrames">
+ <list>
+ <bean class="net.sourceforge.subsonic.controller.ReloadFrame">
+ <property name="frame" value="playQueue"/>
+ <property name="view" value="playQueue.view?"/>
+ </bean>
+ <bean class="net.sourceforge.subsonic.controller.ReloadFrame">
+ <property name="frame" value="main"/>
+ <property name="view" value="more.view"/>
+ </bean>
+ </list>
+ </property>
+ </bean>
+ <bean id="changeCoverArtController" class="net.sourceforge.subsonic.controller.ChangeCoverArtController">
+ <property name="viewName" value="changeCoverArt"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ </bean>
+ <bean id="nowPlayingController" class="net.sourceforge.subsonic.controller.NowPlayingController">
+ <property name="playerService" ref="playerService"/>
+ <property name="statusService" ref="statusService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ </bean>
+ <bean id="starredController" class="net.sourceforge.subsonic.controller.StarredController">
+ <property name="viewName" value="starred"/>
+ <property name="playerService" ref="playerService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="mediaFileDao" ref="mediaFileDao"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ </bean>
+ <bean id="searchController" class="net.sourceforge.subsonic.controller.SearchController">
+ <property name="commandClass" value="net.sourceforge.subsonic.command.SearchCommand"/>
+ <property name="successView" value="search"/>
+ <property name="formView" value="search"/>
+ <property name="searchService" ref="searchService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="playerService" ref="playerService"/>
+ </bean>
+ <bean id="settingsController" class="net.sourceforge.subsonic.controller.SettingsController">
+ <property name="securityService" ref="securityService"/>
+ </bean>
+ <bean id="playerSettingsController" class="net.sourceforge.subsonic.controller.PlayerSettingsController">
+ <property name="commandClass" value="net.sourceforge.subsonic.command.PlayerSettingsCommand"/>
+ <property name="successView" value="playerSettings"/>
+ <property name="formView" value="playerSettings"/>
+ <property name="playerService" ref="playerService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="transcodingService" ref="transcodingService"/>
+ </bean>
+ <bean id="shareSettingsController" class="net.sourceforge.subsonic.controller.ShareSettingsController">
+ <property name="viewName" value="shareSettings"/>
+ <property name="shareService" ref="shareService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ </bean>
+ <bean id="musicFolderSettingsController" class="net.sourceforge.subsonic.controller.MusicFolderSettingsController">
+ <property name="commandClass" value="net.sourceforge.subsonic.command.MusicFolderSettingsCommand"/>
+ <property name="successView" value="musicFolderSettings"/>
+ <property name="formView" value="musicFolderSettings"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="mediaScannerService" ref="mediaScannerService"/>
+ <property name="artistDao" ref="artistDao"/>
+ <property name="albumDao" ref="albumDao"/>
+ <property name="mediaFolderDao" ref="mediaFileDao"/>
+ </bean>
+ <bean id="networkSettingsController" class="net.sourceforge.subsonic.controller.NetworkSettingsController">
+ <property name="commandClass" value="net.sourceforge.subsonic.command.NetworkSettingsCommand"/>
+ <property name="successView" value="networkSettings"/>
+ <property name="formView" value="networkSettings"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="networkService" ref="networkService"/>
+ </bean>
+ <bean id="transcodingSettingsController" class="net.sourceforge.subsonic.controller.TranscodingSettingsController">
+ <property name="viewName" value="transcodingSettings"/>
+ <property name="transcodingService" ref="transcodingService"/>
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+ <bean id="internetRadioSettingsController"
+ class="net.sourceforge.subsonic.controller.InternetRadioSettingsController">
+ <property name="viewName" value="internetRadioSettings"/>
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+ <bean id="podcastSettingsController" class="net.sourceforge.subsonic.controller.PodcastSettingsController">
+ <property name="commandClass" value="net.sourceforge.subsonic.command.PodcastSettingsCommand"/>
+ <property name="successView" value="podcastSettings"/>
+ <property name="formView" value="podcastSettings"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="podcastService" ref="podcastService"/>
+ </bean>
+ <bean id="generalSettingsController" class="net.sourceforge.subsonic.controller.GeneralSettingsController">
+ <property name="commandClass" value="net.sourceforge.subsonic.command.GeneralSettingsCommand"/>
+ <property name="successView" value="generalSettings"/>
+ <property name="formView" value="generalSettings"/>
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+ <bean id="advancedSettingsController" class="net.sourceforge.subsonic.controller.AdvancedSettingsController">
+ <property name="commandClass" value="net.sourceforge.subsonic.command.AdvancedSettingsCommand"/>
+ <property name="successView" value="advancedSettings"/>
+ <property name="formView" value="advancedSettings"/>
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+ <bean id="personalSettingsController" class="net.sourceforge.subsonic.controller.PersonalSettingsController">
+ <property name="commandClass" value="net.sourceforge.subsonic.command.PersonalSettingsCommand"/>
+ <property name="successView" value="personalSettings"/>
+ <property name="formView" value="personalSettings"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="securityService" ref="securityService"/>
+ </bean>
+ <bean id="avatarUploadController" class="net.sourceforge.subsonic.controller.AvatarUploadController">
+ <property name="viewName" value="avatarUploadResult"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="securityService" ref="securityService"/>
+ </bean>
+ <bean id="userSettingsController" class="net.sourceforge.subsonic.controller.UserSettingsController">
+ <property name="sessionForm" value="true"/>
+ <property name="commandClass" value="net.sourceforge.subsonic.command.UserSettingsCommand"/>
+ <property name="validator" ref="userSettingsValidator"/>
+ <property name="successView" value="userSettings"/>
+ <property name="formView" value="userSettings"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="transcodingService" ref="transcodingService"/>
+ </bean>
+ <bean id="passwordSettingsController" class="net.sourceforge.subsonic.controller.PasswordSettingsController">
+ <property name="sessionForm" value="true"/>
+ <property name="commandClass" value="net.sourceforge.subsonic.command.PasswordSettingsCommand"/>
+ <property name="validator" ref="passwordSettingsValidator"/>
+ <property name="successView" value="passwordSettings"/>
+ <property name="formView" value="passwordSettings"/>
+ <property name="securityService" ref="securityService"/>
+ </bean>
+ <bean id="homeController" class="net.sourceforge.subsonic.controller.HomeController">
+ <property name="viewName" value="home"/>
+ <property name="ratingService" ref="musicInfoService"/>
+ <property name="mediaScannerService" ref="mediaScannerService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="searchService" ref="searchService"/>
+ <property name="securityService" ref="securityService"/>
+ </bean>
+ <bean id="editTagsController" class="net.sourceforge.subsonic.controller.EditTagsController">
+ <property name="viewName" value="editTags"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="metaDataParserFactory" ref="metaDataParserFactory"/>
+ </bean>
+ <bean id="playQueueController" class="net.sourceforge.subsonic.controller.PlayQueueController">
+ <property name="viewName" value="playQueue"/>
+ <property name="playerService" ref="playerService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+ <bean id="coverArtController" class="net.sourceforge.subsonic.controller.CoverArtController">
+ <property name="securityService" ref="securityService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="artistDao" ref="artistDao"/>
+ <property name="albumDao" ref="albumDao"/>
+ </bean>
+ <bean id="avatarController" class="net.sourceforge.subsonic.controller.AvatarController">
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+ <bean id="proxyController" class="net.sourceforge.subsonic.controller.ProxyController"/>
+ <bean id="statusChartController" class="net.sourceforge.subsonic.controller.StatusChartController">
+ <property name="statusService" ref="statusService"/>
+ </bean>
+ <bean id="userChartController" class="net.sourceforge.subsonic.controller.UserChartController">
+ <property name="securityService" ref="securityService"/>
+ </bean>
+ <bean id="m3uController" class="net.sourceforge.subsonic.controller.M3UController">
+ <property name="playerService" ref="playerService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="transcodingService" ref="transcodingService"/>
+ </bean>
+ <bean id="streamController" class="net.sourceforge.subsonic.controller.StreamController">
+ <property name="playerService" ref="playerService"/>
+ <property name="playlistService" ref="playlistService"/>
+ <property name="statusService" ref="statusService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="searchService" ref="searchService"/>
+ <property name="transcodingService" ref="transcodingService"/>
+ <property name="audioScrobblerService" ref="audioScrobblerService"/>
+ </bean>
+ <bean id="videoPlayerController" class="net.sourceforge.subsonic.controller.VideoPlayerController">
+ <property name="viewName" value="videoPlayer"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="playerService" ref="playerService"/>
+ </bean>
+ <bean id="externalPlayerController" class="net.sourceforge.subsonic.controller.ExternalPlayerController">
+ <property name="viewName" value="externalPlayer"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="playerService" ref="playerService"/>
+ <property name="shareDao" ref="shareDao"/>
+ </bean>
+ <bean id="downloadController" class="net.sourceforge.subsonic.controller.DownloadController">
+ <property name="playerService" ref="playerService"/>
+ <property name="statusService" ref="statusService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="playlistService" ref="playlistService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ </bean>
+ <bean id="donateController" class="net.sourceforge.subsonic.controller.DonateController">
+ <property name="commandClass" value="net.sourceforge.subsonic.command.DonateCommand"/>
+ <property name="successView" value="donate"/>
+ <property name="formView" value="donate"/>
+ <property name="validator" ref="donateValidator"/>
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+ <bean id="multiController" class="net.sourceforge.subsonic.controller.MultiController">
+ <property name="securityService" ref="securityService"/>
+ <property name="settingsService" ref="settingsService"/>
+ <property name="playlistService" ref="playlistService"/>
+ </bean>
+ <bean id="wapController" class="net.sourceforge.subsonic.controller.WapController">
+ <property name="settingsService" ref="settingsService"/>
+ <property name="playerService" ref="playerService"/>
+ <property name="playlistService" ref="playlistService"/>
+ <property name="searchService" ref="searchService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="musicIndexService" ref="musicIndexService"/>
+ </bean>
+ <bean id="restController" class="net.sourceforge.subsonic.controller.RESTController">
+ <property name="settingsService" ref="settingsService"/>
+ <property name="securityService" ref="securityService"/>
+ <property name="playerService" ref="playerService"/>
+ <property name="mediaFileService" ref="mediaFileService"/>
+ <property name="transcodingService" ref="transcodingService"/>
+ <property name="statusService" ref="statusService"/>
+ <property name="searchService" ref="searchService"/>
+ <property name="jukeboxService" ref="jukeboxService"/>
+ <property name="audioScrobblerService" ref="audioScrobblerService"/>
+ <property name="playlistService" ref="playlistService"/>
+ <property name="playQueueService" ref="ajaxPlayQueueService"/>
+ <property name="ratingService" ref="musicInfoService"/>
+ <property name="chatService" ref="ajaxChatService"/>
+ <property name="lyricsService" ref="ajaxLyricsService"/>
+ <property name="podcastService" ref="podcastService"/>
+ <property name="shareService" ref="shareService"/>
+ <property name="mediaFileDao" ref="mediaFileDao"/>
+ <property name="artistDao" ref="artistDao"/>
+ <property name="albumDao" ref="albumDao"/>
+ <property name="downloadController" ref="downloadController"/>
+ <property name="streamController" ref="streamController"/>
+ <property name="coverArtController" ref="coverArtController"/>
+ <property name="avatarController" ref="avatarController"/>
+ <property name="userSettingsController" ref="userSettingsController"/>
+ <property name="leftController" ref="leftController"/>
+ <property name="homeController" ref="homeController"/>
+ </bean>
+ <bean id="dbController" class="net.sourceforge.subsonic.controller.DBController">
+ <property name="viewName" value="db"/>
+ <property name="daoHelper" ref="daoHelper"/>
+ </bean>
+ <bean id="donateValidator" class="net.sourceforge.subsonic.validator.DonateValidator">
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+ <bean id="userSettingsValidator" class="net.sourceforge.subsonic.validator.UserSettingsValidator">
+ <property name="securityService" ref="securityService"/>
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+ <bean id="passwordSettingsValidator" class="net.sourceforge.subsonic.validator.PasswordSettingsValidator"/>
+
+ <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
+ <property name="alwaysUseFullPath" value="true"/>
+ <property name="mappings">
+ <props>
+ <prop key="/main.view">mainController</prop>
+ <prop key="/playlist.view">playlistController</prop>
+ <prop key="/help.view">helpController</prop>
+ <prop key="/lyrics.view">lyricsController</prop>
+ <prop key="/left.view">leftController</prop>
+ <prop key="/right.view">rightController</prop>
+ <prop key="/status.view">statusController</prop>
+ <prop key="/more.view">moreController</prop>
+ <prop key="/upload.view">uploadController</prop>
+ <prop key="/importPlaylist.view">importPlaylistController</prop>
+ <prop key="/exportPlaylist.view">multiController</prop>
+ <prop key="/setMusicFileInfo.view">setMusicFileInfoController</prop>
+ <prop key="/createShare.view">shareManagementController</prop>
+ <prop key="/setRating.view">setRatingController</prop>
+ <prop key="/top.view">topController</prop>
+ <prop key="/randomPlayQueue.view">randomPlayQueueController</prop>
+ <prop key="/changeCoverArt.view">changeCoverArtController</prop>
+ <prop key="/login.view">multiController</prop>
+ <prop key="/recover.view">multiController</prop>
+ <prop key="/accessDenied.view">multiController</prop>
+ <prop key="/notFound.view">multiController</prop>
+ <prop key="/gettingStarted.view">multiController</prop>
+ <prop key="/index.view">multiController</prop>
+ <prop key="/videoPlayer.view">videoPlayerController</prop>
+ <prop key="/nowPlaying.view">nowPlayingController</prop>
+ <prop key="/starred.view">starredController</prop>
+ <prop key="/search.view">searchController</prop>
+ <prop key="/settings.view">settingsController</prop>
+ <prop key="/playerSettings.view">playerSettingsController</prop>
+ <prop key="/shareSettings.view">shareSettingsController</prop>
+ <prop key="/musicFolderSettings.view">musicFolderSettingsController</prop>
+ <prop key="/networkSettings.view">networkSettingsController</prop>
+ <prop key="/transcodingSettings.view">transcodingSettingsController</prop>
+ <prop key="/internetRadioSettings.view">internetRadioSettingsController</prop>
+ <prop key="/podcastSettings.view">podcastSettingsController</prop>
+ <prop key="/generalSettings.view">generalSettingsController</prop>
+ <prop key="/advancedSettings.view">advancedSettingsController</prop>
+ <prop key="/personalSettings.view">personalSettingsController</prop>
+ <prop key="/avatarUpload.view">avatarUploadController</prop>
+ <prop key="/userSettings.view">userSettingsController</prop>
+ <prop key="/passwordSettings.view">passwordSettingsController</prop>
+ <prop key="/allmusic.view">allmusicController</prop>
+ <prop key="/home.view">homeController</prop>
+ <prop key="/editTags.view">editTagsController</prop>
+ <prop key="/playQueue.view">playQueueController</prop>
+ <prop key="/coverArt.view">coverArtController</prop>
+ <prop key="/avatar.view">avatarController</prop>
+ <prop key="/proxy.view">proxyController</prop>
+ <prop key="/statusChart.view">statusChartController</prop>
+ <prop key="/userChart.view">userChartController</prop>
+ <prop key="/download.view">downloadController</prop>
+ <prop key="/donate.view">donateController</prop>
+ <prop key="/db.view">dbController</prop>
+ <prop key="/test.view">multiController</prop>
+ <prop key="/podcastReceiver.view">podcastReceiverController</prop>
+ <prop key="/podcastReceiverAdmin.view">podcastReceiverAdminController</prop>
+ <prop key="/podcast.view">podcastController</prop>
+ <prop key="/podcast/**">podcastController</prop>
+ <prop key="/wap/download.view">downloadController</prop>
+ <prop key="/wap/**">wapController</prop>
+ <prop key="/rest/**">restController</prop>
+ <prop key="/play.m3u">m3uController</prop>
+ <prop key="/stream/**">streamController</prop>
+ <prop key="/share/**">externalPlayerController</prop>
+ </props>
+ </property>
+ </bean>
+
+ <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
+ <property name="basename" value="net.sourceforge.subsonic.i18n.ResourceBundle"/>
+ </bean>
+
+ <bean id="themeSource" class="net.sourceforge.subsonic.theme.SubsonicThemeSource">
+ <property name="basenamePrefix" value="net.sourceforge.subsonic.theme."/>
+ <property name="defaultResourceBundle" value="net.sourceforge.subsonic.theme.default"/>
+ </bean>
+
+ <bean id="localeResolver"
+ class="net.sourceforge.subsonic.i18n.SubsonicLocaleResolver">
+ <property name="securityService" ref="securityService"/>
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+
+ <bean id="themeResolver"
+ class="net.sourceforge.subsonic.theme.SubsonicThemeResolver">
+ <property name="securityService" ref="securityService"/>
+ <property name="settingsService" ref="settingsService"/>
+ </bean>
+
+ <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
+ <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
+ <property name="prefix" value="/WEB-INF/jsp/"/>
+ <property name="suffix" value=".jsp"/>
+ </bean>
+
+</beans>
diff --git a/subsonic-main/src/main/webapp/WEB-INF/web.xml b/subsonic-main/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000..bf484e28
--- /dev/null
+++ b/subsonic-main/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app id="subsonic" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+
+ <display-name>Subsonic Music Streamer</display-name>
+
+ <!-- Location of application context. Used by ContextLoaderListener. -->
+ <context-param>
+ <param-name>contextConfigLocation</param-name>
+ <param-value>
+ /WEB-INF/applicationContext-service.xml
+ /WEB-INF/applicationContext-security.xml
+ /WEB-INF/applicationContext-cache.xml
+ </param-value>
+ </context-param>
+
+ <listener>
+ <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+ </listener>
+ <listener>
+ <listener-class>net.sf.ehcache.constructs.web.ShutdownListener</listener-class>
+ </listener>
+
+ <servlet>
+ <servlet-name>subsonic</servlet-name>
+ <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+
+ <servlet>
+ <display-name>DWR Servlet</display-name>
+ <servlet-name>dwr-invoker</servlet-name>
+ <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>subsonic</servlet-name>
+ <url-pattern>*.view</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>subsonic</servlet-name>
+ <url-pattern>/podcast</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>subsonic</servlet-name>
+ <url-pattern>/wap</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>subsonic</servlet-name>
+ <url-pattern>/play.m3u</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>subsonic</servlet-name>
+ <url-pattern>/stream/*</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>subsonic</servlet-name>
+ <url-pattern>/rest/*</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>subsonic</servlet-name>
+ <url-pattern>/share/*</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>dwr-invoker</servlet-name>
+ <url-pattern>/dwr/*</url-pattern>
+ </servlet-mapping>
+
+ <welcome-file-list>
+ <welcome-file>index.html</welcome-file>
+ <welcome-file>index.jsp</welcome-file>
+ </welcome-file-list>
+
+ <error-page>
+ <exception-type>java.lang.Throwable</exception-type>
+ <location>/error.jsp</location>
+ </error-page>
+
+ <filter>
+ <filter-name>BootstrapVerificationFilter</filter-name>
+ <filter-class>net.sourceforge.subsonic.filter.BootstrapVerificationFilter</filter-class>
+ </filter>
+ <filter-mapping>
+ <filter-name>BootstrapVerificationFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
+ <filter>
+ <filter-name>ParameterDecodingFilter</filter-name>
+ <filter-class>net.sourceforge.subsonic.filter.ParameterDecodingFilter</filter-class>
+ </filter>
+ <filter-mapping>
+ <filter-name>ParameterDecodingFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
+ <filter>
+ <filter-name>RequestEncodingFilter</filter-name>
+ <filter-class>net.sourceforge.subsonic.filter.RequestEncodingFilter</filter-class>
+ <init-param>
+ <param-name>encoding</param-name>
+ <param-value>UTF-8</param-value>
+ </init-param>
+ </filter>
+ <filter-mapping>
+ <filter-name>RequestEncodingFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
+ <filter>
+ <description>Sets HTTP headers to enable browser caching.</description>
+ <filter-name>CacheFilter</filter-name>
+ <filter-class>net.sourceforge.subsonic.filter.ResponseHeaderFilter</filter-class>
+ <init-param>
+ <param-name>Cache-Control</param-name>
+ <param-value>max-age=36000</param-value>
+ </init-param>
+ </filter>
+
+ <filter>
+ <description>Sets HTTP headers to disable browser caching.</description>
+ <filter-name>NoCacheFilter</filter-name>
+ <filter-class>net.sourceforge.subsonic.filter.ResponseHeaderFilter</filter-class>
+ <init-param>
+ <param-name>Cache-Control</param-name>
+ <param-value>no-cache, post-check=0, pre-check=0</param-value>
+ </init-param>
+ <init-param>
+ <param-name>Pragma</param-name>
+ <param-value>no-cache</param-value>
+ </init-param>
+ <init-param>
+ <param-name>Expires</param-name>
+ <param-value>Thu, 01 Dec 1994 16:00:00 GMT</param-value>
+ </init-param>
+ </filter>
+
+ <filter>
+ <description>The "Expires" HTTP header is set to avoid overly eager browser caching of
+ pages that implements LastModified.</description>
+ <filter-name>ExpiresFilter</filter-name>
+ <filter-class>net.sourceforge.subsonic.filter.ResponseHeaderFilter</filter-class>
+ <init-param>
+ <param-name>Expires</param-name>
+ <param-value>Thu, 01 Dec 1994 16:00:00 GMT</param-value>
+ </init-param>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>CacheFilter</filter-name>
+ <url-pattern>/icons/*</url-pattern>
+ </filter-mapping>
+ <filter-mapping>
+ <filter-name>CacheFilter</filter-name>
+ <url-pattern>/style/*</url-pattern>
+ </filter-mapping>
+
+ <filter-mapping>
+ <filter-name>NoCacheFilter</filter-name>
+ <url-pattern>/statusChart.view</url-pattern>
+ </filter-mapping>
+ <filter-mapping>
+ <filter-name>NoCacheFilter</filter-name>
+ <url-pattern>/userChart.view</url-pattern>
+ </filter-mapping>
+ <filter-mapping>
+ <filter-name>NoCacheFilter</filter-name>
+ <url-pattern>/playQueue.view</url-pattern>
+ </filter-mapping>
+ <filter-mapping>
+ <filter-name>NoCacheFilter</filter-name>
+ <url-pattern>/podcastReceiver.view</url-pattern>
+ </filter-mapping>
+ <filter-mapping>
+ <filter-name>NoCacheFilter</filter-name>
+ <url-pattern>/help.view</url-pattern>
+ </filter-mapping>
+ <filter-mapping>
+ <filter-name>NoCacheFilter</filter-name>
+ <url-pattern>/top.view</url-pattern>
+ </filter-mapping>
+ <filter-mapping>
+ <filter-name>NoCacheFilter</filter-name>
+ <url-pattern>/home.view</url-pattern>
+ </filter-mapping>
+
+ <filter-mapping>
+ <filter-name>ExpiresFilter</filter-name>
+ <url-pattern>/left.view</url-pattern>
+ </filter-mapping>
+
+ <filter>
+ <filter-name>AcegiFilter</filter-name>
+ <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
+ <init-param>
+ <param-name>targetClass</param-name>
+ <param-value>org.acegisecurity.util.FilterChainProxy</param-value>
+ </init-param>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>AcegiFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
+</web-app> \ No newline at end of file