class Epuber::Server

API:

LATER

/file/<path-or-pattern> – displays pretty file (image, text file) (for example: /file/text/s01.xhtml or /file/text/s01.bade)

Public Class Methods

_compile_book() click to toggle source
# File lib/epuber/server.rb, line 345
def self._compile_book
  begin
    compiler = Epuber::Compiler.new(book, target)
    compiler.compile(build_path)
    self.file_resolver = compiler.file_resolver

    true
  rescue => e
    self.file_resolver = compiler.file_resolver

    Epuber::UI.error("Compile error: #{e}", location: e)

    false
  end
end
_log(level, message) click to toggle source

@param level [Symbol] @param message [String]

@return nil

# File lib/epuber/server.rb, line 192
def self._log(level, message)
  case level
  when :ui
    puts message
  when :info
    puts "INFO: #{message}" if verbose
  when :get
    puts " GET: #{message}" if verbose
  when :ws
    puts "  WS: #{message}" if verbose
  else
    raise "Unknown log level #{level}"
  end
end
build_path() click to toggle source

@return [String] base path

# File lib/epuber/server.rb, line 176
def self.build_path
  Epuber::Config.instance.build_path(target)
end
changes_detected(_modified, _added, _removed) click to toggle source

@param _modified [Array<String>] @param _added [Array<String>] @param _removed [Array<String>]

# File lib/epuber/server.rb, line 413
def self.changes_detected(_modified, _added, _removed)
  all_changed = (_modified + _added + _removed).uniq.map { |path| path.unicode_normalize }

  reload_bookspec if all_changed.any? { |file| file == book.file_path }

  changed = filter_not_project_files(all_changed) || []
  return if changed.count == 0

  notify_clients(:compile_start)


  _log :ui, "#{Time.now}  Compiling"
  compile_book do |success|
    unless success
      _log :ui, 'Skipping other steps'
      notify_clients(:compile_end)
      next
    end

    _log :ui, 'Notifying clients'

    # transform all paths to relatives to the server
    changed.map! do |file|
      relative = relative_path_to_book_file(file)
      File.join('', 'book', relative) unless relative.nil?
    end

    # remove nil paths (for example bookspec can't be found so the relative path is nil)
    changed.compact!

    # if changed.size > 0 && changed.all? { |file| file.end_with?(*Epuber::Compiler::FileFinders::GROUP_EXTENSIONS[:style]) }
      # notify_clients(:styles, changed)
    # else
      notify_clients(:reload, changed)
    # end
  end
end
compile_book(&completion) click to toggle source
# File lib/epuber/server.rb, line 361
def self.compile_book(&completion)
  if !@compilation_thread.nil? && @compilation_thread.status != false
    @compilation_thread.kill
    @compilation_thread = nil
  end

  @compilation_thread = Thread.new do
    result = _compile_book
    completion.call(result) unless completion.nil?
  end
  
  if completion.nil?
    @compilation_thread.join
  end
end
filter_not_project_files(files_paths) click to toggle source

@param [Array<String>] files_paths

@return [Array<String>]

# File lib/epuber/server.rb, line 403
def self.filter_not_project_files(files_paths)
  return nil if file_resolver.nil?

  files_paths.select { |file| file_resolver.file_with_source_path(file) || book.file_path == file }
end
instance_class_accessor(name) click to toggle source
# File lib/epuber/server.rb, line 55
def self.instance_class_accessor(name)
  instance_name = "@#{name}"

  define_singleton_method(name) do
    instance_variable_get(instance_name)
  end
  define_singleton_method("#{name}=") do |new_value|
    instance_variable_set(instance_name, new_value)
  end

  define_method(name) do
    self.class.send(name)
  end
  define_method("#{name}=") do |new_value|
    self.class.send("#{name}=", new_value)
  end
end
new() click to toggle source
Calls superclass method
# File lib/epuber/server.rb, line 147
def initialize
  super
  _log :ui, 'Init compile'

  self.class.compile_book
end
notify_clients(type, data = nil) click to toggle source

@param type [Symbol]

# File lib/epuber/server.rb, line 388
def self.notify_clients(type, data = nil)
  _log :info, "Notifying clients with type #{type.inspect}"
  raise "Not known type `#{type}`" unless [:styles, :reload, :compile_start, :compile_end].include?(type)
  message = {
    name: type,
  }
  message[:data] = data unless data.nil?

  send_to_clients(message.to_json)
end
relative_path_to_book_file(path) click to toggle source

@param [String] path

@return [String]

# File lib/epuber/server.rb, line 238
def self.relative_path_to_book_file(path)
  file = file_resolver.file_with_source_path(path)
  return if file.nil?
  file.pkg_destination_path
end
reload_bookspec() click to toggle source
# File lib/epuber/server.rb, line 332
def self.reload_bookspec
  last_target = target
  Config.instance.bookspec = nil
  self.book = Config.instance.bookspec

  if (new_target = book.target_named(last_target.name))
    self.target = new_target
  else
    self.target = book.targets.first
    _log :ui, "[!] Not found previous target after reloading bookspec file, jumping to first #{self.target.name}"
  end
end
render_bade(file_path) click to toggle source

@param [String] file_path path to bade file to render

@return [String]

# File lib/epuber/server/handlers.rb, line 61
def self.render_bade(file_path)
  renderer = Bade::Renderer.from_file(file_path)
                           .with_locals(book: book, target: target, file_resolver: file_resolver)

  renderer.render(new_line: '', indent: '')
end
run!(book, target, verbose: false) { |URI("http://#{host}:#{port}")| ... } click to toggle source

@return nil

Calls superclass method
# File lib/epuber/server.rb, line 107
def self.run!(book, target, verbose: false)
  Encoding.default_internal = Encoding::UTF_8 if Encoding.default_internal.nil?

  self.book = book
  self.target = target
  self.verbose = verbose

  start_listening_if_needed

  old_stderr = $stderr
  $stderr = StringIO.new unless verbose

  super() do |server|
    $stderr = old_stderr
    puts "Started development server on #{server.host}:#{server.port}"

    host = if server.host == '0.0.0.0'
             'localhost'
           else
             server.host
           end

    yield URI("http://#{host}:#{server.port}") if block_given?
  end
end
send_to_clients(message) click to toggle source

@param message [String]

# File lib/epuber/server.rb, line 379
def self.send_to_clients(message)
  _log :info, "sending message to clients #{message.inspect}"

  sockets.each do |ws|
    ws.send(message)
  end
end
sockets() click to toggle source
# File lib/epuber/server.rb, line 97
def self.sockets
  @sockets ||= []
