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 "VirtualDirectoryPoseView.h"
11
12#include <new>
13
14#include <AutoLocker.h>
15#include <NotOwningEntryRef.h>
16#include <PathMonitor.h>
17#include <storage_support.h>
18
19#include "Commands.h"
20#include "Tracker.h"
21#include "VirtualDirectoryEntryList.h"
22#include "VirtualDirectoryManager.h"
23
24
25namespace BPrivate {
26
27//	#pragma mark - VirtualDirectoryPoseView
28
29
30VirtualDirectoryPoseView::VirtualDirectoryPoseView(Model* model)
31	:
32	BPoseView(model, kListMode),
33	fDirectoryPaths(),
34	fRootDefinitionFileRef(-1, -1),
35	fFileChangeTime(-1),
36	fIsRoot(false)
37{
38	VirtualDirectoryManager* manager = VirtualDirectoryManager::Instance();
39	if (manager == NULL)
40		return;
41
42	AutoLocker<VirtualDirectoryManager> managerLocker(manager);
43	if (_UpdateDirectoryPaths() != B_OK)
44		return;
45
46	manager->GetRootDefinitionFile(*model->NodeRef(), fRootDefinitionFileRef);
47	fIsRoot = fRootDefinitionFileRef == *model->NodeRef();
48}
49
50
51VirtualDirectoryPoseView::~VirtualDirectoryPoseView()
52{
53}
54
55
56void
57VirtualDirectoryPoseView::MessageReceived(BMessage* message)
58{
59	if (message->WasDropped())
60		return _inherited::MessageReceived(message);
61
62	switch (message->what) {
63		// ignore all edit operations
64		case B_CUT:
65		case B_PASTE:
66		case kCutMoreSelectionToClipboard:
67		case kDelete:
68		case kDuplicateSelection:
69		case kMoveToTrash:
70		case kNewEntryFromTemplate:
71		case kNewFolder:
72			break;
73
74		default:
75			_inherited::MessageReceived(message);
76			break;
77	}
78}
79
80
81void
82VirtualDirectoryPoseView::AttachedToWindow()
83{
84	_inherited::AttachedToWindow();
85	AddFilter(new TPoseViewFilter(this));
86}
87
88
89void
90VirtualDirectoryPoseView::RestoreState(AttributeStreamNode* node)
91{
92	_inherited::RestoreState(node);
93	fViewState->SetViewMode(kListMode);
94}
95
96
97void
98VirtualDirectoryPoseView::RestoreState(const BMessage& message)
99{
100	_inherited::RestoreState(message);
101	fViewState->SetViewMode(kListMode);
102}
103
104
105void
106VirtualDirectoryPoseView::SavePoseLocations(BRect* frameIfDesktop)
107{
108}
109
110
111void
112VirtualDirectoryPoseView::SetViewMode(uint32 newMode)
113{
114}
115
116
117EntryListBase*
118VirtualDirectoryPoseView::InitDirentIterator(const entry_ref* ref)
119{
120	if (fRootDefinitionFileRef.node < 0 || *ref != *TargetModel()->EntryRef())
121		return NULL;
122
123	Model sourceModel(ref, false, true);
124	if (sourceModel.InitCheck() != B_OK)
125		return NULL;
126
127	VirtualDirectoryEntryList* entryList
128		= new(std::nothrow) VirtualDirectoryEntryList(
129			*TargetModel()->NodeRef(), fDirectoryPaths);
130	if (entryList == NULL || entryList->InitCheck() != B_OK) {
131		delete entryList;
132		return NULL;
133	}
134
135	return entryList;
136}
137
138
139void
140VirtualDirectoryPoseView::StartWatching()
141{
142	// watch the directories
143	int32 count = fDirectoryPaths.CountStrings();
144	for (int32 i = 0; i < count; i++) {
145		BString path = fDirectoryPaths.StringAt(i);
146		BPathMonitor::StartWatching(path, B_WATCH_DIRECTORY, this);
147	}
148
149	// watch the definition file
150	TTracker::WatchNode(TargetModel()->NodeRef(),
151		B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR, this);
152
153	// also watch the root definition file
154	if (!fIsRoot)
155		TTracker::WatchNode(&fRootDefinitionFileRef, B_WATCH_STAT, this);
156}
157
158
159void
160VirtualDirectoryPoseView::StopWatching()
161{
162	BPathMonitor::StopWatching(this);
163	stop_watching(this);
164}
165
166
167bool
168VirtualDirectoryPoseView::FSNotification(const BMessage* message)
169{
170	switch (message->GetInt32("opcode", 0)) {
171		case B_ENTRY_CREATED:
172			return _EntryCreated(message);
173
174		case B_ENTRY_REMOVED:
175			return _EntryRemoved(message);
176
177		case B_ENTRY_MOVED:
178			return _EntryMoved(message);
179
180		case B_STAT_CHANGED:
181			return _NodeStatChanged(message);
182
183		default:
184			return _inherited::FSNotification(message);
185	}
186}
187
188
189bool
190VirtualDirectoryPoseView::_EntryCreated(const BMessage* message)
191{
192	NotOwningEntryRef entryRef;
193	node_ref nodeRef;
194
195	if (message->FindInt32("device", &nodeRef.device) != B_OK
196		|| message->FindInt64("node", &nodeRef.node) != B_OK
197		|| message->FindInt64("directory", &entryRef.directory) != B_OK
198		|| message->FindString("name", (const char**)&entryRef.name) != B_OK) {
199		return true;
200	}
201	entryRef.device = nodeRef.device;
202
203	// It might be one of our directories.
204	BString path;
205	if (message->FindString("path", &path) == B_OK
206		&& fDirectoryPaths.HasString(path)) {
207		// Iterate through the directory and generate an entry-created message
208		// for each entry.
209		BDirectory directory;
210		if (directory.SetTo(&nodeRef) != B_OK)
211			return true;
212
213		BPrivate::Storage::LongDirEntry longEntry;
214		struct dirent* entry = longEntry.dirent();
215		while (directory.GetNextDirents(entry, sizeof(longEntry), 1) == 1) {
216			if (strcmp(entry->d_name, ".") != 0
217				&& strcmp(entry->d_name, "..") != 0) {
218				_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_CREATED,
219					node_ref(entry->d_dev, entry->d_ino),
220					NotOwningEntryRef(entry->d_pdev, entry->d_pino,
221						entry->d_name),
222					NULL, false);
223			}
224		}
225		return true;
226	}
227
228	// See, if this entry actually becomes visible. If not, we can simply ignore
229	// it.
230	struct stat st;
231	entry_ref visibleEntryRef;
232	if (!_GetEntry(entryRef.name, visibleEntryRef, &st)
233		|| visibleEntryRef != entryRef) {
234		return true;
235	}
236
237	// If it is a directory, translate it.
238	VirtualDirectoryManager* manager = VirtualDirectoryManager::Instance();
239	AutoLocker<VirtualDirectoryManager> managerLocker(manager);
240
241	bool entryTranslated = S_ISDIR(st.st_mode);
242	if (entryTranslated) {
243		if (manager == NULL)
244			return true;
245
246		if (manager->TranslateDirectoryEntry(*TargetModel()->NodeRef(),
247				entryRef, nodeRef) != B_OK) {
248			return true;
249		}
250	}
251
252	// The entry might replace another entry. If it does, we'll fake a removed
253	// message for the old one first.
254	BPose* pose = fPoseList->FindPoseByFileName(entryRef.name);
255	if (pose != NULL) {
256		if (nodeRef == *pose->TargetModel()->NodeRef()) {
257			// apparently not really a new entry -- can happen for
258			// subdirectories
259			return true;
260		}
261
262		// It may be a directory, so tell the manager.
263		if (manager != NULL)
264			manager->DirectoryRemoved(*pose->TargetModel()->NodeRef());
265
266		managerLocker.Unlock();
267
268		BMessage removedMessage(B_NODE_MONITOR);
269		_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_REMOVED,
270			*pose->TargetModel()->NodeRef(), *pose->TargetModel()->EntryRef());
271	} else
272		managerLocker.Unlock();
273
274	return entryTranslated
275		? (_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_CREATED, nodeRef,
276			entryRef), true)
277		: _inherited::FSNotification(message);
278}
279
280
281bool
282VirtualDirectoryPoseView::_EntryRemoved(const BMessage* message)
283{
284	NotOwningEntryRef entryRef;
285	node_ref nodeRef;
286
287	if (message->FindInt32("device", &nodeRef.device) != B_OK
288		|| message->FindInt64("node", &nodeRef.node) != B_OK
289		|| message->FindInt64("directory", &entryRef.directory)
290			!= B_OK
291		|| message->FindString("name", (const char**)&entryRef.name) != B_OK) {
292		return true;
293	}
294	entryRef.device = nodeRef.device;
295
296	// It might be our definition file.
297	if (nodeRef == *TargetModel()->NodeRef())
298		return _inherited::FSNotification(message);
299
300	// It might be one of our directories.
301	BString path;
302	if (message->FindString("path", &path) == B_OK
303		&& fDirectoryPaths.HasString(path)) {
304		// Find all poses that stem from that directory and generate an
305		// entry-removed message for each.
306		PoseList poses;
307		for (int32 i = 0; BPose* pose = fPoseList->ItemAt(i); i++) {
308			NotOwningEntryRef poseEntryRef = *pose->TargetModel()->EntryRef();
309			if (poseEntryRef.DirectoryNodeRef() == nodeRef)
310				poses.AddItem(pose);
311		}
312
313		for (int32 i = 0; BPose* pose = poses.ItemAt(i); i++) {
314			_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_REMOVED,
315				*pose->TargetModel()->NodeRef(),
316				*pose->TargetModel()->EntryRef(), NULL, false);
317		}
318
319		return true;
320	}
321
322	// If it is a directory, translate it.
323	entry_ref* actualEntryRef = &entryRef;
324	node_ref* actualNodeRef = &nodeRef;
325	entry_ref definitionEntryRef;
326	node_ref definitionNodeRef;
327
328	VirtualDirectoryManager* manager = VirtualDirectoryManager::Instance();
329	AutoLocker<VirtualDirectoryManager> managerLocker(manager);
330
331	if (manager != NULL
332		&& manager->GetSubDirectoryDefinitionFile(*TargetModel()->NodeRef(),
333			entryRef.name, definitionEntryRef, definitionNodeRef)) {
334		actualEntryRef = &definitionEntryRef;
335		actualNodeRef = &definitionNodeRef;
336	}
337
338	// Check the pose. It might have been an entry that wasn't visible anyway.
339	// In that case we can just ignore the notification.
340	BPose* pose = fPoseList->FindPoseByFileName(actualEntryRef->name);
341	if (pose == NULL || *actualNodeRef != *pose->TargetModel()->NodeRef())
342		return true;
343
344	// See, if another entry becomes visible, now.
345	struct stat st;
346	entry_ref visibleEntryRef;
347	node_ref visibleNodeRef;
348	if (_GetEntry(actualEntryRef->name, visibleEntryRef, &st)) {
349		// If the new entry is a directory, translate it.
350		visibleNodeRef = node_ref(st.st_dev, st.st_ino);
351		if (S_ISDIR(st.st_mode)) {
352			if (manager == NULL || manager->TranslateDirectoryEntry(
353					*TargetModel()->NodeRef(), visibleEntryRef, visibleNodeRef)
354					!= B_OK) {
355				return true;
356			}
357
358			// Effectively nothing changes, when the removed entry was a
359			// directory as well.
360			if (visibleNodeRef == *actualNodeRef)
361				return true;
362		}
363	}
364
365	if (actualEntryRef == &entryRef) {
366		managerLocker.Unlock();
367		if (_inherited::FSNotification(message))
368			pendingNodeMonitorCache.Add(message);
369	} else {
370		// tell the manager that the directory has been removed
371		manager->DirectoryRemoved(*actualNodeRef);
372		managerLocker.Unlock();
373
374		_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_REMOVED, *actualNodeRef,
375			*actualEntryRef);
376	}
377
378	_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_CREATED, visibleNodeRef,
379		visibleEntryRef);
380
381	return true;
382}
383
384
385bool
386VirtualDirectoryPoseView::_EntryMoved(const BMessage* message)
387{
388	NotOwningEntryRef fromEntryRef;
389	NotOwningEntryRef toEntryRef;
390	node_ref nodeRef;
391
392	if (message->FindInt32("node device", &nodeRef.device) != B_OK
393		|| message->FindInt64("node", &nodeRef.node) != B_OK
394		|| message->FindInt32("device", &fromEntryRef.device) != B_OK
395		|| message->FindInt64("from directory", &fromEntryRef.directory) != B_OK
396		|| message->FindInt64("to directory", &toEntryRef.directory) != B_OK
397		|| message->FindString("from name", (const char**)&fromEntryRef.name)
398			!= B_OK
399		|| message->FindString("name", (const char**)&toEntryRef.name)
400			!= B_OK) {
401		return true;
402	}
403	toEntryRef.device = fromEntryRef.device;
404
405	// TODO: That's the lazy approach. Ideally we'd analyze the situation and
406	// forward a B_ENTRY_MOVED, if possible. There are quite a few cases to
407	// consider, though.
408	_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_REMOVED, nodeRef,
409		fromEntryRef, message->GetString("from path", NULL), false);
410	_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_CREATED, nodeRef,
411		toEntryRef, message->GetString("path", NULL), false);
412
413	return true;
414}
415
416
417bool
418VirtualDirectoryPoseView::_NodeStatChanged(const BMessage* message)
419{
420	node_ref nodeRef;
421	if (message->FindInt32("device", &nodeRef.device) != B_OK
422		|| message->FindInt64("node", &nodeRef.node) != B_OK) {
423		return true;
424	}
425
426	if (nodeRef == fRootDefinitionFileRef) {
427		if ((message->GetInt32("fields", 0) & B_STAT_MODIFICATION_TIME) != 0) {
428			VirtualDirectoryManager* manager
429				= VirtualDirectoryManager::Instance();
430			if (manager != NULL) {
431				AutoLocker<VirtualDirectoryManager> managerLocker(manager);
432				if (!manager->DefinitionFileChanged(
433						*TargetModel()->NodeRef())) {
434					// The definition file no longer exists. Ignore the message
435					// -- we'll get a remove notification soon.
436					return true;
437				}
438
439				bigtime_t fileChangeTime;
440				manager->GetDefinitionFileChangeTime(*TargetModel()->NodeRef(),
441					fileChangeTime);
442				if (fileChangeTime != fFileChangeTime) {
443					_UpdateDirectoryPaths();
444					managerLocker.Unlock();
445					Refresh();
446						// TODO: Refresh() is rather radical. Or rather its
447						// implementation is. Ideally it would just compare the
448						// currently added poses with what a new dir iterator
449						// returns and remove/add poses as needed.
450				}
451			}
452		}
453
454		if (!fIsRoot)
455			return true;
456	}
457
458	return _inherited::FSNotification(message);
459}
460
461
462void
463VirtualDirectoryPoseView::_DispatchEntryCreatedOrRemovedMessage(int32 opcode,
464	const node_ref& nodeRef, const entry_ref& entryRef, const char* path,
465	bool dispatchToSuperClass)
466{
467	BMessage message(B_NODE_MONITOR);
468	message.AddInt32("opcode", opcode);
469	message.AddInt32("device", nodeRef.device);
470	message.AddInt64("node", nodeRef.node);
471	message.AddInt64("directory", entryRef.directory);
472	message.AddString("name", entryRef.name);
473	if (path != NULL && path[0] != '\0')
474		message.AddString("path", path);
475	bool result = dispatchToSuperClass
476		? _inherited::FSNotification(&message)
477		: FSNotification(&message);
478	if (!result)
479		pendingNodeMonitorCache.Add(&message);
480}
481
482
483bool
484VirtualDirectoryPoseView::_GetEntry(const char* name, entry_ref& _ref,
485	struct stat* _st)
486{
487	return VirtualDirectoryManager::GetEntry(fDirectoryPaths, name, &_ref, _st);
488}
489
490
491status_t
492VirtualDirectoryPoseView::_UpdateDirectoryPaths()
493{
494	VirtualDirectoryManager* manager = VirtualDirectoryManager::Instance();
495	Model* model = TargetModel();
496	status_t error = manager->ResolveDirectoryPaths(*model->NodeRef(),
497		*model->EntryRef(), fDirectoryPaths);
498	if (error != B_OK)
499		return error;
500
501	manager->GetDefinitionFileChangeTime(*model->NodeRef(), fFileChangeTime);
502	return B_OK;
503}
504
505} // namespace BPrivate
506