2014-05-02 17:46:03 -04:00
require_dependency 'sass/discourse_sass_compiler'
2014-11-12 10:01:41 +11:00
require_dependency 'distributed_cache'
2014-05-02 17:46:03 -04:00
class DiscourseStylesheets
2015-05-05 15:50:13 +10:00
CACHE_PATH || = 'tmp/stylesheet-cache'
2015-02-09 18:53:49 +01:00
MANIFEST_DIR || = " #{ Rails . root } /tmp/cache/assets/ #{ Rails . env } "
MANIFEST_FULL_PATH || = " #{ MANIFEST_DIR } /stylesheet-manifest "
2014-05-02 17:46:03 -04:00
@lock = Mutex . new
2014-11-11 15:32:44 +11:00
2014-11-12 10:01:41 +11:00
def self . cache
2014-11-12 13:51:16 -05:00
return { } if Rails . env . development?
2014-11-12 10:01:41 +11:00
@cache || = DistributedCache . new ( " discourse_stylesheet " )
2014-11-11 15:32:44 +11:00
end
2014-05-02 17:46:03 -04:00
def self . stylesheet_link_tag ( target = :desktop )
2015-05-23 15:25:05 +10:00
2014-11-12 10:01:41 +11:00
tag = cache [ target ]
2014-11-12 10:06:02 +11:00
2014-11-12 10:32:46 +11:00
return tag . dup . html_safe if tag
2014-11-12 10:01:07 +11:00
2014-11-12 10:01:41 +11:00
@lock . synchronize do
builder = self . new ( target )
builder . compile unless File . exists? ( builder . stylesheet_fullpath )
builder . ensure_digestless_file
2015-01-05 12:50:42 +11:00
tag = %[ <link href=" #{ Rails . env . production? ? builder . stylesheet_cdnpath : builder . stylesheet_relpath_no_digest + '?body=1' } " media="all" rel="stylesheet" /> ]
2014-11-11 15:32:44 +11:00
2014-11-12 10:01:41 +11:00
cache [ target ] = tag
2014-11-11 15:32:44 +11:00
2014-11-12 10:32:46 +11:00
tag . dup . html_safe
2014-05-02 17:46:03 -04:00
end
end
2014-05-29 15:42:50 -04:00
def self . compile ( target = :desktop , opts = { } )
2014-05-02 17:46:03 -04:00
@lock . synchronize do
2015-05-05 16:52:03 -04:00
FileUtils . rm ( MANIFEST_FULL_PATH , force : true ) if opts [ :force ]
2014-05-02 17:46:03 -04:00
builder = self . new ( target )
2015-05-05 16:52:03 -04:00
builder . compile ( opts )
2014-05-02 17:46:03 -04:00
builder . stylesheet_filename
end
end
2014-05-29 15:42:50 -04:00
def self . last_file_updated
if Rails . env . production?
@last_file_updated || = if File . exists? ( MANIFEST_FULL_PATH )
File . readlines ( MANIFEST_FULL_PATH , 'r' ) [ 0 ]
else
mtime = max_file_mtime
FileUtils . mkdir_p ( MANIFEST_DIR )
File . open ( MANIFEST_FULL_PATH , " w " ) { | f | f . print ( mtime ) }
mtime
end
else
max_file_mtime
end
end
def self . max_file_mtime
2014-11-17 16:46:58 +02:00
globs = [ " #{ Rails . root } /app/assets/stylesheets/**/*.*css " ]
2016-06-15 18:40:13 +08:00
Discourse . plugins . map { | plugin | File . dirname ( plugin . path ) } . each do | path |
2014-11-17 16:46:58 +02:00
globs += [
" #{ path } /plugin.rb " ,
" #{ path } /**/*.*css " ,
]
end
globs . map do | pattern |
Dir . glob ( pattern ) . map { | x | File . mtime ( x ) } . max
2014-06-03 16:46:32 -04:00
end . compact . max . to_i
2014-05-29 15:42:50 -04:00
end
2014-05-02 17:46:03 -04:00
def initialize ( target = :desktop )
@target = target
end
2015-05-05 16:52:03 -04:00
def compile ( opts = { } )
unless opts [ :force ]
if File . exists? ( stylesheet_fullpath )
unless StylesheetCache . where ( target : @target , digest : digest ) . exists?
begin
StylesheetCache . add ( @target , digest , File . read ( stylesheet_fullpath ) )
rescue = > e
Rails . logger . warn " Completely unexpected error adding contents of ' #{ stylesheet_fullpath } ' to cache #{ e } "
end
end
return true
end
end
2014-05-02 17:46:03 -04:00
scss = File . read ( " #{ Rails . root } /app/assets/stylesheets/ #{ @target } .scss " )
2015-05-20 15:56:54 +10:00
rtl = @target . to_s =~ / _rtl$ /
2014-05-02 17:46:03 -04:00
css = begin
2015-05-20 15:56:54 +10:00
DiscourseSassCompiler . compile ( scss , @target , rtl : rtl )
2014-05-02 17:46:03 -04:00
rescue Sass :: SyntaxError = > e
Rails . logger . error " Stylesheet failed to compile for ' #{ @target } '! Recompiling without plugins and theming. "
Rails . logger . error e . sass_backtrace_str ( " #{ @target } stylesheet " )
DiscourseSassCompiler . compile ( scss + DiscourseSassCompiler . error_as_css ( e , " #{ @target } stylesheet " ) , @target , safe : true )
end
FileUtils . mkdir_p ( cache_fullpath )
File . open ( stylesheet_fullpath , " w " ) do | f |
f . puts css
end
2015-05-05 23:28:19 +10:00
begin
StylesheetCache . add ( @target , digest , css )
rescue = > e
Rails . logger . warn " Completely unexpected error adding item to cache #{ e } "
end
2014-05-02 17:46:03 -04:00
css
end
def ensure_digestless_file
2014-05-27 16:08:47 -04:00
# file without digest is only for auto-reloading css in dev env
unless Rails . env . production? || ( File . exist? ( stylesheet_fullpath_no_digest ) && File . mtime ( stylesheet_fullpath ) == File . mtime ( stylesheet_fullpath_no_digest ) )
2014-05-02 17:46:03 -04:00
FileUtils . cp ( stylesheet_fullpath , stylesheet_fullpath_no_digest )
end
end
2015-05-22 11:21:16 +10:00
def self . cache_fullpath
2015-05-05 15:50:13 +10:00
" #{ Rails . root } / #{ CACHE_PATH } "
2014-05-02 17:46:03 -04:00
end
2015-05-22 11:21:16 +10:00
def cache_fullpath
self . class . cache_fullpath
end
2014-05-02 17:46:03 -04:00
def stylesheet_fullpath
" #{ cache_fullpath } / #{ stylesheet_filename } "
end
def stylesheet_fullpath_no_digest
" #{ cache_fullpath } / #{ stylesheet_filename_no_digest } "
end
2015-01-05 12:50:42 +11:00
def stylesheet_cdnpath
2015-01-05 13:03:43 +11:00
" #{ GlobalSetting . cdn_url } #{ stylesheet_relpath } ?__ws= #{ Discourse . current_hostname } "
2015-01-05 12:50:42 +11:00
end
2015-03-09 11:45:36 +11:00
def root_path
" #{ GlobalSetting . relative_url_root } / "
end
2015-05-05 15:50:13 +10:00
# using uploads cause we already have all the routing in place
2014-05-02 17:46:03 -04:00
def stylesheet_relpath
2015-05-05 15:50:13 +10:00
" #{ root_path } stylesheets/ #{ stylesheet_filename } "
2014-05-02 17:46:03 -04:00
end
2015-01-05 12:50:42 +11:00
2014-05-02 17:46:03 -04:00
def stylesheet_relpath_no_digest
2015-05-05 15:50:13 +10:00
" #{ root_path } stylesheets/ #{ stylesheet_filename_no_digest } "
2014-05-02 17:46:03 -04:00
end
def stylesheet_filename
" #{ @target } _ #{ digest } .css "
end
def stylesheet_filename_no_digest
" #{ @target } .css "
end
2014-05-29 15:42:50 -04:00
# digest encodes the things that trigger a recompile
2014-05-02 17:46:03 -04:00
def digest
@digest || = begin
2015-05-05 17:58:36 -04:00
theme = ( cs = ColorScheme . enabled ) ? " #{ cs . id } - #{ cs . version } " : false
category_updated = Category . where ( " background_url IS NOT NULL and background_url != '' " ) . last_updated_at
if theme || category_updated > 0
Digest :: SHA1 . hexdigest " #{ RailsMultisite :: ConnectionManagement . current_db } - #{ theme } - #{ DiscourseStylesheets . last_file_updated } - #{ category_updated } "
else
2016-06-15 18:40:13 +08:00
digest_string = " defaults- #{ DiscourseStylesheets . last_file_updated } "
if cdn_url = GlobalSetting . cdn_url
digest_string = " #{ digest_string } - #{ cdn_url } "
end
Digest :: SHA1 . hexdigest digest_string
2015-05-05 17:58:36 -04:00
end
2014-05-02 17:46:03 -04:00
end
end
end