// BlockDelta.h #if _MSC_VER >= 1000 #pragma once #endif // _MSC_VER >= 1000 #ifndef BlockDeltah #define BlockDeltah #include "LZW.h" // To compress blocks for transmission #include "MD5.h" // For strong checksum #include "Roller32.h" // For CRoller32 checksum #include "DTree.h" // for CFileDetails (File Object) data structure #include "LocalPath.h" // for CreatePath #include "FileDetails.h" /* CBlockDelta Remote File Get with minimal data transfer. This class is for Client/Server File transfer across slow mediums. It is intended for use in Backup/Archiving/Versioning Applications. Any computer wanting a file from another computer simply calls CBlockDelta::Get(...) with apropriate arguments. The minimal data transfer is a result of Delta Compression: If the computer asking for the file already has a similar file (an older version), the data transmission simply describes the changes needed to convert the old file into the new file. In addition, the Data Blocks transmitted are LZW compressed if the system thinks it's worth doing. The Get and Put routines use CArchives as communication links; you would normally use CSocketFile based CArchives. Normally when comparing two files you compare the first Byte, then the next, and so on, but over a slow medium that would mean copying the whole of the Remote file over anyway, so you need a way of comparing blocks of Bytes at a time. CBlockDelta uses 512 Byte locks simply because I found that worked best and varying the block size makes the code ugly. So comparing blocks can be done with checksums.... MD5 would compare two blocks very accurately, but it's quite slow. The spark that makes delta-compression fire is the "Rolling Checksum" which allows you to get the checksum of the first 512 Bytes (all fine and normal so far) but then you can get the checksum of the 512 Bytes starting from Byte 1 simply by removing the first Byte and adding the next Byte with a little bit of maths (rather than re-visiting most of the Bytes). You may need to re-read this paragraph in a minute, but carry on for now: So, one computer wants a file that it already has an old version of. It creates an array of checksums, two entries for each 512 Byte block. One entry uses the Rolling Checksum alogrithm on the block (because it's fast) and the other entry contains the MD5 checksum for the block (because its accurate). It sends this array of Checksums to a computer which has a more recent version. This computer opens the more recent version and looks at the first 512 Bytes, forming the Rolling Checksum and sees if that checksum exists in the Checksum array. If it doesn't then it rolls one byte forward in its file, by removing the first Byte from the rolling checksum, and adding the 513th Byte to the Rolling checksum. It looks again for the new value in the Checksum Array. Lets assume that it finds it, this time. Since the Rolling Checksum isn't very accurate (different blocks may give the same value), the MD5 checksum is obtained and checked with the appropriate entry in the array (which is now a Hash Table for speed). If the MD5 matches we assume we have the same block (true, we may not, but the chances are microscopic). So that means that we have one new byte at the beginning of the file and a block that we already know. CBlockDelta forms a CString which describes the differences (called Comparison) which is just a text file one line describes either a start and end index to data, or a block number or range of blocks. So if the first Byte was all that was added, and we had 1000 block of data afterwards, the Comparison string would simply say: 1,1 0-999 Comparison: All numbers in the string are in Hexadecimal. The string contains lines of text separated with '\n' characters. A single number on a line indicates a Block from the Local file. A Block is 512 Bytes except for the last Block which may be shorter (FileSums::GetEndBlockLength()). A pair of numbers separated with a '-' indicate a range of adjacent Blocks from the Local File ([First Block]-[Last Block] inclusive). A pair of numbers separated with a ',' indicate a range of adjacent Bytes from the Remote File ([File Position],[Bytes to read]). If the Files are equal you get a single line string: 0-FileSums::GetBlockCount() indicating that all the Blocks of the Local File make the Remote file. If the Files are totally different you get a single line string: 0,FileSums::FileLength indicating that all the Bytes of the Remote File make the Remote file. MakeDelta: The computer with the latest version of the file then uses the comparison string to return a binary stream of data that contains blocks of data that the other computer doesn't have, and block numbers that it does have. UseDelta: The computer with the old version of the file creates a new file using the blocks from either the data stream, or the old file according to the instructions in the data stream. Example Usage: ============== Derive a class from CBlockDelta that will create CSocketFile based CArchives. The following example is of a Dialog Box with a CBlockDelta class within it. Create a dialog application with a List Box (IDC_LIST1) and make the dialog class include the following code: #include // MFC socket extensions #include "BlockDelta.h" class CBlockDeltaTesterDlg : public CDialog { public: CBlockDeltaTesterDlg(CWnd* pParent = NULL); // standard constructor void Say(const CString& Msg) {((CListBox*)GetDlgItem(IDC_LIST1))->AddString(Msg);} class CRemoteFileFetcher : public CBlockDelta { CBlockDeltaTesterDlg* Dlg; static DWORD FAR PASCAL ThreadProc(LPVOID lpData) {((CRemoteFileFetcher*)lpData)->Listen(); return 0;} HANDLE hThread; DWORD ThreadID; public: CRemoteFileFetcher(CBlockDeltaTesterDlg* Dlg) : Dlg(Dlg) {hThread=CreateThread((LPSECURITY_ATTRIBUTES)NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, (LPVOID)this, 0, &ThreadID);} void Say(const CString& Msg) {Dlg->Say(Msg);} virtual void Getting(const CString& Remote) {Say("Putting "+Local);} // Called when starting to Put a file virtual void SetBytesToRead (DWORD Bytes) {CString S; S.Format("<%u Bytes to read" ,Bytes); Say(S);} // Called as soon as BytesToRead is known virtual void SetBytesToWrite(DWORD Bytes) {CString S; S.Format(">%u Bytes to write",Bytes); Say(S);} // Called as soon as BytesToWrite is known virtual void SetInProgress (DWORD Bytes) {CString S; S.Format("<%u Bytes in" ,Bytes); Say(S);} // Called whenever Bytes are received virtual void SetOutProgress (DWORD Bytes) {CString S; S.Format(">%u Bytes out" ,Bytes); Say(S);} // Called whenever Bytes are sent virtual void Received (char Compression) {CString S; S.Format("<%u%% compressed" ,Compression); Say(S);} // Called when finished Putting (Compression is %Compressed) virtual void Sent (char Compression) {CString S; S.Format(">%u%% compressed" ,Compression); Say(S);} // Called when finished Getting (Compression is %Compressed) void Listen() { AfxSocketInit(); CSocket ServerSocket; ServerSocket.Create(14); ServerSocket.Listen(); for(;;) { CSocket Socket; if(Abort || !ServerSocket.Accept(Socket)) return; CSocketFile File(&Socket); CArchive ArI(&File, CArchive::load); CArchive ArO(&File, CArchive::store); Put(ArI,ArO); } } // Returns an error message or "" if everything worked. CString Fetch(const CString& Remote, const CString& Local, const CString& Output, const CString& ServerIP) { AfxSocketInit(); CSocket Socket; if(!Socket.Create() || !Socket.Connect(ServerIP, 14)) return "Socket creation failed"; CSocketFile SocketFile(&Socket); CArchive ArI(&SocketFile, CArchive::load); CArchive ArO(&SocketFile, CArchive::store); return Get(Remote,Local,Output, ArI,ArO); } }; ... The rest of the dialog class }; Make the OnInitDialog() look like this: BOOL CBlockDeltaTesterDlg::OnInitDialog() { CDialog::OnInitDialog(); CRemoteFileFetcher RemoteFileFetcher(this); CString S; S=RemoteFileFetcher.Fetch("Remote.txt","Local.txt","Output.txt", "127.0.0.1"); if(!S.IsEmpty()) AfxMessageBox(S); // could just return S if this is inside a Function returning a CString. return TRUE; // return TRUE unless you set the focus to a control } CRemoteFileFetcher creates a listening Thread as soon as it is instantiated. So if you run it on two computers, either computer may use the Fetch(...) function and the other will respond. For a quick test on a single computer you just use the IP address "127.0.0.1", otherwise use two computers. In the project's folder (the usual startup directory in Debug mode) copy the normal ReadMe.txt file to a file called Local.txt. Then copy Local.txt to a file called Remote.txt. Edit Local.txt with Notepad: select all the text and copy it, then paste it six times in succession. Now you have two similar files and this dialog will report what is happening when they are compared. If you were doing this on two computers, Remote.txt would be on the Server and Local.txt on the Client. The line in the OnInitDialog(): S=RemoteFileFetcher.Fetch("Remote.txt","Local.txt","Output.txt", "127.0.0.1"); tells the class to Fetch Remote.txt using Local.txt (if it can) to create Output.txt on the Local(Client) computer. The block size if fixed throughout the class as 512 Bytes (found by experiment to be optimum). My ReadMe.txt file was split into 7 blocks and one 175 Byte block which is sent compressed to 109 Bytes. Since the Blocks are only sent as numbers the zipped block was the only File data sent through the Sockets. Taking all socket data into account, the file was effectively sent through the Sockets 89% compressed and 96% compressed if the Remote.txt is the one with six pastes. */ //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> CBlockDelta <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #ifndef Languageh static const CString sNoOpenRemote ("Couldn't open Remote File"); static const CString sNoOpenOutput ("Couldn't open Output File"); static const CString sNoOpenTmp ("Couldn't open Temp Data File"); static const CString sNoOpenLZW ("Couldn't open Temp LZW File"); static const CString sCouldntGet ("Couldn't Get "); static const CString sCouldntPut ("Couldn't Put "); static const CString sBadComparison("Empty Comparison"); static const CString sCouldntCreate("Couldn't Create "); #endif class CBlockDelta { protected: //Data that the client may like to have while Geting: CFileDetails RFO; // Remote File Object CString Comparison; /* The Comparison string: All numbers in the string are in Hexadecimal. The string contains lines of text separated with '\n' characters. A single number on a line indicates a Block from the Local file. A Block is 512 Bytes except for the last Block which may be shorter (FileSums::GetEndBlockLength()). A pair of numbers separated with a '-' indicate a range of adjacent Blocks from the Local File ([First Block]-[Last Block] inclusive). A pair of numbers separated with a ',' indicate a range of adjacent Bytes from the Remote File ([File Position],[Bytes to read]). If the Files are equal you get a single line string: 0-FileSums::GetBlockCount() indicating that all the Blocks of the Local File make the Remote file. If the Files are totally different you get a single line string: 0,FileSums::FileLength indicating that all the Bytes of the Remote File make the Remote file. */ CString TempPath; //The path of this computer's temp folder. DWORD BytesToWrite; DWORD LogicalBytesOut; DWORD PhysicalBytesOut; DWORD BytesToRead; DWORD LogicalBytesIn; DWORD PhysicalBytesIn; void Received(DWORD Logically, DWORD Physically) { PhysicalBytesIn+=Physically; LogicalBytesIn+=Logically; if(LogicalBytesIn>BytesToRead) LogicalBytesIn=BytesToRead; if(Logically) SetInProgress(Logically); } void Sent(DWORD Logically, DWORD Physically) { PhysicalBytesOut+=Physically; LogicalBytesOut+=Logically; if(LogicalBytesOut>BytesToWrite) LogicalBytesOut=BytesToWrite; if(Logically) SetOutProgress(Logically); } public: CBlockDelta() { DWORD Len=GetTempPath(0,0); if(Len) { GetTempPath(Len, TempPath.GetBufferSetLength(Len)); TempPath.ReleaseBuffer(); }else TempPath='\\'; } virtual ~CBlockDelta() {} void Clear() { BytesToWrite= LogicalBytesOut= PhysicalBytesOut= BytesToRead= LogicalBytesIn= PhysicalBytesIn=0; } virtual void Getting(const CString& Remote) {} // Called when starting to Get a file virtual void Putting(const CString& Local ) {} // Called when starting to Put a file virtual void SetBytesToRead (DWORD Bytes) {} // Called as soon as BytesToRead (the File size) is known virtual void SetBytesToWrite(DWORD Bytes) {} // Called as soon as BytesToWrite (the File size) is known virtual void SetInProgress (DWORD Bytes) {} // Called whenever Bytes are received (Bytes is NOT cumulative) virtual void SetOutProgress (DWORD Bytes) {} // Called whenever Bytes are sent (Bytes is NOT cumulative) virtual void Received (char Compression) {} // Called when finished Putting (Compression is %Compressed) virtual void Sent (char Compression) {} // Called when finished Getting (Compression is %Compressed) CString GetComparison() const {return Comparison;} //The first 3 parameters are file paths, the last two are the client/server communication link. CString Get(const CString& Remote, const CString& Local, const CString& Output, CArchive& ArI, CArchive& ArO) { try { Clear(); Getting(Remote); int i=Local.ReverseFind('\\')+1; if(i) if(!CLocalPath::CreatePath(Local.Left(i))) return sCouldntCreate+Local.Left(i); //Make sure the folder exists FileSums Sums(Local); // Calculate the checksum array for the local file ArO << Sums << Remote; ArO.Flush(); // Send the result to the server and tell it which file we want Sent(0, Sums.SumPairs ? sizeof(DWORD)+5*Sums.GetBlockCount() : sizeof(DWORD)); ArI >> Comparison; // Get the results of the Server's comparison Received(0, Comparison.GetLength()); CFileStatus FS; // Set the file attributes. ArI >> FS.m_ctime >> FS.m_mtime >> FS.m_atime >> FS.m_size >> FS.m_attribute; Received(0, sizeof(FS.m_ctime)+sizeof(FS.m_mtime)+sizeof(FS.m_atime)+sizeof(FS.m_size)+sizeof(FS.m_attribute)); SetBytesToRead(BytesToRead=FS.m_size); CString OK(UseDelta(Local, ArI, Output)); // Recreate Remote in Output using Local. if(OK.IsEmpty()) { FS._m_padding=0; FS.m_szFullName[0]=0; CFile::SetStatus(Output, FS); } Received((char)(100-100.*PhysicalBytesIn/LogicalBytesIn)); return OK; }catch(...) {return "BlockDelta "+sCouldntGet+Remote;} } //The parameters are the client/server communication link. CString Put(CArchive& ArI, CArchive& ArO) { Clear(); CString Path; try { FileSums Sums; ArI >> Sums >> Path; // Receive the checksum array for the client's file Received(0, Sums.SumPairs ? sizeof(DWORD)+5*Sums.GetBlockCount() : sizeof(DWORD)); Putting(Path); CString Comparison(Compare(Path, Sums)); // Calculate the differences ArO << Comparison; ArO.Flush(); // Tell the client Sent(0, Comparison.GetLength()); CFileStatus FS; CFile::GetStatus(Path, FS); // Send the file attributes. ArO << FS.m_ctime << FS.m_mtime << FS.m_atime << FS.m_size << FS.m_attribute; ArO.Flush(); Sent(0, sizeof(FS.m_ctime)+sizeof(FS.m_mtime)+sizeof(FS.m_atime)+sizeof(FS.m_size)+sizeof(FS.m_attribute)); SetBytesToWrite(BytesToWrite=FS.m_size); CString OK(MakeDelta(Comparison, Path, ArO)); // Then send any data the client needs to reproduce our copy of the file. Sent((char)(100-100.*PhysicalBytesOut/LogicalBytesOut)); return OK; }catch(...) {return "BlockDelta "+sCouldntPut+Path;} } //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> struct FileSums <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< struct FileSums { DWORD FileLength; struct SumPair { DWORD QCS; // Quick checksum BYTE MD5[16]; // Strong checksum }* SumPairs; bool Valid; FileSums() : FileLength(0), SumPairs(0) {} bool IsValid() const {return Valid;} ~FileSums() {try{delete[] SumPairs;}catch(...){}} DWORD GetBlockCount () const {return (FileLength>>9)+1;} DWORD GetEndBlockLength() const {return FileLength & 0x1FF;} FileSums(const CString& Path) : FileLength(0), SumPairs(0), Valid(false) { CFile File; if(!File.Open(Path, CFile::modeRead | CFile::shareDenyNone)) return; Valid=true; FileLength=File.GetLength(); if(!FileLength) return; BYTE Buffer[512]; SumPairs=new SumPair[GetBlockCount()]; if(!SumPairs) return; CMD5 MD5; MD5Context Context; DWORD Bytes; CRoller32 Roller; for(SumPair* Pair=SumPairs; Bytes=File.Read(Buffer, sizeof(Buffer)); ++Pair) { Roller.Set(Buffer, Bytes); Pair->QCS=Roller.Get(); MD5.Init(&Context); MD5.Update(&Context, Buffer, Bytes); MD5.Final(Pair->MD5, &Context); } } friend CArchive& __stdcall operator<<(CArchive& ar, const FileSums& Sums) { if(!Sums.SumPairs) ar << (DWORD)0; else { ar << Sums.FileLength; DWORD* it=(DWORD*)(Sums.SumPairs); for(DWORD i=5*Sums.GetBlockCount(); i--; ar << *it++); //5 DWORDs per array entry } return ar; } friend CArchive& __stdcall operator>>(CArchive& ar, FileSums& Sums) { delete Sums.SumPairs; Sums.SumPairs=0; ar >> Sums.FileLength; if(!Sums.FileLength) Sums.SumPairs=0; else{ Sums.SumPairs=new SumPair[Sums.GetBlockCount()]; DWORD* it=(DWORD*)(Sums.SumPairs); for(DWORD i=5*Sums.GetBlockCount(); i--; ar >> *it++); //5 DWORDs per array entry } return ar; } }; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> struct HashTable <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< struct HashTable { const FileSums& Sums; DWORD* Table; // HashTable 1<<16 entries; Key=>QCS, Value=>Sorter Index struct Sorter { WORD Key; // QCS Hash DWORD Pair; // Index to SumPairs Table; }* Sort; bool IsValid() const {return Table && Sort;} ~HashTable() { delete[] Table; delete[] Sort; } HashTable(const FileSums& Sums) : Sums(Sums) { DWORD Pairs=Sums.GetBlockCount(); if(!Pairs) return; // Create Hash-sorted index to SumPairs Table: Sort=new Sorter[Pairs]; if(!Sort) return; Sorter* Sit=Sort; FileSums::SumPair* Pit=Sums.SumPairs; DWORD i=0; while(iKey=GetHash(Pit++->QCS); Sit++->Pair=i++; } qsort(Sort, Pairs, sizeof(Sorter), (int (__cdecl*)(const void*, const void*))(compare)); // Create Hash Table: Table=new DWORD[0x10000]; if(!Table) { delete[] Sort; Sort=0; return; } memset(Table, -1, 0x10000*sizeof(DWORD)); for(i=Pairs; i--; Table[(--Sit)->Key]=i); } WORD GetHash(DWORD QCS) const {return (WORD)(QCS+(QCS>>16));} static int __cdecl compare(Sorter* s1, Sorter* s2) {return s1->Key - s2->Key;} //For qsort DWORD Find(const BYTE* Buffer, DWORD Bytes, DWORD QCS) const { WORD Hash=GetHash(QCS); DWORD it=Table[Hash]; // index into Sort if(it!=-1) { // Got a matching Hash, check QCS for all Sort entries with this Hash: BYTE Digest[16]; bool GotDigest=false; DWORD Pairs=Sums.GetBlockCount(); for(Sorter* Sit=&Sort[it]; (itKey==Hash); ++Sit) { if(Sums.SumPairs[Sit->Pair].QCS!=QCS) continue; // Now we have a matching QCS check the MD5 Checksum: if(!GotDigest) { GotDigest=true; CMD5 MD5; MD5Context Context; MD5.Init(&Context); MD5.Update(&Context, (BYTE*)Buffer, Bytes); MD5.Final(Digest, &Context); } if(!memcmp(Digest, Sums.SumPairs[Sit->Pair].MD5, sizeof(Digest))) return Sit->Pair; } } return -1; } }; protected: /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Compare <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Here the Server is using the Checkum Array representing the Client's File, and a local file path to construct a Comparison String which will define how to make the Local File from the Cient's File: */ CString Compare(const CString& Path, const FileSums& Sums) { CString S; WIN32_FIND_DATA FindData; HANDLE hFile=FindFirstFile(Path, &FindData); if(hFile!=INVALID_HANDLE_VALUE) { S.Format("%X%04X\n", FindData.nFileSizeHigh, FindData.nFileSizeLow); FindClose(hFile); S.TrimLeft('0'); if(S=="\n") return "0,0\n"; //Path is an empty File S="0,"+S; } if(!Sums.IsValid() || !Sums.FileLength) return S; CString Comparison; HashTable Hash(Sums); if(!Hash.IsValid()) return S; CFile File; if(!File.Open(Path, CFile::modeRead | CFile::shareDenyNone)) return S; BYTE Buffer[512]; DWORD Bytes; CRoller32 Roller; DWORD EndBlockLength=Sums.GetEndBlockLength(); DWORD PrevPosition=0; static const DWORD PrevBlockInitialiser=-3; // must be lower than Block-1 and Block is -1 when Hash.Find fails DWORD PrevBlock=PrevBlockInitialiser; bool Range=false; do { Bytes=File.Read(Buffer, sizeof(Buffer)); Roller.Set(Buffer, Bytes); DWORD Block; while(Bytes && ((Block=Hash.Find(Buffer, Bytes, Roller.Get()))==-1)) { //Try to recognise a Block char Old=Buffer[0]; //if the data is unrecognised, scroll until we recognise a Block or run out of data. memcpy(Buffer, Buffer+1, --Bytes); // Scroll the Buffer 1 Byte: Bytes+=File.Read(Buffer+Bytes, 1); Roller.Roll(Old, Bytes, Bytes==sizeof(Buffer), Buffer[Bytes-1]); } //Now we may have a Block and we may have scrolled through some Data... S.Empty(); if(PrevBlock==Block-1) {if(!Range) {Range=true ; S.Format( "%X" ,PrevBlock);}} // Starting a range of adjacent blocks else if( Range) {Range=false; S.Format("-%X\n",PrevBlock);} // Finishing a range of adjacent blocks else if(PrevBlock!=PrevBlockInitialiser) S.Format( "%X\n",PrevBlock); // A single block else if(Bytes<=EndBlockLength) Bytes=0; // No Block, unrecognised data then EOF so include buffered Bytes in Moved sum Comparison+=S; //Add any Block information to the Comparison DWORD Moved=File.GetPosition()-Bytes-PrevPosition; //The number of Bytes that we scrolled if(Moved) { S.Format("%X,%X\n", PrevPosition, Moved); Comparison+=S; //Add any Data information to the Comparison PrevBlock=PrevBlockInitialiser; //Note that there is no previously recognised Block } if(Block!=-1) { //if we just recognised a Block: PrevBlock=Block; PrevPosition=File.GetPosition(); } }while(Bytes && Bytes>=EndBlockLength); return Comparison; } struct Buffer { char* Buf; Buffer() : Buf(0) {} Buffer(DWORD Size) : Buf(0) {Set(Size);} ~Buffer() {try{delete Buf;}catch(...){}} void Set(DWORD Size) {delete Buf; Buf=new char[Size];} }; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> MakeDelta <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Here the Server is expanding the Comparison String using the File "Using" and sending the reconstruction information to Archive ar. The Data is sent with BYTE prefix codes to let the Client know whats being sent: 0 = End Of Data (Terminates this function) 251,DWORD,DWORD,DATA = Compressed Data Section (FilePos, DataLength) 252,DWORD,DWORD,DATA = Plain Data Section (FilePos, DataLength) 253,DWORD = A Single Block Number (Block Number) 254,DWORD,DWORD = A Block Range (First,Last) 255,CString = An Error string (Terminates this function) */ CString MakeDelta(const char* Comparison, const char* Using, CArchive& ar) { if(!*Comparison) { ar << (BYTE)255 << sBadComparison; ar.Flush(); Sent(0, 1+sBadComparison.GetLength()); return sBadComparison; } CFile File; if(!File.Open(Using, CFile::modeRead | CFile::shareDenyNone)) { ar << (BYTE)255 << sNoOpenRemote; ar.Flush(); Sent(0, 1+sNoOpenRemote.GetLength()); return sNoOpenRemote; } DWORD Number=0; DWORD PrevNumber=0; const char* stt=Comparison; const char* end=stt; char Separator=0; char c; while(c=*end++) { switch(c) { case '-': { Separator='-'; PrevNumber=Number; Number=0; break; } case ',': { Separator=','; PrevNumber=Number; Number=0; break; } case '\n': { switch(Separator) { case ',': { //Send Data Buffer Buf(512); CString S(File.GetFileName()); int i=S.ReverseFind('.')+1; if(i) { //See if there's any point trying ot compress the data: S=S.Mid(i); if(S!="zip" // Don't bother trying to compress data for files that are already compressed && S!="gzip" && S!="lzh" && S!="uha" && S!="arj" && S!="jpg" && S!="jpeg" && S!="gif" && S!="mpg" && S!="mpeg" && S!="mp3") { CString DataFile(TempPath+"BlockDeltaE.dat"); CString LZWFile(TempPath+"BlockDeltaE.lzw"); CFile tmp; if(!tmp.Open(DataFile, CFile::modeCreate|CFile::modeWrite)) { ar << (BYTE)255 << sNoOpenTmp; ar.Flush(); Sent(0, 1+sNoOpenTmp.GetLength()); return sNoOpenTmp; } File.Seek(PrevNumber, CFile::begin); DWORD Blocks=Number>>9; while(Blocks--) tmp.Write(Buf.Buf, File.Read(Buf.Buf, 512)); tmp.Write(Buf.Buf, File.Read(Buf.Buf, Number&0x1FF)); tmp.Close(); CByteStream ByteStream(DataFile, LZWFile); CLZWEncoder Encoder(ByteStream); DeleteFile(DataFile); if(Encoder.GetOutSize()>9; while(Blocks--) { ar.Write(Buf.Buf, tmp.Read(Buf.Buf, 512)); Sent(512, 512); } ar.Write(Buf.Buf, tmp.Read(Buf.Buf, Number&=0x1FF)); Sent(Number, Number); tmp.Close(); ar.Flush(); DeleteFile(LZWFile); break; } DeleteFile(LZWFile); } } File.Seek(PrevNumber, CFile::begin); ar << (BYTE)252 << Number; //Not Compressed Sent(0, 1+sizeof(Number)); DWORD Blocks=Number>>9; while(Blocks--) { ar.Write(Buf.Buf, File.Read(Buf.Buf, 512)); Sent(512, 512); } ar.Write(Buf.Buf, File.Read(Buf.Buf, Number&=0x1FF)); Sent(Number, Number); ar.Flush(); break; } case '-':{ // Send Blocks ar << (BYTE)254 << PrevNumber << Number; Sent(512*(1+Number-PrevNumber), 1+sizeof(PrevNumber)+sizeof(Number)); break; } default: { //Send a single block ar << (BYTE)253 << Number; Sent(512, 1+sizeof(Number)); break; } } stt=end; Separator=0; PrevNumber=Number=0; break; } default: { c-=(c<'A' ? '0' : 'A'-10); // ASCII Hex to Data conversion Number=(Number<<4) | c; break; } } } ar << (BYTE)0; ar.Flush(); Sent(0, 1); return ""; } /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> UseDelta <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Here the Client is expanding the Comparison String using the File "Using" and sending the reconstruction information to Archive ar. The Data is sent with BYTE prefix codes to let the Client know whats being sent: 0 = End Of Data (Terminates this function) 251,DWORD,DWORD,DATA = Compressed Data Section (FilePos, DataLength) 252,DWORD,DWORD,DATA = Plain Data Section (FilePos, DataLength) 253,DWORD = A Single Block Number (Block Number) 254,DWORD,DWORD = A Block Range (First,Last) 255,CString = An Error string (Terminates this function) */ CString UseDelta(const char* Using, CArchive& ar, const char* Making) { CFile oFile; if(!oFile.Open(Making,CFile::modeCreate|CFile::modeWrite)) return sNoOpenOutput; CFile iFile; bool NoInFile=!iFile.Open(Using, CFile::modeRead | CFile::shareDenyNone); DWORD dw; BYTE b; for(;;) { ar >> b; switch(b) { case 0: return ""; // EOF case 251: { // Compressed ar >> dw; Received(0, sizeof(DWORD)); Buffer Buf(512); CString DataFile(TempPath+"BlockDeltaD.dat"); CString LZWFile(TempPath+"BlockDeltaD.lzw"); CFile tmp; if(!tmp.Open(LZWFile, CFile::modeCreate|CFile::modeWrite)) return sNoOpenLZW; DWORD Blocks=dw>>9; while(Blocks--) tmp.Write(Buf.Buf, ar.Read(Buf.Buf, 512)); tmp.Write(Buf.Buf, ar.Read(Buf.Buf, dw&0x1FF)); tmp.Close(); CByteStream ByteStream(LZWFile, DataFile); CLZWDecoder Decoder(ByteStream); DeleteFile(LZWFile); Received(Decoder.GetOutSize(), dw); dw=Decoder.GetOutSize(); if(!tmp.Open(DataFile, CFile::modeRead | CFile::shareDenyNone)) return sNoOpenTmp; Blocks=dw>>9; while(Blocks--) oFile.Write(Buf.Buf, tmp.Read(Buf.Buf, 512)); oFile.Write(Buf.Buf, tmp.Read(Buf.Buf, dw&0x1FF)); tmp.Close(); DeleteFile(DataFile); break; } case 252: { // Not Compressed ar >> dw; Buffer Buf(dw); oFile.Write(Buf.Buf, ar.Read(Buf.Buf, dw)); Received(dw, dw+sizeof(DWORD)); break; } case 253: { // Single Block ar >> dw; Received(0, sizeof(DWORD)); if(NoInFile) break; char Buf[512]; iFile.Seek(dw<<9, CFile::begin); oFile.Write(Buf, iFile.Read(Buf, 512)); Received(512, 0); break; } case 254: { // Blocks DWORD Top; ar >> dw >> Top; Received(0, 2*sizeof(DWORD)); if(NoInFile) break; char Buf[512]; iFile.Seek(dw<<9, CFile::begin); while(dw++<=Top) { oFile.Write(Buf, iFile.Read(Buf, 512)); Received(512, 0); } break; } case 255: { //Remote Error CString S; ar >> S; Received(0, S.GetLength()); return S; } } } } }; #endif //ndef BlockDeltah