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