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
VirtualDirectoryPoseView(Model * model)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
~VirtualDirectoryPoseView()51 VirtualDirectoryPoseView::~VirtualDirectoryPoseView()
52 {
53 }
54
55
56 void
MessageReceived(BMessage * message)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 kDeleteSelection:
68 case kDuplicateSelection:
69 case kMoveSelectionToTrash:
70 case kNewEntryFromTemplate:
71 case kNewFolder:
72 break;
73
74 default:
75 _inherited::MessageReceived(message);
76 break;
77 }
78 }
79
80
81 void
AttachedToWindow()82 VirtualDirectoryPoseView::AttachedToWindow()
83 {
84 _inherited::AttachedToWindow();
85 AddFilter(new TPoseViewFilter(this));
86 }
87
88
89 void
RestoreState(AttributeStreamNode * node)90 VirtualDirectoryPoseView::RestoreState(AttributeStreamNode* node)
91 {
92 _inherited::RestoreState(node);
93 fViewState->SetViewMode(kListMode);
94 }
95
96
97 void
RestoreState(const BMessage & message)98 VirtualDirectoryPoseView::RestoreState(const BMessage& message)
99 {
100 _inherited::RestoreState(message);
101 fViewState->SetViewMode(kListMode);
102 }
103
104
105 void
SavePoseLocations(BRect * frameIfDesktop)106 VirtualDirectoryPoseView::SavePoseLocations(BRect* frameIfDesktop)
107 {
108 }
109
110
111 void
SetViewMode(uint32 newMode)112 VirtualDirectoryPoseView::SetViewMode(uint32 newMode)
113 {
114 }
115
116
117 EntryListBase*
InitDirentIterator(const entry_ref * ref)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
StartWatching()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
StopWatching()160 VirtualDirectoryPoseView::StopWatching()
161 {
162 BPathMonitor::StopWatching(this);
163 stop_watching(this);
164 }
165
166
167 bool
FSNotification(const BMessage * message)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
_EntryCreated(const BMessage * message)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
_EntryRemoved(const BMessage * message)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
_EntryMoved(const BMessage * message)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
_NodeStatChanged(const BMessage * message)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
_DispatchEntryCreatedOrRemovedMessage(int32 opcode,const node_ref & nodeRef,const entry_ref & entryRef,const char * path,bool dispatchToSuperClass)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
_GetEntry(const char * name,entry_ref & _ref,struct stat * _st)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
_UpdateDirectoryPaths()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