/******************************************************************************
 * mod_uploader / UploaderConfig.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: UploaderConfig.cpp 694 2005-09-17 13:54:06Z svn $
 *****************************************************************************/

#define __STDC_CONSTANT_MACROS

#include "mod_uploader_data.h"

#include "UploaderConfig.h"
#include "Misc.h"

#include "apr_strings.h"

#ifdef DEBUG
#include <iostream>
#endif

#undef DELETE
#define DELETE(ptr) if (ptr != NULL) { delete ptr; ptr = NULL;}
#define DESTROY(pool) if (pool != NULL) { apr_pool_destroy(pool); pool = NULL;}

const apr_uint64_t UploaderConfig::TOTAL_FILE_SIZE_LIMIT= 1024*1024*1024;
const apr_size_t UploaderConfig::TOTAL_FILE_NUMBER_LIMIT= 1000;
const apr_size_t UploaderConfig::MAX_FORM_FILE_SIZE     = 100*1024*1024;
const apr_size_t UploaderConfig::MAX_FORM_TEXT_SIZE     = 1024;
const apr_size_t UploaderConfig::MAX_FORM_ITEM_NUMBER   = 5;
const apr_size_t UploaderConfig::PER_PAGE_ITEM_NUMBER   = 50;
const apr_size_t UploaderConfig::ITEM_RELOAD_THRESHOLD  = 10;

static apr_status_t finalize_config(void *data);
#if APR_HAS_THREADS
static apr_status_t finalize_lock(void *data);
#endif


/******************************************************************************
 * public メソッド
 *****************************************************************************/
UploaderConfig::UploaderConfig()
    : url(""),
      file_dir(NULL),
      thumb_dir(NULL),
      tmp_dir(NULL),
      magick_dir(NULL),
      view_tmpl_path(NULL),
      progress_tmpl_path(NULL),
      download_tmpl_path(NULL),
      thumb_tmpl_path(NULL),
      error_tmpl_path(NULL),
      total_file_size_limit(TOTAL_FILE_SIZE_LIMIT),
      total_file_number_limit(TOTAL_FILE_NUMBER_LIMIT),
      max_file_size(MAX_FORM_FILE_SIZE),
      per_page_item_number(PER_PAGE_ITEM_NUMBER),
      remove_item_number(0),
      item_list(NULL),
      texecutor(NULL),
      uparser(NULL),
      view_tmpl_(NULL),
      view_tmpl_pool_(NULL),
      progress_tmpl_(NULL),
      progress_tmpl_pool_(NULL),
      download_tmpl_(NULL),
      download_tmpl_pool_(NULL),
      thumb_tmpl_(NULL),
      thumb_tmpl_pool_(NULL),
      error_tmpl_(NULL),
      error_tmpl_pool_(NULL),
      list_pool_(NULL),
      glock_(NULL),
#if APR_HAS_THREADS
      lock_(NULL),
#endif
      is_init_(false)
{

}

UploaderConfig::~UploaderConfig()
{
    finalize();
}

void UploaderConfig::init(request_rec *r)
{
    if (LIKELY(is_init_)) {
        return update_list(r->pool);
    }

    pool_ = r->server->process->pool;

    // 本当は次のコードは mod_uploader.cpp に置きたいけど，パフォーマン
    // スを考えてここに落ち着いた．
    glock_ = (static_cast<sconfig *>(ap_get_module_config(r->server->module_config,
                                                          &uploader_module)))->upload_lock;

    global_lock();
    try {
        if (!is_init_) {
            exec_init();
        }
        global_unlock();
    } catch(const char *) {
        global_unlock();
        throw;
    }
}

void UploaderConfig::finalize()
{
    DELETE(uparser);
    DELETE(texecutor);
    DELETE(item_list);
    DELETE(view_tmpl_);
    DELETE(progress_tmpl_);
    DELETE(download_tmpl_);
#ifdef MAKE_THUMBNAIL
    DELETE(thumb_tmpl_);
#endif
    DELETE(error_tmpl_);

    DESTROY(list_pool_);
    DESTROY(view_tmpl_pool_);
    DESTROY(progress_tmpl_pool_);
    DESTROY(download_tmpl_pool_);
#ifdef MAKE_THUMBNAIL
    DESTROY(thumb_tmpl_pool_);
#endif
    DESTROY(error_tmpl_pool_);
}

UploaderConfig::page_template *UploaderConfig::get_view_template(apr_pool_t *pool)
{
    if (UNLIKELY(get_mtime(pool, view_tmpl_path) > view_tmpl_->mtime)) {
        load_template(pool, &view_tmpl_pool_, view_tmpl_path, view_tmpl_);
    }

    return view_tmpl_;
}

UploaderConfig::page_template *UploaderConfig::get_progress_template(apr_pool_t *pool)
{
    if (UNLIKELY(get_mtime(pool, progress_tmpl_path) > progress_tmpl_->mtime)) {
        load_template(pool, &progress_tmpl_pool_, progress_tmpl_path, progress_tmpl_);
    }

    return progress_tmpl_;
}

