1 /*
2  * FileChangeEvent.hpp
3  *
4  * Copyright (C) 2021 by RStudio, PBC
5  *
6  * Unless you have received this program directly from RStudio pursuant
7  * to the terms of a commercial license agreement with RStudio, then
8  * this program is licensed to you under the terms of version 3 of the
9  * GNU Affero General Public License. This program is distributed WITHOUT
10  * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12  * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13  *
14  */
15 
16 #ifndef CORE_SYSTEM_FILE_CHANGE_EVENT_HPP
17 #define CORE_SYSTEM_FILE_CHANGE_EVENT_HPP
18 
19 #include <string>
20 #include <iostream>
21 #include <vector>
22 
23 #include <boost/function.hpp>
24 
25 #include <core/FileInfo.hpp>
26 
27 namespace rstudio {
28 namespace core {
29 
30 class Error;
31 
32 namespace system {
33 
34 // event struct
35 class FileChangeEvent
36 {
37 public:
38    // NOTE: skip 2 for compatibility with old clients (used to be FileRenamed)
39    enum Type
40    {
41       None = 0,
42       FileAdded = 1,
43       FileRemoved = 3,
44       FileModified = 4
45    };
46 
47 public:
FileChangeEvent(Type type,const core::FileInfo & fileInfo)48    FileChangeEvent(Type type, const core::FileInfo& fileInfo)
49       : type_(type), fileInfo_(fileInfo)
50    {
51    }
52 
53    // COPYING: via compiler
54 
operator =(const FileChangeEvent & rhs)55    FileChangeEvent& operator=(const FileChangeEvent& rhs)
56    {
57       if (&rhs != this)
58       {
59          type_ = rhs.type_;
60          fileInfo_ = rhs.fileInfo_;
61       }
62       return *this;
63    }
64 
65 public:
type() const66    Type type() const { return type_; }
fileInfo() const67    const FileInfo& fileInfo() const { return fileInfo_; }
68 
69 private:
70    Type type_;
71    core::FileInfo fileInfo_;
72 };
73 
operator <<(std::ostream & ostr,const FileChangeEvent & event)74 inline std::ostream& operator << (std::ostream& ostr,
75                                   const FileChangeEvent& event)
76 {
77    if (event.type() == FileChangeEvent::FileAdded)
78       ostr << "FileAdded: ";
79    else if (event.type() == FileChangeEvent::FileRemoved)
80       ostr << "FileRemoved: ";
81    else if (event.type() == FileChangeEvent::FileModified)
82       ostr << "FileModified: ";
83 
84    ostr << event.fileInfo();
85 
86    if (event.fileInfo().isDirectory())
87       ostr << " (directory)";
88 
89    return ostr;
90 }
91 
92 template<typename PreviousIterator, typename CurrentIterator>
collectFileChangeEvents(PreviousIterator prevBegin,PreviousIterator prevEnd,CurrentIterator currBegin,CurrentIterator currEnd,const boost::function<bool (const FileInfo &)> & filter,std::vector<FileChangeEvent> * pEvents)93 void collectFileChangeEvents(PreviousIterator prevBegin,
94                              PreviousIterator prevEnd,
95                              CurrentIterator currBegin,
96                              CurrentIterator currEnd,
97                              const boost::function<bool(const FileInfo&)>& filter,
98                              std::vector<FileChangeEvent>* pEvents)
99 {
100    // sort the ranges
101    std::vector<FileInfo> prev;
102    std::copy(prevBegin, prevEnd, std::back_inserter(prev));
103    std::sort(prev.begin(), prev.end(), fileInfoPathLessThan);
104    std::vector<FileInfo> curr;
105    std::copy(currBegin, currEnd, std::back_inserter(curr));
106    std::sort(curr.begin(), curr.end(), fileInfoPathLessThan);
107 
108    // initalize the iterators
109    std::vector<FileInfo>::iterator prevIt = prev.begin();
110    std::vector<FileInfo>::iterator currIt = curr.begin();
111 
112    FileInfo noFile;
113    while (prevIt != prev.end() || currIt != curr.end())
114    {
115       const FileInfo& prevFile = prevIt != prev.end() ? *prevIt : noFile;
116       const FileInfo& currFile = currIt != curr.end() ? *currIt : noFile;
117 
118       int comp;
119       if (prevFile.empty())
120          comp = 1;
121       else if (currFile.empty())
122          comp = -1;
123       else
124          comp = fileInfoPathCompare(prevFile, currFile);
125 
126       if (comp == 0)
127       {
128          if (currFile.lastWriteTime() != prevFile.lastWriteTime())
129          {
130             if (!filter || filter(currFile))
131             {
132                pEvents->push_back(FileChangeEvent(FileChangeEvent::FileModified,
133                                                   currFile));
134             }
135          }
136          prevIt++;
137          currIt++;
138       }
139       else if (comp < 0)
140       {
141          if (!filter || filter(prevFile))
142          {
143             pEvents->push_back(FileChangeEvent(FileChangeEvent::FileRemoved,
144                                                prevFile));
145          }
146          prevIt++;
147       }
148       else // comp > 1
149       {
150          if (!filter || filter(currFile))
151          {
152             pEvents->push_back(FileChangeEvent(FileChangeEvent::FileAdded,
153                                                currFile));
154          }
155          currIt++;
156       }
157    }
158 }
159 
160 template<typename PreviousIterator, typename CurrentIterator>
collectFileChangeEvents(PreviousIterator prevBegin,PreviousIterator prevEnd,CurrentIterator currBegin,CurrentIterator currEnd,std::vector<FileChangeEvent> * pEvents)161 void collectFileChangeEvents(PreviousIterator prevBegin,
162                              PreviousIterator prevEnd,
163                              CurrentIterator currBegin,
164                              CurrentIterator currEnd,
165                              std::vector<FileChangeEvent>* pEvents)
166 {
167    collectFileChangeEvents(prevBegin,
168                            prevEnd,
169                            currBegin,
170                            currEnd,
171                            boost::function<bool(const FileInfo&)>(),
172                            pEvents);
173 }
174 
175 } // namespace system
176 } // namespace core
177 } // namespace rstudio
178 
179 #endif // CORE_SYSTEM_FILE_CHANGE_EVENT_HPP
180 
181 
182