xref: /haiku/src/kits/tracker/VirtualDirectoryPoseView.cpp (revision 1e60bdeab63fa7a57bc9a55b032052e95a18bd2c)
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 entry;
212 		while (directory.GetNextDirents(&entry, sizeof(entry), 1) == 1) {
213 			if (strcmp(entry.d_name, ".") != 0
214 				&& strcmp(entry.d_name, "..") != 0) {
215 				_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_CREATED,
216 					node_ref(entry.d_dev, entry.d_ino),
217 					NotOwningEntryRef(entry.d_pdev, entry.d_pino,
218 						entry.d_name),
219 					NULL, false);
220 			}
221 		}
222 		return true;
223 	}
224 
225 	// See, if this entry actually becomes visible. If not, we can simply ignore
226 	// it.
227 	struct stat st;
228 	entry_ref visibleEntryRef;
229 	if (!_GetEntry(entryRef.name, visibleEntryRef, &st)
230 		|| visibleEntryRef != entryRef) {
231 		return true;
232 	}
233 
234 	// If it is a directory, translate it.
235 	VirtualDirectoryManager* manager = VirtualDirectoryManager::Instance();
236 	AutoLocker<VirtualDirectoryManager> managerLocker(manager);
237 
238 	bool entryTranslated = S_ISDIR(st.st_mode);
239 	if (entryTranslated) {
240 		if (manager == NULL)
241 			return true;
242 
243 		if (manager->TranslateDirectoryEntry(*TargetModel()->NodeRef(),
244 				entryRef, nodeRef) != B_OK) {
245 			return true;
246 		}
247 	}
248 
249 	// The entry might replace another entry. If it does, we'll fake a removed
250 	// message for the old one first.
251 	BPose* pose = fPoseList->FindPoseByFileName(entryRef.name);
252 	if (pose != NULL) {
253 		if (nodeRef == *pose->TargetModel()->NodeRef()) {
254 			// apparently not really a new entry -- can happen for
255 			// subdirectories
256 			return true;
257 		}
258 
259 		// It may be a directory, so tell the manager.
260 		if (manager != NULL)
261 			manager->DirectoryRemoved(*pose->TargetModel()->NodeRef());
262 
263 		managerLocker.Unlock();
264 
265 		BMessage removedMessage(B_NODE_MONITOR);
266 		_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_REMOVED,
267 			*pose->TargetModel()->NodeRef(), *pose->TargetModel()->EntryRef());
268 	} else
269 		managerLocker.Unlock();
270 
271 	return entryTranslated
272 		? (_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_CREATED, nodeRef,
273 			entryRef), true)
274 		: _inherited::FSNotification(message);
275 }
276 
277 
278 bool
279 VirtualDirectoryPoseView::_EntryRemoved(const BMessage* message)
280 {
281 	NotOwningEntryRef entryRef;
282 	node_ref nodeRef;
283 
284 	if (message->FindInt32("device", &nodeRef.device) != B_OK
285 		|| message->FindInt64("node", &nodeRef.node) != B_OK
286 		|| message->FindInt64("directory", &entryRef.directory)
287 			!= B_OK
288 		|| message->FindString("name", (const char**)&entryRef.name) != B_OK) {
289 		return true;
290 	}
291 	entryRef.device = nodeRef.device;
292 
293 	// It might be our definition file.
294 	if (nodeRef == *TargetModel()->NodeRef())
295 		return _inherited::FSNotification(message);
296 
297 	// It might be one of our directories.
298 	BString path;
299 	if (message->FindString("path", &path) == B_OK
300 		&& fDirectoryPaths.HasString(path)) {
301 		// Find all poses that stem from that directory and generate an
302 		// entry-removed message for each.
303 		PoseList poses;
304 		for (int32 i = 0; BPose* pose = fPoseList->ItemAt(i); i++) {
305 			NotOwningEntryRef poseEntryRef = *pose->TargetModel()->EntryRef();
306 			if (poseEntryRef.DirectoryNodeRef() == nodeRef)
307 				poses.AddItem(pose);
308 		}
309 
310 		for (int32 i = 0; BPose* pose = poses.ItemAt(i); i++) {
311 			_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_REMOVED,
312 				*pose->TargetModel()->NodeRef(),
313 				*pose->TargetModel()->EntryRef(), NULL, false);
314 		}
315 
316 		return true;
317 	}
318 
319 	// If it is a directory, translate it.
320 	entry_ref* actualEntryRef = &entryRef;
321 	node_ref* actualNodeRef = &nodeRef;
322 	entry_ref definitionEntryRef;
323 	node_ref definitionNodeRef;
324 
325 	VirtualDirectoryManager* manager = VirtualDirectoryManager::Instance();
326 	AutoLocker<VirtualDirectoryManager> managerLocker(manager);
327 
328 	if (manager != NULL
329 		&& manager->GetSubDirectoryDefinitionFile(*TargetModel()->NodeRef(),
330 			entryRef.name, definitionEntryRef, definitionNodeRef)) {
331 		actualEntryRef = &definitionEntryRef;
332 		actualNodeRef = &definitionNodeRef;
333 	}
334 
335 	// Check the pose. It might have been an entry that wasn't visible anyway.
336 	// In that case we can just ignore the notification.
337 	BPose* pose = fPoseList->FindPoseByFileName(actualEntryRef->name);
338 	if (pose == NULL || *actualNodeRef != *pose->TargetModel()->NodeRef())
339 		return true;
340 
341 	// See, if another entry becomes visible, now.
342 	struct stat st;
343 	entry_ref visibleEntryRef;
344 	node_ref visibleNodeRef;
345 	if (_GetEntry(actualEntryRef->name, visibleEntryRef, &st)) {
346 		// If the new entry is a directory, translate it.
347 		visibleNodeRef = node_ref(st.st_dev, st.st_ino);
348 		if (S_ISDIR(st.st_mode)) {
349 			if (manager == NULL || manager->TranslateDirectoryEntry(
350 					*TargetModel()->NodeRef(), visibleEntryRef, visibleNodeRef)
351 					!= B_OK) {
352 				return true;
353 			}
354 
355 			// Effectively nothing changes, when the removed entry was a
356 			// directory as well.
357 			if (visibleNodeRef == *actualNodeRef)
358 				return true;
359 		}
360 	}
361 
362 	if (actualEntryRef == &entryRef) {
363 		managerLocker.Unlock();
364 		if (_inherited::FSNotification(message))
365 			pendingNodeMonitorCache.Add(message);
366 	} else {
367 		// tell the manager that the directory has been removed
368 		manager->DirectoryRemoved(*actualNodeRef);
369 		managerLocker.Unlock();
370 
371 		_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_REMOVED, *actualNodeRef,
372 			*actualEntryRef);
373 	}
374 
375 	_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_CREATED, visibleNodeRef,
376 		visibleEntryRef);
377 
378 	return true;
379 }
380 
381 
382 bool
383 VirtualDirectoryPoseView::_EntryMoved(const BMessage* message)
384 {
385 	NotOwningEntryRef fromEntryRef;
386 	NotOwningEntryRef toEntryRef;
387 	node_ref nodeRef;
388 
389 	if (message->FindInt32("node device", &nodeRef.device) != B_OK
390 		|| message->FindInt64("node", &nodeRef.node) != B_OK
391 		|| message->FindInt32("device", &fromEntryRef.device) != B_OK
392 		|| message->FindInt64("from directory", &fromEntryRef.directory) != B_OK
393 		|| message->FindInt64("to directory", &toEntryRef.directory) != B_OK
394 		|| message->FindString("from name", (const char**)&fromEntryRef.name)
395 			!= B_OK
396 		|| message->FindString("name", (const char**)&toEntryRef.name)
397 			!= B_OK) {
398 		return true;
399 	}
400 	toEntryRef.device = fromEntryRef.device;
401 
402 	// TODO: That's the lazy approach. Ideally we'd analyze the situation and
403 	// forward a B_ENTRY_MOVED, if possible. There are quite a few cases to
404 	// consider, though.
405 	_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_REMOVED, nodeRef,
406 		fromEntryRef, message->GetString("from path", NULL), false);
407 	_DispatchEntryCreatedOrRemovedMessage(B_ENTRY_CREATED, nodeRef,
408 		toEntryRef, message->GetString("path", NULL), false);
409 
410 	return true;
411 }
412 
413 
414 bool
415 VirtualDirectoryPoseView::_NodeStatChanged(const BMessage* message)
416 {
417 	node_ref nodeRef;
418 	if (message->FindInt32("device", &nodeRef.device) != B_OK
419 		|| message->FindInt64("node", &nodeRef.node) != B_OK) {
420 		return true;
421 	}
422 
423 	if (nodeRef == fRootDefinitionFileRef) {
424 		if ((message->GetInt32("fields", 0) & B_STAT_MODIFICATION_TIME) != 0) {
425 			VirtualDirectoryManager* manager
426 				= VirtualDirectoryManager::Instance();
427 			if (manager != NULL) {
428 				AutoLocker<VirtualDirectoryManager> managerLocker(manager);
429 				if (!manager->DefinitionFileChanged(
430 						*TargetModel()->NodeRef())) {
431 					// The definition file no longer exists. Ignore the message
432 					// -- we'll get a remove notification soon.
433 					return true;
434 				}
435 
436 				bigtime_t fileChangeTime;
437 				manager->GetDefinitionFileChangeTime(*TargetModel()->NodeRef(),
438 					fileChangeTime);
439 				if (fileChangeTime != fFileChangeTime) {
440 					_UpdateDirectoryPaths();
441 					managerLocker.Unlock();
442 					Refresh();
443 						// TODO: Refresh() is rather radical. Or rather its
444 						// implementation is. Ideally it would just compare the
445 						// currently added poses with what a new dir iterator
446 						// returns and remove/add poses as needed.
447 				}
448 			}
449 		}
450 
451 		if (!fIsRoot)
452 			return true;
453 	}
454 
455 	return _inherited::FSNotification(message);
456 }
457 
458 
459 void
460 VirtualDirectoryPoseView::_DispatchEntryCreatedOrRemovedMessage(int32 opcode,
461 	const node_ref& nodeRef, const entry_ref& entryRef, const char* path,
462 	bool dispatchToSuperClass)
463 {
464 	BMessage message(B_NODE_MONITOR);
465 	message.AddInt32("opcode", opcode);
466 	message.AddInt32("device", nodeRef.device);
467 	message.AddInt64("node", nodeRef.node);
468 	message.AddInt64("directory", entryRef.directory);
469 	message.AddString("name", entryRef.name);
470 	if (path != NULL && path[0] != '\0')
471 		message.AddString("path", path);
472 	bool result = dispatchToSuperClass
473 		? _inherited::FSNotification(&message)
474 		: FSNotification(&message);
475 	if (!result)
476 		pendingNodeMonitorCache.Add(&message);
477 }
478 
479 
480 bool
481 VirtualDirectoryPoseView::_GetEntry(const char* name, entry_ref& _ref,
482 	struct stat* _st)
483 {
484 	return VirtualDirectoryManager::GetEntry(fDirectoryPaths, name, &_ref, _st);
485 }
486 
487 
488 status_t
489 VirtualDirectoryPoseView::_UpdateDirectoryPaths()
490 {
491 	VirtualDirectoryManager* manager = VirtualDirectoryManager::Instance();
492 	Model* model = TargetModel();
493 	status_t error = manager->ResolveDirectoryPaths(*model->NodeRef(),
494 		*model->EntryRef(), fDirectoryPaths);
495 	if (error != B_OK)
496 		return error;
497 
498 	manager->GetDefinitionFileChangeTime(*model->NodeRef(), fFileChangeTime);
499 	return B_OK;
500 }
501 
502 } // namespace BPrivate
503