UploaderConfig::page_template *UploaderConfig::get_download_template(apr_pool_t *pool)
{
    if (UNLIKELY(get_mtime(pool, download_tmpl_path) > download_tmpl_->mtime)) {
        load_template(pool, &download_tmpl_pool_, download_tmpl_path, download_tmpl_);
    }

    return download_tmpl_;
}

UploaderConfig::page_template *UploaderConfig::get_thumb_template(apr_pool_t *pool)
{
    if (UNLIKELY(get_mtime(pool, thumb_tmpl_path) > thumb_tmpl_->mtime)) {
        load_template(pool, &thumb_tmpl_pool_, thumb_tmpl_path, thumb_tmpl_);
    }

    return thumb_tmpl_;
}

UploaderConfig::page_template *UploaderConfig::get_error_template(apr_pool_t *pool)
{
    if (UNLIKELY(get_mtime(pool, error_tmpl_path) > error_tmpl_->mtime)) {
        load_template(pool, &error_tmpl_pool_, error_tmpl_path, error_tmpl_);
    }

    return error_tmpl_;
}

UploadItemReader *UploaderConfig::get_ureader(apr_pool_t *pool)
{
    return new UploadItemReader(pool, file_dir);
}

UploadItemWriter *UploaderConfig::get_uwriter(apr_pool_t *pool)
{
    return new UploadItemWriter(pool, file_dir);
}

void UploaderConfig::read_lock()
{
    apr_status_t status = APR_SUCCESS;

#if APR_HAS_THREADS
#ifdef LOCK_TYPE_MUTEX
    status = apr_thread_mutex_lock(lock_);
#else
    status = apr_thread_rwlock_rdlock(lock_);
#endif
#endif

    if (UNLIKELY(status != APR_SUCCESS)) {
        throw "read_lock() failed..";
    }
}

void UploaderConfig::read_unlock()
{
    apr_status_t status = APR_SUCCESS;

#if APR_HAS_THREADS
#ifdef LOCK_TYPE_MUTEX
    status = apr_thread_mutex_unlock(lock_);
#else
    status = apr_thread_rwlock_unlock(lock_);
#endif
#endif

    if (UNLIKELY(status != APR_SUCCESS)) {
        throw "read_unlock() failed. ";
    }
}

void UploaderConfig::gwrite_lock()
{
    write_lock();
    global_lock();
}


void UploaderConfig::gwrite_unlock()
{
    write_unlock();
    global_unlock();
}


/******************************************************************************
 * private メソッド
 *****************************************************************************/
void UploaderConfig::exec_init()
{
    validate();

#if APR_HAS_THREADS
    init_lock();
#endif
    load_list();

    view_tmpl_      = new page_template();
    progress_tmpl_  = new page_template();
    download_tmpl_  = new page_template();
#ifdef MAKE_THUMBNAIL
    thumb_tmpl_     = new page_template();
#endif
    error_tmpl_     = new page_template();

    texecutor       = new TemplateExecutor();
    uparser         = new UploadParser(tmp_dir, MAX_FORM_TEXT_SIZE,
                                       max_file_size, MAX_FORM_ITEM_NUMBER,
                                       sizeof(UploadItem::header));

    apr_pool_cleanup_register(pool_, this, apr_pool_cleanup_null, finalize_config);

    is_init_ = true;
}

void UploaderConfig::validate()
{
    if (file_dir            == NULL) throw "FileDirectory is not specified.";
#ifdef MAKE_THUMBNAIL
    if (thumb_dir           == NULL) throw "ThumbDirectory is not specified.";
#endif
    if (tmp_dir             == NULL) throw "TmpDirectory is not specified.";
    if (view_tmpl_path      == NULL) throw "ViewTemplateFile is not specified.";
    if (progress_tmpl_path  == NULL) throw "ProgressTemplateFile is not specified.";
    if (download_tmpl_path  == NULL) throw "DownloadTemplateFile is not specified.";
    if (thumb_tmpl_path     == NULL) throw "ThumbTemplateFile is not specified.";
    if (error_tmpl_path     == NULL) throw "ErrorTemplateFile is not specified.";
}

#if APR_HAS_THREADS
void UploaderConfig::init_lock()
{
#ifdef LOCK_TYPE_MUTEX
    if (apr_thread_mutex_create(&lock_, APR_THREAD_MUTEX_DEFAULT, pool_) != APR_SUCCESS) {
        throw "Can not create thread mutex.";
    }
#else
    if (apr_thread_rwlock_create(&lock_, pool_) != APR_SUCCESS) {
        throw "Can not create rwlock.";
    }
#endif

    apr_pool_cleanup_register(pool_, lock_, apr_pool_cleanup_null, finalize_lock);
}
#endif

