#define _CRT_SECURE_NO_DEPRECATE #include <windows.h> #include <sstream> #include <iostream> #pragma warning(push) #pragma warning(disable : 4267 4244) #include <clientapi.h> #pragma warning( pop ) #include "filewriter.h" #include "fs_filesys.h" #include "fs.h" static std::string sErrorString; int FsFileSys::mNewlinesDecoded = 0; FsFileSys::FsFileSys(FileSysType inType, long long inFileSize) : mFileWriter(NULL), mFileSize(inFileSize), mDecodeFailed(false), mBomWritten(false), mLineCount(1), mUtf8ByteCount(0), mCurCodePoint(0) { type = inType; if ((type & FST_MASK) == FST_GUNZIP) { mZStream.zalloc = Z_NULL; mZStream.zfree = Z_NULL; mZStream.opaque = Z_NULL; mZStream.avail_in = 0; mZStream.next_in = Z_NULL; mZStreamState = inflateInit2(&mZStream, 47); } } FsFileSys::~FsFileSys() { } void FsFileSys::Open(FileOpenMode inMode, Error* ioError) { Log("Open(%d), type = %s, path = %s\n", inMode, cFileType[type & FST_MASK], Name()); if (inMode == FOM_READ) { ioError->Set(E_FATAL, "Attempt to open a FileSys object in unsupported read mode, aborting."); return; } else if (inMode == FOM_WRITE) { mFileWriter = FileWriter::GetFileWriter(Name(), mFileSize); if (mFileWriter && !mFileWriter->IsAborting() && !mFileWriter->Open(perms == FPM_RW, modTime)) ioError->Sys(inMode == FOM_READ ? "open for read" : "open for write", Name()); } } int FsFileSys::DecodeText(const char* inReadBuffer, int& ioReadLength, unsigned char* inWriteBuffer, int inWriteLength, Error* ioError) { int have = 0; while (ioReadLength > 0 && (inWriteLength - have) > 1) { char c = *inReadBuffer++; if (c == '\n') { *inWriteBuffer++ = '\r'; *inWriteBuffer++ = '\n'; have += 2; ++mLineCount; ++mNewlinesDecoded; } else { *inWriteBuffer++ = c; ++have; } --ioReadLength; } return have; } int FsFileSys::DecodeGzip(const char* inReadBuffer, int& ioReadLength, unsigned char* inWriteBuffer, int inWriteLength, Error* ioError) { int have = 0; mZStream.avail_out = inWriteLength; mZStream.next_out = inWriteBuffer; mZStreamState = inflate(&mZStream, Z_SYNC_FLUSH); if ((mZStreamState != Z_OK) && (mZStreamState != Z_STREAM_END)) { ioError->Set(E_WARN, "Inflate failed!"); return -1; } have = inWriteLength - mZStream.avail_out; ioReadLength = mZStream.avail_in; return have; } int FsFileSys::DecodeUtf16(const char* inReadBuffer, int& ioReadLength, unsigned char* inWriteBuffer, int inWriteLength, Error* ioError) { int have = 0; if (!mBomWritten) { // add a little-endian BOM *inWriteBuffer++ = 0xff; *inWriteBuffer++ = 0xfe; have += 2; mBomWritten = true; } while (ioReadLength > 0 && (inWriteLength - have) > 3) { char c = *inReadBuffer++; if (c == '\n') { *inWriteBuffer++ = '\r'; *inWriteBuffer++ = 0; *inWriteBuffer++ = '\n'; *inWriteBuffer++ = 0; have += 4; ++mLineCount; } else { if (mUtf8ByteCount == 0) { unsigned char mask = 0x80; while (c & mask) { mask >>= 1; ++mUtf8ByteCount; } if (mUtf8ByteCount > 4 || mUtf8ByteCount == 1) { std::ostringstream error_stream; error_stream << "Translation of file content failed near line " << mLineCount; sErrorString = error_stream.str(); ioError->Set(E_WARN, sErrorString.c_str()); return -1; } else if (mUtf8ByteCount > 0) --mUtf8ByteCount; // mUtf8ByteCount is now in the range [0-3] --mask; mCurCodePoint = c & mask; } else { if ((c & 0xC0) != 0x80) { std::ostringstream error_stream; error_stream << "Translation of file content failed near line " << mLineCount; sErrorString = error_stream.str(); ioError->Set(E_WARN, sErrorString.c_str()); } mCurCodePoint = (mCurCodePoint << 6) + (c & 0x3F); --mUtf8ByteCount; } if (mUtf8ByteCount == 0) { // ok, code point is complete, now re-encode in utf-16 if (mCurCodePoint < 0x10000) { *inWriteBuffer++ = mCurCodePoint & 0xFF;; *inWriteBuffer++ = mCurCodePoint >> 8; have += 2; } else { mCurCodePoint -= 0x10000; int surrogate2 = (mCurCodePoint & 0x3FF) + 0xDC00; int surrogate1 = (mCurCodePoint >> 10) + 0xD800; *inWriteBuffer++ = surrogate1 & 0xFF;; *inWriteBuffer++ = surrogate1 >> 8; *inWriteBuffer++ = surrogate2 & 0xFF;; *inWriteBuffer++ = surrogate2 >> 8; have += 4; } } } --ioReadLength; } return have; } int FsFileSys::DecodeRaw(const char* inReadBuffer, int& ioReadLength, unsigned char* inWriteBuffer, int inWriteLength, Error* ioError) { // Not sure what to do, just do a straight copy for now int have = (ioReadLength < inWriteLength) ? ioReadLength : inWriteLength; ::memcpy(inWriteBuffer, inReadBuffer, have); inReadBuffer += have; ioReadLength -= have; return have; } void FsFileSys::Write(const char* inBuffer, int inLen, Error* ioError) { Log("Write(%4d), type = %s, path = %s\n", inLen, cFileType[type & FST_MASK], Name()); if (!mFileWriter || mFileWriter->IsAborting() || inLen <= 0) return; if (FileWriter::GetReadCallback()) FileWriter::GetReadCallback()(inLen); if ((type & FST_MASK) == FST_GUNZIP) { // Initialize the zstream before we start the decode loop if (mZStreamState != Z_OK) return; // z_stream is broken, no point in continuing mZStream.avail_in = inLen; mZStream.next_in = (Bytef*) inBuffer; } while (inLen > 0) { int have = 0; unsigned char* out_buf; int out_len_remaining; mFileWriter->GetWriteBuffer(out_buf, out_len_remaining); switch (type & FST_MASK) { case FST_TEXT : have = DecodeText(inBuffer, inLen, out_buf, out_len_remaining, ioError); break; case FST_GUNZIP : have = DecodeGzip(inBuffer, inLen, out_buf, out_len_remaining, ioError); break; case FST_UTF16 : have = DecodeUtf16(inBuffer, inLen, out_buf, out_len_remaining, ioError); break; default : have = DecodeRaw(inBuffer, inLen, out_buf, out_len_remaining, ioError); break; } if (have < 0) { mDecodeFailed = true; return; } if (!mFileWriter->ReleaseWriteBuffer(out_len_remaining - have)) ioError->Sys("write", Name()); } } int FsFileSys::Read(char* outBuf, int inLen, Error* ioError) { Log("Read(%4d) type = %s, path = %s\n", inLen, cFileType[type & FST_MASK], Name()); ioError->Set(E_FATAL, "Attempt to use the unimplemented FileSys::Read() function, aborting."); return 0; } void FsFileSys::Close(Error* ioError) { Log("Close(), type = %s, path = %s\n", cFileType[type & FST_MASK], Name()); if (!mFileWriter || mFileWriter->IsAborting()) return; if ((type & FST_MASK) == FST_GUNZIP) { if (inflateEnd(&mZStream) != Z_OK) ioError->Set(E_WARN, mZStream.msg); } if (!mFileWriter->Close()) ioError->Sys("close", Name()); if (mDecodeFailed) Unlink(ioError); } int FsFileSys::Stat() { Log("Stat(), type = %s, path = %s\n", cFileType[type & FST_MASK], Name()); int stat = 0; WIN32_FILE_ATTRIBUTE_DATA file_info; if (::GetFileAttributesEx(Name(), GetFileExInfoStandard, (void*) &file_info)) { stat |= FSF_EXISTS; if ((file_info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0) stat |= FSF_WRITEABLE; if (file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) stat |= FSF_DIRECTORY; if (file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) stat |= FSF_SYMLINK; if (file_info.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) stat |= FSF_HIDDEN; if ((file_info.nFileSizeHigh == 0) && (file_info.nFileSizeLow == 0)) stat |= FSF_EMPTY; } return stat; } int FsFileSys::StatModTime() { // Note that this function probably functions incorrectly in the presence // of Daylight Saving Time, see the comments in FileWriter::FileTimeFromEpoch() // for more info. I don't think this function is ever called by the p4api during // a sync operation, so it probably shouldn't matter much. Log("StatModTime(), type = %s, path = %s\n", cFileType[type & FST_MASK], Name()); WIN32_FILE_ATTRIBUTE_DATA file_info; if (::GetFileAttributesEx(Name(), GetFileExInfoStandard, (void*) &file_info)) { LONGLONG ft = file_info.ftLastWriteTime.dwHighDateTime; ft <<= 32; ft += file_info.ftLastWriteTime.dwLowDateTime; ft /= 10000000; ft -= 11644473600; return (int) ft; } return 0; } void FsFileSys::Truncate(Error* ioError) { Log("Truncate(), type = %s, path = %s\n", cFileType[type & FST_MASK], Name()); ioError->Set(E_FATAL, "Attempt to use the unimplemented FileSys::Truncate() function, aborting."); } void FsFileSys::Unlink(Error* ioError) { Log("Unlink(), type = %s, path = %s\n", cFileType[type & FST_MASK], Name()); if (mFileWriter) { if (mFileWriter->IsAborting()) return; mFileWriter->Finish(); } bool is_readonly = !(Stat() & FSF_WRITEABLE); if (is_readonly) Chmod(FPM_RW, ioError); if (::DeleteFile(Name()) == 0) { if (is_readonly) Chmod(FPM_RO, ioError); ioError->Sys("unlink", Name()); } } void FsFileSys::Rename(FileSys* inTarget, Error* ioError) { Log("Rename(%s), type = %s, path = %s\n", inTarget->Name(), cFileType[type & FST_MASK], Name()); if (mFileWriter) { if (mFileWriter->IsAborting()) return; mFileWriter->Finish(); } int res = -1; int attempt = 0; bool target_is_readonly = false; while (attempt < 10 && res != 0) { int target_stat = inTarget->Stat(); if (target_stat & FSF_EXISTS) { target_is_readonly = !(target_stat & FSF_WRITEABLE); if (target_is_readonly) inTarget->Chmod(FPM_RW, ioError); ::DeleteFile(inTarget->Name()); } res = ::rename(Name(), inTarget->Name()); if (res != 0) Sleep(1000); ++attempt; } if (res != 0) { if (target_is_readonly) inTarget->Chmod(FPM_RO, ioError); Unlink(ioError); if (DoScriptedOutput()) printf("error: "); printf("File rename() failed after 10 attempts.\n"); ioError->Sys("rename", inTarget->Name()); } else path = *(inTarget->Path()); } void FsFileSys::Chmod(FilePerm inPerms, Error* ioError) { Log("Chmod(%d), type = %s, path = %s\n", inPerms, cFileType[type & FST_MASK], Name()); if (mFileWriter) { if (mFileWriter->IsAborting()) return; mFileWriter->Finish(); } DWORD attr = ::GetFileAttributes(Name()); if (inPerms == FPM_RO) attr |= FILE_ATTRIBUTE_READONLY; else if (inPerms == FPM_RW) attr &= ~FILE_ATTRIBUTE_READONLY; if (::SetFileAttributes(Name(), attr) == 0) ioError->Sys("chmod", Name()); else perms = inPerms; } void FsFileSys::ChmodTime(Error* ioError) { Log("ChmodTime(), type = %s, path = %s\n", cFileType[type & FST_MASK], Name()); if (mFileWriter && mFileWriter->ChModTime(modTime)) return; ioError->Set(E_FATAL, "Could not set mod time, aborting."); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#4 | 6451 | Frank Compagner |
- Now fully support unicode & utf-16 files - Improved accuracy of P4fsV progress bar - Added logging to help in remote debugging |
||
#3 | 6420 | Frank Compagner |
A number of improvements: - p4fs now supports the global -s (scripted output) option. - p4fs and P4fsV now support the modtime client option. - P4CHARSET is now correctly handled (though no full Unicode support yet). - Increased the maximum command line length for p4fs to the Windows maximum 32768. - Improved error handling. - Fixed a crash when cancelling a sync using the async or multithreaded writers. - P4fsV progressbar now behaves well when passing more than one filespec - P4fsV will now offer the option to overwrite any locally changed (but not checked out) files when it finds these during a sync (cannot clobber ...). - Made the P4fsV error dialog resizeable. - P4fsV Windows layout fixed so it works properly with all Windows style setings. - Ooh, and prettier icons too. |
||
#2 | 6280 | Frank Compagner | Added support for +w filetype | ||
#1 | 6187 | Frank Compagner | Added p4fs project |