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

#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <utime.h>

#include "Config.h"
#include "Support.h"
#include "Log.h"
#include "MP3toWAV.h"
#include "Queue.h"
#include "Server.h"
#include "Session.h"
#include "Store.h"

const char
	CDWserverInfo[] = "CDWserver.info";

Session::Session(Server *server, const char *sessionID)
{
	// convert sessionID to a valid dirName: _ -> _0 and / -> _1
	char dirName[40];
	int i, j;
	for (i = 0, j = 0; j < sizeof dirName - 2; i++, j++)
		if (sessionID[i] == 0)
			break;
		else
		if (sessionID[i] == '_')
		{
			dirName[j++] = '_';
			dirName[j] = '0';
		}
		else
		if (sessionID[i] == '/')
		{
			dirName[j++] = '_';
			dirName[j] = '1';
		}
		else
			dirName[j] = sessionID[i];
	dirName[j] = 0;

	this->server = server;
	ID = strDup(dirName);
	filesNum = tracksNum = sizeInKBytes = 0;
	isOpen = 0;

	dir = store->registerSession(this);
	infoFile = new File(*dir, CDWserverInfo);
	currentTrack = NULL;
	currentTrackNo = 0;
	mp3towav = NULL;
}

Session::~Session()
{
	if (mp3towav != NULL)
		closeMP3Track();

	File file(dir, CDWserverInfo);
	FILE *outStream = fopen(file.getName(), "w");
	if (outStream != NULL)
	{
		fprintf(outStream, "sizeInKBytes=%d\n", sizeInKBytes);
		fprintf(outStream, "files=%d\n", filesNum);
		fprintf(outStream, "tracks=%d\n", tracksNum);
		fclose(outStream);
	}

	if (config.getRemoveSessions() && dir->exists())
		store->removeDir(dir->getName());

	store->unregister(this);
	delete[] ID;
}

void Session::closeMP3Track()
{
	log.putv("Session::closeMP3Track()");
	mp3towav->end();
	FILE *outStream = fopen(currentTrack->getName(), "r+");
	if (outStream != NULL)
	{
		fseek(outStream, 0, SEEK_END);
		try {
			while (1)
			{
				const char *buf;
				int n = mp3towav->get(&buf);
				if (n == 0)
					break;
				copy(buf, outStream, n);
			}
		} catch (const char *str)
		{
		}
		mp3towav->updateHeader(outStream);
		fclose(outStream);
	}

	delete mp3towav;
	mp3towav = NULL;
}

void Session::copy(FILE *inStream, FILE *outStream, int length)
{
	try {
		copy2(inStream, outStream, length);
	}
	catch (const char *msg)
	{
		skipBytes(inStream, length);
		throw msg;
	}
}

void Session::copy(const char *buf, FILE *outStream, int length)
{
	int KBytes = sizeToKBytes(length);
	if (sizeInKBytes + KBytes > 1024 * config.getMaxMBytesPerSession())
		throw "session to big";

	int fd = fileno(outStream);
	if (fd == -1)
		throw "fileno";

	sizeInKBytes += sizeToKBytes(length);
	store->write(buf, length, fd);
}

void Session::copy2(FILE *inStream, FILE *outStream, int &length)
{
	int KBytes = sizeToKBytes(length);
	if (sizeInKBytes + KBytes > 1024 * config.getMaxMBytesPerSession())
		throw "session to big";

	int fd = fileno(outStream);
	if (fd == -1)
		throw "fileno";

	while (length > 0)
	{
		char buf[16 * 1024];
		int n = fread(buf, 1,
			min(length, sizeof buf), inStream);
		length -= n;
		if (feof(inStream))
			throw "unexpected end of inStream";
		sizeInKBytes += sizeToKBytes(n);
		store->write(buf, n, fd);
	}
}

void Session::countFile(const char *pathName)
{
	struct stat statBuf;
	if (lstat(pathName, &statBuf) != 0)
		return;

	if (!S_ISDIR(statBuf.st_mode))
	{
		if (!S_ISREG(statBuf.st_mode))
			return;
		filesNum++;
		sizeInKBytes += sizeToKBytes(statBuf.st_size);
		return;
	}

	DIR *dir = opendir(pathName);
	if (dir == NULL)
		return;

	struct dirent *dirEntry;
	while ((dirEntry = readdir(dir)) != NULL)
	{
		if (strcmp(dirEntry->d_name, ".") == 0
			|| strcmp(dirEntry->d_name, "..") == 0)
			continue;

		char tmpPathName[strlen(pathName) + 1
			+ strlen(dirEntry->d_name) + 1];
		sprintf(tmpPathName, "%s/%s",
			pathName, dirEntry->d_name);
		countFile(tmpPathName);
	}

	closedir(dir);
}

void Session::create()
{
	if (dir->exists())
		store->removeDir(dir->getName());

	infoFile->mkdirs();
	FILE *outStream = fopen(infoFile->getName(), "w");
	if (outStream == NULL)
		throw "internal error:"
			" cannot create infofile";
	fclose(outStream);

	isOpen = 1;
}

const File *Session::getFirstTrack()
{
	if (mp3towav != NULL)
		closeMP3Track();

	currentTrackNo = 0;
	return getNextTrack();
}

int Session::getSessionSize(File *dir)
{
	int sizeInKBytes = 0;
	File file(dir, CDWserverInfo);
	FILE *inStream = fopen(file.getName(), "r");
	if (inStream == NULL)
		throw "internal error: can't read sessionInfo";

	char lineStr[1024];
	while (fgets(lineStr, sizeof lineStr, inStream) != NULL)
	{
		char str1[1024], str2[1024];
		Config::parseLine(lineStr, str1, str2);

		if (strlen(str1) == 0 && strlen(str2) == 0)
			continue;
		else
		if (strcasecmp(str1, "sizeInKBytes") == 0)
			sscanf(str2, "%d", &sizeInKBytes);
	}
	fclose(inStream);
	return sizeInKBytes;
}

