
// Project: webCDwriter - Network CD Writing
// File: Status.cpp
// Author: Jörg Haeger

#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>

#include "Config.h"
#include "Server.h"
#include "Session.h"
#include "Status.h"
#include "Store.h"
#include "Version.h"

template<class Item>
class RingBuffer
{
	Item *items;
	int size, n, head;
	Item init;
public:
	RingBuffer(int size, Item init)
	{
		items = new Item[size];
		this->size = size;
		n = head = 0;
		this->init = init;
	}
	Item add(Item item)
	{
		Item old = items[(head + n) % size];
		items[(head + n) % size] = item;
		if (n < size)
		{
			n++;
			old = init;
		}
		else
			head++;
		return old;
	}
	Item get(int i)
	{
		if (i >= n)
			throw "i >= n";
		return items[(head + i) % size];
	}
};

RingBuffer<char *> lastSessionResults(10, NULL);


Status::Status()
{
//	log.putv("Status::Status()");
}

int Status::infoNo = 0;
int Status::sessionsNum = 0;
int Status::MBwritten = 0;

void Status::addResult(const char *date, const char *time,
	const char *user, int size, const char *result)
{
	if (strstr(result, "OK") != NULL)
	{
		sessionsNum++;
		MBwritten += size / (1024 * 1024);
	}

	char str[1024], userStr[1024];
	if (config.getShowUsers())
		sprintf(userStr, "<td align=center>%s</td>", user);
	else
		userStr[0] = 0;
	sprintf(str, "<td>%s</td><td>%s</td>%s"
		"<td align=right>%d</td>"
		"<td>%s</td>",
		date, time, userStr, size, result);

	char *str2 = lastSessionResults.add(strDup(str));
	if (str2 != NULL)
		delete str2;
}

static int scanToken(const char *str, int &pos, int &length)
{
	while (str[pos] == ' '
		|| str[pos] == '\t')
		pos++;

	int i = pos;
	while (str[i] != 0
		&& str[i] != ' '
		&& str[i] != '\t'
		&& str[i] != '\n')
		i++;

	return length = i-pos;
}

void Status::readLastResults()
{
	char fileName[4*1024];
	sprintf(fileName, "%s/sessions", config.getLogDir());
	FILE *file = fopen(fileName, "r");
	if (file == NULL)
		return;

	while (1)
	{
		char lineStr[4*1024];
		if (fgets(lineStr, sizeof lineStr, file) == NULL)
			break;

		char date[80], time[80];
		int size;

		int pos = 0, len;
		scanToken(lineStr, pos, len);
		strncpy(date, &lineStr[pos], len);
		date[len] = 0;
		pos += len;

		scanToken(lineStr, pos, len);
		strncpy(time, &lineStr[pos], len);
		time[len] = 0;
		pos += len;

		scanToken(lineStr, pos, len); pos += len;
		scanToken(lineStr, pos, len); pos += len;
		scanToken(lineStr, pos, len); pos += len;

		scanToken(lineStr, pos, len);
		if (sscanf(&lineStr[pos], "%d", &size) != 1)
			continue;
		pos += len;

		scanToken(lineStr, pos, len); pos += len;

		scanToken(lineStr, pos, len);
		addResult(date, time, "", size, &lineStr[pos]);
	}

	fclose(file);
}

void *Status::runServer(void *arg)
{
//	log.putv("Status::runServer");
	int sock = socket(PF_INET, SOCK_STREAM, 0);
	if (sock < 0)
		return NULL;

	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	addr.sin_port = htons(config.getStatusPortNo());
	int i;
	for (i = 0; i < 60; i++)
	{
		if (bind(sock, (struct sockaddr *)&addr, sizeof addr) == 0)
			break;
		sleep(5);
	}

	if (listen(sock, 5) != 0)
		return NULL;

	for (i = 1; ; i++)
	{
		struct sockaddr_in clientAddr;
		unsigned int clientAddrLength = sizeof(clientAddr);
		int clientSock = accept(sock,
			(struct sockaddr *)&clientAddr, &clientAddrLength);
		if (clientSock < 0)
			return NULL;
//		log.putv("Status-Request %d accepted\n", i);

		int res;
		{
			fd_set rfds;
			struct timeval tv;
			FD_ZERO(&rfds);
			FD_SET(clientSock, &rfds);
			tv.tv_sec = 5;
			tv.tv_usec = 0;
			res = select(FD_SETSIZE, &rfds, NULL, NULL, &tv);
		}

		char buf[1024];
		FILE *inStream = fdopen(clientSock, "r");
		if (res > 0)
			read(clientSock, buf, sizeof buf);
		FILE *outStream = fdopen(clientSock, "w");
		fprintf(outStream, "HTTP/1.1 200 OK\n");
		fprintf(outStream, "Connection: close\n");
		fprintf(outStream, "Content-Type: text/html\n\n");
		write(outStream);
		fflush(outStream);

		close(clientSock);
//		log.putv("Status-Request %d terminated\n", i);
	}
}

void Status::start()
{
	readLastResults();

	pthread_t thread;
	pthread_create(&thread, NULL, &runServer, NULL);
}

