/*
 * (c) 2005 Research Group Dr. Fuhrmann, University of Karlsruhe, Germany
 * All rights reserved.
 *
 * Author:
 *         Stefan Denk <denk@ira.uka.de>
 *
 * $Id: vdrrecording.cc,v 1.5 2006/01/10 13:37:20 denk Exp $
 *
 */


// libc

#include <sys/mman.h>

// vdr

#include <recording.h>

// vidtrans

#include "vdrrecording.h"
#include "helper.h"
#include "parsefile.h"

vdrrecording::vdrrecording(string _dir, int _videofilesindir, cChannel * _channel, time_t _starttime, time_t _duration)
	: dir(_dir) ,
	  videofilesindir(_videofilesindir) ,
	  channel(_channel) ,
	  starttime(_starttime) ,
	  duration(_duration)
{
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	// initializes the variables in the class vdrrecording with the given _values	
}

vdrrecordings::vdrrecordings(ctasks *ct, cvideosources *vs)
	        : recs(),_ct(ct),_vs(vs){
			//dsyslog("%s: begin", __PRETTY_FUNCTION__);
}
		 
/** 
 * Read all the recordings stored on disk (that is, all recordings ScanVideoDir could find
 * on startup and the transferred recordings that were added later on.
 * This thread generates all information linked to recordings already sitting on the disk.
 */

void vdrrecordings::Action()
{
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	cRecording *rec;
	string infofile, snippetfile;
	int recfilecount;
	recinfo myrecinfo;
	unsigned long starttime;
	string channel;
	cChannel *cChan;
	struct stat snippetinfofile;
	int statresult;
	
	// to remember which download snippets are there and which transfers are finished (yes... a little redundant to downloadstatus)
	bool completeditems[1001];
	bool *completedsnippets = completeditems;
	bool existingitems[1001];
	bool *existingsnippets = existingitems;
	
	recinfo *result = &myrecinfo;


	while (true)
	{
		// not exactly nice, but if this thread starts without a pause, ScanVideoDir has not finished and it will fail.
		sleep(10);
		// for each recording
		for (int i=0 ; i < Recordings.Count() ; i++)   // retrieve the directories the recordings are stored in
		{
			bzero(&completeditems, sizeof(completeditems));
			bzero(&existingitems, sizeof(existingitems));

			bzero(&snippetinfofile, sizeof(snippetinfofile));
			rec = Recordings.Get(i);
			// count the number of recfiles in this recording (directory)
			recfilecount = howmanyrecfiles(rec->FileName(), existingsnippets);
			// if it is a directory holding snippets, process the snippetlist and cut if transfer is finished (and tell vdr the task is done)
			snippetfile = rec->FileName();
			snippetfile += "/downloadstatus.videotransport.vdr";
			statresult = stat(snippetfile.c_str(), &snippetinfofile);
			if ( statresult != -1 )
			{
				// if all snippets are successfully downloaded, set first item to true (and cut the snippets to shape at the end of the loop)
				completeditems[0] = processsnippetlist(rec->FileName(), completedsnippets);
			}
			else
			{
				// Parse this recording (directory) for video data.
				// This triggers parsing if the recording was not transferred (there is no snippetfile present).
				// If the recording was transferred (there is a snippetfile), processsnippetlist handles the parsing of 
				// successfully transferred minute blocks.
				// Note: blocks currently being downloaded are not parsed.
				parse(rec->FileName(), recfilecount);
				// we assume a recording without a downloadstatus file is a complete recording (used inside buildminuteblocks)
				completeditems[1000] = true;
			}
			// locate the info file
			infofile = string(rec->FileName()) + "/info.vdr";
			// parse channel and title information from the info file
			if ( getrecinfo(infofile, result) == -1 )
			{
				continue;
			}
			// use this information to build a cChannel object
			cChan = buildchannel(myrecinfo.channel);
			if (cChan == NULL)
			{
				continue;
			}
			// add this recording to the list of recordings
			// duration is set to 0 because we do not really need it here - the recordings gets chopped to minute blocks in the next step anyway
			addrecording(rec->FileName(), recfilecount, cChan, starttime, 0); ///\bug don't do this if the channel is encrypted
			// build minute blocks out of the parsed data files and publish them to cvideosource
			buildminuteblocks(rec->FileName(), recfilecount, rec->start, cChan, completedsnippets);
			// and last, cut the snippets if all have been successfully transferred
			if (completeditems[0] == true) cut(rec->FileName(), rec->start); 
		}
		// wait for a few minutes with the next run
		sleep(170);
	}
}

/** 
 * Tell the vdr a task is done.
 */

int vdrrecordings::done(string recdir, int number, time_t starttime)
{
	unsigned long stoptime;
	cChannel * cChan;
	recinfo myrecinfo;
	string infofile;
	char dlstatusfile[MAXPATHLENGTH];
	struct stat dlinfo;
	recinfo *result = &myrecinfo;

	infofile = recdir;
	infofile += "/info.vdr";
	// parse channel and title information from the info file
	if ( getrecinfo(infofile, result) == -1 )
	{
		//dsyslog("%s: no valid info.vdr file found for recording %s, skipping...", __PRETTY_FUNCTION__, recdir.c_str());
		return -1;
	}
	// use this information to build a cChannel object
	cChan = buildchannel(myrecinfo.channel);
	if (cChan == NULL) return -2;
	///\bug catch this error return
	// add the snippet number (as each snippet has a duration of ~1 minute)
	starttime += (number - 1) * 60;
	// get the duration of the recording
	snprintf(dlstatusfile, MAXPATHLENGTH, "%s%s", recdir.c_str(), "/downloadstatus.videotransport.vdr");
	bzero(&dlinfo, sizeof(dlinfo));
	if (stat(dlstatusfile, &dlinfo) == -1)
	{
		//dsyslog("%s: could not open %s, leaving task set to pending...", __PRETTY_FUNCTION__, dlstatusfile);
		return -2;
	}
	else
	{
		stoptime = ( ( dlinfo.st_size / 4 ) - 1 ) * 60 + starttime;
	}
	_ct->mark_done(starttime, stoptime, cChan);
	return 0;
}

/** 
 * Check how many _consecutive_ recfiles holding video data can be found in one recording (directory).
 * \param string dir: directory to search.
 * \param bool * existingsnippets: remembers which snippets were found and which were not
 * \return number of files found.
 */

int vdrrecordings::howmanyrecfiles(string recdir, bool * existingsnippets)
{
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	struct stat fileinfo;
	char infobuf[MAXPATHLENGTH];
	int count = 0;
	for (int i = 1 ; i < 1000; i++)
	{
		snprintf(infobuf, MAXPATHLENGTH, "%s%s%03i%s", recdir.c_str(), "/", i, ".vdr");
		if (stat(infobuf, &fileinfo) == -1)
		{
			if (existingsnippets != NULL) existingsnippets[i] = false;
		}
		else
		{
			if (existingsnippets != NULL) existingsnippets[i] = true;
			count++;
		}
	}
	return count;
}

/** 
 * Creates a list of recordings for internal use by vdrrecordings.
 * So far this list is only used by startsending and could probably be replaced by using the global Recordings.
 * \todo do that.
 */
void vdrrecordings::addrecording(string dir, int videofilesindir, cChannel *cChan, time_t starttime, time_t duration)
{
        //dsyslog("%s: begin", __PRETTY_FUNCTION__);
	vdrrecording * vrptr = new vdrrecording(dir, videofilesindir, cChan, starttime, duration);
        recs.push_back(vrptr);
}


/** 
 * Parses the file info.vdr created by vdr for the channel (tChannelID) and title of the recording.
 * \param string infofile: path to (and including) info.vdr.
 * \param struct recinfo *result: the struct storing the channel and the title.
 * \return 0 on success, -1 on error (no info.vdr found).
 */

