xref: /glogg/src/winwatchtowerdriver.cpp (revision dc1c9514f2d1e1bf7380b863207bc3ccb0152652) !
1f09fa651SNicolas Bonnefon #include "winwatchtowerdriver.h"
2f09fa651SNicolas Bonnefon 
3f09fa651SNicolas Bonnefon #define WIN32_LEAN_AND_MEAN
4f09fa651SNicolas Bonnefon #include <windows.h>
5f09fa651SNicolas Bonnefon #include <winbase.h>
6f09fa651SNicolas Bonnefon 
7f09fa651SNicolas Bonnefon #include <map>
8f09fa651SNicolas Bonnefon 
9f09fa651SNicolas Bonnefon #include "watchtowerlist.h"
10f09fa651SNicolas Bonnefon #include "utils.h"
11f09fa651SNicolas Bonnefon #include "log.h"
12f09fa651SNicolas Bonnefon 
13f09fa651SNicolas Bonnefon namespace {
14f09fa651SNicolas Bonnefon     std::string shortstringize( const std::wstring& long_string );
15f09fa651SNicolas Bonnefon     std::wstring longstringize( const std::string& short_string );
16f09fa651SNicolas Bonnefon };
17f09fa651SNicolas Bonnefon 
18f09fa651SNicolas Bonnefon // Utility classes
19f09fa651SNicolas Bonnefon 
20f09fa651SNicolas Bonnefon WinNotificationInfoList::WinNotificationInfoList( const char* buffer, size_t buffer_size )
21f09fa651SNicolas Bonnefon {
22f09fa651SNicolas Bonnefon     pointer_ = buffer;
23f09fa651SNicolas Bonnefon     next_ = updateCurrentNotification( pointer_ );
24f09fa651SNicolas Bonnefon }
25f09fa651SNicolas Bonnefon 
26f09fa651SNicolas Bonnefon const char* WinNotificationInfoList::updateCurrentNotification(
27f09fa651SNicolas Bonnefon         const char* new_position )
28f09fa651SNicolas Bonnefon {
29f09fa651SNicolas Bonnefon     using Action = WinNotificationInfo::Action;
30f09fa651SNicolas Bonnefon 
31f09fa651SNicolas Bonnefon     static const std::map<uint16_t, Action> int_to_action = {
32f09fa651SNicolas Bonnefon         { FILE_ACTION_ADDED, Action::ADDED },
33f09fa651SNicolas Bonnefon         { FILE_ACTION_REMOVED, Action::REMOVED },
34f09fa651SNicolas Bonnefon         { FILE_ACTION_MODIFIED, Action::MODIFIED },
35f09fa651SNicolas Bonnefon         { FILE_ACTION_RENAMED_OLD_NAME, Action::RENAMED_OLD_NAME },
36f09fa651SNicolas Bonnefon         { FILE_ACTION_RENAMED_NEW_NAME, Action::RENAMED_NEW_NAME },
37f09fa651SNicolas Bonnefon     };
38f09fa651SNicolas Bonnefon 
39f09fa651SNicolas Bonnefon     uint32_t next_offset = *( reinterpret_cast<const uint32_t*>( new_position ) );
40f09fa651SNicolas Bonnefon     uint32_t action      = *( reinterpret_cast<const uint32_t*>( new_position ) + 1 );
41f09fa651SNicolas Bonnefon     uint32_t length      = *( reinterpret_cast<const uint32_t*>( new_position ) + 2 );
42f09fa651SNicolas Bonnefon 
43f09fa651SNicolas Bonnefon     const std::wstring file_name = { reinterpret_cast<const wchar_t*>( new_position + 12 ), length / 2 };
44f09fa651SNicolas Bonnefon 
45f09fa651SNicolas Bonnefon     LOG(logDEBUG) << "Next: " << next_offset;
46f09fa651SNicolas Bonnefon     LOG(logDEBUG) << "Action: " << action;
47f09fa651SNicolas Bonnefon     LOG(logDEBUG) << "Length: " << length;
48f09fa651SNicolas Bonnefon 
49f09fa651SNicolas Bonnefon     current_notification_ = WinNotificationInfo( int_to_action.at( action ), file_name );
50f09fa651SNicolas Bonnefon 
51f09fa651SNicolas Bonnefon     return ( next_offset == 0 ) ? nullptr : new_position + next_offset;
52f09fa651SNicolas Bonnefon }
53f09fa651SNicolas Bonnefon 
54f09fa651SNicolas Bonnefon const char* WinNotificationInfoList::advanceToNext()
55f09fa651SNicolas Bonnefon {
56f09fa651SNicolas Bonnefon     pointer_ = next_;
57f09fa651SNicolas Bonnefon     if ( pointer_ )
58f09fa651SNicolas Bonnefon         next_ = updateCurrentNotification( pointer_ );
59f09fa651SNicolas Bonnefon 
60f09fa651SNicolas Bonnefon     return pointer_;
61f09fa651SNicolas Bonnefon }
62f09fa651SNicolas Bonnefon 
63f09fa651SNicolas Bonnefon // WinWatchTowerDriver
64f09fa651SNicolas Bonnefon 
65f09fa651SNicolas Bonnefon WinWatchTowerDriver::WinWatchTowerDriver()
66f09fa651SNicolas Bonnefon {
67f09fa651SNicolas Bonnefon     hCompPort_ = CreateIoCompletionPort( INVALID_HANDLE_VALUE,
68f09fa651SNicolas Bonnefon             NULL,
69f09fa651SNicolas Bonnefon             0x0,
70f09fa651SNicolas Bonnefon             0);
71f09fa651SNicolas Bonnefon }
72f09fa651SNicolas Bonnefon 
73f09fa651SNicolas Bonnefon WinWatchTowerDriver::~WinWatchTowerDriver()
74f09fa651SNicolas Bonnefon {
75f09fa651SNicolas Bonnefon }
76f09fa651SNicolas Bonnefon 
77f09fa651SNicolas Bonnefon WinWatchTowerDriver::FileId WinWatchTowerDriver::addFile(
78f09fa651SNicolas Bonnefon         const std::string& file_name )
79f09fa651SNicolas Bonnefon {
80f09fa651SNicolas Bonnefon     // Nothing for Windows
81f09fa651SNicolas Bonnefon     return { };
82f09fa651SNicolas Bonnefon }
83f09fa651SNicolas Bonnefon 
84f09fa651SNicolas Bonnefon WinWatchTowerDriver::SymlinkId WinWatchTowerDriver::addSymlink(
85f09fa651SNicolas Bonnefon         const std::string& file_name )
86f09fa651SNicolas Bonnefon {
87f09fa651SNicolas Bonnefon     // Nothing for Windows
88f09fa651SNicolas Bonnefon     return { };
89f09fa651SNicolas Bonnefon }
90f09fa651SNicolas Bonnefon 
91f09fa651SNicolas Bonnefon // This implementation is blocking, i.e. it will wait until the file
92f09fa651SNicolas Bonnefon // is effectively loaded in the watchtower thread.
93f09fa651SNicolas Bonnefon WinWatchTowerDriver::DirId WinWatchTowerDriver::addDir(
94f09fa651SNicolas Bonnefon         const std::string& file_name )
95f09fa651SNicolas Bonnefon {
96f09fa651SNicolas Bonnefon     DirId dir_id { };
97f09fa651SNicolas Bonnefon 
98f09fa651SNicolas Bonnefon     // Add will be done in the watchtower thread
99f09fa651SNicolas Bonnefon     {
1003104b268SNicolas Bonnefon         /*
101f09fa651SNicolas Bonnefon         std::lock_guard<std::mutex> lk( action_mutex_ );
102f09fa651SNicolas Bonnefon         scheduled_action_ = std::make_unique<Action>( [this, file_name, &dir_id] {
103f09fa651SNicolas Bonnefon             serialisedAddDir( file_name, dir_id );
104f09fa651SNicolas Bonnefon         } );
1053104b268SNicolas Bonnefon         */
1063104b268SNicolas Bonnefon         serialisedAddDir( file_name, dir_id );
107f09fa651SNicolas Bonnefon     }
108f09fa651SNicolas Bonnefon 
109f09fa651SNicolas Bonnefon     // Poke the thread
110f09fa651SNicolas Bonnefon     PostQueuedCompletionStatus( hCompPort_, 0, 0, NULL );
111f09fa651SNicolas Bonnefon 
112f09fa651SNicolas Bonnefon     // Wait for the add task to be completed
113f09fa651SNicolas Bonnefon     {
1143104b268SNicolas Bonnefon         /*
115f09fa651SNicolas Bonnefon         std::unique_lock<std::mutex> lk( action_mutex_ );
116f09fa651SNicolas Bonnefon         action_done_cv_.wait( lk,
117f09fa651SNicolas Bonnefon                 [this]{ return ( scheduled_action_ == nullptr ); } );
1183104b268SNicolas Bonnefon                 */
119f09fa651SNicolas Bonnefon     }
120f09fa651SNicolas Bonnefon 
121*dc1c9514SNicolas Bonnefon     LOG(logDEBUG) << "addDir returned " << dir_id.dir_record_;
122f09fa651SNicolas Bonnefon 
123f09fa651SNicolas Bonnefon     return dir_id;
124f09fa651SNicolas Bonnefon }
125f09fa651SNicolas Bonnefon 
126f09fa651SNicolas Bonnefon 
127f09fa651SNicolas Bonnefon void WinWatchTowerDriver::removeFile(
1288b11848fSNicolas Bonnefon         const WinWatchTowerDriver::FileId& )
129f09fa651SNicolas Bonnefon {
130f09fa651SNicolas Bonnefon }
131f09fa651SNicolas Bonnefon 
1328b11848fSNicolas Bonnefon void WinWatchTowerDriver::removeSymlink( const SymlinkId& )
133f09fa651SNicolas Bonnefon {
134f09fa651SNicolas Bonnefon }
135f09fa651SNicolas Bonnefon 
1363104b268SNicolas Bonnefon void WinWatchTowerDriver::removeDir( const DirId& dir_id )
1373104b268SNicolas Bonnefon {
138*dc1c9514SNicolas Bonnefon     LOG(logDEBUG) << "Entering driver::removeDir";
1398b11848fSNicolas Bonnefon     if ( dir_id.dir_record_ ) {
1403104b268SNicolas Bonnefon         void* handle = dir_id.dir_record_->handle_;
1413104b268SNicolas Bonnefon 
1423104b268SNicolas Bonnefon         LOG(logDEBUG) << "WinWatchTowerDriver::removeDir handle=" << std::hex << handle;
1433104b268SNicolas Bonnefon 
1443104b268SNicolas Bonnefon         CloseHandle( handle );
1453104b268SNicolas Bonnefon     }
1468b11848fSNicolas Bonnefon     else {
1478b11848fSNicolas Bonnefon         /* Happens when an error occured when creating the dir_record_ */
1488b11848fSNicolas Bonnefon     }
1498b11848fSNicolas Bonnefon }
1503104b268SNicolas Bonnefon 
151f09fa651SNicolas Bonnefon //
152f09fa651SNicolas Bonnefon // Private functions
153f09fa651SNicolas Bonnefon //
154f09fa651SNicolas Bonnefon 
155f09fa651SNicolas Bonnefon // Add a file (run in the context of the WatchTower thread)
156f09fa651SNicolas Bonnefon void WinWatchTowerDriver::serialisedAddDir(
157f09fa651SNicolas Bonnefon         const std::string& dir_name,
158f09fa651SNicolas Bonnefon         DirId& dir_id )
159f09fa651SNicolas Bonnefon {
160*dc1c9514SNicolas Bonnefon     bool inserted = false;
161f09fa651SNicolas Bonnefon     auto dir_record = std::make_shared<WinWatchedDirRecord>( dir_name );
162*dc1c9514SNicolas Bonnefon     // The index we will be inserting this record (if success), plus 1 (to avoid
163*dc1c9514SNicolas Bonnefon     // 0 which is used as a magic value)
164*dc1c9514SNicolas Bonnefon     unsigned int index_record = dir_records_.size() + 1;
165f09fa651SNicolas Bonnefon 
166f09fa651SNicolas Bonnefon     LOG(logDEBUG) << "Adding dir for: " << dir_name;
167f09fa651SNicolas Bonnefon 
168f09fa651SNicolas Bonnefon     // Open the directory
169f09fa651SNicolas Bonnefon     HANDLE hDir = CreateFile(
170f09fa651SNicolas Bonnefon #ifdef UNICODE
171f09fa651SNicolas Bonnefon             longstringize( dir_name ).c_str(),
172f09fa651SNicolas Bonnefon #else
173f09fa651SNicolas Bonnefon             ( dir_name ).c_str(),
174f09fa651SNicolas Bonnefon #endif
175f09fa651SNicolas Bonnefon             FILE_LIST_DIRECTORY,
176f09fa651SNicolas Bonnefon             FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
177f09fa651SNicolas Bonnefon             NULL,
178f09fa651SNicolas Bonnefon             OPEN_EXISTING,
179f09fa651SNicolas Bonnefon             FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
180f09fa651SNicolas Bonnefon             NULL );
181f09fa651SNicolas Bonnefon 
1823104b268SNicolas Bonnefon     if ( hDir == INVALID_HANDLE_VALUE ) {
1833104b268SNicolas Bonnefon         LOG(logERROR) << "CreateFile failed for dir " << dir_name;
1843104b268SNicolas Bonnefon     }
185*dc1c9514SNicolas Bonnefon     else {
186f09fa651SNicolas Bonnefon         dir_record->handle_ = hDir;
187f09fa651SNicolas Bonnefon 
188f09fa651SNicolas Bonnefon         //create a IO completion port/or associate this key with
189f09fa651SNicolas Bonnefon         //the existing IO completion port
190f09fa651SNicolas Bonnefon         hCompPort_ = CreateIoCompletionPort( hDir,
191f09fa651SNicolas Bonnefon                 hCompPort_, //if m_hCompPort is NULL, hDir is associated with a NEW completion port,
192f09fa651SNicolas Bonnefon                 //if m_hCompPort is NON-NULL, hDir is associated with the existing completion port that the handle m_hCompPort references
193f09fa651SNicolas Bonnefon                 // We use the index (plus 1) of the weak_ptr as a key
194f09fa651SNicolas Bonnefon                 index_record,
195f09fa651SNicolas Bonnefon                 0 );
196f09fa651SNicolas Bonnefon 
197f09fa651SNicolas Bonnefon         LOG(logDEBUG) << "Weak ptr address stored: " << index_record;
198f09fa651SNicolas Bonnefon 
199f09fa651SNicolas Bonnefon         memset( &overlapped_, 0, sizeof overlapped_ );
200f09fa651SNicolas Bonnefon 
201*dc1c9514SNicolas Bonnefon         inserted = ReadDirectoryChangesW( hDir,
202f09fa651SNicolas Bonnefon                 dir_record->buffer_,
203f09fa651SNicolas Bonnefon                 dir_record->buffer_length_,
204f09fa651SNicolas Bonnefon                 false,
205f09fa651SNicolas Bonnefon                 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
206f09fa651SNicolas Bonnefon                 &buffer_length_, // not set when using asynchronous mechanisms...
207f09fa651SNicolas Bonnefon                 &overlapped_,
208f09fa651SNicolas Bonnefon                 NULL );          // no completion routine
209f09fa651SNicolas Bonnefon 
210*dc1c9514SNicolas Bonnefon         if ( ! inserted ) {
2113104b268SNicolas Bonnefon             LOG(logERROR) << "ReadDirectoryChangesW failed (" << GetLastError() << ")";
212*dc1c9514SNicolas Bonnefon             CloseHandle( hDir );
2133104b268SNicolas Bonnefon         }
2148b11848fSNicolas Bonnefon         else {
215f09fa651SNicolas Bonnefon             dir_id.dir_record_ = dir_record;
216f09fa651SNicolas Bonnefon         }
2178b11848fSNicolas Bonnefon     }
218f09fa651SNicolas Bonnefon 
219*dc1c9514SNicolas Bonnefon     if ( inserted ) {
220*dc1c9514SNicolas Bonnefon         dir_records_.push_back( std::weak_ptr<WinWatchedDirRecord>( dir_record ) );
221*dc1c9514SNicolas Bonnefon     }
222*dc1c9514SNicolas Bonnefon }
223*dc1c9514SNicolas Bonnefon 
224f09fa651SNicolas Bonnefon std::vector<ObservedFile<WinWatchTowerDriver>*> WinWatchTowerDriver::waitAndProcessEvents(
225f09fa651SNicolas Bonnefon         ObservedFileList<WinWatchTowerDriver>* list,
22691f7c705SNicolas Bonnefon         std::unique_lock<std::mutex>* lock,
22791f7c705SNicolas Bonnefon         std::vector<ObservedFile<WinWatchTowerDriver>*>* /* not needed in WinWatchTowerDriver */ )
228f09fa651SNicolas Bonnefon {
229f09fa651SNicolas Bonnefon     std::vector<ObservedFile<WinWatchTowerDriver>*> files_to_notify { };
230f09fa651SNicolas Bonnefon 
231f09fa651SNicolas Bonnefon     unsigned long long key = 0;
232f09fa651SNicolas Bonnefon     DWORD num_bytes = 0;
233f09fa651SNicolas Bonnefon     LPOVERLAPPED lpOverlapped = 0;
234f09fa651SNicolas Bonnefon 
2353104b268SNicolas Bonnefon     lock->unlock();
236*dc1c9514SNicolas Bonnefon     LOG(logDEBUG) << "waitAndProcessEvents now blocking...";
237f09fa651SNicolas Bonnefon     BOOL status = GetQueuedCompletionStatus( hCompPort_,
238f09fa651SNicolas Bonnefon             &num_bytes,
239f09fa651SNicolas Bonnefon             &key,
240f09fa651SNicolas Bonnefon             &lpOverlapped,
241f09fa651SNicolas Bonnefon             INFINITE );
2423104b268SNicolas Bonnefon     lock->lock();
243f09fa651SNicolas Bonnefon 
2443104b268SNicolas Bonnefon     LOG(logDEBUG) << "Event (" << status << ") key: " << std::hex << key;
245f09fa651SNicolas Bonnefon 
246f09fa651SNicolas Bonnefon     if ( key ) {
247f09fa651SNicolas Bonnefon         // Extract the dir from the completion key
248f09fa651SNicolas Bonnefon         auto dir_record_ptr = dir_records_[key - 1];
249f09fa651SNicolas Bonnefon         LOG(logDEBUG) << "use_count = " << dir_record_ptr.use_count();
250f09fa651SNicolas Bonnefon 
251f09fa651SNicolas Bonnefon         if ( std::shared_ptr<WinWatchedDirRecord> dir_record = dir_record_ptr.lock() )
252f09fa651SNicolas Bonnefon         {
253f09fa651SNicolas Bonnefon             LOG(logDEBUG) << "Got event for dir " << dir_record.get();
254f09fa651SNicolas Bonnefon 
255f09fa651SNicolas Bonnefon             WinNotificationInfoList notification_info(
256f09fa651SNicolas Bonnefon                     dir_record->buffer_,
257f09fa651SNicolas Bonnefon                     dir_record->buffer_length_ );
258f09fa651SNicolas Bonnefon 
259f09fa651SNicolas Bonnefon             for ( auto notification : notification_info ) {
260f09fa651SNicolas Bonnefon                 std::string file_path = dir_record->path_ + shortstringize( notification.fileName() );
261f09fa651SNicolas Bonnefon                 LOG(logDEBUG) << "File is " << file_path;
262f09fa651SNicolas Bonnefon                 auto file = list->searchByName( file_path );
263f09fa651SNicolas Bonnefon 
264f09fa651SNicolas Bonnefon                 if ( file )
265f09fa651SNicolas Bonnefon                 {
266f09fa651SNicolas Bonnefon                     files_to_notify.push_back( file );
267f09fa651SNicolas Bonnefon                 }
268f09fa651SNicolas Bonnefon             }
269f09fa651SNicolas Bonnefon 
270f09fa651SNicolas Bonnefon             // Re-listen for changes
271f09fa651SNicolas Bonnefon             status = ReadDirectoryChangesW(
272f09fa651SNicolas Bonnefon                     dir_record->handle_,
273f09fa651SNicolas Bonnefon                     dir_record->buffer_,
274f09fa651SNicolas Bonnefon                     dir_record->buffer_length_,
275f09fa651SNicolas Bonnefon                     false,
276f09fa651SNicolas Bonnefon                     FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
277f09fa651SNicolas Bonnefon                     &buffer_length_,// not set when using asynchronous mechanisms...
278f09fa651SNicolas Bonnefon                     &overlapped_,
279f09fa651SNicolas Bonnefon                     NULL );          // no completion routine
280f09fa651SNicolas Bonnefon         }
281f09fa651SNicolas Bonnefon         else {
282f09fa651SNicolas Bonnefon             LOG(logWARNING) << "Looks like our dir_record disappeared!";
283f09fa651SNicolas Bonnefon         }
284f09fa651SNicolas Bonnefon     }
285f09fa651SNicolas Bonnefon     else {
286f09fa651SNicolas Bonnefon         LOG(logDEBUG) << "Signaled";
287f09fa651SNicolas Bonnefon     }
288f09fa651SNicolas Bonnefon 
289f09fa651SNicolas Bonnefon     {
290f09fa651SNicolas Bonnefon         std::lock_guard<std::mutex> lk( action_mutex_ );
291f09fa651SNicolas Bonnefon         if ( scheduled_action_ ) {
292f09fa651SNicolas Bonnefon             (*scheduled_action_)();
293f09fa651SNicolas Bonnefon             scheduled_action_ = nullptr;
294f09fa651SNicolas Bonnefon             action_done_cv_.notify_all();
295f09fa651SNicolas Bonnefon         }
296f09fa651SNicolas Bonnefon     }
297f09fa651SNicolas Bonnefon 
298f09fa651SNicolas Bonnefon     /*
299f09fa651SNicolas Bonnefon     // Just in case someone is waiting for an action to complete
300f09fa651SNicolas Bonnefon     std::lock_guard<std::mutex> lk( action_mutex_ );
301f09fa651SNicolas Bonnefon     scheduled_action_ = nullptr;
302f09fa651SNicolas Bonnefon     action_done_cv_.notify_all();
303f09fa651SNicolas Bonnefon     */
304f09fa651SNicolas Bonnefon     return files_to_notify;
305f09fa651SNicolas Bonnefon }
306f09fa651SNicolas Bonnefon 
307f09fa651SNicolas Bonnefon void WinWatchTowerDriver::interruptWait()
308f09fa651SNicolas Bonnefon {
309*dc1c9514SNicolas Bonnefon     LOG(logDEBUG) << "Driver::interruptWait()";
310f09fa651SNicolas Bonnefon     PostQueuedCompletionStatus( hCompPort_, 0, 0, NULL );
311f09fa651SNicolas Bonnefon }
312f09fa651SNicolas Bonnefon 
313f09fa651SNicolas Bonnefon namespace {
314f09fa651SNicolas Bonnefon     std::string shortstringize( const std::wstring& long_string )
315f09fa651SNicolas Bonnefon     {
316f09fa651SNicolas Bonnefon         std::string short_result {};
317f09fa651SNicolas Bonnefon 
318f09fa651SNicolas Bonnefon         for ( wchar_t c : long_string ) {
319f09fa651SNicolas Bonnefon             // FIXME: that does not work for non ASCII char!!
320f09fa651SNicolas Bonnefon             char short_c = static_cast<char>( c & 0x00FF );
321f09fa651SNicolas Bonnefon             short_result += short_c;
322f09fa651SNicolas Bonnefon         }
323f09fa651SNicolas Bonnefon 
324f09fa651SNicolas Bonnefon         return short_result;
325f09fa651SNicolas Bonnefon     }
326f09fa651SNicolas Bonnefon 
327f09fa651SNicolas Bonnefon     std::wstring longstringize( const std::string& short_string )
328f09fa651SNicolas Bonnefon     {
329f09fa651SNicolas Bonnefon         std::wstring long_result {};
330f09fa651SNicolas Bonnefon 
331f09fa651SNicolas Bonnefon         for ( char c : short_string ) {
332f09fa651SNicolas Bonnefon             wchar_t long_c = static_cast<wchar_t>( c );
333f09fa651SNicolas Bonnefon             long_result += long_c;
334f09fa651SNicolas Bonnefon         }
335f09fa651SNicolas Bonnefon 
336f09fa651SNicolas Bonnefon         return long_result;
337f09fa651SNicolas Bonnefon     }
338f09fa651SNicolas Bonnefon };
339