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