All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.webimageloader.loader.DiskLoader Maven / Gradle / Ivy

package com.webimageloader.loader;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import android.graphics.Bitmap;
import android.os.Process;
import android.util.Log;

import com.jakewharton.DiskLruCache;
import com.jakewharton.DiskLruCache.Editor;
import com.jakewharton.DiskLruCache.Snapshot;
import com.webimageloader.Constants;
import com.webimageloader.ImageLoader.Logger;
import com.webimageloader.Request;
import com.webimageloader.util.ListenerFuture;
import com.webimageloader.util.BitmapUtils;
import com.webimageloader.util.Hasher;
import com.webimageloader.util.IOUtil;
import com.webimageloader.util.InputSupplier;

public class DiskLoader extends SimpleBackgroundLoader implements Closeable {
    private static final String TAG = "DiskLoader";

    private static final int APP_VERSION = 2;

    private static final int BUFFER_SIZE = 8192;

    private static final int INPUT_IMAGE = 0;
    private static final int INPUT_METADATA = 1;
    private static final int VALUE_COUNT = 2;

    private DiskLruCache cache;
    private final Hasher hasher;

    public static DiskLoader open(File directory, long maxSize, int threadCount) throws IOException {
        return new DiskLoader(DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize), threadCount);
    }

    private DiskLoader(DiskLruCache cache, int threadCount) {
        super("Disk", Process.THREAD_PRIORITY_BACKGROUND, threadCount);

        this.cache = cache;
        hasher = new Hasher();
    }

    @Override
    public void close() {
        super.close();

        IOUtil.closeQuietly(cache);
    }

    @Override
    public void load(LoaderWork.Manager manager, LoaderRequest request) {
        if (request.hasFlag(Request.Flag.IGNORE_CACHE)) {
            manager.next(request, new NextListener(request, manager));
            return;
        }

        super.load(manager, request);
    }

    @Override
    protected void loadInBackground(LoaderWork.Manager manager, LoaderRequest request) throws IOException {
        Snapshot snapshot = getSnapshot(request);
        if (snapshot != null) {
            try {
                if (Logger.VERBOSE) Log.v(TAG, "Loaded " + request + " from disk");

                Metadata metadata = readMetadata(snapshot);
                DiskInputSupplier input = new DiskInputSupplier(request, snapshot);

                manager.deliverStream(input, metadata);

                long expires = metadata.getExpires();
                if (expires != Metadata.NEVER_EXPIRES && System.currentTimeMillis() > expires) {
                    // Cache has expired
                    if (Logger.VERBOSE) Log.v(TAG, request + " has expired, updating");
                    manager.next(request.withMetadata(metadata), new NextListener(request, manager));
                }
            } finally {
                snapshot.close();
            }
        } else {
            // We need to add the next loader
            manager.next(request, new NextListener(request, manager));
        }
    }

    private Metadata readMetadata(Snapshot snapshot) throws IOException {
        // Use a small buffer as the metadata itself is small
        InputStream is = new BufferedInputStream(snapshot.getInputStream(INPUT_METADATA), 1024);
        try {
            return Metadata.from(is);
        } finally {
            is.close();
        }
    }

    private Snapshot getSnapshot(LoaderRequest request) throws IOException {
        String key = hashKeyForDisk(request);
        return cache.get(key);
    }

    private Editor getEditor(LoaderRequest request) throws IOException {
        String key = hashKeyForDisk(request);

        Editor editor = cache.edit(key);
        if (editor == null) {
            throw new IOException("File is already being edited");
        }

        return editor;
    }

    /**
     * A hashing method that changes a string (like a URL) into a hash suitable
     * for using as a disk filename.
     */
    private String hashKeyForDisk(LoaderRequest request) {
        String key = request.getCacheKey();

        // We don't except to have a lot of threads
        // so it's okay to synchronize access
        synchronized (hasher) {
            return hasher.hash(key);
        }
    }

    private class NextListener implements Listener {
        private LoaderRequest request;
        private LoaderWork.Manager manager;

        public NextListener(LoaderRequest request, LoaderWork.Manager manager) {
            this.request = request;
            this.manager = manager;
        }

        @Override
        public void onStreamLoaded(InputSupplier input, final Metadata metadata) {
            if (request.hasFlag(Request.Flag.NO_CACHE)) {
                manager.deliverStream(input, metadata);
                return;
            }

            try {
                Editor editor = getEditor(request);

                OutputStream os = new BufferedOutputStream(editor.newOutputStream(INPUT_IMAGE), BUFFER_SIZE);
                try {
                    try {
                        IOUtil.copy(input, os);
                    } finally {
                        os.close();
                    }

                    writeMetadata(editor, metadata);

                    editor.commit();

                    // Read back the file we just saved
                    run(manager, new ListenerFuture.Task() {
                        @Override
                        public void run() throws Exception {
                            DiskInputSupplier input = new DiskInputSupplier(request);
                            manager.deliverStream(input, metadata);
                        }
                    });
                } catch (IOException e) {
                    // We failed writing to the cache, we can't really do
                    // anything to clean this up
                    editor.abort();
                    manager.deliverError(e);
                }
            } catch (IOException e) {
                // We failed opening the cache, this
                // means that the InputStream is still untouched.
                // Pass it trough to the listener without caching.
                Log.e(TAG, "Failed opening cache", e);
                manager.deliverStream(input, metadata);
            }
        }

        @Override
        public void onBitmapLoaded(Bitmap b, Metadata metadata) {
            try {
                Editor editor = getEditor(request);

                try {
                    Bitmap.CompressFormat format = BitmapUtils.getCompressFormat(metadata.getContentType());
                    writeBitmap(editor, b, format);
                    writeMetadata(editor, metadata);

                    editor.commit();
                } finally {
                    editor.abortUnlessCommitted();
                }
            } catch (IOException e) {
                Log.e(TAG, "Failed saving bitmap to cache", e);
            }

            // We can always pass on the bitmap we got, even if
            // we didn't manage to write it to cache
            manager.deliverBitmap(b, metadata);
        }

        @Override
        public void onNotModified(Metadata metadata) {
            try {
                Editor editor = getEditor(request);

                try {
                    writeMetadata(editor, metadata);

                    editor.commit();
                } finally {
                    editor.abortUnlessCommitted();
                }
            } catch (IOException e) {
                Log.e(TAG, "Failed to update metadata", e);
            }

            manager.deliverNotMotified(metadata);
        }

        @Override
        public void onError(Throwable t) {
            manager.deliverError(t);
        }

        private void writeMetadata(Editor editor, Metadata metadata) throws IOException {
            OutputStream os = new BufferedOutputStream(editor.newOutputStream(INPUT_METADATA), BUFFER_SIZE);
            try {
                metadata.writeTo(os);
            } finally {
                IOUtil.closeQuietly(os);
            }
        }

        private void writeBitmap(Editor editor, Bitmap b, Bitmap.CompressFormat format) throws IOException {
            OutputStream os = new BufferedOutputStream(editor.newOutputStream(INPUT_IMAGE), BUFFER_SIZE);
            try {
                b.compress(format, Constants.DEFAULT_COMPRESS_QUALITY, os);
            } finally {
                IOUtil.closeQuietly(os);
            }
        }
    }

    private class DiskInputSupplier implements InputSupplier {
        private String key;
        private Snapshot snapshot;

        public DiskInputSupplier(LoaderRequest request) {
            this(request, null);
        }

        public DiskInputSupplier(LoaderRequest request, Snapshot snapshot) {
            this.key = hashKeyForDisk(request);
            this.snapshot = snapshot;
        }

        @Override
        public InputStream getInput() throws IOException {
            if (snapshot == null) {
                snapshot = cache.get(key);

                if (snapshot == null) {
                    throw new IOException("Snapshot not available");
                }
            }

            // Wrap input stream so we can close the snapshot
            return new FilterInputStream(snapshot.getInputStream(INPUT_IMAGE)) {
                @Override
                public void close() throws IOException {
                    super.close();

                    snapshot.close();
                    snapshot = null;
                }
            };
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy