anchor.diff:  %% @author Marc Worrell <marc@worrell.nl>
  -%% @copyright 2009 Marc Worrell
  +%% @copyright 2009-2010 Marc Worrell
   %%
   %% @doc Serve static (image) files from a configured list of directories or template lookup keys.  Caches files in the local depcache.
   %% Is also able to generate previews (if configured to do so).
   
  -%% Copyright 2009 Marc Worrell
  +%% Copyright 2009-2010 Marc Worrell
   %%
   %% Licensed under the Apache License, Version 2.0 (the "License");
   %% you may not use this file except in compliance with the License.
  
positive.code:             case filelib:is_regular(MediaFile) of
                  true ->
                      % Media file exists, perform the resize
                      Root = case State#state.root of
                          [ConfRoot|_] -> ConfRoot;
                          _ -> z_path:media_preview(Context)
                      end,
                      PreviewFile = filename:join(Root, Path),
                      case z_media_preview:convert(MediaFile, PreviewFile, PreviewPropList, Context) of
                          ok -> resource_exists(ReqData, State);
                          {error, Reason} -> throw(Reason)
                      end;
                  false ->
                      {false, ReqData, State}
              end
      end.
  
  
  
  %% Encode the data so that the identity variant comes first and then the gzip'ed variant
  
negative.code: %% /media/attachment/<filepath>
  
  -module(resource_file_readonly).
  -export([
      init/1,
      service_available/2,
      allowed_methods/2,
      resource_exists/2,
      forbidden/2,
      last_modified/2,
      expires/2,
      content_types_provided/2,
      charsets_provided/2,
      encodings_provided/2,
      provide_content/2,
      finish_request/2
  ]).
  
  -include_lib("webmachine_resource.hrl").
  -include_lib("zotonic.hrl").
  
  -record(cache, {path, fullpath, mime, last_modified, body}).
  
  -define(MAX_AGE, 315360000).
  
  init(ConfigProps) ->
      {ok, ConfigProps}.
  
  
  %% @doc Initialize the context for the request. Continue session when available.
  service_available(ReqData, ConfigProps) ->
      Context = z_context:set(ConfigProps, z_context:new(ReqData)),
      Context1 = z_context:continue_session(Context),
      {_Found, _ReqData1, ContextFile} = ensure_file_info(ReqData, Context1),
      ContextMime = case z_context:get(mime, ContextFile) of
          undefined ->
              Path = case z_context:get(path, Context) of
                  undefined -> mochiweb_util:unquote(wrq:disp_path(ReqData));
                  ConfiguredPath -> ConfiguredPath
              end, 
              CT = z_media_identify:guess_mime(Path),
              z_context:set(mime, CT, ContextFile);
          _Mime -> 
              ContextFile
      end,
      ?WM_REPLY(true, ContextMime).
  
  
  allowed_methods(ReqData, Context) ->
      {['HEAD', 'GET'], ReqData, Context}.
  
  content_types_provided(ReqData, Context) ->
      {[{z_context:get(mime, Context), provide_content}], ReqData, Context}.
  
  
  %% @doc Oversimplified stub for access checks.
  forbidden(ReqData, Context) ->
      case z_context:get(root, Context) of
          [{module, Module}] -> 
              {Module:file_forbidden(z_context:get(fullpath, Context), Context), ReqData, Context};
          _ ->
              {false, ReqData, Context}
      end.
  
  
  encodings_provided(ReqData, Context) ->
      Encodings = case z_context:get(mime, Context) of
          "image/" ++ _ -> 
              [{"identity", fun(Data) -> Data end}];
          _ -> 
              [{"identity", fun(Data) -> decode_data(identity, Data) end},
               {"gzip",     fun(Data) -> decode_data(gzip, Data) end}]
      end,
      {Encodings, ReqData, z_context:set(encode_data, length(Encodings) > 1, Context)}.
  
  
  resource_exists(ReqData, Context) ->
      {z_context:get(fullpath, Context) =/= undefined, ReqData, Context}.
  
  charsets_provided(ReqData, Context) ->
      case is_text(z_context:get(mime, Context)) of
          true -> {[{"utf-8", fun(X) -> X end}], ReqData, Context};
          _ -> {no_charset, ReqData, Context}
      end.
      
  last_modified(ReqData, Context) ->
      RD1 = wrq:set_resp_header("Cache-Control", "public, max-age="++integer_to_list(?MAX_AGE), ReqData),
      case z_context:get(last_modified, Context) of
          undefined -> 
              LMod = filelib:last_modified(z_context:get(fullpath, Context)),
  			[LModUTC|_] = calendar:local_time_to_universal_time_dst(LMod),
              {LModUTC, RD1, z_context:set(last_modified, LModUTC, Context)};
          LModUTC ->
              {LModUTC, RD1, Context}
      end.
  
  expires(ReqData, Context) ->
      NowSecs = calendar:datetime_to_gregorian_seconds(calendar:universal_time()),
      {calendar:gregorian_seconds_to_datetime(NowSecs + ?MAX_AGE), ReqData, Context}.
  
  provide_content(ReqData, Context) ->
      RD1 = case z_context:get(content_disposition, Context) of
          inline ->     wrq:set_resp_header("Content-Disposition", "inline", ReqData);
          attachment -> wrq:set_resp_header("Content-Disposition", "attachment", ReqData);
          undefined ->  ReqData
      end,
      {Content, Context1} = case z_context:get(body, Context) of
          undefined ->
              {ok, Data} = file:read_file(z_context:get(fullpath, Context)),
              Body = case z_context:get(encode_data, Context, false) of 
                  true -> encode_data(Data);
                  false -> Data
              end,
              {Body, z_context:set(body, Body, Context)};
          Body -> 
              {Body, Context}
      end,
      {Content, RD1, Context1}.
      
      
  finish_request(ReqData, Context) ->
      case z_context:get(is_cached, Context) of
          false ->
              case z_context:get(body, Context) of
                  undefined ->  
                      {ok, ReqData, Context};
                  Body ->
                      case z_context:get(use_cache, Context, false) andalso z_context:get(encode_data, Context, false) of
                          true ->
                              % Cache the served file in the depcache.  Cache it for 3600 secs.
                              Path = z_context:get(path, Context),
                              Cache = #cache{
                                          path=Path,
                                          fullpath=z_context:get(fullpath, Context),
                                          mime=z_context:get(mime, Context),
                                          last_modified=z_context:get(last_modified, Context),
                                          body=Body
                                      },
                              z_depcache:set(cache_key(Path), Cache, Context),
                              {ok, ReqData, Context};
                          _ ->
                              % No cache or no gzip'ed version (file system cache is fast enough for image serving)
                              {ok, ReqData, Context}
                      end
              end;
          true ->
              {ok, ReqData, Context}
      end.
  
  
  %%%%%%%%%%%%%% Helper functions %%%%%%%%%%%%%%
  
  %% @doc Find the file referred to by the reqdata or the preconfigured path
  ensure_file_info(ReqData, Context) ->
      Path = case z_context:get(path, Context) of
          undefined -> mochiweb_util:unquote(wrq:disp_path(ReqData));
          ConfiguredPath -> ConfiguredPath
      end, 
      Cached = case z_context:get(use_cache, Context) of
          true -> z_depcache:get(cache_key(Path), Context);
          _    -> undefined
      end,
      case Cached of
          undefined ->
              case file_exists(Path, Context) of 
              	{true, FullPath} -> 
              	    Context1 = z_context:set([ {path, Path}, {fullpath, FullPath} ], Context),
              	    {true, ReqData, Context1};
              	_ -> 
              	    %% We might be able to generate a new preview
              	    case z_context:get(is_media_preview, Context, false) of
              	        true ->
              	            % Generate a preview, recurse on success
              	            ensure_preview(ReqData, Path, Context);
              	        false ->
                      	    {false, ReqData, Context}
              	    end
              end;
          {ok, Cache} ->
              ContextCached = z_context:set([
                              {is_cached, true},
                              {path, Cache#cache.path},
                              {fullpath, Cache#cache.fullpath},
                              {mime, Cache#cache.mime},
                              {last_modified, Cache#cache.last_modified},
                              {body, Cache#cache.body}
                          ],
                          Context),
              {true, ReqData, ContextCached}
      end.
  
  
  cache_key(Path) ->
      {resource_file, Path}.
  
  file_exists([], _Context) ->
      false;
  file_exists(Name, Context) ->
      RelName = case hd(Name) of
          $/ -> tl(Name);
          _ -> Name
  