int vdrrecordings::getrecinfo(string infofile, struct recinfo *result)
{
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
        FILE *infofp;
        char *line = NULL;
        size_t len = 0;
	ssize_t read;
        int pos;
	long filesize;

	// open the infofile for reading; if this fails or if the filesize is zero, an "empty" result set is returned
        infofp = fopen(infofile.c_str(), "r");
        if (infofp == NULL)
        {
                return -1;
        }
	fseek(infofp, 0, SEEK_END);
	filesize = ftell(infofp);
	if ( filesize == 0 ) return -1;
	else rewind(infofp);

	// parse the lines of infofile; so far only the channel and the title are of interest
        while ((read = getline(&line, &len, infofp)) != -1)
        {
		if (line[0] == 'C') result->channel = line;
		if (line[0] == 'T') result->title = line;
        }
        // if (line) free(line);
        fclose(infofp);
	// remove newlines from the result strings
        pos = result->channel.rfind("\n");
        result->channel = result->channel.substr(2,pos-2);
	if ( result->title.size() > 1 )
	{
		pos = result->title.rfind("\n");
		result->title = result->title.substr(2,pos-2);
		// replace blanks by underscores
		pos = result->title.find(' ');
		while(pos != -1)
		{
			result->title[pos] = '_';
			pos = result->title.find(' ');
		}
	}
        return 0;
}

/** 
 * Build a vtchannel object from the channel information gathered from the info.vdr file.
 * \param vtc: new instance of vtchannel.
 * \return true.
 * \bug I think both param and return documentation are bogus.
 */

cChannel * vdrrecordings::buildchannel(string chanprint)
{
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	vtchannel vtc(chanprint);
	return vtc.tovdr();
}

/** 
 * Parse all consecutive recfiles holding video data in one recording (directory) howmanyrecfiles could find for metadata.
 * \param string recdir: the path to the recording (directory).
 * \param int recfilecount: the number of recfiles found previously in that directory.
 * note: even on slow systems with lots of recordings this task WILL eventually terminate.
 */

void vdrrecordings::parse(string recdir, int recfilecount)
{
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	char recfile[MAXPATHLENGTH];
	char infofile[MAXPATHLENGTH];
	int errorcounter = 0;
	struct stat vidstat;
	struct stat recstat;

	// for each recfile in this directory
	for( int i = 1; i <= recfilecount ; i++ )
	{
		// build the names of the current recfile (source) and viddata file (target) 
		snprintf(recfile, MAXPATHLENGTH, "%s%s%03i%s", recdir.c_str(), "/", i, ".vdr");
		snprintf(infofile, MAXPATHLENGTH, "%s%s%03i", recdir.c_str(), "/viddata.", i);
		bzero(&recstat, sizeof(recstat));
		bzero(&vidstat, sizeof(vidstat));
		// this recfile should not be missing
		if (stat(recfile, &recstat) == -1) continue;
		// if the viddata file does not yet exist or if the recfile was changed since the last parsing (the first test for -1 should be redundant)
		if ( (stat(infofile, &vidstat) == -1) || (recstat.st_mtime > vidstat.st_mtime) )
		{
			// parse the current recfile, write output to infofile; give it up to three tries in case of errors
			if ( parsefile(recfile, infofile) == -1 && errorcounter < 3 )
			{
				//dsyslog("something went wrong while parsing file %s, trying again.", recfile);
				unlink(infofile);
				errorcounter++;
				i--;
			}
			// all went smoothly with this set; reset errorcounter
			else
			{
				errorcounter = 0;
			}
		}
	// and parse next file
	}
}

/**
 * Same as parse, with the difference that one single file will be parsed and that this file may be any from 001-999.
 * \param string recdir: the dir the file is sitting in.
 * \param int filenumber: which file we want to parse.
 * \bug add some nicer error handling
 */

