1 /*
2  * Copyright (C) 2015 Nicolas Bonnefon and other contributors
3  *
4  * This file is part of glogg.
5  *
6  * glogg is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * glogg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with glogg.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifndef WINWATCHTOWERDRIVER_H
21 #define WINWATCHTOWERDRIVER_H
22 
23 #include <atomic>
24 #include <thread>
25 #include <mutex>
26 #include <condition_variable>
27 #include <iterator>
28 #include <vector>
29 
30 #define WIN32_LEAN_AND_MEAN
31 #include <windows.h>
32 
33 // Utility class
34 
35 // Encapsulate a directory notification returned by Windows'
36 // ReadDirectoryChangesW.
37 class WinNotificationInfo {
38   public:
39     enum class Action { UNDEF,
40         ADDED,
41         REMOVED,
42         MODIFIED,
43         RENAMED_OLD_NAME,
44         RENAMED_NEW_NAME };
45 
WinNotificationInfo()46     WinNotificationInfo() { action_ = Action::UNDEF; }
WinNotificationInfo(Action action,std::wstring file_name)47     WinNotificationInfo( Action action, std::wstring file_name )
48     { action_ = action; file_name_ = file_name; }
49 
action()50     Action action() const { return action_; }
fileName()51     std::wstring fileName() const { return file_name_; }
52 
53   private:
54     Action action_;
55     std::wstring file_name_;
56 };
57 
58 class WinNotificationInfoList {
59   public:
60     WinNotificationInfoList( const char* buffer, size_t buffer_size );
61 
62     // Iterator
63     class iterator : std::iterator<std::input_iterator_tag, WinNotificationInfo> {
64       public:
iterator(WinNotificationInfoList * list,const char * position)65         iterator( WinNotificationInfoList* list, const char* position )
66         { list_ = list; position_ = position; }
67 
68         const WinNotificationInfo& operator*() const
69         { return list_->current_notification_; }
70 
71         const WinNotificationInfo* operator->() const
72         { return &( list_->current_notification_ ); }
73 
74         const WinNotificationInfo& operator++() {
75             position_ = list_->advanceToNext();
76             return list_->current_notification_;
77         }
78 
79         WinNotificationInfo operator++( int ) {
80             WinNotificationInfo tmp { list_->current_notification_ };
81             operator++();
82             return tmp;
83         }
84 
85         bool operator!=( const iterator& other ) const {
86             return ( list_ != other.list_ ) || ( position_ != other.position_ );
87         }
88 
89       private:
90         WinNotificationInfoList* list_;
91         const char* position_;
92     };
93 
begin()94     iterator begin() { return iterator( this, pointer_ ); }
end()95     iterator end() { return iterator( this, nullptr ); }
96 
97   private:
98     const char* advanceToNext();
99 
100     // Current notification (in the byte stream)
101     const char* pointer_;
102     // Next notification (in the byte stream)
103     const char* next_;
104     WinNotificationInfo current_notification_;
105 
106     const char* updateCurrentNotification( const char* new_position );
107 };
108 
109 template <typename Driver>
110 class ObservedFile;
111 template <typename Driver>
112 class ObservedFileList;
113 
114 class WinWatchTowerDriver {
115   public:
116     struct WinWatchedDirRecord {
WinWatchedDirRecordWinWatchedDirRecord117         WinWatchedDirRecord( const std::string& file_name )
118             : path_( file_name ) { }
119 
120         static const int READ_DIR_CHANGE_BUFFER_SIZE = 4096;
121 
122         std::string path_;
123         void* handle_ = nullptr;
124         static const unsigned long buffer_length_ = READ_DIR_CHANGE_BUFFER_SIZE;
125         char buffer_[buffer_length_];
126     };
127 
128     class FileId { };
129     class SymlinkId { };
130     class DirId {
131       public:
132         friend class WinWatchTowerDriver;
133 
DirId()134         DirId() {}
135         bool operator==( const DirId& other ) const
136         { return dir_record_ == other.dir_record_; }
valid()137         bool valid() const
138         { return ( dir_record_ != nullptr ); }
139       private:
140         std::shared_ptr<WinWatchedDirRecord> dir_record_;
141     };
142 
143     // On Windows, the token is the "last write" time
144     class FileChangeToken {
145       public:
FileChangeToken()146         FileChangeToken() {}
FileChangeToken(const std::string & file_name)147         FileChangeToken( const std::string& file_name )
148         { readFromFile( file_name ); }
149 
150         void readFromFile( const std::string& file_name );
151 
152         bool operator==( const FileChangeToken& other )
153         { return ( low_date_time_ == other.low_date_time_ ) &&
154             ( high_date_time_ == other.high_date_time_ ) &&
155             ( low_file_size_  == other.low_file_size_ ) &&
156             ( high_file_size_ == other.high_file_size_); }
157         bool operator!=( const FileChangeToken& other )
158         { return ! operator==( other ); }
159 
160       private:
161         DWORD low_date_time_  = 0;
162         DWORD high_date_time_ = 0;
163         DWORD low_file_size_  = 0;
164         DWORD high_file_size_ = 0;
165     };
166 
167     // Default constructor
168     WinWatchTowerDriver();
169     ~WinWatchTowerDriver();
170 
171     FileId addFile( const std::string& file_name );
172     SymlinkId addSymlink( const std::string& file_name );
173     DirId addDir( const std::string& file_name );
174 
175     void removeFile( const FileId& file_id );
176     void removeSymlink( const SymlinkId& symlink_id );
177     void removeDir( const DirId& dir_id );
178 
179     std::vector<ObservedFile<WinWatchTowerDriver>*> waitAndProcessEvents(
180             ObservedFileList<WinWatchTowerDriver>* list,
181             std::unique_lock<std::mutex>* lock,
182             std::vector<ObservedFile<WinWatchTowerDriver>*>* files_needing_readding,
183             int timout_ms );
184 
185     // Interrupt waitAndProcessEvents
186     void interruptWait();
187 
188   private:
189     // An action which will be picked up by the worker thread.
190     class Action {
191       public:
Action(std::function<void ()> function)192         Action( std::function<void()> function ) : function_ { function } {}
~Action()193         ~Action() {}
194 
operator()195         void operator()() { function_(); }
196 
197       private:
198         std::function<void()> function_;
199     };
200 
201     // Action
202     std::mutex action_mutex_;
203     std::condition_variable action_done_cv_;
204     std::unique_ptr<Action> scheduled_action_ = nullptr;
205 
206     // Win32 notification variables
207     HANDLE     hCompPort_;
208     OVERLAPPED overlapped_;
209     unsigned long buffer_length_;
210 
211     // List of directory records
212     // Accessed exclusively in the worker thread context
213     std::vector<std::weak_ptr<WinWatchedDirRecord>> dir_records_ { };
214 
215     // Private member functions
216     void serialisedAddDir(
217             const std::string& file_name,
218             DirId& dir_id );
219 };
220 
221 #endif
222