/******************************************************************************
 * mod_uploader / MmapFileWriter.cpp
 ******************************************************************************
 * Copyright (C) 2005 Tetsuya Kimata <kimata@acapulco.dyndns.org>
 *
 * All rights reserved.
 *
 * 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.
 *
 * $Id: MmapFileWriter.cpp 548 2005-08-31 20:29:04Z svn $
 *****************************************************************************/

#include "MmapFileWriter.h"
#include "Misc.h"

#include "apr_mmap.h"
#define APR_WANT_MEMFUNC
#include "apr_want.h"

#if defined(HAVE_UNISTD_H) && !defined(WIN32)
#include <unistd.h>
#endif

#ifdef DEBUG
#include <iostream>
#endif

const apr_size_t MmapFileWriter::BLOCK_SIZE     = MMAP_BLOCK_SIZE;
#if defined(HAVE_GETPAGESIZE) && !defined(WIN32)
const apr_size_t MmapFileWriter::PAGE_SIZE      = getpagesize();
#else
const apr_size_t MmapFileWriter::PAGE_SIZE      = MMAP_PAGE_SIZE;
#endif


/******************************************************************************
 * public メソッド
 *****************************************************************************/
MmapFileWriter::MmapFileWriter(apr_pool_t *pool, apr_file_t *fd, apr_size_t offset)
    : FileWriter(pool, fd),
      file_map_(NULL),
      size_factor_(0),
      // 一応ページサイズの倍数単位で割り当てる
      block_size_(((BLOCK_SIZE-1)/PAGE_SIZE + 1) * PAGE_SIZE),
      offset_(0),
      is_closed_(false)
{
    expand();
    offset_ = offset;
}

void MmapFileWriter::write(const void *buf, apr_size_t size)
{
    const char *data;
    apr_size_t write_size;

    data = static_cast<const char *>(buf);

    while (size > 0) {
        write_size = (size > block_size_) ? block_size_ : size;

        if (write_size > (block_size_-offset_)) {
            if (offset_ != block_size_) {
                write_size = block_size_ - offset_;

                memcpy(static_cast<char *>(file_map_->mm)+offset_, data, write_size);
                data += write_size;
                size -= write_size;
            }
            expand();

            continue;
        }

        memcpy(static_cast<char *>(file_map_->mm)+offset_, data, write_size);

        data += write_size;
        offset_ += write_size;
        size -= write_size;
    }
}

void MmapFileWriter::close()
{
    if (is_closed_) {
        return;
    }

    if (file_map_ != NULL) {
        if (apr_mmap_delete(file_map_) != APR_SUCCESS) {
            throw "ファイルの unmap に失敗しました．";
        }
    }
    if (apr_file_trunc(get_fd(), block_size_*(size_factor_-1)+offset_) != APR_SUCCESS) {
        throw "ファイルの truncate に失敗しました．";
    }
    FileWriter::close();

    is_closed_ = true;
}

MmapFileWriter::~MmapFileWriter()
{
    close();
}


/******************************************************************************
 * private メソッド
 *****************************************************************************/
void MmapFileWriter::expand()
{
    apr_off_t pseudo_size;
    apr_off_t offset;

    if (file_map_ != NULL) {
        if (apr_mmap_delete(file_map_) != APR_SUCCESS) {
            throw "ファイルの unmap に失敗しました．";
        }
    }

    size_factor_++;
    offset = pseudo_size = block_size_*size_factor_;

    if ((apr_file_seek(get_fd(), APR_SET, &offset) != APR_SUCCESS) ||
        (offset != pseudo_size)) {
        throw "ファイルの seek に失敗しました．";
    }

    if (apr_file_putc('*', get_fd()) != APR_SUCCESS) {
        throw "ファイルの write に失敗しました．";
    }

    if (apr_mmap_create(&file_map_, get_fd(), pseudo_size-block_size_,
                        block_size_, APR_MMAP_READ|APR_MMAP_WRITE, get_pool()) != APR_SUCCESS) {
        throw "ファイルの map に失敗しました．";
    }

    offset_ = 0;
}


/******************************************************************************
 * テスト
 *****************************************************************************/
#ifdef DEBUG_MmapFileWriter
#include "apr_general.h"

static const apr_size_t BUFSIZE     = 1234;

void usage(const char *prog_name)
{
    cout << "Usage: " << prog_name << " <SRC> <DST>" << endl;
}

int main(int argc, const char * const *argv)
{
    apr_pool_t *pool;
    apr_file_t *src;
    apr_file_t *dst;
    char buffer[BUFSIZE];
    apr_size_t size = sizeof(buffer);

    apr_app_initialize(&argc, &argv, NULL);
    apr_pool_create(&pool, NULL);

    try {
        if (argc != 3) {
            throw "コピー元とコピー先のファイルが指定されていません．";
        }

        if (apr_file_open(&src, argv[1], APR_READ|APR_BINARY,
                          APR_OS_DEFAULT, pool) != APR_SUCCESS) {
            throw "コピー元のファイルを開けませんでした．";
        }

        if (apr_file_open(&dst, argv[2],
                          APR_READ|APR_WRITE|APR_CREATE|APR_EXCL|APR_BINARY,
                          APR_OS_DEFAULT, pool) != APR_SUCCESS) {
            throw "コピー先のファイルを開けませんでした．";
        }

        MmapFileWriter writer(pool, dst);
        while (apr_file_read(src, buffer, &size) == APR_SUCCESS) {
            writer.write(buffer, size);
            size = sizeof(buffer);
        }

        apr_file_close(dst);
        apr_file_close(src);
    } catch(const char *message) {
        cerr << "Error: " << message << endl;
        usage(argv[0]);

        return EXIT_FAILURE;
    }

    apr_terminate();

    return EXIT_SUCCESS;
}
#endif

// Local Variables:
// mode: c++
// buffer-file-coding-system: euc-japan-dos
// End:
