1 // Aseprite
2 // Copyright (C) 2001-2018 David Capello
3 //
4 // This program is distributed under the terms of
5 // the End-User License Agreement for Aseprite.
6
7 /* Some of the original code to handle PIDLs come from the
8 MiniExplorer example of the Vaca library:
9 https://github.com/dacap/vaca
10 Copyright (C) by David Capello (MIT License)
11 */
12
13 #ifdef HAVE_CONFIG_H
14 #include "config.h"
15 #endif
16
17 #include "app/file_system.h"
18
19 #include "base/fs.h"
20 #include "base/string.h"
21 #include "she/display.h"
22 #include "she/surface.h"
23 #include "she/system.h"
24
25 #include <algorithm>
26 #include <cstdio>
27 #include <map>
28 #include <utility>
29 #include <vector>
30
31 #ifdef _WIN32
32 #include <windows.h>
33
34 #include <shlobj.h>
35 #include <shlwapi.h>
36
37 #define MYPC_CSLID "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}"
38 #else
39 #include <dirent.h>
40 #endif
41
42 //////////////////////////////////////////////////////////////////////
43
44 #ifndef MAX_PATH
45 #define MAX_PATH 4096
46 #endif
47
48 #define NOTINITIALIZED "{__not_initialized_path__}"
49
50 #define FS_TRACE(...)
51
52 namespace app {
53
54 // a position in the file-system
55 class FileItem : public IFileItem {
56 public:
57 std::string m_keyname;
58 std::string m_filename;
59 std::string m_displayname;
60 FileItem* m_parent;
61 FileItemList m_children;
62 unsigned int m_version;
63 bool m_removed;
64 bool m_is_folder;
65 #ifdef _WIN32
66 LPITEMIDLIST m_pidl; // relative to parent
67 LPITEMIDLIST m_fullpidl; // relative to the Desktop folder
68 // (like a full path-name, because the
69 // desktop is the root on Windows)
70 #endif
71
72 FileItem(FileItem* parent);
73 ~FileItem();
74
75 void insertChildSorted(FileItem* child);
76 int compare(const FileItem& that) const;
77
operator <(const FileItem & that) const78 bool operator<(const FileItem& that) const { return compare(that) < 0; }
operator >(const FileItem & that) const79 bool operator>(const FileItem& that) const { return compare(that) > 0; }
operator ==(const FileItem & that) const80 bool operator==(const FileItem& that) const { return compare(that) == 0; }
operator !=(const FileItem & that) const81 bool operator!=(const FileItem& that) const { return compare(that) != 0; }
82
83 // IFileItem interface
84
85 bool isFolder() const;
86 bool isBrowsable() const;
87 bool isHidden() const;
88
89 std::string keyName() const;
90 std::string fileName() const;
91 std::string displayName() const;
92
93 IFileItem* parent() const;
94 const FileItemList& children();
95 void createDirectory(const std::string& dirname);
96
97 bool hasExtension(const base::paths& extensions);
98
99 she::Surface* getThumbnail();
100 void setThumbnail(she::Surface* thumbnail);
101
102 };
103
104 typedef std::map<std::string, FileItem*> FileItemMap;
105 typedef std::map<std::string, she::Surface*> ThumbnailMap;
106
107 // the root of the file-system
108 static FileItem* rootitem = NULL;
109 static FileItemMap* fileitems_map;
110 static ThumbnailMap* thumbnail_map;
111 static unsigned int current_file_system_version = 0;
112
113 #ifdef _WIN32
114 static IMalloc* shl_imalloc = NULL;
115 static IShellFolder* shl_idesktop = NULL;
116 #endif
117
118 // A more easy PIDLs interface (without using the SH* & IL* routines of W2K)
119 #ifdef _WIN32
120 static bool is_sfgaof_folder(SFGAOF attrib);
121 static void update_by_pidl(FileItem* fileitem, SFGAOF attrib);
122 static LPITEMIDLIST concat_pidl(LPITEMIDLIST pidlHead, LPITEMIDLIST pidlTail);
123 static UINT get_pidl_size(LPITEMIDLIST pidl);
124 static LPITEMIDLIST get_next_pidl(LPITEMIDLIST pidl);
125 static LPITEMIDLIST get_last_pidl(LPITEMIDLIST pidl);
126 static LPITEMIDLIST clone_pidl(LPITEMIDLIST pidl);
127 static LPITEMIDLIST remove_last_pidl(LPITEMIDLIST pidl);
128 static void free_pidl(LPITEMIDLIST pidl);
129 static std::string get_key_for_pidl(LPITEMIDLIST pidl);
130
131 static FileItem* get_fileitem_by_fullpidl(LPITEMIDLIST pidl, bool create_if_not);
132 static void put_fileitem(FileItem* fileitem);
133 #else
134 static FileItem* get_fileitem_by_path(const std::string& path, bool create_if_not);
135 static std::string remove_backslash_if_needed(const std::string& filename);
136 static std::string get_key_for_filename(const std::string& filename);
137 static void put_fileitem(FileItem* fileitem);
138 #endif
139
140 FileSystemModule* FileSystemModule::m_instance = NULL;
141
FileSystemModule()142 FileSystemModule::FileSystemModule()
143 {
144 ASSERT(m_instance == NULL);
145 m_instance = this;
146
147 fileitems_map = new FileItemMap;
148 thumbnail_map = new ThumbnailMap;
149
150 #ifdef _WIN32
151 /* get the IMalloc interface */
152 HRESULT hr = SHGetMalloc(&shl_imalloc);
153 if (hr != S_OK)
154 throw std::runtime_error("Error initializing file system. Report this problem. (SHGetMalloc failed.)");
155
156 /* get desktop IShellFolder interface */
157 hr = SHGetDesktopFolder(&shl_idesktop);
158 if (hr != S_OK)
159 throw std::runtime_error("Error initializing file system. Report this problem. (SHGetDesktopFolder failed.)");
160 #endif
161
162 // first version of the file system
163 ++current_file_system_version;
164
165 // get the root element of the file system (this will create
166 // the 'rootitem' FileItem)
167 getRootFileItem();
168 }
169
~FileSystemModule()170 FileSystemModule::~FileSystemModule()
171 {
172 ASSERT(m_instance == this);
173
174 for (FileItemMap::iterator
175 it=fileitems_map->begin(); it!=fileitems_map->end(); ++it) {
176 delete it->second;
177 }
178 fileitems_map->clear();
179
180 for (ThumbnailMap::iterator
181 it=thumbnail_map->begin(); it!=thumbnail_map->end(); ++it) {
182 it->second->dispose();
183 }
184 thumbnail_map->clear();
185
186 #ifdef _WIN32
187 // relase desktop IShellFolder interface
188 shl_idesktop->Release();
189
190 // release IMalloc interface
191 shl_imalloc->Release();
192 shl_imalloc = NULL;
193 #endif
194
195 delete fileitems_map;
196 delete thumbnail_map;
197
198 m_instance = NULL;
199 }
200
instance()201 FileSystemModule* FileSystemModule::instance()
202 {
203 return m_instance;
204 }
205
refresh()206 void FileSystemModule::refresh()
207 {
208 ++current_file_system_version;
209 }
210
getRootFileItem()211 IFileItem* FileSystemModule::getRootFileItem()
212 {
213 FileItem* fileitem;
214
215 if (rootitem)
216 return rootitem;
217
218 fileitem = new FileItem(NULL);
219 rootitem = fileitem;
220
221 //LOG("FS: Creating root fileitem %p\n", rootitem);
222
223 #ifdef _WIN32
224 {
225 // get the desktop PIDL
226 LPITEMIDLIST pidl = NULL;
227
228 if (SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pidl) != S_OK) {
229 // TODO do something better
230 ASSERT(false);
231 exit(1);
232 }
233 fileitem->m_pidl = pidl;
234 fileitem->m_fullpidl = pidl;
235
236 SFGAOF attrib = SFGAO_FOLDER;
237 shl_idesktop->GetAttributesOf(1, (LPCITEMIDLIST *)&pidl, &attrib);
238
239 update_by_pidl(fileitem, attrib);
240 }
241 #else
242 {
243 const char* root = "/";
244
245 fileitem->m_filename = root;
246 fileitem->m_displayname = root;
247 fileitem->m_is_folder = true;
248 }
249 #endif
250
251 // insert the file-item in the hash-table
252 put_fileitem(fileitem);
253 return fileitem;
254 }
255
getFileItemFromPath(const std::string & path)256 IFileItem* FileSystemModule::getFileItemFromPath(const std::string& path)
257 {
258 IFileItem* fileitem = NULL;
259
260 //LOG("FS: get_fileitem_from_path(%s)\n", path.c_str());
261
262 #ifdef _WIN32
263 {
264 ULONG cbEaten = 0UL;
265 LPITEMIDLIST fullpidl = NULL;
266 SFGAOF attrib = SFGAO_FOLDER;
267
268 if (path.empty()) {
269 fileitem = getRootFileItem();
270 //LOG("FS: > %p (root)\n", fileitem);
271 return fileitem;
272 }
273
274 if (shl_idesktop->ParseDisplayName
275 (NULL, NULL,
276 const_cast<LPWSTR>(base::from_utf8(path).c_str()),
277 &cbEaten, &fullpidl, &attrib) != S_OK) {
278 //LOG("FS: > (null)\n");
279 return NULL;
280 }
281
282 fileitem = get_fileitem_by_fullpidl(fullpidl, true);
283 free_pidl(fullpidl);
284 }
285 #else
286 {
287 std::string buf = remove_backslash_if_needed(path);
288 fileitem = get_fileitem_by_path(buf, true);
289 }
290 #endif
291
292 //LOG("FS: get_fileitem_from_path(%s) -> %p\n", path.c_str(), fileitem);
293
294 return fileitem;
295 }
296
297 // ======================================================================
298 // FileItem class (IFileItem implementation)
299 // ======================================================================
300
isFolder() const301 bool FileItem::isFolder() const
302 {
303 return m_is_folder;
304 }
305
isBrowsable() const306 bool FileItem::isBrowsable() const
307 {
308 ASSERT(m_filename != NOTINITIALIZED);
309
310 return m_is_folder;
311 }
312
isHidden() const313 bool FileItem::isHidden() const
314 {
315 ASSERT(m_displayname != NOTINITIALIZED);
316
317 #ifdef _WIN32
318 return false;
319 #else
320 return m_displayname[0] == '.';
321 #endif
322 }
323
keyName() const324 std::string FileItem::keyName() const
325 {
326 ASSERT(m_keyname != NOTINITIALIZED);
327
328 return m_keyname;
329 }
330
fileName() const331 std::string FileItem::fileName() const
332 {
333 ASSERT(m_filename != NOTINITIALIZED);
334
335 return m_filename;
336 }
337
displayName() const338 std::string FileItem::displayName() const
339 {
340 ASSERT(m_displayname != NOTINITIALIZED);
341
342 return m_displayname;
343 }
344
parent() const345 IFileItem* FileItem::parent() const
346 {
347 if (this == rootitem)
348 return NULL;
349 else {
350 ASSERT(m_parent);
351 return m_parent;
352 }
353 }
354
children()355 const FileItemList& FileItem::children()
356 {
357 // Is the file-item a folder?
358 if (isFolder() &&
359 // if the children list is empty, or the file-system version
360 // change (it's like to say: the current m_children list
361 // is outdated)...
362 (m_children.empty() ||
363 current_file_system_version > m_version)) {
364 FileItemList::iterator it;
365 FileItem* child;
366
367 // we have to mark current items as deprecated
368 for (it=m_children.begin();
369 it!=m_children.end(); ++it) {
370 child = static_cast<FileItem*>(*it);
371 child->m_removed = true;
372 }
373
374 //LOG("FS: Loading files for %p (%s)\n", fileitem, fileitem->displayname);
375 #ifdef _WIN32
376 {
377 IShellFolder* pFolder = NULL;
378 HRESULT hr;
379
380 if (this == rootitem)
381 pFolder = shl_idesktop;
382 else {
383 hr = shl_idesktop->BindToObject(m_fullpidl,
384 NULL, IID_IShellFolder, (LPVOID *)&pFolder);
385
386 if (hr != S_OK)
387 pFolder = NULL;
388 }
389
390 if (pFolder != NULL) {
391 IEnumIDList *pEnum = NULL;
392 ULONG c, fetched;
393
394 /* get the interface to enumerate subitems */
395 hr = pFolder->EnumObjects(reinterpret_cast<HWND>(she::instance()->defaultDisplay()->nativeHandle()),
396 SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &pEnum);
397
398 if (hr == S_OK && pEnum != NULL) {
399 LPITEMIDLIST itempidl[256];
400 SFGAOF attribs[256];
401
402 /* enumerate the items in the folder */
403 while (pEnum->Next(256, itempidl, &fetched) == S_OK && fetched > 0) {
404 /* request the SFGAO_FOLDER attribute to know what of the
405 item is a folder */
406 for (c=0; c<fetched; ++c) {
407 attribs[c] = SFGAO_FOLDER;
408 pFolder->GetAttributesOf(1, (LPCITEMIDLIST *)itempidl, attribs+c);
409 }
410
411 /* generate the FileItems */
412 for (c=0; c<fetched; ++c) {
413 LPITEMIDLIST fullpidl = concat_pidl(m_fullpidl,
414 itempidl[c]);
415
416 child = get_fileitem_by_fullpidl(fullpidl, false);
417 if (!child) {
418 child = new FileItem(this);
419
420 child->m_pidl = itempidl[c];
421 child->m_fullpidl = fullpidl;
422
423 update_by_pidl(child, attribs[c]);
424 put_fileitem(child);
425 }
426 else {
427 ASSERT(child->m_parent == this);
428 free_pidl(fullpidl);
429 free_pidl(itempidl[c]);
430 }
431
432 insertChildSorted(child);
433 }
434 }
435
436 pEnum->Release();
437 }
438
439 if (pFolder != shl_idesktop)
440 pFolder->Release();
441 }
442 }
443 #else
444 {
445 DIR* dir = opendir(m_filename.c_str());
446 if (dir) {
447 dirent* entry;
448 while ((entry = readdir(dir)) != NULL) {
449 FileItem* child;
450 std::string fn = entry->d_name;
451 std::string fullfn = base::join_path(m_filename, fn);
452
453 if (fn == "." || fn == "..")
454 continue;
455
456 child = get_fileitem_by_path(fullfn, false);
457 if (!child) {
458 child = new FileItem(this);
459
460 bool is_folder;
461 if (entry->d_type == DT_LNK) {
462 is_folder = base::is_directory(fullfn);
463 }
464 else {
465 is_folder = (entry->d_type == DT_DIR);
466 }
467
468 child->m_filename = fullfn;
469 child->m_displayname = fn;
470 child->m_is_folder = is_folder;
471
472 put_fileitem(child);
473 }
474 else {
475 ASSERT(child->m_parent == this);
476 }
477
478 insertChildSorted(child);
479 }
480 closedir(dir);
481 }
482 }
483 #endif
484
485 // check old file-items (maybe removed directories or file-items)
486 for (it=m_children.begin();
487 it!=m_children.end(); ) {
488 child = static_cast<FileItem*>(*it);
489 ASSERT(child != NULL);
490
491 if (child && child->m_removed) {
492 it = m_children.erase(it);
493
494 fileitems_map->erase(fileitems_map->find(child->m_keyname));
495 delete child;
496 }
497 else
498 ++it;
499 }
500
501 // now this file-item is updated
502 m_version = current_file_system_version;
503 }
504
505 return m_children;
506 }
507
createDirectory(const std::string & dirname)508 void FileItem::createDirectory(const std::string& dirname)
509 {
510 base::make_directory(base::join_path(m_filename, dirname));
511
512 // Invalidate the children list.
513 m_version = 0;
514 }
515
hasExtension(const base::paths & extensions)516 bool FileItem::hasExtension(const base::paths& extensions)
517 {
518 ASSERT(m_filename != NOTINITIALIZED);
519
520 return base::has_file_extension(m_filename, extensions);
521 }
522
getThumbnail()523 she::Surface* FileItem::getThumbnail()
524 {
525 ThumbnailMap::iterator it = thumbnail_map->find(m_filename);
526 if (it != thumbnail_map->end())
527 return it->second;
528 else
529 return NULL;
530 }
531
setThumbnail(she::Surface * thumbnail)532 void FileItem::setThumbnail(she::Surface* thumbnail)
533 {
534 // destroy the current thumbnail of the file (if exists)
535 ThumbnailMap::iterator it = thumbnail_map->find(m_filename);
536 if (it != thumbnail_map->end()) {
537 it->second->dispose();
538 thumbnail_map->erase(it);
539 }
540
541 // insert the new one in the map
542 thumbnail_map->insert(std::make_pair(m_filename, thumbnail));
543 }
544
FileItem(FileItem * parent)545 FileItem::FileItem(FileItem* parent)
546 {
547 FS_TRACE("FS: Creating %p fileitem with parent %p\n", this, parent);
548
549 m_keyname = NOTINITIALIZED;
550 m_filename = NOTINITIALIZED;
551 m_displayname = NOTINITIALIZED;
552 m_parent = parent;
553 m_version = current_file_system_version;
554 m_removed = false;
555 m_is_folder = false;
556 #ifdef _WIN32
557 m_pidl = NULL;
558 m_fullpidl = NULL;
559 #endif
560 }
561
~FileItem()562 FileItem::~FileItem()
563 {
564 FS_TRACE("FS: Destroying FileItem() with parent %p\n", m_parent);
565
566 #ifdef _WIN32
567 if (m_fullpidl && m_fullpidl != m_pidl) {
568 free_pidl(m_fullpidl);
569 m_fullpidl = NULL;
570 }
571
572 if (m_pidl) {
573 free_pidl(m_pidl);
574 m_pidl = NULL;
575 }
576 #endif
577 }
578
insertChildSorted(FileItem * child)579 void FileItem::insertChildSorted(FileItem* child)
580 {
581 // this file-item wasn't removed from the last lookup
582 child->m_removed = false;
583
584 // if the fileitem is already in the list we can go back
585 if (std::find(m_children.begin(), m_children.end(), child) != m_children.end())
586 return;
587
588 for (auto it=m_children.begin(), end=m_children.end(); it!=end; ++it) {
589 if (*child < *static_cast<FileItem*>(*it)) {
590 m_children.insert(it, child);
591 return;
592 }
593 }
594
595 m_children.push_back(child);
596 }
597
compare(const FileItem & that) const598 int FileItem::compare(const FileItem& that) const
599 {
600 if (isFolder()) {
601 if (!that.isFolder())
602 return -1;
603 }
604 else if (that.isFolder())
605 return 1;
606
607 return base::compare_filenames(m_displayname, that.m_displayname);
608 }
609
610 //////////////////////////////////////////////////////////////////////
611 // PIDLS: Only for Win32
612 //////////////////////////////////////////////////////////////////////
613
614 #ifdef _WIN32
615
calc_is_folder(std::string filename,SFGAOF attrib)616 static bool calc_is_folder(std::string filename, SFGAOF attrib)
617 {
618 return ((attrib & SFGAO_FOLDER) == SFGAO_FOLDER)
619 && (base::get_file_extension(filename) != "zip")
620 && ((!filename.empty() && (*filename.begin()) != ':') || (filename == MYPC_CSLID));
621 }
622
623 // Updates the names of the file-item through its PIDL
update_by_pidl(FileItem * fileitem,SFGAOF attrib)624 static void update_by_pidl(FileItem* fileitem, SFGAOF attrib)
625 {
626 STRRET strret;
627 WCHAR pszName[MAX_PATH];
628 IShellFolder* pFolder = NULL;
629 HRESULT hr;
630
631 if (fileitem == rootitem)
632 pFolder = shl_idesktop;
633 else {
634 ASSERT(fileitem->m_parent);
635 hr = shl_idesktop->BindToObject(fileitem->m_parent->m_fullpidl,
636 NULL, IID_IShellFolder, (LPVOID *)&pFolder);
637 if (hr != S_OK)
638 pFolder = NULL;
639 }
640
641 // Get the file name
642
643 if (pFolder != NULL &&
644 pFolder->GetDisplayNameOf(fileitem->m_pidl,
645 SHGDN_NORMAL | SHGDN_FORPARSING,
646 &strret) == S_OK) {
647 StrRetToBuf(&strret, fileitem->m_pidl, pszName, MAX_PATH);
648 fileitem->m_filename = base::to_utf8(pszName);
649 }
650 else if (shl_idesktop->GetDisplayNameOf(fileitem->m_fullpidl,
651 SHGDN_NORMAL | SHGDN_FORPARSING,
652 &strret) == S_OK) {
653 StrRetToBuf(&strret, fileitem->m_fullpidl, pszName, MAX_PATH);
654 fileitem->m_filename = base::to_utf8(pszName);
655 }
656 else
657 fileitem->m_filename = "ERR";
658
659 // Is it a folder?
660
661 fileitem->m_is_folder = calc_is_folder(fileitem->m_filename, attrib);
662
663 // Get the name to display
664
665 if (fileitem->isFolder() &&
666 pFolder &&
667 pFolder->GetDisplayNameOf(fileitem->m_pidl,
668 SHGDN_INFOLDER,
669 &strret) == S_OK) {
670 StrRetToBuf(&strret, fileitem->m_pidl, pszName, MAX_PATH);
671 fileitem->m_displayname = base::to_utf8(pszName);
672 }
673 else if (fileitem->isFolder() &&
674 shl_idesktop->GetDisplayNameOf(fileitem->m_fullpidl,
675 SHGDN_INFOLDER,
676 &strret) == S_OK) {
677 StrRetToBuf(&strret, fileitem->m_fullpidl, pszName, MAX_PATH);
678 fileitem->m_displayname = base::to_utf8(pszName);
679 }
680 else {
681 fileitem->m_displayname = base::get_file_name(fileitem->m_filename);
682 }
683
684 if (pFolder != NULL && pFolder != shl_idesktop) {
685 pFolder->Release();
686 }
687 }
688
concat_pidl(LPITEMIDLIST pidlHead,LPITEMIDLIST pidlTail)689 static LPITEMIDLIST concat_pidl(LPITEMIDLIST pidlHead, LPITEMIDLIST pidlTail)
690 {
691 LPITEMIDLIST pidlNew;
692 UINT cb1, cb2;
693
694 ASSERT(pidlHead);
695 ASSERT(pidlTail);
696
697 cb1 = get_pidl_size(pidlHead) - sizeof(pidlHead->mkid.cb);
698 cb2 = get_pidl_size(pidlTail);
699
700 pidlNew = (LPITEMIDLIST)shl_imalloc->Alloc(cb1 + cb2);
701 if (pidlNew) {
702 CopyMemory(pidlNew, pidlHead, cb1);
703 CopyMemory(((LPSTR)pidlNew) + cb1, pidlTail, cb2);
704 }
705
706 return pidlNew;
707 }
708
get_pidl_size(LPITEMIDLIST pidl)709 static UINT get_pidl_size(LPITEMIDLIST pidl)
710 {
711 UINT cbTotal = 0;
712
713 if (pidl) {
714 cbTotal += sizeof(pidl->mkid.cb); /* null terminator */
715
716 while (pidl) {
717 cbTotal += pidl->mkid.cb;
718 pidl = get_next_pidl(pidl);
719 }
720 }
721
722 return cbTotal;
723 }
724
get_next_pidl(LPITEMIDLIST pidl)725 static LPITEMIDLIST get_next_pidl(LPITEMIDLIST pidl)
726 {
727 if (pidl != NULL && pidl->mkid.cb > 0) {
728 pidl = (LPITEMIDLIST)(((LPBYTE)(pidl)) + pidl->mkid.cb);
729 if (pidl->mkid.cb > 0)
730 return pidl;
731 }
732
733 return NULL;
734 }
735
get_last_pidl(LPITEMIDLIST pidl)736 static LPITEMIDLIST get_last_pidl(LPITEMIDLIST pidl)
737 {
738 LPITEMIDLIST pidlLast = pidl;
739 LPITEMIDLIST pidlNew = NULL;
740
741 while (pidl) {
742 pidlLast = pidl;
743 pidl = get_next_pidl(pidl);
744 }
745
746 if (pidlLast) {
747 ULONG sz = get_pidl_size(pidlLast);
748 pidlNew = (LPITEMIDLIST)shl_imalloc->Alloc(sz);
749 CopyMemory(pidlNew, pidlLast, sz);
750 }
751
752 return pidlNew;
753 }
754
clone_pidl(LPITEMIDLIST pidl)755 static LPITEMIDLIST clone_pidl(LPITEMIDLIST pidl)
756 {
757 ULONG sz = get_pidl_size(pidl);
758 LPITEMIDLIST pidlNew = (LPITEMIDLIST)shl_imalloc->Alloc(sz);
759
760 CopyMemory(pidlNew, pidl, sz);
761
762 return pidlNew;
763 }
764
remove_last_pidl(LPITEMIDLIST pidl)765 static LPITEMIDLIST remove_last_pidl(LPITEMIDLIST pidl)
766 {
767 LPITEMIDLIST pidlFirst = pidl;
768 LPITEMIDLIST pidlLast = pidl;
769
770 while (pidl) {
771 pidlLast = pidl;
772 pidl = get_next_pidl(pidl);
773 }
774
775 if (pidlLast)
776 pidlLast->mkid.cb = 0;
777
778 return pidlFirst;
779 }
780
free_pidl(LPITEMIDLIST pidl)781 static void free_pidl(LPITEMIDLIST pidl)
782 {
783 shl_imalloc->Free(pidl);
784 }
785
get_key_for_pidl(LPITEMIDLIST pidl)786 static std::string get_key_for_pidl(LPITEMIDLIST pidl)
787 {
788 #if 0
789 char *key = base_malloc(get_pidl_size(pidl)+1);
790 UINT c, i = 0;
791
792 while (pidl) {
793 for (c=0; c<pidl->mkid.cb; ++c) {
794 if (pidl->mkid.abID[c])
795 key[i++] = pidl->mkid.abID[c];
796 else
797 key[i++] = 1;
798 }
799 pidl = get_next_pidl(pidl);
800 }
801 key[i] = 0;
802
803 return key;
804 #else
805 STRRET strret;
806 WCHAR pszName[MAX_PATH];
807 WCHAR key[4096] = { 0 };
808 int len;
809
810 // Go pidl by pidl from the fullpidl to the root (desktop)
811 //LOG("FS: ***\n");
812 pidl = clone_pidl(pidl);
813 while (pidl->mkid.cb > 0) {
814 if (shl_idesktop->GetDisplayNameOf(pidl,
815 SHGDN_INFOLDER | SHGDN_FORPARSING,
816 &strret) == S_OK) {
817 if (StrRetToBuf(&strret, pidl, pszName, MAX_PATH) != S_OK)
818 pszName[0] = 0;
819
820 //LOG("FS: + %s\n", pszName);
821
822 len = wcslen(pszName);
823 if (len > 0) {
824 if (*key) {
825 if (pszName[len-1] != L'\\') {
826 memmove(key+len+1, key, sizeof(WCHAR)*(wcslen(key)+1));
827 key[len] = L'\\';
828 }
829 else
830 memmove(key+len, key, sizeof(WCHAR)*(wcslen(key)+1));
831 }
832 else
833 key[len] = 0;
834
835 memcpy(key, pszName, sizeof(WCHAR)*len);
836 }
837 }
838 remove_last_pidl(pidl);
839 }
840 free_pidl(pidl);
841
842 //LOG("FS: =%s\n***\n", key);
843 return base::to_utf8(key);
844 #endif
845 }
846
get_fileitem_by_fullpidl(LPITEMIDLIST fullpidl,bool create_if_not)847 static FileItem* get_fileitem_by_fullpidl(LPITEMIDLIST fullpidl, bool create_if_not)
848 {
849 FileItemMap::iterator it = fileitems_map->find(get_key_for_pidl(fullpidl));
850 if (it != fileitems_map->end())
851 return it->second;
852
853 if (!create_if_not)
854 return NULL;
855
856 // new file-item
857 FileItem* fileitem = new FileItem(NULL);
858 fileitem->m_fullpidl = clone_pidl(fullpidl);
859
860 SFGAOF attrib = SFGAO_FOLDER;
861 HRESULT hr = shl_idesktop->GetAttributesOf(1, (LPCITEMIDLIST *)&fileitem->m_fullpidl, &attrib);
862 if (hr == S_OK) {
863 LPITEMIDLIST parent_fullpidl = clone_pidl(fileitem->m_fullpidl);
864 remove_last_pidl(parent_fullpidl);
865
866 fileitem->m_pidl = get_last_pidl(fileitem->m_fullpidl);
867 fileitem->m_parent = get_fileitem_by_fullpidl(parent_fullpidl, true);
868
869 free_pidl(parent_fullpidl);
870 }
871
872 update_by_pidl(fileitem, attrib);
873 put_fileitem(fileitem);
874
875 //LOG("FS: fileitem %p created %s with parent %p\n", fileitem, fileitem->keyname.c_str(), fileitem->parent);
876
877 return fileitem;
878 }
879
880 /**
881 * Inserts the @a fileitem in the hash map of items.
882 */
put_fileitem(FileItem * fileitem)883 static void put_fileitem(FileItem* fileitem)
884 {
885 ASSERT(fileitem->m_filename != NOTINITIALIZED);
886 ASSERT(fileitem->m_keyname == NOTINITIALIZED);
887
888 fileitem->m_keyname = get_key_for_pidl(fileitem->m_fullpidl);
889
890 ASSERT(fileitem->m_keyname != NOTINITIALIZED);
891
892 #ifdef _DEBUG
893 auto it = fileitems_map->find(get_key_for_pidl(fileitem->m_fullpidl));
894 ASSERT(it == fileitems_map->end());
895 #endif
896
897 // insert this file-item in the hash-table
898 fileitems_map->insert(std::make_pair(fileitem->m_keyname, fileitem));
899 }
900
901 #else
902
903 //////////////////////////////////////////////////////////////////////
904 // POSIX functions
905 //////////////////////////////////////////////////////////////////////
906
get_fileitem_by_path(const std::string & path,bool create_if_not)907 static FileItem* get_fileitem_by_path(const std::string& path, bool create_if_not)
908 {
909 if (path.empty())
910 return rootitem;
911
912 FileItemMap::iterator it = fileitems_map->find(get_key_for_filename(path));
913 if (it != fileitems_map->end())
914 return it->second;
915
916 if (!create_if_not)
917 return NULL;
918
919 // get the attributes of the file
920 bool is_folder = false;
921 if (!base::is_file(path)) {
922 if (!base::is_directory(path))
923 return NULL;
924
925 is_folder = true;
926 }
927
928 // new file-item
929 FileItem* fileitem = new FileItem(NULL);
930
931 fileitem->m_filename = path;
932 fileitem->m_displayname = base::get_file_name(path);
933 fileitem->m_is_folder = is_folder;
934
935 // get the parent
936 {
937 std::string parent_path = remove_backslash_if_needed(base::join_path(base::get_file_path(path), ""));
938 fileitem->m_parent = get_fileitem_by_path(parent_path, true);
939 }
940
941 put_fileitem(fileitem);
942
943 return fileitem;
944 }
945
remove_backslash_if_needed(const std::string & filename)946 static std::string remove_backslash_if_needed(const std::string& filename)
947 {
948 if (!filename.empty() && base::is_path_separator(*(filename.end()-1))) {
949 int len = filename.size();
950
951 // This is just the root '/' slash
952 if (len == 1)
953 return filename;
954 else
955 return base::remove_path_separator(filename);
956 }
957 return filename;
958 }
959
get_key_for_filename(const std::string & filename)960 static std::string get_key_for_filename(const std::string& filename)
961 {
962 std::string buf(filename);
963 buf = base::fix_path_separators(buf);
964 return buf;
965 }
966
put_fileitem(FileItem * fileitem)967 static void put_fileitem(FileItem* fileitem)
968 {
969 ASSERT(fileitem->m_filename != NOTINITIALIZED);
970 ASSERT(fileitem->m_keyname == NOTINITIALIZED);
971
972 fileitem->m_keyname = get_key_for_filename(fileitem->m_filename);
973
974 ASSERT(fileitem->m_keyname != NOTINITIALIZED);
975
976 // insert this file-item in the hash-table
977 fileitems_map->insert(std::make_pair(fileitem->m_keyname, fileitem));
978 }
979
980 #endif
981
982 } // namespace app
983