mamemose 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -50,7 +50,7 @@ $ gem install mamemose
50
50
  パスを通す必要があるかも。
51
51
 
52
52
  ```bash
53
- $ mamemose &> /dev/null &
53
+ $ nohup mamemose &> /dev/null &
54
54
  ```
55
55
 
56
56
  するとローカルで HTTP サーバが立ち上がります。その後ブラウザから
@@ -69,6 +69,23 @@ http://localhost:PORT/
69
69
  - 文字コードは UTF-8 で書くようにしてください。
70
70
  - コマンドラインオプションは `mamemose help` で出ます。一応。
71
71
 
72
+
73
+ ### 自動更新
74
+
75
+ WebSocket を使って自動更新できます。
76
+ mamemose サーバを立てた後アクセスされたファイルを監視しておき、
77
+ 更新があればそのファイルを開いているブラウザのページを自動的にリロードします。
78
+
79
+ 現在のところ、 WebSocket 用のサーバを別に立てておくという設計になっています。
80
+ 以下のマンドを叩いて mamemose WebSocket サーバを起動しておいてください。
81
+
82
+ ```bash
83
+ $ nohup mamemose_websocket &> /dev/null &
84
+ ```
85
+
86
+ mamemose WebSocket サーバを立てなくても利用できます。
87
+ その場合は手動で更新してください。
88
+
72
89
  ### 一時ファイル閲覧
73
90
 
74
91
  一時的に `DOCUMENT_ROOT` で指定したディレクトリ以外にあるファイルを
data/bin/mamemose CHANGED
@@ -13,7 +13,7 @@ class Mamemose::CLI < Thor
13
13
  def server(filename=nil, port=nil)
14
14
  mamemose = Mamemose::Server.new(port)
15
15
  if !filename && !port
16
- mamemose.start
16
+ mamemose.server
17
17
  elsif filename && port
18
18
  mamemose.file(filename)
