xrefresh-server 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+