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 "DirectoryListing.h"
22
23 #include "QueueManager.h"
24 #include "ClientManager.h"
25
26 #include "StringTokenizer.h"
27 #include "SimpleXML.h"
28 #include "FilteredFile.h"
29 #include "BZUtils.h"
30 #include "CryptoManager.h"
31 #include "ShareManager.h"
32 #include "SimpleXMLReader.h"
33 #include "File.h"
34
35 #ifdef ff
36 #undef ff
37 #endif
38
39 namespace dcpp {
40
DirectoryListing(const HintedUser & aUser)41 DirectoryListing::DirectoryListing(const HintedUser& aUser) :
42 user(aUser),
43 root(new Directory(NULL, Util::emptyString, false, false))
44 {
45 }
46
~DirectoryListing()47 DirectoryListing::~DirectoryListing() {
48 delete root;
49 }
50
getUserFromFilename(const string & fileName)51 UserPtr DirectoryListing::getUserFromFilename(const string& fileName) {
52 // General file list name format: [username].[CID].[xml|xml.bz2]
53
54 string name = Util::getFileName(fileName);
55
56 // Strip off any extensions
57 if(Util::stricmp(name.c_str() + name.length() - 4, ".bz2") == 0) {
58 name.erase(name.length() - 4);
59 }
60
61 if(Util::stricmp(name.c_str() + name.length() - 4, ".xml") == 0) {
62 name.erase(name.length() - 4);
63 }
64
65 // Find CID
66 string::size_type i = name.rfind('.');
67 if(i == string::npos) {
68 return UserPtr();
69 }
70
71 size_t n = name.length() - (i + 1);
72 // CID's always 39 chars long...
73 if(n != 39)
74 return UserPtr();
75
76 CID cid(name.substr(i + 1));
77 if(cid.isZero())
78 return UserPtr();
79
80 return ClientManager::getInstance()->getUser(cid);
81 }
82
loadFile(const string & name)83 void DirectoryListing::loadFile(const string& name) {
84 string txt;
85
86 // For now, we detect type by ending...
87 string ext = Util::getFileExt(name);
88
89 dcpp::File ff(name, dcpp::File::READ, dcpp::File::OPEN);
90 if(Util::stricmp(ext, ".bz2") == 0) {
91 FilteredInputStream<UnBZFilter, false> f(&ff);
92 loadXML(f, false);
93 } else if(Util::stricmp(ext, ".xml") == 0) {
94 loadXML(ff, false);
95 }
96 }
97
98 class ListLoader : public dcpp::SimpleXMLReader::CallBack {
99 public:
ListLoader(DirectoryListing::Directory * root,bool aUpdating)100 ListLoader(DirectoryListing::Directory* root, bool aUpdating) : cur(root),
101 base("/"),
102 inListing(false),
103 updating(aUpdating),
104 m_is_mediainfo_list(false),
105 m_is_first_check_mediainfo_list(false)
106 {
107 }
108
~ListLoader()109 virtual ~ListLoader() { }
110
111 virtual void startTag(const string& name, StringPairList& attribs, bool simple);
112 virtual void endTag(const string& name, const string& data);
113
getBase() const114 const string& getBase() const { return base; }
115 private:
116 DirectoryListing::Directory* cur;
117
118 StringMap params;
119 string base;
120 bool inListing;
121 bool updating;
122 bool m_is_mediainfo_list;
123 bool m_is_first_check_mediainfo_list;
124 };
125
updateXML(const string & xml)126 string DirectoryListing::updateXML(const string& xml) {
127 MemoryInputStream mis(xml);
128 return loadXML(mis, true);
129 }
130
loadXML(InputStream & is,bool updating)131 string DirectoryListing::loadXML(InputStream& is, bool updating) {
132 ListLoader ll(getRoot(), updating);
133
134 dcpp::SimpleXMLReader(&ll).parse(is, SETTING(MAX_FILELIST_SIZE) ? (size_t)SETTING(MAX_FILELIST_SIZE)*1024*1024 : 0);
135
136 return ll.getBase();
137 }
138
139 static const string sFileListing = "FileListing";
140 static const string sBase = "Base";
141 static const string sDirectory = "Directory";
142 static const string sIncomplete = "Incomplete";
143 static const string sFile = "File";
144 static const string sName = "Name";
145 static const string sSize = "Size";
146 static const string sTTH = "TTH";
147 static const string sBR = "BR";
148 static const string sWH = "WH";
149 static const string sMVideo = "MV";
150 static const string sMAudio = "MA";
151 static const string sTS = "TS";
152 static const string sHIT = "HIT";
153
startTag(const string & name,StringPairList & attribs,bool simple)154 void ListLoader::startTag(const string& name, StringPairList& attribs, bool simple) {
155 if(inListing) {
156 if(name == sFile) {
157 const string& n = getAttrib(attribs, sName, 0);
158 if(n.empty())
159 return;
160 const string& s = getAttrib(attribs, sSize, 1);
161 if(s.empty())
162 return;
163 auto size = Util::toInt64(s);
164 const string& h = getAttrib(attribs, sTTH, 2);
165 if(h.empty())
166 return;
167 TTHValue tth(h); /// @todo verify validity?
168
169 if(updating) {
170 // just update the current file if it is already there.
171 for(auto i = cur->files.cbegin(), iend = cur->files.cend(); i != iend; ++i) {
172 auto& file = **i;
173 /// @todo comparisons should be case-insensitive but it takes too long - add a cache
174 if(file.getTTH() == tth || file.getName() == n) {
175 file.setName(n);
176 file.setSize(size);
177 file.setTTH(tth);
178 return;
179 }
180 }
181 }
182
183 DirectoryListing::File* f = new DirectoryListing::File(cur, n, size, tth);
184
185 string l_ts = "";
186
187 if (!m_is_first_check_mediainfo_list){
188 m_is_first_check_mediainfo_list = true;
189 l_ts = getAttrib(attribs, sTS, 3);
190 m_is_mediainfo_list = !l_ts.empty();
191 }
192 else if (m_is_mediainfo_list) {
193 l_ts = getAttrib(attribs, sTS, 3);
194 }
195
196 if (!l_ts.empty()){
197 f->setTS(atol(l_ts.c_str()));
198 f->setHit(atol(getAttrib(attribs, sHIT, 3).c_str()));
199 f->mediaInfo.video_info = getAttrib(attribs, sMVideo, 3);
200 f->mediaInfo.audio_info = getAttrib(attribs, sMAudio, 3);
201 f->mediaInfo.resolution = getAttrib(attribs, sWH, 3);
202 f->mediaInfo.bitrate = atoi(getAttrib(attribs, sBR, 4).c_str());
203 }
204
205 cur->files.push_back(f);
206 } else if(name == sDirectory) {
207 const string& n = getAttrib(attribs, sName, 0);
208 if(n.empty()) {
209 throw SimpleXMLException(_("Directory missing name attribute"));
210 }
211 bool incomp = getAttrib(attribs, sIncomplete, 1) == "1";
212 DirectoryListing::Directory* d = NULL;
213 if(updating) {
214 for(auto i = cur->directories.begin(); i != cur->directories.end(); ++i) {
215 /// @todo comparisons should be case-insensitive but it takes too long - add a cache
216 if((*i)->getName() == n) {
217 d = *i;
218 if(!d->getComplete())
219 d->setComplete(!incomp);
220 break;
221 }
222 }
223 }
224 if(d == NULL) {
225 d = new DirectoryListing::Directory(cur, n, false, !incomp);
226 cur->directories.push_back(d);
227 }
228 cur = d;
229
230 if(simple) {
231 // To handle <Directory Name="..." />
232 endTag(name, Util::emptyString);
233 }
234 }
235 } else if(name == sFileListing) {
236 const string& b = getAttrib(attribs, sBase, 2);
237 if(!b.empty() && b[0] == '/' && b[b.size()-1] == '/') {
238 base = b;
239 }
240 StringList sl = StringTokenizer<string>(base.substr(1), '/').getTokens();
241 for(auto i = sl.begin(); i != sl.end(); ++i) {
242 DirectoryListing::Directory* d = NULL;
243 for(auto j = cur->directories.begin(); j != cur->directories.end(); ++j) {
244 if((*j)->getName() == *i) {
245 d = *j;
246 break;
247 }
248 }
249 if(d == NULL) {
250 d = new DirectoryListing::Directory(cur, *i, false, false);
251 cur->directories.push_back(d);
252 }
253 cur = d;
254 }
255 cur->setComplete(true);
256 inListing = true;
257
258 if(simple) {
259 // To handle <Directory Name="..." />
260 endTag(name, Util::emptyString);
261 }
262 }
263 }
264
endTag(const string & name,const string &)265 void ListLoader::endTag(const string& name, const string&) {
266 if(inListing) {
267 if(name == sDirectory) {
268 cur = cur->getParent();
269 } else if(name == sFileListing) {
270 // cur should be root now...
271 inListing = false;
272 }
273 }
274 }
275
getPath(const Directory * d) const276 string DirectoryListing::getPath(const Directory* d) const {
277 if(d == root)
278 return "";
279
280 string dir;
281 dir.reserve(128);
282 dir.append(d->getName());
283 dir.append(1, '\\');
284
285 Directory* cur = d->getParent();
286 while(cur!=root) {
287 dir.insert(0, cur->getName() + '\\');
288 cur = cur->getParent();
289 }
290 return dir;
291 }
292
getLocalPaths(const File * f) const293 StringList DirectoryListing::getLocalPaths(const File* f) const {
294 try {
295 return ShareManager::getInstance()->getRealPaths(Util::toAdcFile(getPath(f) + f->getName()));
296 } catch(const ShareException&) {
297 return StringList();
298 }
299 }
300
getLocalPaths(const Directory * d) const301 StringList DirectoryListing::getLocalPaths(const Directory* d) const {
302 try {
303 return ShareManager::getInstance()->getRealPaths(Util::toAdcFile(getPath(d)));
304 } catch(const ShareException&) {
305 return StringList();
306 }
307 }
308
download(Directory * aDir,const string & aTarget,bool highPrio)309 void DirectoryListing::download(Directory* aDir, const string& aTarget, bool highPrio) {
310 string tmp;
311 string target = (aDir == getRoot()) ? aTarget : aTarget + aDir->getName() + PATH_SEPARATOR;
312 // First, recurse over the directories
313 Directory::List& lst = aDir->directories;
314 sort(lst.begin(), lst.end(), Directory::DirSort());
315 for(auto j = lst.begin(); j != lst.end(); ++j) {
316 download(*j, target, highPrio);
317 }
318 // Then add the files
319 File::List& l = aDir->files;
320 sort(l.begin(), l.end(), File::FileSort());
321 for(auto i = aDir->files.begin(); i != aDir->files.end(); ++i) {
322 File* file = *i;
323 try {
324 download(file, target + file->getName(), false, highPrio);
325 } catch(const QueueException&) {
326 // Catch it here to allow parts of directories to be added...
327 } catch(const FileException&) {
328 //..
329 }
330 }
331 }
332
download(const string & aDir,const string & aTarget,bool highPrio)333 void DirectoryListing::download(const string& aDir, const string& aTarget, bool highPrio) {
334 dcassert(aDir.size() > 2);
335 dcassert(aDir[aDir.size() - 1] == '\\'); // This should not be PATH_SEPARATOR
336 Directory* d = find(aDir, getRoot());
337 if(d)
338 download(d, aTarget, highPrio);
339 }
340
download(File * aFile,const string & aTarget,bool view,bool highPrio)341 void DirectoryListing::download(File* aFile, const string& aTarget, bool view, bool highPrio) {
342 int flags = (view ? (QueueItem::FLAG_TEXT | QueueItem::FLAG_CLIENT_VIEW) : 0);
343
344 QueueManager::getInstance()->add(aTarget, aFile->getSize(), aFile->getTTH(), getUser(), flags);
345
346 if(highPrio)
347 QueueManager::getInstance()->setPriority(aTarget, QueueItem::HIGHEST);
348 }
349
find(const string & aName,Directory * current)350 DirectoryListing::Directory* DirectoryListing::find(const string& aName, Directory* current) {
351 auto end = aName.find('\\');
352 dcassert(end != string::npos);
353 auto name = aName.substr(0, end);
354
355 auto i = std::find(current->directories.begin(), current->directories.end(), name);
356 if(i != current->directories.end()) {
357 if(end == (aName.size() - 1))
358 return *i;
359 else
360 return find(aName.substr(end + 1), *i);
361 }
362 return NULL;
363 }
364
365 struct HashContained {
HashContaineddcpp::HashContained366 HashContained(const DirectoryListing::Directory::TTHSet& l) : tl(l) { }
367 const DirectoryListing::Directory::TTHSet& tl;
operator ()dcpp::HashContained368 bool operator()(const DirectoryListing::File::Ptr i) const {
369 return tl.count((i->getTTH())) && (DeleteFunction()(i), true);
370 }
371 private:
372 HashContained& operator=(HashContained&);
373 };
374
375 struct DirectoryEmpty {
operator ()dcpp::DirectoryEmpty376 bool operator()(const DirectoryListing::Directory::Ptr i) const {
377 bool r = i->getFileCount() == 0 && i->directories.empty();
378 if (r) DeleteFunction()(i);
379 return r;
380 }
381 };
382
filterList(DirectoryListing & dirList)383 void DirectoryListing::Directory::filterList(DirectoryListing& dirList) {
384 DirectoryListing::Directory* d = dirList.getRoot();
385
386 TTHSet l;
387 d->getHashList(l);
388 filterList(l);
389 }
390
filterList(DirectoryListing::Directory::TTHSet & l)391 void DirectoryListing::Directory::filterList(DirectoryListing::Directory::TTHSet& l) {
392 for(auto i = directories.begin(); i != directories.end(); ++i) (*i)->filterList(l);
393 directories.erase(std::remove_if(directories.begin(),directories.end(),DirectoryEmpty()),directories.end());
394 files.erase(std::remove_if(files.begin(),files.end(),HashContained(l)),files.end());
395 }
396
getHashList(DirectoryListing::Directory::TTHSet & l)397 void DirectoryListing::Directory::getHashList(DirectoryListing::Directory::TTHSet& l) {
398 for(auto i = directories.begin(); i != directories.end(); ++i) (*i)->getHashList(l);
399 for(auto i = files.begin(); i != files.end(); ++i) l.insert((*i)->getTTH());
400 }
401
getTotalSize(bool adl)402 int64_t DirectoryListing::Directory::getTotalSize(bool adl) {
403 int64_t x = getSize();
404 for(auto i = directories.begin(); i != directories.end(); ++i) {
405 if(!(adl && (*i)->getAdls()))
406 x += (*i)->getTotalSize(adls);
407 }
408 return x;
409 }
410
getTotalFileCount(bool adl)411 size_t DirectoryListing::Directory::getTotalFileCount(bool adl) {
412 size_t x = getFileCount();
413 for(auto i = directories.begin(); i != directories.end(); ++i) {
414 if(!(adl && (*i)->getAdls()))
415 x += (*i)->getTotalFileCount(adls);
416 }
417 return x;
418 }
419 } // namespace dcpp
420