Extraced fetching stuff

master
Max Ammann 2016-06-14 15:48:58 +02:00
parent 5031b6437a
commit 6c63a97d92
6 changed files with 279 additions and 232 deletions

View File

@ -0,0 +1,161 @@
package max.music_cyclon.service;
import android.content.res.Resources;
import android.util.JsonReader;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import max.music_cyclon.SynchronizeConfig;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class BeetsFetcher {
public static final Random RANDOM = new Random();
private final String address;
private final Resources resources;
public BeetsFetcher(String address, Resources resources) {
this.address = address;
this.resources = resources;
}
public List<Item> fetch(SynchronizeConfig config) throws IOException {
StringBuilder get;
if (config.isAlbum(resources)) {
get = new StringBuilder("/album");
} else {
get = new StringBuilder("/item");
}
String query = config.getQuery(resources);
if (!query.isEmpty()) {
get.append("/query/").append(query);
}
get.append("?expand");
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(address + get)
.build();
Response response = client.newCall(request).execute();
if (response.code() != 200) {
Log.e("ERROR", "Server returned HTTP " + response.message());
return Collections.emptyList();
}
InputStream stream = response.body().byteStream();
List<Item> items = parseJson(stream, config.getSize(resources), config.isAlbum(resources));
stream.close();
return items;
}
private List<Item> parseJson(InputStream stream, int size, boolean isAlbums) throws IOException {
JsonReader reader = new JsonReader(new BufferedReader(new InputStreamReader(stream, "UTF-8")));
List<Item> items = new ArrayList<>();
List<ArrayList<Item>> albums = new ArrayList<>();
reader.beginObject();
String root = reader.nextName();
// boolean isAlbums = root.equals("albums");
reader.beginArray();
while (reader.hasNext()) {
if (isAlbums) {
albums.add(parseAlbum(reader));
} else {
items.add(parseItem(reader));
}
}
reader.endArray();
reader.endObject();
// Select random
if (isAlbums) {
List<ArrayList<Item>> randomAlbums = selectRandom(albums, size);
for (ArrayList<Item> album : randomAlbums) {
items.addAll(album);
}
return Collections.unmodifiableList(items);
} else {
return selectRandom(items, size);
}
}
public <T> List<T> selectRandom(List<T> list, int n) {
if (list.isEmpty()) {
return Collections.emptyList();
}
ArrayList<T> out = new ArrayList<>();
for (int i = 0; i < n; i++) {
out.add(list.get(RANDOM.nextInt(list.size() - 1)));
}
return Collections.unmodifiableList(out);
}
private ArrayList<Item> parseAlbum(JsonReader reader) throws IOException {
reader.beginObject();
ArrayList<Item> items = new ArrayList<>();
while (reader.hasNext()) {
String tag = reader.nextName();
if (tag.equals("items")) {
reader.beginArray();
while (reader.hasNext()) {
items.add(parseItem(reader));
}
reader.endArray();
} else {
reader.skipValue();
}
}
reader.endObject();
return items;
}
private Item parseItem(JsonReader reader) throws IOException {
reader.beginObject();
Item item = new Item();
while (reader.hasNext()) {
String tag = reader.nextName();
switch (tag) {
case "id":
item.setId(reader.nextInt());
break;
case "path":
item.setPath(reader.nextString());
break;
default:
reader.skipValue();
break;
}
}
reader.endObject();
return item;
}
}

View File

