/*
* Copyright 2008-2013, ETH Zürich, Samuel Welten, Michael Kuhn, Tobias Langner,
* Sandro Affentranger, Lukas Bossard, Michael Grob, Rahul Jain,
* Dominic Langenegger, Sonia Mayor Alonso, Roger Odermatt, Tobias Schlueter,
* Yannick Stucki, Sebastian Wendland, Samuel Zehnder, Samuel Zihlmann,
* Samuel Zweifel
*
* This file is part of Jukefox.
*
* Jukefox is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or any later version. Jukefox is
* distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* Jukefox. If not, see <http://www.gnu.org/licenses/>.
*/
package ch.ethz.dcg.jukefox.manager.libraryimport;
import java.util.ArrayList;
import java.util.List;
import ch.ethz.dcg.jukefox.commons.DataWriteException;
import ch.ethz.dcg.jukefox.commons.utils.JoinableThread;
import ch.ethz.dcg.jukefox.commons.utils.Log;
import ch.ethz.dcg.jukefox.commons.utils.MathUtils;
import ch.ethz.dcg.jukefox.commons.utils.Utils;
import ch.ethz.dcg.jukefox.data.cache.ImportStateListener;
import ch.ethz.dcg.jukefox.model.collection.SongCoords;
import ch.ethz.dcg.jukefox.model.collection.statistics.CollectionProperties;
import ch.ethz.dcg.jukefox.model.libraryimport.ImportState;
import ch.ethz.dcg.jukefox.model.providers.OtherDataProvider;
import ch.ethz.dcg.jukefox.model.providers.SongCoordinatesProvider;
public class CollectionPropertiesFetcherThread implements Runnable {
private final static String TAG = CollectionPropertiesFetcherThread.class.getName();
private final static int NUM_SONGS_FOR_CALCULATION = 1000;
private final SongCoordinatesProvider songCoordinatesProvider;
private final OtherDataProvider otherDataProvider;
private ImportState importState;
private JoinableThread currentThread = null;
public CollectionPropertiesFetcherThread(OtherDataProvider otherDataProvider,
SongCoordinatesProvider songCoordinatesProvider, ImportState importState) {
this.otherDataProvider = otherDataProvider;
this.songCoordinatesProvider = songCoordinatesProvider;
this.importState = importState;
}
public synchronized void startInThread() {
if (currentThread != null) {
throw new IllegalStateException("Another instance is already running.");
}
currentThread = new JoinableThread(this);
currentThread.start();
}
public synchronized void join() throws InterruptedException {
if (currentThread == null) {
throw new IllegalStateException("Not running.");
}
currentThread.realJoin();
currentThread = null;
}
@Override
public void run() {
if (!importState.isCoordinatesFetched()) {
// Wait for the coordinates to be fetched
importState.addListener(new ImportStateListener() {
private void doNotify() {
synchronized (CollectionPropertiesFetcherThread.this) {
CollectionPropertiesFetcherThread.this.notify();
}
}
@Override
public void onImportStarted() {
doNotify();
}
@Override
public void onImportProblem(Throwable e) {
doNotify();
}
@Override
public void onImportCompleted(boolean hadChanges) {
doNotify();
}
@Override
public void onImportAborted(boolean hadChanges) {
doNotify();
}
@Override
public void onCoordinatesFetched() {
doNotify();
}
@Override
public void onBaseDataCommitted() {
}
@Override
public void onAlbumCoversFetched() {
}
});
try {
synchronized (this) {
// Wait for the coordinates to be fetched
while (!importState.isCoordinatesFetched() && importState.isImporting()) { // Ensure that we finish waiting eventually
wait(5000);
}
}
} catch (InterruptedException e) {
Log.w(TAG, e);
}
if (!importState.isCoordinatesFetched()) {
return; // Probably the import got aborted
}
}
// Start the computation
try {
List<SongCoords> songCoords = songCoordinatesProvider.getSongCoords(false); // Read them from the db, since the preloaded data may not be available yet
songCoords = MathUtils.getRandomElements(songCoords, NUM_SONGS_FOR_CALCULATION);
List<Float> distances = new ArrayList<Float>(songCoords.size() * (songCoords.size() - 1) / 2);
float mean = 0;
int progress = -1;
for (int i = 0; i < songCoords.size(); ++i) {
// Update the progress message
int newProgress = (int) (i / (float) songCoords.size() * 3); // 0 - 3
if (newProgress != progress) {
progress = newProgress;
importState.setCollectionPropertiesProgress(progress, 5, "calculating collection properties");
}
// Calculate the distances
SongCoords sc1 = songCoords.get(i);
for (int j = i + 1; j < songCoords.size(); ++j) {
SongCoords sc2 = songCoords.get(j);
float distance = Utils.distance(sc1.getCoords(), sc2.getCoords());
distances.add(distance);
mean += distance;
}
}
mean /= distances.size();
importState.setCollectionPropertiesProgress(4, 5, "calculating collection properties"); // 4 - 5
float sum = 0;
for (float distance : distances) {
float tmp = distance - mean;
sum += tmp * tmp; // Probably faster than Math.pow()
}
float stdDeviation = (float) Math.sqrt(sum / (distances.size() - 1)); // = \sqrt{1/(N-1) * \sum_{i=1}^{N} (x_i - mean)^2} = \sqrt{1/(N-1) * sum}
CollectionProperties cp = new CollectionProperties();
cp.setAverageSongDistance(mean);
cp.setSongDistanceStdDeviation(stdDeviation);
otherDataProvider.setCollectionProperties(cp);
importState.setCollectionPropertiesProgress(5, 5, "calculating collection properties");
importState.setCollectionPropertiesCalculated(true);
} catch (DataWriteException e) {
Log.w(TAG, e);
}
}
}