void vdrrecordings::parseonefile(string dir, int filenumber)
{
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	char recfile[MAXPATHLENGTH];
	char infofile[MAXPATHLENGTH];
	int errorcounter = 0;
	struct stat vidstat;
	struct stat recstat;

	snprintf(recfile, MAXPATHLENGTH, "%s%s%03i%s", dir.c_str(), "/", filenumber, ".vdr");
	snprintf(infofile, MAXPATHLENGTH, "%s%s%03i", dir.c_str(), "/viddata.", filenumber);
	bzero(&recstat, sizeof(recstat));
	bzero(&vidstat, sizeof(vidstat));
	// this recfile should not be missing
	if (stat(recfile, &recstat) == -1) return;
	// if the viddata file does not yet exist or if the recfile was changed since the last parsing (the first test for -1 should be redundant)
	if ( (stat(infofile, &vidstat) == -1) || (recstat.st_mtime > vidstat.st_mtime) )
	{
		// parse this single recfile, write output to infofile: give it up to three tries in case of errors
		while ( parsefile(recfile, infofile) == -1 && errorcounter < 3 )
		{
			esyslog("something went wrong while parsing file %s, trying again.", recfile);
			unlink(infofile);
			errorcounter++;
		}
	}
	else
	{
		return;
	}
}

/** 
 * Build 66 sec blocks (that is, vectors) of hash and len values taken from the viddata files and publish them.
 * \param string recdir the directory that is currently processed.
 * \param int recfilecount: how many viddata files are expected. (possibly deprecated)
 * \param unsigned long starttime: when the recording started.
 * \param cChannel * cChan: the cChannel object.
 * \param bool * completedsnippets: which snippets are already completed (only these will be processed)
 * \return no so far, but 0 on success and -1 on error would probably be nice.
 */