void Status::write(FILE *outStream)
{
	FILE *out = outStream;
	fprintf(out, "<html>\n");
	fprintf(out, "<head><title>%s Status</title></head>\n", product);
	fprintf(out, "<body text=\"#000000\" bgcolor=\"#F0F0FF\">\n");
	if (config.getLogoURL() != NULL)
		fprintf(out, "<img align=right src="
			"\"%s\">\n", config.getLogoURL());
	fprintf(out, "<h3><A HREF=\"%s\">%s</A> - %s</h3>\n",
		projectHome, project, subTitle);
	fprintf(out, "<h1>%s</h1>\n", product);

	fprintf(out, "<table bgcolor=\"#C8C8FF\">\n");
	fprintf(out, "<tr><td>Version: %s</td></tr>\n", version);
	fprintf(out, "<tr><td>Author: <A HREF=\"%s\">"
		"%s</A></td></tr>\n", authorHome, author);
	fprintf(out, "<tr><td>Uses:</td>");
	fprintf(out, "<td><A HREF=\"%s\">%s</A></td>",
		mkisofsHome, config.getMkisofsInfo());
	fprintf(out, "<td><A HREF=\"%s\">%s</A></td></tr>\n",
		cdrecordHome, config.getCdrecordInfo());
	fprintf(out, "</table>\n");

	time_t t;
	time(&t);
	char timeStr[80];
	fprintf(out,
		"<h3>Info %d / Status %s</h3>\n", ++infoNo,
//		timeToString(t, timeStr));
		ctime(&t));

	Error *firstError = config.getFirstError();
	if (firstError != NULL)
	{
		fflush(out);
		config.reload();
		firstError = config.getFirstError();
		if (firstError == NULL)
			Server::initStatic2();
	}
	if (firstError != NULL)
	{
		fprintf(out, "<h2>Server Configuration Error</h2>\n");
		fprintf(out, "<h3>%s</h3>\n", firstError->toString());
		char str[16 * 1024];
		strcpy(str, firstError->getHelp());
		replace(str, "[", "\n<table bgcolor=#E0E0E0 "
			"cellpadding=10 width=100%><tr><td>\n");
		replace(str, "]", "\n</tr></td></table>\n");
		fprintf(out, "%s\n", str);
		fprintf(out, "<h3>Reload this page to try again!<h3>\n",
			config.getFileName());
		fprintf(out, "</body>\n</html>\n");
		return;
	}
	if (store == NULL)
	{
		fprintf(out, "<h1>Problem: store == NULL</h1>\n");
		fprintf(out, "<h2>Make sure you have "
			"glibc2/libc6 installed!</h2>\n");
		fprintf(out, "</body>\n</html>\n");
		return;
	}

	fprintf(out, "<table bgcolor=\"#C8C8FF\">\n");
	fprintf(out, "<tr><td>CD-writer used</td>"
		"<td>\"%s\"</td><td>Max</td></tr>\n",
		config.getWriterInfo());
	fprintf(out, "<tr><td>Open connections</td>"
		"<td>%d</td><td>%d</td></tr>\n",
		Server::getActiveServersNum(),
		config.getMaxOpenConnections());
	fprintf(out, "<tr><td>Open sessions</td>"
		"<td>%d</td><td>%d</td></tr>\n",
		store->getSessionsNum(), config.getMaxOpenSessions());
	fprintf(out, "<tr><td>Cache</td>"
		"<td>%d MB in %d sessions</td><td>%d / %d MB</td>"
		"<td>session / total</td></tr>\n",
			store->getSizeInMB(),
			store->getSessionsTotal(),
			config.getMaxMBytesPerSession(),
			store->getMaxSizeInMB());
	fprintf(out, "<tr><td>CD-writer</td><td>");
	switch (Server::getWriterStatus())
	{
		case idle:
			fprintf(out, "free");
			break;
		case reserved:
		{
			fprintf(out, "reserved");
			if (config.getShowUsers())
			{
				Server *s = Server::getWriterOwner();
				fprintf(out, " by %s@%s",
					s->getClientUserName(),
					s->getClientIP());
			}
			struct timeval t;
			gettimeofday(&t, NULL);
			t.tv_sec = min(t.tv_sec,
				Server::getWriterReservedUntil());
			char str[1024];
			fprintf(out, "</td><td>%s",
				secondsToString(
					Server::getWriterReservedUntil()
						- t.tv_sec, str));
			break;
		}
		case blanking:
			fprintf(out, "blanking CD-RW (cdrecord)");
			break;
		case mkisofs:
			fprintf(out, "creating image (mkisofs) %d%%",
				Server::getWriterPercent());
			break;
		case writing:
			fprintf(out, "writing (cdrecord) %d%%",
				Server::getWriterPercent());
			break;
	}
	fprintf(out, "</td></tr>\n");
	fprintf(out, "</table>\n");

	fprintf(out, "<h3>Last sessions written</h3>\n");
	fprintf(out, "<table>\n");
	fprintf(out, "<tr bgcolor=\"#C8C8FF\">\n");
	fprintf(out, "<th>Date</th><th>Time</th>");
	if (config.getShowUsers())
		fprintf(out, "<th>User</th>");
	fprintf(out, "<th>Size</th><th>Result</th>\n");
	fprintf(out, "</tr>\n");

	try {
		int i = 0;
		while (1)
			fprintf(out, "<tr>%s</tr>\n",
				lastSessionResults.get(i++));
	}
	catch (const char *msg)
	{
	}
	fprintf(out, "</table>\n");

	fprintf(out, "<h3>Statistics</h3>\n");
	fprintf(out, "<table bgcolor=\"#C8C8FF\">\n");
	fprintf(out, "<tr><td>Sessions total</td>"
		"<td>%d</td></tr>\n", sessionsNum);
	fprintf(out, "<tr><td>GB written</td>"
		"<td>%.3f</td></tr>\n", MBwritten / 1024.0);
	fprintf(out, "</table>\n");

	fprintf(out, "</body>\n");
	fprintf(out, "</html>\n");
}

void Status::writeGerman(FILE *outStream)
{
}

Status status;
