// g++ -g -Wall `pkg-config --cflags dclib` `pkg-config --libs dclib` -o dclib-fix-timestamps dclib-fix-timestamps.cpp

/***************************************************************************
   dclib-fix-timestamps.cpp  -  Fix timestamps hit by FAT32 / DST issues
                             -------------------
    begin                : Sun Aug 24 2008
    copyright            : (C) 2008 by Edward Sheldrake
    email                : ejs1920@yahoo.co.uk
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

/*
 * This program fixes all timestamps in the hash database that are
 * out by some integral number of hours. This happens in Linux for
 * files stored on FAT32 partitions (don't know about NTFS)
 * when the clocks change for daylight savings time. It may happen
 * after the next boot, not next mount or when the clock changes.
 *
 * Consider not using this program, and instead use the tz=UTC
 * mount option, or a better filesystem.
 *
 * Warning: There is no way to determine if the file was actually
 * modified exactly N hours ago and it's size was unchanged, and
 * it actually does need to be re-hashed. So your hash database
 * should otherwise be fully up to date.
 *
 * All entries for which:
 *
 * The file cannot be found or stat'ed
 * The file size has changed
 * The modification timestamps do not differ by N hours
 *
 * are copied unchanged into the new files.
 *
 * Your original hashbase.bin is not changed, instead
 * hashbase.bin.fixed is saved.
 */

#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 64
#endif

#include <dclib/core/cstring.h>
#include <dclib/core/cbytearray.h>
#include <dclib/core/cdir.h>

// only for struct fbo/hbo
#include <dclib/core/types.h>
#include <dclib/csearchindex.h>

#include <dclib/cconfig.h>

