Skip to content

Commit

Permalink
#596 download remote resources on unpack
Browse files Browse the repository at this point in the history
  • Loading branch information
Jand42 committed May 23, 2017
1 parent 2370938 commit a3cfb7b
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 16 deletions.
28 changes: 27 additions & 1 deletion src/compiler/WebSharper.Compiler/commands/UnpackCommand.fs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ namespace WebSharper.Compiler
open FileSystem
module PC = WebSharper.PathConventions
module C = Commands
module Re = WebSharper.Core.Resources

module UnpackCommand =

type Config =
{
Assemblies : list<string>
Expand Down Expand Up @@ -79,6 +79,8 @@ module UnpackCommand =
| errors -> C.ParseFailed errors
| _ -> C.NotRecognized

let private localResTyp = typeof<Re.IDownloadableResource>

let Exec env cmd =
let baseDir =
let pathToSelf = typeof<Config>.Assembly.Location
Expand Down Expand Up @@ -133,6 +135,30 @@ module UnpackCommand =
writeText script r.FileName r.Content
for r in a.GetContents() do
writeBinary content r.FileName (r.GetContentData())

let rec printError (e: exn) =
if isNull e.InnerException then
e.Message
else e.Message + " - " + printError e.InnerException

try
let asm =
try
System.Reflection.Assembly.LoadFile (Path.GetFullPath p)
with e ->
eprintfn "Failed to load assembly for unpacking local resources: %s - %s" p (printError e)
null
if not (isNull asm) then
for t in asm.GetTypes() do
if t.GetInterfaces() |> Array.contains localResTyp then
try
let res = Activator.CreateInstance(t) :?> Re.IDownloadableResource
res.Unpack(cmd.RootDirectory)
with e ->
eprintfn "Failed to unpack local resource: %s - %s" t.FullName (printError e)
with e ->
eprintfn "Failed to unpack local resources: %s" (printError e)

C.Ok

let Description =
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/WebSharper.Core/Graph.fs
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ type Graph =
member this.Render ctx =
fun writer ->
let writer = writer R.Scripts
writer.Write("<-- ")
writer.Write("<!-- ")
writer.Write("Failed to load: {0}; because of: {1}", t, e.Message)
writer.WriteLine(" -->")
}
Expand Down
82 changes: 71 additions & 11 deletions src/compiler/WebSharper.Core/Resources.fs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ open System.IO
open System.Reflection
open System.Web
open System.Web.UI

module CT = ContentTypes

type Rendering =
Expand Down Expand Up @@ -60,6 +61,9 @@ type Context =
and IResource =
abstract member Render : Context -> ((RenderLocation -> HtmlTextWriter) -> unit)

type IDownloadableResource =
abstract Unpack : string -> unit

let cleanLink dHttp (url: string) =
if dHttp && url.StartsWith("//")
then "http:" + url
Expand Down Expand Up @@ -218,49 +222,105 @@ let tryFindWebResource (t: Type) (spec: string) =
t.Assembly.GetManifestResourceNames()
|> Seq.tryFind ok

type BaseResource(kind: Kind) =

let tryGetUriFileName (u: string) =
try
let uri = System.Uri u
let parts = u.Split('/')
parts.[parts.Length - 1] |> Some
with _ -> None

type BaseResource(kind: Kind) as this =

let self = this.GetType()
let name = self.FullName

new () =
new BaseResource(Ignore)

new (spec: string) =
new BaseResource(Basic spec)

new (b: string, x: string, [<System.ParamArray>] xs: string []) =
new BaseResource(Complex(b, x :: List.ofArray xs))

member this.GetLocalName() =
name.Replace('+', '.').Split('`').[0]