void UploaderConfig::load_list()
{
    DELETE(item_list);
    DESTROY(list_pool_);

    if (apr_pool_create(&list_pool_, pool_) != APR_SUCCESS) {
        throw "アップロードアイテムのリスト用のメモリ確保に失敗しました．";
    }

    item_list = UploadItemList::load(list_pool_, file_dir, thumb_dir,
                                     total_file_number_limit, total_file_size_limit);
    remove_item_number = 0;
}

void UploaderConfig::update_list(apr_pool_t *pool)
{
    if (LIKELY(!item_list->is_need_update(pool) &&
               (remove_item_number != ITEM_RELOAD_THRESHOLD))) {
        return;
    }

    try {
        gwrite_lock();

        load_list();

        gwrite_unlock();
    } catch(const char *) {
        gwrite_unlock();
        throw;
    }
}

void UploaderConfig::write_lock()
{
    apr_status_t status = APR_SUCCESS;

#if APR_HAS_THREADS
#ifdef LOCK_TYPE_MUTEX
    status = apr_thread_mutex_lock(lock_);
#else
    status = apr_thread_rwlock_wrlock(lock_);
#endif
#endif

    if (UNLIKELY(status != APR_SUCCESS)) {
        throw "write_lock() failed.";
    }
}

void UploaderConfig::write_unlock()
{
    read_unlock();
}

void UploaderConfig::global_lock()
{
    if (UNLIKELY(apr_global_mutex_lock(glock_) != APR_SUCCESS)) {
        throw "global_lock() failed.";
    }
}

void UploaderConfig::global_unlock()
{
    // アップロードファイルの更新をディレクトリの mtime に基づいて判断
    // してるけど，UNIX では mtime が秒単位の精度しかないので必要．:-)
    apr_sleep(APR_USEC_PER_SEC);

    if (UNLIKELY(apr_global_mutex_unlock(glock_) != APR_SUCCESS)) {
        throw "global_unlock() failed.";
    }
}

void UploaderConfig::load_template(apr_pool_t *pool, apr_pool_t **tmpl_pool,
                                   const char *template_path, page_template *tmpl)
{
    apr_file_t *file;
    apr_mmap_t *file_map = NULL;
    apr_finfo_t info;

    if (apr_file_open(&file, template_path, APR_READ,
                      APR_OS_DEFAULT, pool) != APR_SUCCESS) {
        throw apr_psprintf(pool, "Can not open page template. (%s)", template_path);
    }

    try {
        if (apr_file_info_get(&info, APR_FINFO_SIZE|APR_FINFO_MTIME, file) != APR_SUCCESS) {
            throw "テンプレートのサイズを取得できませんでした．";
        }

        if (apr_mmap_create(&file_map, file, 0, static_cast<apr_size_t>(info.size), APR_MMAP_READ,
                            pool) != APR_SUCCESS) {
            throw "テンプレートの mmap に失敗しました．";
        }

        DELETE(tmpl->imap);
        DESTROY(*tmpl_pool);

        if (apr_pool_create(tmpl_pool, pool_) != APR_SUCCESS) {
            throw "テンプレート用のメモリ確保に失敗しました．";
        }

        TemplateLexer lexer(*tmpl_pool, static_cast<const char *>(file_map->mm),
                            static_cast<apr_size_t>(info.size));
        TemplateParser parser(*tmpl_pool);

        tmpl->imap = new TemplateLexer::ident_map(*(lexer.get_ident_map()));
        tmpl->node = parser.parse(lexer.get_token_list(), lexer.get_ident_map());
        tmpl->mtime = info.mtime;

        apr_mmap_delete(file_map);
        apr_file_close(file);

    } catch(const char *) {
        if (file_map != NULL) {
            apr_mmap_delete(file_map);
        }
        apr_file_close(file);

        throw;
    }
}

apr_time_t UploaderConfig::get_mtime(apr_pool_t *pool, const char *path)
{
    apr_finfo_t info;

    if (UNLIKELY(apr_stat(&info, path, APR_FINFO_MTIME, pool) != APR_SUCCESS)) {
        throw apr_psprintf(pool, "Can not get information of file/directory. (%s)",
                           path);
    }

    return info.mtime;
}


/******************************************************************************
 * コールバック関数
 *****************************************************************************/
apr_status_t finalize_config(void *data)
{
    UploaderConfig *config;

    config = static_cast<UploaderConfig *>(data);
    config->finalize();

    return APR_SUCCESS;
}

#if APR_HAS_THREADS
apr_status_t finalize_lock(void *data)
{
#ifdef LOCK_TYPE_MUTEX
    apr_thread_mutex_t *lock;

    lock = static_cast<apr_thread_mutex_t *>(data);
    apr_thread_mutex_destroy(lock);
#else
    apr_thread_rwlock_t *lock;

    lock = static_cast<apr_thread_rwlock_t *>(data);
    apr_thread_rwlock_destroy(lock);
#endif

    return APR_SUCCESS;
}
#endif

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