#include <stdio.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int main( int argc, char *argv[] )
{
	if ( argc != 2 )
	{
		printf("Usage: %s [directory containing your .dc directory]\n",argv[0]);
		printf("So run: %s %s\n",argv[0],CDir::HomeDirPath().Data());
		printf("And because the output will be very long run:\n");
		printf("%s %s > logfile.txt\n",argv[0],CDir::HomeDirPath().Data());
		return 100;
	}
	
	CDir dir;
	CString configpath = argv[1];

	if ( dir.IsDir( configpath, false ) == false )
	{
		printf("%s is not a directory\n",configpath.Data());
		return 200;
	}
	
	CConfig::SetInstance(new CConfig(configpath));
	
	CConfig::Instance()->LoadDCLib();
	
	CString filename;
	CString prefix = configpath + "/.dc/";
	
	struct stat buf;
	
	CByteArray filebase, filepaths, filenames, newfilebase;
	struct filebaseobject fbo;
	
	filebase.LoadFromFile(prefix + "database.bin");
	filepaths.LoadFromFile(prefix + "pathbase.bin");
	filenames.LoadFromFile(prefix + "filebase.bin");
	newfilebase.SetSize(0);
	
	/*
	 * Actually this is not necessary because database.bin is always re-created
	 * from scratch.
	 */
	
	printf("***** Needlessly doing files database anyway *****\n");
	
	for ( unsigned long i = 0; i < filebase.Size(); i += sizeof(struct filebaseobject) )
	{
		memcpy( &fbo, filebase.Data()+i, sizeof(struct filebaseobject) );
		
		if ( (fbo.m_nPathIndex == (unsigned long)-1) || (fbo.m_nFileIndex == (unsigned long)-1) )
		{
			printf("Copy fbo with (unsigned long)-1) file/path index at %lu\n",i);
			newfilebase.Append( (const char*)&fbo, sizeof(struct filebaseobject) );
			continue;
		}
		
		if ( (fbo.m_nPathIndex > filepaths.Size()) || (fbo.m_nFileIndex > filenames.Size()) )
		{
			printf("Copy fbo with invalid file/path index at %lu\n",i);
			newfilebase.Append( (const char*)&fbo, sizeof(struct filebaseobject) );
			continue;
		}
		
		filename  = (char*) filepaths.Data()+fbo.m_nPathIndex;
		filename += '/';
		filename += (char*) filenames.Data()+fbo.m_nFileIndex;
		
		filename = dir.SimplePath( filename );
		printf("%lu %s ",i,filename.Data());
		
		filename = CConfig::Instance()->AliasToPath(filename);
		
		if ( dir.IsFile( filename, false ) )
		{
			if ( dir.getFileSize( filename, false ) == fbo.m_nSize )
			{
				if ( stat( filename.Data(), &buf ) == 0 )
				{
					if ( fbo.m_tModTime != buf.st_mtime )
					{
						time_t difference;
						
						if ( fbo.m_tModTime > buf.st_mtime )
						{
							difference = fbo.m_tModTime - buf.st_mtime;
						}
						else
						{
							difference = buf.st_mtime - fbo.m_tModTime;
						}
						
						if ( (difference % 3600) == 0 )
						{
							printf("mtime was %lu now %lu\n",fbo.m_tModTime,buf.st_mtime);
							fbo.m_tModTime = buf.st_mtime;
						}
						else
						{
							printf("%lu %lu not off by n hours\n",fbo.m_tModTime,buf.st_mtime);
						}
					}
					else
					{
						printf("ok\n");
					}
				}
				else
				{
					printf("stat failed\n");
				}
			}
			else
			{
				printf("sizes differ\n");
			}
		}
		else
		{
			printf("not found / not file\n");
		}
		
		newfilebase.Append( (const char*)&fbo, sizeof(struct filebaseobject) );
	}
	
	newfilebase.SaveToFile(prefix + "database.bin.fixed");
	
	CByteArray hashbase, hashpaths, hashnames, newhashbase;
	struct hashbaseobject hbo;
	
	hashbase.LoadFromFile(prefix + "hashbase.bin");
	hashpaths.LoadFromFile(prefix + "hashpathbase.bin");
	hashnames.LoadFromFile(prefix + "hashfilebase.bin");
	newhashbase.SetSize(0);
	
	printf("***** Doing hash database *****\n");
	
	long fixed = 0;
	
	for ( unsigned long i = 0; i < hashbase.Size(); i += sizeof(struct hashbaseobject) )
	{
		memcpy( &hbo, hashbase.Data()+i, sizeof(struct hashbaseobject) );
		
		if ( (hbo.m_nPathIndex == (unsigned long)-1) || (hbo.m_nFileIndex == (unsigned long)-1) )
		{
			printf("Copy hbo with (unsigned long)-1) file/path index at %lu\n",i);
			newhashbase.Append( (const char*)&hbo, sizeof(struct hashbaseobject) );
			continue;
		}
		
		if ( (hbo.m_nPathIndex > hashpaths.Size()) || (hbo.m_nFileIndex > hashnames.Size()) )
		{
			printf("Copy hbo with invalid file/path index at %lu\n",i);
			newhashbase.Append( (const char*)&hbo, sizeof(struct hashbaseobject) );
			continue;
		}
		
		filename  = (char*) hashpaths.Data()+hbo.m_nPathIndex;
		filename += '/';
		filename += (char*) hashnames.Data()+hbo.m_nFileIndex;
		filename = dir.SimplePath( filename );
		
		printf("%lu %s ",i,filename.Data());
		
		filename = CConfig::Instance()->AliasToPath(filename);
		
		if ( dir.IsFile( filename, false ) )
		{
			if ( dir.getFileSize( filename, false ) == hbo.m_nSize )
			{
				if ( stat( filename.Data(), &buf ) == 0 )
				{
					if ( hbo.m_tModTime != buf.st_mtime )
					{
						time_t difference;
						
						if ( hbo.m_tModTime > buf.st_mtime )
						{
							difference = hbo.m_tModTime - buf.st_mtime;
						}
						else
						{
							difference = buf.st_mtime - hbo.m_tModTime;
						}
						
						if ( (difference % 3600) == 0 )
						{
							printf("mtime was %lu now %lu\n",hbo.m_tModTime,buf.st_mtime);
							hbo.m_tModTime = buf.st_mtime;
							++fixed;
						}
						else
						{
							printf("%lu %lu not off by n hours\n",hbo.m_tModTime,buf.st_mtime);
						}
					}
					else
					{
						printf("ok\n");
					}
				}
				else
				{
					printf("stat failed\n");
				}
			}
			else
			{
				printf("sizes differ\n");
			}
		}
		else
		{
			printf("not found\n");
		}
		
		newhashbase.Append( (const char*)&hbo, sizeof(struct hashbaseobject) );
	}
	
	newhashbase.SaveToFile(prefix + "hashbase.bin.fixed");
	
	printf("Adjusted %ld timestamps in hash database\n",fixed);
	
	printf("New files hashbase.bin.fixed and database.bin.fixed have been\n");
	printf("created in %s\n",prefix.Data());
	printf("Confirm that they are the same size as database.bin and hashbase.bin .\n");
	printf("Rename the original database.bin and hashbase.bin .\n");
	printf("Then remove .fixed from the names of the new files.\n");
	printf("\n");
	printf("Consider using the tz=UTC mount option rather than this program.\n");
	
	delete CConfig::Instance();
	
	return 0;
}
