using afIocusing concurrent** (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]`http://www.fantomfactory.org/pods/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 privateconst AtomicRef _clientUrlRef := AtomicRef()privateconst AtomicRef _lastCheckedRef := AtomicRef(DateTime.now)** 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]`http://www.fantomfactory.org/pods/afColdFeet`.** ** Client URLs are designed to be used / embedded in your HTML and therefore are relative to the host and start with a '/'. ** ** See [BedSheetServer.toAbsoluteUrl()]`BedSheetServer.toAbsoluteUrl` should you require an absolute URL that starts with 'http://'. ** ** Returns 'null' if asset doesn't exist.** ** Subclasses should override 'clientUrl()' if they don't wish the client URL to be transformed by asset caching strategies like [Cold Feet]`http://www.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")if(_clientUrlRef.val == null) _clientUrlRef.val = _assetCache.toClientUrl(localUrl, this)return _clientUrlRef.val} @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} @NoDocvirtual Bool isModified(Duration? timeout){ lastChecked := (DateTime) _lastCheckedRef.getAndSet(DateTime.now) actualModified := actualModifiedif(actualModified == null)returntrueif(timeout == null || (DateTime.now - lastChecked) > timeout)return actualModified.floor(1sec) > modified.floor(1sec)returnfalse}** 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}}