diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 7bbef65d..3f728b91 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2,7 +2,7 @@ + a:versionName="3.9.9.9" a:installLocation="auto"> diff --git a/gen/net/sourceforge/subsonic/androidapp/R.java b/gen/net/sourceforge/subsonic/androidapp/R.java index 34ca2c20..4ff303ed 100644 --- a/gen/net/sourceforge/subsonic/androidapp/R.java +++ b/gen/net/sourceforge/subsonic/androidapp/R.java @@ -131,158 +131,162 @@ public final class R { public static final int select_album_play_all_normal=0x7f02004b; public static final int select_album_play_all_pressed=0x7f02004c; public static final int slider_knob=0x7f02004d; - public static final int status_bg=0x7f02004e; - public static final int status_next=0x7f02004f; - public static final int status_pause=0x7f020050; - public static final int status_play=0x7f020051; - public static final int status_prev=0x7f020052; - public static final int status_stop=0x7f020053; - public static final int title_bar_shadow=0x7f020054; - public static final int unknown_album=0x7f020055; - public static final int unknown_album_large=0x7f020056; + public static final int star=0x7f02004e; + public static final int star_hollow=0x7f02004f; + public static final int status_bg=0x7f020050; + public static final int status_next=0x7f020051; + public static final int status_pause=0x7f020052; + public static final int status_play=0x7f020053; + public static final int status_prev=0x7f020054; + public static final int status_stop=0x7f020055; + public static final int title_bar_shadow=0x7f020056; + public static final int unknown_album=0x7f020057; + public static final int unknown_album_large=0x7f020058; } public static final class id { - public static final int album=0x7f0d0048; + public static final int album=0x7f0d0049; public static final int album_artist=0x7f0d0002; public static final int album_coverart=0x7f0d0000; - public static final int album_menu_pin=0x7f0d0085; - public static final int album_menu_play_last=0x7f0d0084; - public static final int album_menu_play_now=0x7f0d0083; + public static final int album_menu_pin=0x7f0d0087; + public static final int album_menu_play_last=0x7f0d0086; + public static final int album_menu_play_now=0x7f0d0085; + public static final int album_star=0x7f0d0003; public static final int album_title=0x7f0d0001; - public static final int appwidget_coverart=0x7f0d0003; - public static final int appwidget_top=0x7f0d0005; - public static final int artist=0x7f0d0007; - public static final int artist_menu_pin=0x7f0d0088; - public static final int artist_menu_play_last=0x7f0d0087; - public static final int artist_menu_play_now=0x7f0d0086; - public static final int button_bar=0x7f0d000b; - public static final int button_bar_home=0x7f0d000c; - public static final int button_bar_music=0x7f0d000d; - public static final int button_bar_now_playing=0x7f0d0010; - public static final int button_bar_playlists=0x7f0d000f; - public static final int button_bar_search=0x7f0d000e; - public static final int control_next=0x7f0d000a; - public static final int control_play=0x7f0d0009; - public static final int control_previous=0x7f0d0008; - public static final int control_stop=0x7f0d0049; - public static final int download_album=0x7f0d0024; - public static final int download_album_art_image=0x7f0d001e; - public static final int download_album_art_layout=0x7f0d001d; - public static final int download_artist=0x7f0d0023; - public static final int download_button_bar_flipper=0x7f0d0020; - public static final int download_control_layout=0x7f0d0011; - public static final int download_duration=0x7f0d0022; - public static final int download_empty=0x7f0d0025; - public static final int download_equalizer=0x7f0d0079; - public static final int download_jukebox=0x7f0d007b; - public static final int download_list=0x7f0d0026; + public static final int appwidget_coverart=0x7f0d0004; + public static final int appwidget_top=0x7f0d0006; + public static final int artist=0x7f0d0008; + public static final int artist_menu_pin=0x7f0d008a; + public static final int artist_menu_play_last=0x7f0d0089; + public static final int artist_menu_play_now=0x7f0d0088; + public static final int button_bar=0x7f0d000c; + public static final int button_bar_home=0x7f0d000d; + public static final int button_bar_music=0x7f0d000e; + public static final int button_bar_now_playing=0x7f0d0011; + public static final int button_bar_playlists=0x7f0d0010; + public static final int button_bar_search=0x7f0d000f; + public static final int control_next=0x7f0d000b; + public static final int control_play=0x7f0d000a; + public static final int control_previous=0x7f0d0009; + public static final int control_stop=0x7f0d004a; + public static final int download_album=0x7f0d0025; + public static final int download_album_art_image=0x7f0d001f; + public static final int download_album_art_layout=0x7f0d001e; + public static final int download_artist=0x7f0d0024; + public static final int download_button_bar_flipper=0x7f0d0021; + public static final int download_control_layout=0x7f0d0012; + public static final int download_duration=0x7f0d0023; + public static final int download_empty=0x7f0d0026; + public static final int download_equalizer=0x7f0d007b; + public static final int download_jukebox=0x7f0d007d; + public static final int download_list=0x7f0d0027; public static final int download_next=0x7f0d0018; public static final int download_pause=0x7f0d0015; - public static final int download_playlist_flipper=0x7f0d001c; - public static final int download_position=0x7f0d0021; + public static final int download_playlist_flipper=0x7f0d001d; + public static final int download_position=0x7f0d0022; public static final int download_previous=0x7f0d0014; - public static final int download_progress_bar=0x7f0d0027; - public static final int download_repeat=0x7f0d0013; - public static final int download_shuffle=0x7f0d0012; - public static final int download_song_title=0x7f0d001b; + public static final int download_progress_bar=0x7f0d0028; + public static final int download_repeat=0x7f0d0019; + public static final int download_shuffle=0x7f0d0013; + public static final int download_song_title=0x7f0d001c; public static final int download_start=0x7f0d0017; - public static final int download_status=0x7f0d001a; + public static final int download_status=0x7f0d001b; public static final int download_stop=0x7f0d0016; - public static final int download_toggle_list=0x7f0d0019; - public static final int download_visualizer=0x7f0d007a; - public static final int download_visualizer_view_layout=0x7f0d001f; - public static final int equalizer_bar=0x7f0d002d; - public static final int equalizer_frequency=0x7f0d002b; - public static final int equalizer_level=0x7f0d002c; - public static final int equalizer_enabled=0x7f0d0028; - public static final int equalizer_layout=0x7f0d0029; - public static final int equalizer_preset=0x7f0d002a; - public static final int help_back=0x7f0d002f; - public static final int help_buttons=0x7f0d002e; - public static final int help_close=0x7f0d0030; - public static final int help_contents=0x7f0d0031; - public static final int icon=0x7f0d006e; - public static final int jukebox_volume_progress_bar=0x7f0d0033; - public static final int linearLayout1=0x7f0d0004; - public static final int lyrics_artist=0x7f0d0035; - public static final int lyrics_scrollview=0x7f0d0034; - public static final int lyrics_text=0x7f0d0037; - public static final int lyrics_title=0x7f0d0036; - public static final int main_select_server_1=0x7f0d003b; - public static final int main_select_server_2=0x7f0d003c; - public static final int main_albums=0x7f0d003d; - public static final int main_albums_frequent=0x7f0d0040; - public static final int main_albums_highest=0x7f0d0041; - public static final int main_albums_newest=0x7f0d003e; - public static final int main_albums_random=0x7f0d0043; - public static final int main_albums_recent=0x7f0d003f; - public static final int main_albums_starred=0x7f0d0042; - public static final int main_dummy=0x7f0d0039; - public static final int main_list=0x7f0d0038; - public static final int main_select_server=0x7f0d003a; - public static final int main_shuffle=0x7f0d0078; - public static final int menu_exit=0x7f0d0077; - public static final int menu_help=0x7f0d0076; - public static final int menu_lyrics=0x7f0d0080; - public static final int menu_refresh=0x7f0d0089; - public static final int menu_remove=0x7f0d0081; - public static final int menu_remove_all=0x7f0d007d; - public static final int menu_save_playlist=0x7f0d007c; - public static final int menu_screen_on_off=0x7f0d007e; - public static final int menu_settings=0x7f0d0075; - public static final int menu_show_album=0x7f0d007f; - public static final int menu_shuffle=0x7f0d0082; - public static final int notification_image=0x7f0d0045; - public static final int play_video_contents=0x7f0d004a; - public static final int progress_message=0x7f0d004b; - public static final int save_playlist_name=0x7f0d004d; - public static final int save_playlist_root=0x7f0d004c; - public static final int search_albums=0x7f0d0051; - public static final int search_artists=0x7f0d0050; - public static final int search_list=0x7f0d004e; - public static final int search_more_albums=0x7f0d0054; - public static final int search_more_artists=0x7f0d0053; - public static final int search_more_songs=0x7f0d0055; - public static final int search_search=0x7f0d004f; - public static final int search_songs=0x7f0d0052; - public static final int select_album_cover_art=0x7f0d005f; - public static final int select_album_delete=0x7f0d005d; - public static final int select_album_empty=0x7f0d0056; - public static final int select_album_entries=0x7f0d0057; - public static final int select_album_more=0x7f0d005e; - public static final int select_album_pin=0x7f0d005b; - public static final int select_album_play_all=0x7f0d0062; - public static final int select_album_play_last=0x7f0d005a; - public static final int select_album_play_now=0x7f0d0059; - public static final int select_album_select=0x7f0d0058; - public static final int select_album_text1=0x7f0d0060; - public static final int select_album_text2=0x7f0d0061; - public static final int select_album_unpin=0x7f0d005c; - public static final int select_artist_folder=0x7f0d0064; - public static final int select_artist_folder_1=0x7f0d0065; - public static final int select_artist_folder_2=0x7f0d0066; - public static final int select_artist_list=0x7f0d0063; - public static final int select_playlist_empty=0x7f0d0067; - public static final int select_playlist_list=0x7f0d0068; - public static final int song_artist=0x7f0d006c; - public static final int song_check=0x7f0d0069; - public static final int song_duration=0x7f0d006d; - public static final int song_menu_play_last=0x7f0d008c; - public static final int song_menu_play_next=0x7f0d008b; - public static final int song_menu_play_now=0x7f0d008a; - public static final int song_status=0x7f0d006b; - public static final int song_title=0x7f0d006a; - public static final int status_icon=0x7f0d0046; - public static final int status_media_collapse=0x7f0d0072; - public static final int status_media_next=0x7f0d0071; - public static final int status_media_play=0x7f0d0070; - public static final int status_media_prev=0x7f0d006f; - public static final int statusbar=0x7f0d0044; - public static final int tab_progress=0x7f0d0073; - public static final int tab_progress_message=0x7f0d0074; - public static final int title=0x7f0d0006; - public static final int toast_layout_root=0x7f0d0032; - public static final int trackname=0x7f0d0047; + public static final int download_toggle_list=0x7f0d001a; + public static final int download_visualizer=0x7f0d007c; + public static final int download_visualizer_view_layout=0x7f0d0020; + public static final int equalizer_bar=0x7f0d002e; + public static final int equalizer_frequency=0x7f0d002c; + public static final int equalizer_level=0x7f0d002d; + public static final int equalizer_enabled=0x7f0d0029; + public static final int equalizer_layout=0x7f0d002a; + public static final int equalizer_preset=0x7f0d002b; + public static final int help_back=0x7f0d0030; + public static final int help_buttons=0x7f0d002f; + public static final int help_close=0x7f0d0031; + public static final int help_contents=0x7f0d0032; + public static final int icon=0x7f0d0070; + public static final int jukebox_volume_progress_bar=0x7f0d0034; + public static final int linearLayout1=0x7f0d0005; + public static final int lyrics_artist=0x7f0d0036; + public static final int lyrics_scrollview=0x7f0d0035; + public static final int lyrics_text=0x7f0d0038; + public static final int lyrics_title=0x7f0d0037; + public static final int main_select_server_1=0x7f0d003c; + public static final int main_select_server_2=0x7f0d003d; + public static final int main_albums=0x7f0d003e; + public static final int main_albums_frequent=0x7f0d0041; + public static final int main_albums_highest=0x7f0d0042; + public static final int main_albums_newest=0x7f0d003f; + public static final int main_albums_random=0x7f0d0044; + public static final int main_albums_recent=0x7f0d0040; + public static final int main_albums_starred=0x7f0d0043; + public static final int main_dummy=0x7f0d003a; + public static final int main_list=0x7f0d0039; + public static final int main_select_server=0x7f0d003b; + public static final int main_shuffle=0x7f0d007a; + public static final int menu_exit=0x7f0d0079; + public static final int menu_help=0x7f0d0078; + public static final int menu_lyrics=0x7f0d0082; + public static final int menu_refresh=0x7f0d008b; + public static final int menu_remove=0x7f0d0083; + public static final int menu_remove_all=0x7f0d007f; + public static final int menu_save_playlist=0x7f0d007e; + public static final int menu_screen_on_off=0x7f0d0080; + public static final int menu_settings=0x7f0d0077; + public static final int menu_show_album=0x7f0d0081; + public static final int menu_shuffle=0x7f0d0084; + public static final int notification_image=0x7f0d0046; + public static final int play_video_contents=0x7f0d004b; + public static final int progress_message=0x7f0d004c; + public static final int save_playlist_name=0x7f0d004e; + public static final int save_playlist_root=0x7f0d004d; + public static final int search_albums=0x7f0d0052; + public static final int search_artists=0x7f0d0051; + public static final int search_list=0x7f0d004f; + public static final int search_more_albums=0x7f0d0055; + public static final int search_more_artists=0x7f0d0054; + public static final int search_more_songs=0x7f0d0056; + public static final int search_search=0x7f0d0050; + public static final int search_songs=0x7f0d0053; + public static final int select_album_cover_art=0x7f0d0060; + public static final int select_album_delete=0x7f0d005e; + public static final int select_album_empty=0x7f0d0057; + public static final int select_album_entries=0x7f0d0058; + public static final int select_album_more=0x7f0d005f; + public static final int select_album_pin=0x7f0d005c; + public static final int select_album_play_all=0x7f0d0063; + public static final int select_album_play_last=0x7f0d005b; + public static final int select_album_play_now=0x7f0d005a; + public static final int select_album_select=0x7f0d0059; + public static final int select_album_text1=0x7f0d0061; + public static final int select_album_text2=0x7f0d0062; + public static final int select_album_unpin=0x7f0d005d; + public static final int select_artist_folder=0x7f0d0065; + public static final int select_artist_folder_1=0x7f0d0066; + public static final int select_artist_folder_2=0x7f0d0067; + public static final int select_artist_list=0x7f0d0064; + public static final int select_playlist_empty=0x7f0d0068; + public static final int select_playlist_list=0x7f0d0069; + public static final int song_artist=0x7f0d006d; + public static final int song_check=0x7f0d006a; + public static final int song_duration=0x7f0d006e; + public static final int song_menu_play_last=0x7f0d008e; + public static final int song_menu_play_next=0x7f0d008d; + public static final int song_menu_play_now=0x7f0d008c; + public static final int song_star=0x7f0d006f; + public static final int song_status=0x7f0d006c; + public static final int song_title=0x7f0d006b; + public static final int status_icon=0x7f0d0047; + public static final int status_media_collapse=0x7f0d0074; + public static final int status_media_next=0x7f0d0073; + public static final int status_media_play=0x7f0d0072; + public static final int status_media_prev=0x7f0d0071; + public static final int statusbar=0x7f0d0045; + public static final int tab_progress=0x7f0d0075; + public static final int tab_progress_message=0x7f0d0076; + public static final int title=0x7f0d0007; + public static final int toast_layout_root=0x7f0d0033; + public static final int trackname=0x7f0d0048; } public static final class integer { public static final int config_activityDefaultDur=0x7f080001; diff --git a/res/drawable-hdpi/ic_stat_downloaded.png b/res/drawable-hdpi/ic_stat_downloaded.png index 3c20147a..d5bfa457 100644 Binary files a/res/drawable-hdpi/ic_stat_downloaded.png and b/res/drawable-hdpi/ic_stat_downloaded.png differ diff --git a/res/drawable-hdpi/star.png b/res/drawable-hdpi/star.png new file mode 100644 index 00000000..11f86414 Binary files /dev/null and b/res/drawable-hdpi/star.png differ diff --git a/res/drawable-hdpi/star_hollow.png b/res/drawable-hdpi/star_hollow.png new file mode 100644 index 00000000..7259b06b Binary files /dev/null and b/res/drawable-hdpi/star_hollow.png differ diff --git a/res/layout-land/download.xml b/res/layout-land/download.xml index a2182859..c605bbf2 100644 --- a/res/layout-land/download.xml +++ b/res/layout-land/download.xml @@ -13,38 +13,49 @@ a:layout_height="wrap_content" a:layout_alignParentTop="true" a:layout_alignParentLeft="true"> - - - - - - - - + + + + + + + + + + + + + + + @@ -67,13 +79,14 @@ a:layout_height="fill_parent" a:layout_alignParentLeft="true" a:layout_toLeftOf="@+id/download_control_layout"> - - + + - + - + + a:layout_marginLeft="16dip" a:layout_marginRight="16dip" + a:layout_marginTop="16dip" a:singleLine="true" + a:textColor="@color/mediaControlForeground" a:textStyle="bold" + a:textSize="18sp" a:ellipsize="end" /> - + - - - + + + + a:layout_width="0dip" a:layout_height="fill_parent" a:layout_gravity="center_vertical" + a:layout_weight="1" a:background="@drawable/list_selector_holo_dark" + a:paddingRight="4dip" a:src="@drawable/media_toggle_list_normal" + a:paddingTop="12dip" a:paddingBottom="12dip" /> diff --git a/res/layout/album_list_item.xml b/res/layout/album_list_item.xml index 0abb7891..8dae5e8e 100644 --- a/res/layout/album_list_item.xml +++ b/res/layout/album_list_item.xml @@ -38,10 +38,11 @@ - - + a:id="@+id/album_star" + a:layout_width="wrap_content" + a:layout_height="fill_parent" + a:gravity="center_vertical" + a:background="@drawable/list_selector_holo_dark" + a:src="@drawable/star_hollow" /> + diff --git a/res/layout/song_list_item.xml b/res/layout/song_list_item.xml index 746c6813..2a3ef538 100644 --- a/res/layout/song_list_item.xml +++ b/res/layout/song_list_item.xml @@ -11,7 +11,7 @@ a:gravity="center_vertical" a:checkMark="@drawable/btn_check_custom" a:paddingLeft="3dip"/> - + + + diff --git a/src/net/sourceforge/subsonic/androidapp/domain/MusicDirectory.java b/src/net/sourceforge/subsonic/androidapp/domain/MusicDirectory.java index 5b6c0ae1..1eacbccb 100644 --- a/src/net/sourceforge/subsonic/androidapp/domain/MusicDirectory.java +++ b/src/net/sourceforge/subsonic/androidapp/domain/MusicDirectory.java @@ -84,7 +84,25 @@ public class MusicDirectory { private Integer bitRate; private String path; private boolean video; + private boolean starred; + private Integer discNumber; + + public Integer getDiscNumber() { + return discNumber; + } + + public void setDiscNumber(Integer discNumber) { + this.discNumber = discNumber; + } + public boolean getStarred() { + return starred; + } + + public void setStarred(boolean starred) { + this.starred = starred; + } + public String getId() { return id; } diff --git a/src/net/sourceforge/subsonic/androidapp/service/CachedMusicService.java b/src/net/sourceforge/subsonic/androidapp/service/CachedMusicService.java index e0121c09..9665fc62 100644 --- a/src/net/sourceforge/subsonic/androidapp/service/CachedMusicService.java +++ b/src/net/sourceforge/subsonic/androidapp/service/CachedMusicService.java @@ -1,234 +1,245 @@ -/* - This file is part of Subsonic. - - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see . - - Copyright 2009 (C) Sindre Mehus - */ -package net.sourceforge.subsonic.androidapp.service; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -import org.apache.http.HttpResponse; - -import android.content.Context; -import android.graphics.Bitmap; -import net.sourceforge.subsonic.androidapp.domain.Indexes; -import net.sourceforge.subsonic.androidapp.domain.JukeboxStatus; -import net.sourceforge.subsonic.androidapp.domain.Lyrics; -import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; -import net.sourceforge.subsonic.androidapp.domain.MusicFolder; -import net.sourceforge.subsonic.androidapp.domain.Playlist; -import net.sourceforge.subsonic.androidapp.domain.SearchCritera; -import net.sourceforge.subsonic.androidapp.domain.SearchResult; -import net.sourceforge.subsonic.androidapp.domain.Version; -import net.sourceforge.subsonic.androidapp.util.CancellableTask; -import net.sourceforge.subsonic.androidapp.util.LRUCache; -import net.sourceforge.subsonic.androidapp.util.ProgressListener; -import net.sourceforge.subsonic.androidapp.util.TimeLimitedCache; -import net.sourceforge.subsonic.androidapp.util.Util; - -/** - * @author Sindre Mehus - */ -public class CachedMusicService implements MusicService { - - private static final int MUSIC_DIR_CACHE_SIZE = 50; - private static final int TTL_MUSIC_DIR = 5 * 60; // Five minutes - - private final MusicService musicService; - private final LRUCache> cachedMusicDirectories; - private final TimeLimitedCache cachedLicenseValid = new TimeLimitedCache(120, TimeUnit.SECONDS); - private final TimeLimitedCache cachedIndexes = new TimeLimitedCache(60 * 60, TimeUnit.SECONDS); - private final TimeLimitedCache> cachedPlaylists = new TimeLimitedCache>(60, TimeUnit.SECONDS); - private final TimeLimitedCache> cachedMusicFolders = new TimeLimitedCache>(10 * 3600, TimeUnit.SECONDS); - private String restUrl; - - public CachedMusicService(MusicService musicService) { - this.musicService = musicService; - cachedMusicDirectories = new LRUCache>(MUSIC_DIR_CACHE_SIZE); - } - - @Override - public void ping(Context context, ProgressListener progressListener) throws Exception { - checkSettingsChanged(context); - musicService.ping(context, progressListener); - } - - @Override - public boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception { - checkSettingsChanged(context); - Boolean result = cachedLicenseValid.get(); - if (result == null) { - result = musicService.isLicenseValid(context, progressListener); - cachedLicenseValid.set(result, result ? 30L * 60L : 2L * 60L, TimeUnit.SECONDS); - } - return result; - } - - @Override - public List getMusicFolders(Context context, ProgressListener progressListener) throws Exception { - checkSettingsChanged(context); - List result = cachedMusicFolders.get(); - if (result == null) { - result = musicService.getMusicFolders(context, progressListener); - cachedMusicFolders.set(result); - } - return result; - } - - @Override - public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception { - checkSettingsChanged(context); - if (refresh) { - cachedIndexes.clear(); - cachedMusicFolders.clear(); - cachedMusicDirectories.clear(); - } - Indexes result = cachedIndexes.get(); - if (result == null) { - result = musicService.getIndexes(musicFolderId, refresh, context, progressListener); - cachedIndexes.set(result); - } - return result; - } - - @Override - public MusicDirectory getMusicDirectory(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception { - checkSettingsChanged(context); - TimeLimitedCache cache = refresh ? null : cachedMusicDirectories.get(id); - MusicDirectory dir = cache == null ? null : cache.get(); - if (dir == null) { - dir = musicService.getMusicDirectory(id, refresh, context, progressListener); - cache = new TimeLimitedCache(TTL_MUSIC_DIR, TimeUnit.SECONDS); - cache.set(dir); - cachedMusicDirectories.put(id, cache); - } - return dir; - } - - @Override - public SearchResult search(SearchCritera criteria, Context context, ProgressListener progressListener) throws Exception { - return musicService.search(criteria, context, progressListener); - } - - @Override - public MusicDirectory getPlaylist(String id, Context context, ProgressListener progressListener) throws Exception { - return musicService.getPlaylist(id, context, progressListener); - } - - @Override - public List getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception { - checkSettingsChanged(context); - List result = refresh ? null : cachedPlaylists.get(); - if (result == null) { - result = musicService.getPlaylists(refresh, context, progressListener); - cachedPlaylists.set(result); - } - return result; - } - - @Override - public void createPlaylist(String id, String name, List entries, Context context, ProgressListener progressListener) throws Exception { - musicService.createPlaylist(id, name, entries, context, progressListener); - } - - @Override - public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception { - return musicService.getLyrics(artist, title, context, progressListener); - } - - @Override - public void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception { - musicService.scrobble(id, submission, context, progressListener); - } - - @Override - public MusicDirectory getAlbumList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception { - return musicService.getAlbumList(type, size, offset, context, progressListener); - } - - @Override - public MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener) throws Exception { - return musicService.getRandomSongs(size, context, progressListener); - } - - @Override - public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, ProgressListener progressListener) throws Exception { - return musicService.getCoverArt(context, entry, size, saveToFile, progressListener); - } - - @Override - public HttpResponse getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, CancellableTask task) throws Exception { - return musicService.getDownloadInputStream(context, song, offset, maxBitrate, task); - } - - @Override - public Version getLocalVersion(Context context) throws Exception { - return musicService.getLocalVersion(context); - } - - @Override - public Version getLatestVersion(Context context, ProgressListener progressListener) throws Exception { - return musicService.getLatestVersion(context, progressListener); - } - - @Override - public String getVideoUrl(Context context, String id) { - return musicService.getVideoUrl(context, id); - } - - @Override - public JukeboxStatus updateJukeboxPlaylist(List ids, Context context, ProgressListener progressListener) throws Exception { - return musicService.updateJukeboxPlaylist(ids, context, progressListener); - } - - @Override - public JukeboxStatus skipJukebox(int index, int offsetSeconds, Context context, ProgressListener progressListener) throws Exception { - return musicService.skipJukebox(index, offsetSeconds, context, progressListener); - } - - @Override - public JukeboxStatus stopJukebox(Context context, ProgressListener progressListener) throws Exception { - return musicService.stopJukebox(context, progressListener); - } - - @Override - public JukeboxStatus startJukebox(Context context, ProgressListener progressListener) throws Exception { - return musicService.startJukebox(context, progressListener); - } - - @Override - public JukeboxStatus getJukeboxStatus(Context context, ProgressListener progressListener) throws Exception { - return musicService.getJukeboxStatus(context, progressListener); - } - - @Override - public JukeboxStatus setJukeboxGain(float gain, Context context, ProgressListener progressListener) throws Exception { - return musicService.setJukeboxGain(gain, context, progressListener); - } - - private void checkSettingsChanged(Context context) { - String newUrl = Util.getRestUrl(context, null); - if (!Util.equals(newUrl, restUrl)) { - cachedMusicFolders.clear(); - cachedMusicDirectories.clear(); - cachedLicenseValid.clear(); - cachedIndexes.clear(); - cachedPlaylists.clear(); - restUrl = newUrl; - } - } -} +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see . + + Copyright 2009 (C) Sindre Mehus + */ +package net.sourceforge.subsonic.androidapp.service; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.apache.http.HttpResponse; + +import android.content.Context; +import android.graphics.Bitmap; +import net.sourceforge.subsonic.androidapp.domain.Indexes; +import net.sourceforge.subsonic.androidapp.domain.JukeboxStatus; +import net.sourceforge.subsonic.androidapp.domain.Lyrics; +import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; +import net.sourceforge.subsonic.androidapp.domain.MusicFolder; +import net.sourceforge.subsonic.androidapp.domain.Playlist; +import net.sourceforge.subsonic.androidapp.domain.SearchCritera; +import net.sourceforge.subsonic.androidapp.domain.SearchResult; +import net.sourceforge.subsonic.androidapp.domain.Version; +import net.sourceforge.subsonic.androidapp.util.CancellableTask; +import net.sourceforge.subsonic.androidapp.util.LRUCache; +import net.sourceforge.subsonic.androidapp.util.ProgressListener; +import net.sourceforge.subsonic.androidapp.util.TimeLimitedCache; +import net.sourceforge.subsonic.androidapp.util.Util; + +/** + * @author Sindre Mehus + */ +public class CachedMusicService implements MusicService { + + private static final int MUSIC_DIR_CACHE_SIZE = 50; + private static final int TTL_MUSIC_DIR = 5 * 60; // Five minutes + + private final MusicService musicService; + private final LRUCache> cachedMusicDirectories; + private final TimeLimitedCache cachedLicenseValid = new TimeLimitedCache(120, TimeUnit.SECONDS); + private final TimeLimitedCache cachedIndexes = new TimeLimitedCache(60 * 60, TimeUnit.SECONDS); + private final TimeLimitedCache> cachedPlaylists = new TimeLimitedCache>(60, TimeUnit.SECONDS); + private final TimeLimitedCache> cachedMusicFolders = new TimeLimitedCache>(10 * 3600, TimeUnit.SECONDS); + private String restUrl; + + public CachedMusicService(MusicService musicService) { + this.musicService = musicService; + cachedMusicDirectories = new LRUCache>(MUSIC_DIR_CACHE_SIZE); + } + + @Override + public void ping(Context context, ProgressListener progressListener) throws Exception { + checkSettingsChanged(context); + musicService.ping(context, progressListener); + } + + @Override + public boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception { + checkSettingsChanged(context); + Boolean result = cachedLicenseValid.get(); + if (result == null) { + result = musicService.isLicenseValid(context, progressListener); + cachedLicenseValid.set(result, result ? 30L * 60L : 2L * 60L, TimeUnit.SECONDS); + } + return result; + } + + @Override + public List getMusicFolders(Context context, ProgressListener progressListener) throws Exception { + checkSettingsChanged(context); + List result = cachedMusicFolders.get(); + if (result == null) { + result = musicService.getMusicFolders(context, progressListener); + cachedMusicFolders.set(result); + } + return result; + } + + @Override + public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception { + checkSettingsChanged(context); + if (refresh) { + cachedIndexes.clear(); + cachedMusicFolders.clear(); + cachedMusicDirectories.clear(); + } + Indexes result = cachedIndexes.get(); + if (result == null) { + result = musicService.getIndexes(musicFolderId, refresh, context, progressListener); + cachedIndexes.set(result); + } + return result; + } + + @Override + public MusicDirectory getMusicDirectory(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception { + checkSettingsChanged(context); + TimeLimitedCache cache = refresh ? null : cachedMusicDirectories.get(id); + MusicDirectory dir = cache == null ? null : cache.get(); + if (dir == null) { + dir = musicService.getMusicDirectory(id, refresh, context, progressListener); + cache = new TimeLimitedCache(TTL_MUSIC_DIR, TimeUnit.SECONDS); + cache.set(dir); + cachedMusicDirectories.put(id, cache); + } + return dir; + } + + @Override + public SearchResult search(SearchCritera criteria, Context context, ProgressListener progressListener) throws Exception { + return musicService.search(criteria, context, progressListener); + } + + @Override + public MusicDirectory getPlaylist(String id, Context context, ProgressListener progressListener) throws Exception { + return musicService.getPlaylist(id, context, progressListener); + } + + @Override + public List getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception { + checkSettingsChanged(context); + List result = refresh ? null : cachedPlaylists.get(); + if (result == null) { + result = musicService.getPlaylists(refresh, context, progressListener); + cachedPlaylists.set(result); + } + return result; + } + + @Override + public void createPlaylist(String id, String name, List entries, Context context, ProgressListener progressListener) throws Exception { + musicService.createPlaylist(id, name, entries, context, progressListener); + } + + @Override + public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception { + return musicService.getLyrics(artist, title, context, progressListener); + } + + @Override + public void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception { + musicService.scrobble(id, submission, context, progressListener); + } + + @Override + public MusicDirectory getAlbumList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception { + return musicService.getAlbumList(type, size, offset, context, progressListener); + } + + @Override + public MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener) throws Exception { + return musicService.getRandomSongs(size, context, progressListener); + } + + @Override + public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, ProgressListener progressListener) throws Exception { + return musicService.getCoverArt(context, entry, size, saveToFile, progressListener); + } + + @Override + public HttpResponse getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, CancellableTask task) throws Exception { + return musicService.getDownloadInputStream(context, song, offset, maxBitrate, task); + } + + @Override + public Version getLocalVersion(Context context) throws Exception { + return musicService.getLocalVersion(context); + } + + @Override + public Version getLatestVersion(Context context, ProgressListener progressListener) throws Exception { + return musicService.getLatestVersion(context, progressListener); + } + + @Override + public String getVideoUrl(Context context, String id) { + return musicService.getVideoUrl(context, id); + } + + @Override + public JukeboxStatus updateJukeboxPlaylist(List ids, Context context, ProgressListener progressListener) throws Exception { + return musicService.updateJukeboxPlaylist(ids, context, progressListener); + } + + @Override + public JukeboxStatus skipJukebox(int index, int offsetSeconds, Context context, ProgressListener progressListener) throws Exception { + return musicService.skipJukebox(index, offsetSeconds, context, progressListener); + } + + @Override + public JukeboxStatus stopJukebox(Context context, ProgressListener progressListener) throws Exception { + return musicService.stopJukebox(context, progressListener); + } + + @Override + public JukeboxStatus startJukebox(Context context, ProgressListener progressListener) throws Exception { + return musicService.startJukebox(context, progressListener); + } + + @Override + public JukeboxStatus getJukeboxStatus(Context context, ProgressListener progressListener) throws Exception { + return musicService.getJukeboxStatus(context, progressListener); + } + + @Override + public JukeboxStatus setJukeboxGain(float gain, Context context, ProgressListener progressListener) throws Exception { + return musicService.setJukeboxGain(gain, context, progressListener); + } + + private void checkSettingsChanged(Context context) { + String newUrl = Util.getRestUrl(context, null); + if (!Util.equals(newUrl, restUrl)) { + cachedMusicFolders.clear(); + cachedMusicDirectories.clear(); + cachedLicenseValid.clear(); + cachedIndexes.clear(); + cachedPlaylists.clear(); + restUrl = newUrl; + } + } + + @Override + public void star(String id, Context context, ProgressListener progressListener) throws Exception { + musicService.star(id, context, progressListener); + + } + + @Override + public void unstar(String id, Context context, ProgressListener progressListener) throws Exception { + musicService.unstar(id, context, progressListener); + } +} diff --git a/src/net/sourceforge/subsonic/androidapp/service/MusicService.java b/src/net/sourceforge/subsonic/androidapp/service/MusicService.java index eda7d1b3..f1770bdb 100644 --- a/src/net/sourceforge/subsonic/androidapp/service/MusicService.java +++ b/src/net/sourceforge/subsonic/androidapp/service/MusicService.java @@ -1,91 +1,95 @@ -/* - This file is part of Subsonic. - - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see . - - Copyright 2009 (C) Sindre Mehus - */ -package net.sourceforge.subsonic.androidapp.service; - -import java.util.List; - -import org.apache.http.HttpResponse; - -import android.content.Context; -import android.graphics.Bitmap; -import net.sourceforge.subsonic.androidapp.domain.Indexes; -import net.sourceforge.subsonic.androidapp.domain.JukeboxStatus; -import net.sourceforge.subsonic.androidapp.domain.Lyrics; -import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; -import net.sourceforge.subsonic.androidapp.domain.MusicFolder; -import net.sourceforge.subsonic.androidapp.domain.Playlist; -import net.sourceforge.subsonic.androidapp.domain.SearchCritera; -import net.sourceforge.subsonic.androidapp.domain.SearchResult; -import net.sourceforge.subsonic.androidapp.domain.Version; -import net.sourceforge.subsonic.androidapp.util.CancellableTask; -import net.sourceforge.subsonic.androidapp.util.ProgressListener; - -/** - * @author Sindre Mehus - */ -public interface MusicService { - - void ping(Context context, ProgressListener progressListener) throws Exception; - - boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception; - - List getMusicFolders(Context context, ProgressListener progressListener) throws Exception; - - Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception; - - MusicDirectory getMusicDirectory(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception; - - SearchResult search(SearchCritera criteria, Context context, ProgressListener progressListener) throws Exception; - - MusicDirectory getPlaylist(String id, Context context, ProgressListener progressListener) throws Exception; - - List getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception; - - void createPlaylist(String id, String name, List entries, Context context, ProgressListener progressListener) throws Exception; - - Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception; - - void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception; - - MusicDirectory getAlbumList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception; - - MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener) throws Exception; - - Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, ProgressListener progressListener) throws Exception; - - HttpResponse getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, CancellableTask task) throws Exception; - - Version getLocalVersion(Context context) throws Exception; - - Version getLatestVersion(Context context, ProgressListener progressListener) throws Exception; - - String getVideoUrl(Context context, String id); - - JukeboxStatus updateJukeboxPlaylist(List ids, Context context, ProgressListener progressListener) throws Exception; - - JukeboxStatus skipJukebox(int index, int offsetSeconds, Context context, ProgressListener progressListener) throws Exception; - - JukeboxStatus stopJukebox(Context context, ProgressListener progressListener) throws Exception; - - JukeboxStatus startJukebox(Context context, ProgressListener progressListener) throws Exception; - - JukeboxStatus getJukeboxStatus(Context context, ProgressListener progressListener) throws Exception; - - JukeboxStatus setJukeboxGain(float gain, Context context, ProgressListener progressListener) throws Exception; +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see . + + Copyright 2009 (C) Sindre Mehus + */ +package net.sourceforge.subsonic.androidapp.service; + +import java.util.List; + +import org.apache.http.HttpResponse; + +import android.content.Context; +import android.graphics.Bitmap; +import net.sourceforge.subsonic.androidapp.domain.Indexes; +import net.sourceforge.subsonic.androidapp.domain.JukeboxStatus; +import net.sourceforge.subsonic.androidapp.domain.Lyrics; +import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; +import net.sourceforge.subsonic.androidapp.domain.MusicFolder; +import net.sourceforge.subsonic.androidapp.domain.Playlist; +import net.sourceforge.subsonic.androidapp.domain.SearchCritera; +import net.sourceforge.subsonic.androidapp.domain.SearchResult; +import net.sourceforge.subsonic.androidapp.domain.Version; +import net.sourceforge.subsonic.androidapp.util.CancellableTask; +import net.sourceforge.subsonic.androidapp.util.ProgressListener; + +/** + * @author Sindre Mehus + */ +public interface MusicService { + + void ping(Context context, ProgressListener progressListener) throws Exception; + + boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception; + + List getMusicFolders(Context context, ProgressListener progressListener) throws Exception; + + void star(String id, Context context, ProgressListener progressListener) throws Exception; + + void unstar(String id, Context context, ProgressListener progressListener) throws Exception; + + Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception; + + MusicDirectory getMusicDirectory(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception; + + SearchResult search(SearchCritera criteria, Context context, ProgressListener progressListener) throws Exception; + + MusicDirectory getPlaylist(String id, Context context, ProgressListener progressListener) throws Exception; + + List getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception; + + void createPlaylist(String id, String name, List entries, Context context, ProgressListener progressListener) throws Exception; + + Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception; + + void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception; + + MusicDirectory getAlbumList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception; + + MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener) throws Exception; + + Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, ProgressListener progressListener) throws Exception; + + HttpResponse getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, CancellableTask task) throws Exception; + + Version getLocalVersion(Context context) throws Exception; + + Version getLatestVersion(Context context, ProgressListener progressListener) throws Exception; + + String getVideoUrl(Context context, String id); + + JukeboxStatus updateJukeboxPlaylist(List ids, Context context, ProgressListener progressListener) throws Exception; + + JukeboxStatus skipJukebox(int index, int offsetSeconds, Context context, ProgressListener progressListener) throws Exception; + + JukeboxStatus stopJukebox(Context context, ProgressListener progressListener) throws Exception; + + JukeboxStatus startJukebox(Context context, ProgressListener progressListener) throws Exception; + + JukeboxStatus getJukeboxStatus(Context context, ProgressListener progressListener) throws Exception; + + JukeboxStatus setJukeboxGain(float gain, Context context, ProgressListener progressListener) throws Exception; } \ No newline at end of file diff --git a/src/net/sourceforge/subsonic/androidapp/service/OfflineMusicService.java b/src/net/sourceforge/subsonic/androidapp/service/OfflineMusicService.java index 7b9fcc7a..5bc8f00b 100644 --- a/src/net/sourceforge/subsonic/androidapp/service/OfflineMusicService.java +++ b/src/net/sourceforge/subsonic/androidapp/service/OfflineMusicService.java @@ -1,244 +1,254 @@ -/* - This file is part of Subsonic. - - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see . - - Copyright 2009 (C) Sindre Mehus - */ -package net.sourceforge.subsonic.androidapp.service; - -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Random; -import java.util.Set; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import net.sourceforge.subsonic.androidapp.domain.Artist; -import net.sourceforge.subsonic.androidapp.domain.Indexes; -import net.sourceforge.subsonic.androidapp.domain.JukeboxStatus; -import net.sourceforge.subsonic.androidapp.domain.Lyrics; -import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; -import net.sourceforge.subsonic.androidapp.domain.MusicFolder; -import net.sourceforge.subsonic.androidapp.domain.Playlist; -import net.sourceforge.subsonic.androidapp.domain.SearchCritera; -import net.sourceforge.subsonic.androidapp.domain.SearchResult; -import net.sourceforge.subsonic.androidapp.util.Constants; -import net.sourceforge.subsonic.androidapp.util.FileUtil; -import net.sourceforge.subsonic.androidapp.util.ProgressListener; -import net.sourceforge.subsonic.androidapp.util.Util; - -/** - * @author Sindre Mehus - */ -public class OfflineMusicService extends RESTMusicService { - - @Override - public boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception { - return true; - } - - @Override - public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception { - List artists = new ArrayList(); - File root = FileUtil.getMusicDirectory(context); - for (File file : FileUtil.listFiles(root)) { - if (file.isDirectory()) { - Artist artist = new Artist(); - artist.setId(file.getPath()); - artist.setIndex(file.getName().substring(0, 1)); - artist.setName(file.getName()); - artists.add(artist); - } - } - return new Indexes(0L, Collections.emptyList(), artists); - } - - @Override - public MusicDirectory getMusicDirectory(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception { - File dir = new File(id); - MusicDirectory result = new MusicDirectory(); - result.setName(dir.getName()); - - Set names = new HashSet(); - - for (File file : FileUtil.listMusicFiles(dir)) { - String name = getName(file); - if (name != null & !names.contains(name)) { - names.add(name); - result.addChild(createEntry(context, file, name)); - } - } - return result; - } - - private String getName(File file) { - String name = file.getName(); - if (file.isDirectory()) { - return name; - } - - if (name.endsWith(".partial") || name.contains(".partial.") || name.equals(Constants.ALBUM_ART_FILE)) { - return null; - } - - name = name.replace(".complete", ""); - return FileUtil.getBaseName(name); - } - - private MusicDirectory.Entry createEntry(Context context, File file, String name) { - MusicDirectory.Entry entry = new MusicDirectory.Entry(); - entry.setDirectory(file.isDirectory()); - entry.setId(file.getPath()); - entry.setParent(file.getParent()); - entry.setSize(file.length()); - String root = FileUtil.getMusicDirectory(context).getPath(); - entry.setPath(file.getPath().replaceFirst("^" + root + "/" , "")); - if (file.isFile()) { - entry.setArtist(file.getParentFile().getParentFile().getName()); - entry.setAlbum(file.getParentFile().getName()); - } - entry.setTitle(name); - entry.setSuffix(FileUtil.getExtension(file.getName().replace(".complete", ""))); - - File albumArt = FileUtil.getAlbumArtFile(context, entry); - if (albumArt.exists()) { - entry.setCoverArt(albumArt.getPath()); - } - return entry; - } - - @Override - public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, ProgressListener progressListener) throws Exception { - InputStream in = new FileInputStream(entry.getCoverArt()); - try { - byte[] bytes = Util.toByteArray(in); - Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); - return Bitmap.createScaledBitmap(bitmap, size, size, true); - } finally { - Util.close(in); - } - } - - @Override - public List getMusicFolders(Context context, ProgressListener progressListener) throws Exception { - throw new OfflineException("Music folders not available in offline mode"); - } - - @Override - public SearchResult search(SearchCritera criteria, Context context, ProgressListener progressListener) throws Exception { - throw new OfflineException("Search not available in offline mode"); - } - - @Override - public List getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception { - throw new OfflineException("Playlists not available in offline mode"); - } - - @Override - public MusicDirectory getPlaylist(String id, Context context, ProgressListener progressListener) throws Exception { - throw new OfflineException("Playlists not available in offline mode"); - } - - @Override - public void createPlaylist(String id, String name, List entries, Context context, ProgressListener progressListener) throws Exception { - throw new OfflineException("Playlists not available in offline mode"); - } - - @Override - public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception { - throw new OfflineException("Lyrics not available in offline mode"); - } - - @Override - public void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception { - throw new OfflineException("Scrobbling not available in offline mode"); - } - - @Override - public MusicDirectory getAlbumList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception { - throw new OfflineException("Album lists not available in offline mode"); - } - - @Override - public String getVideoUrl(Context context, String id) { - return null; - } - - @Override - public JukeboxStatus updateJukeboxPlaylist(List ids, Context context, ProgressListener progressListener) throws Exception { - throw new OfflineException("Jukebox not available in offline mode"); - } - - @Override - public JukeboxStatus skipJukebox(int index, int offsetSeconds, Context context, ProgressListener progressListener) throws Exception { - throw new OfflineException("Jukebox not available in offline mode"); - } - - @Override - public JukeboxStatus stopJukebox(Context context, ProgressListener progressListener) throws Exception { - throw new OfflineException("Jukebox not available in offline mode"); - } - - @Override - public JukeboxStatus startJukebox(Context context, ProgressListener progressListener) throws Exception { - throw new OfflineException("Jukebox not available in offline mode"); - } - - @Override - public JukeboxStatus getJukeboxStatus(Context context, ProgressListener progressListener) throws Exception { - throw new OfflineException("Jukebox not available in offline mode"); - } - - @Override - public JukeboxStatus setJukeboxGain(float gain, Context context, ProgressListener progressListener) throws Exception { - throw new OfflineException("Jukebox not available in offline mode"); - } - - @Override - public MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener) throws Exception { - File root = FileUtil.getMusicDirectory(context); - List children = new LinkedList(); - listFilesRecursively(root, children); - MusicDirectory result = new MusicDirectory(); - - if (children.isEmpty()) { - return result; - } - Random random = new Random(); - for (int i = 0; i < size; i++) { - File file = children.get(random.nextInt(children.size())); - result.addChild(createEntry(context, file, getName(file))); - } - - return result; - } - - private void listFilesRecursively(File parent, List children) { - for (File file : FileUtil.listMusicFiles(parent)) { - if (file.isFile()) { - children.add(file); - } else { - listFilesRecursively(file, children); - } - } - } -} +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see . + + Copyright 2009 (C) Sindre Mehus + */ +package net.sourceforge.subsonic.androidapp.service; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import net.sourceforge.subsonic.androidapp.domain.Artist; +import net.sourceforge.subsonic.androidapp.domain.Indexes; +import net.sourceforge.subsonic.androidapp.domain.JukeboxStatus; +import net.sourceforge.subsonic.androidapp.domain.Lyrics; +import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; +import net.sourceforge.subsonic.androidapp.domain.MusicFolder; +import net.sourceforge.subsonic.androidapp.domain.Playlist; +import net.sourceforge.subsonic.androidapp.domain.SearchCritera; +import net.sourceforge.subsonic.androidapp.domain.SearchResult; +import net.sourceforge.subsonic.androidapp.util.Constants; +import net.sourceforge.subsonic.androidapp.util.FileUtil; +import net.sourceforge.subsonic.androidapp.util.ProgressListener; +import net.sourceforge.subsonic.androidapp.util.Util; + +/** + * @author Sindre Mehus + */ +public class OfflineMusicService extends RESTMusicService { + + @Override + public boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception { + return true; + } + + @Override + public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception { + List artists = new ArrayList(); + File root = FileUtil.getMusicDirectory(context); + for (File file : FileUtil.listFiles(root)) { + if (file.isDirectory()) { + Artist artist = new Artist(); + artist.setId(file.getPath()); + artist.setIndex(file.getName().substring(0, 1)); + artist.setName(file.getName()); + artists.add(artist); + } + } + return new Indexes(0L, Collections.emptyList(), artists); + } + + @Override + public MusicDirectory getMusicDirectory(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception { + File dir = new File(id); + MusicDirectory result = new MusicDirectory(); + result.setName(dir.getName()); + + Set names = new HashSet(); + + for (File file : FileUtil.listMusicFiles(dir)) { + String name = getName(file); + if (name != null & !names.contains(name)) { + names.add(name); + result.addChild(createEntry(context, file, name)); + } + } + return result; + } + + private String getName(File file) { + String name = file.getName(); + if (file.isDirectory()) { + return name; + } + + if (name.endsWith(".partial") || name.contains(".partial.") || name.equals(Constants.ALBUM_ART_FILE)) { + return null; + } + + name = name.replace(".complete", ""); + return FileUtil.getBaseName(name); + } + + private MusicDirectory.Entry createEntry(Context context, File file, String name) { + MusicDirectory.Entry entry = new MusicDirectory.Entry(); + entry.setDirectory(file.isDirectory()); + entry.setId(file.getPath()); + entry.setParent(file.getParent()); + entry.setSize(file.length()); + String root = FileUtil.getMusicDirectory(context).getPath(); + entry.setPath(file.getPath().replaceFirst("^" + root + "/" , "")); + if (file.isFile()) { + entry.setArtist(file.getParentFile().getParentFile().getName()); + entry.setAlbum(file.getParentFile().getName()); + } + entry.setTitle(name); + entry.setSuffix(FileUtil.getExtension(file.getName().replace(".complete", ""))); + + File albumArt = FileUtil.getAlbumArtFile(context, entry); + if (albumArt.exists()) { + entry.setCoverArt(albumArt.getPath()); + } + return entry; + } + + @Override + public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, ProgressListener progressListener) throws Exception { + InputStream in = new FileInputStream(entry.getCoverArt()); + try { + byte[] bytes = Util.toByteArray(in); + Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); + return Bitmap.createScaledBitmap(bitmap, size, size, true); + } finally { + Util.close(in); + } + } + + @Override + public void star(String id, Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("Star not available in offline mode"); + } + + @Override + public void unstar(String id, Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("UnStar not available in offline mode"); + } + + @Override + public List getMusicFolders(Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("Music folders not available in offline mode"); + } + + @Override + public SearchResult search(SearchCritera criteria, Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("Search not available in offline mode"); + } + + @Override + public List getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("Playlists not available in offline mode"); + } + + @Override + public MusicDirectory getPlaylist(String id, Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("Playlists not available in offline mode"); + } + + @Override + public void createPlaylist(String id, String name, List entries, Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("Playlists not available in offline mode"); + } + + @Override + public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("Lyrics not available in offline mode"); + } + + @Override + public void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("Scrobbling not available in offline mode"); + } + + @Override + public MusicDirectory getAlbumList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("Album lists not available in offline mode"); + } + + @Override + public String getVideoUrl(Context context, String id) { + return null; + } + + @Override + public JukeboxStatus updateJukeboxPlaylist(List ids, Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("Jukebox not available in offline mode"); + } + + @Override + public JukeboxStatus skipJukebox(int index, int offsetSeconds, Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("Jukebox not available in offline mode"); + } + + @Override + public JukeboxStatus stopJukebox(Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("Jukebox not available in offline mode"); + } + + @Override + public JukeboxStatus startJukebox(Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("Jukebox not available in offline mode"); + } + + @Override + public JukeboxStatus getJukeboxStatus(Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("Jukebox not available in offline mode"); + } + + @Override + public JukeboxStatus setJukeboxGain(float gain, Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("Jukebox not available in offline mode"); + } + + @Override + public MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener) throws Exception { + File root = FileUtil.getMusicDirectory(context); + List children = new LinkedList(); + listFilesRecursively(root, children); + MusicDirectory result = new MusicDirectory(); + + if (children.isEmpty()) { + return result; + } + Random random = new Random(); + for (int i = 0; i < size; i++) { + File file = children.get(random.nextInt(children.size())); + result.addChild(createEntry(context, file, getName(file))); + } + + return result; + } + + private void listFilesRecursively(File parent, List children) { + for (File file : FileUtil.listMusicFiles(parent)) { + if (file.isFile()) { + children.add(file); + } else { + listFilesRecursively(file, children); + } + } + } +} diff --git a/src/net/sourceforge/subsonic/androidapp/service/RESTMusicService.java b/src/net/sourceforge/subsonic/androidapp/service/RESTMusicService.java index 4e739a9b..c8deb9bf 100644 --- a/src/net/sourceforge/subsonic/androidapp/service/RESTMusicService.java +++ b/src/net/sourceforge/subsonic/androidapp/service/RESTMusicService.java @@ -203,6 +203,24 @@ public class RESTMusicService implements MusicService { Util.close(reader); } } + + public void star(String id, Context context, ProgressListener progressListener) throws Exception { + Reader reader = getReader(context, progressListener, "star", null, "id", id); + try { + new ErrorParser(context).parse(reader); + } finally { + Util.close(reader); + } + } + + public void unstar(String id, Context context, ProgressListener progressListener) throws Exception { + Reader reader = getReader(context, progressListener, "unstar", null, "id", id); + try { + new ErrorParser(context).parse(reader); + } finally { + Util.close(reader); + } + } @Override public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception { diff --git a/src/net/sourceforge/subsonic/androidapp/service/parser/AbstractParser.java b/src/net/sourceforge/subsonic/androidapp/service/parser/AbstractParser.java index 1f71bdc0..d170ba04 100644 --- a/src/net/sourceforge/subsonic/androidapp/service/parser/AbstractParser.java +++ b/src/net/sourceforge/subsonic/androidapp/service/parser/AbstractParser.java @@ -92,6 +92,11 @@ public abstract class AbstractParser { protected boolean getBoolean(String name) { return "true".equals(get(name)); } + + protected boolean getValueExists(String name) { + String value = get(name); + return value != null && !value.isEmpty(); + } protected Integer getInteger(String name) { String s = get(name); diff --git a/src/net/sourceforge/subsonic/androidapp/service/parser/MusicDirectoryEntryParser.java b/src/net/sourceforge/subsonic/androidapp/service/parser/MusicDirectoryEntryParser.java index d35577b6..72f091c5 100644 --- a/src/net/sourceforge/subsonic/androidapp/service/parser/MusicDirectoryEntryParser.java +++ b/src/net/sourceforge/subsonic/androidapp/service/parser/MusicDirectoryEntryParser.java @@ -38,6 +38,7 @@ public class MusicDirectoryEntryParser extends AbstractParser { entry.setDirectory(getBoolean("isDir")); entry.setCoverArt(get("coverArt")); entry.setArtist(get("artist")); + entry.setStarred(getValueExists("starred")); if (!entry.isDirectory()) { entry.setAlbum(get("album")); @@ -53,7 +54,9 @@ public class MusicDirectoryEntryParser extends AbstractParser { entry.setBitRate(getInteger("bitRate")); entry.setPath(get("path")); entry.setVideo(getBoolean("isVideo")); + entry.setDiscNumber(getInteger("discNumber")); } + return entry; } } \ No newline at end of file diff --git a/src/net/sourceforge/subsonic/androidapp/util/AlbumView.java b/src/net/sourceforge/subsonic/androidapp/util/AlbumView.java index a4dd3acd..b7eaf158 100644 --- a/src/net/sourceforge/subsonic/androidapp/util/AlbumView.java +++ b/src/net/sourceforge/subsonic/androidapp/util/AlbumView.java @@ -1,55 +1,93 @@ -/* - This file is part of Subsonic. - - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see . - - Copyright 2009 (C) Sindre Mehus - */ -package net.sourceforge.subsonic.androidapp.util; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; -import net.sourceforge.subsonic.androidapp.R; -import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; - -/** - * Used to display albums in a {@code ListView}. - * - * @author Sindre Mehus - */ -public class AlbumView extends LinearLayout { - - private TextView titleView; - private TextView artistView; - private View coverArtView; - - public AlbumView(Context context) { - super(context); - LayoutInflater.from(context).inflate(R.layout.album_list_item, this, true); - - titleView = (TextView) findViewById(R.id.album_title); - artistView = (TextView) findViewById(R.id.album_artist); - coverArtView = findViewById(R.id.album_coverart); - } - - public void setAlbum(MusicDirectory.Entry album, ImageLoader imageLoader) { - titleView.setText(album.getTitle()); - artistView.setText(album.getArtist()); - artistView.setVisibility(album.getArtist() == null ? View.GONE : View.VISIBLE); - imageLoader.loadImage(coverArtView, album, false, true); - } -} +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see . + + Copyright 2009 (C) Sindre Mehus + */ +package net.sourceforge.subsonic.androidapp.util; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; +import net.sourceforge.subsonic.androidapp.service.MusicService; +import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; + +/** + * Used to display albums in a {@code ListView}. + * + * @author Sindre Mehus + */ +public class AlbumView extends LinearLayout { + + private TextView titleView; + private TextView artistView; + private View coverArtView; + private ImageView starImageView; + + public AlbumView(Context context) { + super(context); + LayoutInflater.from(context).inflate(R.layout.album_list_item, this, true); + + titleView = (TextView) findViewById(R.id.album_title); + artistView = (TextView) findViewById(R.id.album_artist); + coverArtView = findViewById(R.id.album_coverart); + starImageView = (ImageView) findViewById(R.id.album_star); + } + + public void setAlbum(final MusicDirectory.Entry album, ImageLoader imageLoader) { + titleView.setText(album.getTitle()); + artistView.setText(album.getArtist()); + artistView.setVisibility(album.getArtist() == null ? View.GONE : View.VISIBLE); + starImageView.setImageDrawable(album.getStarred() ? getResources().getDrawable(R.drawable.star) : getResources().getDrawable(R.drawable.star_hollow)); + imageLoader.loadImage(coverArtView, album, false, true); + + starImageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + final boolean isStarred = album.getStarred(); + final String id = album.getId(); + + if (!isStarred) { + starImageView.setImageDrawable(getResources().getDrawable(R.drawable.star)); + album.setStarred(true); + } else { + starImageView.setImageDrawable(getResources().getDrawable(R.drawable.star_hollow)); + album.setStarred(false); + } + + new Thread(new Runnable() { + public void run() { + MusicService musicService = MusicServiceFactory.getMusicService(null); + + try { + if (!isStarred) { + musicService.star(id, getContext(), null); + } else { + musicService.unstar(id, getContext(), null); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }).start(); + } + }); + } +} diff --git a/src/net/sourceforge/subsonic/androidapp/util/Constants.java b/src/net/sourceforge/subsonic/androidapp/util/Constants.java index f8c8964d..2e98d710 100644 --- a/src/net/sourceforge/subsonic/androidapp/util/Constants.java +++ b/src/net/sourceforge/subsonic/androidapp/util/Constants.java @@ -29,7 +29,7 @@ public final class Constants { // REST protocol version and client ID. // Note: Keep it as low as possible to maintain compatibility with older servers. - public static final String REST_PROTOCOL_VERSION = "1.2.0"; + public static final String REST_PROTOCOL_VERSION = "1.8.0"; public static final String REST_CLIENT_ID = "android"; // Names for intent extras. diff --git a/src/net/sourceforge/subsonic/androidapp/util/SongView.java b/src/net/sourceforge/subsonic/androidapp/util/SongView.java index b2ce8129..2b2c5673 100644 --- a/src/net/sourceforge/subsonic/androidapp/util/SongView.java +++ b/src/net/sourceforge/subsonic/androidapp/util/SongView.java @@ -1,178 +1,218 @@ -/* - This file is part of Subsonic. - - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see . - - Copyright 2009 (C) Sindre Mehus - */ -package net.sourceforge.subsonic.androidapp.util; - -import android.content.Context; -import android.os.Handler; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.Checkable; -import android.widget.CheckedTextView; -import android.widget.LinearLayout; -import android.widget.TextView; -import net.sourceforge.subsonic.androidapp.R; -import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; -import net.sourceforge.subsonic.androidapp.service.DownloadService; -import net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl; -import net.sourceforge.subsonic.androidapp.service.DownloadFile; - -import java.io.File; -import java.util.WeakHashMap; - -/** - * Used to display songs in a {@code ListView}. - * - * @author Sindre Mehus - */ -public class SongView extends LinearLayout implements Checkable { - - private static final String TAG = SongView.class.getSimpleName(); - private static final WeakHashMap INSTANCES = new WeakHashMap(); - private static Handler handler; - - private CheckedTextView checkedTextView; - private TextView titleTextView; - private TextView artistTextView; - private TextView durationTextView; - private TextView statusTextView; - private MusicDirectory.Entry song; - - public SongView(Context context) { - super(context); - LayoutInflater.from(context).inflate(R.layout.song_list_item, this, true); - - checkedTextView = (CheckedTextView) findViewById(R.id.song_check); - titleTextView = (TextView) findViewById(R.id.song_title); - artistTextView = (TextView) findViewById(R.id.song_artist); - durationTextView = (TextView) findViewById(R.id.song_duration); - statusTextView = (TextView) findViewById(R.id.song_status); - - INSTANCES.put(this, null); - int instanceCount = INSTANCES.size(); - if (instanceCount > 50) { - Log.w(TAG, instanceCount + " live SongView instances"); - } - startUpdater(); - } - - public void setSong(MusicDirectory.Entry song, boolean checkable) { - this.song = song; - StringBuilder artist = new StringBuilder(40); - - String bitRate = null; - if (song.getBitRate() != null) { - bitRate = String.format(getContext().getString(R.string.song_details_kbps), song.getBitRate()); - } - - String fileFormat = null; - if (song.getTranscodedSuffix() != null && !song.getTranscodedSuffix().equals(song.getSuffix())) { - fileFormat = String.format("%s > %s", song.getSuffix(), song.getTranscodedSuffix()); - } else { - fileFormat = song.getSuffix(); - } - - artist.append(song.getArtist()).append(" (") - .append(String.format(getContext().getString(R.string.song_details_all), bitRate == null ? "" : bitRate, fileFormat)) - .append(")"); - - titleTextView.setText(song.getTitle()); - artistTextView.setText(artist); - durationTextView.setText(Util.formatDuration(song.getDuration())); - checkedTextView.setVisibility(checkable && !song.isVideo() ? View.VISIBLE : View.GONE); - - update(); - } - - private void update() { - DownloadService downloadService = DownloadServiceImpl.getInstance(); - if (downloadService == null) { - return; - } - - DownloadFile downloadFile = downloadService.forSong(song); - File completeFile = downloadFile.getCompleteFile(); - File partialFile = downloadFile.getPartialFile(); - - int leftImage = 0; - int rightImage = 0; - - if (completeFile.exists()) { - leftImage = downloadFile.isSaved() ? R.drawable.ic_stat_saved : R.drawable.ic_stat_downloaded; - } - - if (downloadFile.isDownloading() && !downloadFile.isDownloadCancelled() && partialFile.exists()) { - statusTextView.setText(Util.formatLocalizedBytes(partialFile.length(), getContext())); - rightImage = R.drawable.ic_stat_downloading; - } else { - statusTextView.setText(null); - } - statusTextView.setCompoundDrawablesWithIntrinsicBounds(leftImage, 0, rightImage, 0); - - boolean playing = downloadService.getCurrentPlaying() == downloadFile; - if (playing) { - titleTextView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_stat_play, 0, 0, 0); - } else { - titleTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - } - } - - private static synchronized void startUpdater() { - if (handler != null) { - return; - } - - handler = new Handler(); - Runnable runnable = new Runnable() { - @Override - public void run() { - updateAll(); - handler.postDelayed(this, 1000L); - } - }; - handler.postDelayed(runnable, 1000L); - } - - private static void updateAll() { - try { - for (SongView view : INSTANCES.keySet()) { - if (view.isShown()) { - view.update(); - } - } - } catch (Throwable x) { - Log.w(TAG, "Error when updating song views.", x); - } - } - - @Override - public void setChecked(boolean b) { - checkedTextView.setChecked(b); - } - - @Override - public boolean isChecked() { - return checkedTextView.isChecked(); - } - - @Override - public void toggle() { - checkedTextView.toggle(); - } -} +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see . + + Copyright 2009 (C) Sindre Mehus + */ +package net.sourceforge.subsonic.androidapp.util; + +import android.content.Context; +import android.os.Handler; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Checkable; +import android.widget.CheckedTextView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; +import net.sourceforge.subsonic.androidapp.service.DownloadService; +import net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl; +import net.sourceforge.subsonic.androidapp.service.DownloadFile; +import net.sourceforge.subsonic.androidapp.service.MusicService; +import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; + +import java.io.File; +import java.util.WeakHashMap; + +/** + * Used to display songs in a {@code ListView}. + * + * @author Sindre Mehus + */ +public class SongView extends LinearLayout implements Checkable { + + private static final String TAG = SongView.class.getSimpleName(); + private static final WeakHashMap INSTANCES = new WeakHashMap(); + private static Handler handler; + + private CheckedTextView checkedTextView; + private ImageView starImageView; + private TextView titleTextView; + private TextView artistTextView; + private TextView durationTextView; + private TextView statusTextView; + private MusicDirectory.Entry song; + + public SongView(Context context) { + super(context); + LayoutInflater.from(context).inflate(R.layout.song_list_item, this, true); + + checkedTextView = (CheckedTextView) findViewById(R.id.song_check); + starImageView = (ImageView) findViewById(R.id.song_star); + titleTextView = (TextView) findViewById(R.id.song_title); + artistTextView = (TextView) findViewById(R.id.song_artist); + durationTextView = (TextView) findViewById(R.id.song_duration); + statusTextView = (TextView) findViewById(R.id.song_status); + + INSTANCES.put(this, null); + int instanceCount = INSTANCES.size(); + + if (instanceCount > 50) { + Log.w(TAG, instanceCount + " live SongView instances"); + } + + startUpdater(); + } + + public void setSong(final MusicDirectory.Entry song, boolean checkable) { + this.song = song; + StringBuilder artist = new StringBuilder(40); + + String bitRate = null; + if (song.getBitRate() != null) { + bitRate = String.format(getContext().getString(R.string.song_details_kbps), song.getBitRate()); + } + + String fileFormat = null; + if (song.getTranscodedSuffix() != null && !song.getTranscodedSuffix().equals(song.getSuffix())) { + fileFormat = String.format("%s > %s", song.getSuffix(), song.getTranscodedSuffix()); + } else { + fileFormat = song.getSuffix(); + } + + artist.append(song.getArtist()).append(" (") + .append(String.format(getContext().getString(R.string.song_details_all), bitRate == null ? "" : bitRate, fileFormat)) + .append(")"); + + titleTextView.setText(song.getTitle()); + artistTextView.setText(artist); + durationTextView.setText(Util.formatDuration(song.getDuration())); + starImageView.setImageDrawable(song.getStarred() ? getResources().getDrawable(R.drawable.star) : getResources().getDrawable(R.drawable.star_hollow)); + checkedTextView.setVisibility(checkable && !song.isVideo() ? View.VISIBLE : View.GONE); + + starImageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + final boolean isStarred = song.getStarred(); + final String id = song.getId(); + + if (!isStarred) { + starImageView.setImageDrawable(getResources().getDrawable(R.drawable.star)); + song.setStarred(true); + } else { + starImageView.setImageDrawable(getResources().getDrawable(R.drawable.star_hollow)); + song.setStarred(false); + } + + new Thread(new Runnable() { + public void run() { + MusicService musicService = MusicServiceFactory.getMusicService(null); + + try { + if (!isStarred) { + musicService.star(id, getContext(), null); + } else { + musicService.unstar(id, getContext(), null); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }).start(); + } + }); + + update(); + } + + private void update() { + DownloadService downloadService = DownloadServiceImpl.getInstance(); + if (downloadService == null) { + return; + } + + DownloadFile downloadFile = downloadService.forSong(song); + File completeFile = downloadFile.getCompleteFile(); + File partialFile = downloadFile.getPartialFile(); + + int leftImage = 0; + int rightImage = 0; + + if (completeFile.exists()) { + leftImage = downloadFile.isSaved() ? R.drawable.ic_stat_saved : R.drawable.ic_stat_downloaded; + } + + if (downloadFile.isDownloading() && !downloadFile.isDownloadCancelled() && partialFile.exists()) { + statusTextView.setText(Util.formatLocalizedBytes(partialFile.length(), getContext())); + rightImage = R.drawable.ic_stat_downloading; + } else { + statusTextView.setText(null); + } + statusTextView.setCompoundDrawablesWithIntrinsicBounds(leftImage, 0, rightImage, 0); + + boolean playing = downloadService.getCurrentPlaying() == downloadFile; + if (playing) { + titleTextView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_stat_play, 0, 0, 0); + } else { + titleTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + } + } + + private static synchronized void startUpdater() { + if (handler != null) { + return; + } + + handler = new Handler(); + Runnable runnable = new Runnable() { + @Override + public void run() { + updateAll(); + handler.postDelayed(this, 1000L); + } + }; + handler.postDelayed(runnable, 1000L); + } + + private static void updateAll() { + try { + for (SongView view : INSTANCES.keySet()) { + if (view.isShown()) { + view.update(); + } + } + } catch (Throwable x) { + Log.w(TAG, "Error when updating song views.", x); + } + } + + @Override + public void setChecked(boolean b) { + checkedTextView.setChecked(b); + } + + @Override + public boolean isChecked() { + return checkedTextView.isChecked(); + } + + @Override + public void toggle() { + checkedTextView.toggle(); + } +} diff --git a/src/net/sourceforge/subsonic/androidapp/util/Util.java b/src/net/sourceforge/subsonic/androidapp/util/Util.java index 62773909..3394ceac 100644 --- a/src/net/sourceforge/subsonic/androidapp/util/Util.java +++ b/src/net/sourceforge/subsonic/androidapp/util/Util.java @@ -102,7 +102,10 @@ public class Util extends DownloadActivity { } public static boolean isOffline(Context context) { - return getActiveServer(context) == 0; + if (context == null) + return false; + else + return getActiveServer(context) == 0; } public static boolean isScreenLitOnDownload(Context context) {