1/*
2 * Copyright 2013, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Ingo Weinhold <ingo_weinhold@gmx.de>
7 */
8
9
10#include <MergedDirectory.h>
11
12#include <new>
13#include <set>
14#include <string>
15#include <cstddef>
16
17#include <Directory.h>
18#include <Entry.h>
19
20#include <AutoDeleter.h>
21
22#include "storage_support.h"
23
24
25struct BMergedDirectory::EntryNameSet : std::set<std::string> {
26};
27
28
29BMergedDirectory::BMergedDirectory(BPolicy policy)
30	:
31	BEntryList(),
32	fDirectories(10, true),
33	fPolicy(policy),
34	fDirectoryIndex(0),
35	fVisitedEntries(NULL)
36{
37}
38
39
40BMergedDirectory::~BMergedDirectory()
41{
42	delete fVisitedEntries;
43}
44
45
46status_t
47BMergedDirectory::Init()
48{
49	delete fVisitedEntries;
50	fDirectories.MakeEmpty(true);
51
52	fVisitedEntries = new(std::nothrow) EntryNameSet;
53	return fVisitedEntries != NULL ? B_OK : B_NO_MEMORY;
54}
55
56
57BMergedDirectory::BPolicy
58BMergedDirectory::Policy() const
59{
60	return fPolicy;
61}
62
63
64void
65BMergedDirectory::SetPolicy(BPolicy policy)
66{
67	fPolicy = policy;
68}
69
70
71status_t
72BMergedDirectory::AddDirectory(BDirectory* directory)
73{
74	return fDirectories.AddItem(directory) ? B_OK : B_NO_MEMORY;
75}
76
77
78status_t
79BMergedDirectory::AddDirectory(const char* path)
80{
81	BDirectory* directory = new(std::nothrow) BDirectory(path);
82	if (directory == NULL)
83		return B_NO_MEMORY;
84	ObjectDeleter<BDirectory> directoryDeleter(directory);
85
86	if (directory->InitCheck() != B_OK)
87		return directory->InitCheck();
88
89	status_t error = AddDirectory(directory);
90	if (error != B_OK)
91		return error;
92	directoryDeleter.Detach();
93
94	return B_OK;
95}
96
97
98status_t
99BMergedDirectory::GetNextEntry(BEntry* entry, bool traverse)
100{
101	entry_ref ref;
102	status_t error = GetNextRef(&ref);
103	if (error != B_OK)
104		return error;
105
106	return entry->SetTo(&ref, traverse);
107}
108
109
110status_t
111BMergedDirectory::GetNextRef(entry_ref* ref)
112{
113	BPrivate::Storage::LongDirEntry longEntry;
114	struct dirent* dirEntry = longEntry.dirent();
115	int32 result = GetNextDirents(dirEntry, sizeof(longEntry), 1);
116	if (result < 0)
117		return result;
118	if (result == 0)
119		return B_ENTRY_NOT_FOUND;
120
121	BEntry entry;
122	status_t error
123		= entry.SetTo(fDirectories.ItemAt(fDirectoryIndex), dirEntry->d_name);
124	if (error != B_OK)
125		return error;
126
127	return entry.GetRef(ref);
128}
129
130
131int32
132BMergedDirectory::GetNextDirents(struct dirent* direntBuffer, size_t bufferSize,
133	int32 maxEntries)
134{
135	if (maxEntries <= 0)
136		return B_BAD_VALUE;
137
138	while (fDirectoryIndex < fDirectories.CountItems()) {
139		int32 count = fDirectories.ItemAt(fDirectoryIndex)->GetNextDirents(
140			direntBuffer, bufferSize, 1);
141		if (count < 0)
142			return count;
143		if (count == 0) {
144			fDirectoryIndex++;
145			continue;
146		}
147
148		if (strcmp(direntBuffer->d_name, ".") == 0
149			|| strcmp(direntBuffer->d_name, "..") == 0) {
150			continue;
151		}
152
153		switch (fPolicy) {
154			case B_ALLOW_DUPLICATES:
155				return count;
156
157			case B_ALWAYS_FIRST:
158			case B_COMPARE:
159				if (fVisitedEntries != NULL
160					&& fVisitedEntries->find(direntBuffer->d_name)
161						!= fVisitedEntries->end()) {
162					continue;
163				}
164
165				if (fVisitedEntries != NULL) {
166					try {
167						fVisitedEntries->insert(direntBuffer->d_name);
168					} catch (std::bad_alloc&) {
169						return B_NO_MEMORY;
170					}
171				}
172
173				if (fPolicy == B_ALWAYS_FIRST
174					|| _IsBestEntry(direntBuffer->d_name)) {
175					return 1;
176				}
177
178				fVisitedEntries->erase(direntBuffer->d_name);
179				break;
180		}
181	}
182
183	return B_ENTRY_NOT_FOUND;
184}
185
186
187status_t
188BMergedDirectory::Rewind()
189{
190	for (int32 i = 0; BDirectory* directory = fDirectories.ItemAt(i); i++)
191		directory->Rewind();
192
193	if (fVisitedEntries != NULL)
194		fVisitedEntries->clear();
195
196	fDirectoryIndex = 0;
197	return B_OK;
198}
199
200
201int32
202BMergedDirectory::CountEntries()
203{
204	int32 count = 0;
205	char buffer[offsetof(struct dirent, d_name) + B_FILE_NAME_LENGTH];
206	while (GetNextDirents((dirent*)&buffer, sizeof(buffer), 1) == 1)
207		count++;
208	return count;
209}
210
211
212bool
213BMergedDirectory::ShallPreferFirstEntry(const entry_ref& entry1, int32 index1,
214	const entry_ref& entry2, int32 index2)
215{
216	// That's basically B_ALWAYS_FIRST semantics. A derived class will implement
217	// the desired semantics.
218	return true;
219}
220
221
222bool
223BMergedDirectory::_IsBestEntry(const char* name)
224{
225	entry_ref bestEntry;
226	if (BEntry(fDirectories.ItemAt(fDirectoryIndex), name).GetRef(&bestEntry)
227			!= B_OK) {
228		return false;
229	}
230
231	int32 directoryCount = fDirectories.CountItems();
232	for (int32 i = fDirectoryIndex + 1; i < directoryCount; i++) {
233		BEntry entry(fDirectories.ItemAt(i), name);
234		entry_ref ref;
235		if (entry.Exists() && entry.GetRef(&ref) == B_OK
236			&& !ShallPreferFirstEntry(bestEntry, fDirectoryIndex, ref, i)) {
237			return false;
238		}
239	}
240
241	return true;
242}
243