interface IResource with
member this.Render ctx =
let dHttp = ctx.DefaultToHttp
let isLocal = ctx.GetSetting "UseDownloadedResources" |> Option.exists (fun s -> s.ToLower() = "true")
let localFolder isCss f =
(if isCss then "Content/WebSharper/" else "Scripts/WebSharper/") + this.GetLocalName() + "/" + f
match kind with
| Ignore ->
ignore
| Basic spec ->
let self = this.GetType()
let id = self.FullName
let mt = if spec.EndsWith ".css" then Css else Js
let r =
match ctx.GetSetting id with
match ctx.GetSetting name with
| Some url -> RenderLink url
| None ->
match tryFindWebResource self spec with
| Some e -> Rendering.GetWebResourceRendering(ctx, self, e)
| None -> RenderLink spec
| None ->
if isLocal then
match tryGetUriFileName spec with
| Some f ->
RenderLink (localFolder (mt = Css) f)
| _ ->
RenderLink spec
else
RenderLink spec
fun writer -> r.Emit(writer, mt, dHttp)
| Complex (b, xs) ->
let id = this.GetType().FullName
let b = defaultArg (ctx.GetSetting id) b
let b = defaultArg (ctx.GetSetting name) b
let urls =
xs |> List.map (fun x ->
let url = b.TrimEnd [| '/' |] + "/" + x.TrimStart [| '/' |]
let url = b.TrimEnd('/') + "/" + x.TrimStart('/')
url, url.EndsWith ".css"
)
)
let urls =
if isLocal then
urls |> List.map (fun (u, isCss) ->
match tryGetUriFileName u with
| Some f ->
localFolder isCss f, isCss
| _ ->
u, isCss
)
else urls
fun writer ->
for url, isCss in urls do
if isCss then
link dHttp (writer Styles) url
else script dHttp (writer Scripts) url

interface IDownloadableResource with
member this.Unpack path =
use wc = new System.Net.WebClient()
let localName = this.GetLocalName()
let cssDir = Path.Combine (path, "Content", "WebSharper", localName)
let jsDir = Path.Combine (path, "Scripts", "WebSharper", localName)
let download (url: string) =
match tryGetUriFileName url with
| Some f ->
let localDir = if url.EndsWith ".css" then cssDir else jsDir
let localPath = Path.Combine(localDir, f)
if not (Directory.Exists localDir) then
Directory.CreateDirectory localDir |> ignore
let url = if url.StartsWith("//") then "http:" + url else url
printfn "Downloading %A to %s" url localPath
wc.DownloadFile(url, localPath)
| _ ->
()
match kind with
| Ignore -> ()
| Basic spec ->
download spec
| Complex (b, xs) ->
for x in xs do
download (b.TrimEnd('/') + "/" + x.TrimStart('/'))

[<Sealed>]
type Runtime() =
interface IResource with
Expand Down
10 changes: 7 additions & 3 deletions src/compiler/WebSharper.Core/Resources.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,15 @@ and IResource =
/// Renders the resource to a given TextWriter.
abstract member Render : Context -> ((RenderLocation -> HtmlTextWriter) -> unit)

/// An interface for resources to execute custom unpack.
type IDownloadableResource =

/// Gets the WebSharper output root directory.
abstract Unpack : string -> unit

/// A helper base class for resource-defining types.
type BaseResource =

[<Obsolete "Do not call default constructor of BaseResource, it will do nothing.">]
new : unit -> BaseResource

/// References an embedded resource from he current assembly.
/// The string represents either a reference to an embedded
/// resource from the current assembly, or a (possibly relative)
Expand All @@ -103,6 +106,7 @@ type BaseResource =
new : string * string * [<ParamArray>] xs: string [] -> BaseResource

interface IResource
interface IDownloadableResource

/// Represents the runtime library resource required by all WebSharper code.
[<Sealed>]
Expand Down
2 changes: 2 additions & 0 deletions tests/Web/Web.config
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,7 @@
</runtime>
<appSettings>
<add key="WebSharperSharedMetadata" value="Full" />
<add key="UseDownloadedResources" value="True" />
<add key="WebSharper.JQuery.Resources.JQuery" value="/Scripts/jquery-3.1.1.min.js" />
</appSettings>
</configuration>

0 comments on commit a3cfb7b

Please sign in to comment.