end
start_listening_if_needed() click to toggle source
# File lib/epuber/server.rb, line 154
def self.start_listening_if_needed
  return unless self.listener.nil?

  self.listener = Listen.to(Config.instance.project_path, debug: true) do |modified, added, removed|
    begin
      changes_detected(modified, added, removed)
    rescue => e
      # print error, do not send error further, listener will die otherwise
      $stderr.puts e
      $stderr.puts e.backtrace
    end
  end

  listener.ignore(%r{\.idea})
  listener.ignore(%r{#{Config.instance.working_path}})
  listener.ignore(%r{#{Config::WORKING_PATH}/})

  listener.start
end
verbose=(verbose) click to toggle source
# File lib/epuber/server.rb, line 133
def self.verbose=(verbose)
  @verbose = verbose
  @default_thin_logger ||= Thin::Logging.logger

  unless verbose
    Thin::Logging.logger = Logger.new(nil)
    Thin::Logging.logger.level = :fatal
  else
    Thin::Logging.logger = @default_thin_logger
  end

  set :logging, verbose
end

Public Instance Methods

_log(level, message) click to toggle source
# File lib/epuber/server.rb, line 207
def _log(level, message)
  self.class._log(level, message)
end
add_auto_refresh_script(html_doc) click to toggle source

@param html_doc [Nokogiri::HTML::Document]

# File lib/epuber/server.rb, line 283
def add_auto_refresh_script(html_doc)
  add_file_to_head(:js, html_doc, 'auto_refresh/reloader.coffee')
  add_file_to_head(:js, html_doc, 'auto_refresh/connector.coffee')
  add_file_to_head(:js, html_doc, 'auto_refresh/protocol.coffee')
  add_file_to_head(:js, html_doc, 'auto_refresh/auto_refresh.coffee')
  add_script_to_head(html_doc, 'var auto_refresh = new AutoRefresh(window, console);')
end
add_file_to_head(type, html_doc, file_path) click to toggle source

@param html_doc [Nokogiri::HTML::Document]

# File lib/epuber/server.rb, line 301
def add_file_to_head(type, html_doc, file_path)
  head = html_doc.at_css('head')
  node = case type
         when :style
           html_doc.create_element('link',  href: "/server/raw/#{file_path}" ,rel: 'stylesheet', type: 'text/css')
         when :js
           html_doc.create_element('script', src: "/server/raw/#{file_path}", type: 'text/javascript')
         else
           raise "Unknown file type `#{type}`"
         end

  return if head.css('script, link').any? { |n| (!n['href'].nil? && n['href'] == node['href']) || (!n['src'].nil? && n['src'] == node['src']) }

  head.add_child(node)
end
add_keyboard_control_script(html_doc, previous_path, next_path) click to toggle source

@param html_doc [Nokogiri::HTML::Document]

# File lib/epuber/server.rb, line 293
def add_keyboard_control_script(html_doc, previous_path, next_path)
  add_script_file_to_head(html_doc, 'keyboard_control.coffee',
                          '$previous_path' => previous_path,
                          '$next_path' => next_path)
end
add_meta_to_head(name, content, html_doc, force: false) click to toggle source

@param html_doc [Nokogiri::HTML::Document] @param key [String] @param value [String]

# File lib/epuber/server.rb, line 321
def add_meta_to_head(name, content, html_doc, force: false)
  head = html_doc.at_css('head')
  meta = head.at_css("meta[name=\"#{name}\"]")
  return if force == false && !meta.nil?

  meta ||= html_doc.create_element('meta')
  meta['name'] = name
  meta['content'] = content
  head.add_child(meta)
end
add_script_file_to_head(html_doc, file_name, *args) { |source| ... } click to toggle source
# File lib/epuber/server.rb, line 244
def add_script_file_to_head(html_doc, file_name, *args)
  source = File.read(File.expand_path("server/#{file_name}", File.dirname(__FILE__)))

  if File.extname(file_name) == '.coffee'
    source = CoffeeScript.compile(source)
  end

  args.each do |hash|
    hash.each do |key, value|
      opt_value = if value
                    "'#{value}'"
                  else
                    'null'
                  end
      source.gsub!(key, opt_value)
    end
  end

  source = yield source if block_given?
  add_script_to_head(html_doc, source)
end
add_script_to_head(html_doc, script_text) click to toggle source

@param html_doc [Nokogiri::HTML::Document] @param script_text [String]

# File lib/epuber/server.rb, line 269
def add_script_to_head(html_doc, script_text)
  script_node = html_doc.create_element('script', script_text, type: 'text/javascript')

  head = html_doc.at_css('head')

  if head.nil?
    head = html_doc.create_element('head')
    html_doc.at_css('html').add_child(head)
  end
  head.add_child(script_node)
end
build_path() click to toggle source

@return [String] base path

# File lib/epuber/server.rb, line 182
def build_path
  self.class.build_path
end
find_file(pattern = params[:splat].first, source_path: build_path) click to toggle source

@param pattern [String]

@return [String] path to file

# File lib/epuber/server.rb, line 220
def find_file(pattern = params[:splat].first, source_path: build_path)
  finder = Compiler::FileFinders::Normal.new(source_path)
  finder.find_files(pattern).first
end
handle_bade(file_path) click to toggle source

@param [String] file_path path to bade file to render

@return [(Fixnum, String)]

# File lib/epuber/server/handlers.rb, line 50
def handle_bade(file_path)
  [200, self.class.render_bade(file_path)]
rescue => e
  env['sinatra.error'] = e
  ShowExceptions.new(self).call(env)
end
handle_file(file_path) click to toggle source

@param [String] file_path

# File lib/epuber/server.rb, line 487
def handle_file(file_path)
  return not_found unless File.exists?(file_path)

  last_modified(File.mtime(file_path))

  case File.extname(file_path)
  when '.styl'
    content_type('text/css')
    body(Stylus.compile(::File.new(file_path)))
  when '.coffee'
    content_type('text/javascript')
    body(CoffeeScript.compile(::File.read(file_path)))
  else
    extname = File.extname(file_path)
    type    = unless Compiler::FileFinders::BINARY_EXTENSIONS.include?(extname)
                mime_type = MIME::Types.of(file_path).first
                if mime_type.nil?
                  'text/plain'
                else
                  content_type
                end
              end

    send_file(file_path, type: type)
  end
end
handle_server_bade(file_name) click to toggle source

@param [String] file_name name of the file located in ./pages/

@return [(Fixnum, String)]

# File lib/epuber/server/handlers.rb, line 42
def handle_server_bade(file_name)
  handle_bade(File.expand_path("pages/#{file_name}", File.dirname(__FILE__)))
end
handle_websocket(path) click to toggle source

@param [String] path

# File lib/epuber/server.rb, line 455
def handle_websocket(path)
  _log :ws, "#{path}: start"
  request.websocket do |ws|
    thread = nil

    ws.onopen do
      sockets << ws

      ws.send({name: :hello}.to_json)

      thread = Thread.new do
        loop do
          sleep(10)
          ws.send({name: :heartbeat}.to_json)
        end
      end
    end

    ws.onmessage do |msg|
      _log :ws, "#{path}: received message: #{msg.inspect}"
    end

    ws.onclose do
      _log :ws, "#{path}: socket closed"
      sockets.delete(ws)
      thread.kill
    end
  end
end
handle_xhtml_file(file_path) click to toggle source

@param [String] file_path absolute path to xhtml file

@return [(Fixnum, String)]

# File lib/epuber/server/handlers.rb, line 11
def handle_xhtml_file(file_path)
  html_doc = Nokogiri::XML(File.open(file_path))

  add_file_to_head(:js, html_doc, 'vendor/bower/jquery/jquery.min.js')
  add_file_to_head(:js, html_doc, 'vendor/bower/spin/spin.js')
  add_file_to_head(:js, html_doc, 'vendor/bower/cookies/cookies.min.js')
  add_file_to_head(:js, html_doc, 'vendor/bower/uri/URI.min.js')
  add_file_to_head(:js, html_doc, 'vendor/bower/keymaster/keymaster.js')
  add_file_to_head(:style, html_doc, 'book_content.styl')

  add_file_to_head(:js, html_doc, 'support.coffee')
  add_meta_to_head(:viewport, 'width=device-width, initial-scale=1.0', html_doc)
  add_auto_refresh_script(html_doc)

  unless file_resolver.nil?
    current_index = file_resolver.spine_files.index { |file| file.final_destination_path == file_path }

    unless current_index.nil?
      previous_path = spine_file_at(current_index - 1).try(:pkg_destination_path)
      next_path     = spine_file_at(current_index + 1).try(:pkg_destination_path)
      add_keyboard_control_script(html_doc, previous_path, next_path)
    end
  end

  [200, html_doc.to_xhtml]
end
spine_file_at(index) click to toggle source

@param index [Fixnum] @return [Epuber::Book::File, nil]

# File lib/epuber/server.rb, line 228
def spine_file_at(index)
  if !file_resolver.nil? && index >= 0 && index < file_resolver.spine_files.count
    file_resolver.spine_files[index]
  end
end