/**
* Copyright (C) 2008 Happy Fish / YuQing
*
* FastDFS may be copied only under the terms of the GNU General
* Public License V3, which may be found in the FastDFS source kit.
* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail.
**/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "fdfs_client.h"
#include "fdfs_global.h"
#include "fastcommon/base64.h"
#include "fastcommon/sockopt.h"
#include "fastcommon/logger.h"
#include "fdfs_http_shared.h"

int writeToFileCallback(void *arg, const int64_t file_size, const char *data, \
                const int current_size)
{
	if (arg == NULL)
	{
		return EINVAL;
	}

	if (fwrite(data, current_size, 1, (FILE *)arg) != 1)
	{
		return errno != 0 ? errno : EIO;
	}

	return 0;
}

int uploadFileCallback(void *arg, const int64_t file_size, int sock)
{
	int64_t total_send_bytes;
	char *filename;

	if (arg == NULL)
	{
		return EINVAL;
	}

	filename = (char *)arg;
	return tcpsendfile(sock, filename, file_size, \
		SF_G_NETWORK_TIMEOUT, &total_send_bytes);
}

int main(int argc, char *argv[])
{
	char *conf_filename;
	char *local_filename;
	ConnectionInfo *pTrackerServer;
	ConnectionInfo *pStorageServer;
	int result;
	ConnectionInfo storageServer;
	char group_name[FDFS_GROUP_NAME_MAX_LEN + 1];
	char remote_filename[256];
	char master_filename[256];
	FDFSMetaData meta_list[32];
	int meta_count;
	int i;
	FDFSMetaData *pMetaList;
	char token[32 + 1];
	char file_id[128];
	char file_url[256];
	char szDatetime[20];
	char szPortPart[16];
	int url_len;
	time_t ts;
        char *file_buff;
	int64_t file_size;
	char *operation;
	char *meta_buff;
	int store_path_index;
	FDFSFileInfo file_info;

	printf("This is FastDFS client test program v%d.%d.%d\n" \
"\nCopyright (C) 2008, Happy Fish / YuQing\n" \
"\nFastDFS may be copied only under the terms of the GNU General\n" \
"Public License V3, which may be found in the FastDFS source kit.\n" \
"Please visit the FastDFS Home Page http://www.fastken.com/ \n" \
"for more detail.\n\n", g_fdfs_version.major, g_fdfs_version.minor,
g_fdfs_version.patch);

	if (argc < 3)
	{
		printf("Usage: %s <config_file> <operation>\n" \
			"\toperation: upload, download, getmeta, setmeta, " \
			"delete and query_servers\n", argv[0]);
		return 1;
	}

	log_init();
	g_log_context.log_level = LOG_DEBUG;

	conf_filename = argv[1];
	operation = argv[2];
	if ((result=fdfs_client_init(conf_filename)) != 0)
	{
		return result;
	}

	pTrackerServer = tracker_get_connection();
	if (pTrackerServer == NULL)
	{
		fdfs_client_destroy();
		return errno != 0 ? errno : ECONNREFUSED;
	}

	pStorageServer = NULL;
	*group_name = '\0';
	local_filename = NULL;
	if (strcmp(operation, "upload") == 0)
	{
		int upload_type;
		char *prefix_name;
		const char *file_ext_name;
		char slave_filename[256];
		int slave_filename_len;

		if (argc < 4)
		{
			printf("Usage: %s <config_file> upload " \
				"<local_filename> [FILE | BUFF | CALLBACK] \n",\
				argv[0]);
			fdfs_client_destroy();
			return EINVAL;
		}

		local_filename = argv[3];
		if (argc == 4)
		{
			upload_type = FDFS_UPLOAD_BY_FILE;
		}
		else
		{
			if (strcmp(argv[4], "BUFF") == 0)
			{
				upload_type = FDFS_UPLOAD_BY_BUFF;
			}
			else if (strcmp(argv[4], "CALLBACK") == 0)
			{
				upload_type = FDFS_UPLOAD_BY_CALLBACK;
			}
			else
			{
				upload_type = FDFS_UPLOAD_BY_FILE;
			}
		}

		store_path_index = 0;

		{
		ConnectionInfo storageServers[FDFS_MAX_SERVERS_EACH_GROUP];
		ConnectionInfo *pServer;
		ConnectionInfo *pServerEnd;
		int storage_count;

		if ((result=tracker_query_storage_store_list_without_group( \
			pTrackerServer, storageServers, \
			FDFS_MAX_SERVERS_EACH_GROUP, &storage_count, \
			group_name, &store_path_index)) == 0)
		{
			printf("tracker_query_storage_store_list_without_group: \n");
			pServerEnd = storageServers + storage_count;
			for (pServer=storageServers; pServer<pServerEnd; pServer++)
			{
				printf("\tserver %d. group_name=%s, " \
				       "ip_addr=%s, port=%d\n", \
					(int)(pServer - storageServers) + 1, \
					group_name, pServer->ip_addr, pServer->port);
			}
			printf("\n");
		}
		}

		if ((result=tracker_query_storage_store(pTrackerServer, \
		                &storageServer, group_name, &store_path_index)) != 0)
		{
			fdfs_client_destroy();
			printf("tracker_query_storage fail, " \
				"error no: %d, error info: %s\n", \
				result, STRERROR(result));
			return result;
		}

		printf("group_name=%s, ip_addr=%s, port=%d\n", \
			group_name, storageServer.ip_addr, \
			storageServer.port);

		if ((pStorageServer=tracker_make_connection(&storageServer, \
			&result)) == NULL)
		{
			fdfs_client_destroy();
			return result;
		}

		memset(&meta_list, 0, sizeof(meta_list));
		meta_count = 0;
		strcpy(meta_list[meta_count].name, "ext_name");
		strcpy(meta_list[meta_count].value, "jpg");
		meta_count++;
		strcpy(meta_list[meta_count].name, "width");
		strcpy(meta_list[meta_count].value, "160");
		meta_count++;
		strcpy(meta_list[meta_count].name, "height");
		strcpy(meta_list[meta_count].value, "80");
		meta_count++;
		strcpy(meta_list[meta_count].name, "file_size");
		strcpy(meta_list[meta_count].value, "115120");
		meta_count++;

		file_ext_name = fdfs_get_file_ext_name(local_filename);
		*group_name = '\0';

		if (upload_type == FDFS_UPLOAD_BY_FILE)
		{
			result = storage_upload_by_filename(pTrackerServer, \
				pStorageServer, store_path_index, \
				local_filename, file_ext_name, \
				meta_list, meta_count, \
				group_name, remote_filename);

			printf("storage_upload_by_filename\n");
		}
		else if (upload_type == FDFS_UPLOAD_BY_BUFF)
		{
			char *file_content;
			if ((result=getFileContent(local_filename, \
					&file_content, &file_size)) == 0)
			{
			result = storage_upload_by_filebuff(pTrackerServer, \
				pStorageServer, store_path_index, \
				file_content, file_size, file_ext_name, \
				meta_list, meta_count, \
				group_name, remote_filename);
			free(file_content);
			}

			printf("storage_upload_by_filebuff\n");
		}
		else
		{
			struct stat stat_buf;

			if (stat(local_filename, &stat_buf) == 0 && \
				S_ISREG(stat_buf.st_mode))
			{
			file_size = stat_buf.st_size;
			result = storage_upload_by_callback(pTrackerServer, \
				pStorageServer, store_path_index, \
				uploadFileCallback, local_filename, \
				file_size, file_ext_name, \
				meta_list, meta_count, \
				group_name, remote_filename);
			}

			printf("storage_upload_by_callback\n");
		}

		if (result != 0)
		{
			printf("upload file fail, " \
				"error no: %d, error info: %s\n", \
				result, STRERROR(result));
			tracker_close_connection_ex(pStorageServer, true);
			fdfs_client_destroy();
			return result;
		}

		if (g_tracker_server_http_port == 80)
		{
			*szPortPart = '\0';
		}
		else
		{
			sprintf(szPortPart, ":%d", g_tracker_server_http_port);
		}

		sprintf(file_id, "%s/%s", group_name, remote_filename);
		url_len = sprintf(file_url, "http://%s%s/%s", \
				pStorageServer->ip_addr, szPortPart, file_id);
		if (g_anti_steal_token)
		{
			ts = time(NULL);
			fdfs_http_gen_token(&g_anti_steal_secret_key, file_id, \
                		ts, token);
			sprintf(file_url + url_len, "?token=%s&ts=%d", \
				token, (int)ts);
		}

		printf("group_name=%s, remote_filename=%s\n", \
			group_name, remote_filename);

		fdfs_get_file_info(group_name, remote_filename, &file_info);
		printf("source ip address: %s\n", file_info.source_ip_addr);
		printf("file timestamp=%s\n", formatDatetime(
			file_info.create_timestamp, "%Y-%m-%d %H:%M:%S", \
			szDatetime, sizeof(szDatetime)));
		printf("file size=%"PRId64"\n", file_info.file_size);
		printf("file crc32=%u\n", file_info.crc32);
		printf("example file url: %s\n", file_url);

		strcpy(master_filename, remote_filename);
		*remote_filename = '\0';
		if (upload_type == FDFS_UPLOAD_BY_FILE)
		{
			prefix_name = "_big";
			result = storage_upload_slave_by_filename(pTrackerServer,
				NULL, local_filename, master_filename, \
				prefix_name, file_ext_name, \
				meta_list, meta_count, \
				group_name, remote_filename);

			printf("storage_upload_slave_by_filename\n");
		}
		else if (upload_type == FDFS_UPLOAD_BY_BUFF)
		{
			char *file_content;
			prefix_name = "1024x1024";
			if ((result=getFileContent(local_filename, \
					&file_content, &file_size)) == 0)
			{
			result = storage_upload_slave_by_filebuff(pTrackerServer, \
				NULL, file_content, file_size, master_filename,
				prefix_name, file_ext_name, \
				meta_list, meta_count, \
				group_name, remote_filename);
			free(file_content);
			}

			printf("storage_upload_slave_by_filebuff\n");
		}
		else
		{
			struct stat stat_buf;

			prefix_name = "-small";
			if (stat(local_filename, &stat_buf) == 0 && \
				S_ISREG(stat_buf.st_mode))
			{
			file_size = stat_buf.st_size;
			result = storage_upload_slave_by_callback(pTrackerServer, \
				NULL, uploadFileCallback, local_filename, \
				file_size, master_filename, prefix_name, \
				file_ext_name, meta_list, meta_count, \
				group_name, remote_filename);
			}

			printf("storage_upload_slave_by_callback\n");
		}

		if (result != 0)
		{
			printf("upload slave file fail, " \
				"error no: %d, error info: %s\n", \
				result, STRERROR(result));
			tracker_close_connection_ex(pStorageServer, true);
			fdfs_client_destroy();
			return result;
		}

		if (g_tracker_server_http_port == 80)
		{
			*szPortPart = '\0';
		}
		else
		{
			sprintf(szPortPart, ":%d", g_tracker_server_http_port);
		}

		sprintf(file_id, "%s/%s", group_name, remote_filename);
		url_len = sprintf(file_url, "http://%s%s/%s", \
				pStorageServer->ip_addr, szPortPart, file_id);
		if (g_anti_steal_token)
		{
			ts = time(NULL);
			fdfs_http_gen_token(&g_anti_steal_secret_key, file_id, \
                		ts, token);
			sprintf(file_url + url_len, "?token=%s&ts=%d", \
				token, (int)ts);
		}

		printf("group_name=%s, remote_filename=%s\n", \
			group_name, remote_filename);

		fdfs_get_file_info(group_name, remote_filename, &file_info);

		printf("source ip address: %s\n", file_info.source_ip_addr);
		printf("file timestamp=%s\n", formatDatetime(
			file_info.create_timestamp, "%Y-%m-%d %H:%M:%S", \
			szDatetime, sizeof(szDatetime)));
		printf("file size=%"PRId64"\n", file_info.file_size);
		printf("file crc32=%u\n", file_info.crc32);
		printf("example file url: %s\n", file_url);

		if (fdfs_gen_slave_filename(master_filename, \
               		prefix_name, file_ext_name, \
                	slave_filename, &slave_filename_len) == 0)
		{

			if (strcmp(remote_filename, slave_filename) != 0)
			{
				printf("slave_filename=%s\n" \
					"remote_filename=%s\n" \
					"not equal!\n", \
					slave_filename, remote_filename);
			}
		}
	}
	else if (strcmp(operation, "download") == 0 || 
		strcmp(operation, "getmeta") == 0 ||
		strcmp(operation, "setmeta") == 0 ||
		strcmp(operation, "query_servers") == 0 ||
		strcmp(operation, "delete") == 0)
	{
		if (argc < 5)
		{
			printf("Usage: %s <config_file> %s " \
				"<group_name> <remote_filename>\n", \
				argv[0], operation);
			fdfs_client_destroy();
			return EINVAL;
		}

		snprintf(group_name, sizeof(group_name), "%s", argv[3]);
		snprintf(remote_filename, sizeof(remote_filename), \
				"%s", argv[4]);
		if (strcmp(operation, "setmeta") == 0 ||
	 	    strcmp(operation, "delete") == 0)
		{
			result = tracker_query_storage_update(pTrackerServer, \
       	       			&storageServer, group_name, remote_filename);
		}
		else if (strcmp(operation, "query_servers") == 0)
		{
			ConnectionInfo storageServers[FDFS_MAX_SERVERS_EACH_GROUP];
			int server_count;

			result = tracker_query_storage_list(pTrackerServer, \
                		storageServers, FDFS_MAX_SERVERS_EACH_GROUP, \
                		&server_count, group_name, remote_filename);

			if (result != 0)
			{
				printf("tracker_query_storage_list fail, "\
					"group_name=%s, filename=%s, " \
					"error no: %d, error info: %s\n", \
					group_name, remote_filename, \
					result, STRERROR(result));
			}
			else
			{
				printf("server list (%d):\n", server_count);
				for (i=0; i<server_count; i++)
				{
					printf("\t%s:%u\n", \
						storageServers[i].ip_addr, \
						storageServers[i].port);
				}
				printf("\n");
			}

			tracker_close_connection_ex(pTrackerServer, result != 0);
			fdfs_client_destroy();
			return result;
		}
		else
		{
			result = tracker_query_storage_fetch(pTrackerServer, \
       	       			&storageServer, group_name, remote_filename);
		}

		if (result != 0)
		{
			fdfs_client_destroy();
			printf("tracker_query_storage_fetch fail, " \
				"group_name=%s, filename=%s, " \
				"error no: %d, error info: %s\n", \
				group_name, remote_filename, \
				result, STRERROR(result));
			return result;
		}

		printf("storage=%s:%u\n", storageServer.ip_addr, \
			storageServer.port);

		if ((pStorageServer=tracker_make_connection(&storageServer, \
			&result)) == NULL)
		{
			fdfs_client_destroy();
			return result;
		}

		if (strcmp(operation, "download") == 0)
		{
			if (argc >= 6)
			{
				local_filename = argv[5];
				if (strcmp(local_filename, "CALLBACK") == 0)
				{
				FILE *fp;
				fp = fopen(local_filename, "wb");
				if (fp == NULL)
				{
					result = errno != 0 ? errno : EPERM;
					printf("open file \"%s\" fail, " \
						"errno: %d, error info: %s", \
						local_filename, result, \
						STRERROR(result));
				}
				else
				{
				result = storage_download_file_ex( \
					pTrackerServer, pStorageServer, \
					group_name, remote_filename, 0, 0, \
					writeToFileCallback, fp, &file_size);
				fclose(fp);
				}
				}
				else
				{
				result = storage_download_file_to_file( \
					pTrackerServer, pStorageServer, \
					group_name, remote_filename, \
					local_filename, &file_size);
				}
			}
			else
			{
				file_buff = NULL;
				if ((result=storage_download_file_to_buff( \
					pTrackerServer, pStorageServer, \
					group_name, remote_filename, \
					&file_buff, &file_size)) == 0)
				{
					local_filename = strrchr( \
							remote_filename, '/');
					if (local_filename != NULL)
					{
						local_filename++;  //skip /
					}
					else
					{
						local_filename=remote_filename;
					}

					result = writeToFile(local_filename, \
						file_buff, file_size);

					free(file_buff);
				}
			}

			if (result == 0)
			{
				printf("download file success, " \
					"file size=%"PRId64", file save to %s\n", \
					 file_size, local_filename);
			}
			else
			{
				printf("download file fail, " \
					"error no: %d, error info: %s\n", \
					result, STRERROR(result));
			}
		}
		else if (strcmp(operation, "getmeta") == 0)
		{
			if ((result=storage_get_metadata(pTrackerServer, \
				pStorageServer, group_name, remote_filename, \
				&pMetaList, &meta_count)) == 0)
			{
				printf("get meta data success, " \
					"meta count=%d\n", meta_count);
				for (i=0; i<meta_count; i++)
				{
					printf("%s=%s\n", \
						pMetaList[i].name, \
						pMetaList[i].value);
				}

				free(pMetaList);
			}
			else
			{
				printf("getmeta fail, " \
					"error no: %d, error info: %s\n", \
					result, STRERROR(result));
			}
		}
		else if (strcmp(operation, "setmeta") == 0)
		{
			if (argc < 7)
			{
				printf("Usage: %s <config_file> %s " \
					"<group_name> <remote_filename> " \
					"<op_flag> <metadata_list>\n" \
					"\top_flag: %c for overwrite, " \
					"%c for merge\n" \
					"\tmetadata_list: name1=value1," \
					"name2=value2,...\n", \
					argv[0], operation, \
					STORAGE_SET_METADATA_FLAG_OVERWRITE, \
					STORAGE_SET_METADATA_FLAG_MERGE);
				fdfs_client_destroy();
				return EINVAL;
			}

			meta_buff = strdup(argv[6]);
			if (meta_buff == NULL)
			{
				printf("Out of memory!\n");
				return ENOMEM;
			}

			pMetaList = fdfs_split_metadata_ex(meta_buff, \
					',', '=', &meta_count, &result);
			if (pMetaList == NULL)
			{
				printf("Out of memory!\n");
				free(meta_buff);
				return ENOMEM;
			}

			if ((result=storage_set_metadata(pTrackerServer, \
				NULL, group_name, remote_filename, \
				pMetaList, meta_count, *argv[5])) == 0)
			{
				printf("set meta data success\n");
			}
			else
			{
				printf("setmeta fail, " \
					"error no: %d, error info: %s\n", \
					result, STRERROR(result));
			}

			free(meta_buff);
			free(pMetaList);
		}
		else if(strcmp(operation, "delete") == 0)
		{
			if ((result=storage_delete_file(pTrackerServer, \
			NULL, group_name, remote_filename)) == 0)
			{
				printf("delete file success\n");
			}
			else
			{
				printf("delete file fail, " \
					"error no: %d, error info: %s\n", \
					result, STRERROR(result));
			}
		}
	}
	else
	{
		fdfs_client_destroy();
		printf("invalid operation: %s\n", operation);
		return EINVAL;
	}

	/* for test only */
	if ((result=fdfs_active_test(pTrackerServer)) != 0)
	{
		printf("active_test to tracker server %s:%u fail, errno: %d\n", \
			pTrackerServer->ip_addr, pTrackerServer->port, result);
	}

	/* for test only */
	if ((result=fdfs_active_test(pStorageServer)) != 0)
	{
		printf("active_test to storage server %s:%u fail, errno: %d\n", \
			pStorageServer->ip_addr, pStorageServer->port, result);
	}

	tracker_close_connection_ex(pStorageServer, true);
	tracker_close_connection_ex(pTrackerServer, true);

	fdfs_client_destroy();

	return result;
}