void vdrrecordings::buildminuteblocks(string recdir, int recfilecount, time_t starttime, cChannel * cChan, bool * completedsnippets)
{
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	char infofile[MAXPATHLENGTH];
	int vidfp;
	struct stat st;
	uint viddataentries = 0;
	uint lastmodulo = 0;
	vid * viddata;
	hashes_t oneminhashlist;
	int currentsnippetno = 0;
	bool justoneminuteblock;

	printf("found %d recfiles in %s, processing\n", recfilecount, recdir.c_str());
	
	// for each viddatafile in this directory
	for( int i = 0; i < recfilecount; )
	{
		currentsnippetno++;
		///\todo this is not nice.... in every directory where downloads take place, this loop is executed 1000 times.
		if ( currentsnippetno > 999 ) break;
		if (completedsnippets[1000] != true)
		{
			if (completedsnippets[currentsnippetno] == false) 
			{
				// increment the starttime - if currentsnippet exists, this is done at the end of the for loop.
				starttime+=60;
				continue;
			}
			else 
			{
				i++;
			}
		}
		else
		{
			i = currentsnippetno;
			printf("found recording without downloadstatus\n");
		}
		printf("processing recfile %d: starttime %lu\n", i, starttime);
		
		// we start by assuming there is only one minute block of video data available; the nested for loop sets this to false, so 
		// multiple blocks are handled correctly (this is needed for the correct handling of each blocks / aggr_dest starting time)
		justoneminuteblock = true;
		// build the names of the current viddata file (source) 
		snprintf(infofile, MAXPATHLENGTH, "%s%s%03i", recdir.c_str(), "/viddata.", currentsnippetno);
		vidfp = open(infofile, O_RDONLY);
		bzero(&st, sizeof(st));
		fstat(vidfp, &st);
		errno=0;
		viddata = (vid*) mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, vidfp, 0);
		viddataentries = st.st_size / sizeof(vid);
		printf("found %d viddataentries (size of %s: %ld)\n", viddataentries, infofile, st.st_size);
		for( uint data = 0; data < viddataentries; data++ )
		{
			// build vector
			video_hash vh(viddata[data].hash, viddata[data].length);
			if ( oneminhashlist.size() <= data ) 
			{
				oneminhashlist.push_back(vh);
			}
			else 
			{
			       	oneminhashlist[data] = vh;
			}
			// test if one minute (1500 pics per minute) is reached, if yes publish vector, flush and restart
			if ( data != 0 && lastmodulo > viddata[data].picno % PPM)
			{
				// generate ~10% overlap (12 entries) to next cvideosource (if there are enough entries left in that viddata file)
				uint overlap = 1;
				for (; overlap <= 12; overlap++)
				{
					if (data+overlap < viddataentries)
					{
						video_hash vh(viddata[data+overlap].hash, viddata[data+overlap].length);
						if ( oneminhashlist.size() < data+overlap ) oneminhashlist.push_back(vh);
						else oneminhashlist[data+overlap] = vh;
					}
				}
				// if there are less than 12 entries left, try the consecutive viddata file (if it exists)
				if ( overlap < 12 )
				{
					snprintf(infofile, MAXPATHLENGTH, "%s%s%03i", recdir.c_str(), "/viddata.", currentsnippetno+1);
					bzero(&st, sizeof(st));
					if (stat(infofile, &st) != -1)
					{
						munmap(viddata, st.st_size);
						close(vidfp);
						vidfp = open(infofile, O_RDONLY);
						viddata = (vid*) mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, vidfp, 0);
						viddataentries = st.st_size / sizeof(vid);
						uint datafromnextfile = 0;
						// to prevent holes in oneminhashlist from incrementing both overlap and datafromnextfile
						uint datafromfirstfile = data + overlap;
						for (; overlap <= 12; overlap++)
						{
							video_hash vh(viddata[datafromnextfile].hash, viddata[datafromnextfile].length);
							if ( oneminhashlist.size() < datafromfirstfile+datafromnextfile ) oneminhashlist.push_back(vh);
							else oneminhashlist[datafromfirstfile+datafromnextfile] = vh;
							datafromnextfile++;
						}
						close(vidfp);
						munmap(viddata, st.st_size);
						snprintf(infofile, MAXPATHLENGTH, "%s%s%03i", recdir.c_str(), "/viddata.", currentsnippetno);
						stat(infofile, &st);
						viddata = (vid*) mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, vidfp, 0);
					}
				}
				// create the aggregation destination for that one-minute-block and add it to videosources
				// after that, clear the list, increment the starting time by one (next one-min-block) and start again
				aggr_dest ad(starttime, cChan);
				_vs->add(ad, oneminhashlist);
				printf("adding videosource with aggr_dest %lu, %p (%d entries)\n", starttime, cChan, oneminhashlist.size());
				oneminhashlist.clear();
				// to prevent double incrementation of starttime when processing one minute video blocks
				if (!justoneminuteblock || completedsnippets[1000] == true) starttime+=60;
			}
			justoneminuteblock = false;
			lastmodulo = viddata[data].picno % PPM;
		}
		// throw away the last few seconds of videodata (only full minutes get published)
		oneminhashlist.clear();
		///\bug ? to prevent the two minute gap between two consecutive vdr files (normal vdr recordings, not transferred snippets)
		//if (completedsnippets[1000] != true) starttime+=60;
		if (justoneminuteblock) starttime+=60;
		munmap(viddata, st.st_size);
		close(vidfp);
	}
}

/** 
 * Locates the position of three consecutive hash blocks in the viddata files generated by parsefile
 * \param uint32_t hash1-3: the three hash values
 * \param fileandpos * result: pointer to a fileandpos structure to store the result
 * \return 0 on success, -1 if the hash sequence is not found and -2 if there is no infofile (info.vdr) available to parse neccessary data
 * \todo allow the hash triple to originate from two consecutive viddatas.
 */

