1 /*
2 * Copyright (C) by Markus Goetz <markus@woboq.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * for more details.
13 */
14 #include "config.h"
15
16 #include "folder.h"
17 #include "folderwatcher.h"
18 #include "folderwatcher_mac.h"
19
20
21 #include <cerrno>
22 #include <QDirIterator>
23 #include <QStringList>
24
25
26 namespace OCC {
27
FolderWatcherPrivate(FolderWatcher * p,const QString & path)28 FolderWatcherPrivate::FolderWatcherPrivate(FolderWatcher *p, const QString &path)
29 : _parent(p)
30 , _folder(path)
31 {
32 this->startWatching();
33 }
34
~FolderWatcherPrivate()35 FolderWatcherPrivate::~FolderWatcherPrivate()
36 {
37 FSEventStreamStop(_stream);
38 FSEventStreamInvalidate(_stream);
39 FSEventStreamRelease(_stream);
40 }
41
callback(ConstFSEventStreamRef streamRef,void * clientCallBackInfo,size_t numEvents,void * eventPathsVoid,const FSEventStreamEventFlags eventFlags[],const FSEventStreamEventId eventIds[])42 static void callback(
43 ConstFSEventStreamRef streamRef,
44 void *clientCallBackInfo,
45 size_t numEvents,
46 void *eventPathsVoid,
47 const FSEventStreamEventFlags eventFlags[],
48 const FSEventStreamEventId eventIds[])
49 {
50 Q_UNUSED(streamRef)
51 Q_UNUSED(eventFlags)
52 Q_UNUSED(eventIds)
53
54 const FSEventStreamEventFlags c_interestingFlags = kFSEventStreamEventFlagItemCreated // for new folder/file
55 | kFSEventStreamEventFlagItemRemoved // for rm
56 | kFSEventStreamEventFlagItemInodeMetaMod // for mtime change
57 | kFSEventStreamEventFlagItemRenamed // also coming for moves to trash in finder
58 | kFSEventStreamEventFlagItemModified; // for content change
59 //We ignore other flags, e.g. for owner change, xattr change, Finder label change etc
60
61 qCDebug(lcFolderWatcher) << "FolderWatcherPrivate::callback by OS X";
62
63 QStringList paths;
64 CFArrayRef eventPaths = (CFArrayRef)eventPathsVoid;
65 for (int i = 0; i < static_cast<int>(numEvents); ++i) {
66 CFStringRef path = reinterpret_cast<CFStringRef>(CFArrayGetValueAtIndex(eventPaths, i));
67
68 QString qstring;
69 CFIndex pathLength = CFStringGetLength(path);
70 qstring.resize(pathLength);
71 CFStringGetCharacters(path, CFRangeMake(0, pathLength), reinterpret_cast<UniChar *>(qstring.data()));
72 QString fn = qstring.normalized(QString::NormalizationForm_C);
73
74 if (!(eventFlags[i] & c_interestingFlags)) {
75 qCDebug(lcFolderWatcher) << "Ignoring non-content changes for" << fn << eventFlags[i];
76 continue;
77 }
78
79 paths.append(fn);
80 }
81
82 reinterpret_cast<FolderWatcherPrivate *>(clientCallBackInfo)->doNotifyParent(paths);
83 }
84
startWatching()85 void FolderWatcherPrivate::startWatching()
86 {
87 qCDebug(lcFolderWatcher) << "FolderWatcherPrivate::startWatching()" << _folder;
88 CFStringRef folderCF = CFStringCreateWithCharacters(0, reinterpret_cast<const UniChar *>(_folder.unicode()),
89 _folder.length());
90 CFArrayRef pathsToWatch = CFStringCreateArrayBySeparatingStrings(nullptr, folderCF, CFSTR(":"));
91
92 FSEventStreamContext ctx = { 0, this, nullptr, nullptr, nullptr };
93
94 _stream = FSEventStreamCreate(nullptr,
95 &callback,
96 &ctx,
97 pathsToWatch,
98 kFSEventStreamEventIdSinceNow,
99 0, // latency
100 kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagIgnoreSelf);
101
102 CFRelease(pathsToWatch);
103 CFRelease(folderCF);
104 FSEventStreamScheduleWithRunLoop(_stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
105 FSEventStreamStart(_stream);
106 }
107
addCoalescedPaths(const QStringList & paths) const108 QStringList FolderWatcherPrivate::addCoalescedPaths(const QStringList &paths) const
109 {
110 QStringList coalescedPaths;
111 for (const auto &eventPath : paths) {
112 if (QDir(eventPath).exists()) {
113 QDirIterator it(eventPath, QDir::AllDirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
114 while (it.hasNext()) {
115 auto subfolder = it.next();
116 if (!paths.contains(subfolder)) {
117 coalescedPaths.append(subfolder);
118 }
119 }
120 }
121 }
122 return (paths + coalescedPaths);
123 }
124
doNotifyParent(const QStringList & paths)125 void FolderWatcherPrivate::doNotifyParent(const QStringList &paths)
126 {
127 const QStringList totalPaths = addCoalescedPaths(paths);
128 _parent->changeDetected(totalPaths);
129 }
130
131
132 } // ns mirall
133