Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
gajim
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
197
Issues
197
List
Boards
Labels
Service Desk
Milestones
Merge Requests
20
Merge Requests
20
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Incidents
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
gajim
gajim
Commits
cc032163
Commit
cc032163
authored
Dec 08, 2010
by
Yann Leboulanger
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
more usage of NEC to handle messages
parent
2c1f20b7
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
149 additions
and
140 deletions
+149
-140
src/chat_control.py
src/chat_control.py
+5
-1
src/common/connection_handlers.py
src/common/connection_handlers.py
+3
-6
src/common/connection_handlers_events.py
src/common/connection_handlers_events.py
+66
-1
src/session.py
src/session.py
+75
-132
No files found.
src/chat_control.py
View file @
cc032163
...
...
@@ -1603,6 +1603,8 @@ def __init__(self, parent_win, contact, acct, session, resource = None):
self
.
_nec_vcard_received
)
gajim
.
ged
.
register_event_handler
(
'failed-decrypt'
,
ged
.
GUI1
,
self
.
_nec_failed_decrypt
)
gajim
.
ged
.
register_event_handler
(
'chatstate-received'
,
ged
.
GUI1
,
self
.
_nec_chatstate_received
)
# PluginSystem: adding GUI extension point for this ChatControl
# instance object
...
...
@@ -2593,6 +2595,8 @@ def shutdown(self):
self
.
_nec_vcard_received
)
gajim
.
ged
.
remove_event_handler
(
'failed-decrypt'
,
ged
.
GUI1
,
self
.
_nec_failed_decrypt
)
gajim
.
ged
.
remove_event_handler
(
'chatstate-received'
,
ged
.
GUI1
,
self
.
_nec_chatstate_received
)
self
.
send_chatstate
(
'gone'
,
self
.
contact
)
self
.
contact
.
chatstate
=
None
...
...
@@ -2661,7 +2665,7 @@ def on_cancel():
return
on_yes
(
self
)
def
handle_incoming_chatstate
(
self
):
def
_nec_chatstate_received
(
self
,
obj
):
"""
Handle incoming chatstate that jid SENT TO us
"""
...
...
src/common/connection_handlers.py
View file @
cc032163
...
...
@@ -1329,18 +1329,15 @@ def _nec_decrypted_message_received(self, obj):
if
obj
.
mtype
==
'error'
:
self
.
dispatch_error_message
(
obj
.
stanza
,
obj
.
msgtxt
,
obj
.
session
,
obj
.
fjid
,
obj
.
timestamp
)
return
True
elif
obj
.
mtype
==
'groupchat'
:
gajim
.
nec
.
push_incoming_event
(
GcMessageReceivedEvent
(
None
,
conn
=
self
,
msg_obj
=
obj
))
return
True
elif
obj
.
invite_tag
is
not
None
:
gajim
.
nec
.
push_incoming_event
(
GcInvitationReceivedEvent
(
None
,
conn
=
self
,
msg_obj
=
obj
))
else
:
if
isinstance
(
obj
.
session
,
gajim
.
default_session_type
):
obj
.
session
.
received
(
obj
.
fjid
,
obj
.
msgtxt
,
obj
.
timestamp
,
obj
.
encrypted
,
obj
.
stanza
)
else
:
obj
.
session
.
received
(
obj
.
stanza
)
return
True
# process and dispatch an error message
def
dispatch_error_message
(
self
,
msg
,
msgtxt
,
session
,
frm
,
tim
):
...
...
src/common/connection_handlers_events.py
View file @
cc032163
...
...
@@ -65,6 +65,34 @@ def _generate_timestamp(self, tag):
tim
=
helpers
.
datetime_tuple
(
tag
)
self
.
timestamp
=
localtime
(
timegm
(
tim
))
def
get_chatstate
(
self
):
"""
Extract chatstate from a <message/> stanza
Requires self.stanza and self.msgtxt
"""
self
.
composing_xep
=
None
self
.
chatstate
=
None
# chatstates - look for chatstate tags in a message if not delayed
delayed
=
self
.
stanza
.
getTag
(
'x'
,
namespace
=
xmpp
.
NS_DELAY
)
is
not
None
if
not
delayed
:
self
.
composing_xep
=
False
children
=
self
.
stanza
.
getChildren
()
for
child
in
children
:
if
child
.
getNamespace
()
==
'http://jabber.org/protocol/chatstates'
:
self
.
chatstate
=
child
.
getName
()
self
.
composing_xep
=
'XEP-0085'
break
# No XEP-0085 support, fallback to XEP-0022
if
not
self
.
chatstate
:
chatstate_child
=
self
.
stanza
.
getTag
(
'x'
,
namespace
=
xmpp
.
NS_EVENT
)
if
chatstate_child
:
self
.
chatstate
=
'active'
self
.
composing_xep
=
'XEP-0022'
if
not
self
.
msgtxt
and
chatstate_child
.
getTag
(
'composing'
):
self
.
chatstate
=
'composing'
class
HttpAuthReceivedEvent
(
nec
.
NetworkIncomingEvent
):
name
=
'http-auth-received'
base_network_events
=
[]
...
...
@@ -1021,7 +1049,7 @@ def generate(self):
return
True
class
DecryptedMessageReceivedEvent
(
nec
.
NetworkIncomingEvent
):
class
DecryptedMessageReceivedEvent
(
nec
.
NetworkIncomingEvent
,
HelperEvent
):
name
=
'decrypted-message-received'
base_network_events
=
[]
...
...
@@ -1044,6 +1072,43 @@ def generate(self):
namespace
=
xmpp
.
NS_RECEIPTS
)
self
.
receipt_received_tag
=
self
.
stanza
.
getTag
(
'received'
,
namespace
=
xmpp
.
NS_RECEIPTS
)
self
.
subject
=
self
.
stanza
.
getSubject
()
self
.
displaymarking
=
None
self
.
seclabel
=
self
.
stanza
.
getTag
(
'securitylabel'
,
namespace
=
xmpp
.
NS_SECLABEL
)
if
self
.
seclabel
:
self
.
displaymarking
=
self
.
seclabel
.
getTag
(
'displaymarking'
)
self
.
form_node
=
self
.
stanza
.
getTag
(
'x'
,
namespace
=
xmpp
.
NS_DATA
)
if
gajim
.
config
.
get
(
'ignore_incoming_xhtml'
):
self
.
xhtml
=
None
else
:
self
.
xhtml
=
self
.
stanza
.
getXHTML
()
# XEP-0172 User Nickname
self
.
user_nick
=
self
.
stanza
.
getTagData
(
'nick'
)
or
''
treat_as
=
gajim
.
config
.
get
(
'treat_incoming_messages'
)
if
treat_as
:
self
.
mtype
=
treat_as
self
.
get_chatstate
()
return
True
class
ChatstateReceivedEvent
(
nec
.
NetworkIncomingEvent
):
name
=
'chatstate-received'
base_network_events
=
[]
def
generate
(
self
):
self
.
stanza
=
self
.
msg_obj
.
stanza
self
.
jid
=
self
.
msg_obj
.
jid
self
.
fjid
=
self
.
msg_obj
.
fjid
self
.
resource
=
self
.
msg_obj
.
resource
self
.
composing_xep
=
self
.
msg_obj
.
composing_xep
self
.
chatstate
=
self
.
msg_obj
.
chatstate
return
True
class
GcMessageReceivedEvent
(
nec
.
NetworkIncomingEvent
):
...
...
src/session.py
View file @
cc032163
...
...
@@ -27,6 +27,8 @@
from
common
import
gajim
from
common
import
stanza_session
from
common
import
contacts
from
common
import
ged
from
common.connection_handlers_events
import
ChatstateReceivedEvent
import
common.xmpp
...
...
@@ -41,12 +43,16 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
def
__init__
(
self
,
conn
,
jid
,
thread_id
,
type_
=
'chat'
):
stanza_session
.
EncryptedStanzaSession
.
__init__
(
self
,
conn
,
jid
,
thread_id
,
type_
=
'chat'
)
gajim
.
ged
.
register_event_handler
(
'decrypted-message-received'
,
ged
.
GUI1
,
self
.
_nec_decrypted_message_received
)
self
.
control
=
None
def
detach_from_control
(
self
):
if
self
.
control
:
self
.
control
.
set_session
(
None
)
gajim
.
ged
.
remove_event_handler
(
'decrypted-message-received'
,
ged
.
GUI1
,
self
.
_nec_decrypted_message_received
)
def
acknowledge_termination
(
self
):
self
.
detach_from_control
()
...
...
@@ -56,175 +62,107 @@ def terminate(self, send_termination = True):
stanza_session
.
EncryptedStanzaSession
.
terminate
(
self
,
send_termination
)
self
.
detach_from_control
()
def
get_chatstate
(
self
,
msg
,
msgtxt
):
"""
Extract chatstate from a <message/> stanza
"""
composing_xep
=
None
chatstate
=
None
# chatstates - look for chatstate tags in a message if not delayed
delayed
=
msg
.
getTag
(
'x'
,
namespace
=
common
.
xmpp
.
NS_DELAY
)
is
not
None
if
not
delayed
:
composing_xep
=
False
children
=
msg
.
getChildren
()
for
child
in
children
:
if
child
.
getNamespace
()
==
'http://jabber.org/protocol/chatstates'
:
chatstate
=
child
.
getName
()
composing_xep
=
'XEP-0085'
break
# No XEP-0085 support, fallback to XEP-0022
if
not
chatstate
:
chatstate_child
=
msg
.
getTag
(
'x'
,
namespace
=
common
.
xmpp
.
NS_EVENT
)
if
chatstate_child
:
chatstate
=
'active'
composing_xep
=
'XEP-0022'
if
not
msgtxt
and
chatstate_child
.
getTag
(
'composing'
):
chatstate
=
'composing'
return
(
composing_xep
,
chatstate
)
def
received
(
self
,
full_jid_with_resource
,
msgtxt
,
tim
,
encrypted
,
msg
):
def
_nec_decrypted_message_received
(
self
,
obj
):
"""
Dispatch a received <message> stanza
"""
msg_type
=
msg
.
getType
()
subject
=
msg
.
getSubject
()
resource
=
gajim
.
get_resource_from_jid
(
full_jid_with_resource
)
if
self
.
resource
!=
resource
:
self
.
resource
=
resource
if
obj
.
session
!=
self
:
return
if
self
.
resource
!=
obj
.
resource
:
self
.
resource
=
obj
.
resource
if
self
.
control
and
self
.
control
.
resource
:
self
.
control
.
change_resource
(
self
.
resource
)
seclabel
=
None
displaymarking
=
None
if
not
msg_type
or
msg_type
not
in
(
'chat'
,
'groupchat'
,
'error'
):
msg_type
=
'normal'
msg_id
=
None
# XEP-0172 User Nickname
user_nick
=
msg
.
getTagData
(
'nick'
)
if
not
user_nick
:
user_nick
=
''
form_node
=
None
for
xtag
in
msg
.
getTags
(
'x'
):
if
xtag
.
getNamespace
()
==
common
.
xmpp
.
NS_DATA
:
form_node
=
xtag
break
composing_xep
,
chatstate
=
self
.
get_chatstate
(
msg
,
msgtxt
)
seclabel
=
msg
.
getTag
(
'securitylabel'
)
if
seclabel
and
seclabel
.
getNamespace
()
==
common
.
xmpp
.
NS_SECLABEL
:
displaymarking
=
seclabel
.
getTag
(
'displaymarking'
)
xhtml
=
msg
.
getXHTML
()
if
msg_type
==
'chat'
:
if
not
msg
.
getTag
(
'body'
)
and
chatstate
is
None
:
if
obj
.
mtype
==
'chat'
:
if
not
obj
.
stanza
.
getTag
(
'body'
)
and
obj
.
chatstate
is
None
:
return
log_type
=
'chat_msg_recv'
else
:
log_type
=
'single_msg_recv'
if
self
.
is_loggable
()
and
msgtxt
:
if
self
.
is_loggable
()
and
obj
.
msgtxt
:
try
:
msg_id
=
gajim
.
logger
.
write
(
log_type
,
full_jid_with_resource
,
msgtxt
,
tim
=
tim
,
subject
=
subject
)
msg_id
=
gajim
.
logger
.
write
(
log_type
,
obj
.
fjid
,
obj
.
msgtxt
,
tim
=
obj
.
timestamp
,
subject
=
obj
.
subject
)
except
exceptions
.
PysqliteOperationalError
,
e
:
self
.
conn
.
dispatch
(
'ERROR'
,
(
_
(
'Disk WriteError'
),
str
(
e
)))
except
exceptions
.
DatabaseMalformed
:
pritext
=
_
(
'Database Error'
)
sectext
=
_
(
'The database file (%s) cannot be read. Try to repair '
'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove '
'it (all history will be lost).'
)
%
common
.
logger
.
LOG_DB_PATH
sectext
=
_
(
'The database file (%s) cannot be read. Try to '
'repair it (see http://trac.gajim.org/wiki/DatabaseBackup) '
'or remove it (all history will be lost).'
)
%
\
common
.
logger
.
LOG_DB_PATH
self
.
conn
.
dispatch
(
'ERROR'
,
(
pritext
,
sectext
))
treat_as
=
gajim
.
config
.
get
(
'treat_incoming_messages'
)
if
treat_as
:
msg_type
=
treat_as
jid
=
gajim
.
get_jid_without_resource
(
full_jid_with_resource
)
resource
=
gajim
.
get_resource_from_jid
(
full_jid_with_resource
)
if
gajim
.
config
.
get
(
'ignore_incoming_xhtml'
):
xhtml
=
None
if
gajim
.
jid_is_transport
(
jid
):
jid
=
jid
.
replace
(
'@'
,
''
)
groupchat_control
=
gajim
.
interface
.
msg_win_mgr
.
get_gc_control
(
jid
,
self
.
conn
.
name
)
if
not
groupchat_control
and
\
jid
in
gajim
.
interface
.
minimized_controls
[
self
.
conn
.
name
]:
groupchat_control
=
gajim
.
interface
.
minimized_controls
[
self
.
conn
.
name
]
\
[
jid
]
pm
=
False
if
groupchat_control
and
groupchat_control
.
type_id
==
\
message_control
.
TYPE_GC
and
resource
:
if
obj
.
gc_control
and
obj
.
resource
:
# It's a Private message
pm
=
True
msg_
type
=
'pm'
obj
.
m
type
=
'pm'
highest_contact
=
gajim
.
contacts
.
get_contact_with_highest_priority
(
self
.
conn
.
name
,
jid
)
self
.
conn
.
name
,
obj
.
jid
)
# does this resource have the highest priority of any available?
is_highest
=
not
highest_contact
or
not
highest_contact
.
resource
or
\
resource
==
highest_contact
.
resource
or
highest_contact
.
show
==
\
'offline'
obj
.
resource
==
highest_contact
.
resource
or
highest_contact
.
show
==
\
'offline'
# Handle chat states
contact
=
gajim
.
contacts
.
get_contact
(
self
.
conn
.
name
,
jid
,
resource
)
contact
=
gajim
.
contacts
.
get_contact
(
self
.
conn
.
name
,
obj
.
jid
,
obj
.
resource
)
if
contact
:
if
contact
.
composing_xep
!=
'XEP-0085'
:
# We cache xep85 support
contact
.
composing_xep
=
composing_xep
if
self
.
control
and
self
.
control
.
type_id
==
message_control
.
TYPE_CHAT
:
if
chatstate
is
not
None
:
contact
.
composing_xep
=
obj
.
composing_xep
if
self
.
control
and
self
.
control
.
type_id
==
\
message_control
.
TYPE_CHAT
:
if
obj
.
chatstate
is
not
None
:
# other peer sent us reply, so he supports jep85 or jep22
contact
.
chatstate
=
chatstate
contact
.
chatstate
=
obj
.
chatstate
if
contact
.
our_chatstate
==
'ask'
:
# we were jep85 disco?
contact
.
our_chatstate
=
'active'
# no more
self
.
control
.
handle_incoming_chatstate
()
gajim
.
nec
.
push_incoming_event
(
ChatstateReceivedEvent
(
None
,
conn
=
obj
.
conn
,
msg_obj
=
obj
))
elif
contact
.
chatstate
!=
'active'
:
# got no valid jep85 answer, peer does not support it
contact
.
chatstate
=
False
elif
chatstate
==
'active'
:
elif
obj
.
chatstate
==
'active'
:
# Brand new message, incoming.
contact
.
our_chatstate
=
chatstate
contact
.
chatstate
=
chatstate
contact
.
our_chatstate
=
obj
.
chatstate
contact
.
chatstate
=
obj
.
chatstate
if
msg_id
:
# Do not overwrite an existing msg_id with None
contact
.
msg_id
=
msg_id
# THIS MUST BE AFTER chatstates handling
# AND BEFORE playsound (else we ear sounding on chatstates!)
if
not
msgtxt
:
# empty message text
if
not
obj
.
msgtxt
:
# empty message text
return
if
gajim
.
config
.
get_per
(
'accounts'
,
self
.
conn
.
name
,
'ignore_unknown_contacts'
)
and
not
gajim
.
contacts
.
get_contacts
(
self
.
conn
.
name
,
jid
)
and
not
pm
:
self
.
conn
.
name
,
obj
.
jid
)
and
not
pm
:
return
if
not
contact
:
# contact is not in the roster, create a fake one to display
# notification
contact
=
gajim
.
contacts
.
create_not_in_roster_contact
(
jid
=
jid
,
account
=
self
.
conn
.
name
,
resource
=
resource
)
contact
=
gajim
.
contacts
.
create_not_in_roster_contact
(
jid
=
obj
.
jid
,
account
=
self
.
conn
.
name
,
resource
=
obj
.
resource
)
advanced_notif_num
=
notify
.
get_advanced_notification
(
'message_received'
,
self
.
conn
.
name
,
contact
)
advanced_notif_num
=
notify
.
get_advanced_notification
(
'message_received'
,
self
.
conn
.
name
,
contact
)
if
not
pm
and
is_highest
:
jid_of_control
=
jid
jid_of_control
=
obj
.
jid
else
:
jid_of_control
=
full_jid_with_resource
jid_of_control
=
obj
.
fjid
if
not
self
.
control
:
ctrl
=
gajim
.
interface
.
msg_win_mgr
.
get_control
(
jid_of_control
,
self
.
conn
.
name
)
self
.
conn
.
name
)
if
ctrl
:
self
.
control
=
ctrl
self
.
control
.
set_session
(
self
)
...
...
@@ -232,50 +170,55 @@ def received(self, full_jid_with_resource, msgtxt, tim, encrypted, msg):
# Is it a first or next message received ?
first
=
False
if
not
self
.
control
and
not
gajim
.
events
.
get_events
(
self
.
conn
.
name
,
\
jid_of_control
,
[
msg_
type
]):
jid_of_control
,
[
obj
.
m
type
]):
first
=
True
if
pm
:
nickname
=
resource
nickname
=
obj
.
resource
if
self
.
control
:
# print if a control is open
self
.
control
.
print_conversation
(
msgtxt
,
tim
=
tim
,
xhtml
=
xhtml
,
encrypted
=
encrypted
,
displaymarking
=
displaymarking
)
self
.
control
.
print_conversation
(
obj
.
msgtxt
,
tim
=
obj
.
timestamp
,
xhtml
=
obj
.
xhtml
,
encrypted
=
obj
.
encrypted
,
displaymarking
=
obj
.
displaymarking
)
else
:
# otherwise pass it off to the control to be queued
groupchat_control
.
on_private_message
(
nickname
,
msgtxt
,
tim
,
xhtml
,
self
,
msg_id
=
msg_id
,
encrypted
=
encrypted
,
displaymarking
=
displaymarking
)
obj
.
gc_control
.
on_private_message
(
nickname
,
obj
.
msgtxt
,
obj
.
timestamp
,
obj
.
xhtml
,
self
,
msg_id
=
msg_id
,
encrypted
=
obj
.
encrypted
,
displaymarking
=
obj
.
displaymarking
)
else
:
self
.
roster_message
(
jid
,
msgtxt
,
tim
,
encrypted
,
msg_type
,
subject
,
resource
,
msg_id
,
user_nick
,
advanced_notif_num
,
xhtml
=
xhtml
,
form_node
=
form_node
,
displaymarking
=
displaymarking
)
self
.
roster_message
(
obj
.
jid
,
obj
.
msgtxt
,
obj
.
timestamp
,
obj
.
encrypted
,
obj
.
mtype
,
obj
.
subject
,
obj
.
resource
,
msg_id
,
obj
.
user_nick
,
advanced_notif_num
,
xhtml
=
obj
.
xhtml
,
form_node
=
obj
.
form_node
,
displaymarking
=
obj
.
displaymarking
)
nickname
=
gajim
.
get_name_from_jid
(
self
.
conn
.
name
,
jid
)
nickname
=
gajim
.
get_name_from_jid
(
self
.
conn
.
name
,
obj
.
jid
)
# Check and do wanted notifications
msg
=
msgtxt
if
subject
:
msg
=
_
(
'Subject: %s'
)
%
subject
+
'
\n
'
+
msg
msg
=
obj
.
msgtxt
if
obj
.
subject
:
msg
=
_
(
'Subject: %s'
)
%
obj
.
subject
+
'
\n
'
+
msg
focused
=
False
if
self
.
control
:
parent_win
=
self
.
control
.
parent_win
if
parent_win
and
self
.
control
==
parent_win
.
get_active_control
()
and
\
parent_win
.
window
.
has_focus
:
if
parent_win
and
self
.
control
==
parent_win
.
get_active_control
()
\
and
parent_win
.
window
.
has_focus
:
focused
=
True
notify
.
notify
(
'new_message'
,
jid_of_control
,
self
.
conn
.
name
,
[
msg_
type
,
notify
.
notify
(
'new_message'
,
jid_of_control
,
self
.
conn
.
name
,
[
obj
.
m
type
,
first
,
nickname
,
msg
,
focused
],
advanced_notif_num
)
if
gajim
.
interface
.
remote_ctrl
:
gajim
.
interface
.
remote_ctrl
.
raise_signal
(
'NewMessage'
,
(
self
.
conn
.
name
,
[
full_jid_with_resource
,
msgtxt
,
tim
,
encrypted
,
msg_type
,
subject
,
chatstate
,
msg_id
,
composing_xep
,
user_nick
,
xhtml
,
form_node
]))
gajim
.
ged
.
raise_event
(
'NewMessage'
,
(
self
.
conn
.
name
,
[
full_jid_with_resource
,
msgtxt
,
tim
,
encrypted
,
msg_type
,
subject
,
chatstate
,
msg_id
,
composing_xep
,
user_nick
,
xhtml
,
form_node
]))
gajim
.
interface
.
remote_ctrl
.
raise_signal
(
'NewMessage'
,
(
self
.
conn
.
name
,
[
obj
.
fjid
,
obj
.
msgtxt
,
obj
.
timestamp
,
obj
.
encrypted
,
obj
.
mtype
,
obj
.
subject
,
obj
.
chatstate
,
msg_id
,
obj
.
composing_xep
,
obj
.
user_nick
,
obj
.
xhtml
,
obj
.
form_node
]))
gajim
.
ged
.
raise_event
(
'NewMessage'
,
(
self
.
conn
.
name
,
[
obj
.
fjid
,
obj
.
msgtxt
,
obj
.
timestamp
,
obj
.
encrypted
,
obj
.
mtype
,
obj
.
subject
,
obj
.
chatstate
,
msg_id
,
obj
.
composing_xep
,
obj
.
user_nick
,
obj
.
xhtml
,
obj
.
form_node
]))
def
roster_message
(
self
,
jid
,
msg
,
tim
,
encrypted
=
False
,
msg_type
=
''
,
subject
=
None
,
resource
=
''
,
msg_id
=
None
,
user_nick
=
''
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment