/*
 *  output-tarball.c
 *  mod_musicindex
 *
 *  $Id: output-tarball.c 1023 2013-04-09 11:13:16Z varenet $
 *
 *  Created by Thibaut VARENE on Mon Jun 26 2006.
 *  Copyright (c) 2006-2008,2012-2013 Thibaut VARENE
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License version 2.1,
 *  as published by the Free Software Foundation.
 *
 */

/**
 * @file
 * Tarballs generation
 *
 * @author Thibaut Varene
 * @version $Revision: 1023 $
 * @date 2006-2008
 * @date 2012-2013
 *
 * This file handles on the fly tarball generation and associated operations
 *
 * @todo maybe use apr_file I/O?
 */

/* build flag: -D_FILE_OFFSET_BITS=64 - libarchive goes AWOL without it */
#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 64
#endif

#include "mod_musicindex.h"

#include <http_core.h>
#include <archive.h>	/* libarchive */
#include <archive_entry.h>
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>	/* stat */
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>	/* stat */
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>	/* stat */
#endif
#include <stdio.h>	/* fopen... */

#define OT_SEND_DATA	0	/**< trigger data sending in tarball_op() */
#define OT_SEND_SIZE	1	/**< trigger size report in tarball_op() */

#define RWBUFSIZE	8192	/**< Size of our r/w buffer */
/** Skip file if ... */
#define QSKIP_CHECKS(q)	(!(q->flags & EF_ALLOWTARBALL))


/** Holds our client data for libarchive */
struct cdata {
	request_rec *r;	/**< Apache request_rec */
};

static int
wrapopen(struct archive *a, void *client_data)
{
	return (ARCHIVE_OK);
}

static ssize_t
wrapwrite(struct archive *a, void *client_data, const void *buff, size_t n)
{
	struct cdata *mydata = client_data;

	return (ap_rwrite(buff, n, mydata->r));
}

static ssize_t
wrapwritesize(struct archive *a, void *client_data, const void *buff, size_t n)
{
	ssize_t *mysize = client_data;
	*mysize += n;
	return n;
}
/**
 * Triggers tarball operation.
 *
 * @param r Apache request_rec struct to handle connection details.
 * @param pack A pack of music entries.
 * @param op Type of operation requested.
 * @return the size of the tarball that send_tarball() would generate
 *	for the given set of files. Only valid with #op == OT_SEND_SIZE.
 *
 * It is a known "feature" that when tar'ing a custom playlist, the generated
 * tarball will contain the full path as published on the webserver, including
 * '/'. We could get rid of the leading '/' if needed, but there's one good
 * reason not to put all the files at the root of one big tarball: in case there
 * are name doublons.
 */
static ssize_t
tarball_op(request_rec *r, const mu_pack *const pack, int op)
{
	const mu_ent *q = pack->fhead;
	ssize_t tbsize = 0;
	struct cdata mydata;
	struct archive *a;
	struct archive_entry *entry;
	struct stat st;
	static char buff[RWBUFSIZE];
	int len;
	FILE *file;
	off_t fsize;

	if (!q)
		return 0;

	if (!(a = archive_write_new()))
		return 0;
	
	if (!(entry = archive_entry_new()))
		goto fail;

	archive_write_set_compression_none(a);
	archive_write_set_format_ustar(a);

	if (op == OT_SEND_DATA) {
		mydata.r = r;
		archive_write_open(a, &mydata, wrapopen, wrapwrite, NULL);
	}
	else /* OT_SEND_SIZE */
		archive_write_open(a, &tbsize, NULL, wrapwritesize, NULL);
	
	for (; q; q = q->next) {
		if (unlikely(QSKIP_CHECKS(q)))
			continue;

		stat(q->filename, &st);
		archive_entry_clear(entry);
		archive_entry_copy_stat(entry, &st);
		archive_entry_set_pathname(entry, q->file);
		archive_write_header(a, entry);

		if (op == OT_SEND_DATA) {
			file = fopen(q->filename, "r");
			len = fread(buff, 1, sizeof(buff), file);
			/* NB: size of buffer is 3rd arg, 1-byte long elements, otherwise everything fucks up */
			while (len > 0) {
				archive_write_data(a, buff, len);
				len = fread(buff, 1, sizeof(buff), file);
			}
			fclose(file);
		}
		else {	/* OT_SEND_SIZE */
			fsize = q->size;
			len = (likely(fsize > sizeof(buff)) ? sizeof(buff) : fsize);
			while (len > 0) {
				fsize -= archive_write_data(a, buff, len);
				len = (likely(fsize > sizeof(buff)) ? sizeof(buff) : fsize);
			}
		}
	}

	archive_entry_free(entry);
fail:
	archive_write_finish(a);

	return tbsize;
}

/**
 * Sends tarball.
 *
 * @param r Apache request_rec struct to handle connection details.
 * @param pack A pack of music entries.
 */
void send_tarball(request_rec *r, const mu_pack *const pack)
{
	tarball_op(r, pack, OT_SEND_DATA);
}

/**
 * Compute tarball size
 *
 * @param r Apache request_rec struct to handle connection details.
 * @param pack A pack of music entries.
 * @return the size of the tarball that send_tarball() would generate
 *	for the given set of files.
 */
ssize_t
tarball_size(request_rec *r, const mu_pack *const pack)
{
	return tarball_op(r, pack, OT_SEND_SIZE);
}
