Skip to content

Commit

Permalink
Closes #23 and #19
Browse files Browse the repository at this point in the history
  • Loading branch information
devgianlu committed Nov 19, 2018
1 parent 4c5bd91 commit 05789f6
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,9 @@ public boolean cacheEnabled() {
public @NotNull File cacheDir() {
return new File("./cache/");
}

@Override
public boolean doCleanUp() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import org.jetbrains.annotations.Nullable;

import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;

import static org.librespot.spotify.player.ChannelManager.CHUNK_SIZE;

Expand All @@ -16,7 +16,7 @@
class AudioFileFetch implements AudioFile {
private final CacheManager.Handler cache;
private final ByteArrayOutputStream headersId = new ByteArrayOutputStream();
private final List<byte[]> headersData = new ArrayList<>();
private final BytesArrayList headersData = new BytesArrayList();
private int size = -1;
private int chunks = -1;

Expand Down Expand Up @@ -52,8 +52,12 @@ public void cacheFailedHeader(@NotNull AudioFile file) {

@Override
public synchronized void headerEnd(boolean cached) {
if (!cached && cache != null)
cache.writeHeaders(headersId.toByteArray(), headersData.toArray(new byte[0][]), (short) chunks);
if (!cached && cache != null) {
headersId.write(CacheManager.BYTE_CREATED_AT);
headersData.add(BigInteger.valueOf(System.currentTimeMillis() / 1000).toByteArray());

cache.writeHeaders(headersId.toByteArray(), headersData.toArray(), (short) chunks);
}
}

@Override
Expand All @@ -80,4 +84,35 @@ public int getChunks() {
if (chunks == -1) throw new IllegalStateException("Headers not received yet!");
return chunks;
}

private static class BytesArrayList {
private byte[][] elementData;
private int size;

BytesArrayList() {
size = 0;
elementData = new byte[5][];
}

private void ensureExplicitCapacity(int minCapacity) {
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

void add(byte[] e) {
ensureExplicitCapacity(size + 1);
elementData[size++] = e;
}

byte[][] toArray() {
return elementData;
}

private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0) newCapacity = minCapacity;
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,20 @@ public class AudioFileStreaming implements AudioFile {
private int chunks = -1;
private ChunksBuffer chunksBuffer;

public AudioFileStreaming(@NotNull Session session, @NotNull CacheManager cacheManager, @NotNull Metadata.AudioFile file, byte[] key) {
AudioFileStreaming(@NotNull Session session, @NotNull CacheManager cacheManager, @NotNull Metadata.AudioFile file, byte[] key) {
this.session = session;
this.fileId = file.getFileId();
this.cacheHandler = cacheManager.handler(fileId);
this.key = key;
}

@NotNull
public String getFileIdHex() {
String getFileIdHex() {
return Utils.bytesToHex(fileId);
}

@NotNull
public InputStream stream() {
InputStream stream() {
if (chunksBuffer == null) throw new IllegalStateException("Stream not open!");
return chunksBuffer.stream();
}
Expand All @@ -60,7 +60,7 @@ private AudioFileFetch requestHeaders() throws IOException {
return fetch;
}

public void open() throws IOException {
void open() throws IOException {
AudioFileFetch fetch = requestHeaders();

int size = fetch.getSize();
Expand Down
84 changes: 69 additions & 15 deletions core/src/main/java/org/librespot/spotify/player/CacheManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import static org.librespot.spotify.player.ChannelManager.CHUNK_SIZE;

Expand All @@ -19,14 +20,16 @@
* @author Gianlu
*/
public class CacheManager {
static final byte BYTE_CREATED_AT = 0b1111111;
private static final Logger LOGGER = Logger.getLogger(CacheManager.class);
private static final long CLEAN_UP_THRESHOLD = TimeUnit.DAYS.toMillis(7);
private final File cacheDir;
private final boolean enabled;
private final Map<String, Handler> loadedHandlers;
private final ControlTable controlTable;
private final ExecutorService executorService = Executors.newCachedThreadPool();

public CacheManager(@NotNull CacheConfiguration conf) throws IOException {
CacheManager(@NotNull CacheConfiguration conf) throws IOException {
this.enabled = conf.cacheEnabled();
if (enabled) {
this.loadedHandlers = new HashMap<>();
Expand All @@ -35,6 +38,7 @@ public CacheManager(@NotNull CacheConfiguration conf) throws IOException {
throw new IllegalStateException("Cannot create cache dir!");

this.controlTable = new ControlTable(new File(cacheDir, ".table"));
if (conf.doCleanUp()) controlTable.cleanOldTracks();
} else {
this.cacheDir = null;
this.loadedHandlers = null;
Expand All @@ -43,7 +47,7 @@ public CacheManager(@NotNull CacheConfiguration conf) throws IOException {
}

@Nullable
public Handler handler(@NotNull ByteString fileId) {
Handler handler(@NotNull ByteString fileId) {
if (!enabled) return null;

String hexId = Utils.bytesToHex(fileId);
Expand All @@ -62,6 +66,8 @@ public interface CacheConfiguration {

@NotNull
File cacheDir();

boolean doCleanUp();
}

private class ControlTable {
Expand All @@ -76,17 +82,28 @@ private ControlTable(@NotNull File controlFile) throws IOException {
} else {
file = new RandomAccessFile(controlFile, "rwd");
file.seek(0);

long read = 0;
while (read < file.length()) {
int count = file.readInt();
for (int i = 0; i < count; i++)
entries.add(new CacheEntry(file));
read += file.getFilePointer();
}
}

private void cleanOldTracks() {
Iterator<CacheEntry> iterator = entries.iterator();
while (iterator.hasNext()) {
CacheEntry entry = iterator.next();
if (System.currentTimeMillis() - entry.getCreatedAtMillis() > CLEAN_UP_THRESHOLD) {
entry.deleteFile();
iterator.remove();
}
}

safeSave();
}

private void save() throws IOException {
file.seek(0);
file.writeInt(entries.size());
for (CacheEntry entry : entries)
entry.writeTo(file);
}
Expand All @@ -99,7 +116,7 @@ boolean has(@NotNull String fileId, int chunk) {
return false;
}

public boolean hasHeaders(@NotNull String fileId) {
boolean hasHeaders(@NotNull String fileId) {
for (CacheEntry entry : entries)
if (fileId.equals(entry.hexId))
return true;
Expand Down Expand Up @@ -131,7 +148,9 @@ void writeHeaders(@NotNull String fileId, byte[] headersId, byte[][] headersData
public void remove(@NotNull String fileId) {
Iterator<CacheEntry> iterator = entries.iterator();
while (iterator.hasNext()) {
if (fileId.equals(iterator.next().hexId)) {
CacheEntry entry = iterator.next();
if (fileId.equals(entry.hexId)) {
entry.deleteFile();
iterator.remove();
safeSave();
return;
Expand Down Expand Up @@ -166,7 +185,7 @@ private class CacheEntry {
}

CacheEntry(@NotNull DataInput in) throws IOException {
byte[] buffer = new byte[20];
byte[] buffer = new byte[in.readShort()];
in.readFully(buffer);
gid = ByteString.copyFrom(buffer);
hexId = Utils.bytesToHex(buffer);
Expand All @@ -192,7 +211,9 @@ boolean has(int chunk) {
}

private void writeTo(@NotNull DataOutput out) throws IOException {
out.write(gid.toByteArray());
byte[] gidBytes = gid.toByteArray();
out.writeShort(gidBytes.length);
out.write(gidBytes);

out.writeShort(headersId.length);
for (int i = 0; i < headersId.length; i++) {
Expand All @@ -211,12 +232,45 @@ void requestHeaders(@NotNull AudioFile file) {
for (int i = 0; i < headersId.length; i++)
file.writeHeader(headersId[i], headersData[i], true);

for (int i = 0; i < headersId.length; i++) {
System.out.println("ID: " + headersId[i] + " " + new String(headersData[i]));
}

file.headerEnd(true);
}

void writtenChunk(int index) {
chunks[index] = true;
}

@Nullable
byte[] findHeaderData(byte id) {
for (int i = 0; i < headersId.length; i++)
if (headersId[i] == id)
return headersData[i];

return null;
}

long getCreatedAtMillis() {
byte[] createdAtBytes = findHeaderData(BYTE_CREATED_AT);
if (createdAtBytes == null) {
LOGGER.warn("Missing CREATED_AT header!");
return System.currentTimeMillis();
}

return new BigInteger(createdAtBytes).longValue() * 1000;
}

void deleteFile() {
File toDelete = new File(cacheDir, hexId);
if (toDelete.delete()) {
LOGGER.trace("Deleted cached track: " + hexId);
} else {
LOGGER.warn("Failed deleting cached track: " + toDelete);
toDelete.deleteOnExit();
}
}
}
}

Expand All @@ -234,7 +288,7 @@ private Handler(@NotNull String fileId) throws IOException {
cache = new RandomAccessFile(file, "rw");
}

public boolean has(int chunk) {
boolean has(int chunk) {
return controlTable.has(fileId, chunk);
}

Expand All @@ -243,11 +297,11 @@ public void close() throws IOException {
cache.close();
}

public void requestHeaders(@NotNull AudioFile fetch) {
void requestHeaders(@NotNull AudioFile fetch) {
executorService.execute(() -> controlTable.requestHeaders(fileId, fetch));
}

public void requestChunk(int index, @NotNull AudioFile file) {
void requestChunk(int index, @NotNull AudioFile file) {
executorService.execute(() -> {
try {
cache.seek(index * CHUNK_SIZE);
Expand All @@ -272,11 +326,11 @@ public void remove() {
controlTable.remove(fileId);
}

public void writeHeaders(byte[] headersId, byte[][] headersData, short chunksCount) {
void writeHeaders(byte[] headersId, byte[][] headersData, short chunksCount) {
controlTable.writeHeaders(fileId, headersId, headersData, chunksCount);
}

public boolean hasHeaders() {
boolean hasHeaders() {
return controlTable.hasHeaders(fileId);
}
}
Expand Down
2 changes: 1 addition & 1 deletion mdnsjava

0 comments on commit 05789f6

Please sign in to comment.