const File *Session::getNextTrack()
{
	File *nextTrack = NULL;

	DIR *dir = opendir(this->dir->getName());
	if (dir == NULL)
		return NULL;

	struct dirent *dirEntry;
	int n;
	while ((dirEntry = readdir(dir)) != NULL)
		if (sscanf(dirEntry->d_name, "track%d", &n) == 1)
			if (n == currentTrackNo + 1)
			{
				nextTrack = new File(
					this->dir, dirEntry->d_name);
				break;
			}

	closedir(dir);

	if (nextTrack == NULL)
		return NULL;

	if (nextTrack->exists())
	{
		if (currentTrack != NULL)
			delete currentTrack;
		currentTrack = nextTrack;
		currentTrackNo++;
		return currentTrack;
	}
	else
	{
		delete nextTrack;
		return NULL;
	}
}

int Session::isSessionDir(File &dir)
{
	File file(dir, CDWserverInfo);
	return file.exists();
}

void Session::makeDir(const char *fileName, int mode, int time)
{
	if (!isOpen)
		create();

	if (currentTrack == NULL
		|| !currentTrack->isDirectory())
		newTrack("dir");

	File dir(currentTrack, fileName);
	File dir2(dir, ".");
	dir2.mkdirs();

	struct utimbuf buf;
	buf.actime = buf.modtime = time;
	utime(dir.getName(), &buf);
}

void Session::newTrack(const char *trackType)
{
	if (mp3towav != NULL)
		closeMP3Track();

	const char *extension = "";
	if (strcasecmp(trackType, "image") == 0
		|| strcasecmp(trackType, "iso") == 0)
		extension = ".iso";
	else
	if (strcasecmp(trackType, "wav") == 0)
		extension = ".wav";
	else
	if (strcasecmp(trackType, "mp3") == 0)
	{
		extension = ".wav";
		mp3towav = new MP3toWAV();
	}

	tracksNum++;
	char nextTrackName[20];
	sprintf(nextTrackName, "track%03d%s", tracksNum, extension);
	currentTrack = new File(dir, nextTrackName);
	currentTrackNo = tracksNum;
}

void Session::open()
{
	if (!dir->exists())
		return;

	File file(dir, CDWserverInfo);
	FILE *inStream = fopen(file.getName(), "r");
	if (inStream == NULL)
		throw "internal error: can't read sessionInfo";

	char lineStr[1024];
	while (fgets(lineStr, sizeof lineStr, inStream) != NULL)
	{
		char str1[1024], str2[1024];
		Config::parseLine(lineStr, str1, str2);

		if (strlen(str1) == 0 && strlen(str2) == 0)
			continue;
		else
		if (strcasecmp(str1, "files") == 0)
			sscanf(str2, "%d", &filesNum);
		else
		if (strcasecmp(str1, "sizeInKBytes") == 0)
			sscanf(str2, "%d", &sizeInKBytes);
		else
		if (strcasecmp(str1, "tracks") == 0)
			sscanf(str2, "%d", &tracksNum);
	}
	fclose(inStream);

	isOpen = 1;
	log.put("opened previous session: "
		"files=%d, tracks=%d, sizeInKBytes=%d",
		filesNum, tracksNum, sizeInKBytes);
}

void Session::putFile(FILE *inStream,
	const char *fileName, int length, int mode, int time)
{
	if (!isOpen)
		create();

	if (strstr(fileName, "../") != NULL)
	{
		skipBytes(inStream, length);
		throw "filename contains ../";
	}

	if (currentTrack == NULL
		|| !currentTrack->isDirectory())
		newTrack("dir");

	File file(currentTrack, fileName);
	file.mkdirs();
	file.remove();

	FILE *outStream = fopen(file.getName(), "w");
	if (outStream == NULL)
	{
		skipBytes(inStream, length);
		log.put("Session::putFile: fopen");
		throw "cannot store file";
	}

	try
	{
		copy(inStream, outStream, length);
	}
	catch (const char *msg)
	{
		fclose(outStream);
		throw msg;
	}
	filesNum++;
	fclose(outStream);
	chmod(file.getName(), mode);
	struct utimbuf utimeBuf;
	utimeBuf.actime = utimeBuf.modtime = time;
	utime(file.getName(), &utimeBuf);
}

void Session::putTrack(FILE *inStream, int length)
{
	if (!isOpen)
		create();

	FILE *outStream = fopen(currentTrack->getName(), "a");
	if (outStream == NULL)
	{
		skipBytes(inStream, length);
		throw "cannot store track";
	}

	try
	{
		if (mp3towav != NULL)
		{
			mp3towav->setInStream(inStream);
			mp3towav->put(length);
			while (1)
			{
				const char *buf;
				int n = mp3towav->get(&buf);
				if (n == 0)
					break;
				try {
					copy(buf, outStream, n);
				}
				catch (const char *msg)
				{
					while (mp3towav->get(&buf) > 0);
					throw msg;
				}
			}
		}
		else
			copy(inStream, outStream, length);
	}
	catch (const char *msg)
	{
		fclose(outStream);
		throw msg;
	}
	fclose(outStream);
}

int Session::sizeToKBytes(int size)
{
	if (size <= 0)
		return 0;
	else
		return (size - 1) / 1024 + 1;
}
