xref: /haiku/src/kits/tracker/VirtualDirectoryPoseView.cpp (revision 106388ddbfdd00f4409c86bd3fe8d581bae532ec)
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 	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 
81 void
82 VirtualDirectoryPoseView::AttachedToWindow()
83 {
84 	_inherited::AttachedToWindow();
85 	AddFilter(new TPoseViewFilter(this));
86 }
87 
88 
89 void
90 VirtualDirectoryPoseView::RestoreState(AttributeStreamNode* node)
91 {
92 	_inherited::RestoreState(node);
93 	fViewState->SetViewMode(kListMode);
94 }
95 
96 
97 void
98 VirtualDirectoryPoseView::RestoreState(const BMessage& message)
99 {
100 	_inherited::RestoreState(message);
101 	fViewState->SetViewMode(kListMode);
102 }
103 
104 
105 void
106 VirtualDirectoryPoseView::SavePoseLocations(BRect* frameIfDesktop)
107 {
108 }
109 
110 
111 void
112 VirtualDirectoryPoseView::SetViewMode(uint32 newMode)
113 {
114 }
115 
116 
117 EntryListBase*
118 VirtualDirectoryPoseView::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 
139 void
140 VirtualDirectoryPoseView::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 
159 void
160 VirtualDirectoryPoseView::StopWatching()
161 {
162 	BPathMonitor::StopWatching(this);
163 	stop_watching(this);
164 }
165 
166 
167 bool
168 VirtualDirectoryPoseView::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 
189 bool
190 VirtualDirectoryPoseView::_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 
281 bool
282 VirtualDirectoryPoseView::_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 
385 bool
386 VirtualDirectoryPoseView::_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 
417 bool
418 VirtualDirectoryPoseView::_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 
462 void
463 VirtualDirectoryPoseView::_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 
483 bool
484 VirtualDirectoryPoseView::_GetEntry(const char* name, entry_ref& _ref,
485 	struct stat* _st)
486 {
487 	return VirtualDirectoryManager::GetEntry(fDirectoryPaths, name, &_ref, _st);
488 }
489 
490 
491 status_t
492 VirtualDirectoryPoseView::_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