diff --git a/src/diff.cpp b/src/diff.cpp index 19fdc1c..4e37013 100644 --- a/src/diff.cpp +++ b/src/diff.cpp @@ -1,2470 +1,2459 @@ /*************************************************************************** diff.cpp - description ------------------- begin : Mon Mar 18 2002 copyright : (C) 2002-2007 by Joachim Eibl email : joachim.eibl at gmx.de ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include #ifdef Q_OS_WIN #include #include #endif #include #include "diff.h" #include "fileaccess.h" #include "gnudiff_diff.h" #include "options.h" #include "progress.h" #include #include #include #include #include +#include #include #include #include #include int LineData::width(int tabSize) const { int w = 0; int j = 0; for(int i = 0; i < size; ++i) { if(pLine[i] == '\t') { for(j %= tabSize; j < tabSize; ++j) ++w; j = 0; } else { ++w; ++j; } } return w; } // The bStrict flag is true during the test where a nonmatching area ends. // Then the equal()-function requires that the match has more than 2 nonwhite characters. // This is to avoid matches on trivial lines (e.g. with white space only). // This choice is good for C/C++. bool equal(const LineData& l1, const LineData& l2, bool bStrict) { if(l1.pLine == nullptr || l2.pLine == nullptr) return false; if(bStrict && g_bIgnoreTrivialMatches) return false; // Ignore white space diff const QChar* p1 = l1.pLine; const QChar* p1End = p1 + l1.size; const QChar* p2 = l2.pLine; const QChar* p2End = p2 + l2.size; if(g_bIgnoreWhiteSpace) { int nonWhite = 0; for(;;) { while(isWhite(*p1) && p1 != p1End) ++p1; while(isWhite(*p2) && p2 != p2End) ++p2; if(p1 == p1End && p2 == p2End) { if(bStrict && g_bIgnoreTrivialMatches) { // Then equality is not enough return nonWhite > 2; } else // equality is enough return true; } else if(p1 == p1End || p2 == p2End) return false; if(*p1 != *p2) return false; ++p1; ++p2; ++nonWhite; } } else { if(l1.size == l2.size && memcmp(p1, p2, l1.size) == 0) return true; else return false; } } static bool isLineOrBufEnd(const QChar* p, int i, int size) { return i >= size // End of file || isEndOfLine(p[i]) // Normal end of line // No support for Mac-end of line yet, because incompatible with GNU-diff-routines. // || ( p[i]=='\r' && (i>=size-1 || p[i+1]!='\n') // && (i==0 || p[i-1]!='\n') ) // Special case: '\r' without '\n' ; } /* Features of class SourceData: - Read a file (from the given URL) or accept data via a string. - Allocate and free buffers as necessary. - Run a preprocessor, when specified. - Run the line-matching preprocessor, when specified. - Run other preprocessing steps: Uppercase, ignore comments, remove carriage return, ignore numbers. Order of operation: 1. If data was given via a string then save it to a temp file. (see setData()) 2. If the specified file is nonlocal (URL) copy it to a temp file. 3. If a preprocessor was specified, run the input file through it. 4. Read the output of the preprocessor. 5. If Uppercase was specified: Turn the read data to uppercase. 6. Write the result to a temp file. 7. If a line-matching preprocessor was specified, run the temp file through it. 8. Read the output of the line-matching preprocessor. 9. If ignore numbers was specified, strip the LMPP-output of all numbers. 10. If ignore comments was specified, strip the LMPP-output of comments. Optimizations: Skip unneeded steps. */ SourceData::SourceData() { m_pOptions = nullptr; reset(); } SourceData::~SourceData() { reset(); } void SourceData::reset() { m_pEncoding = nullptr; m_fileAccess = FileAccess(); m_normalData.reset(); m_lmppData.reset(); if(!m_tempInputFileName.isEmpty()) { FileAccess::removeFile(m_tempInputFileName); m_tempInputFileName = ""; } } void SourceData::setFilename(const QString& filename) { if(filename.isEmpty()) { reset(); } else { FileAccess fa(filename); setFileAccess(fa); } } bool SourceData::isEmpty() { return getFilename().isEmpty(); } bool SourceData::hasData() { return m_normalData.m_pBuf != nullptr; } bool SourceData::isValid() { return isEmpty() || hasData(); } void SourceData::setOptions(Options* pOptions) { m_pOptions = pOptions; } QString SourceData::getFilename() { return m_fileAccess.absoluteFilePath(); } QString SourceData::getAliasName() { return m_aliasName.isEmpty() ? m_fileAccess.prettyAbsPath() : m_aliasName; } void SourceData::setAliasName(const QString& name) { m_aliasName = name; } void SourceData::setFileAccess(const FileAccess& fileAccess) { m_fileAccess = fileAccess; m_aliasName = QString(); if(!m_tempInputFileName.isEmpty()) { FileAccess::removeFile(m_tempInputFileName); m_tempInputFileName = ""; } } void SourceData::setEncoding(QTextCodec* pEncoding) { m_pEncoding = pEncoding; } QStringList SourceData::setData(const QString& data) { QStringList errors; // Create a temp file for preprocessing: if(m_tempInputFileName.isEmpty()) { - m_tempInputFileName = FileAccess::tempFileName(); + FileAccess::createTempFile(m_tempFile); + m_tempInputFileName = m_tempFile.fileName(); } FileAccess f(m_tempInputFileName); QByteArray ba = QTextCodec::codecForName("UTF-8")->fromUnicode(data); bool bSuccess = f.writeFile(ba.constData(), ba.length()); if(!bSuccess) { errors.append(i18n("Writing clipboard data to temp file failed.")); } else { m_aliasName = i18n("From Clipboard"); m_fileAccess = FileAccess(""); // Effect: m_fileAccess.isValid() is false } return errors; } const LineData* SourceData::getLineDataForDiff() const { if(m_lmppData.m_pBuf == nullptr) return m_normalData.m_v.size() > 0 ? &m_normalData.m_v[0] : nullptr; else return m_lmppData.m_v.size() > 0 ? &m_lmppData.m_v[0] : nullptr; } const LineData* SourceData::getLineDataForDisplay() const { return m_normalData.m_v.size() > 0 ? &m_normalData.m_v[0] : nullptr; } LineRef SourceData::getSizeLines() const { return (LineRef)m_normalData.m_vSize; } qint64 SourceData::getSizeBytes() const { return m_normalData.m_size; } const char* SourceData::getBuf() const { return m_normalData.m_pBuf; } const QString& SourceData::getText() const { return m_normalData.m_unicodeBuf; } bool SourceData::isText() { return m_normalData.m_bIsText; } bool SourceData::isIncompleteConversion() { return m_normalData.m_bIncompleteConversion; } bool SourceData::isFromBuffer() { return !m_fileAccess.isValid(); } bool SourceData::isBinaryEqualWith(const SourceData& other) const { return m_fileAccess.exists() && other.m_fileAccess.exists() && getSizeBytes() == other.getSizeBytes() && (getSizeBytes() == 0 || memcmp(getBuf(), other.getBuf(), getSizeBytes()) == 0); } void SourceData::FileData::reset() { delete[](char*) m_pBuf; m_pBuf = nullptr; m_v.clear(); m_size = 0; m_vSize = 0; m_bIsText = true; m_bIncompleteConversion = false; m_eLineEndStyle = eLineEndStyleUndefined; } bool SourceData::FileData::readFile(const QString& filename) { reset(); if(filename.isEmpty()) { return true; } FileAccess fa(filename); if(!fa.isNormal()) return true; m_size = fa.sizeForReading(); char* pBuf; m_pBuf = pBuf = new char[m_size + 100]; // Alloc 100 byte extra: Safety hack, not nice but does no harm. // Some extra bytes at the end of the buffer are needed by // the diff algorithm. See also GnuDiff::diff_2_files(). bool bSuccess = fa.readFile(pBuf, m_size); if(!bSuccess) { delete[] pBuf; m_pBuf = nullptr; m_size = 0; } return bSuccess; } bool SourceData::saveNormalDataAs(const QString& fileName) { return m_normalData.writeFile(fileName); } bool SourceData::FileData::writeFile(const QString& filename) { if(filename.isEmpty()) { return true; } FileAccess fa(filename); bool bSuccess = fa.writeFile(m_pBuf, m_size); return bSuccess; } void SourceData::FileData::copyBufFrom(const FileData& src) { reset(); char* pBuf; m_size = src.m_size; m_pBuf = pBuf = new char[m_size + 100]; memcpy(pBuf, src.m_pBuf, m_size); } // Convert the input file from input encoding to output encoding and write it to the output file. static bool convertFileEncoding(const QString& fileNameIn, QTextCodec* pCodecIn, const QString& fileNameOut, QTextCodec* pCodecOut) { QFile in(fileNameIn); if(!in.open(QIODevice::ReadOnly)) return false; QTextStream inStream(&in); inStream.setCodec(pCodecIn); inStream.setAutoDetectUnicode(false); QFile out(fileNameOut); if(!out.open(QIODevice::WriteOnly)) return false; QTextStream outStream(&out); outStream.setCodec(pCodecOut); QString data = inStream.readAll(); outStream << data; return true; } static QTextCodec* getEncodingFromTag(const QByteArray& s, const QByteArray& encodingTag) { int encodingPos = s.indexOf(encodingTag); if(encodingPos >= 0) { int apostrophPos = s.indexOf('"', encodingPos + encodingTag.length()); int apostroph2Pos = s.indexOf('\'', encodingPos + encodingTag.length()); char apostroph = '"'; if(apostroph2Pos >= 0 && (apostrophPos < 0 || apostroph2Pos < apostrophPos)) { apostroph = '\''; apostrophPos = apostroph2Pos; } int encodingEnd = s.indexOf(apostroph, apostrophPos + 1); if(encodingEnd >= 0) // e.g.: or { QByteArray encoding = s.mid(apostrophPos + 1, encodingEnd - (apostrophPos + 1)); return QTextCodec::codecForName(encoding); } else // e.g.: { QByteArray encoding = s.mid(encodingPos + encodingTag.length(), apostrophPos - (encodingPos + encodingTag.length())); return QTextCodec::codecForName(encoding); } } return nullptr; } static QTextCodec* detectEncoding(const char* buf, qint64 size, qint64& skipBytes) { if(size >= 2) { if(buf[0] == '\xFF' && buf[1] == '\xFE') { skipBytes = 2; return QTextCodec::codecForName("UTF-16LE"); } if(buf[0] == '\xFE' && buf[1] == '\xFF') { skipBytes = 2; return QTextCodec::codecForName("UTF-16BE"); } } if(size >= 3) { if(buf[0] == '\xEF' && buf[1] == '\xBB' && buf[2] == '\xBF') { skipBytes = 3; return QTextCodec::codecForName("UTF-8-BOM"); } } skipBytes = 0; QByteArray s; /* We don't need the whole file here just the header. ] */ if(size <= 5000) s=QByteArray(buf, (int)size); else s=QByteArray(buf, 5000); int xmlHeaderPos = s.indexOf("= 0) { int xmlHeaderEnd = s.indexOf("?>", xmlHeaderPos); if(xmlHeaderEnd >= 0) { QTextCodec* pCodec = getEncodingFromTag(s.mid(xmlHeaderPos, xmlHeaderEnd - xmlHeaderPos), "encoding="); if(pCodec) return pCodec; } } else // HTML { int metaHeaderPos = s.indexOf("= 0) { int metaHeaderEnd = s.indexOf(">", metaHeaderPos); if(metaHeaderEnd >= 0) { QTextCodec* pCodec = getEncodingFromTag(s.mid(metaHeaderPos, metaHeaderEnd - metaHeaderPos), "charset="); if(pCodec) return pCodec; metaHeaderPos = s.indexOf(""1" "2"< => >1<, >2< * Eg. >'\'\\'< => >'\< backslash is a meta character between single quotes * Eg. > "\\" < => >\\< but not between double quotes * Eg. >"c:\sed" 's/a/\' /g'< => >c:\sed<, >s/a/' /g< */ static QString getArguments(QString cmd, QString& program, QStringList& args) { program = QString(); args.clear(); for(int i = 0; i < cmd.length(); ++i) { while(i < cmd.length() && cmd[i].isSpace()) { ++i; } if(cmd[i] == '"' || cmd[i] == '\'') // argument beginning with a quote { QChar quoteChar = cmd[i]; ++i; int argStart = i; bool bSkip = false; while(i < cmd.length() && (cmd[i] != quoteChar || bSkip)) { if(bSkip) { bSkip = false; if(cmd[i] == '\\' || cmd[i] == quoteChar) { cmd.remove(i - 1, 1); // remove the backslash '\' continue; } } else if(cmd[i] == '\\' && quoteChar == '\'') bSkip = true; ++i; } if(i < cmd.length()) { args << cmd.mid(argStart, i - argStart); if(i + 1 < cmd.length() && !cmd[i + 1].isSpace()) return i18n("Expecting space after closing apostroph."); } else return i18n("Not matching apostrophs."); continue; } else { int argStart = i; //bool bSkip = false; while(i < cmd.length() && (!cmd[i].isSpace() /*|| bSkip*/)) { /*if ( bSkip ) { bSkip = false; if ( cmd[i]=='\\' || cmd[i]=='"' || cmd[i]=='\'' || cmd[i].isSpace() ) { cmd.remove( i-1, 1 ); // remove the backslash '\' continue; } } else if ( cmd[i]=='\\' ) bSkip = true; else */ if(cmd[i] == '"' || cmd[i] == '\'') return i18n("Unexpected apostroph within argument."); ++i; } args << cmd.mid(argStart, i - argStart); } } if(args.isEmpty()) return i18n("No program specified."); else { program = args[0]; args.pop_front(); #ifdef Q_OS_WIN if(program == QLatin1String("sed")) { QString prg = QCoreApplication::applicationDirPath() + QLatin1String("/bin/sed.exe"); // in subdir bin if(QFile::exists(prg)) { program = prg; } else { prg = QCoreApplication::applicationDirPath() + QLatin1String("/sed.exe"); // in same dir if(QFile::exists(prg)) { program = prg; } } } #endif } return QString(); } QStringList SourceData::readAndPreprocess(QTextCodec* pEncoding, bool bAutoDetectUnicode) { m_pEncoding = pEncoding; + QTemporaryFile fileIn1, fileOut1; QString fileNameIn1; QString fileNameOut1; QString fileNameIn2; QString fileNameOut2; QStringList errors; bool bTempFileFromClipboard = !m_fileAccess.isValid(); // Detect the input for the preprocessing operations if(!bTempFileFromClipboard) { if(m_fileAccess.isLocal()) { fileNameIn1 = m_fileAccess.absoluteFilePath(); } else // File is not local: create a temporary local copy: { - if(m_tempInputFileName.isEmpty()) { - m_tempInputFileName = FileAccess::tempFileName(); + if(m_tempInputFileName.isEmpty()) + { + m_fileAccess.createLocalCopy(); + m_tempInputFileName = m_fileAccess.getTempName(); } - m_fileAccess.copyFile(m_tempInputFileName); fileNameIn1 = m_tempInputFileName; } if(bAutoDetectUnicode) { m_pEncoding = detectEncoding(fileNameIn1, pEncoding); } } else // The input was set via setData(), probably from clipboard. { fileNameIn1 = m_tempInputFileName; m_pEncoding = QTextCodec::codecForName("UTF-8"); } QTextCodec* pEncoding1 = m_pEncoding; QTextCodec* pEncoding2 = m_pEncoding; m_normalData.reset(); m_lmppData.reset(); FileAccess faIn(fileNameIn1); qint64 fileInSize = faIn.size(); if(faIn.exists()) { // Run the first preprocessor if(m_pOptions->m_PreProcessorCmd.isEmpty()) { // No preprocessing: Read the file directly: m_normalData.readFile(fileNameIn1); } else { + QTemporaryFile tmpInPPFile; QString fileNameInPP = fileNameIn1; if(pEncoding1 != m_pOptions->m_pEncodingPP) { // Before running the preprocessor convert to the format that the preprocessor expects. - fileNameInPP = FileAccess::tempFileName(); + FileAccess::createTempFile(tmpInPPFile); + fileNameInPP = tmpInPPFile.fileName(); pEncoding1 = m_pOptions->m_pEncodingPP; convertFileEncoding(fileNameIn1, pEncoding, fileNameInPP, pEncoding1); } QString ppCmd = m_pOptions->m_PreProcessorCmd; - fileNameOut1 = FileAccess::tempFileName(); + FileAccess::createTempFile(fileOut1); + fileNameOut1 = fileOut1.fileName(); QProcess ppProcess; ppProcess.setStandardInputFile(fileNameInPP); ppProcess.setStandardOutputFile(fileNameOut1); QString program; QStringList args; QString errorReason = getArguments(ppCmd, program, args); if(errorReason.isEmpty()) { ppProcess.start(program, args); ppProcess.waitForFinished(-1); } else errorReason = "\n(" + errorReason + ")"; bool bSuccess = errorReason.isEmpty() && m_normalData.readFile(fileNameOut1); if(fileInSize > 0 && (!bSuccess || m_normalData.m_size == 0)) { errors.append( i18n("Preprocessing possibly failed. Check this command:\n\n %1" "\n\nThe preprocessing command will be disabled now.", ppCmd) + errorReason); m_pOptions->m_PreProcessorCmd = ""; m_normalData.readFile(fileNameIn1); pEncoding1 = m_pEncoding; } - if(fileNameInPP != fileNameIn1) - { - FileAccess::removeTempFile(fileNameInPP); - } } // LineMatching Preprocessor if(!m_pOptions->m_LineMatchingPreProcessorCmd.isEmpty()) { + QTemporaryFile tempOut2, fileInPP; fileNameIn2 = fileNameOut1.isEmpty() ? fileNameIn1 : fileNameOut1; QString fileNameInPP = fileNameIn2; pEncoding2 = pEncoding1; if(pEncoding2 != m_pOptions->m_pEncodingPP) { // Before running the preprocessor convert to the format that the preprocessor expects. - fileNameInPP = FileAccess::tempFileName(); + FileAccess::createTempFile(fileInPP); + fileNameInPP = fileInPP.fileName(); pEncoding2 = m_pOptions->m_pEncodingPP; convertFileEncoding(fileNameIn2, pEncoding1, fileNameInPP, pEncoding2); } QString ppCmd = m_pOptions->m_LineMatchingPreProcessorCmd; - fileNameOut2 = FileAccess::tempFileName(); + FileAccess::createTempFile(tempOut2); + fileNameOut2 = tempOut2.fileName(); QProcess ppProcess; ppProcess.setStandardInputFile(fileNameInPP); ppProcess.setStandardOutputFile(fileNameOut2); QString program; QStringList args; QString errorReason = getArguments(ppCmd, program, args); if(errorReason.isEmpty()) { ppProcess.start(program, args); ppProcess.waitForFinished(-1); } else errorReason = "\n(" + errorReason + ")"; bool bSuccess = errorReason.isEmpty() && m_lmppData.readFile(fileNameOut2); if(FileAccess(fileNameIn2).size() > 0 && (!bSuccess || m_lmppData.m_size == 0)) { errors.append( i18n("The line-matching-preprocessing possibly failed. Check this command:\n\n %1" "\n\nThe line-matching-preprocessing command will be disabled now.", ppCmd) + errorReason); m_pOptions->m_LineMatchingPreProcessorCmd = ""; m_lmppData.readFile(fileNameIn2); } - FileAccess::removeTempFile(fileNameOut2); - if(fileNameInPP != fileNameIn2) - { - FileAccess::removeTempFile(fileNameInPP); - } } else if(m_pOptions->m_bIgnoreComments || m_pOptions->m_bIgnoreCase) { // We need a copy of the normal data. m_lmppData.copyBufFrom(m_normalData); } else { // We don't need any lmpp data at all. m_lmppData.reset(); } } if(!m_normalData.preprocess(m_pOptions->m_bPreserveCarriageReturn, pEncoding1) || !m_lmppData.preprocess(false, pEncoding2)) { errors.append(i18n("File %1 too large to process. Skipping.", fileNameIn1)); } else { //TODO: Needed? if(m_lmppData.m_vSize < m_normalData.m_vSize) { // Preprocessing command may result in smaller data buffer so adjust size m_lmppData.m_v.resize((int)m_normalData.m_vSize); for(qint64 i = m_lmppData.m_vSize; i < m_normalData.m_vSize; ++i) { // Set all empty lines to point to the end of the buffer. m_lmppData.m_v[(int)i].pLine = m_lmppData.m_unicodeBuf.unicode() + m_lmppData.m_unicodeBuf.length(); } m_lmppData.m_vSize = m_normalData.m_vSize; } // Internal Preprocessing: Uppercase-conversion if(m_pOptions->m_bIgnoreCase) { m_lmppData.m_unicodeBuf = QLocale::system().toUpper(m_lmppData.m_unicodeBuf); } // Ignore comments if(m_pOptions->m_bIgnoreComments) { m_lmppData.removeComments(); LineRef vSize = (LineRef)std::min(m_normalData.m_vSize, m_lmppData.m_vSize); for(int i = 0; i < (int)vSize; ++i) { m_normalData.m_v[i].bContainsPureComment = m_lmppData.m_v[i].bContainsPureComment; } } } - // Remove unneeded temporary files. (A temp file from clipboard must not be deleted.) - if(!bTempFileFromClipboard && !m_tempInputFileName.isEmpty()) - { - FileAccess::removeTempFile(m_tempInputFileName); - m_tempInputFileName = ""; - } - - if(!fileNameOut1.isEmpty()) - { - FileAccess::removeTempFile(fileNameOut1); - fileNameOut1 = ""; - } return errors; } /** Prepare the linedata vector for every input line.*/ bool SourceData::FileData::preprocess(bool bPreserveCR, QTextCodec* pEncoding) { qint64 i; // detect line end style QVector vOrigDataLineEndStyle; m_eLineEndStyle = eLineEndStyleUndefined; for(i = 0; i < m_size; ++i) { if(m_pBuf[i] == '\r') { if(i + 1 < m_size && m_pBuf[i + 1] == '\n') // not 16-bit unicode { vOrigDataLineEndStyle.push_back(eLineEndStyleDos); ++i; } else if(i > 0 && i + 2 < m_size && m_pBuf[i - 1] == '\0' && m_pBuf[i + 1] == '\0' && m_pBuf[i + 2] == '\n') // 16-bit unicode { vOrigDataLineEndStyle.push_back(eLineEndStyleDos); i += 2; } else // old mac line end style ? { vOrigDataLineEndStyle.push_back(eLineEndStyleUndefined); const_cast(m_pBuf)[i] = '\n'; // fix it in original data } } else if(m_pBuf[i] == '\n') { vOrigDataLineEndStyle.push_back(eLineEndStyleUnix); } } if(!vOrigDataLineEndStyle.isEmpty()) m_eLineEndStyle = vOrigDataLineEndStyle[0]; qint64 skipBytes = 0; QTextCodec* pCodec = ::detectEncoding(m_pBuf, m_size, skipBytes); if(pCodec != pEncoding) skipBytes = 0; if(m_size - skipBytes > INT_MAX) return false; QByteArray ba = QByteArray::fromRawData(m_pBuf + skipBytes, (int)(m_size - skipBytes)); QTextStream ts(ba, QIODevice::ReadOnly | QIODevice::Text); ts.setCodec(pEncoding); ts.setAutoDetectUnicode(false); m_unicodeBuf = ts.readAll(); ba.clear(); int ucSize = m_unicodeBuf.length(); const QChar* p = m_unicodeBuf.unicode(); m_bIsText = true; int lines = 1; m_bIncompleteConversion = false; for(i = 0; i < ucSize; ++i) { if(i >= ucSize || p[i] == '\n') { ++lines; } if(p[i].isNull()) { m_bIsText = false; } if(p[i] == QChar::ReplacementCharacter) { m_bIncompleteConversion = true; } } m_v.resize(lines + 5); int lineIdx = 0; int lineLength = 0; bool bNonWhiteFound = false; int whiteLength = 0; for(i = 0; i <= ucSize; ++i) { if(i >= ucSize || p[i] == '\n') { m_v[lineIdx].pLine = &p[i - lineLength]; while(/*!bPreserveCR &&*/ lineLength > 0 && m_v[lineIdx].pLine[lineLength - 1] == '\r') { --lineLength; } m_v[lineIdx].pFirstNonWhiteChar = m_v[lineIdx].pLine + std::min(whiteLength, lineLength); m_v[lineIdx].size = lineLength; if(lineIdx < vOrigDataLineEndStyle.count() && bPreserveCR && i < ucSize) { ++m_v[lineIdx].size; const_cast(m_v[lineIdx].pLine)[lineLength] = '\r'; //switch ( vOrigDataLineEndStyle[lineIdx] ) //{ //case eLineEndStyleUnix: const_cast(m_v[lineIdx].pLine)[lineLength] = '\n'; break; //case eLineEndStyleDos: const_cast(m_v[lineIdx].pLine)[lineLength] = '\r'; break; //case eLineEndStyleUndefined: const_cast(m_v[lineIdx].pLine)[lineLength] = '\x0b'; break; //} } lineLength = 0; bNonWhiteFound = false; whiteLength = 0; ++lineIdx; } else { ++lineLength; if(!bNonWhiteFound && isWhite(p[i])) ++whiteLength; else bNonWhiteFound = true; } } Q_ASSERT(lineIdx == lines); m_vSize = lines; return true; } // Must not be entered, when within a comment. // Returns either at a newline-character p[i]=='\n' or when i==size. // A line that contains only comments is still "white". // Comments in white lines must remain, while comments in // non-white lines are overwritten with spaces. void SourceData::FileData::checkLineForComments( const QChar* p, // pointer to start of buffer int& i, // index of current position (in, out) int size, // size of buffer bool& bWhite, // false if this line contains nonwhite characters (in, out) bool& bCommentInLine, // true if any comment is within this line (in, out) bool& bStartsOpenComment // true if the line ends within an comment (out) ) { bStartsOpenComment = false; for(; i < size; ++i) { // A single apostroph ' has prio over a double apostroph " (e.g. '"') // (if not in a string) if(p[i] == '\'') { bWhite = false; ++i; for(; !isLineOrBufEnd(p, i, size) && p[i] != '\''; ++i) ; if(p[i] == '\'') ++i; } // Strings have priority over comments: e.g. "/* Not a comment, but a string. */" else if(p[i] == '"') { bWhite = false; ++i; for(; !isLineOrBufEnd(p, i, size) && !(p[i] == '"' && p[i - 1] != '\\'); ++i) ; if(p[i] == '"') ++i; } // C++-comment else if(p[i] == '/' && i + 1 < size && p[i + 1] == '/') { int commentStart = i; bCommentInLine = true; i += 2; for(; !isLineOrBufEnd(p, i, size); ++i) ; if(!bWhite) { size = i - commentStart; m_unicodeBuf.replace(commentStart, size, QString(" ").repeated(size)); } return; } // C-comment else if(p[i] == '/' && i + 1 < size && p[i + 1] == '*') { int commentStart = i; bCommentInLine = true; i += 2; for(; !isLineOrBufEnd(p, i, size); ++i) { if(i + 1 < size && p[i] == '*' && p[i + 1] == '/') // end of the comment { i += 2; // More comments in the line? checkLineForComments(p, i, size, bWhite, bCommentInLine, bStartsOpenComment); if(!bWhite) { size = i - commentStart; m_unicodeBuf.replace(commentStart, size, QString(" ").repeated(size)); } return; } } bStartsOpenComment = true; return; } if(isLineOrBufEnd(p, i, size)) { return; } else if(!p[i].isSpace()) { bWhite = false; } } } // Modifies the input data, and replaces C/C++ comments with whitespace // when the line contains other data too. If the line contains only // a comment or white data, remember this in the flag bContainsPureComment. void SourceData::FileData::removeComments() { int line = 0; const QChar* p = m_unicodeBuf.unicode(); bool bWithinComment = false; int size = m_unicodeBuf.length(); for(int i = 0; i < size; ++i) { // std::cout << "2 " << std::string(&p[i], m_v[line].size) << std::endl; bool bWhite = true; bool bCommentInLine = false; if(bWithinComment) { int commentStart = i; bCommentInLine = true; for(; !isLineOrBufEnd(p, i, size); ++i) { if(i + 1 < size && p[i] == '*' && p[i + 1] == '/') // end of the comment { i += 2; // More comments in the line? checkLineForComments(p, i, size, bWhite, bCommentInLine, bWithinComment); if(!bWhite) { size = i - commentStart; m_unicodeBuf.replace(commentStart, size, QString(" ").repeated(size)); } break; } } } else { checkLineForComments(p, i, size, bWhite, bCommentInLine, bWithinComment); } // end of line Q_ASSERT(isLineOrBufEnd(p, i, size)); m_v[line].bContainsPureComment = bCommentInLine && bWhite; /* std::cout << line << " : " << ( bCommentInLine ? "c" : " " ) << ( bWhite ? "w " : " ") << std::string(pLD[line].pLine, pLD[line].size) << std::endl;*/ ++line; } } // First step void calcDiff3LineListUsingAB( const DiffList* pDiffListAB, Diff3LineList& d3ll) { // First make d3ll for AB (from pDiffListAB) DiffList::const_iterator i = pDiffListAB->begin(); int lineA = 0; int lineB = 0; Diff d(0, 0, 0); for(;;) { if(d.nofEquals == 0 && d.diff1 == 0 && d.diff2 == 0) { if(i != pDiffListAB->end()) { d = *i; ++i; } else break; } Diff3Line d3l; if(d.nofEquals > 0) { d3l.bAEqB = true; d3l.lineA = lineA; d3l.lineB = lineB; --d.nofEquals; ++lineA; ++lineB; } else if(d.diff1 > 0 && d.diff2 > 0) { d3l.lineA = lineA; d3l.lineB = lineB; --d.diff1; --d.diff2; ++lineA; ++lineB; } else if(d.diff1 > 0) { d3l.lineA = lineA; --d.diff1; ++lineA; } else if(d.diff2 > 0) { d3l.lineB = lineB; --d.diff2; ++lineB; } Q_ASSERT(d.nofEquals >= 0); d3ll.push_back(d3l); } } // Second step void calcDiff3LineListUsingAC( const DiffList* pDiffListAC, Diff3LineList& d3ll) { //////////////// // Now insert data from C using pDiffListAC DiffList::const_iterator i = pDiffListAC->begin(); Diff3LineList::iterator i3 = d3ll.begin(); int lineA = 0; int lineC = 0; Diff d(0, 0, 0); for(;;) { if(d.nofEquals == 0 && d.diff1 == 0 && d.diff2 == 0) { if(i != pDiffListAC->end()) { d = *i; ++i; } else break; } Diff3Line d3l; if(d.nofEquals > 0) { // Find the corresponding lineA while((*i3).lineA != lineA) ++i3; (*i3).lineC = lineC; (*i3).bAEqC = true; (*i3).bBEqC = (*i3).bAEqB; --d.nofEquals; ++lineA; ++lineC; ++i3; } else if(d.diff1 > 0 && d.diff2 > 0) { d3l.lineC = lineC; d3ll.insert(i3, d3l); --d.diff1; --d.diff2; ++lineA; ++lineC; } else if(d.diff1 > 0) { --d.diff1; ++lineA; } else if(d.diff2 > 0) { d3l.lineC = lineC; d3ll.insert(i3, d3l); --d.diff2; ++lineC; } } } // Third step void calcDiff3LineListUsingBC( const DiffList* pDiffListBC, Diff3LineList& d3ll) { //////////////// // Now improve the position of data from C using pDiffListBC // If a line from C equals a line from A then it is in the // same Diff3Line already. // If a line from C equals a line from B but not A, this // information will be used here. DiffList::const_iterator i = pDiffListBC->begin(); Diff3LineList::iterator i3b = d3ll.begin(); Diff3LineList::iterator i3c = d3ll.begin(); int lineB = 0; int lineC = 0; Diff d(0, 0, 0); for(;;) { if(d.nofEquals == 0 && d.diff1 == 0 && d.diff2 == 0) { if(i != pDiffListBC->end()) { d = *i; ++i; } else break; } Diff3Line d3l; if(d.nofEquals > 0) { // Find the corresponding lineB and lineC while(i3b != d3ll.end() && (*i3b).lineB != lineB) ++i3b; while(i3c != d3ll.end() && (*i3c).lineC != lineC) ++i3c; Q_ASSERT(i3b != d3ll.end()); Q_ASSERT(i3c != d3ll.end()); if(i3b == i3c) { Q_ASSERT((*i3b).lineC == lineC); (*i3b).bBEqC = true; } else { // Is it possible to move this line up? // Test if no other B's are used between i3c and i3b // First test which is before: i3c or i3b ? Diff3LineList::iterator i3c1 = i3c; Diff3LineList::iterator i3b1 = i3b; while(i3c1 != i3b && i3b1 != i3c) { Q_ASSERT(i3b1 != d3ll.end() || i3c1 != d3ll.end()); if(i3c1 != d3ll.end()) ++i3c1; if(i3b1 != d3ll.end()) ++i3b1; } if(i3c1 == i3b && !(*i3b).bAEqB) // i3c before i3b { Diff3LineList::iterator i3 = i3c; int nofDisturbingLines = 0; while(i3 != i3b && i3 != d3ll.end()) { if((*i3).lineB != -1) ++nofDisturbingLines; ++i3; } if(nofDisturbingLines > 0) //&& nofDisturbingLines < d.nofEquals*d.nofEquals+4 ) { Diff3LineList::iterator i3_last_equal_A = d3ll.end(); i3 = i3c; while(i3 != i3b) { if(i3->bAEqB) { i3_last_equal_A = i3; } ++i3; } /* If i3_last_equal_A isn't still set to d3ll.end(), then * we've found a line in A that is equal to one in B * somewhere between i3c and i3b */ bool before_or_on_equal_line_in_A = (i3_last_equal_A != d3ll.end()); // Move the disturbing lines up, out of sight. i3 = i3c; while(i3 != i3b) { if((*i3).lineB != -1 || (before_or_on_equal_line_in_A && i3->lineA != -1)) { d3l.lineB = (*i3).lineB; (*i3).lineB = -1; // Move A along if it matched B if(before_or_on_equal_line_in_A) { d3l.lineA = i3->lineA; d3l.bAEqB = i3->bAEqB; i3->lineA = -1; i3->bAEqC = false; } (*i3).bAEqB = false; (*i3).bBEqC = false; d3ll.insert(i3c, d3l); } if(i3 == i3_last_equal_A) { before_or_on_equal_line_in_A = false; } ++i3; } nofDisturbingLines = 0; } if(nofDisturbingLines == 0) { // Yes, the line from B can be moved. (*i3b).lineB = -1; // This might leave an empty line: removed later. (*i3b).bAEqB = false; (*i3b).bBEqC = false; (*i3c).lineB = lineB; (*i3c).bBEqC = true; (*i3c).bAEqB = (*i3c).bAEqC; } } else if(i3b1 == i3c && !(*i3c).bAEqC) { Diff3LineList::iterator i3 = i3b; int nofDisturbingLines = 0; while(i3 != i3c && i3 != d3ll.end()) { if((*i3).lineC != -1) ++nofDisturbingLines; ++i3; } if(nofDisturbingLines > 0) //&& nofDisturbingLines < d.nofEquals*d.nofEquals+4 ) { Diff3LineList::iterator i3_last_equal_A = d3ll.end(); i3 = i3b; while(i3 != i3c) { if(i3->bAEqC) { i3_last_equal_A = i3; } ++i3; } /* If i3_last_equal_A isn't still set to d3ll.end(), then * we've found a line in A that is equal to one in C * somewhere between i3b and i3c */ bool before_or_on_equal_line_in_A = (i3_last_equal_A != d3ll.end()); // Move the disturbing lines up. i3 = i3b; while(i3 != i3c) { if((*i3).lineC != -1 || (before_or_on_equal_line_in_A && i3->lineA != -1)) { d3l.lineC = (*i3).lineC; (*i3).lineC = -1; // Move A along if it matched C if(before_or_on_equal_line_in_A) { d3l.lineA = i3->lineA; d3l.bAEqC = i3->bAEqC; i3->lineA = -1; i3->bAEqB = false; } (*i3).bAEqC = false; (*i3).bBEqC = false; d3ll.insert(i3b, d3l); } if(i3 == i3_last_equal_A) { before_or_on_equal_line_in_A = false; } ++i3; } nofDisturbingLines = 0; } if(nofDisturbingLines == 0) { // Yes, the line from C can be moved. (*i3c).lineC = -1; // This might leave an empty line: removed later. (*i3c).bAEqC = false; (*i3c).bBEqC = false; (*i3b).lineC = lineC; (*i3b).bBEqC = true; (*i3b).bAEqC = (*i3b).bAEqB; } } } --d.nofEquals; ++lineB; ++lineC; ++i3b; ++i3c; } else if(d.diff1 > 0) { Diff3LineList::iterator i3 = i3b; while((*i3).lineB != lineB) ++i3; if(i3 != i3b && (*i3).bAEqB == false) { // Take B from this line and move it up as far as possible d3l.lineB = lineB; d3ll.insert(i3b, d3l); (*i3).lineB = -1; } else { i3b = i3; } --d.diff1; ++lineB; ++i3b; if(d.diff2 > 0) { --d.diff2; ++lineC; } } else if(d.diff2 > 0) { --d.diff2; ++lineC; } } /* Diff3LineList::iterator it = d3ll.begin(); int li=0; for( ; it!=d3ll.end(); ++it, ++li ) { printf( "%4d %4d %4d %4d A%c=B A%c=C B%c=C\n", li, (*it).lineA, (*it).lineB, (*it).lineC, (*it).bAEqB ? '=' : '!', (*it).bAEqC ? '=' : '!', (*it).bBEqC ? '=' : '!' ); } printf("\n");*/ } #ifdef Q_OS_WIN using ::equal; #endif // Test if the move would pass a barrier. Return true if not. static bool isValidMove(ManualDiffHelpList* pManualDiffHelpList, int line1, int line2, int winIdx1, int winIdx2) { if(line1 >= 0 && line2 >= 0) { ManualDiffHelpList::const_iterator i; for(i = pManualDiffHelpList->begin(); i != pManualDiffHelpList->end(); ++i) { const ManualDiffHelpEntry& mdhe = *i; // Barrier int l1 = winIdx1 == 1 ? mdhe.lineA1 : winIdx1 == 2 ? mdhe.lineB1 : mdhe.lineC1; int l2 = winIdx2 == 1 ? mdhe.lineA1 : winIdx2 == 2 ? mdhe.lineB1 : mdhe.lineC1; if(l1 >= 0 && l2 >= 0) { if((line1 >= l1 && line2 < l2) || (line1 < l1 && line2 >= l2)) return false; l1 = winIdx1 == 1 ? mdhe.lineA2 : winIdx1 == 2 ? mdhe.lineB2 : mdhe.lineC2; l2 = winIdx2 == 1 ? mdhe.lineA2 : winIdx2 == 2 ? mdhe.lineB2 : mdhe.lineC2; ++l1; ++l2; if((line1 >= l1 && line2 < l2) || (line1 < l1 && line2 >= l2)) return false; } } } return true; // no barrier passed. } static bool runDiff(const LineData* p1, LineRef size1, const LineData* p2, LineRef size2, DiffList& diffList, Options* pOptions) { ProgressProxy pp; static GnuDiff gnuDiff; // All values are initialized with zeros. pp.setCurrent(0); diffList.clear(); if(p1[0].pLine == nullptr || p2[0].pLine == nullptr || size1 == 0 || size2 == 0) { Diff d(0, 0, 0); if(p1[0].pLine == nullptr && p2[0].pLine == nullptr && size1 == size2) d.nofEquals = size1; else { d.diff1 = size1; d.diff2 = size2; } diffList.push_back(d); } else { GnuDiff::comparison comparisonInput; memset(&comparisonInput, 0, sizeof(comparisonInput)); comparisonInput.parent = nullptr; comparisonInput.file[0].buffer = p1[0].pLine; //ptr to buffer comparisonInput.file[0].buffered = (p1[size1 - 1].pLine - p1[0].pLine + p1[size1 - 1].size); // size of buffer comparisonInput.file[1].buffer = p2[0].pLine; //ptr to buffer comparisonInput.file[1].buffered = (p2[size2 - 1].pLine - p2[0].pLine + p2[size2 - 1].size); // size of buffer gnuDiff.ignore_white_space = GnuDiff::IGNORE_ALL_SPACE; // I think nobody needs anything else ... gnuDiff.bIgnoreWhiteSpace = true; gnuDiff.bIgnoreNumbers = pOptions->m_bIgnoreNumbers; gnuDiff.minimal = pOptions->m_bTryHard; gnuDiff.ignore_case = false; GnuDiff::change* script = gnuDiff.diff_2_files(&comparisonInput); LineRef equalLinesAtStart = comparisonInput.file[0].prefix_lines; LineRef currentLine1 = 0; LineRef currentLine2 = 0; GnuDiff::change* p = nullptr; for(GnuDiff::change* e = script; e; e = p) { Diff d(0, 0, 0); d.nofEquals = e->line0 - currentLine1; Q_ASSERT(d.nofEquals == e->line1 - currentLine2); d.diff1 = e->deleted; d.diff2 = e->inserted; currentLine1 += d.nofEquals + d.diff1; currentLine2 += d.nofEquals + d.diff2; diffList.push_back(d); p = e->link; free(e); } if(diffList.empty()) { Diff d(0, 0, 0); d.nofEquals = std::min(size1, size2); d.diff1 = size1 - d.nofEquals; d.diff2 = size2 - d.nofEquals; diffList.push_back(d); /* Diff d(0,0,0); d.nofEquals = equalLinesAtStart; if ( gnuDiff.files[0].missing_newline != gnuDiff.files[1].missing_newline ) { d.diff1 = gnuDiff.files[0].missing_newline ? 0 : 1; d.diff2 = gnuDiff.files[1].missing_newline ? 0 : 1; ++d.nofEquals; } else if ( !gnuDiff.files[0].missing_newline ) { ++d.nofEquals; } diffList.push_back(d); */ } else { diffList.front().nofEquals += equalLinesAtStart; currentLine1 += equalLinesAtStart; currentLine2 += equalLinesAtStart; LineRef nofEquals = std::min(size1 - currentLine1, size2 - currentLine2); if(nofEquals == 0) { diffList.back().diff1 += size1 - currentLine1; diffList.back().diff2 += size2 - currentLine2; } else { Diff d(nofEquals, size1 - currentLine1 - nofEquals, size2 - currentLine2 - nofEquals); diffList.push_back(d); } /* if ( gnuDiff.files[0].missing_newline != gnuDiff.files[1].missing_newline ) { diffList.back().diff1 += gnuDiff.files[0].missing_newline ? 0 : 1; diffList.back().diff2 += gnuDiff.files[1].missing_newline ? 0 : 1; } else if ( !gnuDiff.files[0].missing_newline ) { ++ diffList.back().nofEquals; } */ } } // Verify difflist { LineRef l1 = 0; LineRef l2 = 0; DiffList::iterator i; for(i = diffList.begin(); i != diffList.end(); ++i) { l1 += i->nofEquals + i->diff1; l2 += i->nofEquals + i->diff2; } //if( l1!=p1-p1start || l2!=p2-p2start ) Q_ASSERT(l1 == size1 && l2 == size2); } pp.setCurrent(1.0); return true; } bool runDiff(const LineData* p1, LineRef size1, const LineData* p2, LineRef size2, DiffList& diffList, int winIdx1, int winIdx2, ManualDiffHelpList* pManualDiffHelpList, Options* pOptions) { diffList.clear(); DiffList diffList2; int l1begin = 0; int l2begin = 0; ManualDiffHelpList::const_iterator i; for(i = pManualDiffHelpList->begin(); i != pManualDiffHelpList->end(); ++i) { const ManualDiffHelpEntry& mdhe = *i; int l1end = winIdx1 == 1 ? mdhe.lineA1 : winIdx1 == 2 ? mdhe.lineB1 : mdhe.lineC1; int l2end = winIdx2 == 1 ? mdhe.lineA1 : winIdx2 == 2 ? mdhe.lineB1 : mdhe.lineC1; if(l1end >= 0 && l2end >= 0) { runDiff(p1 + l1begin, l1end - l1begin, p2 + l2begin, l2end - l2begin, diffList2, pOptions); diffList.splice(diffList.end(), diffList2); l1begin = l1end; l2begin = l2end; l1end = winIdx1 == 1 ? mdhe.lineA2 : winIdx1 == 2 ? mdhe.lineB2 : mdhe.lineC2; l2end = winIdx2 == 1 ? mdhe.lineA2 : winIdx2 == 2 ? mdhe.lineB2 : mdhe.lineC2; if(l1end >= 0 && l2end >= 0) { ++l1end; // point to line after last selected line ++l2end; runDiff(p1 + l1begin, l1end - l1begin, p2 + l2begin, l2end - l2begin, diffList2, pOptions); diffList.splice(diffList.end(), diffList2); l1begin = l1end; l2begin = l2end; } } } runDiff(p1 + l1begin, size1 - l1begin, p2 + l2begin, size2 - l2begin, diffList2, pOptions); diffList.splice(diffList.end(), diffList2); return true; } void correctManualDiffAlignment(Diff3LineList& d3ll, ManualDiffHelpList* pManualDiffHelpList) { if(pManualDiffHelpList->empty()) return; // If a line appears unaligned in comparison to the manual alignment, correct this. ManualDiffHelpList::iterator iMDHL; for(iMDHL = pManualDiffHelpList->begin(); iMDHL != pManualDiffHelpList->end(); ++iMDHL) { Diff3LineList::iterator i3 = d3ll.begin(); int missingWinIdx = 0; int alignedSum = (iMDHL->lineA1 < 0 ? 0 : 1) + (iMDHL->lineB1 < 0 ? 0 : 1) + (iMDHL->lineC1 < 0 ? 0 : 1); if(alignedSum == 2) { // If only A & B are aligned then let C rather be aligned with A // If only A & C are aligned then let B rather be aligned with A // If only B & C are aligned then let A rather be aligned with B missingWinIdx = iMDHL->lineA1 < 0 ? 1 : (iMDHL->lineB1 < 0 ? 2 : 3); } else if(alignedSum <= 1) { return; } // At the first aligned line, move up the two other lines into new d3ls until the second input is aligned // Then move up the third input until all three lines are aligned. int wi = 0; for(; i3 != d3ll.end(); ++i3) { for(wi = 1; wi <= 3; ++wi) { if(i3->getLineInFile(wi) >= 0 && iMDHL->firstLine(wi) == i3->getLineInFile(wi)) break; } if(wi <= 3) break; } if(wi >= 1 && wi <= 3) { // Found manual alignment for one source Diff3LineList::iterator iDest = i3; // Move lines up until the next firstLine is found. Omit wi from move and search. int wi2 = 0; for(; i3 != d3ll.end(); ++i3) { for(wi2 = 1; wi2 <= 3; ++wi2) { if(wi != wi2 && i3->getLineInFile(wi2) >= 0 && iMDHL->firstLine(wi2) == i3->getLineInFile(wi2)) break; } if(wi2 > 3) { // Not yet found // Move both others up Diff3Line d3l; // Move both up if(wi == 1) // Move B and C up { d3l.bBEqC = i3->bBEqC; d3l.lineB = i3->lineB; d3l.lineC = i3->lineC; i3->lineB = -1; i3->lineC = -1; } if(wi == 2) // Move A and C up { d3l.bAEqC = i3->bAEqC; d3l.lineA = i3->lineA; d3l.lineC = i3->lineC; i3->lineA = -1; i3->lineC = -1; } if(wi == 3) // Move A and B up { d3l.bAEqB = i3->bAEqB; d3l.lineA = i3->lineA; d3l.lineB = i3->lineB; i3->lineA = -1; i3->lineB = -1; } i3->bAEqB = false; i3->bAEqC = false; i3->bBEqC = false; d3ll.insert(iDest, d3l); } else { // align the found line with the line we already have here if(i3 != iDest) { if(wi2 == 1) { iDest->lineA = i3->lineA; i3->lineA = -1; i3->bAEqB = false; i3->bAEqC = false; } else if(wi2 == 2) { iDest->lineB = i3->lineB; i3->lineB = -1; i3->bAEqB = false; i3->bBEqC = false; } else if(wi2 == 3) { iDest->lineC = i3->lineC; i3->lineC = -1; i3->bBEqC = false; i3->bAEqC = false; } } if(missingWinIdx != 0) { for(; i3 != d3ll.end(); ++i3) { int wi3 = missingWinIdx; if(i3->getLineInFile(wi3) >= 0) { // not found, move the line before iDest Diff3Line d3l; if(wi3 == 1) { if(i3->bAEqB) // Stop moving lines up if one equal is found. break; d3l.lineA = i3->lineA; i3->lineA = -1; i3->bAEqB = false; i3->bAEqC = false; } if(wi3 == 2) { if(i3->bAEqB) break; d3l.lineB = i3->lineB; i3->lineB = -1; i3->bAEqB = false; i3->bBEqC = false; } if(wi3 == 3) { if(i3->bAEqC) break; d3l.lineC = i3->lineC; i3->lineC = -1; i3->bAEqC = false; i3->bBEqC = false; } d3ll.insert(iDest, d3l); } } // for(), searching for wi3 } break; } } // for(), searching for wi2 } // if, wi found } // for (iMDHL) } // Fourth step void calcDiff3LineListTrim( Diff3LineList& d3ll, const LineData* pldA, const LineData* pldB, const LineData* pldC, ManualDiffHelpList* pManualDiffHelpList) { const Diff3Line d3l_empty; d3ll.removeAll(d3l_empty); Diff3LineList::iterator i3 = d3ll.begin(); Diff3LineList::iterator i3A = d3ll.begin(); Diff3LineList::iterator i3B = d3ll.begin(); Diff3LineList::iterator i3C = d3ll.begin(); int line = 0; // diff3line counters int lineA = 0; // int lineB = 0; int lineC = 0; ManualDiffHelpList::iterator iMDHL = pManualDiffHelpList->begin(); // The iterator i3 and the variable line look ahead. // The iterators i3A, i3B, i3C and corresponding lineA, lineB and lineC stop at empty lines, if found. // If possible, then the texts from the look ahead will be moved back to the empty places. for(; i3 != d3ll.end(); ++i3, ++line) { if(iMDHL != pManualDiffHelpList->end()) { if((i3->lineA >= 0 && i3->lineA == iMDHL->lineA1) || (i3->lineB >= 0 && i3->lineB == iMDHL->lineB1) || (i3->lineC >= 0 && i3->lineC == iMDHL->lineC1)) { i3A = i3; i3B = i3; i3C = i3; lineA = line; lineB = line; lineC = line; ++iMDHL; } } if(line > lineA && (*i3).lineA != -1 && (*i3A).lineB != -1 && (*i3A).bBEqC && ::equal(pldA[(*i3).lineA], pldB[(*i3A).lineB], false) && isValidMove(pManualDiffHelpList, (*i3).lineA, (*i3A).lineB, 1, 2) && isValidMove(pManualDiffHelpList, (*i3).lineA, (*i3A).lineC, 1, 3)) { // Empty space for A. A matches B and C in the empty line. Move it up. (*i3A).lineA = (*i3).lineA; (*i3A).bAEqB = true; (*i3A).bAEqC = true; (*i3).lineA = -1; (*i3).bAEqB = false; (*i3).bAEqC = false; ++i3A; ++lineA; } if(line > lineB && (*i3).lineB != -1 && (*i3B).lineA != -1 && (*i3B).bAEqC && ::equal(pldB[(*i3).lineB], pldA[(*i3B).lineA], false) && isValidMove(pManualDiffHelpList, (*i3).lineB, (*i3B).lineA, 2, 1) && isValidMove(pManualDiffHelpList, (*i3).lineB, (*i3B).lineC, 2, 3)) { // Empty space for B. B matches A and C in the empty line. Move it up. (*i3B).lineB = (*i3).lineB; (*i3B).bAEqB = true; (*i3B).bBEqC = true; (*i3).lineB = -1; (*i3).bAEqB = false; (*i3).bBEqC = false; ++i3B; ++lineB; } if(line > lineC && (*i3).lineC != -1 && (*i3C).lineA != -1 && (*i3C).bAEqB && ::equal(pldC[(*i3).lineC], pldA[(*i3C).lineA], false) && isValidMove(pManualDiffHelpList, (*i3).lineC, (*i3C).lineA, 3, 1) && isValidMove(pManualDiffHelpList, (*i3).lineC, (*i3C).lineB, 3, 2)) { // Empty space for C. C matches A and B in the empty line. Move it up. (*i3C).lineC = (*i3).lineC; (*i3C).bAEqC = true; (*i3C).bBEqC = true; (*i3).lineC = -1; (*i3).bAEqC = false; (*i3).bBEqC = false; ++i3C; ++lineC; } if(line > lineA && (*i3).lineA != -1 && !(*i3).bAEqB && !(*i3).bAEqC && isValidMove(pManualDiffHelpList, (*i3).lineA, (*i3A).lineB, 1, 2) && isValidMove(pManualDiffHelpList, (*i3).lineA, (*i3A).lineC, 1, 3)) { // Empty space for A. A doesn't match B or C. Move it up. (*i3A).lineA = (*i3).lineA; (*i3).lineA = -1; if(i3A->lineB != -1 && ::equal(pldA[i3A->lineA], pldB[i3A->lineB], false)) { i3A->bAEqB = true; } if((i3A->bAEqB && i3A->bBEqC) || (i3A->lineC != -1 && ::equal(pldA[i3A->lineA], pldC[i3A->lineC], false))) { i3A->bAEqC = true; } ++i3A; ++lineA; } if(line > lineB && (*i3).lineB != -1 && !(*i3).bAEqB && !(*i3).bBEqC && isValidMove(pManualDiffHelpList, (*i3).lineB, (*i3B).lineA, 2, 1) && isValidMove(pManualDiffHelpList, (*i3).lineB, (*i3B).lineC, 2, 3)) { // Empty space for B. B matches neither A nor C. Move B up. (*i3B).lineB = (*i3).lineB; (*i3).lineB = -1; if(i3B->lineA != -1 && ::equal(pldA[i3B->lineA], pldB[i3B->lineB], false)) { i3B->bAEqB = true; } if((i3B->bAEqB && i3B->bAEqC) || (i3B->lineC != -1 && ::equal(pldB[i3B->lineB], pldC[i3B->lineC], false))) { i3B->bBEqC = true; } ++i3B; ++lineB; } if(line > lineC && (*i3).lineC != -1 && !(*i3).bAEqC && !(*i3).bBEqC && isValidMove(pManualDiffHelpList, (*i3).lineC, (*i3C).lineA, 3, 1) && isValidMove(pManualDiffHelpList, (*i3).lineC, (*i3C).lineB, 3, 2)) { // Empty space for C. C matches neither A nor B. Move C up. (*i3C).lineC = (*i3).lineC; (*i3).lineC = -1; if(i3C->lineA != -1 && ::equal(pldA[i3C->lineA], pldC[i3C->lineC], false)) { i3C->bAEqC = true; } if((i3C->bAEqC && i3C->bAEqB) || (i3C->lineB != -1 && ::equal(pldB[i3C->lineB], pldC[i3C->lineC], false))) { i3C->bBEqC = true; } ++i3C; ++lineC; } if(line > lineA && line > lineB && (*i3).lineA != -1 && (*i3).bAEqB && !(*i3).bAEqC) { // Empty space for A and B. A matches B, but not C. Move A & B up. Diff3LineList::iterator i = lineA > lineB ? i3A : i3B; int l = lineA > lineB ? lineA : lineB; if(isValidMove(pManualDiffHelpList, i->lineC, (*i3).lineA, 3, 1) && isValidMove(pManualDiffHelpList, i->lineC, (*i3).lineB, 3, 2)) { (*i).lineA = (*i3).lineA; (*i).lineB = (*i3).lineB; (*i).bAEqB = true; if(i->lineC != -1 && ::equal(pldA[i->lineA], pldC[i->lineC], false)) { (*i).bAEqC = true; (*i).bBEqC = true; } (*i3).lineA = -1; (*i3).lineB = -1; (*i3).bAEqB = false; i3A = i; i3B = i; ++i3A; ++i3B; lineA = l + 1; lineB = l + 1; } } else if(line > lineA && line > lineC && (*i3).lineA != -1 && (*i3).bAEqC && !(*i3).bAEqB) { // Empty space for A and C. A matches C, but not B. Move A & C up. Diff3LineList::iterator i = lineA > lineC ? i3A : i3C; int l = lineA > lineC ? lineA : lineC; if(isValidMove(pManualDiffHelpList, i->lineB, (*i3).lineA, 2, 1) && isValidMove(pManualDiffHelpList, i->lineB, (*i3).lineC, 2, 3)) { (*i).lineA = (*i3).lineA; (*i).lineC = (*i3).lineC; (*i).bAEqC = true; if(i->lineB != -1 && ::equal(pldA[i->lineA], pldB[i->lineB], false)) { (*i).bAEqB = true; (*i).bBEqC = true; } (*i3).lineA = -1; (*i3).lineC = -1; (*i3).bAEqC = false; i3A = i; i3C = i; ++i3A; ++i3C; lineA = l + 1; lineC = l + 1; } } else if(line > lineB && line > lineC && (*i3).lineB != -1 && (*i3).bBEqC && !(*i3).bAEqC) { // Empty space for B and C. B matches C, but not A. Move B & C up. Diff3LineList::iterator i = lineB > lineC ? i3B : i3C; int l = lineB > lineC ? lineB : lineC; if(isValidMove(pManualDiffHelpList, i->lineA, (*i3).lineB, 1, 2) && isValidMove(pManualDiffHelpList, i->lineA, (*i3).lineC, 1, 3)) { (*i).lineB = (*i3).lineB; (*i).lineC = (*i3).lineC; (*i).bBEqC = true; if(i->lineA != -1 && ::equal(pldA[i->lineA], pldB[i->lineB], false)) { (*i).bAEqB = true; (*i).bAEqC = true; } (*i3).lineB = -1; (*i3).lineC = -1; (*i3).bBEqC = false; i3B = i; i3C = i; ++i3B; ++i3C; lineB = l + 1; lineC = l + 1; } } if((*i3).lineA != -1) { lineA = line + 1; i3A = i3; ++i3A; } if((*i3).lineB != -1) { lineB = line + 1; i3B = i3; ++i3B; } if((*i3).lineC != -1) { lineC = line + 1; i3C = i3; ++i3C; } } d3ll.removeAll(d3l_empty); /* Diff3LineList::iterator it = d3ll.begin(); int li=0; for( ; it!=d3ll.end(); ++it, ++li ) { printf( "%4d %4d %4d %4d A%c=B A%c=C B%c=C\n", li, (*it).lineA, (*it).lineB, (*it).lineC, (*it).bAEqB ? '=' : '!', (*it).bAEqC ? '=' : '!', (*it).bBEqC ? '=' : '!' ); } */ } void DiffBufferInfo::init(Diff3LineList* pD3ll, const Diff3LineVector* pD3lv, const LineData* pldA, LineRef sizeA, const LineData* pldB, LineRef sizeB, const LineData* pldC, LineRef sizeC) { m_pDiff3LineList = pD3ll; m_pDiff3LineVector = pD3lv; m_pLineDataA = pldA; m_pLineDataB = pldB; m_pLineDataC = pldC; m_sizeA = sizeA; m_sizeB = sizeB; m_sizeC = sizeC; Diff3LineList::iterator i3 = pD3ll->begin(); for(; i3 != pD3ll->end(); ++i3) { i3->m_pDiffBufferInfo = this; } } void calcWhiteDiff3Lines( Diff3LineList& d3ll, const LineData* pldA, const LineData* pldB, const LineData* pldC) { Diff3LineList::iterator i3 = d3ll.begin(); for(; i3 != d3ll.end(); ++i3) { i3->bWhiteLineA = ((*i3).lineA == -1 || pldA == nullptr || pldA[(*i3).lineA].whiteLine() || pldA[(*i3).lineA].bContainsPureComment); i3->bWhiteLineB = ((*i3).lineB == -1 || pldB == nullptr || pldB[(*i3).lineB].whiteLine() || pldB[(*i3).lineB].bContainsPureComment); i3->bWhiteLineC = ((*i3).lineC == -1 || pldC == nullptr || pldC[(*i3).lineC].whiteLine() || pldC[(*i3).lineC].bContainsPureComment); } } inline bool equal(QChar c1, QChar c2, bool /*bStrict*/) { // If bStrict then white space doesn't match //if ( bStrict && ( c1==' ' || c1=='\t' ) ) // return false; return c1 == c2; } // My own diff-invention: template void calcDiff(const T* p1, LineRef size1, const T* p2, LineRef size2, DiffList& diffList, int match, int maxSearchRange) { diffList.clear(); const T* p1start = p1; const T* p2start = p2; const T* p1end = p1 + size1; const T* p2end = p2 + size2; for(;;) { int nofEquals = 0; while(p1 != p1end && p2 != p2end && equal(*p1, *p2, false)) { ++p1; ++p2; ++nofEquals; } bool bBestValid = false; int bestI1 = 0; int bestI2 = 0; int i1 = 0; int i2 = 0; for(i1 = 0;; ++i1) { if(&p1[i1] == p1end || (bBestValid && i1 >= bestI1 + bestI2)) { break; } for(i2 = 0; i2 < maxSearchRange; ++i2) { if(&p2[i2] == p2end || (bBestValid && i1 + i2 >= bestI1 + bestI2)) { break; } else if(equal(p2[i2], p1[i1], true) && (match == 1 || abs(i1 - i2) < 3 || (&p2[i2 + 1] == p2end && &p1[i1 + 1] == p1end) || (&p2[i2 + 1] != p2end && &p1[i1 + 1] != p1end && equal(p2[i2 + 1], p1[i1 + 1], false)))) { if(i1 + i2 < bestI1 + bestI2 || bBestValid == false) { bestI1 = i1; bestI2 = i2; bBestValid = true; break; } } } } // The match was found using the strict search. Go back if there are non-strict // matches. while(bestI1 >= 1 && bestI2 >= 1 && equal(p1[bestI1 - 1], p2[bestI2 - 1], false)) { --bestI1; --bestI2; } bool bEndReached = false; if(bBestValid) { // continue somehow Diff d(nofEquals, bestI1, bestI2); diffList.push_back(d); p1 += bestI1; p2 += bestI2; } else { // Nothing else to match. Diff d(nofEquals, p1end - p1, p2end - p2); diffList.push_back(d); bEndReached = true; //break; } // Sometimes the algorithm that chooses the first match unfortunately chooses // a match where later actually equal parts don't match anymore. // A different match could be achieved, if we start at the end. // Do it, if it would be a better match. int nofUnmatched = 0; const T* pu1 = p1 - 1; const T* pu2 = p2 - 1; while(pu1 >= p1start && pu2 >= p2start && equal(*pu1, *pu2, false)) { ++nofUnmatched; --pu1; --pu2; } Diff d = diffList.back(); if(nofUnmatched > 0) { // We want to go backwards the nofUnmatched elements and redo // the matching d = diffList.back(); Diff origBack = d; diffList.pop_back(); while(nofUnmatched > 0) { if(d.diff1 > 0 && d.diff2 > 0) { --d.diff1; --d.diff2; --nofUnmatched; } else if(d.nofEquals > 0) { --d.nofEquals; --nofUnmatched; } if(d.nofEquals == 0 && (d.diff1 == 0 || d.diff2 == 0) && nofUnmatched > 0) { if(diffList.empty()) break; d.nofEquals += diffList.back().nofEquals; d.diff1 += diffList.back().diff1; d.diff2 += diffList.back().diff2; diffList.pop_back(); bEndReached = false; } } if(bEndReached) diffList.push_back(origBack); else { p1 = pu1 + 1 + nofUnmatched; p2 = pu2 + 1 + nofUnmatched; diffList.push_back(d); } } if(bEndReached) break; } // Verify difflist { LineRef l1 = 0; LineRef l2 = 0; DiffList::iterator i; for(i = diffList.begin(); i != diffList.end(); ++i) { l1 += i->nofEquals + i->diff1; l2 += i->nofEquals + i->diff2; } Q_ASSERT(l1 == size1 && l2 == size2); } } bool fineDiff( Diff3LineList& diff3LineList, int selector, const LineData* v1, const LineData* v2) { // Finetuning: Diff each line with deltas ProgressProxy pp; int maxSearchLength = 500; Diff3LineList::iterator i; LineRef k1 = 0; LineRef k2 = 0; bool bTextsTotalEqual = true; int listSize = diff3LineList.size(); pp.setMaxNofSteps(listSize); int listIdx = 0; for(i = diff3LineList.begin(); i != diff3LineList.end(); ++i) { Q_ASSERT(selector == 1 || selector == 2 || selector == 3); if(selector == 1) { k1 = i->lineA; k2 = i->lineB; } else if(selector == 2) { k1 = i->lineB; k2 = i->lineC; } else if(selector == 3) { k1 = i->lineC; k2 = i->lineA; } if((k1 == -1 && k2 != -1) || (k1 != -1 && k2 == -1)) bTextsTotalEqual = false; if(k1 != -1 && k2 != -1) { if(v1[k1].size != v2[k2].size || memcmp(v1[k1].pLine, v2[k2].pLine, v1[k1].size << 1) != 0) { bTextsTotalEqual = false; DiffList* pDiffList = new DiffList; calcDiff(v1[k1].pLine, v1[k1].size, v2[k2].pLine, v2[k2].size, *pDiffList, 2, maxSearchLength); // Optimize the diff list. DiffList::iterator dli; bool bUsefulFineDiff = false; for(dli = pDiffList->begin(); dli != pDiffList->end(); ++dli) { if(dli->nofEquals >= 4) { bUsefulFineDiff = true; break; } } for(dli = pDiffList->begin(); dli != pDiffList->end(); ++dli) { if(dli->nofEquals < 4 && (dli->diff1 > 0 || dli->diff2 > 0) && !(bUsefulFineDiff && dli == pDiffList->begin())) { dli->diff1 += dli->nofEquals; dli->diff2 += dli->nofEquals; dli->nofEquals = 0; } } Q_ASSERT(selector == 1 || selector == 2 || selector == 3); if(selector == 1) { delete(*i).pFineAB; (*i).pFineAB = pDiffList; } else if(selector == 2) { delete(*i).pFineBC; (*i).pFineBC = pDiffList; } else if(selector == 3) { delete(*i).pFineCA; (*i).pFineCA = pDiffList; } } if((v1[k1].bContainsPureComment || v1[k1].whiteLine()) && (v2[k2].bContainsPureComment || v2[k2].whiteLine())) { Q_ASSERT(selector == 1 || selector == 2 || selector == 3); if(selector == 1) { i->bAEqB = true; } else if(selector == 2) { i->bBEqC = true; } else if(selector == 3) { i->bAEqC = true; } } } ++listIdx; pp.step(); } return bTextsTotalEqual; } // Convert the list to a vector of pointers void calcDiff3LineVector(Diff3LineList& d3ll, Diff3LineVector& d3lv) { d3lv.resize(d3ll.size()); Diff3LineList::iterator i; int j = 0; for(i = d3ll.begin(); i != d3ll.end(); ++i, ++j) { d3lv[j] = &(*i); } Q_ASSERT(j == (int)d3lv.size()); } diff --git a/src/diff.h b/src/diff.h index c5345a5..40d245f 100644 --- a/src/diff.h +++ b/src/diff.h @@ -1,431 +1,434 @@ /*************************************************************************** diff.h - description ------------------- begin : Mon Mar 18 2002 copyright : (C) 2002-2007 by Joachim Eibl email : joachim.eibl at gmx.de ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef DIFF_H #define DIFF_H #include #include #include +#include + #include "common.h" #include "fileaccess.h" #include "options.h" #include "gnudiff_diff.h" // Each range with matching elements is followed by a range with differences on either side. // Then again range of matching elements should follow. struct Diff { LineRef nofEquals; qint64 diff1; qint64 diff2; Diff(LineRef eq, qint64 d1, qint64 d2){nofEquals=eq; diff1=d1; diff2=d2; } }; typedef std::list DiffList; struct LineData { const QChar* pLine; const QChar* pFirstNonWhiteChar; int size; LineData(){ pLine=nullptr; pFirstNonWhiteChar=nullptr; size=0; /*occurences=0;*/ bContainsPureComment=false; } int width(int tabSize) const; // Calcs width considering tabs. //int occurences; bool whiteLine() const { return pFirstNonWhiteChar-pLine == size; } bool bContainsPureComment; }; class Diff3LineList; class Diff3LineVector; struct DiffBufferInfo { const LineData* m_pLineDataA; const LineData* m_pLineDataB; const LineData* m_pLineDataC; LineRef m_sizeA; LineRef m_sizeB; LineRef m_sizeC; const Diff3LineList* m_pDiff3LineList; const Diff3LineVector* m_pDiff3LineVector; void init( Diff3LineList* d3ll, const Diff3LineVector* d3lv, const LineData* pldA, LineRef sizeA, const LineData* pldB, LineRef sizeB, const LineData* pldC, LineRef sizeC ); }; struct Diff3Line { LineRef lineA; LineRef lineB; LineRef lineC; bool bAEqC : 1; // These are true if equal or only white-space changes exist. bool bBEqC : 1; bool bAEqB : 1; bool bWhiteLineA : 1; bool bWhiteLineB : 1; bool bWhiteLineC : 1; DiffList* pFineAB; // These are 0 only if completely equal or if either source doesn't exist. DiffList* pFineBC; DiffList* pFineCA; int linesNeededForDisplay; // Due to wordwrap int sumLinesNeededForDisplay; // For fast conversion to m_diff3WrapLineVector DiffBufferInfo* m_pDiffBufferInfo; // For convenience Diff3Line() { lineA=-1; lineB=-1; lineC=-1; bAEqC=false; bAEqB=false; bBEqC=false; pFineAB=nullptr; pFineBC=nullptr; pFineCA=nullptr; linesNeededForDisplay=1; sumLinesNeededForDisplay=0; bWhiteLineA=false; bWhiteLineB=false; bWhiteLineC=false; m_pDiffBufferInfo=nullptr; } ~Diff3Line() { if (pFineAB!=nullptr) delete pFineAB; if (pFineBC!=nullptr) delete pFineBC; if (pFineCA!=nullptr) delete pFineCA; pFineAB=nullptr; pFineBC=nullptr; pFineCA=nullptr; } bool operator==( const Diff3Line& d3l ) const { return lineA == d3l.lineA && lineB == d3l.lineB && lineC == d3l.lineC && bAEqB == d3l.bAEqB && bAEqC == d3l.bAEqC && bBEqC == d3l.bBEqC; } const LineData* getLineData( int src ) const { Q_ASSERT( m_pDiffBufferInfo!=nullptr ); if ( src == 1 && lineA >= 0 ) return &m_pDiffBufferInfo->m_pLineDataA[lineA]; if ( src == 2 && lineB >= 0 ) return &m_pDiffBufferInfo->m_pLineDataB[lineB]; if ( src == 3 && lineC >= 0 ) return &m_pDiffBufferInfo->m_pLineDataC[lineC]; return nullptr; } QString getString( int src ) const { const LineData* pld = getLineData(src); if ( pld ) return QString( pld->pLine, pld->size); else return QString(); } LineRef getLineInFile( int src ) const { if ( src == 1 ) return lineA; if ( src == 2 ) return lineB; if ( src == 3 ) return lineC; return -1; } }; class Diff3LineList : public QLinkedList { }; class Diff3LineVector : public QVector { }; class Diff3WrapLine { public: Diff3Line* pD3L; int diff3LineIndex; int wrapLineOffset; int wrapLineLength; }; typedef QVector Diff3WrapLineVector; class TotalDiffStatus { public: TotalDiffStatus(){ reset(); } void reset() {bBinaryAEqC=false; bBinaryBEqC=false; bBinaryAEqB=false; bTextAEqC=false; bTextBEqC=false; bTextAEqB=false; nofUnsolvedConflicts=0; nofSolvedConflicts=0; nofWhitespaceConflicts=0; } bool bBinaryAEqC : 1; bool bBinaryBEqC : 1; bool bBinaryAEqB : 1; bool bTextAEqC : 1; bool bTextBEqC : 1; bool bTextAEqB : 1; int nofUnsolvedConflicts; int nofSolvedConflicts; int nofWhitespaceConflicts; }; // Three corresponding ranges. (Minimum size of a valid range is one line.) class ManualDiffHelpEntry { public: ManualDiffHelpEntry() { lineA1=-1; lineA2=-1; lineB1=-1; lineB2=-1; lineC1=-1; lineC2=-1; } LineRef lineA1; LineRef lineA2; LineRef lineB1; LineRef lineB2; LineRef lineC1; LineRef lineC2; LineRef& firstLine( int winIdx ) { return winIdx==1 ? lineA1 : (winIdx==2 ? lineB1 : lineC1 ); } LineRef& lastLine( int winIdx ) { return winIdx==1 ? lineA2 : (winIdx==2 ? lineB2 : lineC2 ); } bool isLineInRange( LineRef line, int winIdx ) { return line>=0 && line>=firstLine(winIdx) && line<=lastLine(winIdx); } bool operator==(const ManualDiffHelpEntry& r) const { return lineA1 == r.lineA1 && lineB1 == r.lineB1 && lineC1 == r.lineC1 && lineA2 == r.lineA2 && lineB2 == r.lineB2 && lineC2 == r.lineC2; } }; // A list of corresponding ranges typedef std::list ManualDiffHelpList; void calcDiff3LineListUsingAB( const DiffList* pDiffListAB, Diff3LineList& d3ll ); void calcDiff3LineListUsingAC( const DiffList* pDiffListBC, Diff3LineList& d3ll ); void calcDiff3LineListUsingBC( const DiffList* pDiffListBC, Diff3LineList& d3ll ); void correctManualDiffAlignment( Diff3LineList& d3ll, ManualDiffHelpList* pManualDiffHelpList ); class SourceData { public: SourceData(); ~SourceData(); void setOptions( Options* pOptions ); LineRef getSizeLines() const; qint64 getSizeBytes() const; const char* getBuf() const; const QString& getText() const; const LineData* getLineDataForDisplay() const; const LineData* getLineDataForDiff() const; void setFilename(const QString& filename); void setFileAccess( const FileAccess& fa ); void setEncoding(QTextCodec* pEncoding); //FileAccess& getFileAccess(); QString getFilename(); void setAliasName(const QString& a); QString getAliasName(); bool isEmpty(); // File was set bool hasData(); // Data was readable bool isText(); // is it pure text (vs. binary data) bool isIncompleteConversion(); // true if some replacement characters were found bool isFromBuffer(); // was it set via setData() (vs. setFileAccess() or setFilename()) QStringList setData( const QString& data ); bool isValid(); // Either no file is specified or reading was successful // Returns a list of error messages if anything went wrong QStringList readAndPreprocess(QTextCodec* pEncoding, bool bAutoDetectUnicode ); bool saveNormalDataAs( const QString& fileName ); bool isBinaryEqualWith( const SourceData& other ) const; void reset(); QTextCodec* getEncoding() const { return m_pEncoding; } e_LineEndStyle getLineEndStyle() const { return m_normalData.m_eLineEndStyle; } private: QTextCodec* detectEncoding( const QString& fileName, QTextCodec* pFallbackCodec ); QString m_aliasName; FileAccess m_fileAccess; Options* m_pOptions; QString m_tempInputFileName; + QTemporaryFile m_tempFile;//Created from clipboard content. struct FileData { FileData(){ m_pBuf=nullptr; m_size=0; m_vSize=0; m_bIsText=false; m_eLineEndStyle=eLineEndStyleUndefined; m_bIncompleteConversion=false;} ~FileData(){ reset(); } const char* m_pBuf; qint64 m_size; qint64 m_vSize; // Nr of lines in m_pBuf1 and size of m_v1, m_dv12 and m_dv13 QString m_unicodeBuf; QVector m_v; bool m_bIsText; bool m_bIncompleteConversion; e_LineEndStyle m_eLineEndStyle; bool readFile( const QString& filename ); bool writeFile( const QString& filename ); bool preprocess(bool bPreserveCR, QTextCodec* pEncoding ); void reset(); void removeComments(); void copyBufFrom( const FileData& src ); void checkLineForComments( const QChar* p, // pointer to start of buffer int& i, // index of current position (in, out) int size, // size of buffer bool& bWhite, // false if this line contains nonwhite characters (in, out) bool& bCommentInLine, // true if any comment is within this line (in, out) bool& bStartsOpenComment // true if the line ends within an comment (out) ); }; FileData m_normalData; FileData m_lmppData; QTextCodec* m_pEncoding; }; void calcDiff3LineListTrim( Diff3LineList& d3ll, const LineData* pldA, const LineData* pldB, const LineData* pldC, ManualDiffHelpList* pManualDiffHelpList ); void calcWhiteDiff3Lines( Diff3LineList& d3ll, const LineData* pldA, const LineData* pldB, const LineData* pldC ); void calcDiff3LineVector( Diff3LineList& d3ll, Diff3LineVector& d3lv ); // Helper class that swaps left and right for some commands. class MyPainter : public QPainter { int m_factor; int m_xOffset; int m_fontWidth; public: MyPainter(QPaintDevice* pd, bool bRTL, int width, int fontWidth) : QPainter(pd) { if (bRTL) { m_fontWidth = fontWidth; m_factor = -1; m_xOffset = width-1; } else { m_fontWidth = 0; m_factor = 1; m_xOffset = 0; } } void fillRect( int x, int y, int w, int h, const QBrush& b ) { if (m_factor==1) QPainter::fillRect( m_xOffset + x , y, w, h, b ); else QPainter::fillRect( m_xOffset - x - w, y, w, h, b ); } void drawText( int x, int y, const QString& s, bool bAdapt=false ) { Qt::LayoutDirection ld = (m_factor==1 || bAdapt == false) ? Qt::LeftToRight : Qt::RightToLeft; //QPainter::setLayoutDirection( ld ); if ( ld==Qt::RightToLeft ) // Reverse the text { QString s2; for( int i=s.length()-1; i>=0; --i ) { s2 += s[i]; } QPainter::drawText( m_xOffset-m_fontWidth*s.length() + m_factor*x, y, s2 ); return; } QPainter::drawText( m_xOffset-m_fontWidth*s.length() + m_factor*x, y, s ); } void drawLine( int x1, int y1, int x2, int y2 ) { QPainter::drawLine( m_xOffset + m_factor*x1, y1, m_xOffset + m_factor*x2, y2 ); } }; bool runDiff( const LineData* p1, LineRef size1, const LineData* p2, LineRef size2, DiffList& diffList, int winIdx1, int winIdx2, ManualDiffHelpList *pManualDiffHelpList, Options *pOptions); bool fineDiff( Diff3LineList& diff3LineList, int selector, const LineData* v1, const LineData* v2 ); bool equal( const LineData& l1, const LineData& l2, bool bStrict ); inline bool isWhite( QChar c ) { return c==' ' || c=='\t' || c=='\r'; } /** Returns the number of equivalent spaces at position outPos. */ inline int tabber( int outPos, int tabSize ) { return tabSize - ( outPos % tabSize ); } /** Returns a line number where the linerange [line, line+nofLines] can be displayed best. If it fits into the currently visible range then the returned value is the current firstLine. */ int getBestFirstLine( int line, int nofLines, int firstLine, int visibleLines ); extern bool g_bIgnoreWhiteSpace; extern bool g_bIgnoreTrivialMatches; extern int g_bAutoSolve; // Cursor conversions that consider g_tabSize. int convertToPosInText( const QString& s, int posOnScreen, int tabSize ); int convertToPosOnScreen( const QString& s, int posInText, int tabSize ); enum e_CoordType { eFileCoords, eD3LLineCoords, eWrapCoords }; void calcTokenPos( const QString&, int posOnScreen, int& pos1, int& pos2, int tabSize ); QString calcHistorySortKey( const QString& keyOrder, QRegExp& matchedRegExpr, const QStringList& parenthesesGroupList ); bool findParenthesesGroups( const QString& s, QStringList& sl ); #endif diff --git a/src/directorymergewindow.cpp b/src/directorymergewindow.cpp index b988424..42d3e79 100644 --- a/src/directorymergewindow.cpp +++ b/src/directorymergewindow.cpp @@ -1,3750 +1,3716 @@ /*************************************************************************** directorymergewindow.cpp ----------------- begin : Sat Oct 19 2002 copyright : (C) 2002-2011 by Joachim Eibl email : joachim.eibl at gmx.de ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include #include "directorymergewindow.h" #include "guiutils.h" #include "options.h" #include "progress.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static bool conflictingFileTypes(MergeFileInfos& mfi); static QPixmap getOnePixmap(e_Age eAge, bool bLink, bool bDir); class StatusInfo : public QDialog { QTextEdit* m_pTextEdit; public: explicit StatusInfo(QWidget* pParent) : QDialog(pParent) { QVBoxLayout* pVLayout = new QVBoxLayout(this); m_pTextEdit = new QTextEdit(this); pVLayout->addWidget(m_pTextEdit); setObjectName("StatusInfo"); setWindowFlags(Qt::Dialog); m_pTextEdit->setWordWrapMode(QTextOption::NoWrap); m_pTextEdit->setReadOnly(true); QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Close, this); connect(box, &QDialogButtonBox::rejected, this, &QDialog::accept); pVLayout->addWidget(box); } bool isEmpty() { return m_pTextEdit->toPlainText().isEmpty(); } void addText(const QString& s) { m_pTextEdit->append(s); } void clear() { m_pTextEdit->clear(); } void setVisible(bool bVisible) override { if(bVisible) { m_pTextEdit->moveCursor(QTextCursor::End); m_pTextEdit->moveCursor(QTextCursor::StartOfLine); m_pTextEdit->ensureCursorVisible(); } QDialog::setVisible(bVisible); if(bVisible) setWindowState(windowState() | Qt::WindowMaximized); } }; -class TempRemover -{ - public: - TempRemover(const QString& origName, FileAccess& fa); - ~TempRemover(); - QString name() { return m_name; } - bool success() { return m_bSuccess; } - - private: - QString m_name; - bool m_bTemp; - bool m_bSuccess; -}; -TempRemover::TempRemover(const QString& origName, FileAccess& fa) -{ - if(fa.isLocal()) - { - m_name = origName; - m_bTemp = false; - m_bSuccess = true; - } - else - { - m_name = FileAccess::tempFileName(); - m_bSuccess = fa.copyFile(m_name); - m_bTemp = m_bSuccess; - } -} -TempRemover::~TempRemover() -{ - if(m_bTemp && !m_name.isEmpty()) - FileAccess::removeTempFile(m_name); -} - enum Columns { s_NameCol = 0, s_ACol = 1, s_BCol = 2, s_CCol = 3, s_OpCol = 4, s_OpStatusCol = 5, s_UnsolvedCol = 6, // Nr of unsolved conflicts (for 3 input files) s_SolvedCol = 7, // Nr of auto-solvable conflicts (for 3 input files) s_NonWhiteCol = 8, // Nr of nonwhite deltas (for 2 input files) s_WhiteCol = 9 // Nr of white deltas (for 2 input files) }; enum e_OperationStatus { eOpStatusNone, eOpStatusDone, eOpStatusError, eOpStatusSkipped, eOpStatusNotSaved, eOpStatusInProgress, eOpStatusToDo }; class MergeFileInfos { public: MergeFileInfos() { m_bEqualAB = false; m_bEqualAC = false; m_bEqualBC = false; m_pParent = nullptr; m_bOperationComplete = false; m_bSimOpComplete = false; m_eMergeOperation = eNoOperation; m_eOpStatus = eOpStatusNone; m_ageA = eNotThere; m_ageB = eNotThere; m_ageC = eNotThere; m_bConflictingAges = false; m_pFileInfoA = nullptr; m_pFileInfoB = nullptr; m_pFileInfoC = nullptr; } ~MergeFileInfos() { //for( int i=0; i( const MergeFileInfos& ); QString subPath() const { return m_pFileInfoA && m_pFileInfoA->exists() ? m_pFileInfoA->filePath() : m_pFileInfoB && m_pFileInfoB->exists() ? m_pFileInfoB->filePath() : m_pFileInfoC && m_pFileInfoC->exists() ? m_pFileInfoC->filePath() : QString(""); } QString fileName() const { return m_pFileInfoA && m_pFileInfoA->exists() ? m_pFileInfoA->fileName() : m_pFileInfoB && m_pFileInfoB->exists() ? m_pFileInfoB->fileName() : m_pFileInfoC && m_pFileInfoC->exists() ? m_pFileInfoC->fileName() : QString(""); } bool dirA() const { return m_pFileInfoA ? m_pFileInfoA->isDir() : false; } bool dirB() const { return m_pFileInfoB ? m_pFileInfoB->isDir() : false; } bool dirC() const { return m_pFileInfoC ? m_pFileInfoC->isDir() : false; } bool isLinkA() const { return m_pFileInfoA ? m_pFileInfoA->isSymLink() : false; } bool isLinkB() const { return m_pFileInfoB ? m_pFileInfoB->isSymLink() : false; } bool isLinkC() const { return m_pFileInfoC ? m_pFileInfoC->isSymLink() : false; } bool existsInA() const { return m_pFileInfoA != nullptr; } bool existsInB() const { return m_pFileInfoB != nullptr; } bool existsInC() const { return m_pFileInfoC != nullptr; } MergeFileInfos* m_pParent; FileAccess* m_pFileInfoA; FileAccess* m_pFileInfoB; FileAccess* m_pFileInfoC; TotalDiffStatus m_totalDiffStatus; QList m_children; e_MergeOperation m_eMergeOperation : 5; e_OperationStatus m_eOpStatus : 4; e_Age m_ageA : 3; e_Age m_ageB : 3; e_Age m_ageC : 3; bool m_bOperationComplete : 1; bool m_bSimOpComplete : 1; bool m_bEqualAB : 1; bool m_bEqualAC : 1; bool m_bEqualBC : 1; bool m_bConflictingAges : 1; // Equal age but files are not! }; static Qt::CaseSensitivity s_eCaseSensitivity = Qt::CaseSensitive; class DirectoryMergeWindow::Data : public QAbstractItemModel { public: DirectoryMergeWindow* q; explicit Data(DirectoryMergeWindow* pDMW) { q = pDMW; m_pOptions = nullptr; m_pIconLoader = nullptr; m_pDirectoryMergeInfo = nullptr; m_bSimulatedMergeStarted = false; m_bRealMergeStarted = false; m_bError = false; m_bSyncMode = false; m_pStatusInfo = new StatusInfo(q); m_pStatusInfo->hide(); m_bScanning = false; m_bCaseSensitive = true; m_bUnfoldSubdirs = false; m_bSkipDirStatus = false; m_pRoot = new MergeFileInfos; } ~Data() override { delete m_pRoot; } // Implement QAbstractItemModel QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; //Qt::ItemFlags flags ( const QModelIndex & index ) const QModelIndex parent(const QModelIndex& index) const override { MergeFileInfos* pMFI = getMFI(index); if(pMFI == nullptr || pMFI == m_pRoot || pMFI->m_pParent == m_pRoot) return QModelIndex(); else { MergeFileInfos* pParentsParent = pMFI->m_pParent->m_pParent; return createIndex(pParentsParent->m_children.indexOf(pMFI->m_pParent), 0, pMFI->m_pParent); } } int rowCount(const QModelIndex& parent = QModelIndex()) const override { MergeFileInfos* pParentMFI = getMFI(parent); if(pParentMFI != nullptr) return pParentMFI->m_children.count(); else return m_pRoot->m_children.count(); } int columnCount(const QModelIndex& /*parent*/) const override { return 10; } QModelIndex index(int row, int column, const QModelIndex& parent) const override { MergeFileInfos* pParentMFI = getMFI(parent); if(pParentMFI == nullptr && row < m_pRoot->m_children.count()) return createIndex(row, column, m_pRoot->m_children[row]); else if(pParentMFI != nullptr && row < pParentMFI->m_children.count()) return createIndex(row, column, pParentMFI->m_children[row]); else return QModelIndex(); } QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; void sort(int column, Qt::SortOrder order) override; // private data and helper methods MergeFileInfos* getMFI(const QModelIndex& mi) const { if(mi.isValid()) return (MergeFileInfos*)mi.internalPointer(); else return nullptr; } MergeFileInfos* m_pRoot; QString fullNameA(const MergeFileInfos& mfi) { return mfi.existsInA() ? mfi.m_pFileInfoA->absoluteFilePath() : m_dirA.absoluteFilePath() + "/" + mfi.subPath(); } QString fullNameB(const MergeFileInfos& mfi) { return mfi.existsInB() ? mfi.m_pFileInfoB->absoluteFilePath() : m_dirB.absoluteFilePath() + "/" + mfi.subPath(); } QString fullNameC(const MergeFileInfos& mfi) { return mfi.existsInC() ? mfi.m_pFileInfoC->absoluteFilePath() : m_dirC.absoluteFilePath() + "/" + mfi.subPath(); } QString fullNameDest(const MergeFileInfos& mfi) { if(m_dirDestInternal.prettyAbsPath() == m_dirC.prettyAbsPath()) return fullNameC(mfi); else if(m_dirDestInternal.prettyAbsPath() == m_dirB.prettyAbsPath()) return fullNameB(mfi); else return m_dirDestInternal.absoluteFilePath() + "/" + mfi.subPath(); } FileAccess m_dirA; FileAccess m_dirB; FileAccess m_dirC; FileAccess m_dirDest; FileAccess m_dirDestInternal; Options* m_pOptions; void calcDirStatus(bool bThreeDirs, const QModelIndex& mi, int& nofFiles, int& nofDirs, int& nofEqualFiles, int& nofManualMerges); void mergeContinue(bool bStart, bool bVerbose); void prepareListView(ProgressProxy& pp); void calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation eDefaultOperation); void setAllMergeOperations(e_MergeOperation eDefaultOperation); friend class MergeFileInfos; bool canContinue(); QModelIndex treeIterator(QModelIndex mi, bool bVisitChildren = true, bool bFindInvisible = false); void prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose); bool executeMergeOperation(MergeFileInfos& mfi, bool& bSingleFileMerge); void scanDirectory(const QString& dirName, t_DirectoryList& dirList); void scanLocalDirectory(const QString& dirName, t_DirectoryList& dirList); bool fastFileComparison(FileAccess& fi1, FileAccess& fi2, bool& bError, QString& status); void compareFilesAndCalcAges(MergeFileInfos& mfi); void setMergeOperation(const QModelIndex& mi, e_MergeOperation eMergeOp, bool bRecursive = true); bool isDir(const QModelIndex& mi); QString getFileName(const QModelIndex& mi); bool copyFLD(const QString& srcName, const QString& destName); bool deleteFLD(const QString& name, bool bCreateBackup); bool makeDir(const QString& name, bool bQuiet = false); bool renameFLD(const QString& srcName, const QString& destName); bool mergeFLD(const QString& nameA, const QString& nameB, const QString& nameC, const QString& nameDest, bool& bSingleFileMerge); t_DirectoryList m_dirListA; t_DirectoryList m_dirListB; t_DirectoryList m_dirListC; QString m_dirMergeStateFilename; void buildMergeMap(void); class FileKey { public: const FileAccess* m_pFA; explicit FileKey(const FileAccess& fa) : m_pFA(&fa) {} int getParents(const FileAccess* pFA, const FileAccess* v[]) const { int s = 0; for(s = 0; pFA->parent() != nullptr; pFA = pFA->parent(), ++s) v[s] = pFA; return s; } // This is essentially the same as // int r = filePath().compare( fa.filePath() ) // if ( r<0 ) return true; // if ( r==0 ) return m_col < fa.m_col; // return false; bool operator<(const FileKey& fk) const { const FileAccess* v1[100]; const FileAccess* v2[100]; int v1Size = getParents(m_pFA, v1); int v2Size = getParents(fk.m_pFA, v2); for(int i = 0; i < v1Size && i < v2Size; ++i) { int r = v1[v1Size - i - 1]->fileName().compare(v2[v2Size - i - 1]->fileName(), s_eCaseSensitivity); if(r < 0) return true; else if(r > 0) return false; } if(v1Size < v2Size) return true; return false; } }; typedef QMap t_fileMergeMap; t_fileMergeMap m_fileMergeMap; bool m_bFollowDirLinks; bool m_bFollowFileLinks; bool m_bSimulatedMergeStarted; bool m_bRealMergeStarted; bool m_bError; bool m_bSyncMode; bool m_bDirectoryMerge; // if true, then merge is the default operation, otherwise it's diff. bool m_bCaseSensitive; bool m_bUnfoldSubdirs; bool m_bSkipDirStatus; bool m_bScanning; // true while in init() KIconLoader* m_pIconLoader; DirectoryMergeInfo* m_pDirectoryMergeInfo; StatusInfo* m_pStatusInfo; typedef std::list MergeItemList; // linked list MergeItemList m_mergeItemList; MergeItemList::iterator m_currentIndexForOperation; QModelIndex m_selection1Index; QModelIndex m_selection2Index; QModelIndex m_selection3Index; void selectItemAndColumn(const QModelIndex& mi, bool bContextMenu); friend class DirMergeItem; QAction* m_pDirStartOperation; QAction* m_pDirRunOperationForCurrentItem; QAction* m_pDirCompareCurrent; QAction* m_pDirMergeCurrent; QAction* m_pDirRescan; QAction* m_pDirChooseAEverywhere; QAction* m_pDirChooseBEverywhere; QAction* m_pDirChooseCEverywhere; QAction* m_pDirAutoChoiceEverywhere; QAction* m_pDirDoNothingEverywhere; QAction* m_pDirFoldAll; QAction* m_pDirUnfoldAll; KToggleAction* m_pDirShowIdenticalFiles; KToggleAction* m_pDirShowDifferentFiles; KToggleAction* m_pDirShowFilesOnlyInA; KToggleAction* m_pDirShowFilesOnlyInB; KToggleAction* m_pDirShowFilesOnlyInC; KToggleAction* m_pDirSynchronizeDirectories; KToggleAction* m_pDirChooseNewerFiles; QAction* m_pDirCompareExplicit; QAction* m_pDirMergeExplicit; QAction* m_pDirCurrentDoNothing; QAction* m_pDirCurrentChooseA; QAction* m_pDirCurrentChooseB; QAction* m_pDirCurrentChooseC; QAction* m_pDirCurrentMerge; QAction* m_pDirCurrentDelete; QAction* m_pDirCurrentSyncDoNothing; QAction* m_pDirCurrentSyncCopyAToB; QAction* m_pDirCurrentSyncCopyBToA; QAction* m_pDirCurrentSyncDeleteA; QAction* m_pDirCurrentSyncDeleteB; QAction* m_pDirCurrentSyncDeleteAAndB; QAction* m_pDirCurrentSyncMergeToA; QAction* m_pDirCurrentSyncMergeToB; QAction* m_pDirCurrentSyncMergeToAAndB; QAction* m_pDirSaveMergeState; QAction* m_pDirLoadMergeState; bool init(FileAccess& dirA, FileAccess& dirB, FileAccess& dirC, FileAccess& dirDest, bool bDirectoryMerge, bool bReload); void setOpStatus(const QModelIndex& mi, e_OperationStatus eOpStatus) { if(MergeFileInfos* pMFI = getMFI(mi)) { pMFI->m_eOpStatus = eOpStatus; emit dataChanged(mi, mi); } } }; QVariant DirectoryMergeWindow::Data::data(const QModelIndex& index, int role) const { MergeFileInfos* pMFI = getMFI(index); if(pMFI) { if(role == Qt::DisplayRole) { switch(index.column()) { case s_NameCol: return QFileInfo(pMFI->subPath()).fileName(); case s_ACol: return i18n("A"); case s_BCol: return i18n("B"); case s_CCol: return i18n("C"); //case s_OpCol: return i18n("Operation"); //case s_OpStatusCol: return i18n("Status"); case s_UnsolvedCol: return i18n("Unsolved"); case s_SolvedCol: return i18n("Solved"); case s_NonWhiteCol: return i18n("Nonwhite"); case s_WhiteCol: return i18n("White"); //default : return QVariant(); } if(s_OpCol == index.column()) { bool bDir = pMFI->dirA() || pMFI->dirB() || pMFI->dirC(); switch(pMFI->m_eMergeOperation) { case eNoOperation: return ""; break; case eCopyAToB: return i18n("Copy A to B"); break; case eCopyBToA: return i18n("Copy B to A"); break; case eDeleteA: return i18n("Delete A"); break; case eDeleteB: return i18n("Delete B"); break; case eDeleteAB: return i18n("Delete A & B"); break; case eMergeToA: return i18n("Merge to A"); break; case eMergeToB: return i18n("Merge to B"); break; case eMergeToAB: return i18n("Merge to A & B"); break; case eCopyAToDest: return i18n("A"); break; case eCopyBToDest: return i18n("B"); break; case eCopyCToDest: return i18n("C"); break; case eDeleteFromDest: return i18n("Delete (if exists)"); break; case eMergeABCToDest: return bDir ? i18n("Merge") : i18n("Merge (manual)"); break; case eMergeABToDest: return bDir ? i18n("Merge") : i18n("Merge (manual)"); break; case eConflictingFileTypes: return i18n("Error: Conflicting File Types"); break; case eChangedAndDeleted: return i18n("Error: Changed and Deleted"); break; case eConflictingAges: return i18n("Error: Dates are equal but files are not."); break; default: Q_ASSERT(true); break; } } if(s_OpStatusCol == index.column()) { switch(pMFI->m_eOpStatus) { case eOpStatusNone: return ""; case eOpStatusDone: return i18n("Done"); case eOpStatusError: return i18n("Error"); case eOpStatusSkipped: return i18n("Skipped."); case eOpStatusNotSaved: return i18n("Not saved."); case eOpStatusInProgress: return i18n("In progress..."); case eOpStatusToDo: return i18n("To do."); } } } else if(role == Qt::DecorationRole) { if(s_NameCol == index.column()) { return getOnePixmap(eAgeEnd, pMFI->isLinkA() || pMFI->isLinkB() || pMFI->isLinkC(), pMFI->dirA() || pMFI->dirB() || pMFI->dirC()); } if(s_ACol == index.column()) { return getOnePixmap(pMFI->m_ageA, pMFI->isLinkA(), pMFI->dirA()); } if(s_BCol == index.column()) { return getOnePixmap(pMFI->m_ageB, pMFI->isLinkB(), pMFI->dirB()); } if(s_CCol == index.column()) { return getOnePixmap(pMFI->m_ageC, pMFI->isLinkC(), pMFI->dirC()); } } else if(role == Qt::TextAlignmentRole) { if(s_UnsolvedCol == index.column() || s_SolvedCol == index.column() || s_NonWhiteCol == index.column() || s_WhiteCol == index.column()) return Qt::AlignRight; } } return QVariant(); } QVariant DirectoryMergeWindow::Data::headerData(int section, Qt::Orientation orientation, int role) const { if(orientation == Qt::Horizontal && section >= 0 && section < columnCount(QModelIndex()) && role == Qt::DisplayRole) { switch(section) { case s_NameCol: return i18n("Name"); case s_ACol: return i18n("A"); case s_BCol: return i18n("B"); case s_CCol: return i18n("C"); case s_OpCol: return i18n("Operation"); case s_OpStatusCol: return i18n("Status"); case s_UnsolvedCol: return i18n("Unsolved"); case s_SolvedCol: return i18n("Solved"); case s_NonWhiteCol: return i18n("Nonwhite"); case s_WhiteCol: return i18n("White"); default: return QVariant(); } } return QVariant(); } // Previously Q3ListViewItem::paintCell(p,cg,column,width,align); class DirectoryMergeWindow::DirMergeItemDelegate : public QStyledItemDelegate { DirectoryMergeWindow* m_pDMW; DirectoryMergeWindow::Data* d; public: explicit DirMergeItemDelegate(DirectoryMergeWindow* pParent) : QStyledItemDelegate(pParent), m_pDMW(pParent), d(pParent->d) { } void paint(QPainter* p, const QStyleOptionViewItem& option, const QModelIndex& index) const override { int column = index.column(); if(column == s_ACol || column == s_BCol || column == s_CCol) { QVariant value = index.data(Qt::DecorationRole); QPixmap icon; if(value.isValid()) { if(value.type() == QVariant::Icon) { icon = qvariant_cast(value).pixmap(16, 16); //icon = qvariant_cast(value); //decorationRect = QRect(QPoint(0, 0), icon.actualSize(option.decorationSize, iconMode, iconState)); } else { icon = qvariant_cast(value); //decorationRect = QRect(QPoint(0, 0), option.decorationSize).intersected(pixmap.rect()); } } int x = option.rect.left(); int y = option.rect.top(); //QPixmap icon = value.value(); //pixmap(column); if(!icon.isNull()) { int yOffset = (sizeHint(option, index).height() - icon.height()) / 2; p->drawPixmap(x + 2, y + yOffset, icon); int i = index == d->m_selection1Index ? 1 : index == d->m_selection2Index ? 2 : index == d->m_selection3Index ? 3 : 0; if(i != 0) { Options* pOpts = d->m_pOptions; QColor c(i == 1 ? pOpts->m_colorA : i == 2 ? pOpts->m_colorB : pOpts->m_colorC); p->setPen(c); // highlight() ); p->drawRect(x + 2, y + yOffset, icon.width(), icon.height()); p->setPen(QPen(c, 0, Qt::DotLine)); p->drawRect(x + 1, y + yOffset - 1, icon.width() + 2, icon.height() + 2); p->setPen(Qt::white); QString s(QChar('A' + i - 1)); p->drawText(x + 2 + (icon.width() - p->fontMetrics().width(s)) / 2, y + yOffset + (icon.height() + p->fontMetrics().ascent()) / 2 - 1, s); } else { p->setPen(m_pDMW->palette().background().color()); p->drawRect(x + 1, y + yOffset - 1, icon.width() + 2, icon.height() + 2); } return; } } QStyleOptionViewItem option2 = option; if(column >= s_UnsolvedCol) { option2.displayAlignment = Qt::AlignRight; } QStyledItemDelegate::paint(p, option2, index); } QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override { QSize sz = QStyledItemDelegate::sizeHint(option, index); return sz.expandedTo(QSize(0, 18)); } }; DirectoryMergeWindow::DirectoryMergeWindow(QWidget* pParent, Options* pOptions, KIconLoader* pIconLoader) : QTreeView(pParent) { d = new Data(this); setModel(d); setItemDelegate(new DirMergeItemDelegate(this)); connect(this, &DirectoryMergeWindow::doubleClicked, this, &DirectoryMergeWindow::onDoubleClick); connect(this, &DirectoryMergeWindow::expanded, this, &DirectoryMergeWindow::onExpanded); d->m_pOptions = pOptions; d->m_pIconLoader = pIconLoader; setSortingEnabled(true); } DirectoryMergeWindow::~DirectoryMergeWindow() { delete d; } QString DirectoryMergeWindow::getDirNameA() { return d->m_dirA.prettyAbsPath(); } QString DirectoryMergeWindow::getDirNameB() { return d->m_dirB.prettyAbsPath(); } QString DirectoryMergeWindow::getDirNameC() { return d->m_dirC.prettyAbsPath(); } QString DirectoryMergeWindow::getDirNameDest() { return d->m_dirDest.prettyAbsPath(); } void DirectoryMergeWindow::setDirectoryMergeInfo(DirectoryMergeInfo* p) { d->m_pDirectoryMergeInfo = p; } bool DirectoryMergeWindow::isDirectoryMergeInProgress() { return d->m_bRealMergeStarted; } bool DirectoryMergeWindow::isSyncMode() { return d->m_bSyncMode; } bool DirectoryMergeWindow::isScanning() { return d->m_bScanning; } bool DirectoryMergeWindow::Data::fastFileComparison( FileAccess& fi1, FileAccess& fi2, bool& bError, QString& status) { ProgressProxy pp; status = ""; bool bEqual = false; bError = true; if(!m_bFollowFileLinks) { if(fi1.isSymLink() != fi2.isSymLink()) { status = i18n("Mix of links and normal files."); return bEqual; } else if(fi1.isSymLink() && fi2.isSymLink()) { bError = false; bEqual = fi1.readLink() == fi2.readLink(); status = i18n("Link: "); return bEqual; } } if(fi1.size() != fi2.size()) { bEqual = false; status = i18n("Size. "); return bEqual; } else if(m_pOptions->m_bDmTrustSize) { bEqual = true; return bEqual; } if(m_pOptions->m_bDmTrustDate) { bEqual = (fi1.lastModified() == fi2.lastModified() && fi1.size() == fi2.size()); bError = false; status = i18n("Date & Size: "); return bEqual; } if(m_pOptions->m_bDmTrustDateFallbackToBinary) { bEqual = (fi1.lastModified() == fi2.lastModified() && fi1.size() == fi2.size()); if(bEqual) { bError = false; status = i18n("Date & Size: "); return bEqual; } } QString fileName1 = fi1.absoluteFilePath(); QString fileName2 = fi2.absoluteFilePath(); - TempRemover tr1(fileName1, fi1); - if(!tr1.success()) + + if(!fi2.createLocalCopy()) { status = i18n("Creating temp copy of %1 failed.", fileName1); return bEqual; } - TempRemover tr2(fileName2, fi2); - if(!tr2.success()) + + if(!fi2.createLocalCopy()) { status = i18n("Creating temp copy of %1 failed.", fileName2); return bEqual; } std::vector buf1(100000); std::vector buf2(buf1.size()); - QFile file1(tr1.name()); + QFile file1(fi1.fileName()); if(!file1.open(QIODevice::ReadOnly)) { status = i18n("Opening %1 failed.", fileName1); return bEqual; } - QFile file2(tr2.name()); + QFile file2(fi2.fileName()); if(!file2.open(QIODevice::ReadOnly)) { status = i18n("Opening %1 failed.", fileName2); return bEqual; } pp.setInformation(i18n("Comparing file..."), 0, false); typedef qint64 t_FileSize; t_FileSize fullSize = file1.size(); t_FileSize sizeLeft = fullSize; pp.setMaxNofSteps(fullSize / buf1.size()); while(sizeLeft > 0 && !pp.wasCancelled()) { qint64 len = std::min(sizeLeft, (t_FileSize)buf1.size()); if(len != file1.read(&buf1[0], len)) { status = i18n("Error reading from %1", fileName1); return bEqual; } if(len != file2.read(&buf2[0], len)) { status = i18n("Error reading from %1", fileName2); return bEqual; } if(memcmp(&buf1[0], &buf2[0], len) != 0) { bError = false; return bEqual; } sizeLeft -= len; //pp.setCurrent(double(fullSize-sizeLeft)/fullSize, false ); pp.step(); } // If the program really arrives here, then the files are really equal. bError = false; bEqual = true; return bEqual; } int DirectoryMergeWindow::totalColumnWidth() { int w = 0; for(int i = 0; i < s_OpStatusCol; ++i) { w += columnWidth(i); } return w; } void DirectoryMergeWindow::reload() { if(isDirectoryMergeInProgress()) { int result = KMessageBox::warningYesNo(this, i18n("You are currently doing a directory merge. Are you sure, you want to abort the merge and rescan the directory?"), i18n("Warning"), KGuiItem(i18n("Rescan")), KGuiItem(i18n("Continue Merging"))); if(result != KMessageBox::Yes) return; } init(d->m_dirA, d->m_dirB, d->m_dirC, d->m_dirDest, d->m_bDirectoryMerge, true); //fix file visibilities after reload or menu will be out of sync with display if changed from defaults. updateFileVisibilities(); } // Copy pm2 onto pm1, but preserve the alpha value from pm1 where pm2 is transparent. static QPixmap pixCombiner(const QPixmap* pm1, const QPixmap* pm2) { QImage img1 = pm1->toImage().convertToFormat(QImage::Format_ARGB32); QImage img2 = pm2->toImage().convertToFormat(QImage::Format_ARGB32); for(int y = 0; y < img1.height(); y++) { quint32* line1 = reinterpret_cast(img1.scanLine(y)); quint32* line2 = reinterpret_cast(img2.scanLine(y)); for(int x = 0; x < img1.width(); x++) { if(qAlpha(line2[x]) > 0) line1[x] = (line2[x] | 0xff000000); } } return QPixmap::fromImage(img1); } // like pixCombiner but let the pm1 color shine through static QPixmap pixCombiner2(const QPixmap* pm1, const QPixmap* pm2) { QPixmap pix = *pm1; QPainter p(&pix); p.setOpacity(0.5); p.drawPixmap(0, 0, *pm2); p.end(); return pix; } void DirectoryMergeWindow::Data::calcDirStatus(bool bThreeDirs, const QModelIndex& mi, int& nofFiles, int& nofDirs, int& nofEqualFiles, int& nofManualMerges) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI->dirA() || pMFI->dirB() || pMFI->dirC()) { ++nofDirs; } else { ++nofFiles; if(pMFI->m_bEqualAB && (!bThreeDirs || pMFI->m_bEqualAC)) { ++nofEqualFiles; } else { if(pMFI->m_eMergeOperation == eMergeABCToDest || pMFI->m_eMergeOperation == eMergeABToDest) ++nofManualMerges; } } for(int childIdx = 0; childIdx < rowCount(mi); ++childIdx) calcDirStatus(bThreeDirs, index(childIdx, 0, mi), nofFiles, nofDirs, nofEqualFiles, nofManualMerges); } struct t_ItemInfo { bool bExpanded; bool bOperationComplete; QString status; e_MergeOperation eMergeOperation; }; bool DirectoryMergeWindow::init( FileAccess& dirA, FileAccess& dirB, FileAccess& dirC, FileAccess& dirDest, bool bDirectoryMerge, bool bReload) { return d->init(dirA, dirB, dirC, dirDest, bDirectoryMerge, bReload); } void DirectoryMergeWindow::Data::buildMergeMap(void) { t_DirectoryList::iterator dirIterator; if(m_dirA.isValid()) { for(dirIterator = m_dirListA.begin(); dirIterator != m_dirListA.end(); ++dirIterator) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)]; mfi.m_pFileInfoA = &(*dirIterator); } } if(m_dirB.isValid()) { for(dirIterator = m_dirListB.begin(); dirIterator != m_dirListB.end(); ++dirIterator) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)]; mfi.m_pFileInfoB = &(*dirIterator); } } if(m_dirC.isValid()) { for(dirIterator = m_dirListC.begin(); dirIterator != m_dirListC.end(); ++dirIterator) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)]; mfi.m_pFileInfoC = &(*dirIterator); } } } bool DirectoryMergeWindow::Data::init( FileAccess& dirA, FileAccess& dirB, FileAccess& dirC, FileAccess& dirDest, bool bDirectoryMerge, bool bReload) { if(m_pOptions->m_bDmFullAnalysis) { // A full analysis uses the same resources that a normal text-diff/merge uses. // So make sure that the user saves his data first. bool bCanContinue = false; emit q->checkIfCanContinue(&bCanContinue); if(!bCanContinue) return false; emit q->startDiffMerge("", "", "", "", "", "", "", nullptr); // hide main window } q->show(); q->setUpdatesEnabled(true); std::map expandedDirsMap; if(bReload) { // Remember expanded items TODO //QTreeWidgetItemIterator it( this ); //while ( *it ) //{ // DirMergeItem* pDMI = static_cast( *it ); // t_ItemInfo& ii = expandedDirsMap[ pDMI->m_pMFI->subPath() ]; // ii.bExpanded = pDMI->isExpanded(); // ii.bOperationComplete = pDMI->m_pMFI->m_bOperationComplete; // ii.status = pDMI->text( s_OpStatusCol ); // ii.eMergeOperation = pDMI->m_pMFI->m_eMergeOperation; // ++it; //} } ProgressProxy pp; m_bFollowDirLinks = m_pOptions->m_bDmFollowDirLinks; m_bFollowFileLinks = m_pOptions->m_bDmFollowFileLinks; m_bSimulatedMergeStarted = false; m_bRealMergeStarted = false; m_bError = false; m_bDirectoryMerge = bDirectoryMerge; m_selection1Index = QModelIndex(); m_selection2Index = QModelIndex(); m_selection3Index = QModelIndex(); m_bCaseSensitive = m_pOptions->m_bDmCaseSensitiveFilenameComparison; m_bUnfoldSubdirs = m_pOptions->m_bDmUnfoldSubdirs; m_bSkipDirStatus = m_pOptions->m_bDmSkipDirStatus; beginResetModel(); m_pRoot->m_children.clear(); m_mergeItemList.clear(); endResetModel(); m_currentIndexForOperation = m_mergeItemList.end(); m_dirA = dirA; m_dirB = dirB; m_dirC = dirC; m_dirDest = dirDest; if(!bReload) { m_pDirShowIdenticalFiles->setChecked(true); m_pDirShowDifferentFiles->setChecked(true); m_pDirShowFilesOnlyInA->setChecked(true); m_pDirShowFilesOnlyInB->setChecked(true); m_pDirShowFilesOnlyInC->setChecked(true); } // Check if all input directories exist and are valid. The dest dir is not tested now. // The test will happen only when we are going to write to it. if(!m_dirA.isDir() || !m_dirB.isDir() || (m_dirC.isValid() && !m_dirC.isDir())) { QString text(i18n("Opening of directories failed:")); text += "\n\n"; if(!dirA.isDir()) { text += i18n("Dir A \"%1\" does not exist or is not a directory.\n", m_dirA.prettyAbsPath()); } if(!dirB.isDir()) { text += i18n("Dir B \"%1\" does not exist or is not a directory.\n", m_dirB.prettyAbsPath()); } if(m_dirC.isValid() && !m_dirC.isDir()) { text += i18n("Dir C \"%1\" does not exist or is not a directory.\n", m_dirC.prettyAbsPath()); } KMessageBox::sorry(q, text, i18n("Directory Open Error")); return false; } if(m_dirC.isValid() && (m_dirDest.prettyAbsPath() == m_dirA.prettyAbsPath() || m_dirDest.prettyAbsPath() == m_dirB.prettyAbsPath())) { KMessageBox::error(q, i18n("The destination directory must not be the same as A or B when " "three directories are merged.\nCheck again before continuing."), i18n("Parameter Warning")); return false; } m_bScanning = true; emit q->statusBarMessage(i18n("Scanning directories...")); m_bSyncMode = m_pOptions->m_bDmSyncMode && !m_dirC.isValid() && !m_dirDest.isValid(); if(m_dirDest.isValid()) m_dirDestInternal = m_dirDest; else m_dirDestInternal = m_dirC.isValid() ? m_dirC : m_dirB; QString origCurrentDirectory = QDir::currentPath(); m_fileMergeMap.clear(); s_eCaseSensitivity = m_bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; // calc how many directories will be read: double nofScans = (m_dirA.isValid() ? 1 : 0) + (m_dirB.isValid() ? 1 : 0) + (m_dirC.isValid() ? 1 : 0); int currentScan = 0; //TODO setColumnWidthMode(s_UnsolvedCol, Q3ListView::Manual); // setColumnWidthMode(s_SolvedCol, Q3ListView::Manual); // setColumnWidthMode(s_WhiteCol, Q3ListView::Manual); // setColumnWidthMode(s_NonWhiteCol, Q3ListView::Manual); q->setColumnHidden(s_CCol, !m_dirC.isValid()); q->setColumnHidden(s_WhiteCol, !m_pOptions->m_bDmFullAnalysis); q->setColumnHidden(s_NonWhiteCol, !m_pOptions->m_bDmFullAnalysis); q->setColumnHidden(s_UnsolvedCol, !m_pOptions->m_bDmFullAnalysis); q->setColumnHidden(s_SolvedCol, !(m_pOptions->m_bDmFullAnalysis && m_dirC.isValid())); bool bListDirSuccessA = true; bool bListDirSuccessB = true; bool bListDirSuccessC = true; m_dirListA.clear(); m_dirListB.clear(); m_dirListC.clear(); if(m_dirA.isValid()) { pp.setInformation(i18n("Reading Directory A")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessA = m_dirA.listDir(&m_dirListA, m_pOptions->m_bDmRecursiveDirs, m_pOptions->m_bDmFindHidden, m_pOptions->m_DmFilePattern, m_pOptions->m_DmFileAntiPattern, m_pOptions->m_DmDirAntiPattern, m_pOptions->m_bDmFollowDirLinks, m_pOptions->m_bDmUseCvsIgnore); } if(m_dirB.isValid()) { pp.setInformation(i18n("Reading Directory B")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessB = m_dirB.listDir(&m_dirListB, m_pOptions->m_bDmRecursiveDirs, m_pOptions->m_bDmFindHidden, m_pOptions->m_DmFilePattern, m_pOptions->m_DmFileAntiPattern, m_pOptions->m_DmDirAntiPattern, m_pOptions->m_bDmFollowDirLinks, m_pOptions->m_bDmUseCvsIgnore); } e_MergeOperation eDefaultMergeOp; if(m_dirC.isValid()) { pp.setInformation(i18n("Reading Directory C")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessC = m_dirC.listDir(&m_dirListC, m_pOptions->m_bDmRecursiveDirs, m_pOptions->m_bDmFindHidden, m_pOptions->m_DmFilePattern, m_pOptions->m_DmFileAntiPattern, m_pOptions->m_DmDirAntiPattern, m_pOptions->m_bDmFollowDirLinks, m_pOptions->m_bDmUseCvsIgnore); eDefaultMergeOp = eMergeABCToDest; } else eDefaultMergeOp = m_bSyncMode ? eMergeToAB : eMergeABToDest; buildMergeMap(); bool bContinue = true; if(!bListDirSuccessA || !bListDirSuccessB || !bListDirSuccessC) { QString s = i18n("Some subdirectories were not readable in"); if(!bListDirSuccessA) s += "\nA: " + m_dirA.prettyAbsPath(); if(!bListDirSuccessB) s += "\nB: " + m_dirB.prettyAbsPath(); if(!bListDirSuccessC) s += "\nC: " + m_dirC.prettyAbsPath(); s += "\n"; s += i18n("Check the permissions of the subdirectories."); bContinue = KMessageBox::Continue == KMessageBox::warningContinueCancel(q, s); } if(bContinue) { prepareListView(pp); q->updateFileVisibilities(); for(int childIdx = 0; childIdx < rowCount(); ++childIdx) { QModelIndex mi = index(childIdx, 0, QModelIndex()); calcSuggestedOperation(mi, eDefaultMergeOp); } } q->sortByColumn(0, Qt::AscendingOrder); for(int column = 0; column < columnCount(QModelIndex()); ++column) q->resizeColumnToContents(column); // Try to improve the view a little bit. QWidget* pParent = q->parentWidget(); QSplitter* pSplitter = static_cast(pParent); if(pSplitter != nullptr) { QList sizes = pSplitter->sizes(); int total = sizes[0] + sizes[1]; if(total < 10) total = 100; sizes[0] = total * 6 / 10; sizes[1] = total - sizes[0]; pSplitter->setSizes(sizes); } QDir::setCurrent(origCurrentDirectory); m_bScanning = false; emit q->statusBarMessage(i18n("Ready.")); if(bContinue && !m_bSkipDirStatus) { // Generate a status report int nofFiles = 0; int nofDirs = 0; int nofEqualFiles = 0; int nofManualMerges = 0; //TODO for(int childIdx = 0; childIdx < rowCount(); ++childIdx) calcDirStatus(m_dirC.isValid(), index(childIdx, 0, QModelIndex()), nofFiles, nofDirs, nofEqualFiles, nofManualMerges); QString s; s = i18n("Directory Comparison Status\n\n" "Number of subdirectories: %1\n" "Number of equal files: %2\n" "Number of different files: %3", nofDirs, nofEqualFiles, nofFiles - nofEqualFiles); if(m_dirC.isValid()) s += "\n" + i18n("Number of manual merges: %1", nofManualMerges); KMessageBox::information(q, s); // //TODO //if ( topLevelItemCount()>0 ) //{ // topLevelItem(0)->setSelected(true); // setCurrentItem( topLevelItem(0) ); //} } if(bReload) { // Remember expanded items //TODO //QTreeWidgetItemIterator it( this ); //while ( *it ) //{ // DirMergeItem* pDMI = static_cast( *it ); // std::map::iterator i = expandedDirsMap.find( pDMI->m_pMFI->subPath() ); // if ( i!=expandedDirsMap.end() ) // { // t_ItemInfo& ii = i->second; // pDMI->setExpanded( ii.bExpanded ); // //pDMI->m_pMFI->setMergeOperation( ii.eMergeOperation, false ); unsafe, might have changed // pDMI->m_pMFI->m_bOperationComplete = ii.bOperationComplete; // pDMI->setText( s_OpStatusCol, ii.status ); // } // ++it; //} } else if(m_bUnfoldSubdirs) { m_pDirUnfoldAll->trigger(); } return true; } void DirectoryMergeWindow::onExpanded() { resizeColumnToContents(s_NameCol); } void DirectoryMergeWindow::slotChooseAEverywhere() { d->setAllMergeOperations(eCopyAToDest); } void DirectoryMergeWindow::slotChooseBEverywhere() { d->setAllMergeOperations(eCopyBToDest); } void DirectoryMergeWindow::slotChooseCEverywhere() { d->setAllMergeOperations(eCopyCToDest); } void DirectoryMergeWindow::slotAutoChooseEverywhere() { e_MergeOperation eDefaultMergeOp = d->m_dirC.isValid() ? eMergeABCToDest : d->m_bSyncMode ? eMergeToAB : eMergeABToDest; d->setAllMergeOperations(eDefaultMergeOp); } void DirectoryMergeWindow::slotNoOpEverywhere() { d->setAllMergeOperations(eNoOperation); } void DirectoryMergeWindow::slotFoldAllSubdirs() { collapseAll(); } void DirectoryMergeWindow::slotUnfoldAllSubdirs() { expandAll(); } // Merge current item (merge mode) void DirectoryMergeWindow::slotCurrentDoNothing() { d->setMergeOperation(currentIndex(), eNoOperation); } void DirectoryMergeWindow::slotCurrentChooseA() { d->setMergeOperation(currentIndex(), d->m_bSyncMode ? eCopyAToB : eCopyAToDest); } void DirectoryMergeWindow::slotCurrentChooseB() { d->setMergeOperation(currentIndex(), d->m_bSyncMode ? eCopyBToA : eCopyBToDest); } void DirectoryMergeWindow::slotCurrentChooseC() { d->setMergeOperation(currentIndex(), eCopyCToDest); } void DirectoryMergeWindow::slotCurrentMerge() { bool bThreeDirs = d->m_dirC.isValid(); d->setMergeOperation(currentIndex(), bThreeDirs ? eMergeABCToDest : eMergeABToDest); } void DirectoryMergeWindow::slotCurrentDelete() { d->setMergeOperation(currentIndex(), eDeleteFromDest); } // Sync current item void DirectoryMergeWindow::slotCurrentCopyAToB() { d->setMergeOperation(currentIndex(), eCopyAToB); } void DirectoryMergeWindow::slotCurrentCopyBToA() { d->setMergeOperation(currentIndex(), eCopyBToA); } void DirectoryMergeWindow::slotCurrentDeleteA() { d->setMergeOperation(currentIndex(), eDeleteA); } void DirectoryMergeWindow::slotCurrentDeleteB() { d->setMergeOperation(currentIndex(), eDeleteB); } void DirectoryMergeWindow::slotCurrentDeleteAAndB() { d->setMergeOperation(currentIndex(), eDeleteAB); } void DirectoryMergeWindow::slotCurrentMergeToA() { d->setMergeOperation(currentIndex(), eMergeToA); } void DirectoryMergeWindow::slotCurrentMergeToB() { d->setMergeOperation(currentIndex(), eMergeToB); } void DirectoryMergeWindow::slotCurrentMergeToAAndB() { d->setMergeOperation(currentIndex(), eMergeToAB); } void DirectoryMergeWindow::keyPressEvent(QKeyEvent* e) { if((e->QInputEvent::modifiers() & Qt::ControlModifier) != 0) { bool bThreeDirs = d->m_dirC.isValid(); MergeFileInfos* pMFI = d->getMFI(currentIndex()); if(pMFI == nullptr) return; bool bMergeMode = bThreeDirs || !d->m_bSyncMode; bool bFTConflict = pMFI == nullptr ? false : conflictingFileTypes(*pMFI); if(bMergeMode) { switch(e->key()) { case Qt::Key_1: if(pMFI->existsInA()) { slotCurrentChooseA(); } return; case Qt::Key_2: if(pMFI->existsInB()) { slotCurrentChooseB(); } return; case Qt::Key_3: if(pMFI->existsInC()) { slotCurrentChooseC(); } return; case Qt::Key_Space: slotCurrentDoNothing(); return; case Qt::Key_4: if(!bFTConflict) { slotCurrentMerge(); } return; case Qt::Key_Delete: slotCurrentDelete(); return; default: break; } } else { switch(e->key()) { case Qt::Key_1: if(pMFI->existsInA()) { slotCurrentCopyAToB(); } return; case Qt::Key_2: if(pMFI->existsInB()) { slotCurrentCopyBToA(); } return; case Qt::Key_Space: slotCurrentDoNothing(); return; case Qt::Key_4: if(!bFTConflict) { slotCurrentMergeToAAndB(); } return; case Qt::Key_Delete: if(pMFI->existsInA() && pMFI->existsInB()) slotCurrentDeleteAAndB(); else if(pMFI->existsInA()) slotCurrentDeleteA(); else if(pMFI->existsInB()) slotCurrentDeleteB(); return; default: break; } } } else if(e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { onDoubleClick(currentIndex()); return; } QTreeView::keyPressEvent(e); } void DirectoryMergeWindow::focusInEvent(QFocusEvent*) { emit updateAvailabilities(); } void DirectoryMergeWindow::focusOutEvent(QFocusEvent*) { emit updateAvailabilities(); } void DirectoryMergeWindow::Data::setAllMergeOperations(e_MergeOperation eDefaultOperation) { if(KMessageBox::Yes == KMessageBox::warningYesNo(q, i18n("This affects all merge operations."), i18n("Changing All Merge Operations"), KStandardGuiItem::cont(), KStandardGuiItem::cancel())) { for(int i = 0; i < rowCount(); ++i) { calcSuggestedOperation(index(i, 0, QModelIndex()), eDefaultOperation); } } } void DirectoryMergeWindow::Data::compareFilesAndCalcAges(MergeFileInfos& mfi) { std::map dateMap; if(mfi.existsInA()) { dateMap[mfi.m_pFileInfoA->lastModified()] = 0; } if(mfi.existsInB()) { dateMap[mfi.m_pFileInfoB->lastModified()] = 1; } if(mfi.existsInC()) { dateMap[mfi.m_pFileInfoC->lastModified()] = 2; } if(m_pOptions->m_bDmFullAnalysis) { if((mfi.existsInA() && mfi.dirA()) || (mfi.existsInB() && mfi.dirB()) || (mfi.existsInC() && mfi.dirC())) { // If any input is a directory, don't start any comparison. mfi.m_bEqualAB = mfi.existsInA() && mfi.existsInB(); mfi.m_bEqualAC = mfi.existsInA() && mfi.existsInC(); mfi.m_bEqualBC = mfi.existsInB() && mfi.existsInC(); } else { emit q->startDiffMerge( mfi.existsInA() ? mfi.m_pFileInfoA->absoluteFilePath() : QString(""), mfi.existsInB() ? mfi.m_pFileInfoB->absoluteFilePath() : QString(""), mfi.existsInC() ? mfi.m_pFileInfoC->absoluteFilePath() : QString(""), "", "", "", "", &mfi.m_totalDiffStatus); int nofNonwhiteConflicts = mfi.m_totalDiffStatus.nofUnsolvedConflicts + mfi.m_totalDiffStatus.nofSolvedConflicts - mfi.m_totalDiffStatus.nofWhitespaceConflicts; if(m_pOptions->m_bDmWhiteSpaceEqual && nofNonwhiteConflicts == 0) { mfi.m_bEqualAB = mfi.existsInA() && mfi.existsInB(); mfi.m_bEqualAC = mfi.existsInA() && mfi.existsInC(); mfi.m_bEqualBC = mfi.existsInB() && mfi.existsInC(); } else { mfi.m_bEqualAB = mfi.m_totalDiffStatus.bBinaryAEqB; mfi.m_bEqualBC = mfi.m_totalDiffStatus.bBinaryBEqC; mfi.m_bEqualAC = mfi.m_totalDiffStatus.bBinaryAEqC; } } } else { bool bError; QString eqStatus; if(mfi.existsInA() && mfi.existsInB()) { if(mfi.dirA()) mfi.m_bEqualAB = true; else mfi.m_bEqualAB = fastFileComparison(*mfi.m_pFileInfoA, *mfi.m_pFileInfoB, bError, eqStatus); } if(mfi.existsInA() && mfi.existsInC()) { if(mfi.dirA()) mfi.m_bEqualAC = true; else mfi.m_bEqualAC = fastFileComparison(*mfi.m_pFileInfoA, *mfi.m_pFileInfoC, bError, eqStatus); } if(mfi.existsInB() && mfi.existsInC()) { if(mfi.m_bEqualAB && mfi.m_bEqualAC) mfi.m_bEqualBC = true; else { if(mfi.dirB()) mfi.m_bEqualBC = true; else mfi.m_bEqualBC = fastFileComparison(*mfi.m_pFileInfoB, *mfi.m_pFileInfoC, bError, eqStatus); } } } if(mfi.isLinkA() != mfi.isLinkB()) mfi.m_bEqualAB = false; if(mfi.isLinkA() != mfi.isLinkC()) mfi.m_bEqualAC = false; if(mfi.isLinkB() != mfi.isLinkC()) mfi.m_bEqualBC = false; if(mfi.dirA() != mfi.dirB()) mfi.m_bEqualAB = false; if(mfi.dirA() != mfi.dirC()) mfi.m_bEqualAC = false; if(mfi.dirB() != mfi.dirC()) mfi.m_bEqualBC = false; Q_ASSERT(eNew == 0 && eMiddle == 1 && eOld == 2); // The map automatically sorts the keys. int age = eNew; std::map::reverse_iterator i; for(i = dateMap.rbegin(); i != dateMap.rend(); ++i) { int n = i->second; if(n == 0 && mfi.m_ageA == eNotThere) { mfi.m_ageA = (e_Age)age; ++age; if(mfi.m_bEqualAB) { mfi.m_ageB = mfi.m_ageA; ++age; } if(mfi.m_bEqualAC) { mfi.m_ageC = mfi.m_ageA; ++age; } } else if(n == 1 && mfi.m_ageB == eNotThere) { mfi.m_ageB = (e_Age)age; ++age; if(mfi.m_bEqualAB) { mfi.m_ageA = mfi.m_ageB; ++age; } if(mfi.m_bEqualBC) { mfi.m_ageC = mfi.m_ageB; ++age; } } else if(n == 2 && mfi.m_ageC == eNotThere) { mfi.m_ageC = (e_Age)age; ++age; if(mfi.m_bEqualAC) { mfi.m_ageA = mfi.m_ageC; ++age; } if(mfi.m_bEqualBC) { mfi.m_ageB = mfi.m_ageC; ++age; } } } // The checks below are necessary when the dates of the file are equal but the // files are not. One wouldn't expect this to happen, yet it happens sometimes. if(mfi.existsInC() && mfi.m_ageC == eNotThere) { mfi.m_ageC = (e_Age)age; ++age; mfi.m_bConflictingAges = true; } if(mfi.existsInB() && mfi.m_ageB == eNotThere) { mfi.m_ageB = (e_Age)age; ++age; mfi.m_bConflictingAges = true; } if(mfi.existsInA() && mfi.m_ageA == eNotThere) { mfi.m_ageA = (e_Age)age; ++age; mfi.m_bConflictingAges = true; } if(mfi.m_ageA != eOld && mfi.m_ageB != eOld && mfi.m_ageC != eOld) { if(mfi.m_ageA == eMiddle) mfi.m_ageA = eOld; if(mfi.m_ageB == eMiddle) mfi.m_ageB = eOld; if(mfi.m_ageC == eMiddle) mfi.m_ageC = eOld; } } static QPixmap* s_pm_dir; static QPixmap* s_pm_file; static QPixmap* pmNotThere; static QPixmap* pmNew; static QPixmap* pmOld; static QPixmap* pmMiddle; static QPixmap* pmLink; static QPixmap* pmDirLink; static QPixmap* pmFileLink; static QPixmap* pmNewLink; static QPixmap* pmOldLink; static QPixmap* pmMiddleLink; static QPixmap* pmNewDir; static QPixmap* pmMiddleDir; static QPixmap* pmOldDir; static QPixmap* pmNewDirLink; static QPixmap* pmMiddleDirLink; static QPixmap* pmOldDirLink; static QPixmap colorToPixmap(QColor c) { QPixmap pm(16, 16); QPainter p(&pm); p.setPen(Qt::black); p.setBrush(c); p.drawRect(0, 0, pm.width(), pm.height()); return pm; } static void initPixmaps(QColor newest, QColor oldest, QColor middle, QColor notThere) { if(pmNew == nullptr) { pmNotThere = new QPixmap; pmNew = new QPixmap; pmOld = new QPixmap; pmMiddle = new QPixmap; #include "xpm/link_arrow.xpm" pmLink = new QPixmap(link_arrow); pmDirLink = new QPixmap; pmFileLink = new QPixmap; pmNewLink = new QPixmap; pmOldLink = new QPixmap; pmMiddleLink = new QPixmap; pmNewDir = new QPixmap; pmMiddleDir = new QPixmap; pmOldDir = new QPixmap; pmNewDirLink = new QPixmap; pmMiddleDirLink = new QPixmap; pmOldDirLink = new QPixmap; } *pmNotThere = colorToPixmap(notThere); *pmNew = colorToPixmap(newest); *pmOld = colorToPixmap(oldest); *pmMiddle = colorToPixmap(middle); *pmDirLink = pixCombiner(s_pm_dir, pmLink); *pmFileLink = pixCombiner(s_pm_file, pmLink); *pmNewLink = pixCombiner(pmNew, pmLink); *pmOldLink = pixCombiner(pmOld, pmLink); *pmMiddleLink = pixCombiner(pmMiddle, pmLink); *pmNewDir = pixCombiner2(pmNew, s_pm_dir); *pmMiddleDir = pixCombiner2(pmMiddle, s_pm_dir); *pmOldDir = pixCombiner2(pmOld, s_pm_dir); *pmNewDirLink = pixCombiner(pmNewDir, pmLink); *pmMiddleDirLink = pixCombiner(pmMiddleDir, pmLink); *pmOldDirLink = pixCombiner(pmOldDir, pmLink); } static QPixmap getOnePixmap(e_Age eAge, bool bLink, bool bDir) { static QPixmap* ageToPm[] = {pmNew, pmMiddle, pmOld, pmNotThere, s_pm_file}; static QPixmap* ageToPmLink[] = {pmNewLink, pmMiddleLink, pmOldLink, pmNotThere, pmFileLink}; static QPixmap* ageToPmDir[] = {pmNewDir, pmMiddleDir, pmOldDir, pmNotThere, s_pm_dir}; static QPixmap* ageToPmDirLink[] = {pmNewDirLink, pmMiddleDirLink, pmOldDirLink, pmNotThere, pmDirLink}; QPixmap** ppPm = bDir ? (bLink ? ageToPmDirLink : ageToPmDir) : (bLink ? ageToPmLink : ageToPm); return *ppPm[eAge]; } static void setPixmaps(MergeFileInfos& mfi, bool) { if(mfi.dirA() || mfi.dirB() || mfi.dirC()) { mfi.m_ageA = eNotThere; mfi.m_ageB = eNotThere; mfi.m_ageC = eNotThere; int age = eNew; if(mfi.existsInC()) { mfi.m_ageC = (e_Age)age; if(mfi.m_bEqualAC) mfi.m_ageA = (e_Age)age; if(mfi.m_bEqualBC) mfi.m_ageB = (e_Age)age; ++age; } if(mfi.existsInB() && mfi.m_ageB == eNotThere) { mfi.m_ageB = (e_Age)age; if(mfi.m_bEqualAB) mfi.m_ageA = (e_Age)age; ++age; } if(mfi.existsInA() && mfi.m_ageA == eNotThere) { mfi.m_ageA = (e_Age)age; } if(mfi.m_ageA != eOld && mfi.m_ageB != eOld && mfi.m_ageC != eOld) { if(mfi.m_ageA == eMiddle) mfi.m_ageA = eOld; if(mfi.m_ageB == eMiddle) mfi.m_ageB = eOld; if(mfi.m_ageC == eMiddle) mfi.m_ageC = eOld; } } } static QModelIndex nextSibling(const QModelIndex& mi) { QModelIndex miParent = mi.parent(); int currentIdx = mi.row(); if(currentIdx + 1 < mi.model()->rowCount(miParent)) return mi.model()->index(mi.row() + 1, 0, miParent); // next child of parent return QModelIndex(); } // Iterate through the complete tree. Start by specifying QListView::firstChild(). QModelIndex DirectoryMergeWindow::Data::treeIterator(QModelIndex mi, bool bVisitChildren, bool bFindInvisible) { if(mi.isValid()) { do { if(bVisitChildren && mi.model()->rowCount(mi) != 0) mi = mi.model()->index(0, 0, mi); else { QModelIndex miNextSibling = nextSibling(mi); if(miNextSibling.isValid()) mi = miNextSibling; else { mi = mi.parent(); while(mi.isValid()) { miNextSibling = nextSibling(mi); if(miNextSibling.isValid()) { mi = miNextSibling; break; } else { mi = mi.parent(); } } } } } while(mi.isValid() && q->isRowHidden(mi.row(), mi.parent()) && !bFindInvisible); } return mi; } void DirectoryMergeWindow::Data::prepareListView(ProgressProxy& pp) { static bool bFirstTime = true; if(bFirstTime) { #include "xpm/file.xpm" #include "xpm/folder.xpm" // FIXME specify correct icon loader group s_pm_dir = new QPixmap(m_pIconLoader->loadIcon("folder", KIconLoader::NoGroup, KIconLoader::Small)); if(s_pm_dir->size() != QSize(16, 16)) { delete s_pm_dir; s_pm_dir = new QPixmap(folder_pm); } s_pm_file = new QPixmap(file_pm); bFirstTime = false; } //TODO clear(); initPixmaps(m_pOptions->m_newestFileColor, m_pOptions->m_oldestFileColor, m_pOptions->m_midAgeFileColor, m_pOptions->m_missingFileColor); q->setRootIsDecorated(true); bool bCheckC = m_dirC.isValid(); t_fileMergeMap::iterator j; int nrOfFiles = m_fileMergeMap.size(); int currentIdx = 1; QTime t; t.start(); pp.setMaxNofSteps(nrOfFiles); for(j = m_fileMergeMap.begin(); j != m_fileMergeMap.end(); ++j) { MergeFileInfos& mfi = j.value(); // const QString& fileName = j->first; const QString& fileName = mfi.subPath(); pp.setInformation( i18n("Processing %1 / %2\n%3", currentIdx, nrOfFiles, fileName), currentIdx, false); if(pp.wasCancelled()) break; ++currentIdx; // The comparisons and calculations for each file take place here. compareFilesAndCalcAges(mfi); // Get dirname from fileName: Search for "/" from end: int pos = fileName.lastIndexOf('/'); QString dirPart; QString filePart; if(pos == -1) { // Top dir filePart = fileName; } else { dirPart = fileName.left(pos); filePart = fileName.mid(pos + 1); } if(dirPart.isEmpty()) // Top level { m_pRoot->m_children.push_back(&mfi); //new DirMergeItem( this, filePart, &mfi ); mfi.m_pParent = m_pRoot; } else { FileAccess* pFA = mfi.m_pFileInfoA ? mfi.m_pFileInfoA : mfi.m_pFileInfoB ? mfi.m_pFileInfoB : mfi.m_pFileInfoC; MergeFileInfos& dirMfi = pFA->parent() ? m_fileMergeMap[FileKey(*pFA->parent())] : *m_pRoot; // parent dirMfi.m_children.push_back(&mfi); //new DirMergeItem( dirMfi.m_pDMI, filePart, &mfi ); mfi.m_pParent = &dirMfi; // // Equality for parent dirs is set in updateFileVisibilities() } setPixmaps(mfi, bCheckC); } beginResetModel(); endResetModel(); } static bool conflictingFileTypes(MergeFileInfos& mfi) { // Now check if file/dir-types fit. if(mfi.isLinkA() || mfi.isLinkB() || mfi.isLinkC()) { if((mfi.existsInA() && !mfi.isLinkA()) || (mfi.existsInB() && !mfi.isLinkB()) || (mfi.existsInC() && !mfi.isLinkC())) { return true; } } if(mfi.dirA() || mfi.dirB() || mfi.dirC()) { if((mfi.existsInA() && !mfi.dirA()) || (mfi.existsInB() && !mfi.dirB()) || (mfi.existsInC() && !mfi.dirC())) { return true; } } return false; } void DirectoryMergeWindow::Data::calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation eDefaultMergeOp) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI == nullptr) return; MergeFileInfos& mfi = *pMFI; bool bCheckC = m_dirC.isValid(); bool bCopyNewer = m_pOptions->m_bDmCopyNewer; bool bOtherDest = !((m_dirDestInternal.absoluteFilePath() == m_dirA.absoluteFilePath()) || (m_dirDestInternal.absoluteFilePath() == m_dirB.absoluteFilePath()) || (bCheckC && m_dirDestInternal.absoluteFilePath() == m_dirC.absoluteFilePath())); if(eDefaultMergeOp == eMergeABCToDest && !bCheckC) { eDefaultMergeOp = eMergeABToDest; } if(eDefaultMergeOp == eMergeToAB && bCheckC) { Q_ASSERT(true); } if(eDefaultMergeOp == eMergeToA || eDefaultMergeOp == eMergeToB || eDefaultMergeOp == eMergeABCToDest || eDefaultMergeOp == eMergeABToDest || eDefaultMergeOp == eMergeToAB) { if(!bCheckC) { if(mfi.m_bEqualAB) { setMergeOperation(mi, bOtherDest ? eCopyBToDest : eNoOperation); } else if(mfi.existsInA() && mfi.existsInB()) { if(!bCopyNewer || mfi.dirA()) setMergeOperation(mi, eDefaultMergeOp); else if(bCopyNewer && mfi.m_bConflictingAges) { setMergeOperation(mi, eConflictingAges); } else { if(mfi.m_ageA == eNew) setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyAToB : eCopyAToDest); else setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyBToA : eCopyBToDest); } } else if(!mfi.existsInA() && mfi.existsInB()) { if(eDefaultMergeOp == eMergeABToDest) setMergeOperation(mi, eCopyBToDest); else if(eDefaultMergeOp == eMergeToB) setMergeOperation(mi, eNoOperation); else setMergeOperation(mi, eCopyBToA); } else if(mfi.existsInA() && !mfi.existsInB()) { if(eDefaultMergeOp == eMergeABToDest) setMergeOperation(mi, eCopyAToDest); else if(eDefaultMergeOp == eMergeToA) setMergeOperation(mi, eNoOperation); else setMergeOperation(mi, eCopyAToB); } else //if ( !mfi.existsInA() && !mfi.existsInB() ) { setMergeOperation(mi, eNoOperation); } } else { if(mfi.m_bEqualAB && mfi.m_bEqualAC) { setMergeOperation(mi, bOtherDest ? eCopyCToDest : eNoOperation); } else if(mfi.existsInA() && mfi.existsInB() && mfi.existsInC()) { if(mfi.m_bEqualAB) setMergeOperation(mi, eCopyCToDest); else if(mfi.m_bEqualAC) setMergeOperation(mi, eCopyBToDest); else if(mfi.m_bEqualBC) setMergeOperation(mi, eCopyCToDest); else setMergeOperation(mi, eMergeABCToDest); } else if(mfi.existsInA() && mfi.existsInB() && !mfi.existsInC()) { if(mfi.m_bEqualAB) setMergeOperation(mi, eDeleteFromDest); else setMergeOperation(mi, eChangedAndDeleted); } else if(mfi.existsInA() && !mfi.existsInB() && mfi.existsInC()) { if(mfi.m_bEqualAC) setMergeOperation(mi, eDeleteFromDest); else setMergeOperation(mi, eChangedAndDeleted); } else if(!mfi.existsInA() && mfi.existsInB() && mfi.existsInC()) { if(mfi.m_bEqualBC) setMergeOperation(mi, eCopyCToDest); else setMergeOperation(mi, eMergeABCToDest); } else if(!mfi.existsInA() && !mfi.existsInB() && mfi.existsInC()) { setMergeOperation(mi, eCopyCToDest); } else if(!mfi.existsInA() && mfi.existsInB() && !mfi.existsInC()) { setMergeOperation(mi, eCopyBToDest); } else if(mfi.existsInA() && !mfi.existsInB() && !mfi.existsInC()) { setMergeOperation(mi, eDeleteFromDest); } else //if ( !mfi.existsInA() && !mfi.existsInB() && !mfi.existsInC() ) { setMergeOperation(mi, eNoOperation); } } // Now check if file/dir-types fit. if(conflictingFileTypes(mfi)) { setMergeOperation(mi, eConflictingFileTypes); } } else { e_MergeOperation eMO = eDefaultMergeOp; switch(eDefaultMergeOp) { case eConflictingFileTypes: case eChangedAndDeleted: case eConflictingAges: case eDeleteA: case eDeleteB: case eDeleteAB: case eDeleteFromDest: case eNoOperation: break; case eCopyAToB: if(!mfi.existsInA()) { eMO = eDeleteB; } break; case eCopyBToA: if(!mfi.existsInB()) { eMO = eDeleteA; } break; case eCopyAToDest: if(!mfi.existsInA()) { eMO = eDeleteFromDest; } break; case eCopyBToDest: if(!mfi.existsInB()) { eMO = eDeleteFromDest; } break; case eCopyCToDest: if(!mfi.existsInC()) { eMO = eDeleteFromDest; } break; case eMergeToA: case eMergeToB: case eMergeToAB: case eMergeABCToDest: case eMergeABToDest: break; default: Q_ASSERT(true); break; } setMergeOperation(mi, eMO); } } void DirectoryMergeWindow::onDoubleClick(const QModelIndex& mi) { if(!mi.isValid()) return; d->m_bSimulatedMergeStarted = false; if(d->m_bDirectoryMerge) mergeCurrentFile(); else compareCurrentFile(); } void DirectoryMergeWindow::currentChanged(const QModelIndex& current, const QModelIndex& previous) { QTreeView::currentChanged(current, previous); MergeFileInfos* pMFI = d->getMFI(current); if(pMFI == nullptr) return; d->m_pDirectoryMergeInfo->setInfo(d->m_dirA, d->m_dirB, d->m_dirC, d->m_dirDestInternal, *pMFI); } void DirectoryMergeWindow::mousePressEvent(QMouseEvent* e) { QTreeView::mousePressEvent(e); QModelIndex mi = indexAt(e->pos()); int c = mi.column(); QPoint p = e->globalPos(); MergeFileInfos* pMFI = d->getMFI(mi); if(pMFI == nullptr) return; MergeFileInfos& mfi = *pMFI; if(c == s_OpCol) { bool bThreeDirs = d->m_dirC.isValid(); QMenu m(this); if(bThreeDirs) { m.addAction(d->m_pDirCurrentDoNothing); int count = 0; if(mfi.existsInA()) { m.addAction(d->m_pDirCurrentChooseA); ++count; } if(mfi.existsInB()) { m.addAction(d->m_pDirCurrentChooseB); ++count; } if(mfi.existsInC()) { m.addAction(d->m_pDirCurrentChooseC); ++count; } if(!conflictingFileTypes(mfi) && count > 1) m.addAction(d->m_pDirCurrentMerge); m.addAction(d->m_pDirCurrentDelete); } else if(d->m_bSyncMode) { m.addAction(d->m_pDirCurrentSyncDoNothing); if(mfi.existsInA()) m.addAction(d->m_pDirCurrentSyncCopyAToB); if(mfi.existsInB()) m.addAction(d->m_pDirCurrentSyncCopyBToA); if(mfi.existsInA()) m.addAction(d->m_pDirCurrentSyncDeleteA); if(mfi.existsInB()) m.addAction(d->m_pDirCurrentSyncDeleteB); if(mfi.existsInA() && mfi.existsInB()) { m.addAction(d->m_pDirCurrentSyncDeleteAAndB); if(!conflictingFileTypes(mfi)) { m.addAction(d->m_pDirCurrentSyncMergeToA); m.addAction(d->m_pDirCurrentSyncMergeToB); m.addAction(d->m_pDirCurrentSyncMergeToAAndB); } } } else { m.addAction(d->m_pDirCurrentDoNothing); if(mfi.existsInA()) { m.addAction(d->m_pDirCurrentChooseA); } if(mfi.existsInB()) { m.addAction(d->m_pDirCurrentChooseB); } if(!conflictingFileTypes(mfi) && mfi.existsInA() && mfi.existsInB()) m.addAction(d->m_pDirCurrentMerge); m.addAction(d->m_pDirCurrentDelete); } m.exec(p); } else if(c == s_ACol || c == s_BCol || c == s_CCol) { QString itemPath; if(c == s_ACol && mfi.existsInA()) { itemPath = d->fullNameA(mfi); } else if(c == s_BCol && mfi.existsInB()) { itemPath = d->fullNameB(mfi); } else if(c == s_CCol && mfi.existsInC()) { itemPath = d->fullNameC(mfi); } if(!itemPath.isEmpty()) { d->selectItemAndColumn(mi, e->button() == Qt::RightButton); } } } #ifndef QT_NO_CONTEXTMENU void DirectoryMergeWindow::contextMenuEvent(QContextMenuEvent* e) { QModelIndex mi = indexAt(e->pos()); int c = mi.column(); MergeFileInfos* pMFI = d->getMFI(mi); if(pMFI == nullptr) return; if(c == s_ACol || c == s_BCol || c == s_CCol) { QString itemPath; if(c == s_ACol && pMFI->existsInA()) { itemPath = d->fullNameA(*pMFI); } else if(c == s_BCol && pMFI->existsInB()) { itemPath = d->fullNameB(*pMFI); } else if(c == s_CCol && pMFI->existsInC()) { itemPath = d->fullNameC(*pMFI); } if(!itemPath.isEmpty()) { d->selectItemAndColumn(mi, true); QMenu m(this); m.addAction(d->m_pDirCompareExplicit); m.addAction(d->m_pDirMergeExplicit); m.popup(e->globalPos()); } } } #endif QString DirectoryMergeWindow::Data::getFileName(const QModelIndex& mi) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI != nullptr) { return mi.column() == s_ACol ? pMFI->m_pFileInfoA->absoluteFilePath() : mi.column() == s_BCol ? pMFI->m_pFileInfoB->absoluteFilePath() : mi.column() == s_CCol ? pMFI->m_pFileInfoC->absoluteFilePath() : QString(""); } return ""; } bool DirectoryMergeWindow::Data::isDir(const QModelIndex& mi) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI != nullptr) { return mi.column() == s_ACol ? pMFI->dirA() : mi.column() == s_BCol ? pMFI->dirB() : pMFI->dirC(); } return false; } void DirectoryMergeWindow::Data::selectItemAndColumn(const QModelIndex& mi, bool bContextMenu) { if(bContextMenu && (mi == m_selection1Index || mi == m_selection2Index || mi == m_selection3Index)) return; QModelIndex old1 = m_selection1Index; QModelIndex old2 = m_selection2Index; QModelIndex old3 = m_selection3Index; bool bReset = false; if(m_selection1Index.isValid()) { if(isDir(m_selection1Index) != isDir(mi)) bReset = true; } if(bReset || m_selection3Index.isValid() || mi == m_selection1Index || mi == m_selection2Index || mi == m_selection3Index) { // restart m_selection1Index = QModelIndex(); m_selection2Index = QModelIndex(); m_selection3Index = QModelIndex(); } else if(!m_selection1Index.isValid()) { m_selection1Index = mi; m_selection2Index = QModelIndex(); m_selection3Index = QModelIndex(); } else if(!m_selection2Index.isValid()) { m_selection2Index = mi; m_selection3Index = QModelIndex(); } else if(!m_selection3Index.isValid()) { m_selection3Index = mi; } if(old1.isValid()) emit dataChanged(old1, old1); if(old2.isValid()) emit dataChanged(old2, old2); if(old3.isValid()) emit dataChanged(old3, old3); if(m_selection1Index.isValid()) emit dataChanged(m_selection1Index, m_selection1Index); if(m_selection2Index.isValid()) emit dataChanged(m_selection2Index, m_selection2Index); if(m_selection3Index.isValid()) emit dataChanged(m_selection3Index, m_selection3Index); emit q->updateAvailabilities(); } //TODO //void DirMergeItem::init(MergeFileInfos* pMFI) //{ // pMFI->m_pDMI = this; // m_pMFI = pMFI; // TotalDiffStatus& tds = pMFI->m_totalDiffStatus; // if ( m_pMFI->dirA() || m_pMFI->dirB() || m_pMFI->dirC() ) // { // } // else // { // setText( s_UnsolvedCol, QString::number( tds.nofUnsolvedConflicts ) ); // setText( s_SolvedCol, QString::number( tds.nofSolvedConflicts ) ); // setText( s_NonWhiteCol, QString::number( tds.nofUnsolvedConflicts + tds.nofSolvedConflicts - tds.nofWhitespaceConflicts ) ); // setText( s_WhiteCol, QString::number( tds.nofWhitespaceConflicts ) ); // } // setSizeHint( s_ACol, QSize(17,17) ); // Iconsize // setSizeHint( s_BCol, QSize(17,17) ); // Iconsize // setSizeHint( s_CCol, QSize(17,17) ); // Iconsize //} class MfiCompare { Qt::SortOrder mOrder; public: explicit MfiCompare(Qt::SortOrder order) { mOrder = order; } bool operator()(MergeFileInfos* pMFI1, MergeFileInfos* pMFI2) { bool bDir1 = pMFI1->dirA() || pMFI1->dirB() || pMFI1->dirC(); bool bDir2 = pMFI2->dirA() || pMFI2->dirB() || pMFI2->dirC(); if(bDir1 == bDir2) { if(mOrder == Qt::AscendingOrder) { return pMFI1->fileName().compare(pMFI2->fileName(), Qt::CaseInsensitive) < 0; } else { return pMFI1->fileName().compare(pMFI2->fileName(), Qt::CaseInsensitive) > 0; } } else return bDir1; } }; static void sortHelper(MergeFileInfos* pMFI, Qt::SortOrder order) { std::sort(pMFI->m_children.begin(), pMFI->m_children.end(), MfiCompare(order)); for(int i = 0; i < pMFI->m_children.count(); ++i) sortHelper(pMFI->m_children[i], order); } void DirectoryMergeWindow::Data::sort(int column, Qt::SortOrder order) { Q_UNUSED(column); beginResetModel(); sortHelper(m_pRoot, order); endResetModel(); } // //DirMergeItem::~DirMergeItem() //{ // m_pMFI->m_pDMI = 0; //} void DirectoryMergeWindow::Data::setMergeOperation(const QModelIndex& mi, e_MergeOperation eMOp, bool bRecursive) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI == nullptr) return; MergeFileInfos& mfi = *pMFI; if(eMOp != mfi.m_eMergeOperation) { mfi.m_bOperationComplete = false; setOpStatus(mi, eOpStatusNone); } mfi.m_eMergeOperation = eMOp; if(bRecursive) { e_MergeOperation eChildrenMergeOp = mfi.m_eMergeOperation; if(eChildrenMergeOp == eConflictingFileTypes) eChildrenMergeOp = eMergeABCToDest; for(int childIdx = 0; childIdx < mfi.m_children.count(); ++childIdx) { calcSuggestedOperation(index(childIdx, 0, mi), eChildrenMergeOp); } } } void DirectoryMergeWindow::compareCurrentFile() { if(!d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible")); return; } if(MergeFileInfos* pMFI = d->getMFI(currentIndex())) { if(!(pMFI->dirA() || pMFI->dirB() || pMFI->dirC())) { emit startDiffMerge( pMFI->existsInA() ? pMFI->m_pFileInfoA->absoluteFilePath() : QString(""), pMFI->existsInB() ? pMFI->m_pFileInfoB->absoluteFilePath() : QString(""), pMFI->existsInC() ? pMFI->m_pFileInfoC->absoluteFilePath() : QString(""), "", "", "", "", nullptr); } } emit updateAvailabilities(); } void DirectoryMergeWindow::slotCompareExplicitlySelectedFiles() { if(!d->isDir(d->m_selection1Index) && !d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible")); return; } emit startDiffMerge( d->getFileName(d->m_selection1Index), d->getFileName(d->m_selection2Index), d->getFileName(d->m_selection3Index), "", "", "", "", nullptr); d->m_selection1Index = QModelIndex(); d->m_selection2Index = QModelIndex(); d->m_selection3Index = QModelIndex(); emit updateAvailabilities(); update(); } void DirectoryMergeWindow::slotMergeExplicitlySelectedFiles() { if(!d->isDir(d->m_selection1Index) && !d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible")); return; } QString fn1 = d->getFileName(d->m_selection1Index); QString fn2 = d->getFileName(d->m_selection2Index); QString fn3 = d->getFileName(d->m_selection3Index); emit startDiffMerge(fn1, fn2, fn3, fn3.isEmpty() ? fn2 : fn3, "", "", "", nullptr); d->m_selection1Index = QModelIndex(); d->m_selection2Index = QModelIndex(); d->m_selection3Index = QModelIndex(); emit updateAvailabilities(); update(); } bool DirectoryMergeWindow::isFileSelected() { if(MergeFileInfos* pMFI = d->getMFI(currentIndex())) { return !(pMFI->dirA() || pMFI->dirB() || pMFI->dirC() || conflictingFileTypes(*pMFI)); } return false; } void DirectoryMergeWindow::mergeResultSaved(const QString& fileName) { QModelIndex mi = (d->m_mergeItemList.empty() || d->m_currentIndexForOperation == d->m_mergeItemList.end()) ? QModelIndex() : *d->m_currentIndexForOperation; MergeFileInfos* pMFI = d->getMFI(mi); if(pMFI == nullptr) { // This can happen if the same file is saved and modified and saved again. Nothing to do then. return; } if(fileName == d->fullNameDest(*pMFI)) { MergeFileInfos& mfi = *pMFI; if(mfi.m_eMergeOperation == eMergeToAB) { bool bSuccess = d->copyFLD(d->fullNameB(mfi), d->fullNameA(mfi)); if(!bSuccess) { KMessageBox::error(this, i18n("An error occurred while copying.")); d->m_pStatusInfo->setWindowTitle(i18n("Merge Error")); d->m_pStatusInfo->exec(); //if ( m_pStatusInfo->firstChild()!=0 ) // m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() ); d->m_bError = true; d->setOpStatus(mi, eOpStatusError); mfi.m_eMergeOperation = eCopyBToA; return; } } d->setOpStatus(mi, eOpStatusDone); pMFI->m_bOperationComplete = true; if(d->m_mergeItemList.size() == 1) { d->m_mergeItemList.clear(); d->m_bRealMergeStarted = false; } } emit updateAvailabilities(); } bool DirectoryMergeWindow::Data::canContinue() { bool bCanContinue = false; emit q->checkIfCanContinue(&bCanContinue); if(bCanContinue && !m_bError) { QModelIndex mi = (m_mergeItemList.empty() || m_currentIndexForOperation == m_mergeItemList.end()) ? QModelIndex() : *m_currentIndexForOperation; MergeFileInfos* pMFI = getMFI(mi); if(pMFI && !pMFI->m_bOperationComplete) { setOpStatus(mi, eOpStatusNotSaved); pMFI->m_bOperationComplete = true; if(m_mergeItemList.size() == 1) { m_mergeItemList.clear(); m_bRealMergeStarted = false; } } } return bCanContinue; } bool DirectoryMergeWindow::Data::executeMergeOperation(MergeFileInfos& mfi, bool& bSingleFileMerge) { bool bCreateBackups = m_pOptions->m_bDmCreateBakFiles; // First decide destname QString destName; switch(mfi.m_eMergeOperation) { case eNoOperation: break; case eDeleteAB: break; case eMergeToAB: // let the user save in B. In mergeResultSaved() the file will be copied to A. case eMergeToB: case eDeleteB: case eCopyAToB: destName = fullNameB(mfi); break; case eMergeToA: case eDeleteA: case eCopyBToA: destName = fullNameA(mfi); break; case eMergeABToDest: case eMergeABCToDest: case eCopyAToDest: case eCopyBToDest: case eCopyCToDest: case eDeleteFromDest: destName = fullNameDest(mfi); break; default: KMessageBox::error(q, i18n("Unknown merge operation. (This must never happen!)")); } bool bSuccess = false; bSingleFileMerge = false; switch(mfi.m_eMergeOperation) { case eNoOperation: bSuccess = true; break; case eCopyAToDest: case eCopyAToB: bSuccess = copyFLD(fullNameA(mfi), destName); break; case eCopyBToDest: case eCopyBToA: bSuccess = copyFLD(fullNameB(mfi), destName); break; case eCopyCToDest: bSuccess = copyFLD(fullNameC(mfi), destName); break; case eDeleteFromDest: case eDeleteA: case eDeleteB: bSuccess = deleteFLD(destName, bCreateBackups); break; case eDeleteAB: bSuccess = deleteFLD(fullNameA(mfi), bCreateBackups) && deleteFLD(fullNameB(mfi), bCreateBackups); break; case eMergeABToDest: case eMergeToA: case eMergeToAB: case eMergeToB: bSuccess = mergeFLD(fullNameA(mfi), fullNameB(mfi), "", destName, bSingleFileMerge); break; case eMergeABCToDest: bSuccess = mergeFLD( mfi.existsInA() ? fullNameA(mfi) : QString(""), mfi.existsInB() ? fullNameB(mfi) : QString(""), mfi.existsInC() ? fullNameC(mfi) : QString(""), destName, bSingleFileMerge); break; default: KMessageBox::error(q, i18n("Unknown merge operation.")); } return bSuccess; } // Check if the merge can start, and prepare the m_mergeItemList which then contains all // items that must be merged. void DirectoryMergeWindow::Data::prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose) { if(bVerbose) { int status = KMessageBox::warningYesNoCancel(q, i18n("The merge is about to begin.\n\n" "Choose \"Do it\" if you have read the instructions and know what you are doing.\n" "Choosing \"Simulate it\" will tell you what would happen.\n\n" "Be aware that this program still has beta status " "and there is NO WARRANTY whatsoever! Make backups of your vital data!"), i18n("Starting Merge"), KGuiItem(i18n("Do It")), KGuiItem(i18n("Simulate It"))); if(status == KMessageBox::Yes) m_bRealMergeStarted = true; else if(status == KMessageBox::No) m_bSimulatedMergeStarted = true; else return; } else { m_bRealMergeStarted = true; } m_mergeItemList.clear(); if(!miBegin.isValid()) return; for(QModelIndex mi = miBegin; mi != miEnd; mi = treeIterator(mi)) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI && !pMFI->m_bOperationComplete) { m_mergeItemList.push_back(mi); QString errorText; if(pMFI->m_eMergeOperation == eConflictingFileTypes) { errorText = i18n("The highlighted item has a different type in the different directories. Select what to do."); } if(pMFI->m_eMergeOperation == eConflictingAges) { errorText = i18n("The modification dates of the file are equal but the files are not. Select what to do."); } if(pMFI->m_eMergeOperation == eChangedAndDeleted) { errorText = i18n("The highlighted item was changed in one directory and deleted in the other. Select what to do."); } if(!errorText.isEmpty()) { q->scrollTo(mi, QAbstractItemView::EnsureVisible); q->setCurrentIndex(mi); KMessageBox::error(q, errorText); m_mergeItemList.clear(); m_bRealMergeStarted = false; return; } } } m_currentIndexForOperation = m_mergeItemList.begin(); return; } void DirectoryMergeWindow::slotRunOperationForCurrentItem() { if(!d->canContinue()) return; bool bVerbose = false; if(d->m_mergeItemList.empty()) { QModelIndex miBegin = currentIndex(); QModelIndex miEnd = d->treeIterator(miBegin, false, false); // find next visible sibling (no children) d->prepareMergeStart(miBegin, miEnd, bVerbose); d->mergeContinue(true, bVerbose); } else d->mergeContinue(false, bVerbose); } void DirectoryMergeWindow::slotRunOperationForAllItems() { if(!d->canContinue()) return; bool bVerbose = true; if(d->m_mergeItemList.empty()) { QModelIndex miBegin = d->rowCount() > 0 ? d->index(0, 0, QModelIndex()) : QModelIndex(); d->prepareMergeStart(miBegin, QModelIndex(), bVerbose); d->mergeContinue(true, bVerbose); } else d->mergeContinue(false, bVerbose); } void DirectoryMergeWindow::mergeCurrentFile() { if(!d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible because directory merge is currently running."), i18n("Operation Not Possible")); return; } if(isFileSelected()) { MergeFileInfos* pMFI = d->getMFI(currentIndex()); if(pMFI != nullptr) { MergeFileInfos& mfi = *pMFI; d->m_mergeItemList.clear(); d->m_mergeItemList.push_back(currentIndex()); d->m_currentIndexForOperation = d->m_mergeItemList.begin(); bool bDummy = false; d->mergeFLD( mfi.existsInA() ? mfi.m_pFileInfoA->absoluteFilePath() : QString(""), mfi.existsInB() ? mfi.m_pFileInfoB->absoluteFilePath() : QString(""), mfi.existsInC() ? mfi.m_pFileInfoC->absoluteFilePath() : QString(""), d->fullNameDest(mfi), bDummy); } } emit updateAvailabilities(); } // When bStart is true then m_currentIndexForOperation must still be processed. // When bVerbose is true then a messagebox will tell when the merge is complete. void DirectoryMergeWindow::Data::mergeContinue(bool bStart, bool bVerbose) { ProgressProxy pp; if(m_mergeItemList.empty()) return; int nrOfItems = 0; int nrOfCompletedItems = 0; int nrOfCompletedSimItems = 0; // Count the number of completed items (for the progress bar). for(MergeItemList::iterator i = m_mergeItemList.begin(); i != m_mergeItemList.end(); ++i) { MergeFileInfos* pMFI = getMFI(*i); ++nrOfItems; if(pMFI->m_bOperationComplete) ++nrOfCompletedItems; if(pMFI->m_bSimOpComplete) ++nrOfCompletedSimItems; } m_pStatusInfo->hide(); m_pStatusInfo->clear(); QModelIndex miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation; bool bContinueWithCurrentItem = bStart; // true for first item, else false bool bSkipItem = false; if(!bStart && m_bError && miCurrent.isValid()) { int status = KMessageBox::warningYesNoCancel(q, i18n("There was an error in the last step.\n" "Do you want to continue with the item that caused the error or do you want to skip this item?"), i18n("Continue merge after an error"), KGuiItem(i18n("Continue With Last Item")), KGuiItem(i18n("Skip Item"))); if(status == KMessageBox::Yes) bContinueWithCurrentItem = true; else if(status == KMessageBox::No) bSkipItem = true; else return; m_bError = false; } pp.setMaxNofSteps(nrOfItems); bool bSuccess = true; bool bSingleFileMerge = false; bool bSim = m_bSimulatedMergeStarted; while(bSuccess) { MergeFileInfos* pMFI = getMFI(miCurrent); if(pMFI == nullptr) { m_mergeItemList.clear(); m_bRealMergeStarted = false; break; } if(pMFI != nullptr && !bContinueWithCurrentItem) { if(bSim) { if(rowCount(miCurrent) == 0) { pMFI->m_bSimOpComplete = true; } } else { if(rowCount(miCurrent) == 0) { if(!pMFI->m_bOperationComplete) { setOpStatus(miCurrent, bSkipItem ? eOpStatusSkipped : eOpStatusDone); pMFI->m_bOperationComplete = true; bSkipItem = false; } } else { setOpStatus(miCurrent, eOpStatusInProgress); } } } if(!bContinueWithCurrentItem) { // Depth first QModelIndex miPrev = miCurrent; ++m_currentIndexForOperation; miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation; if((!miCurrent.isValid() || miCurrent.parent() != miPrev.parent()) && miPrev.parent().isValid()) { // Check if the parent may be set to "Done" QModelIndex miParent = miPrev.parent(); bool bDone = true; while(bDone && miParent.isValid()) { for(int childIdx = 0; childIdx < rowCount(miParent); ++childIdx) { pMFI = getMFI(index(childIdx, 0, miParent)); if((!bSim && !pMFI->m_bOperationComplete) || (bSim && pMFI->m_bSimOpComplete)) { bDone = false; break; } } if(bDone) { pMFI = getMFI(miParent); if(bSim) pMFI->m_bSimOpComplete = bDone; else { setOpStatus(miParent, eOpStatusDone); pMFI->m_bOperationComplete = bDone; } } miParent = miParent.parent(); } } } if(!miCurrent.isValid()) // end? { if(m_bRealMergeStarted) { if(bVerbose) { KMessageBox::information(q, i18n("Merge operation complete."), i18n("Merge Complete")); } m_bRealMergeStarted = false; m_pStatusInfo->setWindowTitle(i18n("Merge Complete")); } if(m_bSimulatedMergeStarted) { m_bSimulatedMergeStarted = false; QModelIndex mi = rowCount() > 0 ? index(0, 0, QModelIndex()) : QModelIndex(); for(; mi.isValid(); mi = treeIterator(mi)) { getMFI(mi)->m_bSimOpComplete = false; } m_pStatusInfo->setWindowTitle(i18n("Simulated merge complete: Check if you agree with the proposed operations.")); m_pStatusInfo->exec(); } m_mergeItemList.clear(); m_bRealMergeStarted = false; return; } MergeFileInfos& mfi = *getMFI(miCurrent); pp.setInformation(mfi.subPath(), bSim ? nrOfCompletedSimItems : nrOfCompletedItems, false // bRedrawUpdate ); bSuccess = executeMergeOperation(mfi, bSingleFileMerge); // Here the real operation happens. if(bSuccess) { if(bSim) ++nrOfCompletedSimItems; else ++nrOfCompletedItems; bContinueWithCurrentItem = false; } if(pp.wasCancelled()) break; } // end while //g_pProgressDialog->hide(); q->setCurrentIndex(miCurrent); q->scrollTo(miCurrent, EnsureVisible); if(!bSuccess && !bSingleFileMerge) { KMessageBox::error(q, i18n("An error occurred. Press OK to see detailed information.")); m_pStatusInfo->setWindowTitle(i18n("Merge Error")); m_pStatusInfo->exec(); //if ( m_pStatusInfo->firstChild()!=0 ) // m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() ); m_bError = true; setOpStatus(miCurrent, eOpStatusError); } else { m_bError = false; } emit q->updateAvailabilities(); if(m_currentIndexForOperation == m_mergeItemList.end()) { m_mergeItemList.clear(); m_bRealMergeStarted = false; } } bool DirectoryMergeWindow::Data::deleteFLD(const QString& name, bool bCreateBackup) { FileAccess fi(name, true); if(!fi.exists()) return true; if(bCreateBackup) { bool bSuccess = renameFLD(name, name + ".orig"); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: While deleting %1: Creating backup failed.", name)); return false; } } else { if(fi.isDir() && !fi.isSymLink()) m_pStatusInfo->addText(i18n("delete directory recursively( %1 )", name)); else m_pStatusInfo->addText(i18n("delete( %1 )", name)); if(m_bSimulatedMergeStarted) { return true; } if(fi.isDir() && !fi.isSymLink()) // recursive directory delete only for real dirs, not symlinks { t_DirectoryList dirList; bool bSuccess = fi.listDir(&dirList, false, true, "*", "", "", false, false); // not recursive, find hidden files if(!bSuccess) { // No Permission to read directory or other error. m_pStatusInfo->addText(i18n("Error: delete dir operation failed while trying to read the directory.")); return false; } t_DirectoryList::iterator it; // create list iterator for(it = dirList.begin(); it != dirList.end(); ++it) // for each file... { FileAccess& fi2 = *it; if(fi2.fileName() == "." || fi2.fileName() == "..") continue; bSuccess = deleteFLD(fi2.absoluteFilePath(), false); if(!bSuccess) break; } if(bSuccess) { bSuccess = FileAccess::removeDir(name); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: rmdir( %1 ) operation failed.", name)); return false; } } } else { bool bSuccess = FileAccess::removeFile(name); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: delete operation failed.")); return false; } } } return true; } bool DirectoryMergeWindow::Data::mergeFLD(const QString& nameA, const QString& nameB, const QString& nameC, const QString& nameDest, bool& bSingleFileMerge) { FileAccess fi(nameA); if(fi.isDir()) { return makeDir(nameDest); } // Make sure that the dir exists, into which we will save the file later. int pos = nameDest.lastIndexOf('/'); if(pos > 0) { QString parentName = nameDest.left(pos); bool bSuccess = makeDir(parentName, true /*quiet*/); if(!bSuccess) return false; } m_pStatusInfo->addText(i18n("manual merge( %1, %2, %3 -> %4)", nameA, nameB, nameC, nameDest)); if(m_bSimulatedMergeStarted) { m_pStatusInfo->addText(i18n(" Note: After a manual merge the user should continue by pressing F7.")); return true; } bSingleFileMerge = true; setOpStatus(*m_currentIndexForOperation, eOpStatusInProgress); q->scrollTo(*m_currentIndexForOperation, EnsureVisible); emit q->startDiffMerge(nameA, nameB, nameC, nameDest, "", "", "", nullptr); return false; } bool DirectoryMergeWindow::Data::copyFLD(const QString& srcName, const QString& destName) { bool bSuccess = false; if(srcName == destName) return true; FileAccess fi(srcName); FileAccess faDest(destName, true); if(faDest.exists() && !(fi.isDir() && faDest.isDir() && (fi.isSymLink() == faDest.isSymLink()))) { bSuccess = deleteFLD(destName, m_pOptions->m_bDmCreateBakFiles); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: copy( %1 -> %2 ) failed." "Deleting existing destination failed.", srcName, destName)); return bSuccess; } } if(fi.isSymLink() && ((fi.isDir() && !m_bFollowDirLinks) || (!fi.isDir() && !m_bFollowFileLinks))) { m_pStatusInfo->addText(i18n("copyLink( %1 -> %2 )", srcName, destName)); if(m_bSimulatedMergeStarted) { return true; } FileAccess destFi(destName); if(!destFi.isLocal() || !fi.isLocal()) { m_pStatusInfo->addText(i18n("Error: copyLink failed: Remote links are not yet supported.")); return false; } bSuccess = false; QString linkTarget = fi.readLink(); if(!linkTarget.isEmpty()) { bSuccess = FileAccess::symLink(linkTarget, destName); if(!bSuccess) m_pStatusInfo->addText(i18n("Error: copyLink failed.")); } return bSuccess; } if(fi.isDir()) { if(faDest.exists()) return true; else { bSuccess = makeDir(destName); return bSuccess; } } int pos = destName.lastIndexOf('/'); if(pos > 0) { QString parentName = destName.left(pos); bSuccess = makeDir(parentName, true /*quiet*/); if(!bSuccess) return false; } m_pStatusInfo->addText(i18n("copy( %1 -> %2 )", srcName, destName)); if(m_bSimulatedMergeStarted) { return true; } FileAccess faSrc(srcName); bSuccess = faSrc.copyFile(destName); if(!bSuccess) m_pStatusInfo->addText(faSrc.getStatusText()); return bSuccess; } // Rename is not an operation that can be selected by the user. // It will only be used to create backups. // Hence it will delete an existing destination without making a backup (of the old backup.) bool DirectoryMergeWindow::Data::renameFLD(const QString& srcName, const QString& destName) { if(srcName == destName) return true; if(FileAccess(destName, true).exists()) { bool bSuccess = deleteFLD(destName, false /*no backup*/); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error during rename( %1 -> %2 ): " "Cannot delete existing destination.", srcName, destName)); return false; } } m_pStatusInfo->addText(i18n("rename( %1 -> %2 )", srcName, destName)); if(m_bSimulatedMergeStarted) { return true; } bool bSuccess = FileAccess(srcName).rename(destName); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: Rename failed.")); return false; } return true; } bool DirectoryMergeWindow::Data::makeDir(const QString& name, bool bQuiet) { FileAccess fi(name, true); if(fi.exists() && fi.isDir()) return true; if(fi.exists() && !fi.isDir()) { bool bSuccess = deleteFLD(name, true); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error during makeDir of %1. " "Cannot delete existing file.", name)); return false; } } int pos = name.lastIndexOf('/'); if(pos > 0) { QString parentName = name.left(pos); bool bSuccess = makeDir(parentName, true); if(!bSuccess) return false; } if(!bQuiet) m_pStatusInfo->addText(i18n("makeDir( %1 )", name)); if(m_bSimulatedMergeStarted) { return true; } bool bSuccess = FileAccess::makeDir(name); if(bSuccess == false) { m_pStatusInfo->addText(i18n("Error while creating directory.")); return false; } return true; } DirectoryMergeInfo::DirectoryMergeInfo(QWidget* pParent) : QFrame(pParent) { QVBoxLayout* topLayout = new QVBoxLayout(this); topLayout->setMargin(0); QGridLayout* grid = new QGridLayout(); topLayout->addLayout(grid); grid->setColumnStretch(1, 10); int line = 0; m_pA = new QLabel(i18n("A"), this); grid->addWidget(m_pA, line, 0); m_pInfoA = new QLabel(this); grid->addWidget(m_pInfoA, line, 1); ++line; m_pB = new QLabel(i18n("B"), this); grid->addWidget(m_pB, line, 0); m_pInfoB = new QLabel(this); grid->addWidget(m_pInfoB, line, 1); ++line; m_pC = new QLabel(i18n("C"), this); grid->addWidget(m_pC, line, 0); m_pInfoC = new QLabel(this); grid->addWidget(m_pInfoC, line, 1); ++line; m_pDest = new QLabel(i18n("Dest"), this); grid->addWidget(m_pDest, line, 0); m_pInfoDest = new QLabel(this); grid->addWidget(m_pInfoDest, line, 1); ++line; m_pInfoList = new QTreeWidget(this); topLayout->addWidget(m_pInfoList); m_pInfoList->setHeaderLabels(QStringList() << i18n("Dir") << i18n("Type") << i18n("Size") << i18n("Attr") << i18n("Last Modification") << i18n("Link-Destination")); setMinimumSize(100, 100); m_pInfoList->installEventFilter(this); m_pInfoList->setRootIsDecorated(false); } bool DirectoryMergeInfo::eventFilter(QObject* o, QEvent* e) { if(e->type() == QEvent::FocusIn && o == m_pInfoList) emit gotFocus(); return false; } void DirectoryMergeInfo::addListViewItem(const QString& dir, const QString& basePath, FileAccess* fi) { if(basePath.isEmpty()) { return; } else { if(fi != nullptr && fi->exists()) { QString dateString = fi->lastModified().toString("yyyy-MM-dd hh:mm:ss"); //TODO: Move logic to FileAccess m_pInfoList->addTopLevelItem( new QTreeWidgetItem( m_pInfoList, QStringList() << dir << QString(fi->isDir() ? i18n("Dir") : i18n("File")) + (fi->isSymLink() ? i18n("-Link") : "") << QString::number(fi->size()) << QLatin1String(fi->isReadable() ? "r" : " ") + QLatin1String(fi->isWritable() ? "w" : " ") + QLatin1String((fi->isExecutable() ? "x" : " ")) << dateString << QString(fi->isSymLink() ? (" -> " + fi->readLink()) : QString(""))) ); } else { m_pInfoList->addTopLevelItem(new QTreeWidgetItem( m_pInfoList, QStringList() << dir << i18n("not available") << "" << "" << "" << "")); } } } void DirectoryMergeInfo::setInfo( const FileAccess& dirA, const FileAccess& dirB, const FileAccess& dirC, const FileAccess& dirDest, MergeFileInfos& mfi) { bool bHideDest = false; if(dirA.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pA->setText(i18n("A (Dest): ")); bHideDest = true; } else m_pA->setText(!dirC.isValid() ? QString("A: ") : i18n("A (Base): ")); m_pInfoA->setText(dirA.prettyAbsPath()); if(dirB.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pB->setText(i18n("B (Dest): ")); bHideDest = true; } else m_pB->setText("B: "); m_pInfoB->setText(dirB.prettyAbsPath()); if(dirC.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pC->setText(i18n("C (Dest): ")); bHideDest = true; } else m_pC->setText("C: "); m_pInfoC->setText(dirC.prettyAbsPath()); m_pDest->setText(i18n("Dest: ")); m_pInfoDest->setText(dirDest.prettyAbsPath()); if(!dirC.isValid()) { m_pC->hide(); m_pInfoC->hide(); } else { m_pC->show(); m_pInfoC->show(); } if(!dirDest.isValid() || bHideDest) { m_pDest->hide(); m_pInfoDest->hide(); } else { m_pDest->show(); m_pInfoDest->show(); } m_pInfoList->clear(); addListViewItem(i18n("A"), dirA.prettyAbsPath(), mfi.m_pFileInfoA); addListViewItem(i18n("B"), dirB.prettyAbsPath(), mfi.m_pFileInfoB); addListViewItem(i18n("C"), dirC.prettyAbsPath(), mfi.m_pFileInfoC); if(!bHideDest) { FileAccess fiDest(dirDest.prettyAbsPath() + "/" + mfi.subPath(), true); addListViewItem(i18n("Dest"), dirDest.prettyAbsPath(), &fiDest); } for(int i = 0; i < m_pInfoList->columnCount(); ++i) m_pInfoList->resizeColumnToContents(i); } QTextStream& operator<<(QTextStream& ts, MergeFileInfos& mfi) { ts << "{\n"; ValueMap vm; vm.writeEntry("SubPath", mfi.subPath()); vm.writeEntry("ExistsInA", mfi.existsInA()); vm.writeEntry("ExistsInB", mfi.existsInB()); vm.writeEntry("ExistsInC", mfi.existsInC()); vm.writeEntry("EqualAB", mfi.m_bEqualAB); vm.writeEntry("EqualAC", mfi.m_bEqualAC); vm.writeEntry("EqualBC", mfi.m_bEqualBC); //DirMergeItem* m_pDMI; //MergeFileInfos* m_pParent; vm.writeEntry("MergeOperation", (int)mfi.m_eMergeOperation); vm.writeEntry("DirA", mfi.dirA()); vm.writeEntry("DirB", mfi.dirB()); vm.writeEntry("DirC", mfi.dirC()); vm.writeEntry("LinkA", mfi.isLinkA()); vm.writeEntry("LinkB", mfi.isLinkB()); vm.writeEntry("LinkC", mfi.isLinkC()); vm.writeEntry("OperationComplete", mfi.m_bOperationComplete); //bool m_bSimOpComplete ); vm.writeEntry("AgeA", (int)mfi.m_ageA); vm.writeEntry("AgeB", (int)mfi.m_ageB); vm.writeEntry("AgeC", (int)mfi.m_ageC); vm.writeEntry("ConflictingAges", mfi.m_bConflictingAges); // Equal age but files are not! //FileAccess m_fileInfoA; //FileAccess m_fileInfoB; //FileAccess m_fileInfoC; //TotalDiffStatus m_totalDiffStatus; vm.save(ts); ts << "}\n"; return ts; } void DirectoryMergeWindow::slotSaveMergeState() { //slotStatusMsg(i18n("Saving Directory Merge State ...")); //QString s = KFileDialog::getSaveUrl( QDir::currentPath(), 0, this, i18n("Save As...") ).url(); QString s = QFileDialog::getSaveFileName(this, i18n("Save Directory Merge State As..."), QDir::currentPath()); if(!s.isEmpty()) { d->m_dirMergeStateFilename = s; QFile file(d->m_dirMergeStateFilename); bool bSuccess = file.open(QIODevice::WriteOnly); if(bSuccess) { QTextStream ts(&file); QModelIndex mi(d->index(0, 0, QModelIndex())); while(mi.isValid()) { MergeFileInfos* pMFI = d->getMFI(mi); ts << *pMFI; mi = d->treeIterator(mi, true, true); } } } //slotStatusMsg(i18n("Ready.")); } void DirectoryMergeWindow::slotLoadMergeState() { } void DirectoryMergeWindow::updateFileVisibilities() { bool bShowIdentical = d->m_pDirShowIdenticalFiles->isChecked(); bool bShowDifferent = d->m_pDirShowDifferentFiles->isChecked(); bool bShowOnlyInA = d->m_pDirShowFilesOnlyInA->isChecked(); bool bShowOnlyInB = d->m_pDirShowFilesOnlyInB->isChecked(); bool bShowOnlyInC = d->m_pDirShowFilesOnlyInC->isChecked(); bool bThreeDirs = d->m_dirC.isValid(); d->m_selection1Index = QModelIndex(); d->m_selection2Index = QModelIndex(); d->m_selection3Index = QModelIndex(); // in first run set all dirs to equal and determine if they are not equal. // on second run don't change the equal-status anymore; it is needed to // set the visibility (when bShowIdentical is false). for(int loop = 0; loop < 2; ++loop) { QModelIndex mi = d->rowCount() > 0 ? d->index(0, 0, QModelIndex()) : QModelIndex(); while(mi.isValid()) { MergeFileInfos* pMFI = d->getMFI(mi); bool bDir = pMFI->dirA() || pMFI->dirB() || pMFI->dirC(); if(loop == 0 && bDir) { bool bChange = false; if(!pMFI->m_bEqualAB && pMFI->dirA() == pMFI->dirB() && pMFI->isLinkA() == pMFI->isLinkB()) { pMFI->m_bEqualAB = true; bChange = true; } if(!pMFI->m_bEqualBC && pMFI->dirC() == pMFI->dirB() && pMFI->isLinkC() == pMFI->isLinkB()) { pMFI->m_bEqualBC = true; bChange = true; } if(!pMFI->m_bEqualAC && pMFI->dirA() == pMFI->dirC() && pMFI->isLinkA() == pMFI->isLinkC()) { pMFI->m_bEqualAC = true; bChange = true; } if(bChange) setPixmaps(*pMFI, bThreeDirs); } bool bExistsEverywhere = pMFI->existsInA() && pMFI->existsInB() && (pMFI->existsInC() || !bThreeDirs); int existCount = int(pMFI->existsInA()) + int(pMFI->existsInB()) + int(pMFI->existsInC()); bool bVisible = (bShowIdentical && bExistsEverywhere && pMFI->m_bEqualAB && (pMFI->m_bEqualAC || !bThreeDirs)) || ((bShowDifferent || bDir) && existCount >= 2 && (!pMFI->m_bEqualAB || !(pMFI->m_bEqualAC || !bThreeDirs))) || (bShowOnlyInA && pMFI->existsInA() && !pMFI->existsInB() && !pMFI->existsInC()) || (bShowOnlyInB && !pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC()) || (bShowOnlyInC && !pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC()); QString fileName = pMFI->fileName(); bVisible = bVisible && ((bDir && !wildcardMultiMatch(d->m_pOptions->m_DmDirAntiPattern, fileName, d->m_bCaseSensitive)) || (wildcardMultiMatch(d->m_pOptions->m_DmFilePattern, fileName, d->m_bCaseSensitive) && !wildcardMultiMatch(d->m_pOptions->m_DmFileAntiPattern, fileName, d->m_bCaseSensitive))); setRowHidden(mi.row(), mi.parent(), !bVisible); bool bEqual = bThreeDirs ? pMFI->m_bEqualAB && pMFI->m_bEqualAC : pMFI->m_bEqualAB; if(!bEqual && bVisible && loop == 0) // Set all parents to "not equal" { MergeFileInfos* p2 = pMFI->m_pParent; while(p2 != nullptr) { bool bChange = false; if(!pMFI->m_bEqualAB && p2->m_bEqualAB) { p2->m_bEqualAB = false; bChange = true; } if(!pMFI->m_bEqualAC && p2->m_bEqualAC) { p2->m_bEqualAC = false; bChange = true; } if(!pMFI->m_bEqualBC && p2->m_bEqualBC) { p2->m_bEqualBC = false; bChange = true; } if(bChange) setPixmaps(*p2, bThreeDirs); else break; p2 = p2->m_pParent; } } mi = d->treeIterator(mi, true, true); } } } void DirectoryMergeWindow::slotShowIdenticalFiles() { d->m_pOptions->m_bDmShowIdenticalFiles = d->m_pDirShowIdenticalFiles->isChecked(); updateFileVisibilities(); } void DirectoryMergeWindow::slotShowDifferentFiles() { updateFileVisibilities(); } void DirectoryMergeWindow::slotShowFilesOnlyInA() { updateFileVisibilities(); } void DirectoryMergeWindow::slotShowFilesOnlyInB() { updateFileVisibilities(); } void DirectoryMergeWindow::slotShowFilesOnlyInC() { updateFileVisibilities(); } void DirectoryMergeWindow::slotSynchronizeDirectories() {} void DirectoryMergeWindow::slotChooseNewerFiles() {} void DirectoryMergeWindow::initDirectoryMergeActions(QObject* pKDiff3App, KActionCollection* ac) { #include "xpm/showequalfiles.xpm" #include "xpm/showfilesonlyina.xpm" #include "xpm/showfilesonlyinb.xpm" #include "xpm/showfilesonlyinc.xpm" #include "xpm/startmerge.xpm" DirectoryMergeWindow* p = this; d->m_pDirStartOperation = KDiff3::createAction(i18n("Start/Continue Directory Merge"), QKeySequence(Qt::Key_F7), p, SLOT(slotRunOperationForAllItems()), ac, "dir_start_operation"); d->m_pDirRunOperationForCurrentItem = KDiff3::createAction(i18n("Run Operation for Current Item"), QKeySequence(Qt::Key_F6), p, SLOT(slotRunOperationForCurrentItem()), ac, "dir_run_operation_for_current_item"); d->m_pDirCompareCurrent = KDiff3::createAction(i18n("Compare Selected File"), p, SLOT(compareCurrentFile()), ac, "dir_compare_current"); d->m_pDirMergeCurrent = KDiff3::createAction(i18n("Merge Current File"), QIcon(QPixmap(startmerge)), i18n("Merge\nFile"), pKDiff3App, SLOT(slotMergeCurrentFile()), ac, "merge_current"); d->m_pDirFoldAll = KDiff3::createAction(i18n("Fold All Subdirs"), p, SLOT(collapseAll()), ac, "dir_fold_all"); d->m_pDirUnfoldAll = KDiff3::createAction(i18n("Unfold All Subdirs"), p, SLOT(expandAll()), ac, "dir_unfold_all"); d->m_pDirRescan = KDiff3::createAction(i18n("Rescan"), QKeySequence(Qt::SHIFT + Qt::Key_F5), p, SLOT(reload()), ac, "dir_rescan"); d->m_pDirSaveMergeState = nullptr; //KDiff3::createAction< QAction >(i18n("Save Directory Merge State ..."), 0, p, SLOT(slotSaveMergeState()), ac, "dir_save_merge_state"); d->m_pDirLoadMergeState = nullptr; //KDiff3::createAction< QAction >(i18n("Load Directory Merge State ..."), 0, p, SLOT(slotLoadMergeState()), ac, "dir_load_merge_state"); d->m_pDirChooseAEverywhere = KDiff3::createAction(i18n("Choose A for All Items"), p, SLOT(slotChooseAEverywhere()), ac, "dir_choose_a_everywhere"); d->m_pDirChooseBEverywhere = KDiff3::createAction(i18n("Choose B for All Items"), p, SLOT(slotChooseBEverywhere()), ac, "dir_choose_b_everywhere"); d->m_pDirChooseCEverywhere = KDiff3::createAction(i18n("Choose C for All Items"), p, SLOT(slotChooseCEverywhere()), ac, "dir_choose_c_everywhere"); d->m_pDirAutoChoiceEverywhere = KDiff3::createAction(i18n("Auto-Choose Operation for All Items"), p, SLOT(slotAutoChooseEverywhere()), ac, "dir_autochoose_everywhere"); d->m_pDirDoNothingEverywhere = KDiff3::createAction(i18n("No Operation for All Items"), p, SLOT(slotNoOpEverywhere()), ac, "dir_nothing_everywhere"); // d->m_pDirSynchronizeDirectories = KDiff3::createAction< KToggleAction >(i18n("Synchronize Directories"), 0, this, SLOT(slotSynchronizeDirectories()), ac, "dir_synchronize_directories"); // d->m_pDirChooseNewerFiles = KDiff3::createAction< KToggleAction >(i18n("Copy Newer Files Instead of Merging"), 0, this, SLOT(slotChooseNewerFiles()), ac, "dir_choose_newer_files"); d->m_pDirShowIdenticalFiles = KDiff3::createAction(i18n("Show Identical Files"), QIcon(QPixmap(showequalfiles)), i18n("Identical\nFiles"), this, SLOT(slotShowIdenticalFiles()), ac, "dir_show_identical_files"); d->m_pDirShowDifferentFiles = KDiff3::createAction(i18n("Show Different Files"), this, SLOT(slotShowDifferentFiles()), ac, "dir_show_different_files"); d->m_pDirShowFilesOnlyInA = KDiff3::createAction(i18n("Show Files only in A"), QIcon(QPixmap(showfilesonlyina)), i18n("Files\nonly in A"), this, SLOT(slotShowFilesOnlyInA()), ac, "dir_show_files_only_in_a"); d->m_pDirShowFilesOnlyInB = KDiff3::createAction(i18n("Show Files only in B"), QIcon(QPixmap(showfilesonlyinb)), i18n("Files\nonly in B"), this, SLOT(slotShowFilesOnlyInB()), ac, "dir_show_files_only_in_b"); d->m_pDirShowFilesOnlyInC = KDiff3::createAction(i18n("Show Files only in C"), QIcon(QPixmap(showfilesonlyinc)), i18n("Files\nonly in C"), this, SLOT(slotShowFilesOnlyInC()), ac, "dir_show_files_only_in_c"); d->m_pDirShowIdenticalFiles->setChecked(d->m_pOptions->m_bDmShowIdenticalFiles); d->m_pDirCompareExplicit = KDiff3::createAction(i18n("Compare Explicitly Selected Files"), p, SLOT(slotCompareExplicitlySelectedFiles()), ac, "dir_compare_explicitly_selected_files"); d->m_pDirMergeExplicit = KDiff3::createAction(i18n("Merge Explicitly Selected Files"), p, SLOT(slotMergeExplicitlySelectedFiles()), ac, "dir_merge_explicitly_selected_files"); d->m_pDirCurrentDoNothing = KDiff3::createAction(i18n("Do Nothing"), p, SLOT(slotCurrentDoNothing()), ac, "dir_current_do_nothing"); d->m_pDirCurrentChooseA = KDiff3::createAction(i18n("A"), p, SLOT(slotCurrentChooseA()), ac, "dir_current_choose_a"); d->m_pDirCurrentChooseB = KDiff3::createAction(i18n("B"), p, SLOT(slotCurrentChooseB()), ac, "dir_current_choose_b"); d->m_pDirCurrentChooseC = KDiff3::createAction(i18n("C"), p, SLOT(slotCurrentChooseC()), ac, "dir_current_choose_c"); d->m_pDirCurrentMerge = KDiff3::createAction(i18n("Merge"), p, SLOT(slotCurrentMerge()), ac, "dir_current_merge"); d->m_pDirCurrentDelete = KDiff3::createAction(i18n("Delete (if exists)"), p, SLOT(slotCurrentDelete()), ac, "dir_current_delete"); d->m_pDirCurrentSyncDoNothing = KDiff3::createAction(i18n("Do Nothing"), p, SLOT(slotCurrentDoNothing()), ac, "dir_current_sync_do_nothing"); d->m_pDirCurrentSyncCopyAToB = KDiff3::createAction(i18n("Copy A to B"), p, SLOT(slotCurrentCopyAToB()), ac, "dir_current_sync_copy_a_to_b"); d->m_pDirCurrentSyncCopyBToA = KDiff3::createAction(i18n("Copy B to A"), p, SLOT(slotCurrentCopyBToA()), ac, "dir_current_sync_copy_b_to_a"); d->m_pDirCurrentSyncDeleteA = KDiff3::createAction(i18n("Delete A"), p, SLOT(slotCurrentDeleteA()), ac, "dir_current_sync_delete_a"); d->m_pDirCurrentSyncDeleteB = KDiff3::createAction(i18n("Delete B"), p, SLOT(slotCurrentDeleteB()), ac, "dir_current_sync_delete_b"); d->m_pDirCurrentSyncDeleteAAndB = KDiff3::createAction(i18n("Delete A && B"), p, SLOT(slotCurrentDeleteAAndB()), ac, "dir_current_sync_delete_a_and_b"); d->m_pDirCurrentSyncMergeToA = KDiff3::createAction(i18n("Merge to A"), p, SLOT(slotCurrentMergeToA()), ac, "dir_current_sync_merge_to_a"); d->m_pDirCurrentSyncMergeToB = KDiff3::createAction(i18n("Merge to B"), p, SLOT(slotCurrentMergeToB()), ac, "dir_current_sync_merge_to_b"); d->m_pDirCurrentSyncMergeToAAndB = KDiff3::createAction(i18n("Merge to A && B"), p, SLOT(slotCurrentMergeToAAndB()), ac, "dir_current_sync_merge_to_a_and_b"); } void DirectoryMergeWindow::updateAvailabilities(bool bDirCompare, bool bDiffWindowVisible, KToggleAction* chooseA, KToggleAction* chooseB, KToggleAction* chooseC) { d->m_pDirStartOperation->setEnabled(bDirCompare); d->m_pDirRunOperationForCurrentItem->setEnabled(bDirCompare); d->m_pDirFoldAll->setEnabled(bDirCompare); d->m_pDirUnfoldAll->setEnabled(bDirCompare); d->m_pDirCompareCurrent->setEnabled(bDirCompare && isVisible() && isFileSelected()); d->m_pDirMergeCurrent->setEnabled((bDirCompare && isVisible() && isFileSelected()) || bDiffWindowVisible); d->m_pDirRescan->setEnabled(bDirCompare); d->m_pDirAutoChoiceEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirDoNothingEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseAEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseBEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseCEverywhere->setEnabled(bDirCompare && isVisible()); bool bThreeDirs = d->m_dirC.isValid(); MergeFileInfos* pMFI = d->getMFI(currentIndex()); bool bItemActive = bDirCompare && isVisible() && pMFI != nullptr; // && hasFocus(); bool bMergeMode = bThreeDirs || !d->m_bSyncMode; bool bFTConflict = pMFI == nullptr ? false : conflictingFileTypes(*pMFI); bool bDirWindowHasFocus = isVisible() && hasFocus(); d->m_pDirShowIdenticalFiles->setEnabled(bDirCompare && isVisible()); d->m_pDirShowDifferentFiles->setEnabled(bDirCompare && isVisible()); d->m_pDirShowFilesOnlyInA->setEnabled(bDirCompare && isVisible()); d->m_pDirShowFilesOnlyInB->setEnabled(bDirCompare && isVisible()); d->m_pDirShowFilesOnlyInC->setEnabled(bDirCompare && isVisible() && bThreeDirs); d->m_pDirCompareExplicit->setEnabled(bDirCompare && isVisible() && d->m_selection2Index.isValid()); d->m_pDirMergeExplicit->setEnabled(bDirCompare && isVisible() && d->m_selection2Index.isValid()); d->m_pDirCurrentDoNothing->setEnabled(bItemActive && bMergeMode); d->m_pDirCurrentChooseA->setEnabled(bItemActive && bMergeMode && pMFI->existsInA()); d->m_pDirCurrentChooseB->setEnabled(bItemActive && bMergeMode && pMFI->existsInB()); d->m_pDirCurrentChooseC->setEnabled(bItemActive && bMergeMode && pMFI->existsInC()); d->m_pDirCurrentMerge->setEnabled(bItemActive && bMergeMode && !bFTConflict); d->m_pDirCurrentDelete->setEnabled(bItemActive && bMergeMode); if(bDirWindowHasFocus) { chooseA->setEnabled(bItemActive && pMFI->existsInA()); chooseB->setEnabled(bItemActive && pMFI->existsInB()); chooseC->setEnabled(bItemActive && pMFI->existsInC()); chooseA->setChecked(false); chooseB->setChecked(false); chooseC->setChecked(false); } d->m_pDirCurrentSyncDoNothing->setEnabled(bItemActive && !bMergeMode); d->m_pDirCurrentSyncCopyAToB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA()); d->m_pDirCurrentSyncCopyBToA->setEnabled(bItemActive && !bMergeMode && pMFI->existsInB()); d->m_pDirCurrentSyncDeleteA->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA()); d->m_pDirCurrentSyncDeleteB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInB()); d->m_pDirCurrentSyncDeleteAAndB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA() && pMFI->existsInB()); d->m_pDirCurrentSyncMergeToA->setEnabled(bItemActive && !bMergeMode && !bFTConflict); d->m_pDirCurrentSyncMergeToB->setEnabled(bItemActive && !bMergeMode && !bFTConflict); d->m_pDirCurrentSyncMergeToAAndB->setEnabled(bItemActive && !bMergeMode && !bFTConflict); } //#include "directorymergewindow.moc" diff --git a/src/fileaccess.cpp b/src/fileaccess.cpp index 1ed6dcb..97793af 100644 --- a/src/fileaccess.cpp +++ b/src/fileaccess.cpp @@ -1,1286 +1,1300 @@ /*************************************************************************** * Copyright (C) 2003-2011 by Joachim Eibl * * joachim.eibl at gmx.de * * Copyright (C) Michael Reeves reeves.87@gmail.com * * * * 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. * ***************************************************************************/ #include "fileaccess.h" #include "cvsignorelist.h" #include "common.h" #include "progress.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #include #include #include #else #include // Needed for creating symbolic links via symlink(). #include #endif FileAccess::FileAccess(const QString& name, bool bWantToWrite) { reset(); setFile(name, bWantToWrite); } FileAccess::FileAccess() { reset(); } - - void FileAccess::reset(void) { m_fileInfo = QFileInfo(); m_filePath = ""; m_bExists = false; m_bFile = false; m_bDir = false; m_bSymLink = false; m_bWritable = false; m_bHidden = false; m_size = 0; m_modificationTime = QDateTime(); m_url = QUrl(); m_bValidData = false; m_name = QString(); m_linkTarget = ""; //m_fileType = -1; m_pParent = nullptr; - //Insure that old tempFile is removed we will recreate it as needed. - if(!m_localCopy.isEmpty()) - { - removeTempFile(m_localCopy); - m_localCopy = ""; - } + tmpFile.clear(); + tmpFile = QSharedPointer(new QTemporaryFile()); } FileAccess::~FileAccess() { - //remove tempfile if any - if(!m_localCopy.isEmpty()) - { - removeTempFile(m_localCopy); - m_localCopy = ""; - } + tmpFile.clear(); } // Two kinds of optimization are applied here: // 1. Speed: don't ask for data as long as it is not needed or cheap to get. // When opening a file it is easy enough to ask for details. // 2. Memory usage: Don't store data that is not needed, and avoid redundancy. // For recursive directory trees don't store the full path if a parent is available. // Store urls only if files are not local. void FileAccess::setFile(const QFileInfo& fi, FileAccess* pParent) { reset(); m_fileInfo = fi; m_fileInfo.setCaching(true); m_filePath = pParent == nullptr ? fi.absoluteFilePath() : fi.fileName(); m_bSymLink = fi.isSymLink(); m_pParent = pParent; m_bFile = fi.isFile(); m_bDir = fi.isDir(); m_bExists = fi.exists(); m_size = fi.size(); m_modificationTime = fi.lastModified(); m_bHidden = fi.isHidden(); m_bWritable = m_fileInfo.isWritable(); m_bReadable = m_fileInfo.isReadable(); m_bExecutable = m_fileInfo.isExecutable(); if(isLocal()) { m_name = m_fileInfo.fileName(); if(m_bSymLink) { m_linkTarget = m_fileInfo.readLink(); #ifndef Q_OS_WIN // Unfortunately Qt5 symLinkTarget/readLink always returns an absolute path, even if the link is relative char *s=(char*)malloc(PATH_MAX+1); ssize_t len = readlink(QFile::encodeName(fi.absoluteFilePath()).constData(), s, PATH_MAX); if(len > 0) { s[len] = '\0'; m_linkTarget = QFile::decodeName(s); } free(s); #endif } m_bValidData = true; m_url = QUrl::fromLocalFile(m_fileInfo.filePath()); if(m_url.isRelative()) { m_url.setPath(absoluteFilePath()); } } } void FileAccess::setFile(const QString& name, bool bWantToWrite) { reset(); //Nothing to do here. if(name.isEmpty()) return; QUrl url = QUrl::fromUserInput(name, QString(), QUrl::AssumeLocalFile); m_url = url; m_name = m_url.fileName(); // FileAccess tries to detect if the given name is an URL or a local file. // This is a problem if the filename looks like an URL (i.e. contains a colon ':'). // e.g. "file:f.txt" is a valid filename. // Most of the time it is sufficient to check if the file exists locally. // 2 Problems remain: // 1. When the local file exists and the remote location is wanted nevertheless. (unlikely) // 2. When the local file doesn't exist and should be written to. bool bExistsLocal = QDir().exists(name); if(url.isLocalFile() || url.isRelative() || !url.isValid() || bExistsLocal) // Treate invalid urls as relative. { QString localName = url.path(); #if defined(Q_OS_WIN) if(localName.startsWith(QLatin1String("/tmp/"))) { // git on Cygwin will put files in /tmp // A workaround for the a native kdiff3 binary to find them... QString cygwinBin = QLatin1String(qgetenv("CYGWIN_BIN")); if(!cygwinBin.isEmpty()) { localName = QString("%1\\..%2").arg(cygwinBin).arg(name); } } #endif QFileInfo fi(localName); setFile(fi, nullptr); } else { FileAccessJobHandler jh(this); // A friend, which writes to the parameters of this class! jh.stat(2 /*all details*/, bWantToWrite); // returns bSuccess, ignored m_filePath = name; m_bValidData = true; // After running stat() the variables are initialised // and valid even if the file doesn't exist and the stat // query failed. } } void FileAccess::addPath(const QString& txt) { if(!isLocal() && m_url.isValid()) { QUrl url = m_url.adjusted(QUrl::StripTrailingSlash); m_url.setPath(url.path() + '/' + txt); setFile(m_url.url()); // reinitialise } else { QString slash = (txt.isEmpty() || txt[0] == '/') ? QLatin1String("") : QLatin1String("/"); setFile(absoluteFilePath() + slash + txt); } } /* Filetype: S_IFMT 0170000 bitmask for the file type bitfields S_IFSOCK 0140000 socket S_IFLNK 0120000 symbolic link S_IFREG 0100000 regular file S_IFBLK 0060000 block device S_IFDIR 0040000 directory S_IFCHR 0020000 character device S_IFIFO 0010000 fifo S_ISUID 0004000 set UID bit S_ISGID 0002000 set GID bit (see below) S_ISVTX 0001000 sticky bit (see below) Access: S_IRWXU 00700 mask for file owner permissions S_IRUSR 00400 owner has read permission S_IWUSR 00200 owner has write permission S_IXUSR 00100 owner has execute permission S_IRWXG 00070 mask for group permissions S_IRGRP 00040 group has read permission S_IWGRP 00020 group has write permission S_IXGRP 00010 group has execute permission S_IRWXO 00007 mask for permissions for others (not in group) S_IROTH 00004 others have read permission S_IWOTH 00002 others have write permisson S_IXOTH 00001 others have execute permission */ void FileAccess::setUdsEntry(const KIO::UDSEntry& e) { long acc = 0; long fileType = 0; QVector fields = e.fields(); for(QVector::ConstIterator ei = fields.constBegin(); ei != fields.constEnd(); ++ei) { uint f = *ei; switch(f) { case KIO::UDSEntry::UDS_SIZE: m_size = e.numberValue(f); break; case KIO::UDSEntry::UDS_NAME: m_filePath = e.stringValue(f); break; // During listDir the relative path is given here. case KIO::UDSEntry::UDS_MODIFICATION_TIME: m_modificationTime = QDateTime::fromMSecsSinceEpoch(e.numberValue(f)); break; case KIO::UDSEntry::UDS_LINK_DEST: m_linkTarget = e.stringValue(f); break; case KIO::UDSEntry::UDS_ACCESS: { #ifndef Q_OS_WIN acc = e.numberValue(f); m_bReadable = (acc & S_IRUSR) != 0; m_bWritable = (acc & S_IWUSR) != 0; m_bExecutable = (acc & S_IXUSR) != 0; #endif break; } case KIO::UDSEntry::UDS_FILE_TYPE: { fileType = e.numberValue(f); m_bDir = (fileType & QT_STAT_MASK) == QT_STAT_DIR; m_bFile = (fileType & QT_STAT_MASK) == QT_STAT_REG; m_bSymLink = (fileType & QT_STAT_MASK) == QT_STAT_LNK; m_bExists = fileType != 0; //m_fileType = fileType; break; } case KIO::UDSEntry::UDS_URL: // m_url = QUrl( e.stringValue(f) ); break; case KIO::UDSEntry::UDS_MIME_TYPE: break; case KIO::UDSEntry::UDS_GUESSED_MIME_TYPE: break; case KIO::UDSEntry::UDS_XML_PROPERTIES: break; default: break; } } m_fileInfo = QFileInfo(m_filePath); m_fileInfo.setCaching(true); if(m_url.isEmpty()) m_url = QUrl::fromUserInput(m_fileInfo.absoluteFilePath()); m_name = m_url.fileName(); m_bExists = m_fileInfo.exists(); //insure modifcation time is initialized if it wasn't already. if(m_modificationTime.isNull()) m_modificationTime = m_fileInfo.lastModified(); m_bValidData = true; m_bSymLink = !m_linkTarget.isEmpty(); if(m_name.isEmpty()) { m_name = m_fileInfo.fileName(); } #ifndef Q_OS_WIN m_bHidden = m_name[0] == '.'; #endif } bool FileAccess::isValid() const { return !m_filePath.isEmpty() || m_bValidData; } bool FileAccess::isNormal() const { return isFile() || isDir() || isSymLink(); } bool FileAccess::isFile() const { if(parent() || !isLocal()) return m_bFile; else return QFileInfo(absoluteFilePath()).isFile(); } bool FileAccess::isDir() const { if(isLocal()) return m_bDir; else return QFileInfo(absoluteFilePath()).isDir(); } bool FileAccess::isSymLink() const { return m_bSymLink; } bool FileAccess::exists() const { if(isLocal()) return m_bExists; else return QFileInfo::exists(absoluteFilePath()); } qint64 FileAccess::size() const { if(isLocal()) return m_size; else return QFileInfo(absoluteFilePath()).size(); } QUrl FileAccess::url() const { QUrl url = m_url; if(url.isLocalFile() && url.isRelative()) { url.setPath(absoluteFilePath()); } return url; } bool FileAccess::isLocal() const { return m_url.isLocalFile() || !m_url.isValid(); } bool FileAccess::isReadable() const { //This can be very slow in some network setups so use cached value if(!isLocal()) return m_bReadable; else return m_fileInfo.isReadable(); } bool FileAccess::isWritable() const { //This can be very slow in some network setups so use cached value if(parent() || !isLocal()) return m_bWritable; else return m_fileInfo.isWritable(); } bool FileAccess::isExecutable() const { //This can be very slow in some network setups so use cached value if(!isLocal()) return m_bExecutable; else return m_fileInfo.isExecutable(); } bool FileAccess::isHidden() const { if(!(isLocal())) return m_bHidden; else return m_fileInfo.isHidden(); } QString FileAccess::readLink() const { return m_linkTarget; } QString FileAccess::absoluteFilePath() const { if(parent() != nullptr) return parent()->absoluteFilePath() + "/" + m_filePath; else { if(!isLocal()) return m_url.url(); // return complete url return m_fileInfo.absoluteFilePath(); } } // Full abs path // Just the name-part of the path, without parent directories -QString FileAccess::fileName() const +QString FileAccess::fileName(bool needTmp) const { if(!isLocal()) - return m_name; + return (needTmp) ? m_localCopy : m_name; else if(parent()) return m_filePath; else return m_fileInfo.fileName(); } QString FileAccess::filePath() const { if(parent() && parent()->parent()) return parent()->filePath() + "/" + m_filePath; else return m_filePath; // The path-string that was used during construction } FileAccess* FileAccess::parent() const { return m_pParent; } QString FileAccess::prettyAbsPath() const { return isLocal() ? absoluteFilePath() : m_url.toDisplayString(); } QDateTime FileAccess::lastModified() const { Q_ASSERT(!m_modificationTime.isNull()); return m_modificationTime; } static bool interruptableReadFile(QFile& f, void* pDestBuffer, qint64 maxLength) { ProgressProxy pp; const qint64 maxChunkSize = 100000; qint64 i = 0; pp.setMaxNofSteps(maxLength / maxChunkSize + 1); while(i < maxLength) { qint64 nextLength = std::min(maxLength - i, maxChunkSize); qint64 reallyRead = f.read((char*)pDestBuffer + i, nextLength); if(reallyRead != nextLength) { return false; } i += reallyRead; pp.setCurrent(double(i) / maxLength); if(pp.wasCancelled()) return false; } return true; } bool FileAccess::readFile(void* pDestBuffer, qint64 maxLength) { //Avoid hang on linux for special files. if(!isNormal()) return true; if(!m_localCopy.isEmpty()) { QFile f(m_localCopy); if(f.open(QIODevice::ReadOnly)) return interruptableReadFile(f, pDestBuffer, maxLength); // maxLength == f.read( (char*)pDestBuffer, maxLength ); } else if(isLocal()) { QFile f(absoluteFilePath()); if(f.open(QIODevice::ReadOnly)) return interruptableReadFile(f, pDestBuffer, maxLength); //maxLength == f.read( (char*)pDestBuffer, maxLength ); } else { FileAccessJobHandler jh(this); return jh.get(pDestBuffer, maxLength); } return false; } bool FileAccess::writeFile(const void* pSrcBuffer, qint64 length) { ProgressProxy pp; if(isLocal()) { QFile f(absoluteFilePath()); if(f.open(QIODevice::WriteOnly)) { const qint64 maxChunkSize = 100000; pp.setMaxNofSteps(length / maxChunkSize + 1); qint64 i = 0; while(i < length) { qint64 nextLength = std::min(length - i, maxChunkSize); qint64 reallyWritten = f.write((char*)pSrcBuffer + i, nextLength); if(reallyWritten != nextLength) { return false; } i += reallyWritten; pp.step(); if(pp.wasCancelled()) return false; } f.close(); if(isExecutable()) // value is true if the old file was executable { // Preserve attributes f.setPermissions(f.permissions() | QFile::ExeUser); } return true; } } else { FileAccessJobHandler jh(this); return jh.put(pSrcBuffer, length, true /*overwrite*/); } return false; } bool FileAccess::copyFile(const QString& dest) { FileAccessJobHandler jh(this); return jh.copyFile(dest); // Handles local and remote copying. } bool FileAccess::rename(const QString& dest) { FileAccessJobHandler jh(this); return jh.rename(dest); } bool FileAccess::removeFile() { if(isLocal()) { return QDir().remove(absoluteFilePath()); } else { FileAccessJobHandler jh(this); return jh.removeFile(url()); } } bool FileAccess::removeFile(const QString& name) // static { return FileAccess(name).removeFile(); } bool FileAccess::listDir(t_DirectoryList* pDirList, bool bRecursive, bool bFindHidden, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, bool bFollowDirLinks, bool bUseCvsIgnore) { FileAccessJobHandler jh(this); return jh.listDir(pDirList, bRecursive, bFindHidden, filePattern, fileAntiPattern, dirAntiPattern, bFollowDirLinks, bUseCvsIgnore); } -QString FileAccess::tempFileName() +QString FileAccess::getTempName() const +{ + return m_localCopy; +} + +bool FileAccess::createLocalCopy() +{ + if(isLocal() || !m_localCopy.isEmpty()) + return true; + + tmpFile->setAutoRemove(true); + tmpFile->setFileTemplate(QStringLiteral("XXXXXX-kdiff3tmp")); + tmpFile->open(); + tmpFile->close(); + m_localCopy = tmpFile->fileName(); + + return copyFile(tmpFile->fileName()); +} +//static tempfile Generator +void FileAccess::createTempFile(QTemporaryFile& tmpFile) +{ + tmpFile.setAutoRemove(true); + tmpFile.setFileTemplate(QStringLiteral("XXXXXX-kdiff3tmp")); + tmpFile.open(); + tmpFile.close(); +} +//TODO:Depricated to be removed +/*QString FileAccess::tempFileName() { QTemporaryFile tmpFile; tmpFile.open(); - //tmpFile.setAutoDelete( true ); // We only want the name. Delete the precreated file immediately. - QString name = tmpFile.fileName() + ".2"; + //tmpFile.setAutoDelete( true ); + QString name = tmpFile.fileName(); tmpFile.close(); return name; -} +}*/ bool FileAccess::removeTempFile(const QString& name) // static -{ - if(name.endsWith(QLatin1String(".2"))) - FileAccess(name.left(name.length() - 2)).removeFile(); - +{ return FileAccess(name).removeFile(); } bool FileAccess::makeDir(const QString& dirName) { FileAccessJobHandler fh(nullptr); return fh.mkDir(dirName); } bool FileAccess::removeDir(const QString& dirName) { FileAccessJobHandler fh(nullptr); return fh.rmDir(dirName); } bool FileAccess::symLink(const QString& linkTarget, const QString& linkLocation) { if(linkTarget.isEmpty() || linkLocation.isEmpty()) return false; return QFile::link(linkTarget, linkLocation); //FileAccessJobHandler fh(0); //return fh.symLink( linkTarget, linkLocation ); } bool FileAccess::exists(const QString& name) { FileAccess fa(name); return fa.exists(); } // If the size couldn't be determined by stat() then the file is copied to a local temp file. qint64 FileAccess::sizeForReading() { if(!isLocal() && m_size == 0) { // Size couldn't be determined. Copy the file to a local temp place. - QString localCopy = tempFileName(); + createLocalCopy(); + QString localCopy = tmpFile->fileName(); bool bSuccess = copyFile(localCopy); if(bSuccess) { QFileInfo fi(localCopy); m_size = fi.size(); m_localCopy = localCopy; return m_size; } else { return 0; } } else return size(); } QString FileAccess::getStatusText() { return m_statusText; } void FileAccess::setStatusText(const QString& s) { m_statusText = s; } QString FileAccess::cleanPath(const QString& path) // static { QUrl url = QUrl::fromUserInput(path, QString(""), QUrl::AssumeLocalFile); if(url.isLocalFile() || !url.isValid()) { return QDir().cleanPath(path); } else { return path; } } bool FileAccess::createBackup(const QString& bakExtension) { if(exists()) { // First rename the existing file to the bak-file. If a bak-file file exists, delete that. QString bakName = absoluteFilePath() + bakExtension; FileAccess bakFile(bakName, true /*bWantToWrite*/); if(bakFile.exists()) { bool bSuccess = bakFile.removeFile(); if(!bSuccess) { setStatusText(i18n("While trying to make a backup, deleting an older backup failed.\nFilename: %1", bakName)); return false; } } bool bSuccess = rename(bakName); if(!bSuccess) { setStatusText(i18n("While trying to make a backup, renaming failed.\nFilenames: %1 -> %2", absoluteFilePath(), bakName)); return false; } } return true; } FileAccessJobHandler::FileAccessJobHandler(FileAccess* pFileAccess) { m_pFileAccess = pFileAccess; m_bSuccess = false; } bool FileAccessJobHandler::stat(int detail, bool bWantToWrite) { m_bSuccess = false; m_pFileAccess->setStatusText(QString()); KIO::StatJob* pStatJob = KIO::stat(m_pFileAccess->url(), bWantToWrite ? KIO::StatJob::DestinationSide : KIO::StatJob::SourceSide, detail, KIO::HideProgressInfo); connect(pStatJob, &KIO::StatJob::result, this, &FileAccessJobHandler::slotStatResult); ProgressProxy::enterEventLoop(pStatJob, i18n("Getting file status: %1", m_pFileAccess->prettyAbsPath())); return m_bSuccess; } void FileAccessJobHandler::slotStatResult(KJob* pJob) { if(pJob->error()) { //pJob->uiDelegate()->showErrorMessage(); m_pFileAccess->m_bExists = false; m_bSuccess = true; } else { m_bSuccess = true; m_pFileAccess->m_bValidData = true; const KIO::UDSEntry e = static_cast(pJob)->statResult(); m_pFileAccess->setUdsEntry(e); } ProgressProxy::exitEventLoop(); } bool FileAccessJobHandler::get(void* pDestBuffer, long maxLength) { ProgressProxyExtender pp; // Implicitly used in slotPercent() if(maxLength > 0 && !pp.wasCancelled()) { KIO::TransferJob* pJob = KIO::get(m_pFileAccess->url(), KIO::NoReload); m_transferredBytes = 0; m_pTransferBuffer = (char*)pDestBuffer; m_maxLength = maxLength; m_bSuccess = false; m_pFileAccess->setStatusText(QString()); connect(pJob, &KIO::TransferJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pJob, &KIO::TransferJob::data, this, &FileAccessJobHandler::slotGetData); connect(pJob, SIGNAL(percent(KJob*, qint64)), &pp, SLOT(slotPercent(KJob*, qint64))); ProgressProxy::enterEventLoop(pJob, i18n("Reading file: %1", m_pFileAccess->prettyAbsPath())); return m_bSuccess; } else return true; } void FileAccessJobHandler::slotGetData(KJob* pJob, const QByteArray& newData) { if(pJob->error()) { pJob->uiDelegate()->showErrorMessage(); } else { qint64 length = std::min(qint64(newData.size()), m_maxLength - m_transferredBytes); ::memcpy(m_pTransferBuffer + m_transferredBytes, newData.data(), newData.size()); m_transferredBytes += length; } } bool FileAccessJobHandler::put(const void* pSrcBuffer, long maxLength, bool bOverwrite, bool bResume, int permissions) { ProgressProxyExtender pp; // Implicitly used in slotPercent() if(maxLength > 0) { KIO::TransferJob* pJob = KIO::put(m_pFileAccess->url(), permissions, KIO::HideProgressInfo | (bOverwrite ? KIO::Overwrite : KIO::DefaultFlags) | (bResume ? KIO::Resume : KIO::DefaultFlags)); m_transferredBytes = 0; m_pTransferBuffer = (char*)pSrcBuffer; m_maxLength = maxLength; m_bSuccess = false; m_pFileAccess->setStatusText(QString()); connect(pJob, &KIO::TransferJob::result, this, &FileAccessJobHandler::slotPutJobResult); connect(pJob, &KIO::TransferJob::dataReq, this, &FileAccessJobHandler::slotPutData); connect(pJob, SIGNAL(percent(KJob*, qint64)), &pp, SLOT(slotPercent(KJob*, qint64))); ProgressProxy::enterEventLoop(pJob, i18n("Writing file: %1", m_pFileAccess->prettyAbsPath())); return m_bSuccess; } else return true; } void FileAccessJobHandler::slotPutData(KIO::Job* pJob, QByteArray& data) { if(pJob->error()) { pJob->uiDelegate()->showErrorMessage(); } else { /* Think twice before doing this in new code. The maxChunkSize must be able to fit a 32-bit int. Given that the fallowing is safe. */ qint64 maxChunkSize = 100000; qint64 length = std::min(maxChunkSize, m_maxLength - m_transferredBytes); data.resize((int)length); if(data.size() == (int)length) { if(length > 0) { ::memcpy(data.data(), m_pTransferBuffer + m_transferredBytes, data.size()); m_transferredBytes += length; } } else { KMessageBox::error(ProgressProxy::getDialog(), i18n("Out of memory")); data.resize(0); m_bSuccess = false; } } } void FileAccessJobHandler::slotPutJobResult(KJob* pJob) { if(pJob->error()) { pJob->uiDelegate()->showErrorMessage(); } else { m_bSuccess = (m_transferredBytes == m_maxLength); // Special success condition } ProgressProxy::exitEventLoop(); // Close the dialog, return from exec() } bool FileAccessJobHandler::mkDir(const QString& dirName) { QUrl dirURL = QUrl::fromUserInput(dirName, QString(""), QUrl::AssumeLocalFile); if(dirName.isEmpty()) return false; else if(dirURL.isLocalFile() || dirURL.isRelative()) { return QDir().mkdir(dirURL.path()); } else { m_bSuccess = false; KIO::SimpleJob* pJob = KIO::mkdir(dirURL); connect(pJob, &KIO::SimpleJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Making directory: %1", dirName)); return m_bSuccess; } } bool FileAccessJobHandler::rmDir(const QString& dirName) { QUrl dirURL = QUrl::fromUserInput(dirName, QString(""), QUrl::AssumeLocalFile); if(dirName.isEmpty()) return false; else if(dirURL.isLocalFile()) { return QDir().rmdir(dirURL.path()); } else { m_bSuccess = false; KIO::SimpleJob* pJob = KIO::rmdir(dirURL); connect(pJob, &KIO::SimpleJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Removing directory: %1", dirName)); return m_bSuccess; } } bool FileAccessJobHandler::removeFile(const QUrl& fileName) { if(fileName.isEmpty()) return false; else { m_bSuccess = false; KIO::SimpleJob* pJob = KIO::file_delete(fileName, KIO::HideProgressInfo); connect(pJob, &KIO::SimpleJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Removing file: %1", fileName.toDisplayString())); return m_bSuccess; } } bool FileAccessJobHandler::symLink(const QUrl& linkTarget, const QUrl& linkLocation) { if(linkTarget.isEmpty() || linkLocation.isEmpty()) return false; else { m_bSuccess = false; KIO::CopyJob* pJob = KIO::link(linkTarget, linkLocation, KIO::HideProgressInfo); connect(pJob, &KIO::CopyJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Creating symbolic link: %1 -> %2", linkLocation.toDisplayString(), linkTarget.toDisplayString())); return m_bSuccess; } } bool FileAccessJobHandler::rename(const QString& dest) { if(dest.isEmpty()) return false; QUrl kurl = QUrl::fromUserInput(dest, QString(""), QUrl::AssumeLocalFile); if(kurl.isRelative()) kurl = QUrl::fromUserInput(QDir().absoluteFilePath(dest), QString(""), QUrl::AssumeLocalFile); // assuming that invalid means relative if(m_pFileAccess->isLocal() && kurl.isLocalFile()) { return QDir().rename(m_pFileAccess->absoluteFilePath(), kurl.path()); } else { ProgressProxyExtender pp; int permissions = -1; m_bSuccess = false; KIO::FileCopyJob* pJob = KIO::file_move(m_pFileAccess->url(), kurl, permissions, KIO::HideProgressInfo); connect(pJob, &KIO::FileCopyJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pJob, SIGNAL(percent(KJob*, qint64)), &pp, SLOT(slotPercent(KJob*, qint64))); ProgressProxy::enterEventLoop(pJob, i18n("Renaming file: %1 -> %2", m_pFileAccess->prettyAbsPath(), dest)); return m_bSuccess; } } void FileAccessJobHandler::slotSimpleJobResult(KJob* pJob) { if(pJob->error()) { pJob->uiDelegate()->showErrorMessage(); } else { m_bSuccess = true; } ProgressProxy::exitEventLoop(); // Close the dialog, return from exec() } // Copy local or remote files. bool FileAccessJobHandler::copyFile(const QString& dest) { ProgressProxyExtender pp; QUrl destUrl = QUrl::fromUserInput(dest, QString(""), QUrl::AssumeLocalFile); m_pFileAccess->setStatusText(QString()); if(!m_pFileAccess->isLocal() || !destUrl.isLocalFile()) // if either url is nonlocal { int permissions = (m_pFileAccess->isExecutable() ? 0111 : 0) + (m_pFileAccess->isWritable() ? 0222 : 0) + (m_pFileAccess->isReadable() ? 0444 : 0); m_bSuccess = false; KIO::FileCopyJob* pJob = KIO::file_copy(m_pFileAccess->url(), destUrl, permissions, KIO::HideProgressInfo); connect(pJob, &KIO::FileCopyJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pJob, SIGNAL(percent(KJob*, qint64)), &pp, SLOT(slotPercent(KJob*, qint64))); ProgressProxy::enterEventLoop(pJob, i18n("Copying file: %1 -> %2", m_pFileAccess->prettyAbsPath(), dest)); return m_bSuccess; // Note that the KIO-slave preserves the original date, if this is supported. } // Both files are local: QString srcName = m_pFileAccess->absoluteFilePath(); QString destName = dest; QFile srcFile(srcName); QFile destFile(destName); bool bReadSuccess = srcFile.open(QIODevice::ReadOnly); if(bReadSuccess == false) { m_pFileAccess->setStatusText(i18n("Error during file copy operation: Opening file for reading failed. Filename: %1", srcName)); return false; } bool bWriteSuccess = destFile.open(QIODevice::WriteOnly); if(bWriteSuccess == false) { m_pFileAccess->setStatusText(i18n("Error during file copy operation: Opening file for writing failed. Filename: %1", destName)); return false; } std::vector buffer(100000); qint64 bufSize = buffer.size(); qint64 srcSize = srcFile.size(); while(srcSize > 0 && !pp.wasCancelled()) { qint64 readSize = srcFile.read(&buffer[0], std::min(srcSize, bufSize)); if(readSize == -1 || readSize == 0) { m_pFileAccess->setStatusText(i18n("Error during file copy operation: Reading failed. Filename: %1", srcName)); return false; } srcSize -= readSize; while(readSize > 0) { qint64 writeSize = destFile.write(&buffer[0], readSize); if(writeSize == -1 || writeSize == 0) { m_pFileAccess->setStatusText(i18n("Error during file copy operation: Writing failed. Filename: %1", destName)); return false; } readSize -= writeSize; } destFile.flush(); pp.setCurrent((double)(srcFile.size() - srcSize) / srcFile.size(), false); } srcFile.close(); destFile.close(); // Update the times of the destFile #ifdef Q_OS_WIN struct _stat srcFileStatus; int statResult = ::_stat(srcName.toLocal8Bit().constData(), &srcFileStatus); if(statResult == 0) { _utimbuf destTimes; destTimes.actime = srcFileStatus.st_atime; /* time of last access */ destTimes.modtime = srcFileStatus.st_mtime; /* time of last modification */ _utime(destName.toLocal8Bit().constData(), &destTimes); _chmod(destName.toLocal8Bit().constData(), srcFileStatus.st_mode); } #else struct stat srcFileStatus; int statResult = ::stat(srcName.toLocal8Bit().constData(), &srcFileStatus); if(statResult == 0) { utimbuf destTimes; destTimes.actime = srcFileStatus.st_atime; /* time of last access */ destTimes.modtime = srcFileStatus.st_mtime; /* time of last modification */ utime(destName.toLocal8Bit().constData(), &destTimes); chmod(destName.toLocal8Bit().constData(), srcFileStatus.st_mode); } #endif return true; } bool wildcardMultiMatch(const QString& wildcard, const QString& testString, bool bCaseSensitive) { static QHash s_patternMap; QStringList sl = wildcard.split(";"); for(QStringList::Iterator it = sl.begin(); it != sl.end(); ++it) { QHash::iterator patIt = s_patternMap.find(*it); if(patIt == s_patternMap.end()) { QRegExp pattern(*it, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive, QRegExp::Wildcard); patIt = s_patternMap.insert(*it, pattern); } if(patIt.value().exactMatch(testString)) return true; } return false; } bool FileAccessJobHandler::listDir(t_DirectoryList* pDirList, bool bRecursive, bool bFindHidden, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, bool bFollowDirLinks, bool bUseCvsIgnore) { ProgressProxyExtender pp; m_pDirList = pDirList; m_pDirList->clear(); m_bFindHidden = bFindHidden; m_bRecursive = bRecursive; m_bFollowDirLinks = bFollowDirLinks; // Only relevant if bRecursive==true. m_fileAntiPattern = fileAntiPattern; m_filePattern = filePattern; m_dirAntiPattern = dirAntiPattern; if(pp.wasCancelled()) return true; // Cancelled is not an error. pp.setInformation(i18n("Reading directory: %1", m_pFileAccess->absoluteFilePath()), 0, false); if(m_pFileAccess->isLocal()) { QString currentPath = QDir::currentPath(); m_bSuccess = QDir::setCurrent(m_pFileAccess->absoluteFilePath()); if(m_bSuccess) { m_bSuccess = true; QDir dir("."); dir.setSorting(QDir::Name | QDir::DirsFirst); dir.setFilter(QDir::Files | QDir::Dirs | /* from KDE3 QDir::TypeMaskDirs | */ QDir::Hidden | QDir::System); QFileInfoList fiList = dir.entryInfoList(); if(fiList.isEmpty()) { // No Permission to read directory or other error. m_bSuccess = false; } else { foreach(const QFileInfo& fi, fiList) // for each file... { if(pp.wasCancelled()) break; if(fi.fileName() == "." || fi.fileName() == "..") continue; FileAccess fa; fa.setFile(fi, m_pFileAccess); pDirList->push_back(fa); } } } QDir::setCurrent(currentPath); // restore current path } else { KIO::ListJob* pListJob = nullptr; pListJob = KIO::listDir(m_pFileAccess->url(), KIO::HideProgressInfo, true /*bFindHidden*/); m_bSuccess = false; if(pListJob != nullptr) { connect(pListJob, &KIO::ListJob::entries, this, &FileAccessJobHandler::slotListDirProcessNewEntries); connect(pListJob, &KIO::ListJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pListJob, &KIO::ListJob::infoMessage, &pp, &ProgressProxyExtender::slotListDirInfoMessage); // This line makes the transfer via fish unreliable.:-( //connect( pListJob, SIGNAL(percent(KJob*,qint64)), &pp, SLOT(slotPercent(KJob*, qint64))); ProgressProxy::enterEventLoop(pListJob, i18n("Listing directory: %1", m_pFileAccess->prettyAbsPath())); } } CvsIgnoreList cvsIgnoreList; if(bUseCvsIgnore) { cvsIgnoreList.init(*m_pFileAccess, pDirList); } //TODO: Ask os for this information don't hard code it. #if defined(Q_OS_WIN) bool bCaseSensitive = false; #else bool bCaseSensitive = true; #endif // Now remove all entries that should be ignored: t_DirectoryList::iterator i; for(i = pDirList->begin(); i != pDirList->end();) { t_DirectoryList::iterator i2 = i; ++i2; QString fn = i->fileName(); if((!bFindHidden && i->isHidden()) || (i->isFile() && (!wildcardMultiMatch(filePattern, fn, bCaseSensitive) || wildcardMultiMatch(fileAntiPattern, fn, bCaseSensitive))) || (i->isDir() && wildcardMultiMatch(dirAntiPattern, fn, bCaseSensitive)) || cvsIgnoreList.matches(fn, bCaseSensitive)) { // Remove it pDirList->erase(i); i = i2; } else { ++i; } } if(bRecursive) { t_DirectoryList subDirsList; for(i = m_pDirList->begin(); i != m_pDirList->end(); ++i) { if(i->isDir() && (!i->isSymLink() || m_bFollowDirLinks)) { t_DirectoryList dirList; i->listDir(&dirList, bRecursive, bFindHidden, filePattern, fileAntiPattern, dirAntiPattern, bFollowDirLinks, bUseCvsIgnore); t_DirectoryList::iterator j; for(j = dirList.begin(); j != dirList.end(); ++j) { if(j->parent() == nullptr) j->m_filePath = i->fileName() + "/" + j->m_filePath; } // append data onto the main list subDirsList.splice(subDirsList.end(), dirList); } } m_pDirList->splice(m_pDirList->end(), subDirsList); } return m_bSuccess; } void FileAccessJobHandler::slotListDirProcessNewEntries(KIO::Job*, const KIO::UDSEntryList& l) { //This function is called for non-local urls. Don't use QUrl::fromLocalFile here as it does not handle these. QUrl parentUrl = QUrl::fromUserInput(m_pFileAccess->absoluteFilePath(), QString(""), QUrl::AssumeLocalFile); KIO::UDSEntryList::ConstIterator i; for(i = l.begin(); i != l.end(); ++i) { const KIO::UDSEntry& e = *i; FileAccess fa; fa.m_pParent = m_pFileAccess; fa.setUdsEntry(e); if(fa.fileName() != "." && fa.fileName() != "..") { fa.m_url = parentUrl; QUrl url = fa.m_url.adjusted(QUrl::StripTrailingSlash); fa.m_url.setPath(url.path() + "/" + fa.fileName()); //fa.m_absoluteFilePath = fa.url().url(); m_pDirList->push_back(fa); } } } void ProgressProxyExtender::slotListDirInfoMessage(KJob*, const QString& msg) { setInformation(msg, 0); } void ProgressProxyExtender::slotPercent(KJob*, qint64 percent) { setCurrent(percent); } //#include "fileaccess.moc" diff --git a/src/fileaccess.h b/src/fileaccess.h index 8f9a1f1..cc43db1 100644 --- a/src/fileaccess.h +++ b/src/fileaccess.h @@ -1,187 +1,192 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * joachim.eibl at gmx.de * * * * 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. * ***************************************************************************/ #ifndef FILEACCESS_H #define FILEACCESS_H #include "progress.h" -#include #include #include +#include +#include +#include #include namespace KIO { class Job; } bool wildcardMultiMatch( const QString& wildcard, const QString& testString, bool bCaseSensitive ); class t_DirectoryList; class ProgressProxyExtender: public ProgressProxy { Q_OBJECT public: ProgressProxyExtender() { setMaxNofSteps(100); } public Q_SLOTS: void slotListDirInfoMessage( KJob*, const QString& msg ); void slotPercent( KJob*, qint64 percent ); }; class FileAccess { public: FileAccess(); ~FileAccess(); FileAccess( const QString& name, bool bWantToWrite=false ); // name: local file or dirname or url (when supported) void setFile( const QString& name, bool bWantToWrite=false ); bool isNormal() const; bool isValid() const; bool isFile() const; bool isDir() const; bool isSymLink() const; bool exists() const; qint64 size() const; // Size as returned by stat(). qint64 sizeForReading(); // If the size can't be determined by stat() then the file is copied to a local temp file. bool isReadable() const; bool isWritable() const; bool isExecutable() const; bool isHidden() const; QString readLink() const; QDateTime lastModified() const; - QString fileName() const; // Just the name-part of the path, without parent directories + QString fileName(bool needTmp = false) const; // Just the name-part of the path, without parent directories QString filePath() const; // The path-string that was used during construction QString prettyAbsPath() const; QUrl url() const; QString absoluteFilePath() const; bool isLocal() const; bool readFile(void* pDestBuffer, qint64 maxLength ); bool writeFile(const void* pSrcBuffer, qint64 length ); bool listDir( t_DirectoryList* pDirList, bool bRecursive, bool bFindHidden, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, bool bFollowDirLinks, bool bUseCvsIgnore ); bool copyFile( const QString& destUrl ); bool createBackup( const QString& bakExtension ); - static QString tempFileName(); + QString getTempName() const; + bool createLocalCopy(); + static void createTempFile(QTemporaryFile& ); static bool removeTempFile( const QString& ); bool removeFile(); static bool removeFile( const QString& ); static bool makeDir( const QString& ); static bool removeDir( const QString& ); static bool exists( const QString& ); static QString cleanPath( const QString& ); //bool chmod( const QString& ); bool rename( const QString& ); static bool symLink( const QString& linkTarget, const QString& linkLocation ); void addPath( const QString& txt ); QString getStatusText(); FileAccess* parent() const; // !=0 for listDir-results, but only valid if the parent was not yet destroyed. private: void setUdsEntry( const KIO::UDSEntry& e ); void setFile( const QFileInfo& fi, FileAccess* pParent ); void setStatusText( const QString& s ); void reset(); QUrl m_url; bool m_bValidData; //long m_fileType; // for testing only FileAccess* m_pParent; QFileInfo m_fileInfo; QString m_linkTarget; QString m_name; QString m_localCopy; + QSharedPointer tmpFile; QString m_filePath; // might be absolute or relative if m_pParent!=0 qint64 m_size; QDateTime m_modificationTime; bool m_bSymLink : 1; bool m_bFile : 1; bool m_bDir : 1; bool m_bExists : 1; bool m_bWritable : 1; bool m_bReadable : 1; bool m_bExecutable:1; bool m_bHidden : 1; QString m_statusText; // Might contain an error string, when the last operation didn't succeed. friend class FileAccessJobHandler; }; class t_DirectoryList : public std::list {}; class FileAccessJobHandler : public QObject { Q_OBJECT public: explicit FileAccessJobHandler( FileAccess* pFileAccess ); bool get( void* pDestBuffer, long maxLength ); bool put( const void* pSrcBuffer, long maxLength, bool bOverwrite, bool bResume=false, int permissions=-1 ); bool stat(int detailLevel=2, bool bWantToWrite=false ); bool copyFile( const QString& dest ); bool rename( const QString& dest ); bool listDir( t_DirectoryList* pDirList, bool bRecursive, bool bFindHidden, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, bool bFollowDirLinks, bool bUseCvsIgnore ); bool mkDir( const QString& dirName ); bool rmDir( const QString& dirName ); bool removeFile( const QUrl& dirName ); bool symLink( const QUrl& linkTarget, const QUrl& linkLocation ); private: FileAccess* m_pFileAccess; bool m_bSuccess; // Data needed during Job qint64 m_transferredBytes; char* m_pTransferBuffer = nullptr; // Needed during get or put qint64 m_maxLength; QString m_filePattern; QString m_fileAntiPattern; QString m_dirAntiPattern; t_DirectoryList* m_pDirList = nullptr; bool m_bFindHidden = false; bool m_bRecursive = false; bool m_bFollowDirLinks = false; bool scanLocalDirectory( const QString& dirName, t_DirectoryList* dirList ); private Q_SLOTS: void slotStatResult( KJob* ); void slotSimpleJobResult( KJob* pJob ); void slotPutJobResult( KJob* pJob ); void slotGetData(KJob*,const QByteArray&); void slotPutData(KIO::Job*, QByteArray&); void slotListDirProcessNewEntries( KIO::Job *, const KIO::UDSEntryList& l ); }; #endif diff --git a/src/kdiff3_part.cpp b/src/kdiff3_part.cpp index b72c30f..aaeeca8 100644 --- a/src/kdiff3_part.cpp +++ b/src/kdiff3_part.cpp @@ -1,273 +1,277 @@ /*************************************************************************** * Copyright (C) 2003-2007 Joachim Eibl * * * * 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 is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "kdiff3_part.h" #include "fileaccess.h" #include "kdiff3.h" #include #include +#include #include #include #include #include #include #include static KAboutData createAboutData() { QString appVersion = QString(KDIFF3_VERSION_STRING); if(sizeof(void*) == 8) appVersion += " (64 bit)"; else if(sizeof(void*) == 4) appVersion += " (32 bit)"; KAboutData aboutData(QLatin1String("kdiff3part"), i18n("KDiff3 Part"), appVersion, i18n("A KPart to display SVG images"), KAboutLicense::GPL_V2, i18n("Copyright 2007, Aurélien Gâteau ")); aboutData.addAuthor(i18n("Joachim Eibl"), QString(), QString("joachim.eibl at gmx.de")); return aboutData; } K_PLUGIN_FACTORY(KDiff3PartFactory, registerPlugin();) //K_EXPORT_PLUGIN( KDiff3PartFactory(createAboutData())) KDiff3Part::KDiff3Part(QWidget* parentWidget, QObject* parent, const QVariantList& args) : KParts::ReadWritePart(parent) { //set AboutData setComponentData(createAboutData()); const QString widgetName = args[0].toString(); // this should be your custom internal widget m_widget = new KDiff3App(parentWidget, widgetName, this); //FIXME: This hack is necessary to avoid a crash when the program terminates. m_bIsShell = qobject_cast(parentWidget) != nullptr; // notify the part that this is our internal widget setWidget(m_widget); // create our actions //KStandardAction::open(this, SLOT(fileOpen()), actionCollection()); //KStandardAction::saveAs(this, SLOT(fileSaveAs()), actionCollection()); //KStandardAction::save(this, SLOT(save()), actionCollection()); setXMLFile("kdiff3_part.rc"); // we are read-write by default setReadWrite(true); // we are not modified since we haven't done anything yet setModified(false); } KDiff3Part::~KDiff3Part() { if(m_widget != nullptr && !m_bIsShell) { m_widget->saveOptions(KSharedConfig::openConfig()); } } void KDiff3Part::setReadWrite(bool /*rw*/) { // ReadWritePart::setReadWrite(rw); } void KDiff3Part::setModified(bool /*modified*/) { /* // get a handle on our Save action and make sure it is valid QAction *save = actionCollection()->action(KStandardAction::stdName(KStandardAction::Save)); if (!save) return; // if so, we either enable or disable it based on the current // state if (modified) save->setEnabled(true); else save->setEnabled(false); // in any event, we want our parent to do it's thing ReadWritePart::setModified(modified); */ } static void getNameAndVersion(const QString& str, const QString& lineStart, QString& fileName, QString& version) { if(str.left(lineStart.length()) == lineStart && fileName.isEmpty()) { int pos = lineStart.length(); while(pos < str.length() && (str[pos] == ' ' || str[pos] == '\t')) ++pos; int pos2 = str.length() - 1; while(pos2 > pos) { while(pos2 > pos && str[pos2] != ' ' && str[pos2] != '\t') --pos2; fileName = str.mid(pos, pos2 - pos); fprintf(stderr, "KDiff3: %s\n", fileName.toLatin1().constData()); if(FileAccess(fileName).exists()) break; --pos2; } int vpos = str.lastIndexOf("\t", -1); if(vpos > 0 && vpos > (int)pos2) { version = str.mid(vpos + 1); while(!version.right(1)[0].isLetterOrNumber()) version.truncate(version.length() - 1); } } } bool KDiff3Part::openFile() { // m_file is always local so we can use QFile on it fprintf(stderr, "KDiff3: %s\n", localFilePath().toLatin1().constData()); QFile file(localFilePath()); if(file.open(QIODevice::ReadOnly) == false) return false; // our example widget is text-based, so we use QTextStream instead // of a raw QDataStream QTextStream stream(&file); QString str; QString fileName1; QString fileName2; QString version1; QString version2; while(!stream.atEnd() && (fileName1.isEmpty() || fileName2.isEmpty())) { str = stream.readLine() + "\n"; getNameAndVersion(str, "---", fileName1, version1); getNameAndVersion(str, "+++", fileName2, version2); } file.close(); if(fileName1.isEmpty() && fileName2.isEmpty()) { KMessageBox::sorry(m_widget, i18n("Could not find files for comparison.")); return false; } FileAccess f1(fileName1); FileAccess f2(fileName2); if(f1.exists() && f2.exists() && fileName1 != fileName2) { m_widget->slotFileOpen2(fileName1, fileName2, "", "", "", "", "", nullptr); return true; } else if(version1.isEmpty() && f1.exists()) { // Normal patch // patch -f -u --ignore-whitespace -i [inputfile] -o [outfile] [patchfile] - QString tempFileName = FileAccess::tempFileName(); + QTemporaryFile tmpFile; + FileAccess::createTempFile(tmpFile); + QString tempFileName = tmpFile.fileName(); QString cmd = "patch -f -u --ignore-whitespace -i \"" + localFilePath() + "\" -o \"" + tempFileName + "\" \"" + fileName1 + "\""; QProcess process; process.start(cmd); process.waitForFinished(-1); m_widget->slotFileOpen2(fileName1, tempFileName, "", "", - "", version2.isEmpty() ? fileName2 : "REV:" + version2 + ":" + fileName2, "", nullptr); // alias names - // std::cerr << "KDiff3: f1:" << fileName1.toLatin1() <<"<->"<"<slotFileOpen2(tempFileName, fileName2, "", "", version1.isEmpty() ? fileName1 : "REV:" + version1 + ":" + fileName1, "", "", nullptr); // alias name // std::cerr << "KDiff3: f2:" << fileName2.toLatin1() <<"<->"<%s\n", fileName1.toLatin1().constData(), fileName2.toLatin1().constData()); // Assuming that files are on CVS: Try to get them // cvs update -p -r [REV] [FILE] > [OUTPUTFILE] - QString tempFileName1 = FileAccess::tempFileName(); + QTemporaryFile tmpFile1; + FileAccess::createTempFile(tmpFile1); + QString tempFileName1 = tmpFile1.fileName(); QString cmd1 = "cvs update -p -r " + version1 + " \"" + fileName1 + "\" >\"" + tempFileName1 + "\""; QProcess process1; process1.start(cmd1); process1.waitForFinished(-1); - QString tempFileName2 = FileAccess::tempFileName(); + QTemporaryFile tmpFile2; + FileAccess::createTempFile(tmpFile2); + QString tempFileName2 = tmpFile2.fileName(); QString cmd2 = "cvs update -p -r " + version2 + " \"" + fileName2 + "\" >\"" + tempFileName2 + "\""; QProcess process2; process2.start(cmd2); process2.waitForFinished(-1); m_widget->slotFileOpen2(tempFileName1, tempFileName2, "", "", "REV:" + version1 + ":" + fileName1, "REV:" + version2 + ":" + fileName2, "", nullptr); // std::cerr << "KDiff3: f1/2:" << tempFileName1.toLatin1() <<"<->"<text(); file.close(); return true; */ return false; // Not implemented } #include "kdiff3_part.moc"