sourceafBedSheet::Asset.fan

using concurrent

** (Response Object) - 
** An asset, such as 'File', which may be sent to the client.  
** 
** An 'Asset' instance wraps up all the information needed to send it to a client. 
** The corresponding Asset *ResponseProcessor* sets the following HTTP headers: 
** 
**  - 'Cache-Control'
**  - 'Content-Length'
**  - 'Content-Type'
**  - 'ETag'
**  - 'Last-Modified'
** 
** Should the request headers allow, it may also respond with a '304 - Modified' response.
** The Asset *ResponseProcessor* also correctly responds to HEAD requests.
** 
** When serving up your own files and images (say, from a database), it is recommended that your *Route Handler*
** return a custom 'Asset' instance, from your own 'Asset' subclass. 
** 
** You may also wish to consider returning a `ClientAsset`.  
abstract const class Asset {
    private const AtomicRef _etagRef        := AtomicRef()
    
    ** Returns 'true' if the asset exists. (Or did at the time this class was created.)
    ** 
    ** Returns 'true' by default.
    virtual Bool        exists() { true }

    ** Get the modified time of the asset. Note that pod files have last modified info too!
    ** 
    ** Returns 'null' if asset doesn't exist
    abstract DateTime?  modified()
    
    ** The ETag uniquely identifies the asset and its version. 
    ** The default implementation is a hash of the modified time and the asset size.
    **  
    ** Returns 'null' if asset doesn't exist
    virtual Str?        etag() {
        if (_etagRef.val == null)
            _etagRef.val = exists ? "${this.size?.toHex}-${this.modified?.ticks?.toHex}" : null
        return _etagRef.val
    }

    ** The size of the asset in bytes.
    ** 
    ** Returns 'null' if asset doesn't exist
    abstract Int?       size()
    
    ** Creates an 'InStream' to read the contents of the asset.
    ** A new stream should be created each time 'in()' is called.
    **  
    ** Returns 'null' if asset doesn't exist, or can't be opened.
    ** (For example, if the asset is a recently deleted file resource).
    ** 
    ** The caller is responsible for closing the stream. 
    ** Note this can be as easy as calling 'in.readAllBuf()' or 'in.readAllStr()'. 
    abstract InStream?  in()

    ** Returns the content type for the asset.
    **  
    ** Returns 'null' if asset doesn't exist.
    abstract MimeType?  contentType()
    
    ** Creates a 'Asset' for the given file. 
    ** 
    ** To create a 'ClientAsset' use the 'FileHandler' or 'PodHandler' service:
    ** 
    **   syntax: fantom
    **   fileHandler.fromServerFile(file) 
    static new makeFromFile(File file) {
        FileAsset(file, null)
    }

    @NoDoc
    override Int hash() {
        etag?.hash ?: 0
    }
    
    @NoDoc
    override Bool equals(Obj? obj) {
        etag == (obj as Asset)?.etag
    }
    
    @NoDoc
    override Str toStr() {
        etag ?: "null"
    }
}