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