Class: Epuber::Server

Inherits:
Sinatra::Base
  • Object
show all
Defined in:
lib/epuber/server.rb,
lib/epuber/server/handlers.rb

Overview

API:

LATER

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

Defined Under Namespace

Classes: ShowExceptions

Helpers collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeServer

Returns a new instance of Server.



138
139
140
141
142
143
# File 'lib/epuber/server.rb', line 138

def initialize
  super
  _log :ui, 'Init compile'

  self.class.compile_book
end

Class Method Details

._compile_bookObject



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/epuber/server.rb', line 321

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) ⇒ Object

Returns nil.

Parameters:

  • level (Symbol)
  • message (String)

Returns:

  • nil



183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/epuber/server.rb', line 183

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_pathString

Returns base path.

Returns:

  • (String)

    base path



167
168
169
# File 'lib/epuber/server.rb', line 167

def self.build_path
  Epuber::Config.instance.build_path(target)
end

.changes_detected(_modified, _added, _removed) ⇒ Object

Parameters:

  • _modified (Array<String>)
  • _added (Array<String>)
  • _removed (Array<String>)


388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# File 'lib/epuber/server.rb', line 388

def self.changes_detected(_modified, _added, _removed)
  all_changed = (_modified + _added + _removed).uniq

  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) ⇒ Object



337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/epuber/server.rb', line 337

def self.compile_book(&completion)
  if !@compilation_thread.nil? && @compilation_thread.status != false
    @compilation_thread.kill
    @compilation_thread = nil
  end

  if completion.nil?
    _compile_book
  else
    @compilation_thread = Thread.new do
      completion.call(_compile_book)
    end
  end
end

.filter_not_project_files(files_paths) ⇒ Array<String>

Parameters:

  • files_paths (Array<String>)

Returns:

  • (Array<String>)


378
379
380
381
382
# File 'lib/epuber/server.rb', line 378

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) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/epuber/server.rb', line 54

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

.notify_clients(type, data = nil) ⇒ Object

Parameters:

  • type (Symbol)


363
364
365
366
367
368
369
370
371
372
# File 'lib/epuber/server.rb', line 363

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) ⇒ String

Parameters:

  • path (String)

Returns:

  • (String)


229
230
231
232
233
# File 'lib/epuber/server.rb', line 229

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_bookspecObject



308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/epuber/server.rb', line 308

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) ⇒ String

Parameters:

  • file_path (String)

    path to bade file to render

Returns:

  • (String)


60
61
62
63
64
65
# File 'lib/epuber/server/handlers.rb', line 60

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) ⇒ Object

Returns nil.

Returns:

  • nil



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/epuber/server.rb', line 106

def self.run!(book, target, verbose: false)
  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}"

    yield URI("http://#{server.host}:#{server.port}") if block_given?
  end
end

.send_to_clients(message) ⇒ Object

Parameters:

  • message (String)


354
355
356
357
358
359
360
# File 'lib/epuber/server.rb', line 354

def self.send_to_clients(message)
  _log :info, "sending message to clients #{message.inspect}"

  sockets.each do |ws|
    ws.send(message)
  end
end

.socketsObject



96
97
98
# File 'lib/epuber/server.rb', line 96

def self.sockets
  @sockets ||= []
end

.start_listening_if_neededObject



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/epuber/server.rb', line 145

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) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/epuber/server.rb', line 124

def self.verbose=(verbose)
  @verbose = verbose
  @default_thin_logger ||= Thin::Logging.logger

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

  set :logging, verbose
end

Instance Method Details

#_log(level, message) ⇒ Object



198
199
200
# File 'lib/epuber/server.rb', line 198

def _log(level, message)
  self.class._log(level, message)
end

#add_auto_refresh_script(html_doc) ⇒ Object

Parameters:

  • html_doc (Nokogiri::HTML::Document)


274
275
276
277
278
279
280
# File 'lib/epuber/server.rb', line 274

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) ⇒ Object

Parameters:

  • html_doc (Nokogiri::HTML::Document)


292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/epuber/server.rb', line 292

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) ⇒ Object

Parameters:

  • html_doc (Nokogiri::HTML::Document)


284
285
286
287
288
# File 'lib/epuber/server.rb', line 284

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_script_file_to_head(html_doc, file_name, *args) ⇒ Object



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/epuber/server.rb', line 235

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) ⇒ Object

Parameters:

  • html_doc (Nokogiri::HTML::Document)
  • script_text (String)


260
261
262
263
264
265
266
267
268
269
270
# File 'lib/epuber/server.rb', line 260

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

#bookEpuber::Book::Book

Returns:

  • (Epuber::Book::Book)


74
# File 'lib/epuber/server.rb', line 74

instance_class_accessor :book

#build_pathString

Returns base path.

Returns:

  • (String)

    base path



173
174
175
# File 'lib/epuber/server.rb', line 173

def build_path
  self.class.build_path
end

#file_resolverEpuber::Compiler::FileResolver



86
# File 'lib/epuber/server.rb', line 86

instance_class_accessor :file_resolver

#find_file(pattern = params[:splat].first, source_path: build_path) ⇒ String

Returns path to file.

Parameters:

  • pattern (String) (defaults to: params[:splat].first)

Returns:

  • (String)

    path to file



211
212
213
214
# File 'lib/epuber/server.rb', line 211

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) ⇒ (Fixnum, String)

Parameters:

  • file_path (String)

    path to bade file to render

Returns:

  • ((Fixnum, String))


49
50
51
52
53
54
# File 'lib/epuber/server/handlers.rb', line 49

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) ⇒ Object

Parameters:

  • file_path (String)


462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/epuber/server.rb', line 462

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) ⇒ (Fixnum, String)

Parameters:

  • file_name (String)

    name of the file located in ./pages/

Returns:

  • ((Fixnum, String))


41
42
43
# File 'lib/epuber/server/handlers.rb', line 41

def handle_server_bade(file_name)
  handle_bade(File.expand_path("pages/#{file_name}", File.dirname(__FILE__)))
end

#handle_websocket(path) ⇒ Object

Parameters:

  • path (String)


430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
# File 'lib/epuber/server.rb', line 430

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) ⇒ (Fixnum, String)

Parameters:

  • file_path (String)

    absolute path to xhtml file

Returns:

  • ((Fixnum, String))


11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 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_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_html]
end

#listenerListener

Returns:

  • (Listener)


102
# File 'lib/epuber/server.rb', line 102

instance_class_accessor :listener

#socketsArray<SinatraWebsocket::Connection>

Returns:

  • (Array<SinatraWebsocket::Connection>)


94
# File 'lib/epuber/server.rb', line 94

instance_class_accessor :sockets

#spineArray<Epuber::Compiler::File>

Returns:

  • (Array<Epuber::Compiler::File>)


90
# File 'lib/epuber/server.rb', line 90

instance_class_accessor :spine

#spine_file_at(index) ⇒ Epuber::Book::File?

Parameters:

  • index (Fixnum)

Returns:

  • (Epuber::Book::File, nil)


219
220
221
222
223
# File 'lib/epuber/server.rb', line 219

def spine_file_at(index)
  if !file_resolver.nil? && index >= 0 && index < file_resolver.spine_files.count
    file_resolver.spine_files[index]
  end
end

#targetEpuber::Book::Target



78
# File 'lib/epuber/server.rb', line 78

instance_class_accessor :target

#verboseFalseClass, TrueClass

Returns:

  • (FalseClass, TrueClass)


82
# File 'lib/epuber/server.rb', line 82

instance_class_accessor :verbose