int vdrrecordings::startsending(uint32_t hash1, uint32_t hash2, uint32_t hash3, fileandpos * result)
{
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	int vidfp = 0;
        int counter = 0;
        uint32_t finaloffset = 0;
        int viddataentries = 0;
        ssize_t filesize = 0;
        struct stat fileinfo;
        string path, infofile;
        vid * viddata;
        bool foundfirsthash = false;
        bool foundsecondhash = false;
        bool foundthirdhash = false;
        int files = 0;
	int whichfile = 0;
	char buffer[MAXPATHLENGTH];
	recinfo rcinf;
	recinfo *rcinfptr;
	rcinfptr = &rcinf;

        list<vdrrecording*>::const_iterator current = get_recordingdata().begin();
        list<vdrrecording*>::const_iterator last = get_recordingdata().end();

	if ( current == last )
	{
		//dsyslog("%s: no recordings found in Recordings so far - sleeping 5 seconds", __PRETTY_FUNCTION__);
		sleep(5);
		list<vdrrecording*>::const_iterator current = get_recordingdata().begin();
		list<vdrrecording*>::const_iterator last = get_recordingdata().end();
	}
        for(; current != last; current++)
        {
                path = (**current).get_dir();
                files = (**current).get_videofilesindir();
                for(int i = 1; i <= files; i++)
                {
			snprintf(buffer, MAXPATHLENGTH, "%s%s%03i", path.c_str(), "/viddata.", i);
                        bzero(&fileinfo, sizeof(fileinfo));
                        vidfp = open(buffer, O_RDONLY);
                        fstat(vidfp, &fileinfo);
                        filesize = fileinfo.st_size;
                        viddataentries = filesize / sizeof(vid);

                        viddata = (vid*) mmap(0, filesize, PROT_READ, MAP_PRIVATE, vidfp, 0);

                        for (counter = 0; counter < viddataentries; counter++)
                        {
                                if(viddata[counter].hash == hash1) 
				{
					foundfirsthash = true;
					whichfile = i;	// remember in which file the first hash was found, as the triple may extend over it
				}
                                if(viddata[counter].hash == hash2 && foundfirsthash == true)
				{
					foundsecondhash = true;
				}
                                if(viddata[counter].hash == hash3 && foundfirsthash == true && foundsecondhash == true)
				{
					foundthirdhash = true;
				}
                                if(viddata[counter].hash != hash1 && viddata[counter].hash != hash2 && viddata[counter].hash != hash3)
                                {
                                        foundfirsthash = false;
                                        foundsecondhash = false;
                                        foundthirdhash = false;
                                }

                                if(foundthirdhash == true)      // success: we have found the hash triple
                                {
                                        finaloffset = viddata[counter-2].offset;
                                        counter = 0;
                                        close(vidfp);
                                        munmap(0, filesize);
                                        infofile = path + "/info.vdr";

                                        if ( getrecinfo(infofile, rcinfptr) == 0)
					{
						// there is some excess information here... only position and path are really used right now.
	                                        result->channel = rcinf.channel;
        	                                result->title = rcinf.title;
                	                        result->position = finaloffset;
						snprintf(result->path, MAXPATHLENGTH, "%s%s%03i%s", path.c_str(), "/", whichfile, ".vdr");
                                	        return 0;
					}
					else return -2;
                                }
                        }

                        munmap(0, filesize);
                        close(vidfp);
                }
        }
        close(vidfp);
        return -1;
}

/** 
 * Construct the name of the next recfile from a given (path) and filename.
 * \param string file: the path and filename of the current recfile.
 * \param string * result: a pointer to a string that will store the next recfile (increment extension by one).
 * \return 0 if successful, -1 if too many recfiles are found.
 */

int vdrrecordings::getnextrecfile(string file, string * result)
{
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	string buf;
        char res[MAXPATHLENGTH];
        uint number;

	buf = file.substr(file.size()-7, file.size()-5);
	sscanf(buf.c_str(), "%u", &number);
	number++;
	if ( number > 999 )
	{
		return -1;
	}
	snprintf(res, MAXPATHLENGTH, "%s%s%03u%s", file.substr(0, file.size()-7).c_str(), "/", number, ".vdr");
	result->assign(res);

        return 0;
}

