cockatoo 0.0.1
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/LICENSE +21 -0
- data/Manifest +11 -0
- data/README.rdoc +51 -0
- data/Rakefile +12 -0
- data/bin/cockatoo +12 -0
- data/cockatoo.gemspec +47 -0
- data/extra/_chat.html.haml +182 -0
- data/extra/_chat_initialization.html.haml +38 -0
- data/extra/chat.js +376 -0
- data/lib/cockatoo_daemon.rb +26 -0
- data/lib/cockatoo_http_server.rb +205 -0
- metadata +169 -0
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2010 Yann Klis
|
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.
|
21
|
+
|
data/Manifest
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
= Cockatoo
|
2
|
+
|
3
|
+
Cockatoo is a simple chat server. It's coded in Ruby with the help of the fantastic EventMachine.
|
4
|
+
|
5
|
+
There are several techniques to handle the real-time web, but we decided to use the Long Polling technique.
|
6
|
+
It works with plenty of browsers.
|
7
|
+
|
8
|
+
* homepage[http://github.com/novelys/cockatoo]
|
9
|
+
|
10
|
+
== Server part
|
11
|
+
|
12
|
+
The server part is coded in Ruby and EventMachine. The protocol is pretty straightforward.
|
13
|
+
|
14
|
+
== Client part
|
15
|
+
|
16
|
+
You can use any client part you want.
|
17
|
+
We provide 3 components so you can setup a simple chat system in any Ruby/Rails app you're working on :
|
18
|
+
* _chat_initialization[http://github.com/novelys/cockatoo/raw/master/extra/_chat_initialization.html.haml] HAML template
|
19
|
+
* _chat[http://github.com/novelys/cockatoo/raw/master/extra/_chat.html.haml] HAML template
|
20
|
+
* chat.js[http://github.com/novelys/cockatoo/raw/master/extra/chat.js] is the javascript client library (you will need jQuery[http://jquery.com])
|
21
|
+
|
22
|
+
Just include the 2 templates and the javascript lib in your layout and you'll be good to go.
|
23
|
+
|
24
|
+
Here are two screenshots of the client side in action :
|
25
|
+
* http://www.novelys.com/assets/Gallery/Lollidays/BigFrame5.png
|
26
|
+
* http://www.novelys.com/assets/Gallery/TheSphere/BigFrame3.png
|
27
|
+
|
28
|
+
== Installation
|
29
|
+
|
30
|
+
gem install cockatoo
|
31
|
+
|
32
|
+
== Usage
|
33
|
+
|
34
|
+
Beware that you'll need a "log" directory located where you will run cockatoo.
|
35
|
+
|
36
|
+
start the server (by default it will run on the 8000 port)
|
37
|
+
cockatoo start
|
38
|
+
|
39
|
+
start the server on the 9000 port
|
40
|
+
cockatoo start -- 9000
|
41
|
+
|
42
|
+
stop the server
|
43
|
+
cockatoo stop
|
44
|
+
|
45
|
+
restart the server
|
46
|
+
cockatoo restart
|
47
|
+
|
48
|
+
== Copyright
|
49
|
+
|
50
|
+
Copyright (c) 2010 Yann Klis. See LICENSE[http://github.com/novelys/cockatoo/raw/master/LICENSE] for details.
|
51
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'echoe'
|
2
|
+
Echoe.new('cockatoo', '0.0.1') do |p|
|
3
|
+
p.summary = "Cockatoo is a simple chat server"
|
4
|
+
p.description = "Cockatoo is a simple chat server coded with EventMachine and using the Long Polling technique"
|
5
|
+
p.url = "http://github.com/novelys/cockatoo"
|
6
|
+
p.author = "Yann Klis"
|
7
|
+
p.email = "yann.klis @nospam@ novelys.com"
|
8
|
+
p.ignore_pattern = ["tmp/*", "log/*"]
|
9
|
+
p.development_dependencies = []
|
10
|
+
p.runtime_dependencies = ["daemons >=1.1.0", "log4r >=1.1.8", "eventmachine >=0.12.10", "yajl-ruby >=0.7.0", "htmlentities >=4.2.1"]
|
11
|
+
p.executable_pattern = ["bin/*"]
|
12
|
+
end
|
data/bin/cockatoo
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'daemons'
|
3
|
+
|
4
|
+
file = File.join(File.expand_path(__FILE__), '..', '..', 'lib', 'cockatoo_daemon.rb')
|
5
|
+
pwd = Dir.pwd
|
6
|
+
Daemons.run(file,
|
7
|
+
:app_name => "cockatoo",
|
8
|
+
:backtrace => true,
|
9
|
+
:log_output => true,
|
10
|
+
:dir_mode => :normal,
|
11
|
+
:dir => File.join(pwd, "log")
|
12
|
+
)
|
data/cockatoo.gemspec
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{cockatoo}
|
5
|
+
s.version = "0.0.1"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Yann Klis"]
|
9
|
+
s.date = %q{2010-10-27}
|
10
|
+
s.default_executable = %q{cockatoo}
|
11
|
+
s.description = %q{Cockatoo is a simple chat server coded with EventMachine and using the Long Polling technique}
|
12
|
+
s.email = %q{yann.klis @nospam@ novelys.com}
|
13
|
+
s.executables = ["cockatoo"]
|
14
|
+
s.extra_rdoc_files = ["LICENSE", "README.rdoc", "bin/cockatoo", "extra/_chat.html.haml", "extra/_chat_initialization.html.haml", "extra/chat.js", "lib/cockatoo_daemon.rb", "lib/cockatoo_http_server.rb"]
|
15
|
+
s.files = ["LICENSE", "Manifest", "README.rdoc", "Rakefile", "bin/cockatoo", "cockatoo.gemspec", "extra/_chat.html.haml", "extra/_chat_initialization.html.haml", "extra/chat.js", "lib/cockatoo_daemon.rb", "lib/cockatoo_http_server.rb"]
|
16
|
+
s.homepage = %q{http://github.com/novelys/cockatoo}
|
17
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Cockatoo", "--main", "README.rdoc"]
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
s.rubyforge_project = %q{cockatoo}
|
20
|
+
s.rubygems_version = %q{1.3.7}
|
21
|
+
s.summary = %q{Cockatoo is a simple chat server}
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
25
|
+
s.specification_version = 3
|
26
|
+
|
27
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
28
|
+
s.add_runtime_dependency(%q<daemons>, [">= 1.1.0"])
|
29
|
+
s.add_runtime_dependency(%q<log4r>, [">= 1.1.8"])
|
30
|
+
s.add_runtime_dependency(%q<eventmachine>, [">= 0.12.10"])
|
31
|
+
s.add_runtime_dependency(%q<yajl-ruby>, [">= 0.7.0"])
|
32
|
+
s.add_runtime_dependency(%q<htmlentities>, [">= 4.2.1"])
|
33
|
+
else
|
34
|
+
s.add_dependency(%q<daemons>, [">= 1.1.0"])
|
35
|
+
s.add_dependency(%q<log4r>, [">= 1.1.8"])
|
36
|
+
s.add_dependency(%q<eventmachine>, [">= 0.12.10"])
|
37
|
+
s.add_dependency(%q<yajl-ruby>, [">= 0.7.0"])
|
38
|
+
s.add_dependency(%q<htmlentities>, [">= 4.2.1"])
|
39
|
+
end
|
40
|
+
else
|
41
|
+
s.add_dependency(%q<daemons>, [">= 1.1.0"])
|
42
|
+
s.add_dependency(%q<log4r>, [">= 1.1.8"])
|
43
|
+
s.add_dependency(%q<eventmachine>, [">= 0.12.10"])
|
44
|
+
s.add_dependency(%q<yajl-ruby>, [">= 0.7.0"])
|
45
|
+
s.add_dependency(%q<htmlentities>, [">= 4.2.1"])
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
#ChatApplication.ChatApplication.is-chat
|
2
|
+
.body.csb-body
|
3
|
+
.applets
|
4
|
+
.FriendsApplet.bar-container
|
5
|
+
.Bubble.decoration.TEMPLATE.hidden
|
6
|
+
.header.bbl-header
|
7
|
+
.title
|
8
|
+
%span.out-title
|
9
|
+
[TITLE]
|
10
|
+
%span.date.out-date
|
11
|
+
[DATE]
|
12
|
+
.do-close.action
|
13
|
+
x
|
14
|
+
.body.bbl-body
|
15
|
+
.message
|
16
|
+
.icon.left
|
17
|
+
%img.author.out-author.P{ :src => "/images/user-avatar-default.png", :align => "absmiddle", :height => "60", :width => "60" }
|
18
|
+
.text.out-text
|
19
|
+
[TEXT]
|
20
|
+
.clearer
|
21
|
+
\
|
22
|
+
.footer.bbl-footer
|
23
|
+
.operations
|
24
|
+
%button.do-ok.out-ok.action.T
|
25
|
+
reply
|
26
|
+
%span.hsep-5
|
27
|
+
%button.do-cancel.out-cancel.action.T
|
28
|
+
decline
|
29
|
+
.tip
|
30
|
+
\
|
31
|
+
.FriendsList.SlidingPane.hidden
|
32
|
+
.header.fl-header
|
33
|
+
.title.T
|
34
|
+
My friends
|
35
|
+
%img.do-collapse.action.P{ :src => "/images/icon-collapse.png", :align => "absmiddle", :height => "16", :width => "16" }
|
36
|
+
.clearer
|
37
|
+
\
|
38
|
+
.body.fl-body
|
39
|
+
%ul.friends
|
40
|
+
%li.when-empty.when-service_available
|
41
|
+
.message.when-empty
|
42
|
+
%span.text.T
|
43
|
+
No online friend
|
44
|
+
%li.when-not_service_available.hidden
|
45
|
+
.message
|
46
|
+
%span.text.T
|
47
|
+
No service
|
48
|
+
/%button.action.do-reconnect.T
|
49
|
+
/ reconnect
|
50
|
+
.clearer
|
51
|
+
\
|
52
|
+
.clearer
|
53
|
+
\
|
54
|
+
.footer.TEMPLATES
|
55
|
+
%ul
|
56
|
+
%li.friend-element.when-service_available
|
57
|
+
.friend
|
58
|
+
.left
|
59
|
+
%img.icon.out-picture.P{ :src => "/images/user-avatar-default.png", :align => "absmiddle", :height => "30", :width => "30" }
|
60
|
+
.name.out-name
|
61
|
+
NAME
|
62
|
+
.status.out-status
|
63
|
+
STATUS
|
64
|
+
%span.when-not_in_discussion.hidden
|
65
|
+
%button.do-invite.action.T
|
66
|
+
invite
|
67
|
+
%span.when-in_discussion
|
68
|
+
%button.do-seeChat.action.T
|
69
|
+
chat
|
70
|
+
.clearer
|
71
|
+
\
|
72
|
+
.UserStatus.StatusBar
|
73
|
+
.body.us-body
|
74
|
+
.info
|
75
|
+
%img.P{ :src => "/images/icon-contact.png", :align => "absmiddle", :height => "16", :width => "16" }
|
76
|
+
%span
|
77
|
+
%span.hi
|
78
|
+
%span.out-onlineFriends
|
79
|
+
0
|
80
|
+
%span.T.out-onlineFriendsLabel
|
81
|
+
friends
|
82
|
+
%span
|
83
|
+
%span.T
|
84
|
+
online
|
85
|
+
.toggle
|
86
|
+
%img.do-expand.action.when-not_expanded.P{ :src => "/images/icon-expand.png", :align => "absmiddle", :height => "16", :width => "16" }
|
87
|
+
%img.do-collapse.action.when-expanded.hidden.P{ :src => "/images/icon-collapse.png", :align => "absmiddle", :height => "16", :width => "16" }
|
88
|
+
.clearer
|
89
|
+
\
|
90
|
+
.footer.TEMPLATES
|
91
|
+
.DiscussionApplet.bar-container
|
92
|
+
.Bubble.decoration.TEMPLATE.hidden
|
93
|
+
.header.bbl-header
|
94
|
+
.title
|
95
|
+
%span.out-title
|
96
|
+
[TITLE]
|
97
|
+
%span.date.out-date
|
98
|
+
[DATE]
|
99
|
+
.do-close.action
|
100
|
+
x
|
101
|
+
.body.bbl-body
|
102
|
+
.message
|
103
|
+
.icon.left
|
104
|
+
%img.author.out-author.P{ :src => "/images/user-avatar-default.png", :align => "absmiddle", :height => "60", :width => "60" }
|
105
|
+
.text.out-text
|
106
|
+
[TEXT]
|
107
|
+
.clearer
|
108
|
+
\
|
109
|
+
.footer.bbl-footer
|
110
|
+
.operations
|
111
|
+
%button.do-ok.out-ok.action.T
|
112
|
+
reply
|
113
|
+
%span.hsep-5
|
114
|
+
%button.do-cancel.out-cancel.action.T
|
115
|
+
decline
|
116
|
+
.tip
|
117
|
+
\
|
118
|
+
.ChatLog.SlidingPane.decoration.hidden
|
119
|
+
.header.cl-header
|
120
|
+
.icon.left
|
121
|
+
%img.out-withPicture.P{ :src => "/images/user-avatar-default.png", :align => "absmiddle", :height => "45", :width => "45" }
|
122
|
+
.name.out-withName
|
123
|
+
NAME
|
124
|
+
.status.out-withStatus
|
125
|
+
STATUS
|
126
|
+
%img.do-collapse.action.P{ :src => "/images/icon-collapse.png", :align => "absmiddle", :height => "16", :width => "16" }
|
127
|
+
%img.do-close-discussion.action.P{ :src => "/images/icon-close.png", :align => "absmiddle", :height => "16", :width => "16" }
|
128
|
+
.clearer
|
129
|
+
\
|
130
|
+
.header.cl-header-2
|
131
|
+
.left
|
132
|
+
.discussionStatus
|
133
|
+
%span.out
|
134
|
+
// TODO : find something interesting to display here...
|
135
|
+
|
136
|
+
.right
|
137
|
+
%span.on-state.when-initiated.when-accepted.when-active.when-inactive.hidden
|
138
|
+
%button.do-endDiscussion.action.T
|
139
|
+
end
|
140
|
+
%span.on-state.when-ended.when-cancelled.when-declined.hidden
|
141
|
+
%button.do-closeDiscussion.action.T
|
142
|
+
close
|
143
|
+
.clearer
|
144
|
+
\
|
145
|
+
.body.cl-body
|
146
|
+
%ul.messages.is-empty
|
147
|
+
%li.when-empty.hidden
|
148
|
+
.TEMPLATES
|
149
|
+
%ul
|
150
|
+
%li
|
151
|
+
.message
|
152
|
+
.header
|
153
|
+
.left.out.name
|
154
|
+
NAME
|
155
|
+
.right.out.date
|
156
|
+
HHhMM
|
157
|
+
.body.out.text
|
158
|
+
TEXT
|
159
|
+
.footer.cl-footer
|
160
|
+
.on-state.when-initiated.when-accepted.when-active.when-inactive
|
161
|
+
%input.in-message{ :type => "text" }
|
162
|
+
%button.do-writeMessage.action
|
163
|
+
%img.action.P{ :src => "/images/icon-bubble_and_shadow.png", :align => "absmiddle", :height => "16", :width => "16" }
|
164
|
+
.on-state.when-ended.when-cancelled.when-declined.hidden
|
165
|
+
%span.T
|
166
|
+
discussion ended
|
167
|
+
.ChatStatus.StatusBar
|
168
|
+
.body.us-body
|
169
|
+
.info
|
170
|
+
%img.P{ :src => "/images/icon-bubble.png", :align => "absmiddle", :height => "16", :width => "16" }
|
171
|
+
%span
|
172
|
+
%span.hi
|
173
|
+
%span.out-withName
|
174
|
+
NAME
|
175
|
+
\:
|
176
|
+
%span.out-messageCount
|
177
|
+
0
|
178
|
+
%span.T
|
179
|
+
messages
|
180
|
+
.toggle.action
|
181
|
+
%img.do-expand.when-not_expanded.hidden.action.P{ :src => "/images/icon-expand.png", :align => "absmiddle", :height => "16", :width => "16" }
|
182
|
+
%img.do-collapse.when-expanded.action.P{ :src => "/images/icon-collapse.png", :align => "absmiddle", :height => "16", :width => "16" }
|
@@ -0,0 +1,38 @@
|
|
1
|
+
:javascript
|
2
|
+
var ui_chat_applet = null;
|
3
|
+
var ui_chat_applet_status = null;
|
4
|
+
var ui_chat_applet_sliding_pane = null;
|
5
|
+
var ui_chat_applet_expanded = null;
|
6
|
+
var ui_chat_applet_collapsed = null;
|
7
|
+
var ui_chat_applet_friends = null;
|
8
|
+
var ui_chat_applet_friend_template = null;
|
9
|
+
|
10
|
+
var ui_chat_applets = null;
|
11
|
+
var ui_chat_friends_applet = null;
|
12
|
+
var ui_chat_discussion_applet_template = null;
|
13
|
+
|
14
|
+
var ui_chat_messages = null;
|
15
|
+
var ui_chat_message_template = null;
|
16
|
+
var friends_json = null;
|
17
|
+
var chat_initialization = null;
|
18
|
+
|
19
|
+
$(document).ready(function() {
|
20
|
+
ui_chat_applet = $('#ChatApplication')
|
21
|
+
ui_chat_applet_status = $('.StatusBar', ui_chat_applet)
|
22
|
+
ui_chat_applet_sliding_pane = $('.SlidingPane', ui_chat_applet)
|
23
|
+
ui_chat_applet_expanded = $('.when-expanded', ui_chat_applet)
|
24
|
+
ui_chat_applet_collapsed = $('.when-not_expanded', ui_chat_applet)
|
25
|
+
ui_chat_applet_friends = $('.fl-body .friends', ui_chat_applet)
|
26
|
+
ui_chat_applet_friend_template = $('.TEMPLATES li.friend-element', ui_chat_applet)
|
27
|
+
|
28
|
+
ui_chat_applets = $('.csb-body > .applets', ui_chat_applet)
|
29
|
+
ui_chat_friends_applet = $('.csb-body .FriendsApplet', ui_chat_applet)
|
30
|
+
ui_chat_discussion_applet_template = $('.TEMPLATES .DiscussionApplet', ui_chat_applet)
|
31
|
+
|
32
|
+
ui_chat_messages = $('ul.messages')
|
33
|
+
ui_chat_message_template = $('.TEMPLATES ul li')
|
34
|
+
|
35
|
+
load_friends_json();
|
36
|
+
|
37
|
+
chat_init();
|
38
|
+
});
|
data/extra/chat.js
ADDED
@@ -0,0 +1,376 @@
|
|
1
|
+
function getKeys(h) {
|
2
|
+
var keys = "";
|
3
|
+
for (var key in h)
|
4
|
+
keys += "," + key;
|
5
|
+
return keys.substr(1);
|
6
|
+
}
|
7
|
+
|
8
|
+
function load_friends_json() {
|
9
|
+
$.ajax({
|
10
|
+
cache: false,
|
11
|
+
type: "GET",
|
12
|
+
dataType: "json",
|
13
|
+
url: "/profile/friends.json",
|
14
|
+
async: false,
|
15
|
+
//error: chat_unavailable,
|
16
|
+
success: function(msg) {
|
17
|
+
friends_json = msg;
|
18
|
+
}
|
19
|
+
});
|
20
|
+
}
|
21
|
+
|
22
|
+
function chat_init() {
|
23
|
+
chat_initialization = true;
|
24
|
+
chat_available();
|
25
|
+
chat_register();
|
26
|
+
|
27
|
+
$('.header', ui_chat_applet).click(function(){
|
28
|
+
chat_applet_toggle();
|
29
|
+
return false;
|
30
|
+
});
|
31
|
+
|
32
|
+
$('.info', ui_chat_applet_status).click(function(){
|
33
|
+
chat_applet_toggle();
|
34
|
+
return false;
|
35
|
+
});
|
36
|
+
$(ui_chat_applet_collapsed).click(function() {
|
37
|
+
chat_applet_toggle();
|
38
|
+
return false;
|
39
|
+
});
|
40
|
+
$(ui_chat_applet_expanded).click(function() {
|
41
|
+
chat_applet_toggle();
|
42
|
+
return false;
|
43
|
+
});
|
44
|
+
// ui_chat_applet.click(function(){
|
45
|
+
// chat_applet_focus()
|
46
|
+
// });
|
47
|
+
}
|
48
|
+
|
49
|
+
function chat_applet_toggle() {
|
50
|
+
chat_all_discussions_collapse();
|
51
|
+
|
52
|
+
if ( ui_chat_applet_status.hasClass('is-expanded') ) {
|
53
|
+
chat_applet_collapse();
|
54
|
+
} else {
|
55
|
+
chat_applet_expand();
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
function chat_applet_collapse() {
|
60
|
+
ui_chat_applet_sliding_pane.addClass('hidden');
|
61
|
+
ui_chat_applet_collapsed.removeClass('hidden');
|
62
|
+
ui_chat_applet_expanded.addClass('hidden');
|
63
|
+
ui_chat_applet_status.removeClass('is-expanded');
|
64
|
+
chat_applet_unfocus();
|
65
|
+
}
|
66
|
+
|
67
|
+
function is_all_chat_discussions_collapsed() {
|
68
|
+
return $('.ChatLog.hidden').length == $('.ChatLog').length;
|
69
|
+
}
|
70
|
+
|
71
|
+
function chat_applet_expand() {
|
72
|
+
ui_chat_applet_expanded.removeClass('hidden');
|
73
|
+
ui_chat_applet_collapsed.addClass('hidden');
|
74
|
+
ui_chat_applet_sliding_pane.removeClass('hidden');
|
75
|
+
ui_chat_applet_status.addClass('is-expanded');
|
76
|
+
chat_applet_focus();
|
77
|
+
}
|
78
|
+
|
79
|
+
function chat_applet_focus(){
|
80
|
+
$('#ChatApplication .with-focus').removeClass('with-focus');
|
81
|
+
ui_chat_applet.addClass('with-focus');
|
82
|
+
}
|
83
|
+
|
84
|
+
function chat_applet_unfocus(){
|
85
|
+
ui_chat_applet.removeClass('with-focus');
|
86
|
+
}
|
87
|
+
|
88
|
+
function chat_discussion_toggle(nui) {
|
89
|
+
if ($('.toggle .do-collapse', nui).hasClass('hidden')) {
|
90
|
+
chat_discussion_expand(nui);
|
91
|
+
} else {
|
92
|
+
chat_discussion_collapse(nui);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
function chat_all_discussions_collapse() {
|
97
|
+
$(".ChatLog").addClass('hidden');
|
98
|
+
$(".ChatStatus .toggle .do-collapse").addClass('hidden');
|
99
|
+
$(".ChatStatus .toggle .do-expand").removeClass('hidden');
|
100
|
+
}
|
101
|
+
|
102
|
+
function chat_discussion_expand(nui) {
|
103
|
+
chat_all_discussions_collapse();
|
104
|
+
$(".ChatLog", nui).removeClass('hidden');
|
105
|
+
$(".toggle .do-collapse", nui).removeClass('hidden');
|
106
|
+
$(".toggle .do-expand", nui).addClass('hidden');
|
107
|
+
chat_applet_collapse();
|
108
|
+
chat_scroll(nui);
|
109
|
+
focus_on_input(nui);
|
110
|
+
}
|
111
|
+
|
112
|
+
function focus_on_input(nui) {
|
113
|
+
$('.in-message', nui).focus();
|
114
|
+
}
|
115
|
+
|
116
|
+
function chat_discussion_collapse(nui) {
|
117
|
+
$(".ChatLog", nui).addClass('hidden');
|
118
|
+
$(".toggle .do-collapse", nui).addClass('hidden');
|
119
|
+
$(".toggle .do-expand", nui).removeClass('hidden');
|
120
|
+
chat_applet_collapse();
|
121
|
+
}
|
122
|
+
|
123
|
+
function chat_unavailable() {
|
124
|
+
$('.when-service_available', ui_chat_applet).addClass('hidden');
|
125
|
+
$('.DiscussionApplet', ui_chat_applets).addClass('hidden');
|
126
|
+
$('.when-not_service_available', ui_chat_applet).removeClass('hidden');
|
127
|
+
}
|
128
|
+
|
129
|
+
function chat_available() {
|
130
|
+
$('.when-service_available', ui_chat_applet).removeClass('hidden');
|
131
|
+
$('.DiscussionApplet', ui_chat_applets).removeClass('hidden');
|
132
|
+
$('.when-not_service_available', ui_chat_applet).addClass('hidden');
|
133
|
+
}
|
134
|
+
|
135
|
+
function chat_discussion_close(nui, fid) {
|
136
|
+
nui.remove();
|
137
|
+
|
138
|
+
$.ajax({
|
139
|
+
cache: false,
|
140
|
+
type: "GET",
|
141
|
+
dataType: "json",
|
142
|
+
url: "/chat/close_discussion",
|
143
|
+
error: chat_unavailable,
|
144
|
+
data: {uid: uid, did: fid}
|
145
|
+
});
|
146
|
+
}
|
147
|
+
|
148
|
+
function chat_register() {
|
149
|
+
url = "/chat/register";
|
150
|
+
$.ajax({
|
151
|
+
cache: false,
|
152
|
+
type: "GET",
|
153
|
+
dataType: "json",
|
154
|
+
url: url,
|
155
|
+
error: chat_unavailable,
|
156
|
+
success: chat_dispatch_messages,
|
157
|
+
data: {uid: uid, "friends_ids": getKeys(friends_json)}
|
158
|
+
});
|
159
|
+
}
|
160
|
+
|
161
|
+
function chat_poll() {
|
162
|
+
url = "/chat/poll";
|
163
|
+
$.ajax({
|
164
|
+
cache: false,
|
165
|
+
type: "GET",
|
166
|
+
dataType: "json",
|
167
|
+
url: url,
|
168
|
+
error: chat_unavailable,
|
169
|
+
success: chat_dispatch_messages,
|
170
|
+
data: {uid: uid}
|
171
|
+
});
|
172
|
+
}
|
173
|
+
|
174
|
+
function chat_say(fid, msg) {
|
175
|
+
url = "/chat/say";
|
176
|
+
$.ajax({
|
177
|
+
cache: false,
|
178
|
+
type: "GET",
|
179
|
+
dataType: "json",
|
180
|
+
url: url,
|
181
|
+
data: {sid: uid, did: fid, msg: msg}
|
182
|
+
});
|
183
|
+
}
|
184
|
+
|
185
|
+
function chat_leave() {
|
186
|
+
url = "/chat/leave";
|
187
|
+
$.ajax({
|
188
|
+
cache: false,
|
189
|
+
type: "GET",
|
190
|
+
dataType: "json",
|
191
|
+
url: url,
|
192
|
+
async: false,
|
193
|
+
data: {uid: uid}
|
194
|
+
});
|
195
|
+
}
|
196
|
+
|
197
|
+
function chat_dispatch_messages(messages) {
|
198
|
+
if (messages) {
|
199
|
+
for ( var i=0, len=messages.length; i<len; ++i ){
|
200
|
+
message = messages[i];
|
201
|
+
switch(message.type) {
|
202
|
+
case "status":
|
203
|
+
// user change his status : offline/online
|
204
|
+
chat_user_change_status(message);
|
205
|
+
break;
|
206
|
+
case "message":
|
207
|
+
// user send a message to another
|
208
|
+
chat_incoming_message(message);
|
209
|
+
break;
|
210
|
+
}
|
211
|
+
}
|
212
|
+
}
|
213
|
+
chat_initialization = false;
|
214
|
+
|
215
|
+
setTimeout(chat_poll, 0);
|
216
|
+
}
|
217
|
+
|
218
|
+
function chat_user_change_status(friend) {
|
219
|
+
not_existing = $('[fid='+friend.uid+']').length == 0;
|
220
|
+
if ( not_existing && (friend.state && (friend.state == 'ONLINE')) ) {
|
221
|
+
fui = ui_chat_applet_friend_template.clone();
|
222
|
+
fui.attr({'fid':('' + friend.uid), 'status':friend.state});
|
223
|
+
$('.out-name', fui).html(friends_json[friend.uid].login);
|
224
|
+
$('.out-status', fui).html(friend.state);
|
225
|
+
$('.out-picture', fui).attr({'title':friends_json[friend.uid].login, 'src':friends_json[friend.uid].avatar_url});
|
226
|
+
$('.do-seeChat', fui).click(function(){
|
227
|
+
chat_discuss_with_user(friend);
|
228
|
+
});
|
229
|
+
fui.removeClass('hidden');
|
230
|
+
ui_chat_applet_friends.append(fui);
|
231
|
+
chat_user_online(friend.uid);
|
232
|
+
}
|
233
|
+
if (friend.state && (friend.state == 'OFFLINE')) {
|
234
|
+
chat_user_offline(friend.uid);
|
235
|
+
}
|
236
|
+
chat_applet_update_counters();
|
237
|
+
}
|
238
|
+
|
239
|
+
function chat_user_offline(uid) {
|
240
|
+
$('[fid='+uid+']').remove();
|
241
|
+
nui = $('[discussion_with='+uid+']');
|
242
|
+
if (nui.length != 0) {
|
243
|
+
$('.out-withStatus', nui).html("OFFLINE");
|
244
|
+
$('.in-message', nui).attr("disabled", "disabled");
|
245
|
+
}
|
246
|
+
}
|
247
|
+
|
248
|
+
function chat_user_online(uid) {
|
249
|
+
nui = $('[discussion_with='+uid+']');
|
250
|
+
if (nui.length != 0) {
|
251
|
+
$('.out-withStatus', nui).html("ONLINE");
|
252
|
+
$('.in-message', nui).removeAttr("disabled");
|
253
|
+
}
|
254
|
+
}
|
255
|
+
|
256
|
+
function chat_scroll(nui) {
|
257
|
+
$('.cl-body', nui).scrollTo('100%');
|
258
|
+
}
|
259
|
+
|
260
|
+
function chat_incoming_message(message) {
|
261
|
+
var friend = null;
|
262
|
+
if (message.sid == uid) {
|
263
|
+
friend = {uid:message.did, state:"ONLINE"};
|
264
|
+
message_login = user_name;
|
265
|
+
} else {
|
266
|
+
friend = {uid:message.sid, state:"ONLINE"};
|
267
|
+
message_login = friends_json[friend.uid].login;
|
268
|
+
}
|
269
|
+
var nui = $('[discussion_with='+friend.uid+']');
|
270
|
+
if ( nui.length == 0 ) {
|
271
|
+
chat_discuss_with_user(friend);
|
272
|
+
nui = $('[discussion_with='+friend.uid+']');
|
273
|
+
}
|
274
|
+
|
275
|
+
var chat_log = $('.ChatLog', nui);
|
276
|
+
|
277
|
+
if (chat_initialization) {
|
278
|
+
chat_discussion_collapse(nui);
|
279
|
+
} else if (is_all_chat_discussions_collapsed()) {
|
280
|
+
chat_discussion_expand(nui);
|
281
|
+
}
|
282
|
+
|
283
|
+
var messages = $('ul.messages', chat_log);
|
284
|
+
chat_discussion_message_counter(nui, messages);
|
285
|
+
|
286
|
+
var message_template = $('.TEMPLATES ul li', chat_log).clone();
|
287
|
+
var date=new Date(message.date);
|
288
|
+
var m=date.getMinutes();
|
289
|
+
var h=date.getHours();
|
290
|
+
$('.out.name', message_template).html(message_login);
|
291
|
+
$('.out.text', message_template).html(message.value);
|
292
|
+
$('.out.date', message_template).html(sprintf('%02dh%02d', h, m));
|
293
|
+
if (message.sid == uid) {
|
294
|
+
message_template.addClass('inbound');
|
295
|
+
} else {
|
296
|
+
message_template.addClass('outbound');
|
297
|
+
}
|
298
|
+
messages.append(message_template).removeClass('is-empty');
|
299
|
+
|
300
|
+
chat_scroll(nui);
|
301
|
+
|
302
|
+
// Play a sound...
|
303
|
+
if (!chat_initialization && message.sid != uid) {
|
304
|
+
chatAlarmSound.play();
|
305
|
+
}
|
306
|
+
}
|
307
|
+
|
308
|
+
function chat_discuss_with_user(friend) {
|
309
|
+
var nui = $('[discussion_with='+friend.uid+']');
|
310
|
+
if ( nui.length == 0 ) {
|
311
|
+
var nui = ui_chat_discussion_applet_template.clone();
|
312
|
+
nui.attr({'discussion_with':friend.uid});
|
313
|
+
$('.out-withName', nui).html(friends_json[friend.uid].login);
|
314
|
+
$('.out-withStatus', nui).html(friend.state);
|
315
|
+
$('.out-withPicture', nui).attr({'title':friends_json[friend.uid].login, 'src':friends_json[friend.uid].avatar_url});
|
316
|
+
|
317
|
+
|
318
|
+
$('.ChatStatus.StatusBar', nui).click(function(){
|
319
|
+
chat_discussion_toggle(nui);
|
320
|
+
return false;
|
321
|
+
});
|
322
|
+
|
323
|
+
$('.ChatLog .header .do-collapse', nui).click(function() {
|
324
|
+
chat_discussion_toggle(nui);
|
325
|
+
return false;
|
326
|
+
});
|
327
|
+
|
328
|
+
$('.ChatLog .header .do-close-discussion', nui).click(function() {
|
329
|
+
chat_discussion_close(nui, friend.uid);
|
330
|
+
return false;
|
331
|
+
});
|
332
|
+
|
333
|
+
$(".in-message", nui).keypress(function (e) {
|
334
|
+
if (e.keyCode != 13 /* Return */) return;
|
335
|
+
chat_send_message(nui, friend.uid);
|
336
|
+
});
|
337
|
+
|
338
|
+
$(".do-writeMessage", nui).click(function() {
|
339
|
+
chat_send_message(nui, friend.uid);
|
340
|
+
});
|
341
|
+
|
342
|
+
ui_chat_applets.append(nui);
|
343
|
+
chat_applet_collapse();
|
344
|
+
focus_on_input(nui);
|
345
|
+
} else {
|
346
|
+
chat_discussion_expand(nui);
|
347
|
+
}
|
348
|
+
}
|
349
|
+
|
350
|
+
function chat_send_message(nui, friend_uid) {
|
351
|
+
/*var msg = $("#entry").attr("value").replace("\n", "");*/
|
352
|
+
var msg = $(".in-message", nui).attr("value");
|
353
|
+
if (msg != "") {
|
354
|
+
chat_say(friend_uid, msg);
|
355
|
+
$(".in-message", nui).attr("value", "");
|
356
|
+
}
|
357
|
+
}
|
358
|
+
|
359
|
+
function chat_discussion_message_counter(nui, messages) {
|
360
|
+
nbr = $('li', messages).length;
|
361
|
+
$('.out-messageCount', nui).html(nbr);
|
362
|
+
}
|
363
|
+
|
364
|
+
function chat_applet_update_counters() {
|
365
|
+
nbr = $('li.friend-element[STATUS=ONLINE]', ui_chat_applet_friends).length;
|
366
|
+
$('.out-onlineFriends', ui_chat_applet).html(nbr);
|
367
|
+
if (nbr > 0) {
|
368
|
+
$('.when-empty', ui_chat_applet_friends).addClass('hidden');
|
369
|
+
} else {
|
370
|
+
$('.when-empty', ui_chat_applet_friends).removeClass('hidden');
|
371
|
+
}
|
372
|
+
}
|
373
|
+
|
374
|
+
$(window).unload(function () {
|
375
|
+
chat_leave();
|
376
|
+
});
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.join(File.expand_path(File.dirname(__FILE__) + "/cockatoo_http_server"))
|
2
|
+
require 'log4r'
|
3
|
+
include Log4r
|
4
|
+
|
5
|
+
$logger = Logger.new 'cockatoo'
|
6
|
+
$logger.outputters = Outputter.stdout
|
7
|
+
|
8
|
+
|
9
|
+
loop do
|
10
|
+
begin
|
11
|
+
GC.start
|
12
|
+
$chat_users = {}
|
13
|
+
$timers = {}
|
14
|
+
EM.epoll if EM.epoll?
|
15
|
+
EM.run do
|
16
|
+
$logger.info "#{Time.now} Starting server on port #{ARGV.first || 8000}"
|
17
|
+
EM.start_server '0.0.0.0', ARGV.first || 8000, ChatHttpServer
|
18
|
+
end
|
19
|
+
rescue Interrupt
|
20
|
+
$logger.info "#{Time.now} Shuting down..."
|
21
|
+
exit
|
22
|
+
rescue
|
23
|
+
$logger.error "#{Time.now} Error: " + $!.message
|
24
|
+
$logger.error "\t" + $!.backtrace.join("\n\t")
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'cgi'
|
3
|
+
require 'yajl'
|
4
|
+
require "htmlentities"
|
5
|
+
|
6
|
+
class Object
|
7
|
+
def blank?
|
8
|
+
respond_to?(:empty?) ? empty? : !self
|
9
|
+
end
|
10
|
+
|
11
|
+
def present?
|
12
|
+
!blank?
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class ChatUser
|
17
|
+
attr_accessor :uid, :discussion, :friends_ids
|
18
|
+
def self.new_from_query(query)
|
19
|
+
return nil unless query.present? && query['uid'].present?
|
20
|
+
new(:uid => query['uid'].first.to_i)
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(attributes)
|
24
|
+
@uid = attributes[:uid]
|
25
|
+
@friends_ids = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def log_with_user(other_chat_user)
|
29
|
+
self.discussion.present? ? self.discussion.log.select { |message| message["type"] == "message" && (message["sid"] == other_chat_user.uid || message["did"] == other_chat_user.uid) } : []
|
30
|
+
end
|
31
|
+
|
32
|
+
def clear_discussion(did)
|
33
|
+
if self.discussion.present?
|
34
|
+
self.discussion.log.delete_if { |message| message["sid"] == did || message["did"] == did }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Discussion < EM::Channel
|
40
|
+
attr_accessor :log
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
@log = []
|
44
|
+
super()
|
45
|
+
end
|
46
|
+
|
47
|
+
def say(ary)
|
48
|
+
ary = [ ary ].flatten
|
49
|
+
@log = (@log + ary)[0, 500]
|
50
|
+
json = Yajl::Encoder.encode(ary)
|
51
|
+
push json
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
module ChatHttpServer
|
56
|
+
|
57
|
+
def unbind
|
58
|
+
$chat_users[@user_id].discussion.unsubscribe(@sid) if @user_id && @sid && $chat_users[@user_id] && $chat_users[@user_id].discussion
|
59
|
+
end
|
60
|
+
|
61
|
+
def receive_data(data)
|
62
|
+
# puts "received data : #{data}"
|
63
|
+
lines = data.split(/[\r\n]+/)
|
64
|
+
method, request, version = lines.shift.split(' ', 3)
|
65
|
+
if request.nil?
|
66
|
+
$logger.error "#{Time.now} Warning: strange request #{[method, request, version].inspect}"
|
67
|
+
close_connection
|
68
|
+
return
|
69
|
+
else
|
70
|
+
path, query = request.split('?', 2)
|
71
|
+
$logger.info "#{Time.now} request on #{path} with #{query}"
|
72
|
+
query = CGI.parse(query) if not query.nil?
|
73
|
+
cookies = {}
|
74
|
+
lines.each do |line|
|
75
|
+
if line[0..6].downcase == 'cookie:'
|
76
|
+
cookies = CGI::Cookie.parse(line.split(':', 2).last.strip)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
if query.present? && query['uid'].present?
|
82
|
+
uid = query['uid'].first.to_i
|
83
|
+
chat_user = ($chat_users[uid] ||= ChatUser.new_from_query(query))
|
84
|
+
end
|
85
|
+
|
86
|
+
case path
|
87
|
+
|
88
|
+
when '/chat/register'
|
89
|
+
if chat_user && query['friends_ids'].present? && query['friends_ids'].first.present?
|
90
|
+
remove_all_timers_for(chat_user)
|
91
|
+
|
92
|
+
chat_user.friends_ids = query["friends_ids"].first.split(',').map { |id| id.to_i }
|
93
|
+
|
94
|
+
message = { "type" => "status", "state" => "ONLINE", "uid" => chat_user.uid }
|
95
|
+
back_messages = []
|
96
|
+
chat_user.friends_ids.each do |fid|
|
97
|
+
chat_user_friend = $chat_users[fid]
|
98
|
+
if chat_user_friend && chat_user_friend.discussion
|
99
|
+
chat_user_friend.discussion.say(message)
|
100
|
+
back_messages << {"type" => "status", "state" => "ONLINE", "uid" => fid}
|
101
|
+
|
102
|
+
back_messages += chat_user.discussion ? chat_user.log_with_user(chat_user_friend) : chat_user_friend.log_with_user(chat_user)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
respond Yajl::Encoder.encode(back_messages)
|
106
|
+
else
|
107
|
+
respond "no user id or no friends ids", 500
|
108
|
+
end
|
109
|
+
|
110
|
+
when '/chat/leave'
|
111
|
+
if chat_user
|
112
|
+
empty_response
|
113
|
+
unregister_user_after(chat_user, 15)
|
114
|
+
else
|
115
|
+
respond "no user id", 500
|
116
|
+
end
|
117
|
+
|
118
|
+
when '/chat/close_discussion'
|
119
|
+
empty_response
|
120
|
+
|
121
|
+
if chat_user && query['did'].present?
|
122
|
+
did = query['did'].first.to_i
|
123
|
+
$logger.info "User #{chat_user.uid} clears discussion for user #{did}"
|
124
|
+
chat_user.clear_discussion(did)
|
125
|
+
end
|
126
|
+
|
127
|
+
when '/chat/poll'
|
128
|
+
if chat_user
|
129
|
+
unregister_user_after(chat_user, 120)
|
130
|
+
timer = EventMachine::Timer.new(30) do
|
131
|
+
empty_response
|
132
|
+
end
|
133
|
+
@user_id = chat_user.uid
|
134
|
+
chat_user.discussion ||= Discussion.new
|
135
|
+
@sid = chat_user.discussion.subscribe do |code|
|
136
|
+
respond code
|
137
|
+
end
|
138
|
+
else
|
139
|
+
respond "no user id", 500
|
140
|
+
end
|
141
|
+
|
142
|
+
when '/chat/say'
|
143
|
+
if query['sid'].present? and query['msg'].present? and query['did'].present?
|
144
|
+
empty_response
|
145
|
+
sid = query['sid'].first.to_i
|
146
|
+
did = query['did'].first.to_i
|
147
|
+
msg = query['msg'].first
|
148
|
+
|
149
|
+
chat_user_sender = $chat_users[sid]
|
150
|
+
chat_user_recipient = $chat_users[did]
|
151
|
+
message = {"type" => "message", "value" => HTMLEntities.new.encode(msg.gsub('"', '\"')), "sid" => sid, "did" => did, "date" => (Time.now.to_i * 1000)}
|
152
|
+
|
153
|
+
if chat_user_sender && chat_user_recipient
|
154
|
+
chat_user_sender.discussion.say(message)
|
155
|
+
chat_user_recipient.discussion.say(message)
|
156
|
+
else
|
157
|
+
$logger.error "#{Time.now} ERROR : message could not be transmitted between sid #{sid} and did #{did}!"
|
158
|
+
end
|
159
|
+
else
|
160
|
+
respond "USER and MSG parameters are mandatory", 500
|
161
|
+
end
|
162
|
+
|
163
|
+
else
|
164
|
+
respond "not found", 404
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
RESPONSE = [
|
169
|
+
"HTTP/1.1 %d Cockatoo Alpha 1",
|
170
|
+
"Content-length: %d",
|
171
|
+
"Content-type: %s",
|
172
|
+
"Connection: close",
|
173
|
+
"",
|
174
|
+
"%s"].join("\r\n")
|
175
|
+
|
176
|
+
def empty_response
|
177
|
+
respond Yajl::Encoder.encode([])
|
178
|
+
end
|
179
|
+
|
180
|
+
def respond(body, status = 200, content_type = 'application/json; charset=utf-8')
|
181
|
+
send_data RESPONSE % [status, body.length, content_type, body]
|
182
|
+
close_connection_after_writing
|
183
|
+
end
|
184
|
+
|
185
|
+
def unregister_user_after(chat_user, seconds)
|
186
|
+
remove_all_timers_for(chat_user)
|
187
|
+
$logger.info "INFO : new timer for unregister user #{chat_user.uid} in #{seconds} sec."
|
188
|
+
$timers[chat_user.uid] = EventMachine::Timer.new(seconds) do
|
189
|
+
$logger.info "INFO : execute timer for user #{chat_user.uid}"
|
190
|
+
message = { "type" => "status", "state" => "OFFLINE", "uid" => chat_user.uid }
|
191
|
+
chat_user.friends_ids.each{|fid|
|
192
|
+
$chat_users[fid].discussion.say(message) if $chat_users[fid] && $chat_users[fid].discussion
|
193
|
+
}
|
194
|
+
$chat_users.delete(chat_user.uid)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def remove_all_timers_for(chat_user)
|
199
|
+
if $timers[chat_user.uid]
|
200
|
+
$logger.info "INFO : Removes timer for user #{chat_user.uid}"
|
201
|
+
$timers[chat_user.uid].cancel
|
202
|
+
$timers.delete(chat_user.uid)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
metadata
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cockatoo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Yann Klis
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-27 00:00:00 +02:00
|
19
|
+
default_executable: cockatoo
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: daemons
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 19
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 1
|
33
|
+
- 0
|
34
|
+
version: 1.1.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: log4r
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 1
|
48
|
+
- 1
|
49
|
+
- 8
|
50
|
+
version: 1.1.8
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: eventmachine
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 59
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
- 12
|
65
|
+
- 10
|
66
|
+
version: 0.12.10
|
67
|
+
type: :runtime
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yajl-ruby
|
71
|
+
prerelease: false
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 3
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
- 7
|
81
|
+
- 0
|
82
|
+
version: 0.7.0
|
83
|
+
type: :runtime
|
84
|
+
version_requirements: *id004
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: htmlentities
|
87
|
+
prerelease: false
|
88
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 53
|
94
|
+
segments:
|
95
|
+
- 4
|
96
|
+
- 2
|
97
|
+
- 1
|
98
|
+
version: 4.2.1
|
99
|
+
type: :runtime
|
100
|
+
version_requirements: *id005
|
101
|
+
description: Cockatoo is a simple chat server coded with EventMachine and using the Long Polling technique
|
102
|
+
email: yann.klis @nospam@ novelys.com
|
103
|
+
executables:
|
104
|
+
- cockatoo
|
105
|
+
extensions: []
|
106
|
+
|
107
|
+
extra_rdoc_files:
|
108
|
+
- LICENSE
|
109
|
+
- README.rdoc
|
110
|
+
- bin/cockatoo
|
111
|
+
- extra/_chat.html.haml
|
112
|
+
- extra/_chat_initialization.html.haml
|
113
|
+
- extra/chat.js
|
114
|
+
- lib/cockatoo_daemon.rb
|
115
|
+
- lib/cockatoo_http_server.rb
|
116
|
+
files:
|
117
|
+
- LICENSE
|
118
|
+
- Manifest
|
119
|
+
- README.rdoc
|
120
|
+
- Rakefile
|
121
|
+
- bin/cockatoo
|
122
|
+
- cockatoo.gemspec
|
123
|
+
- extra/_chat.html.haml
|
124
|
+
- extra/_chat_initialization.html.haml
|
125
|
+
- extra/chat.js
|
126
|
+
- lib/cockatoo_daemon.rb
|
127
|
+
- lib/cockatoo_http_server.rb
|
128
|
+
has_rdoc: true
|
129
|
+
homepage: http://github.com/novelys/cockatoo
|
130
|
+
licenses: []
|
131
|
+
|
132
|
+
post_install_message:
|
133
|
+
rdoc_options:
|
134
|
+
- --line-numbers
|
135
|
+
- --inline-source
|
136
|
+
- --title
|
137
|
+
- Cockatoo
|
138
|
+
- --main
|
139
|
+
- README.rdoc
|
140
|
+
require_paths:
|
141
|
+
- lib
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
143
|
+
none: false
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
hash: 3
|
148
|
+
segments:
|
149
|
+
- 0
|
150
|
+
version: "0"
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
|
+
none: false
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
hash: 11
|
157
|
+
segments:
|
158
|
+
- 1
|
159
|
+
- 2
|
160
|
+
version: "1.2"
|
161
|
+
requirements: []
|
162
|
+
|
163
|
+
rubyforge_project: cockatoo
|
164
|
+
rubygems_version: 1.3.7
|
165
|
+
signing_key:
|
166
|
+
specification_version: 3
|
167
|
+
summary: Cockatoo is a simple chat server
|
168
|
+
test_files: []
|
169
|
+
|