// RDOS.h #if _MSC_VER >= 1000 #pragma once #endif // _MSC_VER >= 1000 #ifndef RDOSh #define RDOSh #include "CSV.h" // Messages may have comma separated parameter lists #include "BlockDelta.h" #include "SocketInterface.h" /* This class provides a simple interface to provide access to a Remote Computer's files in the same way that FTP does. The difference is that this uses Delta Compression for all file transfers. Imagine that you have already downloaded a huge file from the server but that you know the file on the Server has been changed and you want the updated version. This class will compare your Local File to the Remote File and only download the differences in the files. This class is for Peer to Peer or Client/Server File transfer across slow mediums. It is intended for use in Backup/Archiving Applications. The Client/Server simply creates an instance of CRDOS which sets up a thread listening to a Windows Socket (14 by default). Any computer wanting a file from another computer simply calls CRDOS::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. Communication is by Windows Sockets, so the computers may be linked using the Internet. The following functions are implemented: Connect Tries to connect to the Remote Server. If successful, returns the the Remote Server's Current Directory, otherwise it returns an error message prefixed with a '¿' character. CD Changes the Remote Server's Current Directory. PWD Print Working Directory (the Remote Server's) DIR Returns a CString containing a listing of the Files and Folders in the Remote Server's Current Directory. Directories are first in the list and have '+' as the first character. The list is sorted alphabetically. Tree Makes the Server create a file describing the Directory tree from its Current Directory. (See CLocalPath in DTree.h) Find Returns a CLocalPath of a Remote File (if found) Fetch Copies a file from the Remote Server to a specified file on the local computer (like SaveAs) Put Sends a file to the Remote Server. Get Copies a file from the Remote Server (No name changing). Delete Deletes a file on the Remote Server. */ #ifndef Languageh static const CString sBadDIR ("No such Directory"); static const CString sLockedLocal("Couldn't overwrite Local File"); static const CString sBadPath ("No such Path: "); static const CString sBadDel ("DeleteFile Failed."); static const CString sNoFile ("File Not Found: "); static const CString sBadParams ("Incorrect Get parameters"); static const CString sBadMsg (" Unknown Message"); #endif class CRDOSClient : public CBlockDelta, public CSocketClient { CString RemotePath; //Maintained for file functions. Initialised to the Working Directory on the Server. CString TreePath; //The file on the server holding the Directory Tree data public: CRDOSClient() {} CString Connect(const CString& ServerIP) { CString S(CSocketClient::Connect(ServerIP)); if(!S.IsEmpty()) return '¿'+S; S=Send(UserName); if(!S.IsEmpty()) return '¿'+S; S=Send(HostName); if(!S.IsEmpty()) return '¿'+S; S=GetReply(); if(*S=='¿') { RemotePath.Empty(); //Check for a Socket Error return S.Mid(1); }else{ RemotePath=S; return S; } } CString CD(const CString& NewDIR) { CString S(Do('c'+NewDIR)); if(*S=='¿') return S; if(S.IsEmpty()) return '¿'+sBadDIR; RemotePath=S; return ""; } CString PWD () const {return RemotePath;} CString DIR () {return Do('d'+RemotePath);} CString Find (const CString& RemoteFile) {return Do('?'+RemoteFile );} //Get CLocalPath RFO CString Fetch(const CString& RemoteFile, const CString& Using, const CString& Making) {return Do('<'+RemoteFile+','+Using+','+Making);} CString Put (const CString& LocalFile, const CString& Using) {return Do('>'+LocalFile +','+Using);} CString Get (const CString& RemoteFile, const CString& Using) { CMD5 MD5(RemoteFile+Using+GetRemoteIP()); CString Making(TempPath+"RDOS"+MD5.GetMD5s()+".tmp"); CString OK(Do('<'+RemoteFile+','+Using+','+Making)); if(OK.IsEmpty() && !CopyFile(Making,Using,FALSE)) OK=sLockedLocal; DeleteFile(Making); return OK; } CString Delete(const CString& RemoteFile) {return Do('x'+RemoteFile);} /* After calling Tree(...), you can do your own local things before calling GetTreePath(). Unless Tree(...) returns an error message you MUST call GetTreePath() to get the next Reply from the Server. */ CString Tree(const CString& RemoteDirectory="") {return Do('t'+(RemoteDirectory.IsEmpty() ? RemotePath : RemoteDirectory));} CString GetTreePath() { if(!TreePath.IsEmpty()) return TreePath; CString S(GetReply()); if(*S=='¿') return S; TreePath=S; return (TreePath=="!" ? "" : TreePath); } CString Do(const CString& Msg) { switch(*Msg) { case 't': // Tree List Directory Tree TreePath.Empty(); case 'c': // CD Change Directory case 'd': {// DIR List Directory CString S(Msg.Mid(1)); CString Path(RemotePath); if((S.Find(':')!=-1) || (*S=='\\')) Path.Empty(); // Second path is absolute Path=CLocalPath::FormatPath(Path+S); Send(Msg); if(*Msg=='c') Send(Path); // CD Change Directory return GetReply(); } case 'w': { // PWD Print Working Directory case 'x': // Del Delete File Send(Msg); return GetReply(); } case '?': {// File Status Check Send(Msg); CString S(GetReply()); if(*S=='¿') return S; *ArI >> RFO; return ""; } case '>': { // Put Sending a file Send(Msg); CString OK(GetReply()); if(*OK=='¿') return OK; OK=CBlockDelta::Put(*ArI,*ArO); CString S(GetReply()); return OK.IsEmpty() ? S : OK; } case '<': {// Get Requesting a file: CString Remote,Local,Output; CCSV CSV(0, Msg.Mid(1)); if(!CSV.GetNextField(&Remote) || !CSV.GetNextField(&Local) || !CSV.GetNextField(&Output)) return sBadParams; *ArO << CString("<"); ArO->Flush(); // Can't use Send cuz Get uses <<. Make the Server call CBlockDelta::Put return CBlockDelta::Get(Remote,Local,Output, *ArI,*ArO); } } return "Rx"+sBadMsg; } }; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> CRDOSConnection <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< class CRDOSServer : public CSocketServer { virtual CConnection* New() {return new CRDOSConnection(this);} }; */ class CRDOSConnection : public CBlockDelta, public CConnection { protected: CString DIR; //The current directory for this Connection public: CRDOSConnection(CSocketServer* Me) : CConnection(Me) { DWORD Len=GetCurrentDirectory(0,0); GetCurrentDirectory(Len, DIR.GetBufferSetLength(Len)); DIR.ReleaseBuffer(); DIR=CLocalPath::FormatPath(DIR); } virtual void OnConnect() { // Called after connection SetUserName(GetReply()); //Since the local User and Host aren't used per Connection, use them to store the Remote User and Host. SetHostName(GetReply()); //(The Server class holds the local User and Host). Send(DIR); } void OnReceive() {Parse(GetReply());} virtual void Parse(const CString& S) { switch(*S) { case 'd': {// DIR List Directory CDTree DIR(S.Mid(1)); CString Output; while(DIR.GetNext(S)) Output+=(DIR.IsDirectory() ? '+'+S : S)+"\r\n"; //Only basic at the moment. Send(Output); break; } case 't': {// Tree List Directory Tree if(GetFileAttributes(S=S.Mid(1))==-1) { Send(sBadPath+S); break; } Send(""); //Tell client we're getting the file CString tmp; tmp.Format("%sRDOSTree%X.tmp", TempPath, AfxGetThread()); CDTreeSaver DTreeSaver(S, tmp); Send((DTreeSaver.IsValid() ? tmp : '!')); //Finished scanning, send back the data break; } case 'c': { // CD Change Directory CCSV CSV(0, S.Mid(1), '\\'); if(S.Mid(1,2)=="\\\\") { // \\ComputerName\Share\Directory format CSV.GetNextField(); //Skip the first two "empty fields" CSV.GetNextField(); CSV.GetNextField(); //Win9x needs the ComputerName and Share to be used together, so we have to skip the ComputerName too. } while(CSV.GetNextField() && ((GetFileAttributes(CSV.GetDone())!=-1) || ::CreateDirectory(CSV.GetDone(),0))); DIR=CLocalPath::FormatPath (CSV.GetDone()); } //Continue as for case 'w': case 'w': {// PWD Print Working Directory Send(DIR); break; } case 'x': {// Del Delete File Send(DeleteFile(S.Mid(1)) ? "" : sBadDel); break; } case '?': {// File Status Check WIN32_FIND_DATA FindFileData; HANDLE hFind; CString Path(S.Mid(1)); if((hFind=FindFirstFile(Path, &FindFileData))==INVALID_HANDLE_VALUE) { Send('¿'+sNoFile+Path); //Indicates no CLocalPath. }else{ CFileDetails File(FindFileData); *ArO << CString("") << File; ArO->Flush(); FindClose(hFind); } break; } case '<': {// Get Client wants a File: CBlockDelta::Put(*ArI,*ArO); break; } case '>': {// Put Client wants us to Get a file: CString Remote,Using; CCSV CSV(0, S.Mid(1)); if(!CSV.GetNextField(&Remote) || !CSV.GetNextField(&Using )) Send(sBadParams); else { CMD5 MD5(Remote+Using); CString Making(TempPath+"RDOS"+MD5.GetMD5s()+".tmp"); if(GetFileAttributes(Remote)==-1) { Send('¿'+sNoFile+Remote); break; } *ArO << CString("<"); ArO->Flush(); // Can't use Send cuz Get uses <<. Tell Client we're sending the file. CString OK(CBlockDelta::Get(Remote,Using,Making, *ArI, *ArO)); if(OK.IsEmpty() && !CopyFile(Making,Using,FALSE)) OK=sLockedLocal; DeleteFile(Making); Send(OK); } break; } } } }; #endif //ndef RDOSh