/** 
 * Cut transferred blocks so that they could be copied together to form the original recording.
 * At least two blocks are expected (obviously... otherwise it would be hard to figure out where to cut the first one).
 * Maximum of 999 files to be cut (vdr-imposed limit).
 * And the files have to be in a consecutive order (a missing file will cause the function to reschedule that block).
 * \bug so far this is not handled correctly: the re-request is not flagged and so will cause trouble or at least not improve matters.
 * \param string dir: the directory that contains the files to be cut (xxx.vdr).
 * \return: 0 on success; if a gap in the hash sequence is encountered, the number of the vdr file where that happened; -1 if no viddata files are found.
 */

int vdrrecordings::cut(string dir, time_t starttime)
{
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	char recfile[MAXPATHLENGTH];
	char infofile1[MAXPATHLENGTH];
	char infofile2[MAXPATHLENGTH];
	int viddatafp;
	int counter = 0;
	int entry;
	vid * viddata;
	struct stat st1;
	struct stat st2;
	uint32_t firsthash;
        uint32_t firstoffset = 0, storedoffset = 0;
	uint32_t wheretocut = 0;
	bool finished = false;

	do
	{
		counter++;
		snprintf(infofile1, MAXPATHLENGTH, "%s%s%03i", dir.c_str(), "/viddata.", counter);		
		snprintf(infofile2, MAXPATHLENGTH, "%s%s%03i", dir.c_str(), "/viddata.", counter+1);		
		bzero(&st1, sizeof(st1));
		bzero(&st2, sizeof(st2));
		if ( stat(infofile1, &st1) == -1 )
		{
			//dsyslog("%s: found no viddata files - exiting.", __PRETTY_FUNCTION__);
			return -1;
		}
		// if this was the last consecutive viddata file, stop the merging.
		if ( stat(infofile2, &st2) == -1 )
		{
			//dsyslog("merging of files in directory %s finished.", dir.c_str());
			finished = true;
		}
		if ( finished != true )
		{
			viddatafp = open(infofile2, O_RDONLY);
			viddata = (vid*) mmap(0, st2.st_size, PROT_READ, MAP_PRIVATE, viddatafp, 0);
			// the first "entry" may differ...
			firsthash = viddata[0].hash;
			storedoffset = firstoffset;
			firstoffset = viddata[0].offset;
			munmap(0, st2.st_size);
			close(viddatafp);
			viddatafp = open(infofile1, O_RDONLY);
			viddata = (vid*) mmap(0, st1.st_size, PROT_READ, MAP_PRIVATE, viddatafp, 0);
			entry = st1.st_size / sizeof(vid);
			// try to find the first hash key from viddata.xxx+1 in viddata.xxx
			for ( ; entry >= 0; entry--)
			{
				if ( firsthash == viddata[entry].hash )
				{
					// mark everything before the first matching hash key from viddata.xxx to remain; rest will be truncated
					wheretocut = viddata[entry].offset;
					break;
				}
				// oops - no match found, so we add this minute snippet as a new task and stop the merging. 
				if ( entry == 0 )
				{
					//dsyslog("sorry - could not find a matching hash value in %s and %s.", infofile1, infofile2);
					munmap(0, st1.st_size);
					close(viddatafp);
					reschedule(dir, counter, starttime);
					changename(dir, counter);
					return counter;
				}
			}
			munmap(0, st1.st_size);
			close(viddatafp);
		}

		// now we open the recfile corresponding to viddata.xxx and truncate it at the correct position 
		snprintf(recfile, MAXPATHLENGTH, "%s%s%03i%s", dir.c_str(), "/", counter, ".vdr");		
		// if this is the last recfile, copy its entire content
		if ( finished == true )
		{
			bzero(&st1, sizeof(st1));
			stat(recfile, &st1);
			wheretocut = st1.st_size;
		}
		// now cut it to the right size
		truncate(recfile, wheretocut);
	}
	// and continue as long as there are viddata files left to check and we have processed less than 999 snippets
	while ( finished == false && counter < 999 );

	// job done, now delete the viddata files (they contain wrong data) to trigger new parsing with next run of vdrrecording 
	// note: this may be removed as the vdr-files will have a newer timestamp than the viddata files and so there will be a reparsing anyways.
	for ( ; counter > 0; counter-- )
	{
		snprintf(infofile1, MAXPATHLENGTH, "%s%s%03i", dir.c_str(), "/viddata.", counter);
		unlink(infofile1);
	}
	// we have finished that directory -> tell vdr
	done(dir, 1, starttime);
	return 0;
}

