/*
    Album Art plugin for DeaDBeeF
    Copyright (C) 2009-2011 Viktor Semykin <thesame.ml@gmail.com>
    Copyright (C) 2009-2013 Alexey Yakovenko <waker@users.sourceforge.net>

    This software is provided 'as-is', without any express or implied
    warranty.  In no event will the authors be held liable for any damages
    arising from the use of this software.

    Permission is granted to anyone to use this software for any purpose,
    including commercial applications, and to alter it and redistribute it
    freely, subject to the following restrictions:

    1. The origin of this software must not be misrepresented; you must not
     claim that you wrote the original software. If you use this software
     in a product, an acknowledgment in the product documentation would be
     appreciated but is not required.

    2. Altered source versions must be plainly marked as such, and must not be
     misrepresented as being the original software.

    3. This notice may not be removed or altered from any source distribution.
*/

#ifdef HAVE_CONFIG_H
#  include "../../config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#ifdef __linux__
#include <sys/prctl.h>
#endif
#include <errno.h>
#include <dirent.h>
#include <unistd.h>
#include <fnmatch.h>
#include <inttypes.h>
#if HAVE_SYS_CDEFS_H
#include <sys/cdefs.h>
#endif
#if HAVE_SYS_SYSLIMITS_H
#include <sys/syslimits.h>
#endif
#include <assert.h>
#include "../../deadbeef.h"
#include "artwork.h"
#ifdef USE_VFS_CURL
#include "lastfm.h"
#include "albumartorg.h"
#include "wos.h"
#endif

#ifdef USE_IMLIB2
#include <Imlib2.h>
static uintptr_t imlib_mutex;
#else
#include <jpeglib.h>
#include <jerror.h>
//#include <setjmp.h>
#include <png.h>
#endif

#ifdef USE_METAFLAC
#include <FLAC/metadata.h>
#endif

#include "../../strdupa.h"

#define min(x,y) ((x)<(y)?(x):(y))

//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
#define trace(...)

static char default_cover[PATH_MAX];
#define DEFAULT_FILEMASK "*cover*.jpg;*front*.jpg;*folder*.jpg;*cover*.png;*front*.png;*folder*.png"

static DB_artwork_plugin_t plugin;
DB_functions_t *deadbeef;

DB_FILE *current_file;

#define MAX_CALLBACKS 200

typedef struct cover_callback_s {
    artwork_callback cb;
    void *ud;
} cover_callback_t;

typedef struct cover_query_s {
    char *fname;
    char *artist;
    char *album;
    int size;
    cover_callback_t callbacks[MAX_CALLBACKS];
    int numcb;
    struct cover_query_s *next;
} cover_query_t;

typedef struct mutex_cond_s {
    uintptr_t mutex;
    uintptr_t cond;
} mutex_cond_t;

static cover_query_t *queue;
static cover_query_t *queue_tail;
static uintptr_t mutex;
static uintptr_t cond;
static volatile int terminate;
static volatile int clear_queue;
static intptr_t tid;

static int artwork_enable_embedded;
static int artwork_enable_local;
#ifdef USE_VFS_CURL
static int artwork_enable_lfm;
static int artwork_enable_aao;
static int artwork_enable_wos;
#endif
static time_t artwork_reset_time;
static char artwork_filemask[200];

static const char *get_default_cover (void) {
    return default_cover;
}

static int
esc_char (char c) {
    if (c < 1
        || (c >= 'a' && c <= 'z')
        || (c >= 'A' && c <= 'Z')
        || (c >= '0' && c <= '9')
        || c == ' '
        || c == '_'
        || c == '-') {
        return c;
    }
    return '_';
}

static int
make_cache_dir_path (char *path, int size, const char *artist, int img_size) {
    char esc_artist[PATH_MAX];
    int i;

    if (artist) {
        for (i = 0; artist[i]; i++) {
            esc_artist[i] = esc_char (artist[i]);
        }
        esc_artist[i] = 0;
    }
    else {
        strcpy (esc_artist, "Unknown artist");
    }

    const char *cache = getenv ("XDG_CACHE_HOME");
    int sz;

    if (img_size == -1) {
        sz = snprintf (path, size, cache ? "%s/deadbeef/covers/" : "%s/.cache/deadbeef/covers/", cache ? cache : getenv ("HOME"));
    }
    else {
        sz = snprintf (path, size, cache ? "%s/deadbeef/covers-%d/" : "%s/.cache/deadbeef/covers-%d/", cache ? cache : getenv ("HOME"), img_size);
    }
    path += sz;

    sz += snprintf (path, size-sz, "%s", esc_artist);
    for (char *p = path; *p; p++) {
        if (*p == '/') {
            *p = '_';
        }
    }
    return sz;
}

static int
make_cache_path2 (char *path, int size, const char *fname, const char *album, const char *artist, int img_size) {
    *path = 0;

    int unk = 0;
    int unk_artist = 0;

    if (!album || !(*album)) {
        album = "Unknown album";
        unk = 1;
    }
    if (!artist || !(*artist)) {
        artist = "Unknown artist";
        unk_artist = 1;
    }

    if (unk)
    {
        if (fname) {
            album = fname;
        }
        else if (!unk_artist) {
            album = artist;
        }
        else {
            trace ("not possible to get any unique album name\n");
            return -1;
        }
    }

    char *p = path;
    char esc_album[PATH_MAX];
    const char *palbum = album;
    size_t l = strlen (album);
    if (l > 200) {
        palbum = album + l - 200;
    }
    int i;
    for (i = 0; palbum[i]; i++) {
        esc_album[i] = esc_char (palbum[i]);
    }
    esc_album[i] = 0;

    int sz = make_cache_dir_path (path, size, artist, img_size);
    size -= sz;
    path += sz;
    sz = snprintf (path, size, "/%s.jpg", esc_album);
    for (char *p = path+1; *p; p++) {
        if (*p == '/') {
            *p = '_';
        }
    }
}

static void
make_cache_path (char *path, int size, const char *album, const char *artist, int img_size) {
    make_cache_path2 (path, size, NULL, album, artist, img_size);
}

static void
queue_add (const char *fname, const char *artist, const char *album, int img_size, artwork_callback callback, void *user_data) {
    if (!artist) {
        artist = "";
    }
    if (!album) {
        album = "";
    }
    deadbeef->mutex_lock (mutex);

    for (cover_query_t *q = queue; q; q = q->next) {
        if (!strcasecmp (artist, q->artist) && !strcasecmp (album, q->album) && img_size == q->size) {
            // already in queue, add callback
            if (q->numcb < MAX_CALLBACKS && callback) {
                q->callbacks[q->numcb].cb = callback;
                q->callbacks[q->numcb].ud = user_data;
                q->numcb++;
            }
            deadbeef->mutex_unlock (mutex);
            return;
        }
    }

    trace ("artwork:queue_add %s %s %s %d\n", fname, artist, album, img_size);
    cover_query_t *q = malloc (sizeof (cover_query_t));
    memset (q, 0, sizeof (cover_query_t));
    q->fname = strdup (fname);
    q->artist = strdup (artist);
    q->album = strdup (album);
    q->size = img_size;
    q->callbacks[q->numcb].cb = callback;
    q->callbacks[q->numcb].ud = user_data;
    q->numcb++;
    if (queue_tail) {
        queue_tail->next = q;
        queue_tail = q;
    }
    else {
        queue = queue_tail = q;
    }
    deadbeef->mutex_unlock (mutex);
    deadbeef->cond_signal (cond);
}

static void
queue_pop (void) {
    deadbeef->mutex_lock (mutex);
    cover_query_t *next = queue ? queue->next : NULL;
    if (queue) {
        if (queue->fname) {
            free (queue->fname);
        }
        if (queue->artist) {
            free (queue->artist);
        }
        if (queue->album) {
            free (queue->album);
        }
        for (int i = 0; i < queue->numcb; i++) {
            if (queue->callbacks[i].cb) {
                queue->callbacks[i].cb (NULL, NULL, NULL, queue->callbacks[i].ud);
            }
        }
        free (queue);
    }
    queue = next;
    if (!queue) {
        queue_tail = NULL;
    }
    deadbeef->mutex_unlock (mutex);
}

static int
check_dir (const char *dir, mode_t mode)
{
    char *tmp = strdup (dir);
    char *slash = tmp;
    struct stat stat_buf;
    do
    {
        slash = strstr (slash+1, "/");
        if (slash)
            *slash = 0;
        if (-1 == stat (tmp, &stat_buf))
        {
            trace ("creating dir %s\n", tmp);
            if (0 != mkdir (tmp, mode))
            {
                trace ("Failed to create %s (%d)\n", tmp, errno);
                free (tmp);
                return 0;
            }
        }
        if (slash)
            *slash = '/';
    } while (slash);
    free (tmp);
    return 1;
}

#ifndef USE_IMLIB2
struct my_error_mgr {
  struct jpeg_error_mgr pub;	/* "public" fields */

  jmp_buf setjmp_buffer;	/* for return to caller */
};

typedef struct my_error_mgr * my_error_ptr;

METHODDEF(void)
my_error_exit (j_common_ptr cinfo)
{
  /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
  my_error_ptr myerr = (my_error_ptr) cinfo->err;

//  (*cinfo->err->output_message) (cinfo);

  /* Return control to the setjmp point */
  longjmp(myerr->setjmp_buffer, 1);
}


static int
jpeg_resize (const char *fname, const char *outname, int scaled_size) {
    trace ("resizing %s into %s\n", fname, outname);
    struct jpeg_decompress_struct cinfo;
    struct jpeg_compress_struct cinfo_out;

    memset (&cinfo, 0, sizeof (cinfo));
    memset (&cinfo_out, 0, sizeof (cinfo_out));

    struct my_error_mgr jerr;

    uint8_t *c;
    FILE *fp = NULL, *out = NULL;


    cinfo.err = jpeg_std_error (&jerr.pub);

    jerr.pub.error_exit = my_error_exit;
    /* Establish the setjmp return context for my_error_exit to use. */
    if (setjmp(jerr.setjmp_buffer)) {
        /* If we get here, the JPEG code has signaled an error.
         * We need to clean up the JPEG object, close the input file, and return.
         */
        jpeg_destroy_decompress(&cinfo);
        jpeg_destroy_compress(&cinfo_out);
        if (fp) {
            fclose(fp);
        }
        if (out) {
            fclose (out);
        }
        return -1;
    }

    jpeg_create_decompress (&cinfo);

    fp = fopen (fname, "rb");
    if (!fp) {
        return -1;
    }
    out = fopen (outname, "w+b");
    if (!out) {
        fclose (fp);
        return -1;
    }

    jpeg_stdio_src (&cinfo, fp);

    jpeg_read_header (&cinfo, TRUE);
    jpeg_start_decompress (&cinfo);

    int i;

    cinfo_out.err = cinfo.err;

    jpeg_create_compress(&cinfo_out);

    jpeg_stdio_dest(&cinfo_out, out);

    int sw, sh;
    if (deadbeef->conf_get_int ("artwork.scale_towards_longer", 1)) {
        if (cinfo.image_width > cinfo.image_height) {
            sh = scaled_size;
            sw = scaled_size * cinfo.image_width / cinfo.image_height;
        }
        else {
            sw = scaled_size;
            sh = scaled_size * cinfo.image_height / cinfo.image_width;
        }
    }
    else {
        if (cinfo.image_width < cinfo.image_height) {
            sh = scaled_size;
            sw = scaled_size * cinfo.image_width / cinfo.image_height;
        }
        else {
            sw = scaled_size;
            sh = scaled_size * cinfo.image_height / cinfo.image_width;
        }
    }


    cinfo_out.image_width      = sw;
    cinfo_out.image_height     = sh;
    cinfo_out.input_components = cinfo.output_components;
    cinfo_out.in_color_space   = cinfo.out_color_space;

    jpeg_set_defaults(&cinfo_out);

    jpeg_set_quality(&cinfo_out, 100, TRUE);
    jpeg_start_compress(&cinfo_out, TRUE);

    float sy = 0;
    float dy = (float)cinfo.output_height / (float)sh;

    while (cinfo.output_scanline < cinfo.output_height)
    {
        uint8_t buf[cinfo.output_width * cinfo.output_components];
        uint8_t *ptr = buf;
        jpeg_read_scanlines (&cinfo, &ptr, 1);

        // scale row
        uint8_t out_buf[sw * cinfo.output_components];
        float sx = 0;
        float dx = (float)cinfo.output_width/(float)sw;
        for (int i = 0; i < sw; i++) {
            memcpy (&out_buf[i * cinfo.output_components], &buf[(int)sx * cinfo.output_components], cinfo.output_components);
            sx += dx;
        }

        while ((int)sy == cinfo.output_scanline-1) {
            uint8_t *ptr = out_buf;
            jpeg_write_scanlines(&cinfo_out, &ptr, 1);
            sy += dy;
        }
    }

    jpeg_finish_compress(&cinfo_out); //Always finish
    jpeg_destroy_compress(&cinfo_out); //Free resources

    jpeg_finish_decompress (&cinfo);
    jpeg_destroy_decompress (&cinfo);

    fclose(fp);
    fclose(out);

    return 0;
}

static int
png_resize (const char *fname, const char *outname, int scaled_size) {
    png_structp png_ptr = NULL;
    png_infop info_ptr = NULL;
    unsigned int sig_read = 0;
    png_uint_32 width, height;
    int bit_depth, color_type, interlace_type;
    int number_passes;
    png_uint_32 row;
    int bpp;
    int err = -1;
    FILE *fp = NULL;
    FILE *out = NULL;
    struct my_error_mgr jerr;
    struct jpeg_compress_struct cinfo_out;

    fp = fopen(fname, "rb");
    if (!fp) {
        goto error;
    }

    png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!png_ptr) {
        goto error;
    }

    if (setjmp(png_jmpbuf((png_ptr))))
    {
        fprintf (stderr, "failed to read png: %s\n", fname);
        goto error;
    }
    png_init_io(png_ptr, fp);

    info_ptr = png_create_info_struct (png_ptr);
    if (!info_ptr) {
        goto error;
    }
    //    if (setjmp (png_ptr->jmpbuf)) {
    //        goto err;
    //    }

    png_set_sig_bytes (png_ptr, sig_read);

    png_read_info(png_ptr, info_ptr);

    png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
            &interlace_type, NULL, NULL);

    /* Tell libpng to strip 16 bit/color files down to 8 bits/color */
    png_set_strip_16(png_ptr);

    /* Strip alpha bytes from the input data without combining with the
     * background (not recommended).
     */
    png_set_strip_alpha(png_ptr);

    /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
     * byte into separate bytes (useful for paletted and grayscale images).
     */
    png_set_packing(png_ptr);

    /* Expand paletted colors into true RGB triplets */
    if (color_type == PNG_COLOR_TYPE_PALETTE)
        png_set_palette_to_rgb(png_ptr);

    /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
    if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
        png_set_expand_gray_1_2_4_to_8(png_ptr);

    //    /* Expand paletted or RGB images with transparency to full alpha channels
    //     * so the data will be available as RGBA quartets.
    //     */
    //    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
    //        png_set_tRNS_to_alpha(png_ptr);

    //    /* Set the background color to draw transparent and alpha images over.
    //     * It is possible to set the red, green, and blue components directly
    //     * for paletted images instead of supplying a palette index.  Note that
    //     * even if the PNG file supplies a background, you are not required to
    //     * use it - you should use the (solid) application background if it has one.
    //     */
    //
    //    png_color_16 my_background, *image_background;
    //
    //    if (png_get_bKGD(png_ptr, info_ptr, &image_background))
    //        png_set_background(png_ptr, image_background,
    //                PNG_BACKGROUND_GAMMA_FILE, 1, 1.0);
    //    else
    //        png_set_background(png_ptr, &my_background,
    //                PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);

    //    screen_gamma = 2.2;  /* A good guess for a PC monitor in a dimly
    //                            lit room */

    /* Tell libpng to handle the gamma conversion for you.  The final call
     * is a good guess for PC generated images, but it should be configurable
     * by the user at run time by the user.  It is strongly suggested that
     * your application support gamma correction.
     */

    //    int intent;
    //
    //    if (png_get_sRGB(png_ptr, info_ptr, &intent))
    //        png_set_gamma(png_ptr, screen_gamma, 0.45455);
    //    else
    //    {
    //        double image_gamma;
    //        if (png_get_gAMA(png_ptr, info_ptr, &image_gamma))
    //            png_set_gamma(png_ptr, screen_gamma, image_gamma);
    //        else
    //            png_set_gamma(png_ptr, screen_gamma, 0.45455);
    //    }

    //    /* Flip the RGB pixels to BGR (or RGBA to BGRA) */
    //    if (color_type & PNG_COLOR_MASK_COLOR)
    //        png_set_bgr(png_ptr);
    //
    //    /* Swap the RGBA or GA data to ARGB or AG (or BGRA to ABGR) */
    //    png_set_swap_alpha(png_ptr);
    //
    //    /* Swap bytes of 16 bit files to least significant byte first */
    //    png_set_swap(png_ptr);
    //
    //    /* Add filler (or alpha) byte (before/after each RGB triplet) */
    //    png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);

    /* Turn on interlace handling.  REQUIRED if you are not using
     * png_read_image().  To see how to handle interlacing passes,
     * see the png_read_row() method below:
     */
    number_passes = png_set_interlace_handling(png_ptr);

    //    /* Optional call to gamma correct and add the background to the palette
    //     * and update info structure.  REQUIRED if you are expecting libpng to
    //     * update the palette for you (ie you selected such a transform above).
    //     */
    //    png_read_update_info(png_ptr, info_ptr);

    /* Allocate the memory to hold the image using the fields of info_ptr. */

    /* The easiest way to read the image: */

    png_bytep *row_pointers = png_malloc(png_ptr, height*sizeof (png_bytep));

    /* Clear the pointer array */
    for (row = 0; row < height; row++)
        row_pointers[row] = NULL;

    png_read_update_info(png_ptr, info_ptr);

    for (row = 0; row < height; row++)
        row_pointers[row] = png_malloc(png_ptr, png_get_rowbytes(png_ptr,
                    info_ptr));

    /* Now it's time to read the image.  One of these methods is REQUIRED */
    /* The other way to read images - deal with interlacing: */

    png_read_image(png_ptr, row_pointers);


    out = fopen (outname, "w+b");
    if (!out) {
        fclose (fp);
        return -1;
    }


    int i;

    cinfo_out.err = jpeg_std_error (&jerr.pub);
    jerr.pub.error_exit = my_error_exit;
    /* Establish the setjmp return context for my_error_exit to use. */
    if (setjmp(jerr.setjmp_buffer)) {
        /* If we get here, the JPEG code has signaled an error.
         * We need to clean up the JPEG object, close the input file, and return.
         */
         goto error;
    }

    jpeg_create_compress(&cinfo_out);

    jpeg_stdio_dest(&cinfo_out, out);

    int sw, sh;
    if (deadbeef->conf_get_int ("artwork.scale_towards_longer", 1)) {
        if (width > height) {
            sh = scaled_size;
            sw = scaled_size * width / height;
        }
        else {
            sw = scaled_size;
            sh = scaled_size * height / width;
        }
    }
    else {
        if (width < height) {
            sh = scaled_size;
            sw = scaled_size * width / height;
        }
        else {
            sw = scaled_size;
            sh = scaled_size * height / width;
        }
    }

    cinfo_out.image_width      = sw;
    cinfo_out.image_height     = sh;
    cinfo_out.input_components = 3;
    cinfo_out.in_color_space   = JCS_RGB;

    jpeg_set_defaults(&cinfo_out);

    jpeg_set_quality(&cinfo_out, 100, TRUE);
    jpeg_start_compress(&cinfo_out, TRUE);

    float sy = 0;
    float dy = (float)height / (float)sh;

    int input_y = 1;
    while (input_y <= height)
    {
        uint8_t *buf = row_pointers[input_y-1];

        // scale row
        uint8_t out_buf[sw * cinfo_out.input_components];
        float sx = 0;
        float dx = (float)width/(float)sw;
        for (int i = 0; i < sw; i++) {
            memcpy (&out_buf[i * cinfo_out.input_components], &buf[(int)sx * cinfo_out.input_components], cinfo_out.input_components);
            sx += dx;
        }

        while ((int)sy == input_y-1) {
            uint8_t *ptr = out_buf;
            jpeg_write_scanlines(&cinfo_out, &ptr, 1);
            sy += dy;
        }
        input_y++;
    }

    jpeg_finish_compress(&cinfo_out); //Always finish
    jpeg_destroy_compress(&cinfo_out); //Free resources

    /* Read rest of file, and get additional chunks in info_ptr - REQUIRED */
    png_read_end(png_ptr, info_ptr);

    err = 0;
error:
    if (out) {
        fclose (out);
    }
    if (fp) {
        fclose (fp);
    }
    if (png_ptr) {
        png_destroy_read_struct (&png_ptr, info_ptr ? &info_ptr : NULL, (png_infopp)NULL);
    }

    return err;
}

#endif

#define BUFFER_SIZE 4096

static int
copy_file (const char *in, const char *out, int img_size) {
    trace ("copying %s to %s\n", in, out);

    if (img_size != -1) {
#ifdef USE_IMLIB2
        deadbeef->mutex_lock (imlib_mutex);
        // need to scale, use imlib2
        Imlib_Image img = imlib_load_image_immediately (in);
        if (!img) {
            trace ("file %s not found, or imlib2 can't load it\n", in);
            deadbeef->mutex_unlock (imlib_mutex);
            return -1;
        }
        imlib_context_set_image(img);
        int w = imlib_image_get_width ();
        int h = imlib_image_get_height ();
        int sw, sh;
        if (deadbeef->conf_get_int ("artwork.scale_towards_longer", 1)) {
            if (w > h) {
                sh = img_size;
                sw = img_size * w / h;
            }
            else {
                sw = img_size;
                sh = img_size * h / w;
            }
        }
        else {
            if (w < h) {
                sh = img_size;
                sw = img_size * w / h;
            }
            else {
                sw = img_size;
                sh = img_size * h / w;
            }
        }
        Imlib_Image scaled = imlib_create_image (sw, sh);
        imlib_context_set_image (scaled);
        imlib_blend_image_onto_image (img, 1, 0, 0, w, h, 0, 0, sw, sh);
        Imlib_Load_Error err = 0;
        imlib_image_set_format ("jpg");
        imlib_save_image_with_error_return (out, &err);
        if (err != 0) {
            trace ("imlib save %s returned %d\n", out, err);
            imlib_free_image ();
            imlib_context_set_image(img);
            imlib_free_image ();
            deadbeef->mutex_unlock (imlib_mutex);
            return -1;
        }
        imlib_free_image ();
        imlib_context_set_image(img);
        imlib_free_image ();
        deadbeef->mutex_unlock (imlib_mutex);
#else
        int res = jpeg_resize (in, out, img_size);
        if (res != 0) {
            unlink (out);
            res = png_resize (in, out, img_size);
            if (res != 0) {
                unlink (out);
                return -1;
            }
        }
#endif
        return 0;
    }

    FILE *fin = fopen (in, "rb");
    if (!fin) {
        trace ("artwork: failed to open file %s for reading\n", in);
        return -1;
    }
    FILE *fout = fopen (out, "w+b");
    if (!fout) {
        fclose (fin);
        trace ("artwork: failed to open file %s for writing\n", out);
        return -1;
    }
    char *buf = malloc (BUFFER_SIZE);
    if (!buf) {
        trace ("artwork: failed to alloc %d bytes\n", BUFFER_SIZE);
        fclose (fin);
        fclose (fout);
        return -1;
    }

    fseek (fin, 0, SEEK_END);
    size_t sz = ftell (fin);
    rewind (fin);

    while (sz > 0) {
        int rs = min (sz, BUFFER_SIZE);
        if (fread (buf, rs, 1, fin) != 1) {
            trace ("artwork: failed to read file %s\n", in);
            break;
        }
        if (fwrite (buf, rs, 1, fout) != 1) {
            trace ("artwork: failed to write file %s\n", out);
            break;
        }
        sz -= rs;
    }
    free (buf);
    fclose (fin);
    fclose (fout);
    if (sz > 0) {
        unlink (out);
    }
    return 0;
}

static const char *filter_custom_mask = NULL;

static int
filter_custom (const struct dirent *f)
{
// FNM_CASEFOLD is not defined on solaris. On other platforms it is.
// It should be safe to define it as FNM_INGORECASE if it isn't defined.
#ifndef FNM_CASEFOLD
#define FNM_CASEFOLD FNM_IGNORECASE
#endif
    if (!fnmatch (filter_custom_mask, f->d_name, FNM_CASEFOLD)) {
        return 1;
    }
    return 0;
}

static int
filter_jpg (const struct dirent *f)
{
    const char *ext = strrchr (f->d_name, '.');
    if (!ext)
        return 0;
    if (!strcasecmp (ext, ".jpg") || !strcasecmp (ext, ".jpeg")) {
        return 1;
    }

    return 0;
}

static uint8_t *
id3v2_skip_str (int enc, uint8_t *ptr, uint8_t *end) {
    if (enc == 0 || enc == 3) {
        while (ptr < end && *ptr) {
            ptr++;
        }
        ptr++;
        if (ptr >= end) {
            return NULL;
        }
        return ptr;
    }
    else {
        while (ptr < end-1 && (ptr[0] || ptr[1])) {
            ptr += 2;
        }
        ptr += 2;
        if (ptr >= end) {
            return NULL;
        }
        return ptr;
    }
    return NULL;
}

#ifdef USE_METAFLAC
static size_t
flac_io_read (void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle) {
    return deadbeef->fread (ptr, size, nmemb, (DB_FILE *)handle);
}

static int
flac_io_seek (FLAC__IOHandle handle, FLAC__int64 offset, int whence) {
    return deadbeef->fseek ((DB_FILE *)handle, offset, whence);
}

static FLAC__int64
flac_io_tell (FLAC__IOHandle handle) {
    return deadbeef->ftell ((DB_FILE *)handle);
}

static int
flac_io_eof (FLAC__IOHandle handle) {
    int64_t pos = deadbeef->ftell ((DB_FILE *)handle);
    return pos == deadbeef->fgetlength ((DB_FILE *)handle);
}

static int
flac_io_close (FLAC__IOHandle handle) {
    deadbeef->fclose ((DB_FILE *)handle);
    return 0;
}

static FLAC__IOCallbacks iocb = {
    .read = flac_io_read,
    .write = NULL,
    .seek = flac_io_seek,
    .tell = flac_io_tell,
    .eof = flac_io_eof,
    .close = flac_io_close,
};
#endif

