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