@ -10,18 +10,18 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.zip.Adler32;
import cz.msebera.android.httpclient.client.methods.CloseableHttpResponse;
import cz.msebera.android.httpclient.client.methods.HttpGet;
import cz.msebera.android.httpclient.impl.client.CloseableHttpClient;
import cz.msebera.android.httpclient.impl.client.HttpClients;
import max.music_cyclon.service.db.FileTracker;
import max.music_cyclon.SynchronizeConfig;
import max.music_cyclon.tracker.FileTracker;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class DownloadTask implements Runnable {
private final SynchronizeConfig config;
private final URI uri;
private final String itemPath;
@ -29,30 +29,36 @@ public class DownloadTask implements Runnable {
private final ProgressUpdater progressUpdater;
private CountDownLatch itemsLeftLatch;
private static final CloseableHttpClient httpclient = HttpClients.createDefault();
public DownloadTask(URI uri, String itemPath,
FileTracker tracker, ProgressUpdater progressUpdater,
CountDownLatch itemsLeftLatch) {
public DownloadTask(SynchronizeConfig config, URI uri, String itemPath,
FileTracker tracker, ProgressUpdater progressUpdater) {
this.config = config;
this.uri = uri;
this.itemPath = itemPath;
this.tracker = tracker;
this.progressUpdater = progressUpdater;
this.itemsLeftLatch = itemsLeftLatch;
}
private InputStream prepareConnection() throws IOException {
HttpGet httpGet = new HttpGet(uri);
OkHttpClient client = new OkHttpClient();
CloseableHttpResponse response = httpclient.execute(httpGet);
Request request = new Request.Builder()
.url(uri.toURL())
.build();
if (response.getStatusLine().getStatusCode() != 200) {
Log.e("ERROR", "Server returned HTTP " + response.getStatusLine().getStatusCode());
Response response = client.newCall(request).execute();
if (response.code() != 200) {
Log.e("ERROR", "Server returned HTTP " + response.message());
return null;
}
return response.getEntity().getContent();
return response.body().byteStream();
}
public void setItemsLeftLatch(CountDownLatch itemsLeftLatch) {
this.itemsLeftLatch = itemsLeftLatch;
}
@Override
@ -81,9 +87,9 @@ public class DownloadTask implements Runnable {
input.close();
}
tracker.track(target, checksum.getValue());
tracker.track(config, target, checksum.getValue());
} catch (IOException e) {
Log.wtf("WTF", e);
Log.e("DOWNLOAD", "Failed to download", e);
}
progressUpdater.increment();

View File

@ -3,9 +3,7 @@ package max.music_cyclon.service;
public class Item {
private int id;
private String name;
private String artist;
private String album;
private String path;
public int getId() {
return id;
@ -15,27 +13,11 @@ public class Item {
this.id = id;
}
public String getAlbum() {
return album;
public String getPath() {
return path;
}
public void setAlbum(String album) {
this.album = album;
}
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
public void setPath(String path) {
this.path = path;
}
}

View File

@ -1,9 +1,10 @@
package max.music_cyclon.service;
import android.Manifest;
import android.app.IntentService;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
@ -12,57 +13,40 @@ import android.os.Messenger;
import android.os.Parcelable;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.util.JsonReader;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import com.maxmpz.poweramp.player.PowerampAPI;
import java.io.BufferedReader;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.lang.ref.WeakReference;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import cz.msebera.android.httpclient.client.methods.CloseableHttpResponse;
import cz.msebera.android.httpclient.client.methods.HttpGet;
import cz.msebera.android.httpclient.impl.client.CloseableHttpClient;
import cz.msebera.android.httpclient.impl.client.HttpClients;
import max.music_cyclon.R;
import max.music_cyclon.SynchronizeConfig;
import max.music_cyclon.service.db.FileTracker;
import max.music_cyclon.tracker.FileTracker;
public class LibraryService extends IntentService {
public static final FilenameFilter NOMEDIA_FILTER = new FilenameFilter() {
@Override
public boolean accept(File file, String s) {
return !s.equals(".nomedia");
}
};
/**
* Command to the service to register a client, receiving callbacks
* from the service. The Message's replyTo field must be a Messenger of
* Command to the serviceReference to register a client, receiving callbacks
* from the serviceReference. The Message's replyTo field must be a Messenger of
* the client where callbacks should be sent.
*/
public static final int MSG_REGISTER_CLIENT = 1;
/**
* Command to the service to unregister a client, ot stop receiving callbacks
* from the service. The Message's replyTo field must be a Messenger of
* Command to the serviceReference to unregister a client, ot stop receiving callbacks
* from the serviceReference. The Message's replyTo field must be a Messenger of
* the client as previously given with MSG_REGISTER_CLIENT.
*/
public static final int MSG_UNREGISTER_CLIENT = 2;
@ -71,7 +55,6 @@ public class LibraryService extends IntentService {
public static final int MSG_CANCEL = 3;
public static final int MSG_STARTED = 4;
public static final int MSG_FINISHED = 5;
public static final Random RANDOM = new Random();
/**
* Keeps track of all current registered clients.
@ -81,151 +64,42 @@ public class LibraryService extends IntentService {
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
private final Messenger mMessenger = new Messenger(new IncomingHandler());
private final Messenger mMessenger = new Messenger(
new IncomingHandler(new WeakReference<>(this))
);
public LibraryService() {
super("max.music_cyclon.service.LibraryService");
super(LibraryService.class.getName());
}
public List<Item> fetchRandom(String address, SynchronizeConfig config, Resources resources) throws IOException {
StringBuilder get;
if (config.isAlbum(resources)) {
get = new StringBuilder("/album");
} else {
get = new StringBuilder("/item");
}
String query = config.getQuery(resources);
if (!query.isEmpty()) {
get.append("/query/").append(query);
}
get.append("?expand");
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(address + get);
CloseableHttpResponse response = httpclient.execute(httpGet);
if (response.getStatusLine().getStatusCode() != 200) {
Log.e("ERROR", "Server returned HTTP " + response.getStatusLine().getStatusCode());
return Collections.emptyList();
}
InputStream stream = response.getEntity().getContent();
ArrayList<Item> items = parseJson(stream, config.getSize(resources));
stream.close();
return items;
}
private ArrayList<Item> parseJson(InputStream stream, int size) throws IOException {
JsonReader reader = new JsonReader(new BufferedReader(new InputStreamReader(stream, "UTF-8")));
ArrayList<Item> items = new ArrayList<>();
ArrayList<ArrayList<Item>> albums = new ArrayList<>();
reader.beginObject();
boolean isAlbums = reader.nextName().equals("albums");
reader.beginArray();
while (reader.hasNext()) {
if (isAlbums) {
albums.add(parseAlbum(reader));
} else {
items.add(parseItem(reader));
}
}
reader.endArray();
reader.endObject();
items = selectRandom(items, size);
ArrayList<ArrayList<Item>> randomAlbums = selectRandom(albums, size);
for (ArrayList<Item> album : randomAlbums) {
items.addAll(album);
}
return items;
}
public <T> ArrayList<T> selectRandom(ArrayList<T> list, int n) {
ArrayList<T> out = new ArrayList<>();
for (int i = 0; i < n; i++) {
out.add(list.get(RANDOM.nextInt(list.size() - 1)));
}
return out;
}
private ArrayList<Item> parseAlbum(JsonReader reader) throws IOException {
reader.beginObject();
ArrayList<Item> items = new ArrayList<>();
while (reader.hasNext()) {
String tag = reader.nextName();
if (tag.equals("items")) {
reader.beginArray();
while (reader.hasNext()) {
items.add(parseItem(reader));
}
reader.endArray();
} else {
reader.skipValue();
}
}
reader.endObject();
return items;
}
private Item parseItem(JsonReader reader) throws IOException {
reader.beginObject();
Item item = new Item();
while (reader.hasNext()) {
String tag = reader.nextName();
switch (tag) {
case "id":
item.setId(reader.nextInt());
break;
case "title":
item.setName(reader.nextString());
break;
case "album":
item.setAlbum(reader.nextString());
break;
case "artist":
item.setArtist(reader.nextString());
break;
default:
reader.skipValue();
break;
}
}
reader.endObject();
return item;
}
@Override
protected void onHandleIntent(Intent intent) {
broadcast(Message.obtain(null, MSG_STARTED));
Parcelable[] configs = intent.getParcelableArrayExtra("configs");
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(this);
ExecutorService executor = Executors.newFixedThreadPool(Integer.parseInt(globalSettings.getString("threads", "2")));
String address = globalSettings.getString("address", "127.0.0.1");
File root = new File(Environment.getExternalStorageDirectory(), "library");
ProgressUpdater updater = new ProgressUpdater(this);
broadcast(Message.obtain(null, MSG_STARTED));
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
updater.showMessage("No permission to write!", false);
finished();
return;
}
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(this);
int threads = Integer.parseInt(globalSettings.getString("threads", Integer.toString(getResources().getInteger(R.integer.threads))));
String address = globalSettings.getString("address", getResources().getString(R.string.address));
ExecutorService executor = Executors.newFixedThreadPool(threads);
File root = new File(Environment.getExternalStorageDirectory(), "library");
BeetsFetcher fetcher = new BeetsFetcher(address, getResources());
if (root.exists() && !root.isDirectory()) {
updater.showMessage("Library is no dictionary! Fix manually");
updater.showMessage("Library is no dictionary! Fix manually", false);
finished();
return;
}
@ -234,54 +108,62 @@ public class LibraryService extends IntentService {
FileTracker tracker = new FileTracker(getApplicationContext());
try {
updater.showMessage("Cleaning library");
updater.showMessage("Cleaning library", true);
tracker.delete();
} catch (IOException e) {
e.printStackTrace();
}
if (root.exists() && root.list(NOMEDIA_FILTER).length != 0) {
updater.showMessage("Library not empty! Clean in manually");
if (root.exists() && root.list().length != 0) {
updater.showMessage("Library not empty! Clean in manually", false);
finished();
return;
}
ArrayList<DownloadTask> tasks = new ArrayList<>();
for (Parcelable parcelable : configs) {
SynchronizeConfig config = (SynchronizeConfig) parcelable;
List<Item> items;
try {
updater.showMessage("Fetching music information for %s", config.getName());
items = fetchRandom(address, config, getResources());
updater.showMessage("Fetching music information for %s", true, config.getName());
items = fetcher.fetch(config);
} catch (IOException e) {
Log.wtf("WTF", e);
updater.showMessage("Remote not available");
updater.showMessage("Remote not available", false);
finished();
return;
}
updater.showMessage("Mixing new music for %s!", config.getName());
updater.showMessage("Mixing new music for %s!", true, config.getName());
updater.setMaximumProgress(items.size());
CountDownLatch itemsLeftLatch = new CountDownLatch(items.size());
for (Item item : items) {
try {
executor.submit(new DownloadTask(new URL(address + "/item/" + item.getId() + "/file").toURI(), item.getArtist() + "/" + item.getAlbum() + "/" + item.getName() + ".mp3", tracker, updater, itemsLeftLatch));
URI uri = new URI(address + "/item/" + item.getId() + "/file");
tasks.add(new DownloadTask(config, uri, item.getPath(), tracker, updater));
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
try {
itemsLeftLatch.await();
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
updater.showMessage("Musik aktualisiert");
CountDownLatch itemsLeftLatch = new CountDownLatch(tasks.size());
for (DownloadTask task : tasks) {
task.setItemsLeftLatch(itemsLeftLatch);
executor.submit(task);
}
try {
itemsLeftLatch.await();
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
updater.showMessage("Musik aktualisiert", false);
// Update last_updated info
SharedPreferences preferences = getSharedPreferences("info", MODE_PRIVATE);
@ -293,21 +175,38 @@ public class LibraryService extends IntentService {
poweramp.putExtra(PowerampAPI.Scanner.EXTRA_FULL_RESCAN, true);
startService(poweramp);
finished();
}
public void finished() {
broadcast(Message.obtain(null, MSG_FINISHED));
}
/**
* Handler of incoming messages from clients.
*/
private class IncomingHandler extends Handler {
private static class IncomingHandler extends Handler {
private final WeakReference<LibraryService> serviceReference;
private IncomingHandler(WeakReference<LibraryService> serviceReference) {
this.serviceReference = serviceReference;
}
@Override
public void handleMessage(Message msg) {
LibraryService service = serviceReference.get();
if (service == null) {
return;
}
switch (msg.what) {
case MSG_REGISTER_CLIENT:
mClients.add(msg.replyTo);
service.mClients.add(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
mClients.remove(msg.replyTo);
service.mClients.remove(msg.replyTo);
break;
case MSG_CANCEL:
//todo

View File

@ -26,16 +26,15 @@ public class ProgressUpdater {
this.notificationManager = NotificationManagerCompat.from(context);
}
public void showMessage(String message, Object... args) {
showMessage(String.format(message, args));
public void showMessage(String message, boolean persist, Object... args) {
showMessage(String.format(message, args), persist);
}
public void showMessage(String message) {
public void showMessage(String message, boolean persist) {
NotificationCompat.Builder builder = notificationBuilder();
builder.setContentTitle(message);
builder.setContentText("");
builder.setProgress(0, 0, false);
builder.setProgress(0, 0, !persist);
updateNotification(builder);
}

View File

@ -14,7 +14,7 @@
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/viewer"
android:name="max.music_cyclon.MainPreferenceActivity$MainPreferenceFragment"
android:name="max.music_cyclon.preference.MainPreferenceActivity$MainPreferenceFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>