/** 
 * Reschedule missing minute blocks. 
 * \param string dir: the directory in which a block is missing.
 * \param int number: which block to reschedule (starttime + counter*60)
 * \todo pass an offset to ctasks; this would allow the resuming of broken downloads. But so far broken downloads look like running downloads. 
 */

void vdrrecordings::reschedule(string dir, int number, time_t starttime)
{
	unsigned long stoptime;
	cChannel * channel;
	struct recinfo rinfo;
	struct recinfo *ri;
	string infofile;
	string savedir;
	int pos;

	ri = &rinfo;

	starttime += (number * 60);
	stoptime = starttime + 60;

	infofile = dir;
	infofile += "/info.vdr";
	getrecinfo(infofile, ri);
	channel = buildchannel(rinfo.channel);
	savedir = dir;
	pos = savedir.rfind("/");
	savedir.erase(pos, savedir.size());
	pos = savedir.rfind("/");
	savedir.erase(0, pos);
	
	_ct->add(starttime, stoptime, channel, number, savedir.c_str());
}

/** 
 * Rename all recfiles after counter.vdr.
 * \param string dir: the directory this is going to take place in.
 * \param int number: the last recfile that is to be left untouched.
 * Note: all snippets have been successfully downloaded - otherwise this function would not have been called.
 * But: we have to revise the snippet list (do we?).
 */

void vdrrecordings::changename(string dir, int number)
{
	int recfilecount;
	char oldfilename[MAXPATHLENGTH];
	char newfilename[MAXPATHLENGTH];
	downloadstatus _dlstatus;
	
	recfilecount = howmanyrecfiles(dir, NULL);

	_dlstatus.switchdir(dir);
	_dlstatus[number].state = missing;
	
	for ( ; recfilecount > number; recfilecount-- )
	{
		snprintf(newfilename, MAXPATHLENGTH, "%s%s%03i%s", dir.c_str(), "/", recfilecount+1, ".vdr");
		snprintf(oldfilename, MAXPATHLENGTH, "%s%s%03i%s", dir.c_str(), "/", recfilecount, ".vdr");
		rename(oldfilename, newfilename);
		_dlstatus[number].state = finished;
	}
}

/** 
 * Checks if all snippets from one recording were downloaded successfully.
 * If they are, cut is called.
 * If they are not, dirty (i.e. partially transferred) snippets are deleted and their state is reset to missing.
 * \todo it would be nicer to allow resuming of partially downloaded snippets.
 */

bool vdrrecordings::processsnippetlist(string dir, bool * completedsnippets)
{
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	string snippetdata;
	bool completed = true;
	int entries;
	downloadstatus _dlstatus;
	struct stat snippetinfo;
	
	snippetdata = dir + "/downloadstatus.videotransport.vdr";
	bzero(&snippetinfo, sizeof(snippetinfo));
	stat(snippetdata.c_str(), &snippetinfo);
	
	entries = snippetinfo.st_size / sizeof(snippet) - 1;
	_dlstatus.switchdir(dir);
	for ( int i = 1; i <= entries; i++ )
	{
		if (_dlstatus[i].state == missing)
		{
			completed = false;
			completedsnippets[i] = false;
		}
		else if (_dlstatus[i].state == dirty)
		{
			completed = false;
			completedsnippets[i] = false;
			///\bug check if these are still transferred - if not, delete them and start again (or continue at offset)
		}
		else if (_dlstatus[i].state == finished)
		{
			completedsnippets[i] = true;
			parseonefile(dir, i);
		}
	}
	return completed;
}


