Assorted whitespace cleanup and typo fixes.
[haiku.git] / src / kits / tracker / VirtualDirectoryManager.cpp
blobf2f1d414fbb0f25eaa930b1d2d0add819abb925b
1 /*
2 * Copyright 2013 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * Ingo Weinhold, ingo_weinhold@gmx.de
7 */
10 #include "VirtualDirectoryManager.h"
12 #include <errno.h>
14 #include <new>
16 #include <Directory.h>
17 #include <File.h>
18 #include <StringList.h>
20 #include <AutoDeleter.h>
21 #include <AutoLocker.h>
22 #include <DriverSettings.h>
23 #include <NotOwningEntryRef.h>
24 #include <Uuid.h>
26 #include "MimeTypes.h"
27 #include "Model.h"
30 namespace BPrivate {
32 static const size_t kMaxVirtualDirectoryFileSize = 10 * 1024;
33 static const char* const kTemporaryDefinitionFileBaseDirectoryPath
34 = "/tmp/tracker/virtual-directories";
37 // #pragma mark - VirtualDirectoryManager::Info
40 class VirtualDirectoryManager::Info {
41 private:
42 typedef BObjectList<Info> InfoList;
44 public:
45 Info(RootInfo* root, Info* parent, const BString& path,
46 const node_ref& definitionFileNodeRef,
47 const entry_ref& definitionFileEntryRef)
49 fRoot(root),
50 fParent(parent),
51 fPath(path),
52 fDefinitionFileNodeRef(definitionFileNodeRef),
53 fDefinitionFileEntryRef(definitionFileEntryRef),
54 fId(),
55 fChildDefinitionsDirectoryRef(-1, -1),
56 fChildren(10, true)
60 ~Info()
64 RootInfo* Root() const
66 return fRoot;
69 Info* Parent() const
71 return fParent;
74 const char* Name() const
76 return fDefinitionFileEntryRef.name;
79 const BString& Path() const
81 return fPath;
84 const node_ref& DefinitionFileNodeRef() const
86 return fDefinitionFileNodeRef;
89 const entry_ref& DefinitionFileEntryRef() const
91 return fDefinitionFileEntryRef;
94 const InfoList& Children() const
96 return fChildren;
99 const BString& Id() const
101 return fId;
104 void SetId(const BString& id)
106 fId = id;
109 const node_ref& ChildDefinitionsDirectoryRef() const
111 return fChildDefinitionsDirectoryRef;
114 void SetChildDefinitionsDirectoryRef(const node_ref& ref)
116 fChildDefinitionsDirectoryRef = ref;
119 Info* GetChild(const char* name) const
121 for (int32 i = 0; Info* child = fChildren.ItemAt(i); i++) {
122 if (strcmp(name, child->Name()) == 0)
123 return child;
125 return NULL;
128 Info* CreateChild(const node_ref& definitionFileNodeRef,
129 const entry_ref& definitionFileEntryRef)
131 BString path;
132 if (fPath.IsEmpty()) {
133 path = definitionFileEntryRef.name;
134 } else {
135 path.SetToFormat("%s/%s", fPath.String(),
136 definitionFileEntryRef.name);
138 if (path.IsEmpty())
139 return NULL;
141 Info* info = new(std::nothrow) Info(fRoot, this, path,
142 definitionFileNodeRef, definitionFileEntryRef);
143 if (info == NULL || !fChildren.AddItem(info)) {
144 delete info;
145 return NULL;
147 return info;
150 bool DeleteChild(Info* info)
152 return fChildren.RemoveItem(info, true);
155 void DeleteChildAt(int32 index)
157 delete fChildren.RemoveItemAt(index);
160 private:
161 RootInfo* fRoot;
162 Info* fParent;
163 BString fPath;
164 node_ref fDefinitionFileNodeRef;
165 entry_ref fDefinitionFileEntryRef;
166 BString fId;
167 node_ref fChildDefinitionsDirectoryRef;
168 InfoList fChildren;
172 // #pragma mark - VirtualDirectoryManager::RootInfo
175 class VirtualDirectoryManager::RootInfo {
176 public:
177 RootInfo(const node_ref& definitionFileNodeRef,
178 const entry_ref& definitionFileEntryRef)
180 fDirectoryPaths(),
181 fInfo(new(std::nothrow) VirtualDirectoryManager::Info(this, NULL,
182 BString(), definitionFileNodeRef, definitionFileEntryRef)),
183 fFileTime(-1),
184 fLastChangeTime(-1)
188 ~RootInfo()
190 delete fInfo;
193 status_t InitCheck() const
195 return fInfo != NULL ? B_OK : B_NO_MEMORY;
198 bigtime_t FileTime() const
200 return fFileTime;
203 bigtime_t LastChangeTime() const
205 return fLastChangeTime;
208 const BStringList& DirectoryPaths() const
210 return fDirectoryPaths;
213 status_t ReadDefinition(bool* _changed = NULL)
215 // open the definition file
216 BFile file;
217 status_t error = file.SetTo(&fInfo->DefinitionFileEntryRef(),
218 B_READ_ONLY);
219 if (error != B_OK)
220 return error;
222 struct stat st;
223 error = file.GetStat(&st);
224 if (error != B_OK)
225 return error;
227 bigtime_t fileTime = st.st_mtim.tv_sec;
228 fileTime *= 1000000;
229 fileTime += st.st_mtim.tv_nsec / 1000;
230 if (fileTime == fFileTime) {
231 if (_changed != NULL)
232 *_changed = false;
234 return B_OK;
237 if (node_ref(st.st_dev, st.st_ino) != fInfo->DefinitionFileNodeRef())
238 return B_ENTRY_NOT_FOUND;
240 // read the contents
241 off_t fileSize = st.st_size;
242 if (fileSize > (off_t)kMaxVirtualDirectoryFileSize)
243 return B_BAD_VALUE;
245 char* buffer = new(std::nothrow) char[fileSize + 1];
246 if (buffer == NULL)
247 return B_NO_MEMORY;
248 ArrayDeleter<char> bufferDeleter(buffer);
250 ssize_t bytesRead = file.ReadAt(0, buffer, fileSize);
251 if (bytesRead < 0)
252 return bytesRead;
254 buffer[bytesRead] = '\0';
256 // parse it
257 BStringList oldDirectoryPaths(fDirectoryPaths);
258 fDirectoryPaths.MakeEmpty();
260 BDriverSettings driverSettings;
261 error = driverSettings.SetToString(buffer);
262 if (error != B_OK)
263 return error;
265 BDriverParameterIterator it
266 = driverSettings.ParameterIterator("directory");
267 while (it.HasNext()) {
268 BDriverParameter parameter = it.Next();
269 for (int32 i = 0; i < parameter.CountValues(); i++)
270 fDirectoryPaths.Add(parameter.ValueAt(i));
273 // update file time and check whether something has changed
274 fFileTime = fileTime;
276 bool changed = fDirectoryPaths != oldDirectoryPaths;
277 if (changed || fLastChangeTime < 0)
278 fLastChangeTime = fFileTime;
280 if (_changed != NULL)
281 *_changed = changed;
283 return B_OK;
286 VirtualDirectoryManager::Info* Info() const
288 return fInfo;
291 private:
292 typedef std::map<BString, VirtualDirectoryManager::Info*> InfoMap;
294 private:
295 BStringList fDirectoryPaths;
296 VirtualDirectoryManager::Info* fInfo;
297 bigtime_t fFileTime;
298 // actual file modified time
299 bigtime_t fLastChangeTime;
300 // last time something actually changed
304 // #pragma mark - VirtualDirectoryManager
307 VirtualDirectoryManager::VirtualDirectoryManager()
309 fLock("virtual directory manager")
314 /*static*/ VirtualDirectoryManager*
315 VirtualDirectoryManager::Instance()
317 static VirtualDirectoryManager* manager
318 = new(std::nothrow) VirtualDirectoryManager;
319 return manager;
323 status_t
324 VirtualDirectoryManager::ResolveDirectoryPaths(
325 const node_ref& definitionFileNodeRef,
326 const entry_ref& definitionFileEntryRef, BStringList& _directoryPaths,
327 node_ref* _definitionFileNodeRef, entry_ref* _definitionFileEntryRef)
329 Info* info = _InfoForNodeRef(definitionFileNodeRef);
330 if (info == NULL) {
331 status_t error = _ResolveUnknownDefinitionFile(definitionFileNodeRef,
332 definitionFileEntryRef, info);
333 if (error != B_OK)
334 return error;
337 const BString& subDirectory = info->Path();
338 const BStringList& rootDirectoryPaths = info->Root()->DirectoryPaths();
339 if (subDirectory.IsEmpty()) {
340 _directoryPaths = rootDirectoryPaths;
341 } else {
342 _directoryPaths.MakeEmpty();
343 int32 count = rootDirectoryPaths.CountStrings();
344 for (int32 i = 0; i < count; i++) {
345 BString path = rootDirectoryPaths.StringAt(i);
346 _directoryPaths.Add(path << '/' << subDirectory);
350 if (_definitionFileEntryRef != NULL) {
351 *_definitionFileEntryRef = info->DefinitionFileEntryRef();
352 if (_definitionFileEntryRef->name == NULL)
353 return B_NO_MEMORY;
356 if (_definitionFileNodeRef != NULL)
357 *_definitionFileNodeRef = info->DefinitionFileNodeRef();
359 return B_OK;
363 bool
364 VirtualDirectoryManager::GetDefinitionFileChangeTime(
365 const node_ref& definitionFileRef, bigtime_t& _time) const
367 Info* info = _InfoForNodeRef(definitionFileRef);
368 if (info == NULL)
369 return false;
371 _time = info->Root()->LastChangeTime();
372 return true;
376 bool
377 VirtualDirectoryManager::GetRootDefinitionFile(
378 const node_ref& definitionFileRef, node_ref& _rootDefinitionFileRef)
380 Info* info = _InfoForNodeRef(definitionFileRef);
381 if (info == NULL)
382 return false;
384 _rootDefinitionFileRef = info->Root()->Info()->DefinitionFileNodeRef();
385 return true;
389 bool
390 VirtualDirectoryManager::GetSubDirectoryDefinitionFile(
391 const node_ref& baseDefinitionRef, const char* subDirName,
392 entry_ref& _entryRef, node_ref& _nodeRef)
394 Info* parentInfo = _InfoForNodeRef(baseDefinitionRef);
395 if (parentInfo == NULL)
396 return false;
398 Info* info = parentInfo->GetChild(subDirName);
399 if (info == NULL)
400 return false;
402 _entryRef = info->DefinitionFileEntryRef();
403 _nodeRef = info->DefinitionFileNodeRef();
404 return _entryRef.name != NULL;
408 bool
409 VirtualDirectoryManager::GetParentDirectoryDefinitionFile(
410 const node_ref& subDirDefinitionRef, entry_ref& _entryRef,
411 node_ref& _nodeRef)
413 Info* info = _InfoForNodeRef(subDirDefinitionRef);
414 if (info == NULL)
415 return false;
417 Info* parentInfo = info->Parent();
418 if (parentInfo == NULL)
419 return false;
421 _entryRef = parentInfo->DefinitionFileEntryRef();
422 _nodeRef = parentInfo->DefinitionFileNodeRef();
423 return _entryRef.name != NULL;
427 status_t
428 VirtualDirectoryManager::TranslateDirectoryEntry(
429 const node_ref& definitionFileRef, dirent* buffer)
431 NotOwningEntryRef entryRef(buffer->d_pdev, buffer->d_pino, buffer->d_name);
432 node_ref nodeRef(buffer->d_dev, buffer->d_ino);
434 status_t result = TranslateDirectoryEntry(definitionFileRef, entryRef,
435 nodeRef);
436 if (result != B_OK)
437 return result;
439 buffer->d_pdev = entryRef.device;
440 buffer->d_pino = entryRef.directory;
441 buffer->d_dev = nodeRef.device;
442 buffer->d_ino = nodeRef.node;
444 return B_OK;
448 status_t
449 VirtualDirectoryManager::TranslateDirectoryEntry(
450 const node_ref& definitionFileRef, entry_ref& _entryRef, node_ref& _nodeRef)
452 Info* parentInfo = _InfoForNodeRef(definitionFileRef);
453 if (parentInfo == NULL)
454 return B_BAD_VALUE;
456 // get the info for the entry
457 Info* info = parentInfo->GetChild(_entryRef.name);
458 if (info == NULL) {
459 // If not done yet, create a directory for the parent, where we can
460 // place the new definition file.
461 if (parentInfo->Id().IsEmpty()) {
462 BString id = BUuid().SetToRandom().ToString();
463 if (id.IsEmpty())
464 return B_NO_MEMORY;
466 BPath path(kTemporaryDefinitionFileBaseDirectoryPath, id);
467 status_t error = path.InitCheck();
468 if (error != B_OK)
469 return error;
471 error = create_directory(path.Path(),
472 S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
473 if (error != B_OK)
474 return error;
476 struct stat st;
477 if (lstat(path.Path(), &st) != 0)
478 return errno;
480 parentInfo->SetId(id);
481 parentInfo->SetChildDefinitionsDirectoryRef(
482 node_ref(st.st_dev, st.st_ino));
485 // create the definition file
486 const node_ref& directoryRef
487 = parentInfo->ChildDefinitionsDirectoryRef();
488 NotOwningEntryRef entryRef(directoryRef, _entryRef.name);
490 BFile definitionFile;
491 status_t error = definitionFile.SetTo(&entryRef,
492 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
493 if (error != B_OK)
494 return error;
496 node_ref nodeRef;
497 error = definitionFile.GetNodeRef(&nodeRef);
498 if (error != B_OK)
499 return error;
501 BNodeInfo nodeInfo(&definitionFile);
502 error = nodeInfo.SetType(kVirtualDirectoryMimeType);
503 if (error != B_OK)
504 return error;
506 // create the info
507 info = parentInfo->CreateChild(nodeRef, entryRef);
508 if (info == NULL || !_AddInfo(info))
509 return B_NO_MEMORY;
511 // Write some info into the definition file that helps us to find the
512 // root definition file. This is only necessary when definition file
513 // entry refs are transferred between applications. Then the receiving
514 // application may need to find the root definition file and resolve
515 // the subdirectories.
516 const entry_ref& rootEntryRef
517 = parentInfo->Root()->Info()->DefinitionFileEntryRef();
518 BString definitionFileContent;
519 definitionFileContent.SetToFormat(
520 "root {\n"
521 " device %" B_PRIdDEV "\n"
522 " directory %" B_PRIdINO "\n"
523 " name \"%s\"\n"
524 "}\n"
525 "subdir \"%s\"\n",
526 rootEntryRef.device, rootEntryRef.directory, rootEntryRef.name,
527 info->Path().String());
528 // failure is not nice, but not mission critical for this application
529 if (!definitionFileContent.IsEmpty()) {
530 definitionFile.WriteAt(0,
531 definitionFileContent.String(), definitionFileContent.Length());
535 const entry_ref& entryRef = info->DefinitionFileEntryRef();
536 _nodeRef = info->DefinitionFileNodeRef();
537 _entryRef.device = entryRef.device;
538 _entryRef.directory = entryRef.directory;
540 return B_OK;
544 bool
545 VirtualDirectoryManager::DefinitionFileChanged(
546 const node_ref& definitionFileRef)
548 Info* info = _InfoForNodeRef(definitionFileRef);
549 if (info == NULL)
550 return false;
552 _UpdateTree(info->Root());
554 return _InfoForNodeRef(definitionFileRef) != NULL;
558 status_t
559 VirtualDirectoryManager::DirectoryRemoved(const node_ref& definitionFileRef)
561 Info* info = _InfoForNodeRef(definitionFileRef);
562 if (info == NULL)
563 return B_ENTRY_NOT_FOUND;
565 _RemoveDirectory(info);
567 // delete the info
568 if (info->Parent() == NULL)
569 delete info->Root();
570 else
571 info->Parent()->DeleteChild(info);
573 return B_OK;
577 /*static*/ bool
578 VirtualDirectoryManager::GetEntry(const BStringList& directoryPaths,
579 const char* name, entry_ref* _ref, struct stat* _st)
581 int32 count = directoryPaths.CountStrings();
582 for (int32 i = 0; i < count; i++) {
583 BPath path;
584 if (path.SetTo(directoryPaths.StringAt(i), name) != B_OK)
585 continue;
587 struct stat st;
588 if (lstat(path.Path(), &st) == 0) {
589 if (_ref != NULL) {
590 if (get_ref_for_path(path.Path(), _ref) != B_OK)
591 return false;
593 if (_st != NULL)
594 *_st = st;
595 return true;
599 return false;
603 VirtualDirectoryManager::Info*
604 VirtualDirectoryManager::_InfoForNodeRef(const node_ref& nodeRef) const
606 NodeRefInfoMap::const_iterator it = fInfos.find(nodeRef);
607 return it != fInfos.end() ? it->second : NULL;
611 bool
612 VirtualDirectoryManager::_AddInfo(Info* info)
614 try {
615 fInfos[info->DefinitionFileNodeRef()] = info;
616 return true;
617 } catch (...) {
618 return false;
623 void
624 VirtualDirectoryManager::_RemoveInfo(Info* info)
626 NodeRefInfoMap::iterator it = fInfos.find(info->DefinitionFileNodeRef());
627 if (it != fInfos.end())
628 fInfos.erase(it);
632 void
633 VirtualDirectoryManager::_UpdateTree(RootInfo* root)
635 bool changed = false;
636 status_t result = root->ReadDefinition(&changed);
637 if (result != B_OK) {
638 DirectoryRemoved(root->Info()->DefinitionFileNodeRef());
639 return;
642 if (!changed)
643 return;
645 _UpdateTree(root->Info());
649 void
650 VirtualDirectoryManager::_UpdateTree(Info* info)
652 const BStringList& directoryPaths = info->Root()->DirectoryPaths();
654 int32 childCount = info->Children().CountItems();
655 for (int32 i = childCount -1; i >= 0; i--) {
656 Info* childInfo = info->Children().ItemAt(i);
657 struct stat st;
658 if (GetEntry(directoryPaths, childInfo->Path(), NULL, &st)
659 && S_ISDIR(st.st_mode)) {
660 _UpdateTree(childInfo);
661 } else {
662 _RemoveDirectory(childInfo);
663 info->DeleteChildAt(i);
669 void
670 VirtualDirectoryManager::_RemoveDirectory(Info* info)
672 // recursively remove the subdirectories
673 for (int32 i = 0; Info* child = info->Children().ItemAt(i); i++)
674 _RemoveDirectory(child);
676 // remove the directory for the child definition file
677 if (!info->Id().IsEmpty()) {
678 BPath path(kTemporaryDefinitionFileBaseDirectoryPath, info->Id());
679 if (path.InitCheck() == B_OK)
680 rmdir(path.Path());
683 // unless this is the root directory, remove the definition file
684 if (info != info->Root()->Info())
685 BEntry(&info->DefinitionFileEntryRef()).Remove();
687 _RemoveInfo(info);
691 status_t
692 VirtualDirectoryManager::_ResolveUnknownDefinitionFile(
693 const node_ref& definitionFileNodeRef,
694 const entry_ref& definitionFileEntryRef, Info*& _info)
696 // This is either a root definition file or a subdir definition file
697 // created by another application. We'll just try to read the info from the
698 // file that a subdir definition file would contain. If that fails, we
699 // assume a root definition file.
700 entry_ref entryRef;
701 BString subDirPath;
702 if (_ReadSubDirectoryDefinitionFileInfo(definitionFileEntryRef, entryRef,
703 subDirPath) != B_OK) {
704 return _CreateRootInfo(definitionFileNodeRef, definitionFileEntryRef,
705 _info);
708 if (subDirPath.IsEmpty())
709 return B_BAD_VALUE;
711 // get the root definition file node ref
712 node_ref nodeRef;
713 status_t error = BEntry(&entryRef).GetNodeRef(&nodeRef);
714 if (error != B_OK)
715 return error;
717 // resolve/create the root info
718 Info* info = _InfoForNodeRef(nodeRef);
719 if (info == NULL) {
720 error = _CreateRootInfo(nodeRef, entryRef, info);
721 if (error != B_OK)
722 return error;
723 } else if (info->Root()->Info() != info)
724 return B_BAD_VALUE;
726 const BStringList& rootDirectoryPaths = info->Root()->DirectoryPaths();
728 // now we can traverse the subdir path and resolve all infos along the way
729 int32 nextComponentOffset = 0;
730 while (nextComponentOffset < subDirPath.Length()) {
731 int32 componentEnd = subDirPath.FindFirst('/', nextComponentOffset);
732 if (componentEnd >= 0) {
733 // skip duplicate '/'s
734 if (componentEnd == nextComponentOffset + 1) {
735 nextComponentOffset = componentEnd;
736 continue;
738 nextComponentOffset = componentEnd + 1;
739 } else {
740 componentEnd = subDirPath.Length();
741 nextComponentOffset = componentEnd;
744 BString entryPath(subDirPath, componentEnd);
745 if (entryPath.IsEmpty())
746 return B_NO_MEMORY;
748 struct stat st;
749 if (!GetEntry(rootDirectoryPaths, entryPath, &entryRef, &st))
750 return B_ENTRY_NOT_FOUND;
752 if (!S_ISDIR(st.st_mode))
753 return B_BAD_VALUE;
755 error = TranslateDirectoryEntry(info->DefinitionFileNodeRef(), entryRef,
756 nodeRef);
757 if (error != B_OK)
758 return error;
760 info = _InfoForNodeRef(nodeRef);
763 _info = info;
765 return B_OK;
769 status_t
770 VirtualDirectoryManager::_CreateRootInfo(const node_ref& definitionFileNodeRef,
771 const entry_ref& definitionFileEntryRef, Info*& _info)
773 RootInfo* root = new(std::nothrow) RootInfo(definitionFileNodeRef,
774 definitionFileEntryRef);
775 if (root == NULL || root->InitCheck() != B_OK) {
776 delete root;
777 return B_NO_MEMORY;
779 ObjectDeleter<RootInfo> rootDeleter(root);
781 status_t error = root->ReadDefinition();
782 if (error != B_OK)
783 return error;
785 if (!_AddInfo(root->Info()))
786 return B_NO_MEMORY;
788 rootDeleter.Detach();
789 _info = root->Info();
791 return B_OK;
795 status_t
796 VirtualDirectoryManager::_ReadSubDirectoryDefinitionFileInfo(
797 const entry_ref& entryRef, entry_ref& _rootDefinitionFileEntryRef,
798 BString& _subDirPath)
800 BDriverSettings driverSettings;
801 status_t error = driverSettings.Load(entryRef);
802 if (error != B_OK)
803 return error;
805 const char* subDirPath = driverSettings.GetParameterValue("subdir");
806 if (subDirPath == NULL || subDirPath[0] == '\0')
807 return B_BAD_DATA;
809 BDriverParameter rootParameter;
810 if (!driverSettings.FindParameter("root", &rootParameter))
811 return B_BAD_DATA;
813 const char* name = rootParameter.GetParameterValue("name");
814 dev_t device = rootParameter.GetInt32ParameterValue("device", -1, -1);
815 ino_t directory = rootParameter.GetInt64ParameterValue("directory");
816 if (name == NULL || name[0] == '\0' || device < 0)
817 return B_BAD_DATA;
819 _rootDefinitionFileEntryRef = entry_ref(device, directory, name);
820 _subDirPath = subDirPath;
822 return !_subDirPath.IsEmpty() && _rootDefinitionFileEntryRef.name != NULL
823 ? B_OK : B_NO_MEMORY;
826 } // namespace BPrivate