← Back to team overview

linuxdcpp-team team mailing list archive

[Branch ~dcplusplus-team/dcplusplus/trunk] Rev 3156: Share file name duplicates due to case differences

 

------------------------------------------------------------
revno: 3156
committer: poy <poy@xxxxxxxxxx>
branch nick: trunk
timestamp: Fri 2012-12-14 18:07:25 +0100
message:
  Share file name duplicates due to case differences
modified:
  changelog.txt
  dcpp/File.cpp
  dcpp/FileReader.cpp
  dcpp/HashManager.cpp
  dcpp/HashManager.h
  dcpp/QueueManager.cpp
  dcpp/ShareManager.cpp


--
lp:dcplusplus
https://code.launchpad.net/~dcplusplus-team/dcplusplus/trunk

Your team Dcplusplus-team is subscribed to branch lp:dcplusplus.
To unsubscribe from this branch go to https://code.launchpad.net/~dcplusplus-team/dcplusplus/trunk/+edit-subscription
=== modified file 'changelog.txt'
--- changelog.txt	2012-12-12 22:44:47 +0000
+++ changelog.txt	2012-12-14 17:07:25 +0000
@@ -1,3 +1,7 @@
+*** WARNING ***
+  In order to allow case-sensitive sharing, your files will have to be
+  re-hashed. Hashes stored by queued downloads won't be lost.
+*** WARNING ***
 * Fix a race condition on file list download (thanks bigmuscle)
 * [L#668548] Fix a potential infinite loop in BufferedSocket->setDataMode (crise)
 * Add "chunked" transfer encoding as per the HTTP/1.1 spec (crise)
@@ -10,6 +14,7 @@
 * Restore "Requesting" messages in the transfer list
 * [L#1071363] Apply link & plugin formatting to status messages (crise, poy)
 * [L#311818] Share file name duplicates due to directory merges (poy)
+* [L#311818] Share file name duplicates due to case differences (poy)
 
 -- 0.802 2012-10-20 --
 * Perf improvements using lock-free queues, requires P6 CPUs (poy)

=== modified file 'dcpp/File.cpp'
--- dcpp/File.cpp	2012-09-13 21:15:43 +0000
+++ dcpp/File.cpp	2012-12-14 17:07:25 +0000
@@ -41,7 +41,8 @@
 	}
 	DWORD shared = FILE_SHARE_READ | (mode & SHARED ? FILE_SHARE_WRITE : 0);
 
-	h = ::CreateFile(Text::toT(aFileName).c_str(), access, shared, NULL, m, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
+	h = ::CreateFile(Text::toT(aFileName).c_str(), access, shared, nullptr, m,
+		FILE_FLAG_POSIX_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
 
 	if(h == INVALID_HANDLE_VALUE) {
 		throw FileException(Util::translateError(GetLastError()));
@@ -164,17 +165,8 @@
 }
 
 int64_t File::getSize(const string& aFileName) noexcept {
-	WIN32_FIND_DATA fd;
-	HANDLE hFind;
-
-	hFind = FindFirstFile(Text::toT(aFileName).c_str(), &fd);
-
-	if (hFind == INVALID_HANDLE_VALUE) {
-		return -1;
-	} else {
-		FindClose(hFind);
-		return ((int64_t)fd.nFileSizeHigh << 32 | (int64_t)fd.nFileSizeLow);
-	}
+	auto i = FileFindIter(aFileName);
+	return i != FileFindIter() ? i->getSize() : -1;
 }
 
 void File::ensureDirectory(const string& aFile) noexcept {
@@ -422,19 +414,14 @@
 	StringList ret;
 
 #ifdef _WIN32
-	WIN32_FIND_DATA data;
-	HANDLE hFind;
-
-	hFind = ::FindFirstFile(Text::toT(path + pattern).c_str(), &data);
-	if(hFind != INVALID_HANDLE_VALUE) {
-		do {
-			const char* extra = (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? "\\" : "";
-			ret.push_back(path + Text::fromT(data.cFileName) + extra);
-		} while(::FindNextFile(hFind, &data));
-
-		::FindClose(hFind);
+	for(FileFindIter i(path + pattern), end; i != end; ++i) {
+		ret.push_back(path + i->getFileName());
+		if(i->isDirectory()) {
+			ret.back() += PATH_SEPARATOR;
+		}
 	}
 #else
+	/// @todo can FileFindIter be used on linux?
 	DIR* dir = opendir(Text::fromUtf8(path).c_str());
 	if (dir) {
 		while (struct dirent* ent = readdir(dir)) {
@@ -457,7 +444,8 @@
 FileFindIter::FileFindIter() : handle(INVALID_HANDLE_VALUE) { }
 
 FileFindIter::FileFindIter(const string& path) : handle(INVALID_HANDLE_VALUE) {
-	handle = ::FindFirstFile(Text::toT(path).c_str(), &data);
+	handle = ::FindFirstFileEx(Text::toT(path).c_str(), FindExInfoStandard, &data,
+		FindExSearchNameMatch, nullptr, FIND_FIRST_EX_CASE_SENSITIVE);
 }
 
 FileFindIter::~FileFindIter() {

=== modified file 'dcpp/FileReader.cpp'
--- dcpp/FileReader.cpp	2012-03-21 17:39:02 +0000
+++ dcpp/FileReader.cpp	2012-12-14 17:07:25 +0000
@@ -110,8 +110,8 @@
 		return READ_FAILED;
 	}
 
-	auto tmp = ::CreateFile(tfile.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
-		FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, NULL);
+	auto tmp = ::CreateFile(tfile.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
+		FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED | FILE_FLAG_POSIX_SEMANTICS, nullptr);
 
 	if (tmp == INVALID_HANDLE_VALUE) {
 		dcdebug("Failed to open unbuffered file: %s\n", Util::translateError(::GetLastError()).c_str());
@@ -210,8 +210,8 @@
 
 	auto tfile = Text::toT(file);
 
-	auto tmp = ::CreateFile(tfile.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
-		FILE_FLAG_SEQUENTIAL_SCAN, NULL);
+	auto tmp = ::CreateFile(tfile.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
+		FILE_FLAG_POSIX_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
 
 	if (tmp == INVALID_HANDLE_VALUE) {
 		dcdebug("Failed to open unbuffered file: %s\n", Util::translateError(::GetLastError()).c_str());

=== modified file 'dcpp/HashManager.cpp'
--- dcpp/HashManager.cpp	2012-12-13 17:04:31 +0000
+++ dcpp/HashManager.cpp	2012-12-14 17:07:25 +0000
@@ -32,8 +32,12 @@
 
 using std::swap;
 
-#define HASH_FILE_VERSION_STRING "2"
-static const uint32_t HASH_FILE_VERSION = 2;
+/* Version history:
+- Version 1: DC++ 0.307 to 0.68.
+- Version 2: DC++ 0.670 to DC++ 0.802. Improved efficiency.
+- Version 3: from DC++ 0.810 on. Changed the file registry to be case-sensitive. */
+#define HASH_FILE_VERSION_STRING "3"
+static const uint32_t HASH_FILE_VERSION = 3;
 const int64_t HashManager::MIN_BLOCK_SIZE = 64 * 1024;
 
 optional<TTHValue> HashManager::getTTH(const string& aFileName, int64_t aSize, uint32_t aTimeStamp) noexcept {
@@ -50,7 +54,7 @@
 	return store.getTree(root, tt);
 }
 
-size_t HashManager::getBlockSize(const TTHValue& root) {
+int64_t HashManager::getBlockSize(const TTHValue& root) {
 	Lock l(cs);
 	return store.getBlockSize(root);
 }
@@ -80,10 +84,9 @@
 void HashManager::HashStore::addFile(const string& aFileName, uint32_t aTimeStamp, const TigerTree& tth, bool aUsed) {
 	addTree(tth);
 
-	string fname = Text::toLower(Util::getFileName(aFileName));
-	string fpath = Text::toLower(Util::getFilePath(aFileName));
+	auto fname = Util::getFileName(aFileName), fpath = Util::getFilePath(aFileName);
 
-	FileInfoList& fileList = fileIndex[fpath];
+	auto& fileList = fileIndex[fpath];
 
 	auto j = find(fileList.begin(), fileList.end(), fname);
 	if (j != fileList.end()) {
@@ -163,14 +166,13 @@
 	}
 }
 
-size_t HashManager::HashStore::getBlockSize(const TTHValue& root) const {
+int64_t HashManager::HashStore::getBlockSize(const TTHValue& root) const {
 	auto i = treeIndex.find(root);
 	return i == treeIndex.end() ? 0 : i->second.getBlockSize();
 }
 
 optional<TTHValue> HashManager::HashStore::getTTH(const string& aFileName, int64_t aSize, uint32_t aTimeStamp) noexcept {
-	auto fname = Text::toLower(Util::getFileName(aFileName));
-	auto fpath = Text::toLower(Util::getFilePath(aFileName));
+	auto fname = Util::getFileName(aFileName), fpath = Util::getFilePath(aFileName);
 
 	auto i = fileIndex.find(fpath);
 	if (i != fileIndex.end()) {
@@ -194,8 +196,8 @@
 
 void HashManager::HashStore::rebuild() {
 	try {
-		DirMap newFileIndex;
-		TreeMap newTreeIndex;
+		decltype(fileIndex) newFileIndex;
+		decltype(treeIndex) newTreeIndex;
 
 		for (auto& i: fileIndex) {
 			for (auto& j: i.second) {
@@ -230,16 +232,22 @@
 		}
 
 		for (auto& i: fileIndex) {
-			auto fi = newFileIndex.emplace(i.first, FileInfoList()).first;
+#ifndef _MSC_VER
+			decltype(fileIndex)::mapped_type newFileList;
+#else
+			/// @todo remove this workaround when VS has a proper decltype...
+			decltype(fileIndex.begin()->second) newFileList;
+#endif
 
 			for (auto& j: i.second) {
 				if (newTreeIndex.find(j.getRoot()) != newTreeIndex.end()) {
-					fi->second.push_back(j);
+					newFileList.push_back(j);
 				}
 			}
 
-			if (fi->second.empty())
-				newFileIndex.erase(fi);
+			if(!newFileList.empty()) {
+				newFileIndex[i.first] = move(newFileList);
+			}
 		}
 
 		File::deleteFile(origName);
@@ -315,17 +323,13 @@
 class HashLoader: public SimpleXMLReader::CallBack {
 public:
 	HashLoader(HashManager::HashStore& s) :
-		store(s), size(0), timeStamp(0), version(HASH_FILE_VERSION), inTrees(false), inFiles(false), inHashStore(false) {
+		store(s), version(HASH_FILE_VERSION), inTrees(false), inFiles(false), inHashStore(false) {
 	}
 	void startTag(const string& name, StringPairList& attribs, bool simple);
-	void endTag(const string& name);
 
 private:
 	HashManager::HashStore& store;
 
-	string file;
-	int64_t size;
-	uint32_t timeStamp;
 	int version;
 
 	bool inTrees;
@@ -368,7 +372,8 @@
 			version = Util::toInt(getAttrib(attribs, sversion, 0));
 		}
 		inHashStore = !simple;
-	} else if (inHashStore && version == 2) {
+	} else if (inHashStore && (version == 2 || version == 3)) {
+		// when upgrading from version 2 to 3, import trees but not the file registry.
 		if (inTrees && name == sHash) {
 			const string& type = getAttrib(attribs, sType, 0);
 			int64_t index = Util::toInt64(getAttrib(attribs, sIndex, 1));
@@ -378,16 +383,14 @@
 			if (!root.empty() && type == sTTH && (index >= 8 || index == HashManager::SMALL_TREE) && blockSize >= 1024) {
 				store.treeIndex[TTHValue(root)] = HashManager::HashStore::TreeInfo(size, index, blockSize);
 			}
-		} else if (inFiles && name == sFile) {
-			file = getAttrib(attribs, sName, 0);
-			timeStamp = Util::toUInt32(getAttrib(attribs, sTimeStamp, 1));
-			const string& root = getAttrib(attribs, sRoot, 2);
-
-			if (!file.empty() && size >= 0 && timeStamp > 0 && !root.empty()) {
-				string fname = Text::toLower(Util::getFileName(file));
-				string fpath = Text::toLower(Util::getFilePath(file));
-
-				store.fileIndex[fpath].push_back(HashManager::HashStore::FileInfo(fname, TTHValue(root), timeStamp, false));
+		} else if (inFiles && version != 2 && name == sFile) {
+			const auto& file = getAttrib(attribs, sName, 0);
+			auto timeStamp = Util::toUInt32(getAttrib(attribs, sTimeStamp, 1));
+			const auto& root = getAttrib(attribs, sRoot, 2);
+
+			if (!file.empty() && timeStamp > 0 && !root.empty()) {
+				auto fname = Util::getFileName(file), fpath = Util::getFilePath(file);
+				store.fileIndex[fpath].emplace_back(fname, TTHValue(root), timeStamp, false);
 			}
 		} else if (name == sTrees) {
 			inTrees = !simple;
@@ -397,12 +400,6 @@
 	}
 }
 
-void HashLoader::endTag(const string& name) {
-	if (name == sFile) {
-		file.clear();
-	}
-}
-
 HashManager::HashStore::HashStore() :
 	dirty(false) {
 
@@ -469,8 +466,8 @@
 
 void HashManager::Hasher::stopHashing(const string& baseDir) {
 	Lock l(cs);
-	for (auto i = w.begin(); i != w.end();) {
-		if (Util::strnicmp(baseDir, i->first, baseDir.length()) == 0) {
+	for(auto i = w.begin(); i != w.end();) {
+		if(strncmp(baseDir.c_str(), i->first.c_str(), baseDir.size()) == 0) {
 			w.erase(i++);
 		} else {
 			++i;

=== modified file 'dcpp/HashManager.h'
--- dcpp/HashManager.h	2012-12-13 17:50:01 +0000
+++ dcpp/HashManager.h	2012-12-14 17:07:25 +0000
@@ -67,7 +67,7 @@
 	bool getTree(const TTHValue& root, TigerTree& tt);
 
 	/** Return block size of the tree associated with root, or 0 if no such tree is in the store */
-	size_t getBlockSize(const TTHValue& root);
+	int64_t getBlockSize(const TTHValue& root);
 
 	void addTree(const string& aFileName, uint32_t aTimeStamp, const TigerTree& tt) {
 		hashDone(aFileName, aTimeStamp, tt, -1, -1);
@@ -127,10 +127,7 @@
 	private:
 		// Case-sensitive (faster), it is rather unlikely that case changes, and if it does it's harmless.
 		// map because it's sorted (to avoid random hash order that would create quite strange shares while hashing)
-		typedef map<string, int64_t> WorkMap;
-		typedef WorkMap::iterator WorkIter;
-
-		WorkMap w;
+		map<string, int64_t> w;
 		mutable CriticalSection cs;
 		Semaphore s;
 
@@ -160,7 +157,7 @@
 
 		void addTree(const TigerTree& tt) noexcept;
 		bool getTree(const TTHValue& root, TigerTree& tth);
-		size_t getBlockSize(const TTHValue& root) const;
+		int64_t getBlockSize(const TTHValue& root) const;
 		bool isDirty() { return dirty; }
 	private:
 		/** Root -> tree mapping info, we assume there's only one tree for each root (a collision would mean we've broken tiger...) */
@@ -187,19 +184,10 @@
 			GETSET(bool, used, Used);
 		};
 
-		typedef vector<FileInfo> FileInfoList;
-		typedef FileInfoList::iterator FileInfoIter;
-
-		typedef unordered_map<string, FileInfoList> DirMap;
-		typedef DirMap::iterator DirIter;
-
-		typedef unordered_map<TTHValue, TreeInfo> TreeMap;
-		typedef TreeMap::iterator TreeIter;
-
 		friend class HashLoader;
 
-		DirMap fileIndex;
-		TreeMap treeIndex;
+		unordered_map<string, vector<FileInfo>> fileIndex;
+		unordered_map<TTHValue, TreeInfo> treeIndex;
 
 		bool dirty;
 

=== modified file 'dcpp/QueueManager.cpp'
--- dcpp/QueueManager.cpp	2012-11-06 18:52:30 +0000
+++ dcpp/QueueManager.cpp	2012-12-14 17:07:25 +0000
@@ -208,7 +208,7 @@
 					continue;
 				}
 				if(!qi->isSet(QueueItem::FLAG_USER_LIST)) {
-					int64_t blockSize = HashManager::getInstance()->getBlockSize(qi->getTTH());
+					auto blockSize = HashManager::getInstance()->getBlockSize(qi->getTTH());
 					if(blockSize == 0)
 						blockSize = qi->getSize();
 					if(qi->getNextSegment(blockSize, wantedSize).getSize() == 0) {

=== modified file 'dcpp/ShareManager.cpp'
--- dcpp/ShareManager.cpp	2012-12-13 18:05:50 +0000
+++ dcpp/ShareManager.cpp	2012-12-14 17:07:25 +0000
@@ -702,8 +702,10 @@
 			// don't share the private key file
 			if(Util::stricmp(fileName, SETTING(TLS_PRIVATE_KEY_FILE)) == 0) { continue; }
 
-			lastFileIter = dir->files.insert(lastFileIter, Directory::File(name, size, dir,
-				HashManager::getInstance()->getTTH(fileName, size, i->getLastWriteTime())));
+			Directory::File f(name, size, dir,
+				HashManager::getInstance()->getTTH(fileName, size, i->getLastWriteTime()));
+			f.validateName(aName);
+			lastFileIter = dir->files.insert(lastFileIter, move(f));
 		}
 	}
 
@@ -1502,8 +1504,10 @@
 		// Check if the finished download dir is supposed to be shared
 		auto dir = getDirectory(realPath);
 		if(dir) {
-			dir->files.insert(Directory::File(Util::getFileName(realPath), size, dir,
-				HashManager::getInstance()->getTTH(realPath, size, 0)));
+			Directory::File f(Util::getFileName(realPath), size, dir,
+				HashManager::getInstance()->getTTH(realPath, size, 0));
+			f.validateName(Util::getFilePath(realPath));
+			dir->files.insert(move(f));
 		}
 	}
 }