static void
fetcher_thread (void *none)
{
#ifdef __linux__
    prctl (PR_SET_NAME, "deadbeef-artwork", 0, 0, 0, 0);
#endif
    for (;;) {
        trace ("artwork: waiting for signal\n");
        deadbeef->cond_wait (cond, mutex);
        trace ("artwork: cond signalled\n");
        deadbeef->mutex_unlock (mutex);
        while (!terminate && queue && !clear_queue) {
            cover_query_t *param = queue;
            char path [PATH_MAX];
            struct dirent **files;
            int files_count = 0;

            make_cache_dir_path (path, sizeof (path), param->artist, -1);
            trace ("cache folder: %s\n", path);
            if (!check_dir (path, 0755)) {
                queue_pop ();
                trace ("failed to create folder for %s %s\n", param->album, param->artist);
                continue;
            }
            if (param->size != -1) {
                make_cache_dir_path (path, sizeof (path), param->artist, param->size);
                trace ("cache folder: %s\n", path);
                if (!check_dir (path, 0755)) {
                    queue_pop ();
                    trace ("failed to create folder for %s %s\n", param->album, param->artist);
                    continue;
                }
            }

            trace ("fetching cover for %s %s\n", param->album, param->artist);
            char cache_path[1024];
            make_cache_path2 (cache_path, sizeof (cache_path), param->fname, param->album, param->artist, -1);
            int got_pic = 0;

            if (deadbeef->is_local_file (param->fname)) {
                if (artwork_enable_embedded) {
                    // try to load embedded from id3v2
                    {
                        trace ("trying to load artwork from id3v2 tag for %s\n", param->fname);
                        DB_id3v2_tag_t tag;
                        memset (&tag, 0, sizeof (tag));
                        DB_FILE *fp = deadbeef->fopen (param->fname);
                        current_file = fp;
                        if (fp) {
                            int res = deadbeef->junk_id3v2_read_full (NULL, &tag, fp);
                            if (!res) {
                                for (DB_id3v2_frame_t *f = tag.frames; f; f = f->next) {
                                    if (!strcmp (f->id, "APIC")) {
                                        if (f->size < 20) {
                                            trace ("artwork: id3v2 APIC frame is too small\n");
                                            continue;
                                        }

                                        uint8_t *data = f->data;

                                        if (tag.version[0] == 4 && (f->flags[1] & 1)) {
                                            data += 4;
                                        }
#if 0
                                        printf ("version: %d, flags: %d %d\n", (int)tag.version[0], (int)f->flags[0], (int)f->flags[1]);
                                        for (int i = 0; i < 20; i++) {
                                            printf ("%c", data[i] < 0x20 ? '?' : data[i]);
                                        }
                                        printf ("\n");
                                        for (int i = 0; i < 20; i++) {
                                            printf ("%02x ", data[i]);
                                        }
                                        printf ("\n");
#endif
                                        uint8_t *end = f->data + f->size;
                                        int enc = *data;
                                        data++; // enc
                                        // mime-type must always be ASCII - hence enc is 0 here
                                        uint8_t *mime_end = id3v2_skip_str (enc, data, end);
                                        if (!mime_end) {
                                            trace ("artwork: corrupted id3v2 APIC frame\n");
                                            continue;
                                        }
                                        if (strcasecmp (data, "image/jpeg") && strcasecmp (data, "image/png") && strcasecmp (data, "image/gif")) {
                                            trace ("artwork: unsupported mime type: %s\n", data);
                                            continue;
                                        }
                                        if (*mime_end != 3) {
                                            trace ("artwork: picture type=%d\n", *mime_end);
                                            continue;
                                        }
                                        trace ("artwork: mime-type=%s, picture type: %d\n", data, *mime_end);
                                        data = mime_end;
                                        data++; // picture type
                                        data = id3v2_skip_str (enc, data, end); // description
                                        if (!data) {
                                            trace ("artwork: corrupted id3v2 APIC frame\n");
                                            continue;
                                        }
                                        int sz = f->size - (data - f->data);

                                        char tmp_path[1024];
                                        trace ("will write id3v2 APIC into %s\n", cache_path);
                                        snprintf (tmp_path, sizeof (tmp_path), "%s.part", cache_path);
                                        FILE *out = fopen (tmp_path, "w+b");
                                        if (!out) {
                                            trace ("artwork: failed to open %s for writing\n", tmp_path);
                                            break;
                                        }
                                        if (fwrite (data, 1, sz, out) != sz) {
                                            trace ("artwork: failed to write id3v2 picture into %s\n", tmp_path);
                                            fclose (out);
                                            unlink (tmp_path);
                                            break;
                                        }
                                        fclose (out);
                                        int err = rename (tmp_path, cache_path);
                                        if (err != 0) {
                                            trace ("Failed not move %s to %s: %s\n", tmp_path, cache_path, strerror (err));
                                            unlink (tmp_path);
                                            break;
                                        }
                                        unlink (tmp_path);
                                        got_pic = 1;
                                        break;
                                    }
                                }
                            }

                            deadbeef->junk_id3v2_free (&tag);
                            current_file = NULL;
                            deadbeef->fclose (fp);
                        }
                    }

                    // try to load embedded from apev2
                    {
                        trace ("trying to load artwork from apev2 tag for %s\n", param->fname);
                        DB_apev2_tag_t tag;
                        memset (&tag, 0, sizeof (tag));
                        DB_FILE *fp = deadbeef->fopen (param->fname);
                        current_file = fp;
                        if (fp) {
                            int res = deadbeef->junk_apev2_read_full (NULL, &tag, fp);
                            if (!res) {
                                for (DB_apev2_frame_t *f = tag.frames; f; f = f->next) {
                                    if (!strcasecmp (f->key, "cover art (front)")) {
                                        uint8_t *name = f->data, *ext = f->data, *data = f->data;
                                        uint8_t *end = f->data + f->size;
                                        while (data < end && *data)
                                            data++;
                                        if (data == end) {
                                            trace ("artwork: apev2 cover art frame has no name\n");
                                            continue;
                                        }
                                        int sz = end - ++data;
                                        if (sz < 20) {
                                            trace ("artwork: apev2 cover art frame is too small\n");
                                            continue;
                                        }
                                        ext = strrchr (name, '.');
                                        if (!ext || !*++ext) {
                                            trace ("artwork: apev2 cover art name has no extension\n");
                                            continue;
                                        }
                                        if (strcasecmp (ext, "jpeg") && strcasecmp (ext, "jpg") && strcasecmp (ext, "png")) {
                                            trace ("artwork: unsupported file type: %s\n", ext);
                                            continue;
                                        }
                                        trace ("found apev2 cover art of %d bytes (%s)\n", sz, ext);
                                        char tmp_path[1024];
                                        char cache_path[1024];
                                        make_cache_path2 (cache_path, sizeof (cache_path), param->fname, param->album, param->artist, -1);
                                        trace ("will write apev2 cover art into %s\n", cache_path);
                                        snprintf (tmp_path, sizeof (tmp_path), "%s.part", cache_path);
                                        FILE *out = fopen (tmp_path, "w+b");
                                        if (!out) {
                                            trace ("artwork: failed to open %s for writing\n", tmp_path);
                                            break;
                                        }
                                        if (fwrite (data, 1, sz, out) != sz) {
                                            trace ("artwork: failed to write apev2 picture into %s\n", tmp_path);
                                            fclose (out);
                                            unlink (tmp_path);
                                            break;
                                        }
                                        fclose (out);
                                        int err = rename (tmp_path, cache_path);
                                        if (err != 0) {
                                            trace ("Failed not move %s to %s: %s\n", tmp_path, cache_path, strerror (err));
                                            unlink (tmp_path);
                                            break;
                                        }
                                        unlink (tmp_path);
                                        got_pic = 1;
                                        break;
                                    }
                                }
                            }

                            deadbeef->junk_apev2_free (&tag);
                            current_file = NULL;
                            deadbeef->fclose (fp);
                        }
                    }

#ifdef USE_METAFLAC
                    // try to load embedded from flac metadata
                    for (;;)
                    {
                        const char *filename = param->fname;
                        FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new();
                        int is_ogg = 0;
                        if(strlen(filename) >= 4 && (0 == strcmp(filename+strlen(filename)-4, ".oga") || 0 == strcasecmp(filename+strlen(filename)-4, ".ogg"))) {
                            is_ogg = 1;
                        }

                        DB_FILE *file = deadbeef->fopen (filename);
                        if (!file) {
                            break;
                        }

                        int res = 0;
                        if (is_ogg) {
#if USE_OGG
                            res = FLAC__metadata_chain_read_ogg_with_callbacks(chain, (FLAC__IOHandle)file, iocb);
#endif
                        }
                        else
                        {
                            res = FLAC__metadata_chain_read_with_callbacks(chain, (FLAC__IOHandle)file, iocb);
                        }

                        if(!res) {
                            trace ("artwork: failed to read metadata from flac: %s\n", filename);
                            deadbeef->fclose (file);
                            FLAC__metadata_chain_delete(chain);
                            break;
                        }
                        deadbeef->fclose (file);
                        FLAC__StreamMetadata *picture = 0;
                        FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new();
                        FLAC__metadata_iterator_init(iterator, chain);

                        do {
                            FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(iterator);
                            if(block->type == FLAC__METADATA_TYPE_PICTURE) {
                                picture = block;
                            }
                        } while(FLAC__metadata_iterator_next(iterator) && 0 == picture);

                        if (!picture) {
                            trace ("%s doesn't have an embedded cover\n", param->fname);
                            break;
                        }
                        FLAC__StreamMetadata_Picture *pic = &picture->data.picture;
                        trace ("found flac cover art of %d bytes (%s)\n", pic->data_length, pic->description);
                        char tmp_path[1024];
                        char cache_path[1024];
                        make_cache_path2 (cache_path, sizeof (cache_path), param->fname, param->album, param->artist, -1);
                        trace ("will write flac cover art into %s\n", cache_path);
                        snprintf (tmp_path, sizeof (tmp_path), "%s.part", cache_path);
                        FILE *out = fopen (tmp_path, "w+b");
                        if (!out) {
                            trace ("artwork: failed to open %s for writing\n", tmp_path);
                            break;
                        }
                        if (fwrite (pic->data, 1, pic->data_length, out) != pic->data_length) {
                            trace ("artwork: failed to write flac picture into %s\n", tmp_path);
                            fclose (out);
                            unlink (tmp_path);
                            break;
                        }
                        fclose (out);
                        int err = rename (tmp_path, cache_path);
                        if (err != 0) {
                            trace ("Failed not move %s to %s: %s\n", tmp_path, cache_path, strerror (err));
                            unlink (tmp_path);
                            break;
                        }
                        unlink (tmp_path);
                        got_pic = 1;

                        if (chain) {
                            FLAC__metadata_chain_delete(chain);
                        }
                        if (iterator) {
                            FLAC__metadata_iterator_delete(iterator);
                        }
                        break;
                    }
#endif
                }

                if (!got_pic && artwork_enable_local) {
                    /* Searching in track directory */
                    strncpy (path, param->fname, sizeof (path));
                    char *slash = strrchr (path, '/');
                    if (slash) {
                        *slash = 0; // assuming at least one slash exist
                    }
                    trace ("scanning directory: %s\n", path);
                    char mask[200] = "";
                    char *p = artwork_filemask;
                    while (p) {
                        *mask = 0;
                        char *e = strchr (p, ';');
                        if (e) {
                            strncpy (mask, p, e-p);
                            mask[e-p] = 0;
                            e++;
                        }
                        else {
                            strcpy (mask, p);
                        }
                        if (*mask) {
                            filter_custom_mask = mask;
                            files_count = scandir (path, &files, filter_custom, NULL);
                            if (files_count != 0) {
                                break;
                            }
                        }
                        p = e;
                    }
                    if (files_count == 0) {
                        files_count = scandir (path, &files, filter_jpg, alphasort);
                    }

                    if (files_count > 0) {
                        trace ("found cover for %s - %s in local folder\n", param->artist, param->album);
                        strcat (path, "/");
                        strcat (path, files[0]->d_name);
                        char cache_path[PATH_MAX];
                        char tmp_path[PATH_MAX];
                        char cache_path_dir[PATH_MAX];
                        make_cache_path2 (cache_path, sizeof (cache_path), param->fname, param->album, param->artist, -1);
                        strcpy (cache_path_dir, cache_path);
                        char *slash = strrchr (cache_path_dir, '/');
                        if (slash) {
                            *slash = 0;
                        }
                        trace ("check_dir: %s\n", cache_path_dir);
                        if (check_dir (cache_path_dir, 0755)) {
                            snprintf (tmp_path, sizeof (tmp_path), "%s.part", cache_path);
                            copy_file (path, tmp_path, -1);
                            int err = rename (tmp_path, cache_path);
                            if (err != 0) {
                                trace ("artwork: rename error %d: failed to move %s to %s: %s\n", err, tmp_path, cache_path, strerror (err));
                                unlink (tmp_path);
                            }
                            int i;
                            for (i = 0; i < files_count; i++) {
                                free (files [i]);
                            }
                            got_pic = 1;
                        }
                    }
                }
            }

#ifdef USE_VFS_CURL
            if (!got_pic) {
                if (artwork_enable_wos) {

                    char *dot = strrchr (param->fname, '.');
                    if (dot && !strcasecmp (dot, ".ay") && !fetch_from_wos (param->album, cache_path)) {
                        got_pic = 1;
                    }
                }
                if (!got_pic && artwork_enable_lfm) {
                    if (!fetch_from_lastfm (param->artist, param->album, cache_path)) {
                        got_pic = 1;
                    }
                    else {
                        // try to fix parentheses
                        char *fixed_alb = strdupa (param->album);
                        char *openp = strchr (fixed_alb, '(');
                        if (openp && openp != fixed_alb) {
                            *openp = 0;
                            if (!fetch_from_lastfm (param->artist, fixed_alb, cache_path)) {
                                got_pic = 1;
                            }
                        }
                    }
                }
                if (!got_pic && artwork_enable_aao && !fetch_from_albumart_org (param->artist, param->album, cache_path)) {
                    got_pic = 1;
                }
            }
#endif

            if (got_pic) {
                trace ("downloaded art for %s %s\n", param->album, param->artist);
                if (param->size != -1) {
                    make_cache_dir_path (path, sizeof (path), param->artist, param->size);
                    trace ("cache folder: %s\n", path);
                    if (!check_dir (path, 0755)) {
                        trace ("failed to create folder %s\n", path);
                        queue_pop ();
                        continue;
                    }
                    char scaled_path[1024];
                    make_cache_path2 (scaled_path, sizeof (scaled_path), param->fname, param->album, param->artist, param->size);
                    copy_file (cache_path, scaled_path, param->size);
                }
                for (int i = 0; i < param->numcb; i++) {
                    if (param->callbacks[i].cb) {
                        param->callbacks[i].cb (param->fname, param->artist, param->album, param->callbacks[i].ud);
                        param->callbacks[i].cb = NULL;
                    }
                }
            }
            queue_pop ();
        }
        if (clear_queue) {
            trace ("artwork: received queue clear request\n");
            while (queue) {
                queue_pop ();
            }
            clear_queue = 0;
            trace ("artwork: queue clear done\n");
            continue;
        }
        if (terminate) {
            break;
        }
    }
}

