/* $Id: servers.c 5078 2007-12-09 16:56:36Z morris $ */

/*
 *  Copyright (c) 2004-2007 Axel Andersson
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#include <stdlib.h>
#include <string.h>
#include <wired/wired.h>

#include "main.h"
#include "servers.h"
#include "settings.h"
#include "tracker.h"

#define WT_SERVER_MAGIC					"WTSV"
#define WT_SERVER_VERSION				1

#define WT_SERVER_KEY_SIZE				41
#define WT_SERVER_CATEGORY_SIZE			256
#define WT_SERVER_NAME_SIZE				256
#define WT_SERVER_URL_SIZE				256
#define WT_SERVER_DESCRIPTION_SIZE		256

#define WT_SERVERS_UPDATE_INTERVAL		60.0


struct _wt_server_packed {
	char								key[WT_SERVER_KEY_SIZE];
	wi_time_interval_t					update_time;
	wi_time_interval_t					register_time;

	char								ip[WI_IP_SIZE];
	uint32_t							port;

	char								category[WT_SERVER_CATEGORY_SIZE];
	char								url[WT_SERVER_URL_SIZE];
	char								name[WT_SERVER_NAME_SIZE];
	uint32_t							users;
	uint32_t							bandwidth;
	wi_boolean_t						guest;
	wi_boolean_t						download;
	uint32_t							files;
	wi_file_offset_t					size;
	char								description[WT_SERVER_DESCRIPTION_SIZE];
};
typedef struct _wt_server_packed		wt_server_packed_t;


static void								wt_server_dealloc(wi_runtime_instance_t *);

static void								wt_update_servers(wi_timer_t *);

static wt_server_t *					wt_server_init_with_packed(wt_server_t *, wt_server_packed_t);
static wt_server_packed_t				wt_server_packed(wt_server_t *);


static wi_lock_t						*wt_servers_lock;
static wi_timer_t						*wt_servers_timer;

static wi_hash_t						*wt_servers;

static wi_runtime_id_t					wt_server_runtime_id = WI_RUNTIME_ID_NULL;
static wi_runtime_class_t				wt_server_runtime_class = {
	"wt_server_t",
	wt_server_dealloc,
	NULL,
	NULL,
	NULL,
	NULL
};


void wt_servers_init(void) {
	wt_server_runtime_id = wi_runtime_register_class(&wt_server_runtime_class);

	wt_servers = wi_hash_init(wi_hash_alloc());
	
	wt_servers_lock = wi_lock_init(wi_lock_alloc());

	wt_servers_timer = wi_timer_init_with_function(wi_timer_alloc(),
												   wt_update_servers,
												   WT_SERVERS_UPDATE_INTERVAL,
												   true);
}



void wt_servers_apply_settings(void) {
	if(wi_log_startup && wt_settings.servers)
		wt_servers_read_file();
}



void wt_servers_schedule(void) {
	wi_timer_schedule(wt_servers_timer);
}



void wt_servers_read_file(void) {
	FILE				*fp;
	wt_server_packed_t	server_packed;
	wt_server_t			*server;
	char				magic[5];
	wi_time_interval_t	interval, update;
	wi_uinteger_t		count = 0;
	wi_boolean_t		loaded = false;
	uint32_t			version;

	wi_lock_lock(wt_servers_lock);
	
	fp = fopen(wi_string_cstring(wt_settings.servers), "r");

	if(!fp) {
		if(errno != ENOENT) {
			wi_log_err(WI_STR("Could not open %@: %s"),
				wt_settings.servers, strerror(errno));
		}

		goto end;
	}

	if(fread(&magic, 4, 1, fp) != 1 || strncmp(magic, WT_SERVER_MAGIC, 4) != 0) {
		wi_log_warn(WI_STR("Could not read %@: %s"), wt_settings.servers, "Not a server file");

		goto end;
	}

	if(fread(&version, 4, 1, fp) != 1 || version != WT_SERVER_VERSION) {
		wi_log_warn(WI_STR("Could not read %@: %s"), wt_settings.servers, "Wrong version");

		goto end;
	}

	wi_log_info(WI_STR("Reading %@"), wt_settings.servers);

	interval = wi_time_interval();

	while((fread(&server_packed, sizeof(wt_server_packed_t), 1, fp)) > 0) {
		update = server_packed.update_time > 0 ? server_packed.update_time : server_packed.register_time;

		if(interval - update < wt_settings.minupdatetime) {
			server = wt_server_init_with_packed(wt_server_alloc(), server_packed);
			wt_servers_add_server(server);
			wt_servers_add_stats_for_server(server);
			wi_release(server);

			loaded = true;
			count++;
		}
	}

	if(loaded) {
		wi_lock_lock(wt_status_lock);
		wt_write_status(true);
		wi_lock_unlock(wt_status_lock);

		wi_log_info(WI_STR("Loaded %u %s from %@"),
			count,
			count == 1
				? "server"
				: "servers",
			wt_settings.servers);
	}

end:
	wi_lock_unlock(wt_servers_lock);
}



void wt_servers_write_file(void) {
	static char				magic[] = WT_SERVER_MAGIC;
	static uint32_t			version = WT_SERVER_VERSION;
	FILE					*fp;
	wi_enumerator_t			*enumerator;
	wt_server_t				*server;
	wt_server_packed_t		server_packed;

	wi_lock_lock(wt_servers_lock);
	fp = fopen(wi_string_cstring(wt_settings.servers), "w");

	if(!fp) {
		wi_log_warn(WI_STR("Could not open %@: %s"),
			wt_settings.servers, strerror(errno));

		goto end;
	}

	fwrite(magic, 4, 1, fp);
	fwrite(&version, 4, 1, fp);

	wi_hash_rdlock(wt_servers);
	
	enumerator = wi_hash_data_enumerator(wt_servers);
	
	while((server = wi_enumerator_next_data(enumerator))) {
		server_packed = wt_server_packed(server);
		fwrite(&server_packed, sizeof(wt_server_packed_t), 1, fp);
	}

	wi_hash_unlock(wt_servers);

	fclose(fp);

end:
	wi_lock_unlock(wt_servers_lock);
}



static void wt_update_servers(wi_timer_t *timer) {
	wi_enumerator_t		*enumerator;
	wt_server_t			*server;
	void				*key;
	wi_time_interval_t	interval, update;
	wi_boolean_t		changed = false;

	wi_hash_wrlock(wt_servers);
		
	if(wi_hash_count(wt_servers) > 0) {
		interval = wi_time_interval();

		enumerator = wi_array_data_enumerator(wi_hash_all_keys(wt_servers));
		
		while((key = wi_enumerator_next_data(enumerator))) {
			server = wi_hash_data_for_key(wt_servers, key);
			update = server->update_time > 0.0 ? server->update_time : server->register_time;
			
			if(interval - update > wt_settings.minupdatetime) {
				wi_log_warn(WI_STR("Deleting \"%@\" with URL %@: Last update %.0f seconds ago considered too slow"),
							server->name, server->url, interval - update);

				wt_servers_remove_stats_for_server(server);
				wi_hash_remove_data_for_key(wt_servers, key);
				
				changed = true;
			}
		}
	}

	wi_hash_unlock(wt_servers);

	if(changed) {
		wi_lock_lock(wt_status_lock);
		wt_write_status(true);
		wi_lock_unlock(wt_status_lock);

		wt_servers_write_file();
	}
}



#pragma mark -

void wt_servers_add_server(wt_server_t *server) {
	wi_hash_wrlock(wt_servers);
	wi_hash_set_data_for_key(wt_servers, server, server->key);
	wi_hash_unlock(wt_servers);
}



void wt_servers_remove_server(wt_server_t *server) {
	wi_hash_wrlock(wt_servers);
	wi_hash_remove_data_for_key(wt_servers, server->key);
	wi_hash_unlock(wt_servers);
}



wt_server_t * wt_servers_server_with_ip(wi_string_t *ip) {
	wi_enumerator_t	*enumerator;
	wt_server_t		*server, *value = NULL;

	wi_hash_rdlock(wt_servers);
	
	enumerator = wi_hash_data_enumerator(wt_servers);
	
	while((server = wi_enumerator_next_data(enumerator))) {
		if(wi_is_equal(server->ip, ip)) {
			value = wi_autorelease(wi_retain(server));

			break;
		}
	}

	wi_hash_unlock(wt_servers);

	return value;
}



wt_server_t * wt_servers_server_with_key(wi_string_t *key) {
	wt_server_t		*server;
	
	wi_hash_rdlock(wt_servers);
	server = wi_autorelease(wi_retain(wi_hash_data_for_key(wt_servers, key)));
	wi_hash_unlock(wt_servers);
	
	return server;
}



void wt_servers_add_stats_for_server(wt_server_t *server) {
	wi_lock_lock(wt_status_lock);
	wt_current_servers++;
	wt_current_users += server->users;
	wt_current_files += server->files;
	wt_current_size += server->size;
	wi_lock_unlock(wt_status_lock);
}



void wt_servers_remove_stats_for_server(wt_server_t *server) {
	wi_lock_lock(wt_status_lock);
	wt_current_servers--;
	wt_current_users -= server->users;
	wt_current_files -= server->files;
	wt_current_size -= server->size;
	wi_lock_unlock(wt_status_lock);
}



#pragma mark -

void wt_servers_reply_server_list(void) {
	wi_enumerator_t		*enumerator;
	wt_server_t			*server;
	
	wi_hash_rdlock(wt_servers);
	
	enumerator = wi_hash_data_enumerator(wt_servers);
	
	while((server = wi_enumerator_next_data(enumerator))) {
		wt_reply(720, WI_STR("%@%c%@%c%@%c%u%c%u%c%u%c%u%c%u%c%llu%c%@"),
				 server->category,		WT_FIELD_SEPARATOR,
				 server->url,			WT_FIELD_SEPARATOR,
				 server->name,			WT_FIELD_SEPARATOR,
				 server->users,			WT_FIELD_SEPARATOR,
				 server->bandwidth,		WT_FIELD_SEPARATOR,
				 server->guest,			WT_FIELD_SEPARATOR,
				 server->download,		WT_FIELD_SEPARATOR,
				 server->files,			WT_FIELD_SEPARATOR,
				 server->size,			WT_FIELD_SEPARATOR,
				 server->description);
	}
	
	wi_hash_unlock(wt_servers);

	wt_reply(721, WI_STR("Done"));
}



wi_boolean_t wt_servers_category_is_valid(wi_string_t *category) {
	wi_file_t		*file;
	wi_string_t		*string;
	wi_boolean_t	result = false;

	if(wi_string_length(category) == 0)
		return true;

	file = wi_file_for_reading(wt_settings.categories);
	
	if(!file) {
		wi_log_err(WI_STR("Could not open %@: %m"), wt_settings.categories);
		
		return true;
	}
	
	while((string = wi_file_read_config_line(file))) {
		if(wi_is_equal(category, string)) {
			result = true;
			
			break;
		}
	}

	return result;
}



#pragma mark -

wt_server_t * wt_server_alloc(void) {
	return wi_runtime_create_instance(wt_server_runtime_id, sizeof(wt_server_t));
}



wt_server_t * wt_server_init(wt_server_t *server) {
	return server;
}



static wt_server_t * wt_server_init_with_packed(wt_server_t *server, wt_server_packed_t server_packed) {
	server->key				= wi_string_init_with_cstring(wi_string_alloc(), server_packed.key);
	server->update_time		= server_packed.update_time;
	server->register_time	= server_packed.register_time;

	server->ip				= wi_string_init_with_cstring(wi_string_alloc(), server_packed.ip);
	server->port			= server_packed.port;

	server->category		= wi_string_init_with_cstring(wi_string_alloc(), server_packed.category);
	server->url				= wi_string_init_with_cstring(wi_string_alloc(), server_packed.url);
	server->name			= wi_string_init_with_cstring(wi_string_alloc(), server_packed.name);
	server->users			= server_packed.users;
	server->bandwidth		= server_packed.bandwidth;
	server->guest			= server_packed.guest;
	server->download		= server_packed.download;
	server->files			= server_packed.files;
	server->size			= server_packed.size;
	server->description		= wi_string_init_with_cstring(wi_string_alloc(), server_packed.description);
	
	return server;
}



static void wt_server_dealloc(wi_runtime_instance_t *instance) {
	wt_server_t		*server = instance;
	
	wi_release(server->key);
	wi_release(server->ip);
	wi_release(server->category);
	wi_release(server->url);
	wi_release(server->name);
	wi_release(server->description);
}



#pragma mark -

static wt_server_packed_t wt_server_packed(wt_server_t *server) {
	wt_server_packed_t	server_packed;

	memset(&server_packed, 0, sizeof(server_packed));

	wi_strlcpy(server_packed.key, wi_string_cstring(server->key), sizeof(server_packed.key));
	server_packed.update_time	= server->update_time;
	server_packed.register_time	= server->register_time;

	wi_strlcpy(server_packed.ip, wi_string_cstring(server->ip), sizeof(server_packed.ip));
	server_packed.port			= server->port;

	wi_strlcpy(server_packed.category, wi_string_cstring(server->category), sizeof(server_packed.category));
	wi_strlcpy(server_packed.url, wi_string_cstring(server->url), sizeof(server_packed.url));
	wi_strlcpy(server_packed.name, wi_string_cstring(server->name), sizeof(server_packed.name));
	server_packed.users			= server->users;
	server_packed.bandwidth		= server->bandwidth;
	server_packed.guest			= server->guest;
	server_packed.download		= server->download;
	server_packed.files			= server->files;
	server_packed.size			= server->size;
	wi_strlcpy(server_packed.description, wi_string_cstring(server->description), sizeof(server_packed.description));

	return server_packed;
}


syntax highlighted by Code2HTML, v. 0.9.1