xrefresh-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2008-06-18
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/License.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Antonin Hildebrand
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,26 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ PostInstall.txt
5
+ README.txt
6
+ Rakefile
7
+ bin/xrefresh-server
8
+ config/hoe.rb
9
+ config/requirements.rb
10
+ lib/client.rb
11
+ lib/monitor.rb
12
+ lib/server.rb
13
+ lib/xrefresh-server.rb
14
+ script/console
15
+ script/destroy
16
+ script/generate
17
+ script/txt2html
18
+ setup.rb
19
+ tasks/deployment.rake
20
+ tasks/environment.rake
21
+ tasks/website.rake
22
+ website/index.html
23
+ website/index.txt
24
+ website/javascripts/rounded_corners_lite.inc.js
25
+ website/stylesheets/screen.css
26
+ website/template.html.erb
data/PostInstall.txt ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ To run monitoring server launch:
3
+ xrefresh-server
4
+ (when first launched, you will be asked to setup config file)
5
+ For more information see http://xrefresh-server.rubyforge.org
6
+ ---
data/README.txt ADDED
@@ -0,0 +1,48 @@
1
+ = xrefresh-server
2
+
3
+ http://xrefresh.com
4
+
5
+ == DESCRIPTION:
6
+
7
+ FIX (describe your package)
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * FIX (list of features or problems)
12
+
13
+ == SYNOPSIS:
14
+
15
+ FIX (code sample of usage)
16
+
17
+ == REQUIREMENTS:
18
+
19
+ * FIX (list of requirements)
20
+
21
+ == INSTALL:
22
+
23
+ * FIX (sudo gem install, anything else)
24
+
25
+ == LICENSE:
26
+
27
+ (The MIT License)
28
+
29
+ Copyright (c) 2008 Antonin Hildebrand
30
+
31
+ Permission is hereby granted, free of charge, to any person obtaining
32
+ a copy of this software and associated documentation files (the
33
+ 'Software'), to deal in the Software without restriction, including
34
+ without limitation the rights to use, copy, modify, merge, publish,
35
+ distribute, sublicense, and/or sell copies of the Software, and to
36
+ permit persons to whom the Software is furnished to do so, subject to
37
+ the following conditions:
38
+
39
+ The above copyright notice and this permission notice shall be
40
+ included in all copies or substantial portions of the Software.
41
+
42
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
43
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
44
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
45
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
46
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
47
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
48
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env ruby -rubygems
2
+
3
+ # This script watches modifications on the given directories, using the new # FSEvents API in Leopard.
4
+ # Depends on rubycocoa!
5
+ # Needs rubygems and json gem!
6
+
7
+ # Based on code by Dave Dribin
8
+ # http://www.dribin.org/dave/blog/archives/2008/01/04/fswatch/
9
+
10
+ require 'set'
11
+ require 'optparse'
12
+ require 'ostruct'
13
+ require "yaml"
14
+ begin
15
+ require 'xrefresh-server'
16
+ rescue LoadError
17
+ require '../lib/xrefresh-server.rb'
18
+ end
19
+ begin
20
+ require 'json'
21
+ rescue LoadError
22
+ die 'You must "sudo gem install json" to get xrefresh monitor running'
23
+ end
24
+
25
+ module XRefreshServer
26
+
27
+ ################################################################################
28
+ # command-line parsing
29
+
30
+ $COMMAND = File.basename($0)
31
+ $USAGE = "Usage: #{$COMMAND} [OPTIONS]"
32
+ $VERSION = XRefreshServer::VERSION
33
+ $AGENT = "OSX xrefresh-server"
34
+ CONFIG_FILE = ".xrefresh-server.yml"
35
+
36
+ options = OpenStruct.new
37
+ options.output = "-"
38
+ options.config = nil
39
+
40
+ opts = OptionParser.new do |o|
41
+ o.banner = $USAGE
42
+ o.separator ""
43
+ o.separator "Specific options:"
44
+
45
+ o.on("-c", "--config FILE", "Config file") do |fn|
46
+ options.config = fn
47
+ end
48
+
49
+ o.on("-g", "--generate [FILE]", "Generates default config file on given path") do |fn|
50
+ fn = "~/#{CONFIG_FILE}" if fn.nil?
51
+ XRefreshServer::generate_config(File.expand_path(fn))
52
+ exit
53
+ end
54
+
55
+ o.on("-o", "--output FILE", "Write output to a file") do |fn|
56
+ options.output = fn
57
+ end
58
+
59
+ o.on_tail("-h", "--help", "Show this message") do
60
+ puts o
61
+ exit
62
+ end
63
+
64
+ o.on_tail("-v", "--version", "Show version") do
65
+ puts $VERSION
66
+ exit
67
+ end
68
+ end
69
+
70
+ begin
71
+ opts.parse!(ARGV)
72
+ rescue
73
+ die "Unable to parse options: #{$!}"
74
+ end
75
+
76
+ # initialize output handle
77
+ if options.output == "-"
78
+ $out = $stdout.clone
79
+ else
80
+ $out = File.open(options.output, "w")
81
+ end
82
+
83
+ ################################################################################
84
+ # load config
85
+ unless options.config
86
+ path = File.expand_path("~/#{CONFIG_FILE}")
87
+ options.config = path if File.exists?(path)
88
+ path = File.expand_path("./#{CONFIG_FILE}")
89
+ options.config = path if File.exists?(path)
90
+ end
91
+ unless options.config
92
+ puts "Config file #{CONFIG_FILE} not found in current folder or home."
93
+ puts "It seems you are running xrefresh-server for first time."
94
+ puts "Do you want to generate default config file in home directory? [Yn]"
95
+ s = STDIN.getc.chr
96
+ if s=='y' || s=='Y'
97
+ options.config = File.expand_path("~/#{CONFIG_FILE}")
98
+ generate_config(options.config)
99
+ else
100
+ die "Please launch xrefresh-server with -c option and specify path to your config"
101
+ end
102
+ end
103
+ begin
104
+ CONFIG = YAML::load_file(options.config)
105
+ rescue
106
+ die "Unable to load or parse config: #{options.config}"
107
+ end
108
+
109
+ # sanitize config values
110
+ CONFIG["dir_include"] = '.*' unless CONFIG["dir_include"]
111
+ CONFIG["file_include"] = '.*' unless CONFIG["file_include"]
112
+ CONFIG["dir_exclude"] = '^$' unless CONFIG["dir_exclude"]
113
+ CONFIG["file_exclude"] = '^$' unless CONFIG["file_exclude"]
114
+ CONFIG["dir_include"] = Regexp.new(CONFIG["dir_include"])
115
+ CONFIG["file_include"] = Regexp.new(CONFIG["file_include"])
116
+ CONFIG["dir_exclude"] = Regexp.new(CONFIG["dir_exclude"])
117
+ CONFIG["file_exclude"] = Regexp.new(CONFIG["file_exclude"])
118
+ CONFIG["max_connections"] = 4 unless CONFIG["max_connections"]
119
+ CONFIG["host"] = GServer::DEFAULT_HOST unless CONFIG["host"]
120
+ CONFIG["debug"] = !!CONFIG["debug"]
121
+ CONFIG["audit"] = !!CONFIG["audit"]
122
+ CONFIG["defer_time"] = 0.5 unless CONFIG["defer_time"]
123
+ CONFIG["sleep_time"] = 0.1 unless CONFIG["sleep_time"]
124
+ CONFIG.freeze
125
+
126
+ ################################################################################
127
+ # run server
128
+ $out.puts "Starting server on #{CONFIG["host"]}:#{CONFIG["port"]} (max #{CONFIG["max_connections"]} clients)"
129
+ server = Server.new(CONFIG["port"], CONFIG["host"], CONFIG["max_connections"], $stderr, CONFIG["audit"], CONFIG["debug"])
130
+ server.start
131
+
132
+ ################################################################################
133
+ # run filesystem monitoring loop
134
+ start_id = FSEventsGetCurrentEventId()
135
+ start_time = Time.now.to_i # used to compare with mtime, which only has second accuracy
136
+ monitor = Monitor.new(server, CONFIG)
137
+ monitor.schedule(start_id)
138
+ monitor.run_loop(start_time) # blocking call
139
+
140
+ ################################################################################
141
+ # leave in peace
142
+ $out.flush
143
+ exit(0)
144
+ end
data/config/hoe.rb ADDED
@@ -0,0 +1,73 @@
1
+ require "lib/xrefresh-server.rb"
2
+
3
+ AUTHOR = 'Antonin Hildebrand' # can also be an array of Authors
4
+ EMAIL = "antonin@hildebrand.cz"
5
+ DESCRIPTION = "XRefresh filesystem monitor - browser refresh automation tool for web developers"
6
+ GEM_NAME = 'xrefresh-server' # what ppl will type to install your gem
7
+ RUBYFORGE_PROJECT = 'xrefresh-server' # The unix name for your project
8
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+ EXTRA_DEPENDENCIES = [
11
+ ['json', '>= 1.0.0']
12
+ ] # An array of rubygem dependencies [name, version]
13
+ REV = nil
14
+ # UNCOMMENT IF REQUIRED:
15
+ # REV = YAML.load(`svn info`)['Revision']
16
+ VERS = XRefreshServer::VERSION + (REV ? ".#{REV}" : "")
17
+
18
+ @config_file = "~/.rubyforge/user-config.yml"
19
+ @config = nil
20
+ RUBYFORGE_USERNAME = "woid"
21
+ def rubyforge_username
22
+ unless @config
23
+ begin
24
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
25
+ rescue
26
+ puts <<-EOS
27
+ ERROR: No rubyforge config file found: #{@config_file}
28
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
29
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
30
+ EOS
31
+ exit
32
+ end
33
+ end
34
+ RUBYFORGE_USERNAME.replace @config["username"]
35
+ end
36
+
37
+
38
+ RDOC_OPTS = ['--quiet', '--title', 'xrefresh-server documentation',
39
+ "--opname", "index.html",
40
+ "--line-numbers",
41
+ "--main", "README",
42
+ "--inline-source"]
43
+
44
+ class Hoe
45
+ def extra_deps
46
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
47
+ @extra_deps
48
+ end
49
+ end
50
+
51
+ # Generate all the Rake tasks
52
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
53
+ $hoe = Hoe.new(GEM_NAME, VERS) do |p|
54
+ p.developer(AUTHOR, EMAIL)
55
+ p.description = DESCRIPTION
56
+ p.summary = DESCRIPTION
57
+ p.url = HOMEPATH
58
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
59
+ p.test_globs = ["test/**/test_*.rb"]
60
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
61
+
62
+ # == Optional
63
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
64
+ #p.extra_deps = EXTRA_DEPENDENCIES
65
+
66
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
67
+ end
68
+
69
+ CHANGES = $hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
70
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
71
+ $hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
72
+ $hoe.rsync_args = '-av --delete --ignore-errors'
73
+ $hoe.spec.post_install_message = File.open(File.dirname(__FILE__) + "/../PostInstall.txt").read rescue ""
@@ -0,0 +1,15 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
data/lib/client.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'json'
2
+
3
+ module XRefreshServer
4
+
5
+ # client representation on server side
6
+ class Client
7
+ attr :id, :dead
8
+
9
+ def initialize(id, socket)
10
+ @id = id
11
+ @socket = socket
12
+ @dead = false
13
+ end
14
+
15
+ def send(data)
16
+ return if @dead
17
+ begin
18
+ @socket << data
19
+ rescue
20
+ $out.puts "Client ##{@id} is dead"
21
+ @dead = true
22
+ end
23
+ end
24
+
25
+ def send_about(version, agent)
26
+ send({"command" => "AboutMe", "version" => version, "agent" => agent}.to_json)
27
+ end
28
+
29
+ def send_do_refresh(root, name, type, date, time, files)
30
+ send({"command" => "DoRefresh", "root" => root, "name" => name, "date" => date, "time" => time, "type" => type, "files" => files}.to_json)
31
+ end
32
+ end
33
+
34
+ end
data/lib/monitor.rb ADDED
@@ -0,0 +1,172 @@
1
+ require 'osx/foundation'
2
+ OSX.require_framework '/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework'
3
+ include OSX
4
+
5
+ module XRefreshServer
6
+
7
+ class Monitor
8
+
9
+ def initialize(server, config)
10
+ @config = config
11
+ @server = server
12
+ @modified_dirs = Set.new
13
+ @paths_info = Hash.new
14
+ @streams = []
15
+ end
16
+
17
+ def schedule(start_event_id)
18
+ fsevents_cb = proc do |stream, ctx, numEvents, paths, marks, eventIDs|
19
+ # ctx doesn't work through rubycocoa?
20
+ root = FSEventStreamCopyPathsBeingWatched(stream).first
21
+ paths.regard_as('*')
22
+ numEvents.times do |n|
23
+ $out.puts "Event: #{paths[n]}" if @config["debug"]
24
+ @modified_dirs.add({:root=>root, :dir=>paths[n]})
25
+ end
26
+ end
27
+
28
+ @config["paths"].each do |path|
29
+ $out.puts " monitoring #{path}"
30
+ # need to create new stream for every supplied path
31
+ # because we want to report registered sources of the changes
32
+ stream = FSEventStreamCreate(
33
+ KCFAllocatorDefault,
34
+ fsevents_cb,
35
+ nil,
36
+ [path],
37
+ start_event_id,
38
+ @config["defer_time"],
39
+ KFSEventStreamCreateFlagNone) #KFSEventStreamCreateFlagNoDefer
40
+ die "Failed to create the FSEventStream" unless stream
41
+
42
+ FSEventStreamScheduleWithRunLoop(
43
+ stream,
44
+ CFRunLoopGetCurrent(),
45
+ KCFRunLoopDefaultMode)
46
+
47
+ ok = FSEventStreamStart(stream)
48
+ die "Failed to start the FSEventStream" unless ok
49
+
50
+ @streams << stream
51
+ end
52
+ end
53
+
54
+ # blocking call
55
+ def run_loop(start_time)
56
+
57
+ activities = {"changed" => '*', "deleted" => '-', "created" => '+', "renamed" => '>'}
58
+
59
+ # main loop
60
+ $out.puts "Waiting for file system events ..."
61
+ not_first_time = false
62
+ loop do
63
+ if @server.stopped?
64
+ $out.puts "Server stopped"
65
+ break
66
+ end
67
+ @streams.each do |stream|
68
+ FSEventStreamFlushSync(stream)
69
+ end
70
+ buckets = Hash.new()
71
+ if not_first_time # all root folders are reported during first stream flush
72
+ @modified_dirs.each do |dir_info|
73
+ begin
74
+ dir = dir_info[:dir]
75
+ root = dir_info[:root]
76
+ unless dir=~@config["dir_include"]
77
+ $out.puts "debug: #{dir} rejected because dir_include" if @config["debug"]
78
+ next
79
+ end
80
+ if dir=~@config["dir_exclude"]
81
+ $out.puts "debug: #{dir} rejected because dir_exclude" if @config["debug"]
82
+ next
83
+ end
84
+
85
+ if File.exists?(dir)
86
+ $out.puts "debug: checking dir #{dir}" if @config["debug"]
87
+ Dir.foreach(dir) do |file|
88
+ unless file=~@config["file_include"]
89
+ $out.puts "debug: #{file} rejected because file_include" if @config["debug"]
90
+ next
91
+ end
92
+ if file=~@config["file_exclude"]
93
+ $out.puts "debug: #{file} rejected because file_exclude" if @config["debug"]
94
+ next
95
+ end
96
+
97
+ full_path = File.join(dir, file)
98
+ next if File.directory?(full_path)
99
+ begin
100
+ stat = File.stat(full_path)
101
+ $out.puts "debug: stat #{full_path}" if @config["debug"]
102
+ rescue
103
+ # file may not exist
104
+ $out.puts "debug: stat failed #{full_path}" if @config["debug"]
105
+ next # keep silence
106
+ end
107
+ current_time = stat.mtime.to_i
108
+ original_time = @paths_info[full_path] || start_time
109
+
110
+ if (current_time > original_time)
111
+ $out.puts "debug: reported #{full_path}" if @config["debug"]
112
+ relative_path = full_path[root.size+1..-1]
113
+ buckets[root]||=[]
114
+ buckets[root]<< {
115
+ "action" => "changed",
116
+ "path1" => relative_path,
117
+ "path2" => nil
118
+ }
119
+ end
120
+ @paths_info[full_path] = current_time
121
+ end
122
+ else
123
+ relative_path = dir[root.size+1..-1]
124
+ buckets[root]||=[]
125
+ buckets[root]<< {
126
+ "action" => "deleted",
127
+ "path1" => relative_path,
128
+ "path2" => nil
129
+ }
130
+ end
131
+ rescue
132
+ $out.puts "debug: exception! #{dir}" if @config["debug"]
133
+ raise if @config["debug"]
134
+ next #keep silence
135
+ end
136
+ end
137
+ else
138
+ not_first_time = true
139
+ end
140
+
141
+ if buckets.size
142
+ buckets.each do |root, files|
143
+ $out.puts " activity in #{root}:"
144
+ files.each do |file|
145
+ $out.puts " #{activities[file["action"]]} #{file["path1"]}"
146
+ end
147
+ date = nil
148
+ time = nil
149
+ name = root
150
+ type = 'type?'
151
+
152
+ @server.clients.each do |client|
153
+ client.send_do_refresh(root, name, type, date, time, files)
154
+ end
155
+ end
156
+ buckets.clear
157
+ end
158
+
159
+ @modified_dirs.clear
160
+ sleep @config["sleep_time"]
161
+ end
162
+
163
+ streams.each do |stream|
164
+ FSEventStreamStop(stream)
165
+ FSEventStreamInvalidate(stream)
166
+ FSEventStreamRelease(stream)
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+