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