19
19
  else
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path("../../lib", __FILE__)
4
+
5
+ load 'mamemose/websocket.rb'
6
+
7
+ require 'rubygems'
8
+ require 'thor'
9
+
10
+ class Mamemose::WebSocket::CLI < Thor
11
+ desc "server", "run the mamemose websocket server"
12
+ def server
13
+ ws = Mamemose::WebSocket::Server.new
14
+ ws.start
15
+ end
16
+
17
+ desc "s", "alias of server"
18
+ alias :s :server
19
+
20
+ desc "version", "print version"
21
+ def version
22
+ puts Mamemose::WebSocket::VERSION
23
+ end
24
+ end
25
+
26
+ args = ARGV == [] ? ["s"] : ARGV
27
+ Mamemose::WebSocket::CLI.start(args)
@@ -0,0 +1,19 @@
1
+ conf = File.expand_path("~" + File::SEPARATOR + ".mamemose.rb")
2
+ load conf if File.exists?(conf)
3
+
4
+ DOCUMENT_ROOT = "~/Dropbox/memo" if !defined?(DOCUMENT_ROOT)
5
+ PORT = 20000 if !defined?(PORT)
6
+ WS_PORT = 30000 if !defined?(WS_PORT)
7
+ RECENT_NUM = 10 if !defined?(RECENT_NUM)
8
+ RECENT_PATTERN = /.*/ if !defined?(RECENT_PATTERN)
9
+ IGNORE_FILES = ['.DS_Store','.AppleDouble','.LSOverride','Icon',/^\./,/~$/,
10
+ '.Spotlight-V100','.Trashes','Thumbs.db','ehthumbs.db',
11
+ 'Desktop.ini','$RECYCLE.BIN',/^#/,'MathJax','syntaxhighlighter'] if !defined?(IGNORE_FILES)
12
+ MARKDOWN_PATTERN = /\.(md|markdown)$/ if !defined?(MARKDOWN_PATTERN)
13
+ INDEX_PATTERN = /^README/i if !defined?(INDEX_PATTERN)
14
+ CUSTOM_HEADER = '' if !defined?(CUSTOM_HEADER)
15
+ CUSTOM_BODY = '' if !defined?(CUSTOM_BODY)
16
+ CUSTOM_FOOTER = '' if !defined?(CUSTOM_FOOTER)
17
+
18
+ CONTENT_TYPE = "text/html; charset=utf-8"
19
+ DIR = File::expand_path(DOCUMENT_ROOT, '/')
@@ -0,0 +1,32 @@
1
+ module Mamemose::Path
2
+ # returns escaped characters so that the markdown parser doesn't interpret it has special meaning.
3
+ def escape(text)
4
+ return text.gsub(/[\`*_{}\[\]()#+\-.!]/, "\\\\\\0")
5
+ end
6
+
7
+ # returns /-rooted path. eg. /path/to/my_document.md
8
+ def uri(path)
9
+ s = File::expand_path(path).gsub(DIR, "").gsub(File::SEPARATOR, '/')
10
+ return s == '' ? '/' : s
11
+ end
12
+
13
+ # returns fullpath. eg. /home/daimatz/Dropbox/memo/path/to/my_document.md
14
+ def fullpath(uri)
15
+ return File.join(DIR, uri.gsub(DIR, '').gsub('/', File::SEPARATOR))
16
+ end
17
+
18
+ # returns DOCUMENT_ROOT-rooted path. eg. ~/Dropbox/memo/path/to/my_document.md
19
+ def docpath(uri)
20
+ return File.join(DOCUMENT_ROOT, uri.gsub('/', File::SEPARATOR)).gsub(/#{File::SEPARATOR}$/, "")
21
+ end
22
+
23
+ # returns DOCUMENT_ROOT-rooted path, but escaped. eg. ~/Dropbox/memo/path/to/my\_document.md
24
+ # used in user-viewable (HTML) context.
25
+ def showpath(uri)
26
+ return escape(docpath(uri))
27
+ end
28
+
29
+ def escaped_basename(filename)
30
+ return escape(File::basename(filename))
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ module Mamemose::Util
2
+ def debug(tag, msg)
3
+ STDERR.puts "#{tag}: #{msg}"
4
+ end
5
+ end
@@ -1,3 +1,7 @@
1
1
  module Mamemose
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
+
4
+ module WebSocket
5
+ VERSION = "0.1.0"
6
+ end
3
7
  end
@@ -0,0 +1,108 @@
1
+ require 'em-websocket'
2
+ require 'thread'
3
+
4
+ require 'mamemose/version'
5
+
6
+ require 'mamemose/path'
7
+ require 'mamemose/util'
8
+
9
+ require 'mamemose/env'
10
+
11
+ class Mamemose::WebSocket::Server
12
+ include Mamemose::Util
13
+
14
+ @@update_send_message = "updated"
15
+
16
+ def initialize
17
+ @connections = []
18
+ @mutex = Mutex::new
19
+ @tag = "WebSocket"
20
+ end
21
+
22
+ def start
23
+ Thread.new do
24
+ debug(@tag, "start watcher...")
25
+ watcher
26
+ end
27
+
28
+ EventMachine::WebSocket.start(:host => '0.0.0.0', :port => WS_PORT) do |ws|
29
+ ws.onopen {
30
+ debug(@tag, "connected.")
31
+ ws.send("connected.")
32
+ }
33
+
34
+ ws.onmessage { |fullpath|
35
+ # receive url from client
36
+ debug(@tag, "receive: #{fullpath}")
37
+ if File.exists?(fullpath)
38
+ # connections are managed as tuple of (socket, url, mtime_cache)
39
+ con = {:ws => ws, :fullpath => fullpath, :mtime_cache => get_mtime(fullpath)}
40
+ @mutex.synchronize do
41
+ @connections.push(con) unless @connections.index(con)
42
+ debug(@tag, "added path to watch: #{fullpath}. now watch #{fullpaths.to_s}")
43
+ end
44
+ end
45
+ }
46
+
47
+ ws.onclose {
48
+ debug(@tag, "closed.")
49
+ # when a connection is closed, delete it from @connections
50
+ @mutex.synchronize do
51
+ @connections.delete_if { |con| con[:ws] == ws }
52
+ debug(@tag, "closed and removed path. now watch #{fullpaths.to_s}")
53
+ end
54
+ }
55
+ end
56
+ end
57
+
58
+ def watcher
59
+ loop do
60
+ # gather paths to watch using mutex
61
+ watch_fullpaths = []
62
+ @mutex.synchronize do
63
+ watch_fullpaths = fullpaths
64
+ end
65
+
66
+ # gather mtimes of watch_fullpaths
67
+ mtimes = {}
68
+ watch_fullpaths.uniq.each do |fullpath|
69
+ if File.exists?(fullpath)
70
+ # get mtime
71
+ mtimes[fullpath] = get_mtime(fullpath)
72
+ else
73
+ # file no longer exists. remove the entry
74
+ @mutex.synchronize do
75
+ @connections.delete_if { |con| con[:fullpath] == fullpath }
76
+ debug(@tag, "detected deletion: #{fullpath} and updated the list. now watch #{fullpaths.to_s}")
77
+ end
78
+ end
79
+ end
80
+
81
+ # push notification if watching file is updated
82
+ to_notify = []
83
+ @mutex.synchronize do
84
+ @connections.each do |con|
85
+ fullpath = con[:fullpath]
86
+ if mtimes[fullpath] && con[:mtime_cache] < mtimes[fullpath]
87
+ debug(@tag, "detected update: #{fullpath}. pushing...")
88
+ con[:mtime_cache] = mtimes[fullpath]
89
+ to_notify << con
90
+ end
91
+ end
92
+ end
93
+ to_notify.each do |con|
94
+ con[:ws].send(@@update_send_message)
95
+ end
96
+
97
+ sleep 1
98
+ end
99
+ end
100
+
101
+ def fullpaths
102
+ @connections.map{ |con| con[:fullpath] }
103
+ end
104
+
105
+ def get_mtime(fullpath)
106
+ File.mtime(fullpath) if File.exists?(fullpath)
107
+ end
108
+ end
data/lib/mamemose.rb CHANGED
@@ -5,26 +5,11 @@ require 'uri'
5
5
  require 'redcarpet'
6
6
  require 'htmlentities'
7
7
 
8
- require "mamemose/version"
9
-
10
- conf = File.expand_path("~" + File::SEPARATOR + ".mamemose.rb")
11
- load conf if File.exists?(conf)
12
-
13
- DOCUMENT_ROOT = "~/Dropbox/memo" if !defined?(DOCUMENT_ROOT)
14
- PORT = 20000 if !defined?(PORT)
15
- RECENT_NUM = 10 if !defined?(RECENT_NUM)
16
- RECENT_PATTERN = /.*/ if !defined?(RECENT_PATTERN)
17
- IGNORE_FILES = ['.DS_Store','.AppleDouble','.LSOverride','Icon',/^\./,/~$/,
18
- '.Spotlight-V100','.Trashes','Thumbs.db','ehthumbs.db',
19
- 'Desktop.ini','$RECYCLE.BIN',/^#/,'MathJax','syntaxhighlighter'] if !defined?(IGNORE_FILES)
20
- MARKDOWN_PATTERN = /\.(md|markdown)$/ if !defined?(MARKDOWN_PATTERN)
21
- INDEX_PATTERN = /^README/i if !defined?(INDEX_PATTERN)
22
- CUSTOM_HEADER = '' if !defined?(CUSTOM_HEADER)
23
- CUSTOM_BODY = '' if !defined?(CUSTOM_BODY)
24
- CUSTOM_FOOTER = '' if !defined?(CUSTOM_FOOTER)
25
-
26
- CONTENT_TYPE = "text/html; charset=utf-8"
27
- DIR = File::expand_path(DOCUMENT_ROOT, '/')
8
+ require 'mamemose/version'
9
+
10
+ require 'mamemose/path'
11
+
12
+ require 'mamemose/env'
28
13
 
29
14
  class HTMLwithSyntaxHighlighter < Redcarpet::Render::XHTML
30
15
  def block_code(code, lang)
@@ -35,21 +20,28 @@ class HTMLwithSyntaxHighlighter < Redcarpet::Render::XHTML
35
20
  end
36
21
 
37
22
  class Mamemose::Server
23
+ include Mamemose::Path
24
+
38
25
  def initialize(port)
39
- @server = WEBrick::HTTPServer.new({ :Port => port ? port.to_i : PORT })
40
- trap(:INT){@server.shutdown}
41
- trap(:TERM){@server.shutdown}
26
+ @mamemose = WEBrick::HTTPServer.new({ :Port => port ? port.to_i : PORT })
27
+ trap(:INT){finalize}
28
+ trap(:TERM){finalize}
42
29
  end
43
30
 
44
31
  def start
45
- @server.mount_proc('/') do |req, res|
32
+ @mamemose.start
33
+ end
34
+
35
+ def server
36
+ @mamemose.mount_proc('/') do |req, res|
46
37
  res['Cache-Control'] = 'no-cache, no-store, must-revalidate'
47
38
  res['Pragma'] = 'no-cache'
48
39
  res['Expires'] = '0'
49
40
 
41
+ p fullpath(req.path)
50
42
  if req.path =~ /^\/search/
51
43
  res = req_search(req, res)
52
- elsif File.directory?(fullpath(req.path)) then
44
+ elsif File.directory?(fullpath(req.path))
53
45
  res = req_index(req, res)
54
46
  elsif File.exists?(fullpath(req.path))
55
47
  res = req_file(fullpath(req.path), res, false)
@@ -57,25 +49,28 @@ class Mamemose::Server
57
49
  res.status = WEBrick::HTTPStatus::RC_NOT_FOUND
58
50
  end
59
51
  end
60
-
61
- @server.start
52
+ start
62
53
  end
63
54
 
64
55
  def file(filename)
65
- @server.mount_proc('/') do |req, res|
56
+ @mamemose.mount_proc('/') do |req, res|
66
57
  res['Cache-Control'] = 'no-cache, no-store, must-revalidate'
67
58
  res['Pragma'] = 'no-cache'
68
59
  res['Expires'] = '0'
69
60
  res = req_file(File.absolute_path(filename), res, true)
70
61
  res.content_type = CONTENT_TYPE
71
62
  end
72
-
73
- @server.start
63
+ start
74
64
  end
75
65
 
76
66
  private
77
67
 
78
- def header_html(title, path)
68
+ def finalize
69
+ Thread::list.each {|t| Thread::kill(t) if t != Thread::current}
70
+ @mamemose.shutdown
71
+ end
72
+
73
+ def header_html(title, fullpath)
79
74
  html = <<HTML
80
75
  <!DOCTYPE HTML>
81
76
  <html>
@@ -221,6 +216,23 @@ function copy(text) {
221
216
  prompt("Copy filepath below:", text);
222
217
  }
223
218
  </script>
219
+ <script>
220
+ (function(){
221
+ var fullpath = "#{fullpath}";
222
+ ws = new WebSocket("ws://localhost:#{WS_PORT}");
223
+ ws.onopen = function() {
224
+ console.log("WebSocket (port=#{WS_PORT}) connected: " + fullpath);
225
+ ws.send(fullpath);
226
+ };
227
+ ws.onmessage = function(evt) {
228
+ console.log("received: " + evt.data);
229
+ if (evt.data == "updated") {
230
+ console.log("update detected. reloading...");
231
+ location.reload();
232
+ }
233
+ };
234
+ })();
235
+ </script>
224
236
  #{CUSTOM_HEADER}
225
237
  </head>
226
238
  <body>
@@ -288,7 +300,7 @@ HTML
288
300
  value.each {|v| body += link_list(v[0], v[1])}
289
301
  end
290
302
 
291
- res.body = header_html(title, uri(path))\
303
+ res.body = header_html(title, path)\
292
304
  + search_form(uri(path), q)\
293
305
  + markdown(body)\
294
306
  + footer_html
@@ -321,7 +333,7 @@ HTML
321
333
  body += File.read(index)
322
334
  end
323
335
 
324
- res.body = header_html(title, req.path)\
336
+ res.body = header_html(title, directory)\
325
337
  + search_form(uri(req.path))\
326
338
  + markdown(body)\
327
339
  + footer_html(index)
@@ -407,37 +419,6 @@ HTML
407
419
  return {:dirs=>dirs, :markdowns=>markdowns, :others=>others}
408
420
  end
409
421
 
410
- # returns escaped characters so that the markdown parser doesn't interpret it has special meaning.
411
- def escape(text)
412
- return text.gsub(/[\`*_{}\[\]()#+\-.!]/, "\\\\\\0")
413
- end
414
-
415
- # returns /-rooted path. eg. /path/to/my_document.md
416
- def uri(path)
417
- s = File::expand_path(path).gsub(DIR, "").gsub(File::SEPARATOR, '/')
418
- return s == '' ? '/' : s
419
- end
420
-
421
- # returns fullpath. eg. /home/daimatz/Dropbox/memo/path/to/my_document.md
422
- def fullpath(uri)
423
- return File.join(DIR, uri.gsub('/', File::SEPARATOR))
424
- end
425
-
426
- # returns DOCUMENT_ROOT-rooted path. eg. ~/Dropbox/memo/path/to/my_document.md
427
- def docpath(uri)
428
- return File.join(DOCUMENT_ROOT, uri.gsub('/', File::SEPARATOR)).gsub(/#{File::SEPARATOR}$/, "")
429
- end
430
-
431
- # returns DOCUMENT_ROOT-rooted path, but escaped. eg. ~/Dropbox/memo/path/to/my\_document.md
432
- # used in user-viewable (HTML) context.
433
- def showpath(uri)
434
- return escape(docpath(uri))
435
- end
436
-
437
- def escaped_basename(filename)
438
- return escape(File::basename(filename))
439
- end
440
-
441
422
  def link_list(title, link)
442
423
  file = fullpath(link)
443
424
  str = filesize(file)
data/mamemose.gemspec CHANGED
@@ -17,5 +17,6 @@ Gem::Specification.new do |gem|
17
17
 
18
18
  gem.add_dependency "redcarpet", ">= 2.2.0"
19
19
  gem.add_dependency "htmlentities", ">= 4.3.0"
20
- gem.add_dependency "thor"
20
+ gem.add_dependency "thor", ">= 0.17.0"
21
+ gem.add_dependency "em-websocket", ">= 0.5.0"
21
22
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mamemose
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-16 00:00:00.000000000 Z
12
+ date: 2013-03-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redcarpet
@@ -50,7 +50,7 @@ dependencies:
50
50
  requirements:
51
51
  - - ! '>='
52
52
  - !ruby/object:Gem::Version
53
- version: '0'
53
+ version: 0.17.0
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
@@ -58,12 +58,29 @@ dependencies:
58
58
  requirements:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 0.17.0
62
+ - !ruby/object:Gem::Dependency
63
+ name: em-websocket
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: 0.5.0
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 0.5.0
62
78
  description: Markdown memo server
63
79
  email:
64
80
  - dai@daimatz.net
65
81
  executables:
66
82
  - mamemose
83
+ - mamemose_websocket
67
84
  extensions: []
68
85
  extra_rdoc_files: []
69
86
  files:
@@ -73,9 +90,14 @@ files:
73
90
  - README.md
74
91
  - Rakefile
75
92
  - bin/mamemose
93
+ - bin/mamemose_websocket
76
94
  - index.png
77
95
  - lib/mamemose.rb
96
+ - lib/mamemose/env.rb
97
+ - lib/mamemose/path.rb
98
+ - lib/mamemose/util.rb
78
99
  - lib/mamemose/version.rb
100
+ - lib/mamemose/websocket.rb
79
101
  - mamemose.gemspec
80
102
  - sample.png
81
103
  homepage: https://github.com/daimatz/mamemose