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.



147
148
149
150
151
152
# File 'lib/epuber/server.rb', line 147

def initialize
  super
  _log :ui, 'Init compile'

  self.class.compile_book
end

Class Method Details

._compile_bookObject



345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# 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) ⇒ Object

Returns nil.

Parameters:

  • level (Symbol)
  • message (String)

Returns:

  • nil



192
193
194
195
196
197
198
199
200
201
202
203
204
205
# 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_pathString

Returns base path.

Returns:

  • (String)

    base path



176
177
178
# File 'lib/epuber/server.rb', line 176

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


413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# 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) ⇒ Object



361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# 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) ⇒ Array<String>

Parameters:

  • files_paths (Array<String>)

Returns:

  • (Array<String>)


403
404
405
406
407
# 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) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# 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

.notify_clients(type, data = nil) ⇒ Object

Parameters:

  • type (Symbol)


388
389
390
391
392
393
394
395
396
397
# 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) ⇒ String

Parameters:

  • path (String)

Returns:

  • (String)


238
239
240
241
242
# 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_bookspecObject



332
333
334
335
336
337
338
339
340
341
342
343
# 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) ⇒ String

Parameters:

  • file_path (String)

    path to bade file to render

Returns:

  • (String)


61
62
63
64
65
66
# 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) ⇒ Object

Returns nil.

Returns:

  • nil



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# 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) ⇒ Object

Parameters:

  • message (String)


379
380
381
382
383
384
385
# 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

.socketsObject



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

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

.start_listening_if_neededObject



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# 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) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
# 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

Instance Method Details

#_log(level, message) ⇒ Object



207
208
209
# File 'lib/epuber/server.rb', line 207

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

#add_auto_refresh_script(html_doc) ⇒ Object

Parameters:

  • html_doc (Nokogiri::HTML::Document)


283
284
285
286
287
288
289
# 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) ⇒ Object

Parameters:

  • html_doc (Nokogiri::HTML::Document)


301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# 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) ⇒ Object

Parameters:

  • html_doc (Nokogiri::HTML::Document)


293
294
295
296
297
# 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) ⇒ Object

Parameters:

  • html_doc (Nokogiri::HTML::Document)
  • key (String)
  • value (String)


321
322
323
324
325
326
327
328
329
330
# 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) ⇒ Object



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# 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) ⇒ Object

Parameters:

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


269
270
271
272
273
274
275
276
277
278
279
# 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

#bookEpuber::Book::Book

Returns:

  • (Epuber::Book::Book)


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

instance_class_accessor :book

#build_pathString

Returns base path.

Returns:

  • (String)

    base path



182
183
184
# File 'lib/epuber/server.rb', line 182

def build_path
  self.class.build_path
end

#file_resolverEpuber::Compiler::FileResolver



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

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



220
221
222
223
# 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) ⇒ (Fixnum, String)

Parameters:

  • file_path (String)

    path to bade file to render

Returns:

  • ((Fixnum, String))


50
51
52
53
54
55
# 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) ⇒ Object

Parameters:

  • file_path (String)


487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
# 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) ⇒ (Fixnum, String)

Parameters:

  • file_name (String)

    name of the file located in ./pages/

Returns:

  • ((Fixnum, String))


42
43
44
# 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) ⇒ Object

Parameters:

  • path (String)


455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
# 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) ⇒ (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
36
# 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

#listenerListener

Returns:

  • (Listener)


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

instance_class_accessor :listener

#socketsArray<SinatraWebsocket::Connection>

Returns:

  • (Array<SinatraWebsocket::Connection>)


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

instance_class_accessor :sockets

#spineArray<Epuber::Compiler::File>

Returns:

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


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

instance_class_accessor :spine

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

Parameters:

  • index (Fixnum)

Returns:

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


228
229
230
231
232
# 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

#targetEpuber::Book::Target



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

instance_class_accessor :target

#verboseFalseClass, TrueClass

Returns:

  • (FalseClass, TrueClass)


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

instance_class_accessor :verbose