summaryrefslogtreecommitdiff
path: root/pharo-mooc/tiny-chat
diff options
context:
space:
mode:
authorEugen Wissner <belka@caraus.de>2025-11-23 17:05:53 +0100
committerEugen Wissner <belka@caraus.de>2025-11-23 17:05:53 +0100
commitf3b3d4b1a26eba872c32ad95fc112650011b625c (patch)
tree483c90b08fb640f9f96b83393f64af9b7e752984 /pharo-mooc/tiny-chat
parentbf11813e4fa859a4833cab226c4ea560765d6d77 (diff)
downloadbook-exercises-f3b3d4b1a26eba872c32ad95fc112650011b625c.tar.gz
Add the tiny-chat project from the Pharo MOOC
Diffstat (limited to 'pharo-mooc/tiny-chat')
-rw-r--r--pharo-mooc/tiny-chat/src/.properties3
-rw-r--r--pharo-mooc/tiny-chat/src/TinyChat-client/TCConsole.class.st62
-rw-r--r--pharo-mooc/tiny-chat/src/TinyChat-client/TinyChat.class.st130
-rw-r--r--pharo-mooc/tiny-chat/src/TinyChat-client/package.st1
-rw-r--r--pharo-mooc/tiny-chat/src/TinyChat-server/TCMessageQueue.class.st46
-rw-r--r--pharo-mooc/tiny-chat/src/TinyChat-server/TCServer.class.st75
-rw-r--r--pharo-mooc/tiny-chat/src/TinyChat-server/package.st1
-rw-r--r--pharo-mooc/tiny-chat/src/TinyChat/TCMessage.class.st73
-rw-r--r--pharo-mooc/tiny-chat/src/TinyChat/package.st1
9 files changed, 392 insertions, 0 deletions
diff --git a/pharo-mooc/tiny-chat/src/.properties b/pharo-mooc/tiny-chat/src/.properties
new file mode 100644
index 0000000..ad0471d
--- /dev/null
+++ b/pharo-mooc/tiny-chat/src/.properties
@@ -0,0 +1,3 @@
+{
+ #format : #tonel
+} \ No newline at end of file
diff --git a/pharo-mooc/tiny-chat/src/TinyChat-client/TCConsole.class.st b/pharo-mooc/tiny-chat/src/TinyChat-client/TCConsole.class.st
new file mode 100644
index 0000000..3f7055e
--- /dev/null
+++ b/pharo-mooc/tiny-chat/src/TinyChat-client/TCConsole.class.st
@@ -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'
+]
diff --git a/pharo-mooc/tiny-chat/src/TinyChat-client/TinyChat.class.st b/pharo-mooc/tiny-chat/src/TinyChat-client/TinyChat.class.st
new file mode 100644
index 0000000..f19e3a4
--- /dev/null
+++ b/pharo-mooc/tiny-chat/src/TinyChat-client/TinyChat.class.st
@@ -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
+]
diff --git a/pharo-mooc/tiny-chat/src/TinyChat-client/package.st b/pharo-mooc/tiny-chat/src/TinyChat-client/package.st
new file mode 100644
index 0000000..acbdb12
--- /dev/null
+++ b/pharo-mooc/tiny-chat/src/TinyChat-client/package.st
@@ -0,0 +1 @@
+Package { #name : 'TinyChat-client' }
diff --git a/pharo-mooc/tiny-chat/src/TinyChat-server/TCMessageQueue.class.st b/pharo-mooc/tiny-chat/src/TinyChat-server/TCMessageQueue.class.st
new file mode 100644
index 0000000..859c77d
--- /dev/null
+++ b/pharo-mooc/tiny-chat/src/TinyChat-server/TCMessageQueue.class.st
@@ -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
+]
diff --git a/pharo-mooc/tiny-chat/src/TinyChat-server/TCServer.class.st b/pharo-mooc/tiny-chat/src/TinyChat-server/TCServer.class.st
new file mode 100644
index 0000000..cda06a0
--- /dev/null
+++ b/pharo-mooc/tiny-chat/src/TinyChat-server/TCServer.class.st
@@ -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
+]
diff --git a/pharo-mooc/tiny-chat/src/TinyChat-server/package.st b/pharo-mooc/tiny-chat/src/TinyChat-server/package.st
new file mode 100644
index 0000000..9fa4170
--- /dev/null
+++ b/pharo-mooc/tiny-chat/src/TinyChat-server/package.st
@@ -0,0 +1 @@
+Package { #name : 'TinyChat-server' }
diff --git a/pharo-mooc/tiny-chat/src/TinyChat/TCMessage.class.st b/pharo-mooc/tiny-chat/src/TinyChat/TCMessage.class.st
new file mode 100644
index 0000000..64962a9
--- /dev/null
+++ b/pharo-mooc/tiny-chat/src/TinyChat/TCMessage.class.st
@@ -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
+]
diff --git a/pharo-mooc/tiny-chat/src/TinyChat/package.st b/pharo-mooc/tiny-chat/src/TinyChat/package.st
new file mode 100644
index 0000000..fe8e725
--- /dev/null
+++ b/pharo-mooc/tiny-chat/src/TinyChat/package.st
@@ -0,0 +1 @@
+Package { #name : 'TinyChat' }