1/*
2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Copyright 2011-2017, Rene Gollent, rene@gollent.com.
4 * Distributed under the terms of the MIT License.
5 */
6
7#include "FileManager.h"
8
9#include <new>
10
11#include <AutoDeleter.h>
12#include <AutoLocker.h>
13
14#include "LocatableDirectory.h"
15#include "LocatableFile.h"
16#include "SourceFile.h"
17#include "StringUtils.h"
18#include "TeamFileManagerSettings.h"
19
20
21// #pragma mark - EntryPath
22
23
24struct FileManager::EntryPath {
25	const char*	directory;
26	const char*	name;
27
28	EntryPath(const char* directory, const char* name)
29		:
30		directory(directory),
31		name(name)
32	{
33	}
34
35	EntryPath(const BString& directory, const BString& name)
36		:
37		directory(directory.Length() > 0 ? directory.String() : NULL),
38		name(name.String())
39	{
40	}
41
42	EntryPath(const LocatableEntry* entry)
43		:
44		directory(NULL),
45		name(entry->Name())
46	{
47		LocatableDirectory* parent = entry->Parent();
48		if (parent != NULL && strlen(parent->Path()) > 0)
49			directory = parent->Path();
50	}
51
52	EntryPath(const EntryPath& other)
53		:
54		directory(other.directory),
55		name(other.name)
56	{
57	}
58
59	size_t HashValue() const
60	{
61		return StringUtils::HashValue(directory)
62			^ StringUtils::HashValue(name);
63	}
64
65	bool operator==(const EntryPath& other) const
66	{
67		if (directory != other.directory
68			&& (directory == NULL || other.directory == NULL
69				|| strcmp(directory, other.directory) != 0)) {
70			return false;
71		}
72
73		return strcmp(name, other.name) == 0;
74	}
75};
76
77
78// #pragma mark - EntryHashDefinition
79
80
81struct FileManager::EntryHashDefinition {
82	typedef EntryPath		KeyType;
83	typedef	LocatableEntry	ValueType;
84
85	size_t HashKey(const EntryPath& key) const
86	{
87		return key.HashValue();
88	}
89
90	size_t Hash(const LocatableEntry* value) const
91	{
92		return HashKey(EntryPath(value));
93	}
94
95	bool Compare(const EntryPath& key, const LocatableEntry* value) const
96	{
97		return EntryPath(value) == key;
98	}
99
100	LocatableEntry*& GetLink(LocatableEntry* value) const
101	{
102		return value->fNext;
103	}
104};
105
106
107// #pragma mark - Domain
108
109
110class FileManager::Domain : private LocatableEntryOwner {
111public:
112	Domain(FileManager* manager, bool isLocal)
113		:
114		fManager(manager),
115		fIsLocal(isLocal)
116	{
117	}
118
119	~Domain()
120	{
121		LocatableEntry* entry = fEntries.Clear(true);
122		while (entry != NULL) {
123			LocatableEntry* next = entry->fNext;
124			entry->ReleaseReference();
125			entry = next;
126		}
127	}
128
129	status_t Init()
130	{
131		status_t error = fEntries.Init();
132		if (error != B_OK)
133			return error;
134
135		return B_OK;
136	}
137
138	LocatableFile* GetFile(const BString& directoryPath,
139		const BString& relativePath)
140	{
141		if (directoryPath.Length() == 0 || relativePath[0] == '/')
142			return GetFile(relativePath);
143		return GetFile(BString(directoryPath) << '/' << relativePath);
144	}
145
146	LocatableFile* GetFile(const BString& path)
147	{
148		BString directoryPath;
149		BString name;
150		_SplitPath(path, directoryPath, name);
151		LocatableFile* file = _GetFile(directoryPath, name);
152		if (file == NULL)
153			return NULL;
154
155		// try to auto-locate the file
156		if (LocatableDirectory* directory = file->Parent()) {
157			if (directory->State() == LOCATABLE_ENTRY_UNLOCATED) {
158				// parent not yet located -- try locate with the entry's path
159				BString path;
160				file->GetPath(path);
161				_LocateEntry(file, path, true, true);
162			} else {
163				// parent already located -- locate the entry in the parent
164				BString locatedDirectoryPath;
165				if (directory->GetLocatedPath(locatedDirectoryPath))
166					_LocateEntryInParentDir(file, locatedDirectoryPath, true);
167			}
168		}
169
170		return file;
171	}
172
173	void EntryLocated(const BString& path, const BString& locatedPath)
174	{
175		BString directory;
176		BString name;
177		_SplitPath(path, directory, name);
178
179		LocatableEntry* entry = _LookupEntry(EntryPath(directory, name));
180		if (entry == NULL)
181			return;
182
183		_LocateEntry(entry, locatedPath, false, true);
184	}
185
186private:
187	virtual bool Lock()
188	{
189		return fManager->Lock();
190	}
191
192	virtual void Unlock()
193	{
194		fManager->Unlock();
195	}
196
197	virtual void LocatableEntryUnused(LocatableEntry* entry)
198	{
199		AutoLocker<FileManager> lock(fManager);
200		if (fEntries.Lookup(EntryPath(entry)) == entry)
201			fEntries.Remove(entry);
202
203		LocatableDirectory* parent = entry->Parent();
204		if (parent != NULL)
205			parent->RemoveEntry(entry);
206	}
207
208	bool _LocateDirectory(LocatableDirectory* directory,
209		const BString& locatedPath, bool implicit)
210	{
211		if (directory == NULL
212			|| directory->State() != LOCATABLE_ENTRY_UNLOCATED) {
213			return false;
214		}
215
216		if (!_LocateEntry(directory, locatedPath, implicit, true))
217			return false;
218
219		_LocateEntries(directory, locatedPath, implicit);
220
221		return true;
222	}
223
224	bool _LocateEntry(LocatableEntry* entry, const BString& locatedPath,
225		bool implicit, bool locateAncestors)
226	{
227		if (implicit && entry->State() == LOCATABLE_ENTRY_LOCATED_EXPLICITLY)
228			return false;
229
230		struct stat st;
231		if (stat(locatedPath, &st) != 0)
232			return false;
233
234		if (S_ISDIR(st.st_mode)) {
235			LocatableDirectory* directory
236				= dynamic_cast<LocatableDirectory*>(entry);
237			if (directory == NULL)
238				return false;
239			entry->SetLocatedPath(locatedPath, implicit);
240		} else if (S_ISREG(st.st_mode)) {
241			LocatableFile* file = dynamic_cast<LocatableFile*>(entry);
242			if (file == NULL)
243				return false;
244			entry->SetLocatedPath(locatedPath, implicit);
245		}
246
247		// locate the ancestor directories, if requested
248		if (locateAncestors) {
249			BString locatedDirectory;
250			BString locatedName;
251			_SplitPath(locatedPath, locatedDirectory, locatedName);
252			if (locatedName == entry->Name())
253				_LocateDirectory(entry->Parent(), locatedDirectory, implicit);
254		}
255
256		return true;
257	}
258
259	bool _LocateEntryInParentDir(LocatableEntry* entry,
260		const BString& locatedDirectoryPath, bool implicit)
261	{
262		// construct the located entry path
263		BString locatedEntryPath(locatedDirectoryPath);
264		int32 pathLength = locatedEntryPath.Length();
265		if (pathLength >= 1 && locatedEntryPath[pathLength - 1] != '/')
266			locatedEntryPath << '/';
267		locatedEntryPath << entry->Name();
268
269		return _LocateEntry(entry, locatedEntryPath, implicit, false);
270	}
271
272	void _LocateEntries(LocatableDirectory* directory,
273		const BString& locatedPath, bool implicit)
274	{
275		for (LocatableEntryList::ConstIterator it
276				= directory->Entries().GetIterator();
277			LocatableEntry* entry = it.Next();) {
278			if (entry->State() == LOCATABLE_ENTRY_LOCATED_EXPLICITLY)
279				continue;
280
281			 if (_LocateEntryInParentDir(entry, locatedPath, implicit)) {
282				// recurse for directories
283				if (LocatableDirectory* subDir
284						= dynamic_cast<LocatableDirectory*>(entry)) {
285					BString locatedEntryPath;
286					if (subDir->GetLocatedPath(locatedEntryPath))
287						_LocateEntries(subDir, locatedEntryPath, implicit);
288				}
289			}
290		}
291	}
292
293	LocatableFile* _GetFile(const BString& directoryPath, const BString& name)
294	{
295		BString normalizedDirPath;
296		_NormalizePath(directoryPath, normalizedDirPath);
297
298		// if already known return the file
299		LocatableEntry* entry = _LookupEntry(EntryPath(normalizedDirPath, name));
300		if (entry != NULL) {
301			LocatableFile* file = dynamic_cast<LocatableFile*>(entry);
302			if (file == NULL)
303				return NULL;
304
305			if (file->AcquireReference() == 0)
306				fEntries.Remove(file);
307			else
308				return file;
309		}
310
311		// no such file yet -- create it
312		LocatableDirectory* directory = _GetDirectory(normalizedDirPath);
313		if (directory == NULL)
314			return NULL;
315
316		LocatableFile* file = new(std::nothrow) LocatableFile(this, directory,
317			name);
318		if (file == NULL) {
319			directory->ReleaseReference();
320			return NULL;
321		}
322
323		directory->AddEntry(file);
324
325		fEntries.Insert(file);
326
327		return file;
328	}
329
330	LocatableDirectory* _GetDirectory(const BString& path)
331	{
332		BString directoryPath;
333		BString fileName;
334		_SplitNormalizedPath(path, directoryPath, fileName);
335
336		// if already know return the directory
337		LocatableEntry* entry
338			= _LookupEntry(EntryPath(directoryPath, fileName));
339		if (entry != NULL) {
340			LocatableDirectory* directory
341				= dynamic_cast<LocatableDirectory*>(entry);
342			if (directory == NULL)
343				return NULL;
344			directory->AcquireReference();
345			return directory;
346		}
347
348		// get the parent directory
349		LocatableDirectory* parentDirectory = NULL;
350		if (directoryPath.Length() > 0) {
351			parentDirectory = _GetDirectory(directoryPath);
352			if (parentDirectory == NULL)
353				return NULL;
354		}
355
356		// create a new directory
357		LocatableDirectory* directory = new(std::nothrow) LocatableDirectory(
358			this, parentDirectory, path);
359		if (directory == NULL) {
360			parentDirectory->ReleaseReference();
361			return NULL;
362		}
363
364		// auto-locate, if possible
365		if (fIsLocal) {
366			BString dirPath;
367			directory->GetPath(dirPath);
368			directory->SetLocatedPath(dirPath, false);
369		} else if (parentDirectory != NULL
370			&& parentDirectory->State() != LOCATABLE_ENTRY_UNLOCATED) {
371			BString locatedDirectoryPath;
372			if (parentDirectory->GetLocatedPath(locatedDirectoryPath))
373				_LocateEntryInParentDir(directory, locatedDirectoryPath, true);
374		}
375
376		if (parentDirectory != NULL)
377			parentDirectory->AddEntry(directory);
378
379		fEntries.Insert(directory);
380		return directory;
381	}
382
383	LocatableEntry* _LookupEntry(const EntryPath& entryPath)
384	{
385		LocatableEntry* entry = fEntries.Lookup(entryPath);
386		if (entry == NULL)
387			return NULL;
388
389		// if already unreferenced, remove it
390		if (entry->CountReferences() == 0) {
391			fEntries.Remove(entry);
392			return NULL;
393		}
394
395		return entry;
396	}
397
398	void _NormalizePath(const BString& path, BString& _normalizedPath)
399	{
400		BString normalizedPath;
401		char* buffer = normalizedPath.LockBuffer(path.Length());
402		int32 outIndex = 0;
403		const char* remaining = path.String();
404
405		while (*remaining != '\0') {
406			// collapse repeated slashes
407			if (*remaining == '/') {
408				buffer[outIndex++] = '/';
409				remaining++;
410				while (*remaining == '/')
411					remaining++;
412			}
413
414			if (*remaining == '\0') {
415				// remove trailing slash (unless it's "/" only)
416				if (outIndex > 1)
417					outIndex--;
418				break;
419			}
420
421			// skip "." components
422			if (*remaining == '.') {
423				if (remaining[1] == '\0')
424					break;
425
426				if (remaining[1] == '/') {
427					remaining += 2;
428					while (*remaining == '/')
429						remaining++;
430					continue;
431				}
432			}
433
434			// copy path component
435			while (*remaining != '\0' && *remaining != '/')
436				buffer[outIndex++] = *(remaining++);
437		}
438
439		// If the path didn't change, use the original path (BString's copy on
440		// write mechanism) rather than the new string.
441		if (outIndex == path.Length()) {
442			_normalizedPath = path;
443		} else {
444			normalizedPath.UnlockBuffer(outIndex);
445			_normalizedPath = normalizedPath;
446		}
447	}
448
449	void _SplitPath(const BString& path, BString& _directory, BString& _name)
450	{
451		BString normalized;
452		_NormalizePath(path, normalized);
453		_SplitNormalizedPath(normalized, _directory, _name);
454	}
455
456	void _SplitNormalizedPath(const BString& path, BString& _directory,
457		BString& _name)
458	{
459		// handle single component (including root dir) cases
460		int32 lastSlash = path.FindLast('/');
461		if (lastSlash < 0 || path.Length() == 1) {
462			_directory = (const char*)NULL;
463			_name = path;
464			return;
465		}
466
467		// handle root dir + one component and multi component cases
468		if (lastSlash == 0)
469			_directory = "/";
470		else
471			_directory.SetTo(path, lastSlash);
472		_name = path.String() + (lastSlash + 1);
473	}
474
475private:
476	FileManager*		fManager;
477	LocatableEntryTable	fEntries;
478	bool				fIsLocal;
479};
480
481
482// #pragma mark - SourceFileEntry
483
484
485struct FileManager::SourceFileEntry : public SourceFileOwner {
486
487	FileManager*		manager;
488	BString				path;
489	SourceFile*			file;
490	SourceFileEntry*	next;
491
492	SourceFileEntry(FileManager* manager, const BString& path)
493		:
494		manager(manager),
495		path(path),
496		file(NULL)
497	{
498	}
499
500	virtual void SourceFileUnused(SourceFile* sourceFile)
501	{
502		manager->_SourceFileUnused(this);
503	}
504
505	virtual void SourceFileDeleted(SourceFile* sourceFile)
506	{
507		// We have already been removed from the table, so commit suicide.
508		delete this;
509	}
510};
511
512
513// #pragma mark - SourceFileHashDefinition
514
515
516struct FileManager::SourceFileHashDefinition {
517	typedef BString			KeyType;
518	typedef	SourceFileEntry	ValueType;
519
520	size_t HashKey(const BString& key) const
521	{
522		return StringUtils::HashValue(key);
523	}
524
525	size_t Hash(const SourceFileEntry* value) const
526	{
527		return HashKey(value->path);
528	}
529
530	bool Compare(const BString& key, const SourceFileEntry* value) const
531	{
532		return value->path == key;
533	}
534
535	SourceFileEntry*& GetLink(SourceFileEntry* value) const
536	{
537		return value->next;
538	}
539};
540
541
542// #pragma mark - FileManager
543
544
545FileManager::FileManager()
546	:
547	fLock("file manager"),
548	fTargetDomain(NULL),
549	fSourceDomain(NULL),
550	fSourceFiles(NULL)
551{
552}
553
554
555FileManager::~FileManager()
556{
557	delete fTargetDomain;
558	delete fSourceDomain;
559
560	SourceFileEntry* entry = fSourceFiles->Clear();
561	while (entry != NULL) {
562		SourceFileEntry* next = entry->next;
563		delete entry;
564		entry = next;
565	}
566	delete fSourceFiles;
567}
568
569
570status_t
571FileManager::Init(bool targetIsLocal)
572{
573	status_t error = fLock.InitCheck();
574	if (error != B_OK)
575		return error;
576
577	// create target domain
578	fTargetDomain = new(std::nothrow) Domain(this, targetIsLocal);
579	if (fTargetDomain == NULL)
580		return B_NO_MEMORY;
581
582	error = fTargetDomain->Init();
583	if (error != B_OK)
584		return error;
585
586	// create source domain
587	fSourceDomain = new(std::nothrow) Domain(this, false);
588	if (fSourceDomain == NULL)
589		return B_NO_MEMORY;
590
591	error = fSourceDomain->Init();
592	if (error != B_OK)
593		return error;
594
595	// create source file table
596	fSourceFiles = new(std::nothrow) SourceFileTable;
597	if (fSourceFiles == NULL)
598		return B_NO_MEMORY;
599
600	error = fSourceFiles->Init();
601	if (error != B_OK)
602		return error;
603
604	return B_OK;
605}
606
607
608LocatableFile*
609FileManager::GetTargetFile(const BString& directory,
610	const BString& relativePath)
611{
612	AutoLocker<FileManager> locker(this);
613	return fTargetDomain->GetFile(directory, relativePath);
614}
615
616
617LocatableFile*
618FileManager::GetTargetFile(const BString& path)
619{
620	AutoLocker<FileManager> locker(this);
621	return fTargetDomain->GetFile(path);
622}
623
624
625void
626FileManager::TargetEntryLocated(const BString& path,
627	const BString& locatedPath)
628{
629	AutoLocker<FileManager> locker(this);
630	fTargetDomain->EntryLocated(path, locatedPath);
631}
632
633
634LocatableFile*
635FileManager::GetSourceFile(const BString& directory,
636	const BString& relativePath)
637{
638	AutoLocker<FileManager> locker(this);
639	LocatableFile* file = fSourceDomain->GetFile(directory, relativePath);
640
641	return file;
642}
643
644
645LocatableFile*
646FileManager::GetSourceFile(const BString& path)
647{
648	AutoLocker<FileManager> locker(this);
649	LocatableFile* file = fSourceDomain->GetFile(path);
650
651	return file;
652}
653
654
655status_t
656FileManager::SourceEntryLocated(const BString& path,
657	const BString& locatedPath)
658{
659	AutoLocker<FileManager> locker(this);
660
661	// check if we already have this path mapped. If so,
662	// first clear the mapping, as the user may be attempting
663	// to correct an existing entry.
664	SourceFileEntry* entry = _LookupSourceFile(path);
665	if (entry != NULL)
666		_SourceFileUnused(entry);
667
668	fSourceDomain->EntryLocated(path, locatedPath);
669
670	try {
671		fSourceLocationMappings[path] = locatedPath;
672	} catch (...) {
673		return B_NO_MEMORY;
674	}
675
676	return B_OK;
677}
678
679
680status_t
681FileManager::LoadSourceFile(LocatableFile* file, SourceFile*& _sourceFile)
682{
683	AutoLocker<FileManager> locker(this);
684
685	// get the path
686	BString path;
687	BString originalPath;
688	file->GetPath(originalPath);
689	if (!file->GetLocatedPath(path)) {
690		// see if this is a file we have a lazy mapping for.
691		if (!_LocateFileIfMapped(originalPath, file)
692			|| !file->GetLocatedPath(path)) {
693			return B_ENTRY_NOT_FOUND;
694		}
695	}
696
697	// we might already know the source file
698	SourceFileEntry* entry = _LookupSourceFile(originalPath);
699	if (entry != NULL) {
700		entry->file->AcquireReference();
701		_sourceFile = entry->file;
702		return B_OK;
703	}
704
705	// create the hash table entry
706	entry = new(std::nothrow) SourceFileEntry(this, originalPath);
707	if (entry == NULL)
708		return B_NO_MEMORY;
709
710	// load the file
711	SourceFile* sourceFile = new(std::nothrow) SourceFile(entry);
712	if (sourceFile == NULL) {
713		delete entry;
714		return B_NO_MEMORY;
715	}
716	ObjectDeleter<SourceFile> sourceFileDeleter(sourceFile);
717
718	entry->file = sourceFile;
719
720	status_t error = sourceFile->Init(path);
721	if (error != B_OK)
722		return error;
723
724	fSourceFiles->Insert(entry);
725
726	_sourceFile = sourceFileDeleter.Detach();
727	return B_OK;
728}
729
730
731status_t
732FileManager::LoadLocationMappings(TeamFileManagerSettings* settings)
733{
734	AutoLocker<FileManager> locker(this);
735	for (int32 i = 0; i < settings->CountSourceMappings(); i++) {
736		BString sourcePath;
737		BString locatedPath;
738
739		if (settings->GetSourceMappingAt(i, sourcePath, locatedPath) != B_OK)
740			return B_NO_MEMORY;
741
742		try {
743			fSourceLocationMappings[sourcePath] = locatedPath;
744		} catch (...) {
745			return B_NO_MEMORY;
746		}
747	}
748
749	return B_OK;
750}
751
752
753status_t
754FileManager::SaveLocationMappings(TeamFileManagerSettings* settings)
755{
756	AutoLocker<FileManager> locker(this);
757
758	for (LocatedFileMap::const_iterator it = fSourceLocationMappings.begin();
759		it != fSourceLocationMappings.end(); ++it) {
760		status_t error = settings->AddSourceMapping(it->first, it->second);
761		if (error != B_OK)
762			return error;
763	}
764
765	return B_OK;
766}
767
768
769FileManager::SourceFileEntry*
770FileManager::_LookupSourceFile(const BString& path)
771{
772	SourceFileEntry* entry = fSourceFiles->Lookup(path);
773	if (entry == NULL)
774		return NULL;
775
776	// the entry might be unused already -- in that case remove it
777	if (entry->file->CountReferences() == 0) {
778		fSourceFiles->Remove(entry);
779		return NULL;
780	}
781
782	return entry;
783}
784
785
786void
787FileManager::_SourceFileUnused(SourceFileEntry* entry)
788{
789	AutoLocker<FileManager> locker(this);
790
791	SourceFileEntry* otherEntry = fSourceFiles->Lookup(entry->path);
792	if (otherEntry == entry)
793		fSourceFiles->Remove(entry);
794}
795
796
797bool
798FileManager::_LocateFileIfMapped(const BString& sourcePath,
799	LocatableFile* file)
800{
801	// called with lock held
802
803	LocatedFileMap::const_iterator it = fSourceLocationMappings.find(
804		sourcePath);
805	if (it != fSourceLocationMappings.end()
806		&& file->State() != LOCATABLE_ENTRY_LOCATED_EXPLICITLY
807		&& file->State() != LOCATABLE_ENTRY_LOCATED_IMPLICITLY) {
808		fSourceDomain->EntryLocated(it->first, it->second);
809		return true;
810	}
811
812	return false;
813}
814