Skip to content

Commit

Permalink
Monitor system timezone changes
Browse files Browse the repository at this point in the history
  • Loading branch information
jeroenvollenbrock committed Apr 15, 2018
1 parent c2bf66f commit b7cd999
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,2 +1,3 @@
.nyc_output
build
node_modules
1 change: 1 addition & 0 deletions binding.gyp
Expand Up @@ -5,6 +5,7 @@
"<!(node -e \"require('nan')\")"
],
"sources": [
"src/monitor.cc",
"src/binding.cc"
]
}]
Expand Down
4 changes: 2 additions & 2 deletions index.js
@@ -1,5 +1,5 @@
'use strict'

const bindings = require('bindings')('resetdatecache')
var bindings = require('bindings')('resetdatecache');

module.exports = bindings.reset
module.exports = bindings.reset
13 changes: 12 additions & 1 deletion src/binding.cc
Expand Up @@ -3,6 +3,8 @@
#include <v8.h>
#include <time.h>

#include "monitor.h"

using v8::Date;
using v8::FunctionTemplate;
using v8::Isolate;
Expand All @@ -11,7 +13,11 @@ using Nan::GetFunction;
using Nan::New;
using Nan::Set;

NAN_METHOD(Reset) {
TimeZoneMonitor *_monitor;

void TimeZoneMonitor::Notify() {
Nan::HandleScope scope;

Isolate *isolate = Isolate::GetCurrent();
#ifdef _WIN32
_tzset();
Expand All @@ -21,7 +27,12 @@ NAN_METHOD(Reset) {
Date::DateTimeConfigurationChangeNotification(isolate);
}

NAN_METHOD(Reset) {
_monitor->Notify();
}

NAN_MODULE_INIT(Init) {
_monitor = TimeZoneMonitor::Create();
Set(target, New<String>("reset").ToLocalChecked(),
GetFunction(New<FunctionTemplate>(Reset)).ToLocalChecked());
}
Expand Down
48 changes: 48 additions & 0 deletions src/monitor.cc
@@ -0,0 +1,48 @@
#include "monitor.h"

static const uint64_t kDebounceDelay = 500;

void TimeZoneMonitor::_asyncWorkCb(uv_async_t *work) {
TimeZoneMonitor *monitor = static_cast<TimeZoneMonitor *>(work->data);
monitor->ThreadedCallback();
monitor->DebouncedNotify();
}

void TimeZoneMonitor::tzDelayExpired(uv_timer_t* handle) {
TimeZoneMonitor *monitor = static_cast<TimeZoneMonitor *>(handle->data);
monitor->Notify();
}

TimeZoneMonitor::TimeZoneMonitor() {
uv_async_init(uv_default_loop(), &_asyncWork, _asyncWorkCb);
uv_timer_init(uv_default_loop(), &_debouncer);
_asyncWork.data = this;
_debouncer.data = this;

uv_unref((uv_handle_t*)&_asyncWork);
uv_unref((uv_handle_t*)&_debouncer);
}

void TimeZoneMonitor::ThreadedNotify() {
uv_async_send(&_asyncWork);
}

void TimeZoneMonitor::DebouncedNotify() {
//Some platforms write the file instead of symlinking it, add a small delay just to be sure
uv_timer_start(&_debouncer, TimeZoneMonitor::tzDelayExpired, kDebounceDelay, 0);
}

#ifdef _WIN32
# include "monitor_windows.cc"
#elif __ANDROID__
# include "monitor_android.cc"
#elif __APPLE__
# include "monitor_unix.cc"
#elif __unix__
# include "monitor_unix.cc"
#else
// static
TimeZoneMonitor *TimeZoneMonitor::Create() {
return new TimeZoneMonitor();
}
#endif
23 changes: 23 additions & 0 deletions src/monitor.h
@@ -0,0 +1,23 @@

#ifndef _TZD_MONITOR_H
#define _TZD_MONITOR_H
#include <uv.h>

class TimeZoneMonitor {
public:
// Returns a new TimeZoneMonitor object specific to the platform.
static TimeZoneMonitor *Create();
void Notify();
protected:
TimeZoneMonitor(); //Creation can only occur through create.
void DebouncedNotify();
void ThreadedNotify();
virtual void ThreadedCallback() {}
private:
static void tzDelayExpired(uv_timer_t* handle);
static void _asyncWorkCb(uv_async_t *work);
uv_async_t _asyncWork;
uv_timer_t _debouncer;
};

#endif
9 changes: 9 additions & 0 deletions src/monitor_android.cc
@@ -0,0 +1,9 @@

//TODO: We should catch the TIMEZONE_CHANGED intent and fire ThreadedNotify
//Unfortunatly, it looks like this part requires some jni magic which may not work from inside a module
//Lets disable the notifications for now...

// static
TimeZoneMonitor *TimeZoneMonitor::Create() {
return new TimeZoneMonitor();
}
53 changes: 53 additions & 0 deletions src/monitor_unix.cc
@@ -0,0 +1,53 @@
#include <uv.h>
#include <string.h>
#include "monitor.h"


// There is no true standard for where time zone information is actually
// stored. glibc uses /etc/localtime, uClibc uses /etc/TZ, and some older
// systems store the name of the time zone file within /usr/share/zoneinfo
// in /etc/timezone. Different libraries and custom builds may mean that
// still more paths are used. Just watch all three of these paths, because
// false positives are harmless, assuming the false positive rate is
// reasonable.
static const char* kFilesToWatch[] = {
"localtime",
"timezone",
"TZ",
};
// libuv seems to have some troubles when monitoring symlinks.
//It does however work if we monitor the parent folder non-recursively
static const char* kWatchFolder = "/etc/";

class TimeZoneMonitorUnix : public TimeZoneMonitor {
public:
TimeZoneMonitorUnix();
private:
static void tzChange(uv_fs_event_t *handle, const char *filename, int events, int status);
uv_fs_event_t fileWatcher;
};


void TimeZoneMonitorUnix::tzChange(uv_fs_event_t *handle, const char *filename, int events, int status) {
TimeZoneMonitorUnix *monitor = static_cast<TimeZoneMonitorUnix *>(handle->data);
for (size_t i = 0; i < sizeof(kFilesToWatch)/sizeof(kFilesToWatch[0]); i++) {
if(strcmp(kFilesToWatch[i], filename) == 0) {
monitor->DebouncedNotify(); //Debounce the notification (sometimes multiple files change)
return;
}
}
}

TimeZoneMonitorUnix::TimeZoneMonitorUnix()
{
uv_fs_event_init(uv_default_loop(), &fileWatcher);
fileWatcher.data = this;
uv_unref((uv_handle_t*)&fileWatcher);

uv_fs_event_start(&fileWatcher, TimeZoneMonitorUnix::tzChange, kWatchFolder, 0);
}

// static
TimeZoneMonitor *TimeZoneMonitor::Create() {
return new TimeZoneMonitorUnix();
}
60 changes: 60 additions & 0 deletions src/monitor_windows.cc
@@ -0,0 +1,60 @@
#include "monitor.h"

static const DWORD dwFilter = REG_NOTIFY_CHANGE_LAST_SET;

class TimeZoneMonitorWindows : public TimeZoneMonitor {
public:
TimeZoneMonitorWindows();
protected:
void ThreadedCallback();
private:
void Notify();
void SetUp(void);
static void TimeZoneChangeThreadedCallback(PVOID context, BOOLEAN timeout);
HANDLE hTZEvent;
HKEY hRegKey;
};


void TimeZoneMonitorWindows::ThreadedCallback() {
//Setup only causes notify to be called once
SetUp();
}

void TimeZoneMonitorWindows::SetUp() {
ResetEvent(hTZEvent);
// Watch the registry key for a change of value, must be run on main thread
RegNotifyChangeKeyValue(hRegKey, TRUE,
dwFilter, hTZEvent, TRUE);
}

TimeZoneMonitorWindows::TimeZoneMonitorWindows() {

HANDLE hWait;
LONG lErrorCode;
lErrorCode = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", 0, KEY_NOTIFY, &hRegKey);
if (lErrorCode != ERROR_SUCCESS) return;

// Create an event.
hTZEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (hTZEvent == NULL) return;

SetUp();

//Invoke the TimeZoneChangeThreadedCallback upon a change
RegisterWaitForSingleObject(
&hWait, hTZEvent, TimeZoneChangeThreadedCallback,
this, INFINITE, WT_EXECUTEINWAITTHREAD
);
}

//static
void TimeZoneMonitorWindows::TimeZoneChangeThreadedCallback(PVOID context, BOOLEAN timeout) {
TimeZoneMonitorWindows *monitor = static_cast<TimeZoneMonitorWindows *>(context);
monitor->ThreadedNotify(); //First enter the main eventloop, then debounce the notify
}

// static
TimeZoneMonitor *TimeZoneMonitor::Create() {
return new TimeZoneMonitorWindows();
}

0 comments on commit b7cd999

Please sign in to comment.