static char *
find_image (const char *path) {
    struct stat stat_buf;
    if (0 == stat (path, &stat_buf)) {
        int cache_period = deadbeef->conf_get_int ("artwork.cache.period", 48);
        time_t tm = time (NULL);
        // invalidate cache every 2 days
        if ((cache_period > 0 && (tm - stat_buf.st_mtime > cache_period * 60 * 60))
                || artwork_reset_time > stat_buf.st_mtime) {
            trace ("deleting cached file %s\n", path);
            unlink (path);
            return NULL;
        }

        return strdup (path);
    }
    return NULL;
}

static char*
get_album_art (const char *fname, const char *artist, const char *album, int size, artwork_callback callback, void *user_data)
{
    char path [1024];

    make_cache_path2 (path, sizeof (path), fname, album, artist, size);
    char *p = find_image (path);
    if (p) {
        if (callback) {
            callback (NULL, NULL, NULL, user_data);
        }
        return p;
    }

    if (size != -1) {
        // check if we have unscaled image
        char unscaled_path[1024];
        make_cache_path2 (unscaled_path, sizeof (unscaled_path), fname, album, artist, -1);
        p = find_image (unscaled_path);
        if (p) {
            free (p);
            char dir[1024];
            make_cache_dir_path (dir, sizeof (dir), artist, size);
            if (!check_dir (dir, 0755)) {
                trace ("failed to create folder for %s\n", dir);
            }
            else {
                int res = copy_file (unscaled_path, path, size);
                if (!res) {
                    if (callback) {
                        callback (NULL, NULL, NULL, user_data);
                    }
                    return strdup (path);
                }
            }
        }
    }

    queue_add (fname, artist, album, size, callback, user_data);
    return NULL;
}

