Initial commit
This commit is contained in:
3
src/.properties
Normal file
3
src/.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
#format : #tonel
|
||||
}
|
||||
62
src/TinyChat-client/TCConsole.class.st
Normal file
62
src/TinyChat-client/TCConsole.class.st
Normal file
@@ -0,0 +1,62 @@
|
||||
Class {
|
||||
#name : 'TCConsole',
|
||||
#superclass : 'SpPresenter',
|
||||
#instVars : [
|
||||
'chat',
|
||||
'list',
|
||||
'input'
|
||||
],
|
||||
#category : 'TinyChat-client',
|
||||
#package : 'TinyChat-client'
|
||||
}
|
||||
|
||||
{ #category : 'as yet unclassified' }
|
||||
TCConsole class >> attach: aTinyChat [
|
||||
|
||||
| window |
|
||||
window := self new chat: aTinyChat.
|
||||
window open whenClosedDo: [ aTinyChat disconnect ].
|
||||
^ window
|
||||
]
|
||||
|
||||
{ #category : 'as yet unclassified' }
|
||||
TCConsole class >> defaultLayout [
|
||||
^ SpBoxLayout newTopToBottom
|
||||
add: #list; add: #input; yourself
|
||||
]
|
||||
|
||||
{ #category : 'accessing' }
|
||||
TCConsole >> chat: anObject [
|
||||
chat := anObject
|
||||
]
|
||||
|
||||
{ #category : 'accessing' }
|
||||
TCConsole >> initializeWidgets [
|
||||
|
||||
list := SpListPresenter new.
|
||||
input := SpTextInputFieldPresenter new
|
||||
placeholder: 'Type your message here...';
|
||||
enabled: true;
|
||||
whenSubmitDo: [ :string | chat send: string. input text: '' ].
|
||||
self focusOrder add: input
|
||||
]
|
||||
|
||||
{ #category : 'accessing' }
|
||||
TCConsole >> input [
|
||||
^ input
|
||||
]
|
||||
|
||||
{ #category : 'accessing' }
|
||||
TCConsole >> list [
|
||||
^ list
|
||||
]
|
||||
|
||||
{ #category : 'accessing' }
|
||||
TCConsole >> print: aCollectionOfMessages [
|
||||
list items: (aCollectionOfMessages collect: [ :m | m printString ])
|
||||
]
|
||||
|
||||
{ #category : 'accessing' }
|
||||
TCConsole >> title [
|
||||
^ 'TinyChat'
|
||||
]
|
||||
130
src/TinyChat-client/TinyChat.class.st
Normal file
130
src/TinyChat-client/TinyChat.class.st
Normal file
@@ -0,0 +1,130 @@
|
||||
Class {
|
||||
#name : 'TinyChat',
|
||||
#superclass : 'Object',
|
||||
#instVars : [
|
||||
'url',
|
||||
'login',
|
||||
'exit',
|
||||
'messages',
|
||||
'console',
|
||||
'lastMessageIndex'
|
||||
],
|
||||
#category : 'TinyChat-client',
|
||||
#package : 'TinyChat-client'
|
||||
}
|
||||
|
||||
{ #category : 'as yet unclassified' }
|
||||
TinyChat class >> connect: aHost port: aPort login: aLogin [
|
||||
|
||||
^ self new
|
||||
host: aHost port: aPort login: aLogin;
|
||||
start
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TinyChat >> cmdLastMessageID [
|
||||
^ self command: '/messages/count'
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TinyChat >> cmdMessagesFromLastIndexToEnd [
|
||||
"Returns the server messages from my current last index to the last on the server."
|
||||
^ self command: '/messages' argument: lastMessageIndex
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TinyChat >> cmdNewMessage [
|
||||
^self command: '/messages/add'
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TinyChat >> command: aPath [
|
||||
^'{1}{2}' format: { url . aPath }
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TinyChat >> command: aPath argument: anArgument [
|
||||
^'{1}{2}/{3}' format: { url . aPath . anArgument asString }
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TinyChat >> disconnect [
|
||||
self sendNewMessage: (TCMessage from: login text: 'I exited from the chat room.').
|
||||
exit := true
|
||||
]
|
||||
|
||||
{ #category : 'as yet unclassified' }
|
||||
TinyChat >> host: aHost port: aPort login: aLogin [
|
||||
url := 'http://' , aHost , ':' , aPort asString.
|
||||
login := aLogin
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TinyChat >> initialize [
|
||||
super initialize.
|
||||
exit := false.
|
||||
lastMessageIndex := 0.
|
||||
messages := OrderedCollection new
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TinyChat >> readLastMessageID [
|
||||
| id |
|
||||
id := (ZnClient new url: self cmdLastMessageID; get) asInteger.
|
||||
id = 0 ifTrue: [ id := 1 ].
|
||||
^ id
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TinyChat >> readMissingMessages [
|
||||
"Gets the new messages that have been posted since the last request."
|
||||
| response receivedMessages |
|
||||
response := (ZnClient new url: self cmdMessagesFromLastIndexToEnd; get).
|
||||
^ response
|
||||
ifNil: [ 0 ]
|
||||
ifNotNil: [
|
||||
receivedMessages := response substrings: (String crlf).
|
||||
receivedMessages do: [ :msg | messages add: (TCMessage fromString: msg) ].
|
||||
receivedMessages size
|
||||
]
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TinyChat >> refreshMessages [
|
||||
[
|
||||
[ exit ] whileFalse: [
|
||||
(Delay forSeconds: 2) wait.
|
||||
lastMessageIndex := lastMessageIndex + (self readMissingMessages).
|
||||
console print: messages
|
||||
]
|
||||
] fork
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TinyChat >> send: aString [
|
||||
"When we send a message, we push it to the server and in addition
|
||||
we update the local list of posted messages"
|
||||
|
||||
| msg |
|
||||
msg := TCMessage from: login text: aString.
|
||||
self sendNewMessage: msg.
|
||||
lastMessageIndex := lastMessageIndex + (self readMissingMessages).
|
||||
console print: messages
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TinyChat >> sendNewMessage: aMessage [
|
||||
^ ZnClient new
|
||||
url: self cmdNewMessage;
|
||||
formAt: 'sender' put: (aMessage sender);
|
||||
formAt: 'text' put: (aMessage text);
|
||||
post
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TinyChat >> start [
|
||||
console := TCConsole attach: self.
|
||||
self sendNewMessage: (TCMessage from: login text: 'I joined the chat room').
|
||||
lastMessageIndex := self readLastMessageID.
|
||||
self refreshMessages
|
||||
]
|
||||
1
src/TinyChat-client/package.st
Normal file
1
src/TinyChat-client/package.st
Normal file
@@ -0,0 +1 @@
|
||||
Package { #name : 'TinyChat-client' }
|
||||
46
src/TinyChat-server/TCMessageQueue.class.st
Normal file
46
src/TinyChat-server/TCMessageQueue.class.st
Normal file
@@ -0,0 +1,46 @@
|
||||
Class {
|
||||
#name : 'TCMessageQueue',
|
||||
#superclass : 'Object',
|
||||
#instVars : [
|
||||
'messages'
|
||||
],
|
||||
#category : 'TinyChat-server',
|
||||
#package : 'TinyChat-server'
|
||||
}
|
||||
|
||||
{ #category : 'adding' }
|
||||
TCMessageQueue >> add: aMessage [
|
||||
messages add: aMessage
|
||||
]
|
||||
|
||||
{ #category : 'adding' }
|
||||
TCMessageQueue >> formattedMessagesFrom: aMessageNumber [
|
||||
|
||||
^ String streamContents: [ :formattedMessagesStream |
|
||||
(self listFrom: aMessageNumber)
|
||||
do: [ :m | formattedMessagesStream << m printString ]
|
||||
]
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TCMessageQueue >> initialize [
|
||||
super initialize.
|
||||
messages := OrderedCollection new
|
||||
]
|
||||
|
||||
{ #category : 'adding' }
|
||||
TCMessageQueue >> listFrom: aIndex [
|
||||
^ (aIndex > 0 and: [ aIndex <= messages size ])
|
||||
ifTrue: [ messages copyFrom: aIndex to: messages size ]
|
||||
ifFalse: [ #() ]
|
||||
]
|
||||
|
||||
{ #category : 'adding' }
|
||||
TCMessageQueue >> reset [
|
||||
messages removeAll
|
||||
]
|
||||
|
||||
{ #category : 'adding' }
|
||||
TCMessageQueue >> size [
|
||||
^ messages size
|
||||
]
|
||||
75
src/TinyChat-server/TCServer.class.st
Normal file
75
src/TinyChat-server/TCServer.class.st
Normal file
@@ -0,0 +1,75 @@
|
||||
Class {
|
||||
#name : 'TCServer',
|
||||
#superclass : 'Object',
|
||||
#instVars : [
|
||||
'teapotServer',
|
||||
'messagesQueue'
|
||||
],
|
||||
#category : 'TinyChat-server',
|
||||
#package : 'TinyChat-server'
|
||||
}
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TCServer class >> startOn: aPortNumber [
|
||||
^self new
|
||||
initializePort: aPortNumber;
|
||||
registerRoutes;
|
||||
registerErrorHandlers;
|
||||
yourself
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TCServer class >> stopAll [
|
||||
self allInstancesDo: #stop
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TCServer >> addMessage: aRequest [
|
||||
messagesQueue add: (TCMessage from: (aRequest at: #sender) text: (aRequest at: #text)).
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TCServer >> initialize [
|
||||
super initialize.
|
||||
messagesQueue := TCMessageQueue new
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TCServer >> initializePort: anInteger [
|
||||
teapotServer := Teapot configure: {
|
||||
#defaultOutput -> #text.
|
||||
#port -> anInteger.
|
||||
#debugMode -> true
|
||||
}.
|
||||
teapotServer start
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TCServer >> messageCount [
|
||||
^ messagesQueue size
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TCServer >> messagesFrom: request [
|
||||
^ messagesQueue formattedMessagesFrom: (request at: #id)
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TCServer >> registerErrorHandlers [
|
||||
teapotServer
|
||||
exception: KeyNotFound -> (TeaResponse notFound body: 'No such message')
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TCServer >> registerRoutes [
|
||||
teapotServer
|
||||
GET: '/messages/count' -> (Send message: #messageCount to: self);
|
||||
GET: '/messages/<id:IsInteger>' -> (Send message: #messagesFrom: to: self);
|
||||
POST: '/messages/add' -> (Send message: #addMessage: to: self)
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TCServer >> stop [
|
||||
teapotServer stop.
|
||||
messagesQueue reset
|
||||
]
|
||||
1
src/TinyChat-server/package.st
Normal file
1
src/TinyChat-server/package.st
Normal file
@@ -0,0 +1 @@
|
||||
Package { #name : 'TinyChat-server' }
|
||||
73
src/TinyChat/TCMessage.class.st
Normal file
73
src/TinyChat/TCMessage.class.st
Normal file
@@ -0,0 +1,73 @@
|
||||
Class {
|
||||
#name : 'TCMessage',
|
||||
#superclass : 'Object',
|
||||
#instVars : [
|
||||
'sender',
|
||||
'text',
|
||||
'separator'
|
||||
],
|
||||
#category : 'TinyChat',
|
||||
#package : 'TinyChat'
|
||||
}
|
||||
|
||||
{ #category : 'instance creation' }
|
||||
TCMessage class >> from: aSender text: aText [
|
||||
|
||||
^ self new sender: aSender; text: aText; yourself
|
||||
]
|
||||
|
||||
{ #category : 'instance creation' }
|
||||
TCMessage class >> fromString: aString [
|
||||
^ self new
|
||||
fromString: aString;
|
||||
yourself
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TCMessage >> fromString: aString [
|
||||
"Compose a message from a string of this form 'sender>message'."
|
||||
|
||||
| items |
|
||||
items := aString substrings: separator.
|
||||
self sender: items first.
|
||||
self text: items second.
|
||||
]
|
||||
|
||||
{ #category : 'initialization' }
|
||||
TCMessage >> initialize [
|
||||
super initialize.
|
||||
separator := '>'
|
||||
]
|
||||
|
||||
{ #category : 'printing' }
|
||||
TCMessage >> printOn: aStream [
|
||||
"Generate a string representation of the receiver based on its instance variables."
|
||||
|
||||
aStream
|
||||
<< self sender; << separator;
|
||||
<< self text; << String crlf
|
||||
]
|
||||
|
||||
{ #category : 'accessing' }
|
||||
TCMessage >> sender [
|
||||
|
||||
^ sender
|
||||
]
|
||||
|
||||
{ #category : 'accessing' }
|
||||
TCMessage >> sender: anObject [
|
||||
|
||||
sender := anObject
|
||||
]
|
||||
|
||||
{ #category : 'accessing' }
|
||||
TCMessage >> text [
|
||||
|
||||
^ text
|
||||
]
|
||||
|
||||
{ #category : 'accessing' }
|
||||
TCMessage >> text: anObject [
|
||||
|
||||
text := anObject
|
||||
]
|
||||
1
src/TinyChat/package.st
Normal file
1
src/TinyChat/package.st
Normal file
@@ -0,0 +1 @@
|
||||
Package { #name : 'TinyChat' }
|
||||
Reference in New Issue
Block a user