1/*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 2010-2016, The OpenClonk Team and contributors
5 *
6 * Distributed under the terms of the ISC license; see accompanying file
7 * "COPYING" for details.
8 *
9 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
10 * See accompanying file "TRADEMARK" for details.
11 *
12 * To redistribute this file separately, substitute the full license texts
13 * for the above references.
14 */
15
16#ifdef __APPLE__
17
18#include "C4Include.h"
19#include "platform/C4FileMonitor.h"
20
21#include "game/C4Application.h"
22
23#import <Foundation/Foundation.h>
24
25// Implementation using FSEvents
26
27C4FileMonitor::C4FileMonitor(ChangeNotify pCallback): fStarted(false), pCallback(pCallback)
28{
29	eventStream = NULL;
30	context.version = 0;
31	context.info = this;
32	context.retain = NULL;
33	context.release = NULL;
34	context.copyDescription = NULL;
35	setObjectiveCObject([NSMutableArray arrayWithCapacity:10]);
36}
37
38C4FileMonitor::~C4FileMonitor()
39{
40	if (fStarted)
41		StopStream();
42}
43
44static void FSEvents_Callback(
45    ConstFSEventStreamRef streamRef,
46    void *clientCallBackInfo,
47    size_t numEvents,
48    void *eventPaths,
49    const FSEventStreamEventFlags eventFlags[],
50    const FSEventStreamEventId eventIds[])
51{
52	// FSEvents only tells us about directories in which some files were modified
53	char** paths = (char**)eventPaths;
54	C4FileMonitor* mon = (C4FileMonitor*)clientCallBackInfo;
55	for (unsigned int i = 0; i < numEvents; i++)
56	{
57		NSString* dir = [NSString stringWithUTF8String:paths[i]];
58		NSArray* filesInDir = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dir error:NULL];
59		for (NSString* str in filesInDir)
60		{
61			NSString* fullPath = [dir stringByAppendingPathComponent:str];
62			NSDictionary* attribs = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:NULL];
63			NSDate* modified = [attribs fileModificationDate];
64			if (modified && -[modified timeIntervalSinceNow] <= 3.0)
65				mon->OnThreadEvent(Ev_FileChange, (void*)[fullPath UTF8String]);
66		}
67	}
68}
69
70void C4FileMonitor::StartStream()
71{
72	eventStream = FSEventStreamCreate(kCFAllocatorDefault, &FSEvents_Callback, &context, (__bridge CFArrayRef)objectiveCObject<NSMutableArray>(), kFSEventStreamEventIdSinceNow, 0.3,
73		kFSEventStreamCreateFlagNone);
74	FSEventStreamScheduleWithRunLoop(eventStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
75	FSEventStreamStart(eventStream);
76}
77
78void C4FileMonitor::StopStream()
79{
80	if (fStarted)
81	{
82		fStarted = false;
83		FSEventStreamStop(eventStream);
84		FSEventStreamInvalidate(eventStream);
85		FSEventStreamRelease(eventStream);
86	}
87}
88
89void C4FileMonitor::StartMonitoring()
90{
91	StartStream();
92	fStarted = true;
93}
94
95void C4FileMonitor::AddDirectory(const char *szDir)
96{
97	NSString* path = [NSString stringWithUTF8String:szDir];
98	NSString* fullPath = [path characterAtIndex:0] == '/'
99		? path
100		: [NSString stringWithFormat:@"%@/%@", [[NSFileManager defaultManager] currentDirectoryPath], path];
101	[objectiveCObject<NSMutableArray>() addObject:fullPath];
102}
103
104void C4FileMonitor::OnThreadEvent(C4InteractiveEventType eEvent, void* pEventData)
105{
106	if (eEvent != Ev_FileChange) return;
107	pCallback((const char *)pEventData, 0);
108}
109
110void C4FileMonitor::GetFDs(std::vector<struct pollfd> & FDs) { }
111
112bool C4FileMonitor::Execute(int iTimeout, pollfd *) { return true; }
113
114#endif
115