static void
sync_callback (const char *fname, const char *artist, const char *album, void *user_data) {
    mutex_cond_t *mc = (mutex_cond_t *)user_data;
    deadbeef->mutex_lock (mc->mutex);
    deadbeef->cond_signal (mc->cond);
    deadbeef->mutex_unlock (mc->mutex);
}

static char*
get_album_art_sync (const char *fname, const char *artist, const char *album, int size) {
    mutex_cond_t mc;
    mc.mutex = deadbeef->mutex_create ();
    mc.cond = deadbeef->cond_create ();
    deadbeef->mutex_lock (mc.mutex);
    char *image_fname = get_album_art (fname, artist, album, size, sync_callback, &mc);
    while (!image_fname) {
        deadbeef->cond_wait (mc.cond, mc.mutex);
        image_fname = get_album_art (fname, artist, album, size, sync_callback, &mc);
    }
    deadbeef->mutex_unlock (mc.mutex);
    deadbeef->mutex_free (mc.mutex);
    deadbeef->cond_free (mc.cond);
    return image_fname;
}

static void
artwork_reset (int fast) {
    if (fast) {
//        if (current_file) {
//            deadbeef->fabort (current_file);
//        }
        deadbeef->mutex_lock (mutex);
        while (queue && queue->next) {
            cover_query_t *next = queue->next->next;
            free (queue->next->fname);
            free (queue->next->artist);
            free (queue->next->album);
            for (int i = 0; i < queue->next->numcb; i++) {
                if (queue->next->callbacks[i].cb == sync_callback) {
                    sync_callback (NULL, NULL, NULL, queue->next->callbacks[i].ud);
                }
            }
            queue->next = next;
            if (next == NULL) {
                queue_tail = queue;
            }
        }
        deadbeef->mutex_unlock (mutex);
    }
    else {
        trace ("artwork: reset\n");
        clear_queue = 1;
        deadbeef->cond_signal (cond);
        trace ("artwork: waiting for clear to complete\n");
        while (clear_queue) {
            usleep (100000);
        }
    }
}

