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