using afIoc::Injectusing concurrent::AtomicBoolusing concurrent::AtomicRef** (Response Object) - ** An asset that is uniquely identified by a client URL.** ** A 'ClientAsset' corresponds to a client URL that may be used by clients (e.g. internet browsers) to retrieve the asset.** ** Generally 'ClientAssets' are acquired from the 'FileHander' and 'PodHander' services and used to embed client URLs in web pages.** ** syntax: fantom** urlStr := fileHandler.fromLocalUrl(`/images/fanny.jpg`).clientUrl.encode** ** The URLs generated by 'ClientAssets' may be automatically transformed by asset caching strategies such as ** [Cold Feet]`pod:afColdFeet`. ** As such, 'ClientAsset' instances are cached and automatically updated should the underlying asset be modified.** To prevent needless polling of the file system, assets are checked for modification every 2 minutes in production ** or 2 seconds otherwise.** ** Custom Client Assets** ====================** If you want to serve up assets from a database or other source, subclass 'ClientAsset' to create your own custom implementation. ** Custom 'ClientAsset' instances should created by a `ClientAssetProducer` and contributed to the 'ClientAssetProducers' service. ** This ensures your custom assets will automatically adopt any asset caching strategy set by Cold Feet.constabstractclass ClientAsset : Asset { @Injectprivateconst ClientAssetCache? _assetCache @Injectprivateconst BedSheetServer? _bedServerprivateconst AtomicRef _clientUrlRef := AtomicRef()** Autobuild 'ClientAsset' instances with IoC. @NoDocprotectednew make(|This|? in){ in?.call(this)}** The URL relative to the 'BedSheet' [WebMod]`web::WebMod` that corresponds to the asset resource. ** If your application is the ROOT WebMod then this will be the same as 'clientUrl'; bar any asset caching. ** If in doubt, use the 'clientUrl' instead.** ** Returns 'null' if asset doesn't exist.abstract Uri? localUrl()** The URL that clients (e.g. web browsers) should use to access the asset resource. ** The 'clientUrl' contains any extra 'WebMod' path segments required to reach the 'BedSheet WebMod'.** It also contains path segments as provided by any asset caching strategies, such as [Cold Feet]`pod:afColdFeet`.** ** Client URLs are designed to be used / embedded in your HTML and therefore are relative to the host and start with a '/'. ** ** Returns 'null' if asset doesn't exist.** ** Subclasses should override 'clientUrl()' if they **do not** wish the client URL to be transformed by asset caching strategies like [Cold Feet]`http://eggbox.fantomfactory.org/pods/afColdFeet`. virtual Uri? clientUrl(){if(_assetCache == null)// assetCache is nullable for FileAsset legacy codethrow Err("${this.typeof.qname} needs to be built via IoC") localUrl := localUrlif(localUrl == null)returnnullif(_clientUrlRef.val == null) _clientUrlRef.val = _assetCache.toClientUrl(localUrl, this)return _clientUrlRef.val}** Returns an absolute URL (for example, one that starts with 'http://...') using [BedSheetServer.toAbsoluteUrl()]`BedSheetServer.toAbsoluteUrl`.** ** Returns 'null' if asset doesn't exist.virtual Uri? clientUrlAbs(){ _bedServer.toAbsoluteUrl(clientUrl)} @NoDocoverride Int hash(){ localUrl ?: super.hash} @NoDocoverride Bool equals(Obj? obj){ localUrl != null ? localUrl == (obj as ClientAsset)?.localUrl : super.equals(obj)}** Returns 'clientUrl.encode()' so it may be printed in HTML. Returns the string 'null' if the asset doesn't exist.override Str toStr(){ clientUrl?.encode ?: super.toStr}privateconst AtomicBool _lastIsModifiedRef := AtomicBool(false)privateconst AtomicRef _lastCheckedRef := AtomicRef(Duration.now - 1day) @NoDocvirtual Bool isModified(Duration timeout){// cache false responses for X secs to avoid hammering the file systemif(_lastIsModifiedRef.val == false){ now := Duration.now.floor(1sec) oldNow := (Duration) _lastCheckedRef.valif((now - oldNow) < timeout)return _lastIsModifiedRef.val// don't update old now until timeout expires _lastCheckedRef.val = now} newModifiedTs := actualModified // ?.floor(1sec) - lets not mess with the *actual* value oldModifiedTs := modified isModified := newModifiedTs == null || newModifiedTs > oldModifiedTs _lastIsModifiedRef.val = isModified// no need to cache the new "modified" value, because the Cache deletes us when this isModified() returns true// plus it'd be difficult to make a nice API around it // reset our cache timeoutif(isModified == false) _lastCheckedRef.val = Duration.now.floor(1sec)return isModified}** If the asset contents are liable to change behind the scenes, ** like the contents of a file may, then this should return the latest** calculated modified date. ** ** May return 'null' if not known. @NoDoc // used by isModified()virtual DateTime? actualModified(){ modified}}