commit
e31ba2084c
|
@ -65,7 +65,7 @@
|
|||
android:exported="false"/>
|
||||
|
||||
<!-- Services -->
|
||||
<service android:name="org.xbmc.kore.service.LibrarySyncService"
|
||||
<service android:name=".service.library.LibrarySyncService"
|
||||
android:exported="false"/>
|
||||
<service android:name="org.xbmc.kore.service.NotificationService"
|
||||
android:exported="false"/>
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.jsonrpc;
|
||||
|
||||
import org.xbmc.kore.jsonrpc.type.ListType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ApiList<T> {
|
||||
public final List<T> items;
|
||||
public final ListType.LimitsReturned limits;
|
||||
|
||||
public ApiList(List<T> items, ListType.LimitsReturned limits) {
|
||||
this.items = items;
|
||||
this.limits = limits;
|
||||
}
|
||||
}
|
|
@ -17,6 +17,8 @@ package org.xbmc.kore.jsonrpc.event;
|
|||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.xbmc.kore.service.library.LibrarySyncService;
|
||||
|
||||
/**
|
||||
* Event to post on {@link de.greenrobot.event.EventBus} that notifies of a sync
|
||||
*/
|
||||
|
@ -33,7 +35,7 @@ public class MediaSyncEvent {
|
|||
/**
|
||||
* Creates a new sync event
|
||||
*
|
||||
* @param syncType One of the constants in {@link org.xbmc.kore.service.LibrarySyncService}
|
||||
* @param syncType One of the constants in {@link LibrarySyncService}
|
||||
*/
|
||||
public MediaSyncEvent(String syncType, Bundle syncExtras, int status) {
|
||||
this(syncType, syncExtras, status, -1, null);
|
||||
|
|
|
@ -18,7 +18,9 @@ package org.xbmc.kore.jsonrpc.method;
|
|||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import org.xbmc.kore.jsonrpc.ApiException;
|
||||
import org.xbmc.kore.jsonrpc.ApiList;
|
||||
import org.xbmc.kore.jsonrpc.ApiMethod;
|
||||
import org.xbmc.kore.jsonrpc.type.AudioType;
|
||||
import org.xbmc.kore.jsonrpc.type.LibraryType;
|
||||
|
@ -83,7 +85,7 @@ public class AudioLibrary {
|
|||
/**
|
||||
* Retrieve all artists
|
||||
*/
|
||||
public static class GetArtists extends ApiMethod<List<AudioType.DetailsArtist>> {
|
||||
public static class GetArtists extends ApiMethod<ApiList<AudioType.DetailsArtist>> {
|
||||
public final static String METHOD_NAME = "AudioLibrary.GetArtists";
|
||||
|
||||
private final static String LIST_NODE = "artists";
|
||||
|
@ -126,28 +128,29 @@ public class AudioLibrary {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<AudioType.DetailsArtist> resultFromJson(ObjectNode jsonObject)
|
||||
throws ApiException {
|
||||
public ApiList<AudioType.DetailsArtist> resultFromJson(ObjectNode jsonObject) throws ApiException {
|
||||
ListType.LimitsReturned limits = new ListType.LimitsReturned(jsonObject);
|
||||
|
||||
JsonNode resultNode = jsonObject.get(RESULT_NODE);
|
||||
ArrayNode items = resultNode.has(LIST_NODE) ?
|
||||
(ArrayNode)resultNode.get(LIST_NODE) : null;
|
||||
(ArrayNode)resultNode.get(LIST_NODE) : null;
|
||||
if (items == null) {
|
||||
return new ArrayList<AudioType.DetailsArtist>(0);
|
||||
return new ApiList<>(new ArrayList<AudioType.DetailsArtist>(0), limits);
|
||||
}
|
||||
ArrayList<AudioType.DetailsArtist> result = new ArrayList<AudioType.DetailsArtist>(items.size());
|
||||
ArrayList<AudioType.DetailsArtist> result = new ArrayList<>(items.size());
|
||||
|
||||
for (JsonNode item : items) {
|
||||
result.add(new AudioType.DetailsArtist(item));
|
||||
}
|
||||
|
||||
return result;
|
||||
return new ApiList<>(result, limits);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all albums from specified artist or genre
|
||||
*/
|
||||
public static class GetAlbums extends ApiMethod<List<AudioType.DetailsAlbum>> {
|
||||
public static class GetAlbums extends ApiMethod<ApiList<AudioType.DetailsAlbum>> {
|
||||
public final static String METHOD_NAME = "AudioLibrary.GetAlbums";
|
||||
|
||||
private final static String LIST_NODE = "albums";
|
||||
|
@ -182,20 +185,22 @@ public class AudioLibrary {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<AudioType.DetailsAlbum> resultFromJson(ObjectNode jsonObject)
|
||||
public ApiList<AudioType.DetailsAlbum> resultFromJson(ObjectNode jsonObject)
|
||||
throws ApiException {
|
||||
ListType.LimitsReturned limits = new ListType.LimitsReturned(jsonObject);
|
||||
|
||||
JsonNode resultNode = jsonObject.get(RESULT_NODE);
|
||||
ArrayNode items = resultNode.has(LIST_NODE) ?
|
||||
(ArrayNode)resultNode.get(LIST_NODE) : null;
|
||||
if (items == null) {
|
||||
return new ArrayList<AudioType.DetailsAlbum>(0);
|
||||
return new ApiList<>(new ArrayList<AudioType.DetailsAlbum>(0), limits);
|
||||
}
|
||||
ArrayList<AudioType.DetailsAlbum> result = new ArrayList<AudioType.DetailsAlbum>(items.size());
|
||||
ArrayList<AudioType.DetailsAlbum> result = new ArrayList<>(items.size());
|
||||
for (JsonNode item : items) {
|
||||
result.add(new AudioType.DetailsAlbum(item));
|
||||
}
|
||||
|
||||
return result;
|
||||
return new ApiList<>(result, limits);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,7 +249,7 @@ public class AudioLibrary {
|
|||
/**
|
||||
* Retrieve all songs from specified album, artist or genre
|
||||
*/
|
||||
public static class GetSongs extends ApiMethod<List<AudioType.DetailsSong>> {
|
||||
public static class GetSongs extends ApiMethod<ApiList<AudioType.DetailsSong>> {
|
||||
public final static String METHOD_NAME = "AudioLibrary.GetSongs";
|
||||
|
||||
private final static String LIST_NODE = "songs";
|
||||
|
@ -279,21 +284,22 @@ public class AudioLibrary {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<AudioType.DetailsSong> resultFromJson(ObjectNode jsonObject)
|
||||
public ApiList<AudioType.DetailsSong> resultFromJson(ObjectNode jsonObject)
|
||||
throws ApiException {
|
||||
JsonNode resultNode = jsonObject.get(RESULT_NODE);
|
||||
ListType.LimitsReturned limits = new ListType.LimitsReturned(jsonObject);
|
||||
|
||||
JsonNode resultNode = jsonObject.get(RESULT_NODE);
|
||||
ArrayNode items = resultNode.has(LIST_NODE) ?
|
||||
(ArrayNode)resultNode.get(LIST_NODE) : null;
|
||||
if (items == null) {
|
||||
return new ArrayList<AudioType.DetailsSong>(0);
|
||||
return new ApiList<>(new ArrayList<AudioType.DetailsSong>(0), limits);
|
||||
}
|
||||
ArrayList<AudioType.DetailsSong> result = new ArrayList<AudioType.DetailsSong>(items.size());
|
||||
ArrayList<AudioType.DetailsSong> result = new ArrayList<>(items.size());
|
||||
for (JsonNode item : items) {
|
||||
result.add(new AudioType.DetailsSong(item));
|
||||
}
|
||||
|
||||
return result;
|
||||
return new ApiList<>(result, limits);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.xbmc.kore.jsonrpc.type;
|
|||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import org.xbmc.kore.utils.JsonUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -342,6 +343,31 @@ public class ListType {
|
|||
}
|
||||
}
|
||||
|
||||
public static class LimitsReturned {
|
||||
public int start = -1;
|
||||
public int end = -1;
|
||||
public int total = -1;
|
||||
|
||||
public LimitsReturned(ObjectNode jsonNode) {
|
||||
JsonNode resultNode = jsonNode.get("result");
|
||||
JsonNode item = resultNode.has("limits") ? resultNode.get("limits") : null;
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
start = JsonUtils.intFromJsonNode(item, "start");
|
||||
end = JsonUtils.intFromJsonNode(item, "end");
|
||||
total = JsonUtils.intFromJsonNode(item, "total");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() +
|
||||
", start="+start+
|
||||
", end="+end+
|
||||
", total="+total;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enums for List.Fields.All
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* Copyright 2015 Synced Synapse. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.xbmc.kore.service.library;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.Process;
|
||||
|
||||
import org.xbmc.kore.host.HostInfo;
|
||||
import org.xbmc.kore.host.HostManager;
|
||||
import org.xbmc.kore.jsonrpc.HostConnection;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
import org.xbmc.kore.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Service that syncs the XBMC local database with the remote library
|
||||
*/
|
||||
public class LibrarySyncService extends Service {
|
||||
public static final String TAG = LogUtils.makeLogTag(LibrarySyncService.class);
|
||||
|
||||
/**
|
||||
* Possible requests to sync
|
||||
*/
|
||||
public static final String SYNC_ALL_MOVIES = "sync_all_movies";
|
||||
public static final String SYNC_SINGLE_MOVIE = "sync_single_movie";
|
||||
public static final String SYNC_ALL_TVSHOWS = "sync_all_tvshows";
|
||||
public static final String SYNC_SINGLE_TVSHOW = "sync_single_tvshow";
|
||||
public static final String SYNC_ALL_MUSIC = "sync_all_music";
|
||||
public static final String SYNC_ALL_MUSIC_VIDEOS = "sync_all_music_videos";
|
||||
|
||||
public static final String SYNC_MOVIEID = "sync_movieid";
|
||||
public static final String SYNC_TVSHOWID = "sync_tvshowid";
|
||||
|
||||
/**
|
||||
* Extra used to pass parameters that will be sent back to the caller
|
||||
*/
|
||||
public static final String SYNC_EXTRAS = "sync_extras";
|
||||
|
||||
/**
|
||||
* Constant for UI to use to signal a silent sync (pass these in SYNC_EXTRAS)
|
||||
*/
|
||||
public static final String SILENT_SYNC = "silent_sync";
|
||||
|
||||
/**
|
||||
* Our handler to post callbacks from {@link HostConnection} calls
|
||||
*/
|
||||
private Handler callbackHandler;
|
||||
private HandlerThread handlerThread;
|
||||
|
||||
private ArrayList<SyncOrchestrator> syncOrchestrators;
|
||||
|
||||
private final IBinder serviceBinder = new LocalBinder();
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
// Create a Handler Thread to process callback calls after the Xbmc method call
|
||||
handlerThread = new HandlerThread("LibrarySyncService", Process.THREAD_PRIORITY_BACKGROUND);
|
||||
handlerThread.start();
|
||||
|
||||
// Get the HandlerThread's Looper and use it for our Handler
|
||||
callbackHandler = new Handler(handlerThread.getLooper());
|
||||
// Check which libraries to update and call the corresponding methods on Xbmc
|
||||
|
||||
syncOrchestrators = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, final int startId) {
|
||||
// Get the connection here, not on create because we can be called for different hosts
|
||||
// We'll use a specific connection through HTTP, not the singleton one,
|
||||
// to not interfere with the normal application usage of it (namely calls to disconnect
|
||||
// and usage of the socket).
|
||||
HostInfo hostInfo = HostManager.getInstance(this).getHostInfo();
|
||||
|
||||
SyncOrchestrator syncOrchestrator = new SyncOrchestrator(this, startId, hostInfo,
|
||||
callbackHandler, getContentResolver());
|
||||
syncOrchestrator.setListener(new SyncOrchestrator.OnSyncListener() {
|
||||
@Override
|
||||
public void onSyncFinished(SyncOrchestrator syncOrchestrator) {
|
||||
stopSelf(startId);
|
||||
}
|
||||
});
|
||||
|
||||
syncOrchestrators.add(syncOrchestrator);
|
||||
|
||||
// Get the request parameters that we should pass when calling back the caller
|
||||
Bundle syncExtras = intent.getBundleExtra(SYNC_EXTRAS);
|
||||
|
||||
// Sync all movies
|
||||
boolean syncAllMovies = intent.getBooleanExtra(SYNC_ALL_MOVIES, false);
|
||||
if (syncAllMovies) {
|
||||
syncOrchestrator.addSyncItem(new SyncMovies(hostInfo.getId(), syncExtras));
|
||||
}
|
||||
|
||||
// Sync a single movie
|
||||
boolean syncSingleMovie = intent.getBooleanExtra(SYNC_SINGLE_MOVIE, false);
|
||||
if (syncSingleMovie) {
|
||||
int movieId = intent.getIntExtra(SYNC_MOVIEID, -1);
|
||||
if (movieId != -1) {
|
||||
syncOrchestrator.addSyncItem(new SyncMovies(hostInfo.getId(), movieId, syncExtras));
|
||||
}
|
||||
}
|
||||
|
||||
// Sync all tvshows
|
||||
boolean syncAllTVShows = intent.getBooleanExtra(SYNC_ALL_TVSHOWS, false);
|
||||
if (syncAllTVShows) {
|
||||
syncOrchestrator.addSyncItem(new SyncTVShows(hostInfo.getId(), syncExtras));
|
||||
}
|
||||
|
||||
// Sync a single tvshow
|
||||
boolean syncSingleTVShow = intent.getBooleanExtra(SYNC_SINGLE_TVSHOW, false);
|
||||
if (syncSingleTVShow) {
|
||||
int tvshowId = intent.getIntExtra(SYNC_TVSHOWID, -1);
|
||||
if (tvshowId != -1) {
|
||||
syncOrchestrator.addSyncItem(new SyncTVShows(hostInfo.getId(), tvshowId, syncExtras));
|
||||
}
|
||||
}
|
||||
|
||||
// Sync all music
|
||||
boolean syncAllMusic = intent.getBooleanExtra(SYNC_ALL_MUSIC, false);
|
||||
if (syncAllMusic) {
|
||||
syncOrchestrator.addSyncItem(new SyncMusic(hostInfo.getId(), syncExtras));
|
||||
}
|
||||
|
||||
// Sync all music videos
|
||||
boolean syncAllMusicVideos = intent.getBooleanExtra(SYNC_ALL_MUSIC_VIDEOS, false);
|
||||
if (syncAllMusicVideos) {
|
||||
syncOrchestrator.addSyncItem(new SyncMusicVideos(hostInfo.getId(), syncExtras));
|
||||
}
|
||||
|
||||
// Start syncing
|
||||
syncOrchestrator.startSync();
|
||||
|
||||
// If we get killed, after returning from here, don't restart
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return serviceBinder;
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
LogUtils.LOGD(TAG, "Destroying the service.");
|
||||
if (Utils.isJellybeanMR2OrLater()) {
|
||||
handlerThread.quitSafely();
|
||||
} else {
|
||||
handlerThread.quit();
|
||||
}
|
||||
}
|
||||
|
||||
public class LocalBinder extends Binder {
|
||||
public LibrarySyncService getService() {
|
||||
return LibrarySyncService.this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param hostInfo host information for which to get items currently syncing
|
||||
* @return currently syncing syncitems for given hostInfo
|
||||
*/
|
||||
public ArrayList<SyncItem> getItemsSyncing(HostInfo hostInfo) {
|
||||
ArrayList<SyncItem> syncItems = new ArrayList<>();
|
||||
for( SyncOrchestrator orchestrator : syncOrchestrators) {
|
||||
if( orchestrator.getHostInfo().getId() == hostInfo.getId() ) {
|
||||
syncItems.addAll(orchestrator.getSyncItems());
|
||||
return syncItems;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2015 Synced Synapse. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.service.library;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
|
||||
import org.xbmc.kore.jsonrpc.HostConnection;
|
||||
|
||||
/**
|
||||
* Represent an item that can be synced
|
||||
*/
|
||||
public abstract class SyncItem {
|
||||
/**
|
||||
* Syncs an item from the XBMC host to the local database
|
||||
* @param orchestrator Orchestrator to call when finished
|
||||
* @param hostConnection Host connection to use
|
||||
* @param callbackHandler Handler on which to post callbacks
|
||||
* @param contentResolver Content resolver
|
||||
*/
|
||||
abstract public void sync(final SyncOrchestrator orchestrator,
|
||||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver);
|
||||
|
||||
/**
|
||||
* Friendly description of this sync item
|
||||
* @return Description
|
||||
*/
|
||||
abstract public String getDescription();
|
||||
|
||||
/**
|
||||
* Returns the sync event that should be posted after completion
|
||||
* @return Sync type, one of the constants in {@link LibrarySyncService}
|
||||
*/
|
||||
abstract public String getSyncType();
|
||||
|
||||
/**
|
||||
* Returns the extras that were passed during creation.
|
||||
* Allows the caller to pass parameters that will be sent back to him
|
||||
* @return Sync extras passed during construction
|
||||
*/
|
||||
abstract public Bundle getSyncExtras();
|
||||
}
|
|
@ -0,0 +1,239 @@
|
|||
/*
|
||||
* Copyright 2015 Synced Synapse. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.service.library;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
|
||||
import org.xbmc.kore.jsonrpc.ApiCallback;
|
||||
import org.xbmc.kore.jsonrpc.HostConnection;
|
||||
import org.xbmc.kore.jsonrpc.method.VideoLibrary;
|
||||
import org.xbmc.kore.jsonrpc.type.ListType;
|
||||
import org.xbmc.kore.jsonrpc.type.VideoType;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SyncMovies extends SyncItem {
|
||||
public static final String TAG = LogUtils.makeLogTag(SyncMovies.class);
|
||||
|
||||
private static final int LIMIT_SYNC_MOVIES = 300;
|
||||
|
||||
private final int hostId;
|
||||
private final int movieId;
|
||||
private final Bundle syncExtras;
|
||||
|
||||
/**
|
||||
* Syncs all the movies on selected XBMC to the local database
|
||||
* @param hostId XBMC host id
|
||||
*/
|
||||
public SyncMovies(final int hostId, Bundle syncExtras) {
|
||||
this.hostId = hostId;
|
||||
this.movieId = -1;
|
||||
this.syncExtras = syncExtras;
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs a specific movie on selected XBMC to the local database
|
||||
* @param hostId XBMC host id
|
||||
*/
|
||||
public SyncMovies(final int hostId, final int movieId, Bundle syncExtras) {
|
||||
this.hostId = hostId;
|
||||
this.movieId = movieId;
|
||||
this.syncExtras = syncExtras;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public String getDescription() {
|
||||
return (movieId != -1) ?
|
||||
"Sync movies for host: " + hostId :
|
||||
"Sync movie " + movieId + " for host: " + hostId;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public String getSyncType() {
|
||||
return (movieId == -1) ? LibrarySyncService.SYNC_ALL_MOVIES
|
||||
: LibrarySyncService.SYNC_SINGLE_MOVIE;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public Bundle getSyncExtras() {
|
||||
return syncExtras;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void sync(final SyncOrchestrator orchestrator,
|
||||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver) {
|
||||
String properties[] = {
|
||||
VideoType.FieldsMovie.TITLE, VideoType.FieldsMovie.GENRE,
|
||||
VideoType.FieldsMovie.YEAR, VideoType.FieldsMovie.RATING,
|
||||
VideoType.FieldsMovie.DIRECTOR, VideoType.FieldsMovie.TRAILER,
|
||||
VideoType.FieldsMovie.TAGLINE, VideoType.FieldsMovie.PLOT,
|
||||
// VideoType.FieldsMovie.PLOTOUTLINE, VideoType.FieldsMovie.ORIGINALTITLE,
|
||||
// VideoType.FieldsMovie.LASTPLAYED,
|
||||
VideoType.FieldsMovie.PLAYCOUNT, VideoType.FieldsMovie.DATEADDED,
|
||||
VideoType.FieldsMovie.WRITER, VideoType.FieldsMovie.STUDIO,
|
||||
VideoType.FieldsMovie.MPAA, VideoType.FieldsMovie.CAST,
|
||||
VideoType.FieldsMovie.COUNTRY, VideoType.FieldsMovie.IMDBNUMBER,
|
||||
VideoType.FieldsMovie.RUNTIME, VideoType.FieldsMovie.SET,
|
||||
// VideoType.FieldsMovie.SHOWLINK,
|
||||
VideoType.FieldsMovie.STREAMDETAILS, VideoType.FieldsMovie.TOP250,
|
||||
VideoType.FieldsMovie.VOTES, VideoType.FieldsMovie.FANART,
|
||||
VideoType.FieldsMovie.THUMBNAIL, VideoType.FieldsMovie.FILE,
|
||||
// VideoType.FieldsMovie.SORTTITLE, VideoType.FieldsMovie.RESUME,
|
||||
VideoType.FieldsMovie.SETID,
|
||||
// VideoType.FieldsMovie.DATEADDED, VideoType.FieldsMovie.TAG,
|
||||
// VideoType.FieldsMovie.ART
|
||||
};
|
||||
|
||||
if (movieId == -1) {
|
||||
syncAllMovies(orchestrator, hostConnection, callbackHandler, contentResolver, properties, 0);
|
||||
} else {
|
||||
// Sync a specific movie
|
||||
VideoLibrary.GetMovieDetails action =
|
||||
new VideoLibrary.GetMovieDetails(movieId, properties);
|
||||
action.execute(hostConnection, new ApiCallback<VideoType.DetailsMovie>() {
|
||||
@Override
|
||||
public void onSuccess(VideoType.DetailsMovie result) {
|
||||
deleteMovies(contentResolver, hostId, movieId);
|
||||
List<VideoType.DetailsMovie> movies = new ArrayList<VideoType.DetailsMovie>(1);
|
||||
movies.add(result);
|
||||
insertMovies(orchestrator, contentResolver, movies);
|
||||
orchestrator.syncItemFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, String description) {
|
||||
// Ok, something bad happend, just quit
|
||||
orchestrator.syncItemFailed(errorCode, description);
|
||||
}
|
||||
}, callbackHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs all the movies, calling itself recursively
|
||||
* Uses the {@link VideoLibrary.GetMovies} version with limits to make sure
|
||||
* that Kodi doesn't blow up, and calls itself recursively until all the
|
||||
* movies are returned
|
||||
*/
|
||||
private void syncAllMovies(final SyncOrchestrator orchestrator,
|
||||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver,
|
||||
final String properties[],
|
||||
final int startIdx) {
|
||||
// Call GetMovies with the current limits set
|
||||
ListType.Limits limits = new ListType.Limits(startIdx, startIdx + LIMIT_SYNC_MOVIES);
|
||||
VideoLibrary.GetMovies action = new VideoLibrary.GetMovies(limits, properties);
|
||||
action.execute(hostConnection, new ApiCallback<List<VideoType.DetailsMovie>>() {
|
||||
@Override
|
||||
public void onSuccess(List<VideoType.DetailsMovie> result) {
|
||||
if (startIdx == 0) {
|
||||
// First call, delete movies from DB
|
||||
deleteMovies(contentResolver, hostId, -1);
|
||||
}
|
||||
if (result.size() > 0) {
|
||||
insertMovies(orchestrator, contentResolver, result);
|
||||
}
|
||||
|
||||
LogUtils.LOGD(TAG, "syncAllMovies, movies gotten: " + result.size());
|
||||
if (result.size() == LIMIT_SYNC_MOVIES) {
|
||||
// Max limit returned, there may be some more movies
|
||||
// As we're going to recurse, these result objects can add up, so
|
||||
// let's help the GC and indicate that we don't need this memory
|
||||
// (hopefully this works)
|
||||
result = null;
|
||||
syncAllMovies(orchestrator, hostConnection, callbackHandler, contentResolver,
|
||||
properties, startIdx + LIMIT_SYNC_MOVIES);
|
||||
} else {
|
||||
// Less than the limit was returned so we can finish
|
||||
// (if it returned more there's a bug in Kodi but it
|
||||
// shouldn't be a problem as they got inserted in the DB)
|
||||
orchestrator.syncItemFinished();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, String description) {
|
||||
// Ok, something bad happend, just quit
|
||||
orchestrator.syncItemFailed(errorCode, description);
|
||||
}
|
||||
}, callbackHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes one or all movies from the database (pass -1 on movieId to delete all)
|
||||
*/
|
||||
private void deleteMovies(final ContentResolver contentResolver,
|
||||
int hostId, int movieId) {
|
||||
if (movieId == -1) {
|
||||
// Delete all movies
|
||||
String where = MediaContract.MoviesColumns.HOST_ID + "=?";
|
||||
contentResolver.delete(MediaContract.MovieCast.CONTENT_URI,
|
||||
where, new String[]{String.valueOf(hostId)});
|
||||
contentResolver.delete(MediaContract.Movies.CONTENT_URI,
|
||||
where, new String[]{String.valueOf(hostId)});
|
||||
} else {
|
||||
// Delete a movie
|
||||
contentResolver.delete(MediaContract.MovieCast.buildMovieCastListUri(hostId, movieId),
|
||||
null, null);
|
||||
contentResolver.delete(MediaContract.Movies.buildMovieUri(hostId, movieId),
|
||||
null, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given movies in the database
|
||||
*/
|
||||
private void insertMovies(final SyncOrchestrator orchestrator,
|
||||
final ContentResolver contentResolver,
|
||||
final List<VideoType.DetailsMovie> movies) {
|
||||
ContentValues movieValuesBatch[] = new ContentValues[movies.size()];
|
||||
int castCount = 0;
|
||||
|
||||
// Iterate on each movie
|
||||
for (int i = 0; i < movies.size(); i++) {
|
||||
VideoType.DetailsMovie movie = movies.get(i);
|
||||
movieValuesBatch[i] = SyncUtils.contentValuesFromMovie(hostId, movie);
|
||||
castCount += movie.cast.size();
|
||||
}
|
||||
|
||||
// Insert the movies
|
||||
contentResolver.bulkInsert(MediaContract.Movies.CONTENT_URI, movieValuesBatch);
|
||||
|
||||
ContentValues movieCastValuesBatch[] = new ContentValues[castCount];
|
||||
int count = 0;
|
||||
// Iterate on each movie/cast
|
||||
for (VideoType.DetailsMovie movie : movies) {
|
||||
for (VideoType.Cast cast : movie.cast) {
|
||||
movieCastValuesBatch[count] = SyncUtils.contentValuesFromCast(hostId, cast);
|
||||
movieCastValuesBatch[count].put(MediaContract.MovieCastColumns.MOVIEID, movie.movieid);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the cast list for this movie
|
||||
contentResolver.bulkInsert(MediaContract.MovieCast.CONTENT_URI, movieCastValuesBatch);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,383 @@
|
|||
/*
|
||||
* Copyright 2015 Synced Synapse. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.service.library;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
|
||||
import org.xbmc.kore.jsonrpc.ApiCallback;
|
||||
import org.xbmc.kore.jsonrpc.ApiList;
|
||||
import org.xbmc.kore.jsonrpc.HostConnection;
|
||||
import org.xbmc.kore.jsonrpc.method.AudioLibrary;
|
||||
import org.xbmc.kore.jsonrpc.type.AudioType;
|
||||
import org.xbmc.kore.jsonrpc.type.LibraryType;
|
||||
import org.xbmc.kore.jsonrpc.type.ListType;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SyncMusic extends SyncItem {
|
||||
public static final String TAG = LogUtils.makeLogTag(SyncMusic.class);
|
||||
|
||||
private static final int LIMIT_SYNC_ARTISTS = 300;
|
||||
private static final int LIMIT_SYNC_ALBUMS = 300;
|
||||
private static final int LIMIT_SYNC_SONGS = 600;
|
||||
|
||||
private final int hostId;
|
||||
private final Bundle syncExtras;
|
||||
|
||||
/**
|
||||
* Syncs all the music on selected XBMC to the local database
|
||||
* @param hostId XBMC host id
|
||||
*/
|
||||
public SyncMusic(final int hostId, Bundle syncExtras) {
|
||||
this.hostId = hostId;
|
||||
this.syncExtras = syncExtras;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public String getDescription() {
|
||||
return "Sync music for host: " + hostId;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public String getSyncType() { return LibrarySyncService.SYNC_ALL_MUSIC; }
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public Bundle getSyncExtras() {
|
||||
return syncExtras;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void sync(final SyncOrchestrator orchestrator,
|
||||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver) {
|
||||
chainCallSyncArtists(orchestrator, hostConnection, callbackHandler, contentResolver, 0);
|
||||
}
|
||||
|
||||
private final static String getArtistsProperties[] = {
|
||||
// AudioType.FieldsArtists.INSTRUMENT, AudioType.FieldsArtists.STYLE,
|
||||
// AudioType.FieldsArtists.MOOD, AudioType.FieldsArtists.BORN,
|
||||
// AudioType.FieldsArtists.FORMED,
|
||||
AudioType.FieldsArtists.DESCRIPTION,
|
||||
AudioType.FieldsArtists.GENRE,
|
||||
// AudioType.FieldsArtists.DIED,
|
||||
// AudioType.FieldsArtists.DISBANDED, AudioType.FieldsArtists.YEARSACTIVE,
|
||||
//AudioType.FieldsArtists.MUSICBRAINZARTISTID,
|
||||
AudioType.FieldsArtists.FANART,
|
||||
AudioType.FieldsArtists.THUMBNAIL
|
||||
};
|
||||
/**
|
||||
* Gets all artists recursively and forwards the call to Genres
|
||||
* Genres->Albums->Songs
|
||||
*/
|
||||
public void chainCallSyncArtists(final SyncOrchestrator orchestrator,
|
||||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver,
|
||||
final int startIdx) {
|
||||
// Artists->Genres->Albums->Songs
|
||||
// Only gets album artists (first parameter)
|
||||
ListType.Limits limits = new ListType.Limits(startIdx, startIdx + LIMIT_SYNC_ARTISTS);
|
||||
AudioLibrary.GetArtists action = new AudioLibrary.GetArtists(limits, true, getArtistsProperties);
|
||||
action.execute(hostConnection, new ApiCallback<ApiList<AudioType.DetailsArtist>>() {
|
||||
@Override
|
||||
public void onSuccess(ApiList<AudioType.DetailsArtist> result) {
|
||||
List<AudioType.DetailsArtist> items;
|
||||
ListType.LimitsReturned limitsReturned;
|
||||
if (result == null) { // Safeguard
|
||||
items = new ArrayList<>(0);
|
||||
limitsReturned = null;
|
||||
} else {
|
||||
items = result.items;
|
||||
limitsReturned = result.limits;
|
||||
}
|
||||
|
||||
// First delete all music info
|
||||
if (startIdx == 0) deleteMusicInfo(contentResolver, hostId);
|
||||
|
||||
// Insert artists
|
||||
ContentValues artistValuesBatch[] = new ContentValues[items.size()];
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
AudioType.DetailsArtist artist = items.get(i);
|
||||
artistValuesBatch[i] = SyncUtils.contentValuesFromArtist(hostId, artist);
|
||||
}
|
||||
contentResolver.bulkInsert(MediaContract.Artists.CONTENT_URI, artistValuesBatch);
|
||||
|
||||
if (moreItemsAvailable(limitsReturned)) {
|
||||
LogUtils.LOGD(TAG, "chainCallSyncArtists: More results on media center, recursing.");
|
||||
result = null; // Help the GC?
|
||||
chainCallSyncArtists(orchestrator, hostConnection, callbackHandler, contentResolver,
|
||||
startIdx + LIMIT_SYNC_ARTISTS);
|
||||
} else {
|
||||
// Ok, we have all the artists, proceed
|
||||
LogUtils.LOGD(TAG, "chainCallSyncArtists: Got all results, continuing");
|
||||
chainCallSyncGenres(orchestrator, hostConnection, callbackHandler, contentResolver);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, String description) {
|
||||
// Ok, something bad happend, just quit
|
||||
orchestrator.syncItemFailed(errorCode, description);
|
||||
}
|
||||
}, callbackHandler);
|
||||
}
|
||||
|
||||
private void deleteMusicInfo(final ContentResolver contentResolver,
|
||||
int hostId) {
|
||||
// Delete music info
|
||||
String where = MediaContract.Artists.HOST_ID + "=?";
|
||||
contentResolver.delete(MediaContract.AlbumArtists.CONTENT_URI,
|
||||
where, new String[]{String.valueOf(hostId)});
|
||||
contentResolver.delete(MediaContract.AlbumGenres.CONTENT_URI,
|
||||
where, new String[]{String.valueOf(hostId)});
|
||||
contentResolver.delete(MediaContract.Songs.CONTENT_URI,
|
||||
where, new String[]{String.valueOf(hostId)});
|
||||
contentResolver.delete(MediaContract.AudioGenres.CONTENT_URI,
|
||||
where, new String[]{String.valueOf(hostId)});
|
||||
contentResolver.delete(MediaContract.Albums.CONTENT_URI,
|
||||
where, new String[]{String.valueOf(hostId)});
|
||||
contentResolver.delete(MediaContract.Artists.CONTENT_URI,
|
||||
where, new String[]{String.valueOf(hostId)});
|
||||
}
|
||||
|
||||
|
||||
private final static String getGenresProperties[] = {
|
||||
LibraryType.FieldsGenre.TITLE, LibraryType.FieldsGenre.THUMBNAIL
|
||||
};
|
||||
/**
|
||||
* Syncs Audio genres and forwards calls to sync albums:
|
||||
* Genres->Albums->Songs
|
||||
*/
|
||||
private void chainCallSyncGenres(final SyncOrchestrator orchestrator,
|
||||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver) {
|
||||
// Genres->Albums->Songs
|
||||
AudioLibrary.GetGenres action = new AudioLibrary.GetGenres(getGenresProperties);
|
||||
action.execute(hostConnection, new ApiCallback<List<LibraryType.DetailsGenre>>() {
|
||||
@Override
|
||||
public void onSuccess(List<LibraryType.DetailsGenre> result) {
|
||||
if (result == null) result = new ArrayList<>(0); // Safeguard
|
||||
ContentValues genresValuesBatch[] = new ContentValues[result.size()];
|
||||
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
LibraryType.DetailsGenre genre = result.get(i);
|
||||
genresValuesBatch[i] = SyncUtils.contentValuesFromAudioGenre(hostId, genre);
|
||||
}
|
||||
|
||||
// Insert the genres and proceed to albums
|
||||
contentResolver.bulkInsert(MediaContract.AudioGenres.CONTENT_URI, genresValuesBatch);
|
||||
chainCallSyncAlbums(orchestrator, hostConnection, callbackHandler, contentResolver, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, String description) {
|
||||
// Ok, something bad happend, just quit
|
||||
orchestrator.syncItemFailed(errorCode, description);
|
||||
}
|
||||
}, callbackHandler);
|
||||
}
|
||||
|
||||
private static final String getAlbumsProperties[] = {
|
||||
AudioType.FieldsAlbum.TITLE, AudioType.FieldsAlbum.DESCRIPTION,
|
||||
AudioType.FieldsAlbum.ARTIST, AudioType.FieldsAlbum.GENRE,
|
||||
//AudioType.FieldsAlbum.THEME, AudioType.FieldsAlbum.MOOD,
|
||||
//AudioType.FieldsAlbum.STYLE, AudioType.FieldsAlbum.TYPE,
|
||||
AudioType.FieldsAlbum.ALBUMLABEL, AudioType.FieldsAlbum.RATING,
|
||||
AudioType.FieldsAlbum.YEAR,
|
||||
//AudioType.FieldsAlbum.MUSICBRAINZALBUMID,
|
||||
//AudioType.FieldsAlbum.MUSICBRAINZALBUMARTISTID,
|
||||
AudioType.FieldsAlbum.FANART, AudioType.FieldsAlbum.THUMBNAIL,
|
||||
AudioType.FieldsAlbum.PLAYCOUNT, AudioType.FieldsAlbum.GENREID,
|
||||
AudioType.FieldsAlbum.ARTISTID, AudioType.FieldsAlbum.DISPLAYARTIST
|
||||
};
|
||||
|
||||
/**
|
||||
* Syncs Albums recursively and forwards calls to sync songs:
|
||||
* Albums->Songs
|
||||
*/
|
||||
private void chainCallSyncAlbums(final SyncOrchestrator orchestrator,
|
||||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver,
|
||||
final int startIdx) {
|
||||
final long albumSyncStartTime = System.currentTimeMillis();
|
||||
// Albums->Songs
|
||||
ListType.Limits limits = new ListType.Limits(startIdx, startIdx + LIMIT_SYNC_ALBUMS);
|
||||
AudioLibrary.GetAlbums action = new AudioLibrary.GetAlbums(limits, getAlbumsProperties);
|
||||
action.execute(hostConnection, new ApiCallback<ApiList<AudioType.DetailsAlbum>>() {
|
||||
@Override
|
||||
public void onSuccess(ApiList<AudioType.DetailsAlbum> result) {
|
||||
List<AudioType.DetailsAlbum> items;
|
||||
ListType.LimitsReturned limitsReturned;
|
||||
if (result == null) { // Safeguard
|
||||
items = new ArrayList<>(0);
|
||||
limitsReturned = null;
|
||||
} else {
|
||||
items = result.items;
|
||||
limitsReturned = result.limits;
|
||||
}
|
||||
|
||||
// Insert the partial results
|
||||
ContentValues albumValuesBatch[] = new ContentValues[items.size()];
|
||||
int artistsCount = 0, genresCount = 0;
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
AudioType.DetailsAlbum album = items.get(i);
|
||||
albumValuesBatch[i] = SyncUtils.contentValuesFromAlbum(hostId, album);
|
||||
|
||||
artistsCount += album.artistid.size();
|
||||
genresCount += album.genreid.size();
|
||||
}
|
||||
contentResolver.bulkInsert(MediaContract.Albums.CONTENT_URI, albumValuesBatch);
|
||||
|
||||
LogUtils.LOGD(TAG, "Finished inserting albums in: " +
|
||||
(System.currentTimeMillis() - albumSyncStartTime));
|
||||
|
||||
// Iterate on each album, collect the artists and the genres and insert them
|
||||
ContentValues albumArtistsValuesBatch[] = new ContentValues[artistsCount];
|
||||
ContentValues albumGenresValuesBatch[] = new ContentValues[genresCount];
|
||||
int artistCount = 0, genreCount = 0;
|
||||
for (AudioType.DetailsAlbum album : items) {
|
||||
for (int artistId : album.artistid) {
|
||||
albumArtistsValuesBatch[artistCount] = new ContentValues();
|
||||
albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.HOST_ID, hostId);
|
||||
albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.ALBUMID, album.albumid);
|
||||
albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.ARTISTID, artistId);
|
||||
artistCount++;
|
||||
}
|
||||
|
||||
for (int genreId : album.genreid) {
|
||||
albumGenresValuesBatch[genreCount] = new ContentValues();
|
||||
albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.HOST_ID, hostId);
|
||||
albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.ALBUMID, album.albumid);
|
||||
albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.GENREID, genreId);
|
||||
genreCount++;
|
||||
}
|
||||
}
|
||||
|
||||
contentResolver.bulkInsert(MediaContract.AlbumArtists.CONTENT_URI, albumArtistsValuesBatch);
|
||||
contentResolver.bulkInsert(MediaContract.AlbumGenres.CONTENT_URI, albumGenresValuesBatch);
|
||||
|
||||
LogUtils.LOGD(TAG, "Finished inserting artists and genres in: " +
|
||||
(System.currentTimeMillis() - albumSyncStartTime));
|
||||
|
||||
if (moreItemsAvailable(limitsReturned)) {
|
||||
LogUtils.LOGD(TAG, "chainCallSyncAlbums: More results on media center, recursing.");
|
||||
result = null; // Help the GC?
|
||||
chainCallSyncAlbums(orchestrator, hostConnection, callbackHandler, contentResolver,
|
||||
startIdx + LIMIT_SYNC_ALBUMS);
|
||||
} else {
|
||||
// Ok, we have all the albums, proceed to songs
|
||||
LogUtils.LOGD(TAG, "chainCallSyncAlbums: Got all results, continuing");
|
||||
chainCallSyncSongs(orchestrator, hostConnection, callbackHandler, contentResolver, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, String description) {
|
||||
// Ok, something bad happend, just quit
|
||||
orchestrator.syncItemFailed(errorCode, description);
|
||||
}
|
||||
}, callbackHandler);
|
||||
}
|
||||
|
||||
private static final String getSongsProperties[] = {
|
||||
AudioType.FieldsSong.TITLE,
|
||||
//AudioType.FieldsSong.ARTIST, AudioType.FieldsSong.ALBUMARTIST, AudioType.FieldsSong.GENRE,
|
||||
//AudioType.FieldsSong.YEAR, AudioType.FieldsSong.RATING,
|
||||
//AudioType.FieldsSong.ALBUM,
|
||||
AudioType.FieldsSong.TRACK, AudioType.FieldsSong.DURATION,
|
||||
//AudioType.FieldsSong.COMMENT, AudioType.FieldsSong.LYRICS,
|
||||
//AudioType.FieldsSong.MUSICBRAINZTRACKID,
|
||||
//AudioType.FieldsSong.MUSICBRAINZARTISTID,
|
||||
//AudioType.FieldsSong.MUSICBRAINZALBUMID,
|
||||
//AudioType.FieldsSong.MUSICBRAINZALBUMARTISTID,
|
||||
//AudioType.FieldsSong.PLAYCOUNT, AudioType.FieldsSong.FANART,
|
||||
AudioType.FieldsSong.THUMBNAIL, AudioType.FieldsSong.FILE,
|
||||
AudioType.FieldsSong.ALBUMID,
|
||||
//AudioType.FieldsSong.LASTPLAYED, AudioType.FieldsSong.DISC,
|
||||
//AudioType.FieldsSong.GENREID, AudioType.FieldsSong.ARTISTID,
|
||||
//AudioType.FieldsSong.DISPLAYARTIST, AudioType.FieldsSong.ALBUMARTISTID
|
||||
};
|
||||
|
||||
/**
|
||||
* Syncs songs and stops
|
||||
*/
|
||||
private void chainCallSyncSongs(final SyncOrchestrator orchestrator,
|
||||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver,
|
||||
final int startIdx) {
|
||||
// Songs
|
||||
ListType.Limits limits = new ListType.Limits(startIdx, startIdx + LIMIT_SYNC_SONGS);
|
||||
AudioLibrary.GetSongs action = new AudioLibrary.GetSongs(limits, getSongsProperties);
|
||||
action.execute(hostConnection, new ApiCallback<ApiList<AudioType.DetailsSong>>() {
|
||||
@Override
|
||||
public void onSuccess(ApiList<AudioType.DetailsSong> result) {
|
||||
List<AudioType.DetailsSong> items;
|
||||
ListType.LimitsReturned limitsReturned;
|
||||
if (result == null) { // Safeguard
|
||||
items = new ArrayList<>(0);
|
||||
limitsReturned = null;
|
||||
} else {
|
||||
items = result.items;
|
||||
limitsReturned = result.limits;
|
||||
}
|
||||
|
||||
// Save partial results to DB
|
||||
ContentValues songValuesBatch[] = new ContentValues[items.size()];
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
AudioType.DetailsSong song = items.get(i);
|
||||
songValuesBatch[i] = SyncUtils.contentValuesFromSong(hostId, song);
|
||||
}
|
||||
contentResolver.bulkInsert(MediaContract.Songs.CONTENT_URI, songValuesBatch);
|
||||
|
||||
if (moreItemsAvailable(limitsReturned)) {
|
||||
LogUtils.LOGD(TAG, "chainCallSyncSongs: More results on media center, recursing.");
|
||||
result = null; // Help the GC?
|
||||
chainCallSyncSongs(orchestrator, hostConnection, callbackHandler, contentResolver,
|
||||
startIdx + LIMIT_SYNC_SONGS);
|
||||
} else {
|
||||
// Ok, we have all the songs, insert them
|
||||
LogUtils.LOGD(TAG, "chainCallSyncSongs: Got all results, continuing");
|
||||
orchestrator.syncItemFinished();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, String description) {
|
||||
// Ok, something bad happend, just quit
|
||||
orchestrator.syncItemFailed(errorCode, description);
|
||||
}
|
||||
}, callbackHandler);
|
||||
}
|
||||
|
||||
private boolean moreItemsAvailable(ListType.LimitsReturned limitsReturned) {
|
||||
boolean moreItemsAvailable = false;
|
||||
if (limitsReturned != null) {
|
||||
moreItemsAvailable = ( limitsReturned.total - limitsReturned.end ) > 0;
|
||||
}
|
||||
return moreItemsAvailable;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright 2015 Synced Synapse. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.service.library;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
|
||||
import org.xbmc.kore.jsonrpc.ApiCallback;
|
||||
import org.xbmc.kore.jsonrpc.HostConnection;
|
||||
import org.xbmc.kore.jsonrpc.method.VideoLibrary;
|
||||
import org.xbmc.kore.jsonrpc.type.VideoType;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SyncMusicVideos extends SyncItem {
|
||||
public static final String TAG = LogUtils.makeLogTag(SyncMusicVideos.class);
|
||||
|
||||
private final int hostId;
|
||||
private final Bundle syncExtras;
|
||||
|
||||
/**
|
||||
* Syncs all the music videos on XBMC, to the local database
|
||||
* @param hostId XBMC host id
|
||||
*/
|
||||
public SyncMusicVideos(final int hostId, Bundle syncExtras) {
|
||||
this.hostId = hostId;
|
||||
this.syncExtras = syncExtras;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public String getDescription() {
|
||||
return "Sync music videos for host: " + hostId;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public String getSyncType() {
|
||||
return LibrarySyncService.SYNC_ALL_MUSIC_VIDEOS;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public Bundle getSyncExtras() {
|
||||
return syncExtras;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void sync(final SyncOrchestrator orchestrator,
|
||||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver) {
|
||||
String properties[] = {
|
||||
VideoType.FieldsMusicVideo.TITLE, VideoType.FieldsMusicVideo.PLAYCOUNT,
|
||||
VideoType.FieldsMusicVideo.RUNTIME, VideoType.FieldsMusicVideo.DIRECTOR,
|
||||
VideoType.FieldsMusicVideo.STUDIO, VideoType.FieldsMusicVideo.YEAR,
|
||||
VideoType.FieldsMusicVideo.PLOT, VideoType.FieldsMusicVideo.ALBUM,
|
||||
VideoType.FieldsMusicVideo.ARTIST, VideoType.FieldsMusicVideo.GENRE,
|
||||
VideoType.FieldsMusicVideo.TRACK, VideoType.FieldsMusicVideo.STREAMDETAILS,
|
||||
//VideoType.FieldsMusicVideo.LASTPLAYED,
|
||||
VideoType.FieldsMusicVideo.FANART,
|
||||
VideoType.FieldsMusicVideo.THUMBNAIL, VideoType.FieldsMusicVideo.FILE,
|
||||
// VideoType.FieldsMusicVideo.RESUME, VideoType.FieldsMusicVideo.DATEADDED,
|
||||
VideoType.FieldsMusicVideo.TAG,
|
||||
//VideoType.FieldsMusicVideo.ART
|
||||
};
|
||||
|
||||
// Delete and sync all music videos
|
||||
VideoLibrary.GetMusicVideos action = new VideoLibrary.GetMusicVideos(properties);
|
||||
action.execute(hostConnection, new ApiCallback<List<VideoType.DetailsMusicVideo>>() {
|
||||
@Override
|
||||
public void onSuccess(List<VideoType.DetailsMusicVideo> result) {
|
||||
deleteMusicVideos(contentResolver, hostId);
|
||||
insertMusicVideos(orchestrator, contentResolver, result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, String description) {
|
||||
// Ok, something bad happend, just quit
|
||||
orchestrator.syncItemFailed(errorCode, description);
|
||||
}
|
||||
}, callbackHandler);
|
||||
}
|
||||
|
||||
private void deleteMusicVideos(final ContentResolver contentResolver, int hostId) {
|
||||
// Delete all music videos
|
||||
String where = MediaContract.MusicVideosColumns.HOST_ID + "=?";
|
||||
contentResolver.delete(MediaContract.MusicVideos.CONTENT_URI,
|
||||
where, new String[]{String.valueOf(hostId)});
|
||||
}
|
||||
|
||||
private void insertMusicVideos(final SyncOrchestrator orchestrator,
|
||||
final ContentResolver contentResolver,
|
||||
final List<VideoType.DetailsMusicVideo> musicVideos) {
|
||||
ContentValues musicVideosValuesBatch[] = new ContentValues[musicVideos.size()];
|
||||
|
||||
// Iterate on each music video
|
||||
for (int i = 0; i < musicVideos.size(); i++) {
|
||||
VideoType.DetailsMusicVideo musicVideo = musicVideos.get(i);
|
||||
musicVideosValuesBatch[i] = SyncUtils.contentValuesFromMusicVideo(hostId, musicVideo);
|
||||
}
|
||||
|
||||
// Insert the movies
|
||||
contentResolver.bulkInsert(MediaContract.MusicVideos.CONTENT_URI, musicVideosValuesBatch);
|
||||
orchestrator.syncItemFinished();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* Copyright 2015 Synced Synapse. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.service.library;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.ContentResolver;
|
||||
import android.os.Handler;
|
||||
|
||||
import org.xbmc.kore.host.HostInfo;
|
||||
import org.xbmc.kore.jsonrpc.HostConnection;
|
||||
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Iterator;
|
||||
|
||||
import de.greenrobot.event.EventBus;
|
||||
|
||||
public class SyncOrchestrator {
|
||||
public static final String TAG = LogUtils.makeLogTag(SyncOrchestrator.class);
|
||||
|
||||
private ArrayDeque<SyncItem> syncItems;
|
||||
private Service syncService;
|
||||
private final int serviceStartId;
|
||||
private HostConnection hostConnection;
|
||||
private final HostInfo hostInfo;
|
||||
private final Handler callbackHandler;
|
||||
private final ContentResolver contentResolver;
|
||||
|
||||
private SyncItem currentSyncItem;
|
||||
|
||||
private Iterator<SyncItem> syncItemIterator;
|
||||
|
||||
public interface OnSyncListener {
|
||||
void onSyncFinished(SyncOrchestrator syncOrchestrator);
|
||||
}
|
||||
|
||||
private OnSyncListener listener;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param syncService Service on which to call {@link LibrarySyncService#stopSelf()} when finished
|
||||
* @param startId Service startid to use when calling {@link LibrarySyncService#stopSelf()}
|
||||
* @param hostInfo Host from which to sync
|
||||
* @param callbackHandler Handler on which to post callbacks
|
||||
* @param contentResolver Content resolver
|
||||
*/
|
||||
public SyncOrchestrator(Service syncService, final int startId,
|
||||
final HostInfo hostInfo,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver) {
|
||||
this.syncService = syncService;
|
||||
this.syncItems = new ArrayDeque<SyncItem>();
|
||||
this.serviceStartId = startId;
|
||||
this.hostInfo = hostInfo;
|
||||
this.callbackHandler = callbackHandler;
|
||||
this.contentResolver = contentResolver;
|
||||
}
|
||||
|
||||
public void setListener(OnSyncListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public HostInfo getHostInfo() {
|
||||
return hostInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this item to the sync list
|
||||
* @param syncItem Sync item
|
||||
*/
|
||||
public void addSyncItem(SyncItem syncItem) {
|
||||
syncItems.add(syncItem);
|
||||
}
|
||||
|
||||
public ArrayDeque<SyncItem> getSyncItems() {
|
||||
return syncItems;
|
||||
}
|
||||
|
||||
private long startTime = -1;
|
||||
private long partialStartTime;
|
||||
|
||||
/**
|
||||
* Starts the syncing process
|
||||
*/
|
||||
public void startSync() {
|
||||
startTime = System.currentTimeMillis();
|
||||
hostConnection = new HostConnection(hostInfo);
|
||||
hostConnection.setProtocol(HostConnection.PROTOCOL_HTTP);
|
||||
syncItemIterator = syncItems.iterator();
|
||||
nextSync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the next item on the sync list, or cleans up if it is finished.
|
||||
*/
|
||||
private void nextSync() {
|
||||
if (syncItemIterator.hasNext()) {
|
||||
partialStartTime = System.currentTimeMillis();
|
||||
currentSyncItem = syncItemIterator.next();
|
||||
currentSyncItem.sync(this, hostConnection, callbackHandler, contentResolver);
|
||||
} else {
|
||||
LogUtils.LOGD(TAG, "Sync finished for all items. Total time: " +
|
||||
(System.currentTimeMillis() - startTime));
|
||||
// No more syncs, cleanup.
|
||||
// No need to disconnect, as this is HTTP
|
||||
//hostConnection.disconnect();
|
||||
if (listener != null) {
|
||||
listener.onSyncFinished(this);
|
||||
}
|
||||
syncService.stopSelf(serviceStartId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* One of the syync items finish syncing
|
||||
*/
|
||||
public void syncItemFinished() {
|
||||
LogUtils.LOGD(TAG, "Sync finished for item: " + currentSyncItem.getDescription() +
|
||||
". Total time: " + (System.currentTimeMillis() - partialStartTime));
|
||||
|
||||
EventBus.getDefault()
|
||||
.post(new MediaSyncEvent(currentSyncItem.getSyncType(),
|
||||
currentSyncItem.getSyncExtras(),
|
||||
MediaSyncEvent.STATUS_SUCCESS));
|
||||
|
||||
syncItems.remove(currentSyncItem);
|
||||
|
||||
nextSync();
|
||||
}
|
||||
|
||||
/**
|
||||
* One of the sync items failed, stop and clean up
|
||||
* @param errorCode Error code
|
||||
* @param description Description
|
||||
*/
|
||||
public void syncItemFailed(int errorCode, String description) {
|
||||
LogUtils.LOGD(TAG, "A Sync item has got an error. Sync item: " +
|
||||
currentSyncItem.getDescription() +
|
||||
". Error description: " + description);
|
||||
// No need to disconnect, as this is HTTP
|
||||
//hostConnection.disconnect();
|
||||
EventBus.getDefault()
|
||||
.post(new MediaSyncEvent(currentSyncItem.getSyncType(),
|
||||
currentSyncItem.getSyncExtras(),
|
||||
MediaSyncEvent.STATUS_FAIL, errorCode, description));
|
||||
// Keep syncing till the end
|
||||
nextSync();
|
||||
//syncService.stopSelf(serviceStartId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,384 @@
|
|||
/*
|
||||
* Copyright 2015 Synced Synapse. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.service.library;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
|
||||
import org.xbmc.kore.jsonrpc.ApiCallback;
|
||||
import org.xbmc.kore.jsonrpc.HostConnection;
|
||||
import org.xbmc.kore.jsonrpc.method.VideoLibrary;
|
||||
import org.xbmc.kore.jsonrpc.type.ListType;
|
||||
import org.xbmc.kore.jsonrpc.type.VideoType;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SyncTVShows extends SyncItem {
|
||||
public static final String TAG = LogUtils.makeLogTag(SyncTVShows.class);
|
||||
|
||||
private static final int LIMIT_SYNC_TVSHOWS = 200;
|
||||
|
||||
private final int hostId;
|
||||
private final int tvshowId;
|
||||
private final Bundle syncExtras;
|
||||
|
||||
/**
|
||||
* Syncs all the TVShows on selected XBMC to the local database
|
||||
* @param hostId XBMC host id
|
||||
*/
|
||||
public SyncTVShows(final int hostId, Bundle syncExtras) {
|
||||
this.hostId = hostId;
|
||||
this.tvshowId = -1;
|
||||
this.syncExtras = syncExtras;
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs a specific TVShow to the local database
|
||||
* @param hostId XBMC host id
|
||||
* @param tvshowId Show to sync
|
||||
*/
|
||||
public SyncTVShows(final int hostId, final int tvshowId, Bundle syncExtras) {
|
||||
this.hostId = hostId;
|
||||
this.tvshowId = tvshowId;
|
||||
this.syncExtras = syncExtras;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public String getDescription() {
|
||||
return (tvshowId != -1) ?
|
||||
"Sync TV shows for host: " + hostId :
|
||||
"Sync TV show " + tvshowId + " for host: " + hostId;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public String getSyncType() {
|
||||
return (tvshowId == -1) ? LibrarySyncService.SYNC_ALL_TVSHOWS
|
||||
: LibrarySyncService.SYNC_SINGLE_TVSHOW;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public Bundle getSyncExtras() {
|
||||
return syncExtras;
|
||||
}
|
||||
|
||||
private final static String getTVShowsProperties[] = {
|
||||
VideoType.FieldsTVShow.TITLE, VideoType.FieldsTVShow.GENRE,
|
||||
//VideoType.FieldsTVShow.YEAR,
|
||||
VideoType.FieldsTVShow.RATING, VideoType.FieldsTVShow.PLOT,
|
||||
VideoType.FieldsTVShow.STUDIO, VideoType.FieldsTVShow.MPAA,
|
||||
VideoType.FieldsTVShow.CAST, VideoType.FieldsTVShow.PLAYCOUNT,
|
||||
VideoType.FieldsTVShow.EPISODE, VideoType.FieldsTVShow.IMDBNUMBER,
|
||||
VideoType.FieldsTVShow.PREMIERED,
|
||||
//VideoType.FieldsTVShow.VOTES, VideoType.FieldsTVShow.LASTPLAYED,
|
||||
VideoType.FieldsTVShow.FANART, VideoType.FieldsTVShow.THUMBNAIL,
|
||||
VideoType.FieldsTVShow.FILE,
|
||||
//VideoType.FieldsTVShow.ORIGINALTITLE, VideoType.FieldsTVShow.SORTTITLE,
|
||||
// VideoType.FieldsTVShow.EPISODEGUIDE, VideoType.FieldsTVShow.SEASON,
|
||||
VideoType.FieldsTVShow.WATCHEDEPISODES, VideoType.FieldsTVShow.DATEADDED,
|
||||
//VideoType.FieldsTVShow.TAG, VideoType.FieldsTVShow.ART
|
||||
};
|
||||
/** {@inheritDoc} */
|
||||
public void sync(final SyncOrchestrator orchestrator,
|
||||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver) {
|
||||
if (tvshowId == -1) {
|
||||
syncAllTVShows(orchestrator, hostConnection, callbackHandler, contentResolver,
|
||||
0, new ArrayList<VideoType.DetailsTVShow>());
|
||||
} else {
|
||||
VideoLibrary.GetTVShowDetails action =
|
||||
new VideoLibrary.GetTVShowDetails(tvshowId, getTVShowsProperties);
|
||||
action.execute(hostConnection, new ApiCallback<VideoType.DetailsTVShow>() {
|
||||
@Override
|
||||
public void onSuccess(VideoType.DetailsTVShow result) {
|
||||
deleteTVShows(contentResolver, hostId, tvshowId);
|
||||
List<VideoType.DetailsTVShow> tvShows = new ArrayList<>(1);
|
||||
tvShows.add(result);
|
||||
insertTVShowsAndGetDetails(orchestrator, hostConnection, callbackHandler,
|
||||
contentResolver, tvShows);
|
||||
// insertTVShows calls syncItemFinished
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, String description) {
|
||||
// Ok, something bad happend, just quit
|
||||
orchestrator.syncItemFailed(errorCode, description);
|
||||
}
|
||||
}, callbackHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs all the TV shows, calling itself recursively
|
||||
* Uses the {@link VideoLibrary.GetTVShows} version with limits to make sure
|
||||
* that Kodi doesn't blow up, and calls itself recursively until all the
|
||||
* shows are returned
|
||||
*/
|
||||
private void syncAllTVShows(final SyncOrchestrator orchestrator,
|
||||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver,
|
||||
final int startIdx,
|
||||
final List<VideoType.DetailsTVShow> allResults) {
|
||||
// Call GetTVShows with the current limits set
|
||||
ListType.Limits limits = new ListType.Limits(startIdx, startIdx + LIMIT_SYNC_TVSHOWS);
|
||||
VideoLibrary.GetTVShows action = new VideoLibrary.GetTVShows(limits, getTVShowsProperties);
|
||||
action.execute(hostConnection, new ApiCallback<List<VideoType.DetailsTVShow>>() {
|
||||
@Override
|
||||
public void onSuccess(List<VideoType.DetailsTVShow> result) {
|
||||
allResults.addAll(result);
|
||||
if (result.size() == LIMIT_SYNC_TVSHOWS) {
|
||||
// Max limit returned, there may be some more movies
|
||||
LogUtils.LOGD(TAG, "syncAllTVShows: More tv shows on media center, recursing.");
|
||||
syncAllTVShows(orchestrator, hostConnection, callbackHandler, contentResolver,
|
||||
startIdx + LIMIT_SYNC_TVSHOWS, allResults);
|
||||
} else {
|
||||
// Ok, we have all the shows, insert them
|
||||
LogUtils.LOGD(TAG, "syncAllTVShows: Got all tv shows. Total: " + allResults.size());
|
||||
deleteTVShows(contentResolver, hostId, -1);
|
||||
insertTVShowsAndGetDetails(orchestrator, hostConnection, callbackHandler,
|
||||
contentResolver, allResults);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, String description) {
|
||||
// Ok, something bad happend, just quit
|
||||
orchestrator.syncItemFailed(errorCode, description);
|
||||
}
|
||||
}, callbackHandler);
|
||||
}
|
||||
|
||||
private void deleteTVShows(final ContentResolver contentResolver,
|
||||
int hostId, int tvshowId) {
|
||||
if (tvshowId == -1) {
|
||||
LogUtils.LOGD(TAG, "Deleting all existing tv shows: ");
|
||||
// Delete all tvshows
|
||||
String where = MediaContract.TVShowsColumns.HOST_ID + "=?";
|
||||
contentResolver.delete(MediaContract.Episodes.CONTENT_URI,
|
||||
where, new String[]{String.valueOf(hostId)});
|
||||
contentResolver.delete(MediaContract.Seasons.CONTENT_URI,
|
||||
where, new String[]{String.valueOf(hostId)});
|
||||
contentResolver.delete(MediaContract.TVShowCast.CONTENT_URI,
|
||||
where, new String[]{String.valueOf(hostId)});
|
||||
contentResolver.delete(MediaContract.TVShows.CONTENT_URI,
|
||||
where, new String[]{String.valueOf(hostId)});
|
||||
} else {
|
||||
// Delete a specific tvshow
|
||||
contentResolver.delete(MediaContract.Episodes.buildTVShowEpisodesListUri(hostId, tvshowId),
|
||||
null, null);
|
||||
contentResolver.delete(MediaContract.Seasons.buildTVShowSeasonsListUri(hostId, tvshowId),
|
||||
null, null);
|
||||
contentResolver.delete(MediaContract.TVShowCast.buildTVShowCastListUri(hostId, tvshowId),
|
||||
null, null);
|
||||
contentResolver.delete(MediaContract.TVShows.buildTVShowUri(hostId, tvshowId),
|
||||
null, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void insertTVShowsAndGetDetails(final SyncOrchestrator orchestrator,
|
||||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver,
|
||||
List<VideoType.DetailsTVShow> tvShows) {
|
||||
ContentValues tvshowsValuesBatch[] = new ContentValues[tvShows.size()];
|
||||
int castCount = 0;
|
||||
|
||||
// Iterate on each show
|
||||
for (int i = 0; i < tvShows.size(); i++) {
|
||||
VideoType.DetailsTVShow tvshow = tvShows.get(i);
|
||||
tvshowsValuesBatch[i] = SyncUtils.contentValuesFromTVShow(hostId, tvshow);
|
||||
castCount += tvshow.cast.size();
|
||||
}
|
||||
// Insert the tvshows
|
||||
contentResolver.bulkInsert(MediaContract.TVShows.CONTENT_URI, tvshowsValuesBatch);
|
||||
LogUtils.LOGD(TAG, "Inserted " + tvShows.size() + " tv shows.");
|
||||
|
||||
ContentValues tvshowsCastValuesBatch[] = new ContentValues[castCount];
|
||||
int count = 0;
|
||||
// Iterate on each show/cast
|
||||
for (VideoType.DetailsTVShow tvshow : tvShows) {
|
||||
for (VideoType.Cast cast : tvshow.cast) {
|
||||
tvshowsCastValuesBatch[count] = SyncUtils.contentValuesFromCast(hostId, cast);
|
||||
tvshowsCastValuesBatch[count].put(MediaContract.TVShowCastColumns.TVSHOWID, tvshow.tvshowid);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
// Insert the cast list for this movie
|
||||
contentResolver.bulkInsert(MediaContract.TVShowCast.CONTENT_URI, tvshowsCastValuesBatch);
|
||||
|
||||
// Start the sequential syncing of seasons
|
||||
chainSyncSeasons(orchestrator, hostConnection, callbackHandler,
|
||||
contentResolver, tvShows, 0);
|
||||
}
|
||||
|
||||
private final static String seasonsProperties[] = {
|
||||
VideoType.FieldsSeason.SEASON, VideoType.FieldsSeason.SHOWTITLE,
|
||||
//VideoType.FieldsSeason.PLAYCOUNT,
|
||||
VideoType.FieldsSeason.EPISODE,
|
||||
VideoType.FieldsSeason.FANART, VideoType.FieldsSeason.THUMBNAIL,
|
||||
VideoType.FieldsSeason.TVSHOWID, VideoType.FieldsSeason.WATCHEDEPISODES,
|
||||
//VideoType.FieldsSeason.ART
|
||||
};
|
||||
|
||||
/**
|
||||
* Sequentially syncs seasons for the tvshow specified, and on success recursively calls
|
||||
* itself to sync the next tvshow on the list.
|
||||
* This basically iterates through the tvshows list updating the seasons,
|
||||
* in a sequential manner (defeating the parallel nature of host calls)
|
||||
* After processing all tvshows on the list, starts the episode syncing
|
||||
*
|
||||
* @param orchestrator Orchestrator to call when finished
|
||||
* @param hostConnection Host connection to use
|
||||
* @param callbackHandler Handler on which to post callbacks
|
||||
* @param contentResolver Content resolver
|
||||
* @param tvShows TV shows list to get seasons to
|
||||
* @param position Position of the tvshow on the list to process
|
||||
*/
|
||||
private void chainSyncSeasons(final SyncOrchestrator orchestrator,
|
||||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver,
|
||||
final List<VideoType.DetailsTVShow> tvShows,
|
||||
final int position) {
|
||||
if (position < tvShows.size()) {
|
||||
// Process this tvshow
|
||||
final VideoType.DetailsTVShow tvShow = tvShows.get(position);
|
||||
|
||||
VideoLibrary.GetSeasons action = new VideoLibrary.GetSeasons(tvShow.tvshowid, seasonsProperties);
|
||||
action.execute(hostConnection, new ApiCallback<List<VideoType.DetailsSeason>>() {
|
||||
@Override
|
||||
public void onSuccess(List<VideoType.DetailsSeason> result) {
|
||||
ContentValues seasonsValuesBatch[] = new ContentValues[result.size()];
|
||||
int totalWatchedEpisodes = 0;
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
VideoType.DetailsSeason season = result.get(i);
|
||||
seasonsValuesBatch[i] = SyncUtils.contentValuesFromSeason(hostId, season);
|
||||
|
||||
totalWatchedEpisodes += season.watchedepisodes;
|
||||
}
|
||||
// Insert the seasons
|
||||
contentResolver.bulkInsert(MediaContract.Seasons.CONTENT_URI, seasonsValuesBatch);
|
||||
|
||||
if (getSyncType().equals(LibrarySyncService.SYNC_SINGLE_TVSHOW)) {
|
||||
// HACK: Update watched episodes count for the tvshow with the sum
|
||||
// of watched episodes from seasons, given that the value that we
|
||||
// got from XBMC from the call to GetTVShowDetails is wrong (note
|
||||
// that the value returned from GetTVShows is correct).
|
||||
Uri uri = MediaContract.TVShows.buildTVShowUri(hostId, tvShow.tvshowid);
|
||||
ContentValues tvshowUpdate = new ContentValues(1);
|
||||
tvshowUpdate.put(MediaContract.TVShowsColumns.WATCHEDEPISODES, totalWatchedEpisodes);
|
||||
contentResolver.update(uri, tvshowUpdate, null, null);
|
||||
}
|
||||
|
||||
// Sync the next tv show
|
||||
chainSyncSeasons(orchestrator, hostConnection, callbackHandler,
|
||||
contentResolver, tvShows, position + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, String description) {
|
||||
// Ok, something bad happend, just quit
|
||||
orchestrator.syncItemFailed(errorCode, description);
|
||||
}
|
||||
}, callbackHandler);
|
||||
} else {
|
||||
// We've processed all tvshows, start episode syncing
|
||||
chainSyncEpisodes(orchestrator, hostConnection, callbackHandler,
|
||||
contentResolver, tvShows, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private final static String getEpisodesProperties[] = {
|
||||
VideoType.FieldsEpisode.TITLE, VideoType.FieldsEpisode.PLOT,
|
||||
//VideoType.FieldsEpisode.VOTES,
|
||||
VideoType.FieldsEpisode.RATING,
|
||||
VideoType.FieldsEpisode.WRITER, VideoType.FieldsEpisode.FIRSTAIRED,
|
||||
VideoType.FieldsEpisode.PLAYCOUNT, VideoType.FieldsEpisode.RUNTIME,
|
||||
VideoType.FieldsEpisode.DIRECTOR,
|
||||
//VideoType.FieldsEpisode.PRODUCTIONCODE,
|
||||
VideoType.FieldsEpisode.SEASON,
|
||||
VideoType.FieldsEpisode.EPISODE,
|
||||
//VideoType.FieldsEpisode.ORIGINALTITLE,
|
||||
VideoType.FieldsEpisode.SHOWTITLE,
|
||||
//VideoType.FieldsEpisode.CAST,
|
||||
VideoType.FieldsEpisode.STREAMDETAILS,
|
||||
//VideoType.FieldsEpisode.LASTPLAYED,
|
||||
VideoType.FieldsEpisode.FANART, VideoType.FieldsEpisode.THUMBNAIL,
|
||||
VideoType.FieldsEpisode.FILE,
|
||||
//VideoType.FieldsEpisode.RESUME,
|
||||
VideoType.FieldsEpisode.TVSHOWID, VideoType.FieldsEpisode.DATEADDED,
|
||||
//VideoType.FieldsEpisode.UNIQUEID, VideoType.FieldsEpisode.ART
|
||||
};
|
||||
|
||||
/**
|
||||
* Sequentially syncs episodes for the tvshow specified, and on success recursively calls
|
||||
* itself to sync the next tvshow on the list.
|
||||
* This basically iterates through the tvshows list updating the episodes,
|
||||
* in a sequential manner (defeating the parallel nature of host calls)
|
||||
*
|
||||
* @param orchestrator Orchestrator to call when finished
|
||||
* @param hostConnection Host connection to use
|
||||
* @param callbackHandler Handler on which to post callbacks
|
||||
* @param contentResolver Content resolver
|
||||
* @param tvShows TV shows list to get episodes to
|
||||
* @param position Position of the tvshow on the list to process
|
||||
*/
|
||||
private void chainSyncEpisodes(final SyncOrchestrator orchestrator,
|
||||
final HostConnection hostConnection,
|
||||
final Handler callbackHandler,
|
||||
final ContentResolver contentResolver,
|
||||
final List<VideoType.DetailsTVShow> tvShows,
|
||||
final int position) {
|
||||
if (position < tvShows.size()) {
|
||||
VideoType.DetailsTVShow tvShow = tvShows.get(position);
|
||||
|
||||
VideoLibrary.GetEpisodes action = new VideoLibrary.GetEpisodes(tvShow.tvshowid, getEpisodesProperties);
|
||||
action.execute(hostConnection, new ApiCallback<List<VideoType.DetailsEpisode>>() {
|
||||
@Override
|
||||
public void onSuccess(List<VideoType.DetailsEpisode> result) {
|
||||
ContentValues episodesValuesBatch[] = new ContentValues[result.size()];
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
VideoType.DetailsEpisode episode = result.get(i);
|
||||
episodesValuesBatch[i] = SyncUtils.contentValuesFromEpisode(hostId, episode);
|
||||
}
|
||||
// Insert the episodes
|
||||
contentResolver.bulkInsert(MediaContract.Episodes.CONTENT_URI, episodesValuesBatch);
|
||||
|
||||
chainSyncEpisodes(orchestrator, hostConnection, callbackHandler,
|
||||
contentResolver, tvShows, position + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, String description) {
|
||||
// Ok, something bad happend, just quit
|
||||
orchestrator.syncItemFailed(errorCode, description);
|
||||
}
|
||||
}, callbackHandler);
|
||||
} else {
|
||||
// We're finished
|
||||
LogUtils.LOGD(TAG, "Sync tv shows finished successfully");
|
||||
orchestrator.syncItemFinished();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.xbmc.kore.service;
|
||||
package org.xbmc.kore.service.library;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentValues;
|
||||
|
@ -27,6 +27,7 @@ import org.xbmc.kore.jsonrpc.type.VideoType;
|
|||
import org.xbmc.kore.jsonrpc.type.AudioType;
|
||||
import org.xbmc.kore.jsonrpc.type.LibraryType;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.service.library.LibrarySyncService;
|
||||
import org.xbmc.kore.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -462,11 +463,11 @@ public class SyncUtils {
|
|||
if (service == null || hostInfo == null || syncTypes == null)
|
||||
return false;
|
||||
|
||||
ArrayList<LibrarySyncService.SyncItem> itemsSyncing = service.getItemsSyncing(hostInfo);
|
||||
ArrayList<SyncItem> itemsSyncing = service.getItemsSyncing(hostInfo);
|
||||
if( itemsSyncing == null )
|
||||
return false;
|
||||
|
||||
for (LibrarySyncService.SyncItem syncItem : itemsSyncing) {
|
||||
for (SyncItem syncItem : itemsSyncing) {
|
||||
for( String syncType : syncTypes ) {
|
||||
if (syncItem.getSyncType().equals(syncType)) {
|
||||
return true;
|
|
@ -37,7 +37,6 @@ import android.view.MenuItem;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
@ -47,8 +46,8 @@ import org.xbmc.kore.host.HostInfo;
|
|||
import org.xbmc.kore.host.HostManager;
|
||||
import org.xbmc.kore.jsonrpc.ApiException;
|
||||
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
|
||||
import org.xbmc.kore.service.LibrarySyncService;
|
||||
import org.xbmc.kore.service.SyncUtils;
|
||||
import org.xbmc.kore.service.library.LibrarySyncService;
|
||||
import org.xbmc.kore.service.library.SyncUtils;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
|
||||
import de.greenrobot.event.EventBus;
|
||||
|
|
|
@ -36,8 +36,8 @@ import org.xbmc.kore.host.HostInfo;
|
|||
import org.xbmc.kore.host.HostManager;
|
||||
import org.xbmc.kore.jsonrpc.ApiException;
|
||||
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
|
||||
import org.xbmc.kore.service.LibrarySyncService;
|
||||
import org.xbmc.kore.service.SyncUtils;
|
||||
import org.xbmc.kore.service.library.LibrarySyncService;
|
||||
import org.xbmc.kore.service.library.SyncUtils;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
import org.xbmc.kore.utils.UIUtils;
|
||||
|
||||
|
@ -65,16 +65,16 @@ abstract public class AbstractDetailsFragment extends Fragment
|
|||
abstract protected View createView(LayoutInflater inflater, ViewGroup container);
|
||||
|
||||
/**
|
||||
* Should return {@link org.xbmc.kore.service.LibrarySyncService} SyncType that
|
||||
* Should return {@link LibrarySyncService} SyncType that
|
||||
* this fragment initiates
|
||||
* @return {@link org.xbmc.kore.service.LibrarySyncService} SyncType
|
||||
* @return {@link LibrarySyncService} SyncType
|
||||
*/
|
||||
abstract protected String getSyncType();
|
||||
|
||||
/**
|
||||
* Should return the {@link org.xbmc.kore.service.LibrarySyncService} syncID if this fragment
|
||||
* Should return the {@link LibrarySyncService} syncID if this fragment
|
||||
* synchronizes a single item. The itemId that should be synced must returned by {@link #getSyncItemID()}
|
||||
* @return {@link org.xbmc.kore.service.LibrarySyncService} SyncID
|
||||
* @return {@link LibrarySyncService} SyncID
|
||||
*/
|
||||
abstract protected String getSyncID();
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ import org.xbmc.kore.host.HostManager;
|
|||
import org.xbmc.kore.jsonrpc.type.PlaylistType;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.provider.MediaDatabase;
|
||||
import org.xbmc.kore.service.LibrarySyncService;
|
||||
import org.xbmc.kore.service.library.LibrarySyncService;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
import org.xbmc.kore.utils.MediaPlayerUtils;
|
||||
import org.xbmc.kore.utils.UIUtils;
|
||||
|
|
|
@ -39,7 +39,7 @@ import org.xbmc.kore.host.HostManager;
|
|||
import org.xbmc.kore.jsonrpc.type.PlaylistType;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.provider.MediaDatabase;
|
||||
import org.xbmc.kore.service.LibrarySyncService;
|
||||
import org.xbmc.kore.service.library.LibrarySyncService;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
import org.xbmc.kore.utils.MediaPlayerUtils;
|
||||
import org.xbmc.kore.utils.UIUtils;
|
||||
|
|
|
@ -37,7 +37,7 @@ import org.xbmc.kore.host.HostInfo;
|
|||
import org.xbmc.kore.host.HostManager;
|
||||
import org.xbmc.kore.jsonrpc.type.PlaylistType;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.service.LibrarySyncService;
|
||||
import org.xbmc.kore.service.library.LibrarySyncService;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
import org.xbmc.kore.utils.MediaPlayerUtils;
|
||||
import org.xbmc.kore.utils.UIUtils;
|
||||
|
|
|
@ -56,7 +56,7 @@ import org.xbmc.kore.jsonrpc.method.VideoLibrary;
|
|||
import org.xbmc.kore.jsonrpc.type.PlaylistType;
|
||||
import org.xbmc.kore.jsonrpc.type.VideoType;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.service.LibrarySyncService;
|
||||
import org.xbmc.kore.service.library.LibrarySyncService;
|
||||
import org.xbmc.kore.utils.FileDownloadHelper;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
import org.xbmc.kore.utils.UIUtils;
|
||||
|
|
|
@ -42,7 +42,7 @@ import org.xbmc.kore.host.HostInfo;
|
|||
import org.xbmc.kore.host.HostManager;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.provider.MediaDatabase;
|
||||
import org.xbmc.kore.service.LibrarySyncService;
|
||||
import org.xbmc.kore.service.library.LibrarySyncService;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
import org.xbmc.kore.utils.UIUtils;
|
||||
import org.xbmc.kore.utils.Utils;
|
||||
|
|
|
@ -53,7 +53,7 @@ import org.xbmc.kore.jsonrpc.method.Player;
|
|||
import org.xbmc.kore.jsonrpc.method.Playlist;
|
||||
import org.xbmc.kore.jsonrpc.type.PlaylistType;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.service.LibrarySyncService;
|
||||
import org.xbmc.kore.service.library.LibrarySyncService;
|
||||
import org.xbmc.kore.utils.FileDownloadHelper;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
import org.xbmc.kore.utils.UIUtils;
|
||||
|
|
|
@ -36,7 +36,7 @@ import org.xbmc.kore.host.HostInfo;
|
|||
import org.xbmc.kore.host.HostManager;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.provider.MediaDatabase;
|
||||
import org.xbmc.kore.service.LibrarySyncService;
|
||||
import org.xbmc.kore.service.library.LibrarySyncService;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
import org.xbmc.kore.utils.UIUtils;
|
||||
import org.xbmc.kore.utils.Utils;
|
||||
|
|
|
@ -44,7 +44,7 @@ import org.xbmc.kore.host.HostManager;
|
|||
import org.xbmc.kore.jsonrpc.type.PlaylistType;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.provider.MediaDatabase;
|
||||
import org.xbmc.kore.service.LibrarySyncService;
|
||||
import org.xbmc.kore.service.library.LibrarySyncService;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
import org.xbmc.kore.utils.MediaPlayerUtils;
|
||||
import org.xbmc.kore.utils.UIUtils;
|
||||
|
|
|
@ -52,7 +52,7 @@ import org.xbmc.kore.jsonrpc.method.Playlist;
|
|||
import org.xbmc.kore.jsonrpc.method.VideoLibrary;
|
||||
import org.xbmc.kore.jsonrpc.type.PlaylistType;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.service.LibrarySyncService;
|
||||
import org.xbmc.kore.service.library.LibrarySyncService;
|
||||
import org.xbmc.kore.utils.FileDownloadHelper;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
import org.xbmc.kore.utils.UIUtils;
|
||||
|
|
|
@ -48,7 +48,7 @@ import org.xbmc.kore.host.HostManager;
|
|||
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
|
||||
import org.xbmc.kore.jsonrpc.type.PlaylistType;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.service.LibrarySyncService;
|
||||
import org.xbmc.kore.service.library.LibrarySyncService;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
import org.xbmc.kore.utils.MediaPlayerUtils;
|
||||
import org.xbmc.kore.utils.UIUtils;
|
||||
|
|
|
@ -42,7 +42,7 @@ import org.xbmc.kore.host.HostInfo;
|
|||
import org.xbmc.kore.host.HostManager;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.provider.MediaDatabase;
|
||||
import org.xbmc.kore.service.LibrarySyncService;
|
||||
import org.xbmc.kore.service.library.LibrarySyncService;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
import org.xbmc.kore.utils.UIUtils;
|
||||
import org.xbmc.kore.utils.Utils;
|
||||
|
|
|
@ -40,7 +40,7 @@ import org.xbmc.kore.Settings;
|
|||
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
|
||||
import org.xbmc.kore.jsonrpc.type.VideoType;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.service.LibrarySyncService;
|
||||
import org.xbmc.kore.service.library.LibrarySyncService;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
import org.xbmc.kore.utils.UIUtils;
|
||||
import org.xbmc.kore.utils.Utils;
|
||||
|
|
|
@ -38,7 +38,7 @@ import org.xbmc.kore.Settings;
|
|||
import org.xbmc.kore.host.HostManager;
|
||||
import org.xbmc.kore.jsonrpc.ApiCallback;
|
||||
import org.xbmc.kore.jsonrpc.HostConnection;
|
||||
import org.xbmc.kore.service.LibrarySyncService;
|
||||
import org.xbmc.kore.service.library.LibrarySyncService;
|
||||
import org.xbmc.kore.ui.NavigationDrawerFragment;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
|
Loading…
Reference in New Issue