Cache calculated watering scale when using data from Dark Sky

This commit is contained in:
Matthew Oslan
2019-07-03 16:26:14 -04:00
parent 2567e3a871
commit 5eb696c3e8
6 changed files with 192 additions and 76 deletions

View File

@@ -11,6 +11,7 @@ import { AdjustmentMethod, AdjustmentMethodResponse, AdjustmentOptions } from ".
import ManualAdjustmentMethod from "./adjustmentMethods/ManualAdjustmentMethod";
import ZimmermanAdjustmentMethod from "./adjustmentMethods/ZimmermanAdjustmentMethod";
import RainDelayAdjustmentMethod from "./adjustmentMethods/RainDelayAdjustmentMethod";
import WateringScaleCache, { CachedScale } from "../WateringScaleCache";
const WEATHER_PROVIDER: WeatherProvider = new ( require("./weatherProviders/" + ( process.env.WEATHER_PROVIDER || "OWM" ) ).default )();
const PWS_WEATHER_PROVIDER: WeatherProvider = new ( require("./weatherProviders/" + ( process.env.PWS_WEATHER_PROVIDER || "WUnderground" ) ).default )();
@@ -30,6 +31,8 @@ const ADJUSTMENT_METHOD: { [ key: number ] : AdjustmentMethod } = {
2: RainDelayAdjustmentMethod
};
const cache = new WateringScaleCache();
/**
* Resolves a location description to geographic coordinates.
* @param location A partial zip/city/country or a coordinate pair.
@@ -227,57 +230,83 @@ export const getWateringData = async function( req: express.Request, res: expres
}
const weatherProvider = pws ? PWS_WEATHER_PROVIDER : WEATHER_PROVIDER;
let adjustmentMethodResponse: AdjustmentMethodResponse;
try {
adjustmentMethodResponse = await adjustmentMethod.calculateWateringScale(
adjustmentOptions, coordinates, weatherProvider, pws
);
} catch ( err ) {
if ( typeof err != "string" ) {
/* If an error occurs under expected circumstances (e.g. required optional fields from a weather API are
missing), an AdjustmentOption must throw a string. If a non-string error is caught, it is likely an Error
thrown by the JS engine due to unexpected circumstances. The user should not be shown the error message
since it may contain sensitive information. */
res.send( "Error: an unexpected error occurred." );
console.error( `An unexpected error occurred for ${ req.url }: `, err );
} else {
res.send( "Error: " + err );
}
return;
}
let scale = adjustmentMethodResponse.scale;
if ( checkRestrictions ) {
let wateringData: BaseWateringData = adjustmentMethodResponse.wateringData;
// Fetch the watering data if the AdjustmentMethod didn't fetch it and restrictions are being checked.
if ( checkRestrictions && !wateringData ) {
try {
wateringData = await weatherProvider.getWateringData( coordinates );
} catch ( err ) {
res.send( "Error: " + err );
return;
}
}
// Check for any user-set restrictions and change the scale to 0 if the criteria is met
if ( checkWeatherRestriction( req.params[ 0 ], wateringData ) ) {
scale = 0;
}
}
const data = {
scale: scale,
rd: adjustmentMethodResponse.rainDelay,
scale: undefined,
rd: undefined,
tz: getTimezone( timeData.timezone, undefined ),
sunrise: timeData.sunrise,
sunset: timeData.sunset,
eip: ipToInt( remoteAddress ),
rawData: adjustmentMethodResponse.rawData,
error: adjustmentMethodResponse.errorMessage
rawData: undefined,
error: undefined
};
let cachedScale: CachedScale;
if ( weatherProvider.shouldCacheWateringScale() ) {
cachedScale = cache.getWateringScale( req.params[ 0 ], coordinates, pws, adjustmentOptions );
}
if ( cachedScale ) {
// Use the cached data if it exists.
data.scale = cachedScale.scale;
data.rawData = cachedScale.rawData;
data.rd = cachedScale.rainDelay;
} else {
// Calculate the watering scale if it wasn't found in the cache.
let adjustmentMethodResponse: AdjustmentMethodResponse;
try {
adjustmentMethodResponse = await adjustmentMethod.calculateWateringScale(
adjustmentOptions, coordinates, weatherProvider, pws
);
} catch ( err ) {
if ( typeof err != "string" ) {
/* If an error occurs under expected circumstances (e.g. required optional fields from a weather API are
missing), an AdjustmentOption must throw a string. If a non-string error is caught, it is likely an Error
thrown by the JS engine due to unexpected circumstances. The user should not be shown the error message
since it may contain sensitive information. */
res.send( "Error: an unexpected error occurred." );
console.error( `An unexpected error occurred for ${ req.url }: `, err );
} else {
res.send( "Error: " + err );
}
return;
}
data.scale = adjustmentMethodResponse.scale;
data.error = adjustmentMethodResponse.errorMessage;
data.rd = adjustmentMethodResponse.rainDelay;
data.rawData = adjustmentMethodResponse.rawData;
if ( checkRestrictions ) {
let wateringData: BaseWateringData = adjustmentMethodResponse.wateringData;
// Fetch the watering data if the AdjustmentMethod didn't fetch it and restrictions are being checked.
if ( checkRestrictions && !wateringData ) {
try {
wateringData = await weatherProvider.getWateringData( coordinates );
} catch ( err ) {
res.send( "Error: " + err );
return;
}
}
// Check for any user-set restrictions and change the scale to 0 if the criteria is met
if ( checkWeatherRestriction( req.params[ 0 ], wateringData ) ) {
data.scale = 0;
}
}
// Cache the watering scale if caching is enabled and no error occurred.
if ( weatherProvider.shouldCacheWateringScale() && !data.error ) {
cache.storeWateringScale( req.params[ 0 ], coordinates, pws, adjustmentOptions, {
scale: data.scale,
rawData: data.rawData,
rainDelay: data.rd
} );
}
}
// Return the response to the client in the requested format
if ( outputFormat === "json" ) {
res.json( data );

View File

@@ -111,4 +111,8 @@ export default class DarkSkyWeatherProvider extends WeatherProvider {
return weather;
}
public shouldCacheWateringScale(): boolean {
return true;
}
}

View File

@@ -24,4 +24,13 @@ export class WeatherProvider {
getWeatherData( coordinates : GeoCoordinates ): Promise< WeatherData > {
throw "Selected WeatherProvider does not support getWeatherData";
}
/**
* Returns a boolean indicating if watering scales calculated using data from this WeatherProvider should be cached
* until the end of the day in timezone the data was for.
* @return a boolean indicating if watering scales calculated using data from this WeatherProvider should be cached.
*/
shouldCacheWateringScale(): boolean {
return false;
}
}