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 341
def self._compile_book
  compiler = Epuber::Compiler.new(book, target)
  compiler.compile(build_path)
  self.file_resolver = compiler.file_resolver

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

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

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

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

@return nil

# File lib/epuber/server.rb, line 187
def self._log(level, message)
  case level
  when :ui
    UI.info message
  when :info
    UI.info "INFO: #{message}" if verbose
  when :get
    UI.info " GET: #{message}" if verbose
  when :ws
    UI.info "  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 172
def self.build_path
  Epuber::Config.instance.build_path(target)
end
changes_detected(modified, added, removed) click to toggle source

@param [Array<String>] modified @param [Array<String>] added @param [Array<String>] removed

# File lib/epuber/server.rb, line 408
def self.changes_detected(modified, added, removed)
  all_changed = (modified + added + removed).uniq.map(&:unicode_normalize)

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

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

  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!

    changed_only_styles = changed.all? do |file|
      file.end_with?(*Epuber::Compiler::FileFinders::GROUP_EXTENSIONS[:style])
    end

    if changed.size.positive? && changed_only_styles
      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 355
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)
  end

  return unless completion.nil?

  @compilation_thread.join
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 398
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 53
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 145
def initialize
  super
  _log :ui, 'Init compile'

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

@param [Symbol] type

# File lib/epuber/server.rb, line 382
def self.notify_clients(type, data = nil)
  _log :info, "Notifying clients with type #{type.inspect}"
  raise "Not known type `#{type}`" unless %i[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 233
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 328
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 #{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 105
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
    UI.info "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 [String] message

# File lib/epuber/server.rb, line 373
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 95
def self.sockets
  @sockets ||= []
end
start_listening_if_needed() click to toggle source
# File lib/epuber/server.rb, line 152
def self.start_listening_if_needed
  return unless listener.nil?

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

  listener.ignore(/\.idea/)
  listener.ignore(/#{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 131
def self.verbose=(verbose)
  @verbose = verbose
  @default_thin_logger ||= Thin::Logging.logger

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

  set :logging, verbose
end

Public Instance Methods

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

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

# File lib/epuber/server.rb, line 277
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 [Nokogiri::HTML::Document] html_doc

# File lib/epuber/server.rb, line 295
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? do |n|
              (!n['href'].nil? && n['href'] == node['href']) || (!n['src'].nil? && n['src'] == node['src'])
            end

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

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

# File lib/epuber/server.rb, line 287
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 [Nokogiri::HTML::Document] html_doc @param [String] key @param [String] value

# File lib/epuber/server.rb, line 317
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 240
def add_script_file_to_head(html_doc, file_name, *args)
  source = File.read(File.expand_path("server/#{file_name}", File.dirname(__FILE__)))

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

  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 [Nokogiri::HTML::Document] html_doc @param [String] script_text

# File lib/epuber/server.rb, line 263
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 178
def build_path
  self.class.build_path
end
find_file(pattern = params[:splat].first, source_path: build_path) click to toggle source

@param [String] pattern

@return [String] path to file

# File lib/epuber/server.rb, line 215
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 StandardError => 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 486
def handle_file(file_path)
  return not_found unless File.exist?(file_path)

  mtime = File.mtime(file_path)
  last_modified(mtime)
  etag(mtime.to_s)
  cache_control :public, :must_revalidate

  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, last_modified: mtime)
  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 454
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 [Fixnum] index @return [Epuber::Book::File, nil]

# File lib/epuber/server.rb, line 223
def spine_file_at(index)
  return unless !file_resolver.nil? && index >= 0 && index < file_resolver.spine_files.count

  file_resolver.spine_files[index]
end