static int
artwork_configchanged (void) {
    int new_artwork_enable_embedded = deadbeef->conf_get_int ("artwork.enable_embedded", 1);
    int new_artwork_enable_local = deadbeef->conf_get_int ("artwork.enable_localfolder", 1);
#ifdef USE_VFS_CURL
    int new_artwork_enable_lfm = deadbeef->conf_get_int ("artwork.enable_lastfm", 0);
    int new_artwork_enable_aao = deadbeef->conf_get_int ("artwork.enable_albumartorg", 0);
    int new_artwork_enable_wos = deadbeef->conf_get_int ("artwork.enable_wos", 0);
#endif

    char new_artwork_filemask[200];
    deadbeef->conf_get_str ("artwork.filemask", DEFAULT_FILEMASK, new_artwork_filemask, sizeof (new_artwork_filemask));

    if (new_artwork_enable_embedded != artwork_enable_embedded
            || new_artwork_enable_local != artwork_enable_local
#ifdef USE_VFS_CURL
            || new_artwork_enable_lfm != artwork_enable_lfm
            || new_artwork_enable_aao != artwork_enable_aao
            || new_artwork_enable_wos != artwork_enable_wos
#endif
            || strcmp (new_artwork_filemask, artwork_filemask)) {
        trace ("artwork config changed, invalidating cache...\n");
        artwork_enable_embedded = new_artwork_enable_embedded;
        artwork_enable_local = new_artwork_enable_local;
#ifdef USE_VFS_CURL
        artwork_enable_lfm = new_artwork_enable_lfm;
        artwork_enable_aao = new_artwork_enable_aao;
        artwork_enable_wos = new_artwork_enable_wos;
#endif
        artwork_reset_time = time (NULL);
        strcpy (artwork_filemask, new_artwork_filemask);
        deadbeef->conf_set_int64 ("artwork.cache_reset_time", artwork_reset_time);
        artwork_reset (0);
        deadbeef->sendmessage (DB_EV_PLAYLIST_REFRESH, 0, 0, 0);
    }

    return 0;
}

static int
artwork_message (uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) {
    switch (id) {
    case DB_EV_CONFIGCHANGED:
        artwork_configchanged ();
        break;
    }
    return 0;
}

static int
artwork_plugin_start (void)
{
    deadbeef->conf_lock ();

    const char *def_art = deadbeef->conf_get_str_fast ("gtkui.nocover_pixmap", NULL);
    if (!def_art) {
        snprintf (default_cover, sizeof (default_cover), "%s/noartwork.png", deadbeef->get_pixmap_dir ());
    }
    else {
        strcpy (default_cover, def_art);
    }
    terminate = 0;

    artwork_enable_embedded = deadbeef->conf_get_int ("artwork.enable_embedded", 1);
    artwork_enable_local = deadbeef->conf_get_int ("artwork.enable_localfolder", 1);
#ifdef USE_VFS_CURL
    artwork_enable_lfm = deadbeef->conf_get_int ("artwork.enable_lastfm", 0);
    artwork_enable_aao = deadbeef->conf_get_int ("artwork.enable_albumartorg", 0);
    artwork_enable_wos = deadbeef->conf_get_int ("artwork.enable_wos", 0);
#endif
    artwork_reset_time = deadbeef->conf_get_int64 ("artwork.cache_reset_time", 0);

    deadbeef->conf_get_str ("artwork.filemask", DEFAULT_FILEMASK, artwork_filemask, sizeof (artwork_filemask));

    deadbeef->conf_unlock ();

    artwork_filemask[sizeof(artwork_filemask)-1] = 0;

    mutex = deadbeef->mutex_create_nonrecursive ();
#ifdef USE_IMLIB2
    imlib_mutex = deadbeef->mutex_create_nonrecursive ();
#endif
    cond = deadbeef->cond_create ();
    tid = deadbeef->thread_start_low_priority (fetcher_thread, NULL);

    return 0;
}

static int
artwork_plugin_stop (void)
{
    if (current_file) {
        deadbeef->fabort (current_file);
    }
    if (tid) {
        terminate = 1;
        deadbeef->cond_signal (cond);
        deadbeef->thread_join (tid);
        tid = 0;
    }
    while (queue) {
        queue_pop ();
    }
    if (mutex) {
        deadbeef->mutex_free (mutex);
        mutex = 0;
    }
#ifdef USE_IMLIB2
    if (imlib_mutex) {
        deadbeef->mutex_free (imlib_mutex);
        imlib_mutex = 0;
    }
#endif
    if (cond) {
        deadbeef->cond_free (cond);
        cond = 0;
    }

    return 0;
}

static const char settings_dlg[] =
    "property \"Cache update period (hr)\" entry artwork.cache.period 48;\n"
    "property \"Fetch from embedded tags\" checkbox artwork.enable_embedded 1;\n"
    "property \"Fetch from local folder\" checkbox artwork.enable_localfolder 1;\n"
    "property \"Local cover file mask\" entry artwork.filemask \"" DEFAULT_FILEMASK "\";\n"
#ifdef USE_VFS_CURL
    "property \"Fetch from last.fm\" checkbox artwork.enable_lastfm 0;\n"
    "property \"Fetch from albumart.org\" checkbox artwork.enable_albumartorg 0;\n"
    "property \"Fetch from worldofspectrum.org (AY only)\" checkbox artwork.enable_wos 0;\n"
#endif
    "property \"Scale artwork towards longer side\" checkbox artwork.scale_towards_longer 1;\n"
;

// define plugin interface
static DB_artwork_plugin_t plugin = {
    .plugin.plugin.api_vmajor = 1,
    .plugin.plugin.api_vminor = 0,
    .plugin.plugin.version_major = 1,
    .plugin.plugin.version_minor = 2,
    .plugin.plugin.type = DB_PLUGIN_MISC,
    .plugin.plugin.id = "artwork",
    .plugin.plugin.name = "Album Artwork",
    .plugin.plugin.descr = "Loads album artwork either from local directories or from internet",
    .plugin.plugin.copyright =
        "Album Art plugin for DeaDBeeF\n"
        "Copyright (C) 2009-2011 Viktor Semykin <thesame.ml@gmail.com>\n"
        "Copyright (C) 2009-2013 Alexey Yakovenko <waker@users.sourceforge.net>\n"
        "\n"
        "This software is provided 'as-is', without any express or implied\n"
        "warranty.  In no event will the authors be held liable for any damages\n"
        "arising from the use of this software.\n"
        "\n"
        "Permission is granted to anyone to use this software for any purpose,\n"
        "including commercial applications, and to alter it and redistribute it\n"
        "freely, subject to the following restrictions:\n"
        "\n"
        "1. The origin of this software must not be misrepresented; you must not\n"
        " claim that you wrote the original software. If you use this software\n"
        " in a product, an acknowledgment in the product documentation would be\n"
        " appreciated but is not required.\n"
        "\n"
        "2. Altered source versions must be plainly marked as such, and must not be\n"
        " misrepresented as being the original software.\n"
        "\n"
        "3. This notice may not be removed or altered from any source distribution.\n"
    ,
    .plugin.plugin.website = "http://deadbeef.sf.net",
    .plugin.plugin.start = artwork_plugin_start,
    .plugin.plugin.stop = artwork_plugin_stop,
    .plugin.plugin.configdialog = settings_dlg,
    .plugin.plugin.message = artwork_message,
    .get_album_art = get_album_art,
    .reset = artwork_reset,
    .get_default_cover = get_default_cover,
    .get_album_art_sync = get_album_art_sync,
    .make_cache_path = make_cache_path,
    .make_cache_path2 = make_cache_path2,
};

DB_plugin_t *
artwork_load (DB_functions_t *api) {
    deadbeef = api;
    return DB_PLUGIN (&plugin);
}

