1 /*
2 * Copyright (C) 2001-2012 Jacek Sieka, arnetheduck on gmail point com
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #include "stdinc.h"
20
21 #include "ShareManager.h"
22 #include "AdcHub.h"
23 #include "CryptoManager.h"
24 #include "ClientManager.h"
25 #include "LogManager.h"
26 #include "HashManager.h"
27 #include "QueueManager.h"
28 #include "UploadManager.h"
29 #include "SimpleXML.h"
30 #include "StringTokenizer.h"
31 #include "File.h"
32 #include "FilteredFile.h"
33 #include "BZUtils.h"
34 #include "Wildcards.h"
35 #include "Transfer.h"
36 #include "UserConnection.h"
37 #include "Download.h"
38 #include "HashBloom.h"
39 #include "SearchResult.h"
40 #include "version.h"
41 #ifdef WITH_DHT
42 #include "dht/IndexManager.h"
43 #endif
44 #ifndef _WIN32
45 #include <sys/types.h>
46 #include <dirent.h>
47 #include <sys/stat.h>
48 #include <unistd.h>
49 #include <fnmatch.h>
50 #endif
51
52 #include <limits>
53
54 namespace dcpp {
55
ShareManager()56 ShareManager::ShareManager() : hits(0), xmlListLen(0), bzXmlListLen(0),
57 xmlDirty(true), forceXmlRefresh(false), refreshDirs(false), update(false), initial(true), listN(0), refreshing(false),
58 lastXmlUpdate(0), lastFullUpdate(GET_TICK()), bloom(1<<20)
59 {
60 SettingsManager::getInstance()->addListener(this);
61 TimerManager::getInstance()->addListener(this);
62 QueueManager::getInstance()->addListener(this);
63 HashManager::getInstance()->addListener(this);
64 }
65
~ShareManager()66 ShareManager::~ShareManager() {
67 SettingsManager::getInstance()->removeListener(this);
68 TimerManager::getInstance()->removeListener(this);
69 QueueManager::getInstance()->removeListener(this);
70 HashManager::getInstance()->removeListener(this);
71
72 join();
73
74 if(bzXmlRef.get()) {
75 bzXmlRef.reset();
76 File::deleteFile(getBZXmlFile());
77 }
78 }
79
Directory(const string & aName,const ShareManager::Directory::Ptr & aParent)80 ShareManager::Directory::Directory(const string& aName, const ShareManager::Directory::Ptr& aParent) :
81 size(0),
82 name(aName),
83 parent(aParent.get()),
84 fileTypes(1 << SearchManager::TYPE_DIRECTORY)
85 {
86 }
87
getADCPath() const88 string ShareManager::Directory::getADCPath() const noexcept {
89 if(!getParent())
90 return '/' + name + '/';
91 return getParent()->getADCPath() + name + '/';
92 }
93
getFullName() const94 string ShareManager::Directory::getFullName() const noexcept {
95 if(!getParent())
96 return getName() + '\\';
97 return getParent()->getFullName() + getName() + '\\';
98 }
99
addType(uint32_t type)100 void ShareManager::Directory::addType(uint32_t type) noexcept {
101 if(!hasType(type)) {
102 fileTypes |= (1 << type);
103 if(getParent())
104 getParent()->addType(type);
105 }
106 }
107
getRealPath(const std::string & path) const108 string ShareManager::Directory::getRealPath(const std::string& path) const {
109 if(getParent()) {
110 return getParent()->getRealPath(getName() + PATH_SEPARATOR_STR + path);
111 } else {
112 return ShareManager::getInstance()->findRealRoot(getName(), path);
113 }
114 }
115
findRealRoot(const string & virtualRoot,const string & virtualPath) const116 string ShareManager::findRealRoot(const string& virtualRoot, const string& virtualPath) const {
117 for(auto i = shares.begin(); i != shares.end(); ++i) {
118 if(Util::stricmp(i->second, virtualRoot) == 0) {
119 std::string name = i->first + virtualPath;
120 dcdebug("Matching %s\n", name.c_str());
121 if (File::getSize(name) != -1)//NOTE: see core 0.750
122 return name;
123 }
124 }
125
126 throw ShareException(UserConnection::FILE_NOT_AVAILABLE);
127 }
128
getSize() const129 int64_t ShareManager::Directory::getSize() const noexcept {
130 int64_t tmp = size;
131 for(auto i = directories.begin(); i != directories.end(); ++i)
132 tmp+=i->second->getSize();
133 return tmp;
134 }
135
toVirtual(const TTHValue & tth) const136 string ShareManager::toVirtual(const TTHValue& tth) const {
137 if(tth == bzXmlRoot) {
138 return Transfer::USER_LIST_NAME_BZ;
139 } else if(tth == xmlRoot) {
140 return Transfer::USER_LIST_NAME;
141 }
142
143 Lock l(cs);
144 auto i = tthIndex.find(tth);
145 if(i != tthIndex.end()) {
146 return i->second->getADCPath();
147 } else {
148 throw ShareException(UserConnection::FILE_NOT_AVAILABLE);
149 }
150 }
151
toReal(const string & virtualFile)152 string ShareManager::toReal(const string& virtualFile) {
153 Lock l(cs);
154 if(virtualFile == "MyList.DcLst") {
155 throw ShareException("NMDC-style lists no longer supported, please upgrade your client");
156 } else if(virtualFile == Transfer::USER_LIST_NAME_BZ || virtualFile == Transfer::USER_LIST_NAME) {
157 generateXmlList();
158 return getBZXmlFile();
159 }
160
161 return findFile(virtualFile)->getRealPath();
162 }
163
getRealPaths(const string & virtualPath)164 StringList ShareManager::getRealPaths(const string& virtualPath) {
165 if(virtualPath.empty())
166 throw ShareException("empty virtual path");
167
168 StringList ret;
169
170 Lock l(cs);
171
172 if(*(virtualPath.end() - 1) == '/') {
173 // directory
174 Directory::Ptr d = splitVirtual(virtualPath).first;
175
176 // imitate Directory::getRealPath
177 if(d->getParent()) {
178 ret.push_back(d->getParent()->getRealPath(d->getName()));
179 } else {
180 for(auto i = shares.begin(); i != shares.end(); ++i) {
181 if(Util::stricmp(i->second, d->getName()) == 0) {
182 // remove the trailing path sep
183 if(FileFindIter(i->first.substr(0, i->first.size() - 1)) != FileFindIter()) {
184 ret.push_back(i->first);
185 }
186 }
187 }
188 }
189
190 } else {
191 // file
192 ret.push_back(toReal(virtualPath));
193 }
194
195 return ret;
196 }
197
getTTH(const string & virtualFile) const198 TTHValue ShareManager::getTTH(const string& virtualFile) const {
199 Lock l(cs);
200 if(virtualFile == Transfer::USER_LIST_NAME_BZ) {
201 return bzXmlRoot;
202 } else if(virtualFile == Transfer::USER_LIST_NAME) {
203 return xmlRoot;
204 }
205
206 return findFile(virtualFile)->getTTH();
207 }
208
getTree(const string & virtualFile) const209 MemoryInputStream* ShareManager::getTree(const string& virtualFile) const {
210 TigerTree tree;
211 if(virtualFile.compare(0, 4, "TTH/") == 0) {
212 if(!HashManager::getInstance()->getTree(TTHValue(virtualFile.substr(4)), tree))
213 return 0;
214 } else {
215 try {
216 TTHValue tth = getTTH(virtualFile);
217 HashManager::getInstance()->getTree(tth, tree);
218 } catch(const Exception&) {
219 return 0;
220 }
221 }
222
223 ByteVector buf = tree.getLeafData();
224 return new MemoryInputStream(&buf[0], buf.size());
225 }
226
getFileInfo(const string & aFile)227 AdcCommand ShareManager::getFileInfo(const string& aFile) {
228 if(aFile == Transfer::USER_LIST_NAME) {
229 generateXmlList();
230 AdcCommand cmd(AdcCommand::CMD_RES);
231 cmd.addParam("FN", aFile);
232 cmd.addParam("SI", Util::toString(xmlListLen));
233 cmd.addParam("TR", xmlRoot.toBase32());
234 return cmd;
235 } else if(aFile == Transfer::USER_LIST_NAME_BZ) {
236 generateXmlList();
237
238 AdcCommand cmd(AdcCommand::CMD_RES);
239 cmd.addParam("FN", aFile);
240 cmd.addParam("SI", Util::toString(bzXmlListLen));
241 cmd.addParam("TR", bzXmlRoot.toBase32());
242 return cmd;
243 }
244
245 if(aFile.compare(0, 4, "TTH/") != 0)
246 throw ShareException(UserConnection::FILE_NOT_AVAILABLE);
247
248 TTHValue val(aFile.substr(4));
249 Lock l(cs);
250 auto i = tthIndex.find(val);
251 if(i == tthIndex.end()) {
252 throw ShareException(UserConnection::FILE_NOT_AVAILABLE);
253 }
254
255 const Directory::File& f = *i->second;
256 AdcCommand cmd(AdcCommand::CMD_RES);
257 cmd.addParam("FN", f.getADCPath());
258 cmd.addParam("SI", Util::toString(f.getSize()));
259 cmd.addParam("TR", f.getTTH().toBase32());
260 return cmd;
261 }
262
splitVirtual(const string & virtualPath) const263 pair<ShareManager::Directory::Ptr, string> ShareManager::splitVirtual(const string& virtualPath) const {
264 if(virtualPath.empty() || virtualPath[0] != '/') {
265 throw ShareException(UserConnection::FILE_NOT_AVAILABLE);
266 }
267
268 auto i = virtualPath.find('/', 1);
269 if(i == string::npos || i == 1) {
270 throw ShareException(UserConnection::FILE_NOT_AVAILABLE);
271 }
272
273 auto dmi = getByVirtual( virtualPath.substr(1, i - 1));
274 if(dmi == directories.end()) {
275 throw ShareException(UserConnection::FILE_NOT_AVAILABLE);
276 }
277
278 auto d = *dmi;
279
280 auto j = i + 1;
281 while((i = virtualPath.find('/', j)) != string::npos) {
282 auto mi = d->directories.find(virtualPath.substr(j, i - j));
283 j = i + 1;
284 if(mi == d->directories.end())
285 throw ShareException(UserConnection::FILE_NOT_AVAILABLE);
286 d = mi->second;
287 }
288
289 return make_pair(d, virtualPath.substr(j));
290 }
291
findFile(const string & virtualFile) const292 ShareManager::Directory::File::Set::const_iterator ShareManager::findFile(const string& virtualFile) const {
293 if(virtualFile.compare(0, 4, "TTH/") == 0) {
294 auto i = tthIndex.find(TTHValue(virtualFile.substr(4)));
295 if(i == tthIndex.end()) {
296 throw ShareException(UserConnection::FILE_NOT_AVAILABLE);
297 }
298 return i->second;
299 }
300
301 auto v = splitVirtual(virtualFile);
302 auto it = find_if(v.first->files.begin(), v.first->files.end(),
303 Directory::File::StringComp(v.second));
304 if(it == v.first->files.end())
305 throw ShareException(UserConnection::FILE_NOT_AVAILABLE);
306 return it;
307 }
308
validateVirtual(const string & aVirt) const309 string ShareManager::validateVirtual(const string& aVirt) const noexcept {
310 string tmp = aVirt;
311 string::size_type idx = 0;
312
313 while( (idx = tmp.find_first_of("\\/"), idx) != string::npos) {
314 tmp[idx] = '_';
315 }
316 return tmp;
317 }
318
hasVirtual(const string & virtualName) const319 bool ShareManager::hasVirtual(const string& virtualName) const noexcept {
320 Lock l(cs);
321 return getByVirtual(virtualName) != directories.end();
322 }
323
load(SimpleXML & aXml)324 void ShareManager::load(SimpleXML& aXml) {
325 Lock l(cs);
326
327 aXml.resetCurrentChild();
328 if(aXml.findChild("Share")) {
329 aXml.stepIn();
330 while(aXml.findChild("Directory")) {
331 string realPath = aXml.getChildData();
332 if(realPath.empty()) {
333 continue;
334 }
335 // make sure realPath ends with a PATH_SEPARATOR
336 if(realPath[realPath.size() - 1] != PATH_SEPARATOR) {
337 realPath += PATH_SEPARATOR;
338 }
339
340 const string& virtualName = aXml.getChildAttrib("Virtual");
341 string vName = validateVirtual(virtualName.empty() ? Util::getLastDir(realPath) : virtualName);
342 shares.insert(std::make_pair(realPath, vName));
343 if(getByVirtual(vName) == directories.end()) {
344 directories.push_back(Directory::create(vName));
345 }
346 }
347 aXml.stepOut();
348 }
349 }
350
351 static const string SDIRECTORY = "Directory";
352 static const string SFILE = "File";
353 static const string SNAME = "Name";
354 static const string SSIZE = "Size";
355 static const string STTH = "TTH";
356
357 struct ShareLoader : public SimpleXMLReader::CallBack {
ShareLoaderdcpp::ShareLoader358 ShareLoader(ShareManager::DirList& aDirs) : dirs(aDirs), cur(0), depth(0) { }
startTagdcpp::ShareLoader359 virtual void startTag(const string& name, StringPairList& attribs, bool simple) {
360 if(name == SDIRECTORY) {
361 const string& name = getAttrib(attribs, SNAME, 0);
362 if(!name.empty()) {
363 if(depth == 0) {
364 for(auto i = dirs.begin(); i != dirs.end(); ++i) {
365 if(Util::stricmp((*i)->getName(), name) == 0) {
366 cur = *i;
367 break;
368 }
369 }
370 } else if(cur) {
371 cur = ShareManager::Directory::create(name, cur);
372 cur->getParent()->directories[cur->getName()] = cur;
373 }
374 }
375
376 if(simple) {
377 if(cur) {
378 cur = cur->getParent();
379 }
380 } else {
381 depth++;
382 }
383 } else if(cur && name == SFILE) {
384 const string& fname = getAttrib(attribs, SNAME, 0);
385 const string& size = getAttrib(attribs, SSIZE, 1);
386 const string& root = getAttrib(attribs, STTH, 2);
387 if(fname.empty() || size.empty() || (root.size() != 39)) {
388 dcdebug("Invalid file found: %s\n", fname.c_str());
389 return;
390 }
391 cur->files.insert(ShareManager::Directory::File(fname, Util::toInt64(size), cur, TTHValue(root)));
392 }
393 }
endTagdcpp::ShareLoader394 virtual void endTag(const string& name, const string&) {
395 if(name == SDIRECTORY) {
396 depth--;
397 if(cur) {
398 cur = cur->getParent();
399 }
400 }
401 }
402
403 private:
404 ShareManager::DirList& dirs;
405
406 ShareManager::Directory::Ptr cur;
407 size_t depth;
408 };
409
loadCache()410 bool ShareManager::loadCache() noexcept {
411 try {
412 ShareLoader loader(directories);
413 SimpleXMLReader xml(&loader);
414
415 dcpp::File ff(Util::getPath(Util::PATH_USER_CONFIG) + "files.xml.bz2", dcpp::File::READ, dcpp::File::OPEN);
416 FilteredInputStream<UnBZFilter, false> f(&ff);
417
418 xml.parse(f);
419
420 for(auto i = directories.begin(); i != directories.end(); ++i) {
421 const Directory::Ptr& d = *i;
422 updateIndices(*d);
423 }
424
425 return true;
426 } catch(const Exception& e) {
427 dcdebug("%s\n", e.getError().c_str());
428 }
429 return false;
430 }
431
save(SimpleXML & aXml)432 void ShareManager::save(SimpleXML& aXml) {
433 Lock l(cs);
434
435 aXml.addTag("Share");
436 aXml.stepIn();
437 for(auto i = shares.begin(); i != shares.end(); ++i) {
438 aXml.addTag("Directory", i->first);
439 aXml.addChildAttrib("Virtual", i->second);
440 }
441 aXml.stepOut();
442 }
443
addDirectory(const string & realPath,const string & virtualName)444 void ShareManager::addDirectory(const string& realPath, const string& virtualName) {
445 if(realPath.empty() || virtualName.empty()) {
446 throw ShareException(_("No directory specified"));
447 }
448
449 if (!checkHidden(realPath)) {
450 throw ShareException(_("Directory is hidden"));
451 }
452
453 if(Util::stricmp(SETTING(TEMP_DOWNLOAD_DIRECTORY), realPath) == 0) {
454 throw ShareException(_("The temporary download directory cannot be shared"));
455 }
456 list<string> removeMap;
457 {
458 Lock l(cs);
459
460 for(auto i = shares.begin(); i != shares.end(); ++i) {
461 if(Util::strnicmp(realPath, i->first, i->first.length()) == 0) {
462 // Trying to share an already shared directory
463 //throw ShareException(_("Directory already shared"));
464 removeMap.push_front(i->first);
465 } else if(Util::strnicmp(realPath, i->first, realPath.length()) == 0) {
466 // Trying to share a parent directory
467 //throw ShareException(_("Remove all subdirectories before adding this one"));
468 removeMap.push_front(i->first);
469 }
470 }
471 }
472 for(list<string>::const_iterator i = removeMap.begin(); i != removeMap.end(); ++i) {
473 removeDirectory(*i);
474 }
475
476
477 HashManager::HashPauser pauser;
478
479 Directory::Ptr dp = buildTree(realPath, Directory::Ptr());
480
481 string vName = validateVirtual(virtualName);
482 dp->setName(vName);
483
484 {
485 Lock l(cs);
486
487 shares.insert(std::make_pair(realPath, vName));
488 updateIndices(*merge(dp));
489
490 setDirty();
491 }
492 }
493
merge(const Directory::Ptr & directory)494 ShareManager::Directory::Ptr ShareManager::merge(const Directory::Ptr& directory) {
495 for(auto i = directories.begin(); i != directories.end(); ++i) {
496 if(Util::stricmp((*i)->getName(), directory->getName()) == 0) {
497 dcdebug("Merging directory %s\n", directory->getName().c_str());
498 (*i)->merge(directory);
499 return *i;
500 }
501 }
502
503 dcdebug("Adding new directory %s\n", directory->getName().c_str());
504
505 directories.push_back(directory);
506 return directory;
507 }
508
merge(const Directory::Ptr & source)509 void ShareManager::Directory::merge(const Directory::Ptr& source) {
510 for(auto i = source->directories.begin(); i != source->directories.end(); ++i) {
511 auto subSource = i->second;
512
513 auto ti = directories.find(subSource->getName());
514 if(ti == directories.end()) {
515 if(findFile(subSource->getName()) != files.end()) {
516 dcdebug("File named the same as directory");
517 } else {
518 directories.insert(std::make_pair(subSource->getName(), subSource));
519 subSource->parent = this;
520 }
521 } else {
522 auto subTarget = ti->second;
523 subTarget->merge(subSource);
524 }
525 }
526
527 // All subdirs either deleted or moved to target...
528 source->directories.clear();
529
530 for(auto i = source->files.begin(); i != source->files.end(); ++i) {
531 if(findFile(i->getName()) == files.end()) {
532 if(directories.find(i->getName()) != directories.end()) {
533 dcdebug("Directory named the same as file");
534 } else {
535 auto added = files.insert(*i);
536 if(added.second) {
537 const_cast<File&>(*added.first).setParent(this);
538 }
539 }
540 }
541 }
542 }
543
removeDirectory(const string & realPath)544 void ShareManager::removeDirectory(const string& realPath) {
545 if(realPath.empty())
546 return;
547
548 HashManager::getInstance()->stopHashing(realPath);
549
550 Lock l(cs);
551
552 auto i = shares.find(realPath);
553 if(i == shares.end()) {
554 return;
555 }
556
557 auto vName = i->second;
558 for(auto j = directories.begin(); j != directories.end(); ) {
559 if(Util::stricmp((*j)->getName(), vName) == 0) {
560 directories.erase(j++);
561 } else {
562 ++j;
563 }
564 }
565
566 shares.erase(i);
567
568 HashManager::HashPauser pauser;
569
570 // Readd all directories with the same vName
571 for(i = shares.begin(); i != shares.end(); ++i) {
572 if(Util::stricmp(i->second, vName) == 0 && checkHidden(i->first)) {
573 Directory::Ptr dp = buildTree(i->first, 0);
574 dp->setName(i->second);
575 merge(dp);
576 }
577 }
578
579 rebuildIndices();
580 setDirty();
581 }
582
renameDirectory(const string & realPath,const string & virtualName)583 void ShareManager::renameDirectory(const string& realPath, const string& virtualName) {
584 removeDirectory(realPath);
585 addDirectory(realPath, virtualName);
586 }
587
getByVirtual(const string & virtualName) const588 ShareManager::DirList::const_iterator ShareManager::getByVirtual(const string& virtualName) const noexcept {
589 for(auto i = directories.begin(); i != directories.end(); ++i) {
590 if(Util::stricmp((*i)->getName(), virtualName) == 0) {
591 return i;
592 }
593 }
594 return directories.end();
595 }
596
getShareSize(const string & realPath) const597 int64_t ShareManager::getShareSize(const string& realPath) const noexcept {
598 Lock l(cs);
599 dcassert(!realPath.empty());
600 auto i = shares.find(realPath);
601
602 if(i != shares.end()) {
603 auto j = getByVirtual(i->second);
604 if(j != directories.end()) {
605 return (*j)->getSize();
606 }
607 }
608 return -1;
609 }
610
getShareSize() const611 int64_t ShareManager::getShareSize() const noexcept {
612 Lock l(cs);
613 int64_t tmp = 0;
614 for(auto i = tthIndex.begin(); i != tthIndex.end(); ++i) {
615 tmp += i->second->getSize();
616 }
617 return tmp;
618 }
619
getSharedFiles() const620 size_t ShareManager::getSharedFiles() const noexcept {
621 Lock l(cs);
622 return tthIndex.size();
623 }
624
buildTree(const string & aName,const Directory::Ptr & aParent)625 ShareManager::Directory::Ptr ShareManager::buildTree(const string& aName, const Directory::Ptr& aParent) {
626 auto dir = Directory::create(Util::getLastDir(aName), aParent);
627
628 auto lastFileIter = dir->files.begin();
629
630 FileFindIter end;
631 const string l_skip_list = SETTING(SKIPLIST_SHARE);
632 #ifdef _WIN32
633 for(FileFindIter i(aName + "*"); i != end; ++i) {
634 #else
635 //the fileiter just searches directorys for now, not sure if more
636 //will be needed later
637 //for(FileFindIter i(aName + "*"); i != end; ++i) {
638 for(FileFindIter i(aName); i != end; ++i) {
639 #endif
640 string name = i->getFileName();
641 if(name.empty()) {
642 LogManager::getInstance()->message(str(F_("Invalid file name found while hashing folder %1%") % Util::addBrackets(aName)));
643 continue;
644 }
645
646 if(name == "." || name == "..")
647 continue;
648 if(!BOOLSETTING(SHARE_HIDDEN) && i->isHidden())
649 continue;
650 if(!BOOLSETTING(FOLLOW_LINKS) && i->isLink())
651 continue;
652
653 int64_t size = i->getSize();
654
655 string fileName = aName + name;
656
657 if (l_skip_list.size())
658 {
659 if (Wildcard::patternMatch(fileName , l_skip_list, '|'))
660 {
661 LogManager::getInstance()->message(str(F_("Skip share file: %1% (Size: %2%)")
662 % Util::addBrackets(fileName) % Util::formatBytes(size)));
663 continue;
664 }
665 }
666 if(i->isDirectory()) {
667 string newName = aName + name + PATH_SEPARATOR;
668 if((::strcmp(newName.c_str(), SETTING(TEMP_DOWNLOAD_DIRECTORY).c_str()) != 0)
669 && (::strcmp(newName.c_str(), Util::getPath(Util::PATH_USER_CONFIG).c_str()) != 0)
670 && (::strcmp(newName.c_str(), SETTING(LOG_DIRECTORY).c_str()) != 0)) {
671 dir->directories[name] = buildTree(newName, dir);
672 }
673 } else {
674 // Not a directory, assume it's a file...make sure we're not sharing the settings file...
675 const string l_ext = Util::getFileExt(name);
676 if ((name != "Thumbs.db") &&
677 (name != "desktop.ini") &&
678 (name != "folder.htt")
679 ) {
680 if (!BOOLSETTING(SHARE_TEMP_FILES) &&
681 (::strcmp(l_ext.c_str(), ".dctmp") == 0)) {
682 LogManager::getInstance()->message(str(F_("Skip share temp file: %1% (Size: %2%)")
683 % Util::addBrackets(fileName) % Util::formatBytes(size)));
684 continue;
685 }
686 if (BOOLSETTING(SHARE_SKIP_ZERO_BYTE) && size == 0)
687 continue;
688 if(Util::stricmp(fileName, SETTING(TLS_PRIVATE_KEY_FILE)) == 0) {
689 continue;
690 }
691 try {
692 if(HashManager::getInstance()->checkTTH(fileName, size, i->getLastWriteTime()))
693 lastFileIter = dir->files.insert(lastFileIter, Directory::File(name, size, dir, HashManager::getInstance()->getTTH(fileName, size)));
694 } catch(const HashException&) {
695 }
696 }
697 }
698 }
699
700 return dir;
701 }
702
703 //NOTE: freedcpp [+
704 #ifdef _WIN32
705 bool ShareManager::checkHidden(const string& aName) const {
706 FileFindIter ff = FileFindIter(aName.substr(0, aName.size() - 1));
707
708 if (ff != FileFindIter()) {
709 return (BOOLSETTING(SHARE_HIDDEN) || !ff->isHidden());
710 }
711
712 return true;
713 }
714
715 #else // !_WIN32
716
717 bool ShareManager::checkHidden(const string& aName) const
718 {
719 // check open a directory
720 if (!(FileFindIter(aName) != FileFindIter()))
721 return true;
722
723 // check hidden directory
724 bool hidden = false;
725 string path = aName.substr(0, aName.size() - 1);
726 string::size_type i = path.rfind(PATH_SEPARATOR);
727
728 if (i != string::npos)
729 {
730 string dir = path.substr(i + 1);
731 if (dir[0] == '.')
732 hidden = true;
733 }
734
735 return (BOOLSETTING(SHARE_HIDDEN) || !hidden);
736 }
737 #endif // !_WIN32
738 //NOTE: freedcpp +]
739
740 void ShareManager::updateIndices(Directory& dir) {
741 bloom.add(Text::toLower(dir.getName()));
742
743 for(auto i = dir.directories.begin(); i != dir.directories.end(); ++i) {
744 updateIndices(*i->second);
745 }
746
747 dir.size = 0;
748
749 for(auto i = dir.files.begin(); i != dir.files.end(); ++i) {
750 updateIndices(dir, i);
751 }
752 }
753
754 void ShareManager::rebuildIndices() {
755 tthIndex.clear();
756 bloom.clear();
757
758 for(auto i = directories.begin(); i != directories.end(); ++i) {
759 updateIndices(**i);
760 }
761 }
762
763 void ShareManager::updateIndices(Directory& dir, const Directory::File::Set::iterator& i) {
764 const Directory::File& f = *i;
765
766 auto j = tthIndex.find(f.getTTH());
767 if(j == tthIndex.end()) {
768 dir.size+=f.getSize();
769 } else {
770 if(!SETTING(LIST_DUPES)) {
771 try {
772 LogManager::getInstance()->message(str(F_("Duplicate file will not be shared: %1% (Size: %2% B) Dupe matched against: %3%")
773 % Util::addBrackets(dir.getRealPath(f.getName())) % Util::toString(f.getSize()) % Util::addBrackets(j->second->getParent()->getRealPath(j->second->getName()))));
774 dir.files.erase(i);
775 } catch (const ShareException&) { }
776 return;
777 }
778 }
779
780 dir.addType(getType(f.getName()));
781
782 tthIndex.insert(make_pair(f.getTTH(), i));
783 bloom.add(Text::toLower(f.getName()));
784 #ifdef WITH_DHT
785 dht::IndexManager* im = dht::IndexManager::getInstance();
786 if(im && im->isTimeForPublishing())
787 im->publishFile(f.getTTH(), f.getSize());
788 #endif
789 }
790
791 void ShareManager::refresh(bool dirs /* = false */, bool aUpdate /* = true */, bool block /* = false */) noexcept {
792 if(refreshing.exchange(true) == true) {
793 LogManager::getInstance()->message(_("File list refresh in progress, please wait for it to finish before trying to refresh again"));
794 return;
795 }
796 UploadManager::getInstance()->updateLimits();
797
798 update = aUpdate;
799 refreshDirs = dirs;
800 join();
801 bool cached = false;
802 if(initial) {
803 cached = loadCache();
804 initial = false;
805 }
806 try {
807 start();
808 if(block && !cached) {
809 join();
810 } else {
811 setThreadPriority(Thread::LOW);
812 }
813 } catch(const ThreadException& e) {
814 LogManager::getInstance()->message(str(F_("File list refresh failed: %1%") % e.getError()));
815 }
816 }
817
818 StringPairList ShareManager::getDirectories() const noexcept {
819 Lock l(cs);
820 StringPairList ret;
821 for(auto i = shares.begin(); i != shares.end(); ++i) {
822 ret.push_back(make_pair(i->second, i->first));
823 }
824 return ret;
825 }
826
827 int ShareManager::run() {
828 setThreadName("ShareManager");
829
830 StringPairList dirs = getDirectories();
831 // Don't need to refresh if no directories are shared
832 if(dirs.empty())
833 refreshDirs = false;
834
835 if(refreshDirs) {
836 HashManager::HashPauser pauser;
837 LogManager::getInstance()->message(_("File list refresh initiated"));
838
839 lastFullUpdate = GET_TICK();
840
841 DirList newDirs;
842 for(auto i = dirs.begin(); i != dirs.end(); ++i) {
843 if (checkHidden(i->second)) {
844 Directory::Ptr dp = buildTree(i->second, Directory::Ptr());
845 dp->setName(i->first);
846 newDirs.push_back(dp);
847 }
848 }
849
850 {
851 Lock l(cs);
852 directories.clear();
853
854 for(auto i = newDirs.begin(); i != newDirs.end(); ++i) {
855 merge(*i);
856 }
857
858 rebuildIndices();
859 }
860 refreshDirs = false;
861
862 LogManager::getInstance()->message(_("File list refresh finished"));
863 }
864
865 if(update) {
866 ClientManager::getInstance()->infoUpdated();
867 }
868 refreshing = false;
869 #ifdef WITH_DHT
870 dht::IndexManager* im = dht::IndexManager::getInstance();
871 if(im && im->isTimeForPublishing())
872 im->setNextPublishing();
873 #endif
874 return 0;
875 }
876
877 void ShareManager::getBloom(ByteVector& v, size_t k, size_t m, size_t h) const {
878 dcdebug("Creating bloom filter, k=%u, m=%u, h=%u\n",
879 static_cast<unsigned int>(k), static_cast<unsigned int>(m), static_cast<unsigned int>(h));
880 Lock l(cs);
881
882 HashBloom bloom;
883 bloom.reset(k, m, h);
884 for(auto i = tthIndex.begin(); i != tthIndex.end(); ++i) {
885 bloom.add(i->first);
886 }
887 bloom.copy_to(v);
888 }
889
890 void ShareManager::generateXmlList() {
891 Lock l(cs);
892 if(forceXmlRefresh || (xmlDirty && (lastXmlUpdate + 15 * 60 * 1000 < GET_TICK() || lastXmlUpdate < lastFullUpdate))) {
893 listN++;
894
895 try {
896 string tmp2;
897 string indent;
898
899 string newXmlName = Util::getPath(Util::PATH_USER_CONFIG) + "files" + Util::toString(listN) + ".xml.bz2";
900 {
901 File f(newXmlName, File::WRITE, File::TRUNCATE | File::CREATE);
902 // We don't care about the leaves...
903 CalcOutputStream<TTFilter<1024*1024*1024>, false> bzTree(&f);
904 FilteredOutputStream<BZFilter, false> bzipper(&bzTree);
905 CountOutputStream<false> count(&bzipper);
906 CalcOutputStream<TTFilter<1024*1024*1024>, false> newXmlFile(&count);
907
908 newXmlFile.write(SimpleXML::utf8Header);
909 newXmlFile.write("<FileListing Version=\"1\" CID=\"" + ClientManager::getInstance()->getMe()->getCID().toBase32() + "\" Base=\"/\" Generator=\"" APPNAME " " VERSIONSTRING "\">\r\n");
910 for(auto i = directories.begin(); i != directories.end(); ++i) {
911 (*i)->toXml(newXmlFile, indent, tmp2, true);
912 }
913 newXmlFile.write("</FileListing>");
914 newXmlFile.flush();
915
916 xmlListLen = count.getCount();
917
918 newXmlFile.getFilter().getTree().finalize();
919 bzTree.getFilter().getTree().finalize();
920
921 xmlRoot = newXmlFile.getFilter().getTree().getRoot();
922 bzXmlRoot = bzTree.getFilter().getTree().getRoot();
923 }
924 const string XmlListFileName = Util::getPath(Util::PATH_USER_CONFIG) + "files.xml.bz2";
925 if(bzXmlRef.get()) {
926 bzXmlRef.reset();
927 try {
928 File::renameFile(XmlListFileName, XmlListFileName + ".bak");
929 } catch(const FileException&) { }
930 }
931
932 try {
933 File::renameFile(newXmlName, XmlListFileName);
934 newXmlName = XmlListFileName;
935 } catch(const FileException&) {
936 // Ignore, this is for caching only...
937 }
938 try {
939 File::copyFile(XmlListFileName, XmlListFileName + ".bak");
940 } catch(const FileException&) { }
941 bzXmlRef = unique_ptr<File>(new File(newXmlName, File::READ, File::OPEN));
942 setBZXmlFile(newXmlName);
943 bzXmlListLen = File::getSize(newXmlName);
944 LogManager::getInstance()->message(str(F_("File list %1% generated") % Util::addBrackets(bzXmlFile)));
945 } catch(const Exception&) {
946 // No new file lists...
947 }
948
949 xmlDirty = false;
950 forceXmlRefresh = false;
951 lastXmlUpdate = GET_TICK();
952 }
953 }
954
955 MemoryInputStream* ShareManager::generatePartialList(const string& dir, bool recurse) const {
956 if(dir[0] != '/' || dir[dir.size()-1] != '/')
957 return 0;
958
959 string xml = SimpleXML::utf8Header;
960 string tmp;
961 xml += "<FileListing Version=\"1\" CID=\"" + ClientManager::getInstance()->getMe()->getCID().toBase32() + "\" Base=\"" + SimpleXML::escape(dir, tmp, false) + "\" Generator=\"" APPNAME " " VERSIONSTRING "\">\r\n";
962 StringOutputStream sos(xml);
963 string indent = "\t";
964
965 Lock l(cs);
966 if(dir == "/") {
967 for(auto i = directories.begin(); i != directories.end(); ++i) {
968 tmp.clear();
969 (*i)->toXml(sos, indent, tmp, recurse);
970 }
971 } else {
972 string::size_type i = 1, j = 1;
973
974 Directory::Ptr root;
975
976 bool first = true;
977 while( (i = dir.find('/', j)) != string::npos) {
978 if(i == j) {
979 j++;
980 continue;
981 }
982
983 if(first) {
984 first = false;
985 auto it = getByVirtual(dir.substr(j, i-j));
986
987 if(it == directories.end())
988 return 0;
989 root = *it;
990
991 } else {
992 auto it2 = root->directories.find(dir.substr(j, i-j));
993 if(it2 == root->directories.end()) {
994 return 0;
995 }
996 root = it2->second;
997 }
998 j = i + 1;
999 }
1000
1001 if(!root)
1002 return 0;
1003
1004 for(auto it2 = root->directories.begin(); it2 != root->directories.end(); ++it2) {
1005 it2->second->toXml(sos, indent, tmp, recurse);
1006 }
1007 root->filesToXml(sos, indent, tmp);
1008 }
1009
1010 xml += "</FileListing>";
1011 return new MemoryInputStream(xml);
1012 }
1013
1014 #define LITERAL(n) n, sizeof(n)-1
1015 void ShareManager::Directory::toXml(OutputStream& xmlFile, string& indent, string& tmp2, bool fullList) const {
1016 xmlFile.write(indent);
1017 xmlFile.write(LITERAL("<Directory Name=\""));
1018 xmlFile.write(SimpleXML::escape(name, tmp2, true));
1019
1020 if(fullList) {
1021 xmlFile.write(LITERAL("\">\r\n"));
1022
1023 indent += '\t';
1024 for(auto i = directories.begin(); i != directories.end(); ++i) {
1025 i->second->toXml(xmlFile, indent, tmp2, fullList);
1026 }
1027
1028 filesToXml(xmlFile, indent, tmp2);
1029
1030 indent.erase(indent.length()-1);
1031 xmlFile.write(indent);
1032 xmlFile.write(LITERAL("</Directory>\r\n"));
1033 } else {
1034 if(directories.empty() && files.empty()) {
1035 xmlFile.write(LITERAL("\" />\r\n"));
1036 } else {
1037 xmlFile.write(LITERAL("\" Incomplete=\"1\" />\r\n"));
1038 }
1039 }
1040 }
1041
1042 void ShareManager::Directory::filesToXml(OutputStream& xmlFile, string& indent, string& tmp2) const {
1043 for(auto i = files.begin(); i != files.end(); ++i) {
1044 const Directory::File& f = *i;
1045
1046 xmlFile.write(indent);
1047 xmlFile.write(LITERAL("<File Name=\""));
1048 xmlFile.write(SimpleXML::escape(f.getName(), tmp2, true));
1049 xmlFile.write(LITERAL("\" Size=\""));
1050 xmlFile.write(Util::toString(f.getSize()));
1051 xmlFile.write(LITERAL("\" TTH=\""));
1052 tmp2.clear();
1053 xmlFile.write(f.getTTH().toBase32(tmp2));
1054 xmlFile.write(LITERAL("\"/>\r\n"));
1055 }
1056 }
1057
1058 // These ones we can look up as ints (4 bytes...)...
1059
1060 static const char* typeAudio[] = { ".mp3", ".mp2", ".mid", ".wav", ".ogg", ".wma", ".669", ".aac", ".aif", ".amf", ".ams", ".ape", ".dbm", ".dmf", ".dsm", ".far", ".mdl", ".med", ".mod", ".mol", ".mp1", ".mpa", ".mpc", ".mpp", ".mtm", ".nst", ".okt", ".psm", ".ptm", ".rmi", ".s3m", ".stm", ".ult", ".umx", ".wow" };
1061 static const char* typeCompressed[] = { ".rar", ".zip", ".ace", ".arj", ".hqx", ".lha", ".sea", ".tar", ".tgz", ".uc2" };
1062 static const char* typeDocument[] = { ".htm", ".doc", ".txt", ".nfo", ".pdf", ".chm", ".rtf",
1063 ".xls", ".ppt", ".odt", ".ods", ".odf", ".odp" };
1064 static const char* typeExecutable[] = { ".exe", ".com", ".msi" };
1065 static const char* typePicture[] = { ".jpg", ".gif", ".png", ".eps", ".img", ".pct", ".psp", ".pic", ".tif", ".rle", ".bmp", ".pcx", ".jpe", ".dcx", ".emf", ".ico", ".psd", ".tga", ".wmf", ".xif" };
1066 static const char* typeVideo[] = { ".avi", ".mpg", ".mov", ".flv", ".asf", ".pxp", ".wmv", ".ogm", ".mkv", ".m1v", ".m2v", ".mpe", ".mps", ".mpv", ".ram", ".vob", ".mp4" };
1067 static const char* typeCDImage[] = {".iso", ".mdf", ".mds", ".nrg", ".vcd", ".bwt", ".ccd", ".cdi", ".pdi", ".cue", ".isz", ".img", ".vc4"};
1068
1069 static const string type2Audio[] = { ".au", ".it", ".ra", ".xm", ".aiff", ".flac", ".midi" };
1070 static const string type2Picture[] = { ".ai", ".ps", ".pict", ".jpeg", ".tiff" };
1071 static const string type2Video[] = { ".rm", ".divx", ".mpeg", ".mp1v", ".mp2v", ".mpv1", ".mpv2", ".qt", ".rv", ".vivo", ".ts", ".ps" };
1072
1073 #define IS_TYPE(x) ( type == (*((uint32_t*)x)) )
1074 #define IS_TYPE2(x) (Util::stricmp(aString.c_str() + aString.length() - x.length(), x.c_str()) == 0)
1075
1076 static bool checkType(const string& aString, int aType) {
1077 if(aType == SearchManager::TYPE_ANY)
1078 return true;
1079
1080 if(aString.length() < 5)
1081 return false;
1082
1083 const char* c = aString.c_str() + aString.length() - 3;
1084 if(!Text::isAscii(c))
1085 return false;
1086
1087 uint32_t type = '.' | (Text::asciiToLower(c[0]) << 8) | (Text::asciiToLower(c[1]) << 16) | (((uint32_t)Text::asciiToLower(c[2])) << 24);
1088
1089 switch(aType) {
1090 case SearchManager::TYPE_AUDIO:
1091 {
1092 for(size_t i = 0; i < (sizeof(typeAudio) / sizeof(typeAudio[0])); i++) {
1093 if(IS_TYPE(typeAudio[i])) {
1094 return true;
1095 }
1096 }
1097 if( IS_TYPE2(type2Audio[0]) || IS_TYPE2(type2Audio[1]) || IS_TYPE2(type2Audio[2]) ) {
1098 return true;
1099 }
1100 }
1101 break;
1102 case SearchManager::TYPE_CD_IMAGE:
1103 for(size_t i = 0; i < (sizeof(typeCDImage) / sizeof(typeCDImage[0])); i++) {
1104 if(IS_TYPE(typeCDImage[i])) {
1105 return true;
1106 }
1107 }
1108
1109 break;
1110 case SearchManager::TYPE_COMPRESSED:
1111 if( IS_TYPE(typeCompressed[0]) || IS_TYPE(typeCompressed[1]) || IS_TYPE(typeCompressed[2]) ) {
1112 return true;
1113 }
1114 break;
1115 case SearchManager::TYPE_DOCUMENT:
1116 if( IS_TYPE(typeDocument[0]) || IS_TYPE(typeDocument[1]) ||
1117 IS_TYPE(typeDocument[2]) || IS_TYPE(typeDocument[3]) ) {
1118 return true;
1119 }
1120 break;
1121 case SearchManager::TYPE_EXECUTABLE:
1122 if(IS_TYPE(typeExecutable[0]) ) {
1123 return true;
1124 }
1125 break;
1126 case SearchManager::TYPE_PICTURE:
1127 {
1128 for(size_t i = 0; i < (sizeof(typePicture) / sizeof(typePicture[0])); i++) {
1129 if(IS_TYPE(typePicture[i])) {
1130 return true;
1131 }
1132 }
1133 if( IS_TYPE2(type2Picture[0]) || IS_TYPE2(type2Picture[1]) || IS_TYPE2(type2Picture[2]) ) {
1134 return true;
1135 }
1136 }
1137 break;
1138 case SearchManager::TYPE_VIDEO:
1139 {
1140 for(size_t i = 0; i < (sizeof(typeVideo) / sizeof(typeVideo[0])); i++) {
1141 if(IS_TYPE(typeVideo[i])) {
1142 return true;
1143 }
1144 }
1145 if( IS_TYPE2(type2Video[0]) || IS_TYPE2(type2Video[1]) || IS_TYPE2(type2Video[2]) ) {
1146 return true;
1147 }
1148 }
1149 break;
1150 default:
1151 dcassert(0);
1152 break;
1153 }
1154 return false;
1155 }
1156
1157 SearchManager::TypeModes ShareManager::getType(const string& aFileName) const noexcept {
1158 if(aFileName[aFileName.length() - 1] == PATH_SEPARATOR) {
1159 return SearchManager::TYPE_DIRECTORY;
1160 }
1161
1162 if(checkType(aFileName, SearchManager::TYPE_VIDEO))
1163 return SearchManager::TYPE_VIDEO;
1164 else if(checkType(aFileName, SearchManager::TYPE_AUDIO))
1165 return SearchManager::TYPE_AUDIO;
1166 else if(checkType(aFileName, SearchManager::TYPE_COMPRESSED))
1167 return SearchManager::TYPE_COMPRESSED;
1168 else if(checkType(aFileName, SearchManager::TYPE_DOCUMENT))
1169 return SearchManager::TYPE_DOCUMENT;
1170 else if(checkType(aFileName, SearchManager::TYPE_EXECUTABLE))
1171 return SearchManager::TYPE_EXECUTABLE;
1172 else if(checkType(aFileName, SearchManager::TYPE_PICTURE))
1173 return SearchManager::TYPE_PICTURE;
1174 else if(checkType(aFileName, SearchManager::TYPE_CD_IMAGE))
1175 return SearchManager::TYPE_CD_IMAGE;
1176
1177 return SearchManager::TYPE_ANY;
1178 }
1179
1180 /**
1181 * Alright, the main point here is that when searching, a search string is most often found in
1182 * the filename, not directory name, so we want to make that case faster. Also, we want to
1183 * avoid changing StringLists unless we absolutely have to --> this should only be done if a string
1184 * has been matched in the directory name. This new stringlist should also be used in all descendants,
1185 * but not the parents...
1186 */
1187 void ShareManager::Directory::search(SearchResultList& aResults, StringSearch::List& aStrings, int aSearchType, int64_t aSize, int aFileType, Client* aClient, StringList::size_type maxResults) const noexcept {
1188 // Skip everything if there's nothing to find here (doh! =)
1189 if(!hasType(aFileType))
1190 return;
1191
1192 StringSearch::List* cur = &aStrings;
1193 unique_ptr<StringSearch::List> newStr;
1194
1195 // Find any matches in the directory name
1196 for(auto k = aStrings.begin(); k != aStrings.end(); ++k) {
1197 if(k->match(name)) {
1198 if(!newStr.get()) {
1199 newStr = unique_ptr<StringSearch::List>(new StringSearch::List(aStrings));
1200 }
1201 newStr->erase(remove(newStr->begin(), newStr->end(), *k), newStr->end());
1202 }
1203 }
1204
1205 if(newStr.get() != 0) {
1206 cur = newStr.get();
1207 }
1208
1209 bool sizeOk = (aSearchType != SearchManager::SIZE_ATLEAST) || (aSize == 0);
1210 if( (cur->empty()) &&
1211 (((aFileType == SearchManager::TYPE_ANY) && sizeOk) || (aFileType == SearchManager::TYPE_DIRECTORY)) ) {
1212 // We satisfied all the search words! Add the directory...(NMDC searches don't support directory size)
1213 SearchResultPtr sr(new SearchResult(SearchResult::TYPE_DIRECTORY, 0, getFullName(), TTHValue()));
1214 aResults.push_back(sr);
1215 ShareManager::getInstance()->setHits(ShareManager::getInstance()->getHits()+1);
1216 }
1217
1218 if(aFileType != SearchManager::TYPE_DIRECTORY) {
1219 for(auto i = files.begin(); i != files.end(); ++i) {
1220
1221 if(aSearchType == SearchManager::SIZE_ATLEAST && aSize > i->getSize()) {
1222 continue;
1223 } else if(aSearchType == SearchManager::SIZE_ATMOST && aSize < i->getSize()) {
1224 continue;
1225 }
1226 auto j = cur->begin();
1227 for(; j != cur->end() && j->match(i->getName()); ++j)
1228 ; // Empty
1229
1230 if(j != cur->end())
1231 continue;
1232
1233 // Check file type...
1234 if(checkType(i->getName(), aFileType)) {
1235 SearchResultPtr sr(new SearchResult(SearchResult::TYPE_FILE, i->getSize(), getFullName() + i->getName(), i->getTTH()));
1236 aResults.push_back(sr);
1237 ShareManager::getInstance()->setHits(ShareManager::getInstance()->getHits()+1);
1238 if(aResults.size() >= maxResults) {
1239 break;
1240 }
1241 }
1242 }
1243 }
1244
1245 for(auto l = directories.begin(); (l != directories.end()) && (aResults.size() < maxResults); ++l) {
1246 l->second->search(aResults, *cur, aSearchType, aSize, aFileType, aClient, maxResults);
1247 }
1248 }
1249
1250 void ShareManager::search(SearchResultList& results, const string& aString, int aSearchType, int64_t aSize, int aFileType, Client* aClient, StringList::size_type maxResults) noexcept {
1251 Lock l(cs);
1252 if(aFileType == SearchManager::TYPE_TTH) {
1253 if(aString.compare(0, 4, "TTH:") == 0) {
1254 TTHValue tth(aString.substr(4));
1255 auto i = tthIndex.find(tth);
1256 if(i != tthIndex.end()) {
1257 SearchResultPtr sr(new SearchResult(SearchResult::TYPE_FILE, i->second->getSize(),
1258 i->second->getParent()->getFullName() + i->second->getName(), i->second->getTTH()));
1259
1260 results.push_back(sr);
1261 ShareManager::getInstance()->addHits(1);
1262 }
1263 }
1264 return;
1265 }
1266 StringTokenizer<string> t(Text::toLower(aString), '$');
1267 StringList& sl = t.getTokens();
1268 if(!bloom.match(sl))
1269 return;
1270
1271 StringSearch::List ssl;
1272 for(auto i = sl.begin(); i != sl.end(); ++i) {
1273 if(!i->empty()) {
1274 ssl.push_back(StringSearch(*i));
1275 }
1276 }
1277 if(ssl.empty())
1278 return;
1279
1280 for(auto j = directories.begin(); (j != directories.end()) && (results.size() < maxResults); ++j) {
1281 (*j)->search(results, ssl, aSearchType, aSize, aFileType, aClient, maxResults);
1282 }
1283 }
1284
1285 namespace {
1286 inline uint16_t toCode(char a, char b) { return (uint16_t)a | ((uint16_t)b)<<8; }
1287 }
1288
1289 ShareManager::AdcSearch::AdcSearch(const StringList& params) : include(&includeX), gt(0),
1290 lt(numeric_limits<int64_t>::max()), hasRoot(false), isDirectory(false)
1291 {
1292 for(auto i = params.begin(); i != params.end(); ++i) {
1293 const string& p = *i;
1294 if(p.length() <= 2)
1295 continue;
1296
1297 uint16_t cmd = toCode(p[0], p[1]);
1298 if(toCode('T', 'R') == cmd) {
1299 hasRoot = true;
1300 root = TTHValue(p.substr(2));
1301 return;
1302 } else if(toCode('A', 'N') == cmd) {
1303 includeX.push_back(StringSearch(p.substr(2)));
1304 } else if(toCode('N', 'O') == cmd) {
1305 exclude.push_back(StringSearch(p.substr(2)));
1306 } else if(toCode('E', 'X') == cmd) {
1307 ext.push_back(p.substr(2));
1308 } else if(toCode('G', 'R') == cmd) {
1309 auto exts = AdcHub::parseSearchExts(Util::toInt(p.substr(2)));
1310 ext.insert(ext.begin(), exts.begin(), exts.end());
1311 } else if(toCode('R', 'X') == cmd) {
1312 noExt.push_back(p.substr(2));
1313 } else if(toCode('G', 'E') == cmd) {
1314 gt = Util::toInt64(p.substr(2));
1315 } else if(toCode('L', 'E') == cmd) {
1316 lt = Util::toInt64(p.substr(2));
1317 } else if(toCode('E', 'Q') == cmd) {
1318 lt = gt = Util::toInt64(p.substr(2));
1319 } else if(toCode('T', 'Y') == cmd) {
1320 isDirectory = (p[2] == '2');
1321 }
1322 }
1323 }
1324
1325 bool ShareManager::AdcSearch::isExcluded(const string& str) {
1326 for(auto i = exclude.begin(); i != exclude.end(); ++i) {
1327 if(i->match(str))
1328 return true;
1329 }
1330 return false;
1331 }
1332
1333 bool ShareManager::AdcSearch::hasExt(const string& name) {
1334 if(ext.empty())
1335 return true;
1336 if(!noExt.empty()) {
1337 ext = StringList(ext.begin(), set_difference(ext.begin(), ext.end(), noExt.begin(), noExt.end(), ext.begin()));
1338 noExt.clear();
1339 }
1340 for(auto i = ext.cbegin(), iend = ext.cend(); i != iend; ++i) {
1341 if(name.length() >= i->length() && Util::stricmp(name.c_str() + name.length() - i->length(), i->c_str()) == 0)
1342 return true;
1343 }
1344 return false;
1345 }
1346
1347 void ShareManager::Directory::search(SearchResultList& aResults, AdcSearch& aStrings, StringList::size_type maxResults) const noexcept {
1348 StringSearch::List* cur = aStrings.include;
1349 StringSearch::List* old = aStrings.include;
1350
1351 unique_ptr<StringSearch::List> newStr;
1352
1353 // Find any matches in the directory name
1354 for(auto k = cur->begin(); k != cur->end(); ++k) {
1355 if(k->match(name) && !aStrings.isExcluded(name)) {
1356 if(!newStr.get()) {
1357 newStr = unique_ptr<StringSearch::List>(new StringSearch::List(*cur));
1358 }
1359 newStr->erase(remove(newStr->begin(), newStr->end(), *k), newStr->end());
1360 }
1361 }
1362
1363 if(newStr.get() != 0) {
1364 cur = newStr.get();
1365 }
1366
1367 bool sizeOk = (aStrings.gt == 0);
1368 if( cur->empty() && aStrings.ext.empty() && sizeOk ) {
1369 // We satisfied all the search words! Add the directory...
1370 SearchResultPtr sr(new SearchResult(SearchResult::TYPE_DIRECTORY, getSize(), getFullName(), TTHValue()));
1371 aResults.push_back(sr);
1372 ShareManager::getInstance()->setHits(ShareManager::getInstance()->getHits()+1);
1373 }
1374
1375 if(!aStrings.isDirectory) {
1376 for(auto i = files.begin(); i != files.end(); ++i) {
1377
1378 if(!(i->getSize() >= aStrings.gt)) {
1379 continue;
1380 } else if(!(i->getSize() <= aStrings.lt)) {
1381 continue;
1382 }
1383
1384 if(aStrings.isExcluded(i->getName()))
1385 continue;
1386
1387 auto j = cur->begin();
1388 for(; j != cur->end() && j->match(i->getName()); ++j)
1389 ; // Empty
1390
1391 if(j != cur->end())
1392 continue;
1393
1394 // Check file type...
1395 if(aStrings.hasExt(i->getName())) {
1396
1397 SearchResultPtr sr(new SearchResult(SearchResult::TYPE_FILE,
1398 i->getSize(), getFullName() + i->getName(), i->getTTH()));
1399 aResults.push_back(sr);
1400 ShareManager::getInstance()->addHits(1);
1401 if(aResults.size() >= maxResults) {
1402 return;
1403 }
1404 }
1405 }
1406 }
1407
1408 for(auto l = directories.begin(); (l != directories.end()) && (aResults.size() < maxResults); ++l) {
1409 l->second->search(aResults, aStrings, maxResults);
1410 }
1411 aStrings.include = old;
1412 }
1413
1414 void ShareManager::search(SearchResultList& results, const StringList& params, StringList::size_type maxResults) noexcept {
1415 AdcSearch srch(params);
1416
1417 Lock l(cs);
1418
1419 if(srch.hasRoot) {
1420 auto i = tthIndex.find(srch.root);
1421 if(i != tthIndex.end()) {
1422 SearchResultPtr sr(new SearchResult(SearchResult::TYPE_FILE,
1423 i->second->getSize(), i->second->getParent()->getFullName() + i->second->getName(),
1424 i->second->getTTH()));
1425 results.push_back(sr);
1426 addHits(1);
1427 }
1428 return;
1429 }
1430
1431 for(auto i = srch.includeX.begin(); i != srch.includeX.end(); ++i) {
1432 if(!bloom.match(i->getPattern()))
1433 return;
1434 }
1435
1436 for(auto j = directories.begin(); (j != directories.end()) && (results.size() < maxResults); ++j) {
1437 (*j)->search(results, srch, maxResults);
1438 }
1439 }
1440
1441 ShareManager::Directory::Ptr ShareManager::getDirectory(const string& fname) {
1442 for(auto mi = shares.begin(); mi != shares.end(); ++mi) {
1443 if(Util::strnicmp(fname, mi->first, mi->first.length()) == 0) {
1444 Directory::Ptr d;
1445 for(auto i = directories.begin(); i != directories.end(); ++i) {
1446 if(Util::stricmp((*i)->getName(), mi->second) == 0) {
1447 d = *i;
1448 }
1449 }
1450
1451 if(!d) {
1452 return Directory::Ptr();
1453 }
1454
1455 string::size_type i;
1456 string::size_type j = mi->first.length();
1457 while( (i = fname.find(PATH_SEPARATOR, j)) != string::npos) {
1458 auto dmi = d->directories.find(fname.substr(j, i-j));
1459 j = i + 1;
1460 if(dmi == d->directories.end())
1461 return Directory::Ptr();
1462 d = dmi->second;
1463 }
1464 return d;
1465 }
1466 }
1467 return Directory::Ptr();
1468 }
1469
1470 void ShareManager::on(QueueManagerListener::FileMoved, const string& n) noexcept {
1471 if(BOOLSETTING(ADD_FINISHED_INSTANTLY)) {
1472 // Check if finished download is supposed to be shared
1473 Lock l(cs);
1474 for(auto i = shares.begin(); i != shares.end(); ++i) {
1475 if(Util::strnicmp(i->first, n, i->first.size()) == 0 && n[i->first.size() - 1] == PATH_SEPARATOR) {
1476 try {
1477 // Schedule for hashing, it'll be added automatically later on...
1478 HashManager::getInstance()->checkTTH(n, File::getSize(n), 0);
1479 } catch(const Exception&) {
1480 // Not a vital feature...
1481 }
1482 break;
1483 }
1484 }
1485 }
1486 }
1487
1488 void ShareManager::on(HashManagerListener::TTHDone, const string& fname, const TTHValue& root) noexcept {
1489 Lock l(cs);
1490 Directory::Ptr d = getDirectory(fname);
1491 if(d) {
1492 auto i = d->findFile(Util::getFileName(fname));
1493 if(i != d->files.end()) {
1494 if(root != i->getTTH())
1495 tthIndex.erase(i->getTTH());
1496 // Get rid of false constness...
1497 auto f = const_cast<Directory::File*>(&(*i));
1498 f->setTTH(root);
1499 tthIndex.insert(make_pair(f->getTTH(), i));
1500 } else {
1501 string name = Util::getFileName(fname);
1502 int64_t size = File::getSize(fname);
1503 auto it = d->files.insert(Directory::File(name, size, d, root)).first;
1504 updateIndices(*d, it);
1505 }
1506 setDirty();
1507 forceXmlRefresh = true;
1508 }
1509 }
1510
1511 void ShareManager::on(TimerManagerListener::Minute, uint64_t tick) noexcept {
1512 if (SETTING(AUTO_REFRESH_TIME) > 0) {
1513 if (lastFullUpdate + SETTING(AUTO_REFRESH_TIME) * 60 * 1000 < tick) {
1514 refresh(true, true);
1515 }
1516 }
1517 }
1518
1519 } // namespace dcpp
1520