1 /***************************************************************************
2 Copyright 2006 David Nolden <david.nolden.kdevelop@art-master.de>
3 ***************************************************************************/
5 /***************************************************************************
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
12 ***************************************************************************/
14 #include "filecollaborationmanager.h"
19 #include <QModelIndex>
20 #include <QStandardItemModel>
22 #include <ktexteditor/document.h>
23 #include <ktexteditor/cursor.h>
25 #include <interfaces/idocumentcontroller.h>
27 #include "lib/network/messagesendhelper.h"
28 #include "lib/network/messagetypeset.h"
29 #include "lib/network/sessioninterface.h"
31 #include "lib/dynamictext/verify.h"
33 #include "teamworkfwd.h"
34 #include "patchesmanager.h"
35 #include "kdevteamwork_user.h"
36 #include "kdevutils.h"
37 #include "documentwrapper.h"
38 #include "kdevteamwork_helpers.h"
39 #include "ui_kdevteamwork_filecollaborationsession.h"
41 /* Exclude this file from doublequote_chars check as krazy doesn't understand
43 //krazy:excludeall=doublequote_chars
45 using namespace Teamwork
;
47 CROSSMAP_KEY_EXTRACTOR( FileCollaborationSessionPointer
, QList
<QString
>, 0, value
->plainFileNames() )
48 CROSSMAP_KEY_EXTRACTOR( FileCollaborationSessionPointer
, QList
<KDevTeamworkUserPointer
>, 0, value
->users() )
49 CROSSMAP_KEY_EXTRACTOR( FileCollaborationSessionPointer
, SessionName
, 0, value
->name() )
50 CROSSMAP_KEY_EXTRACTOR( FileCollaborationSessionPointer
, FileCollaborationSessionPointer
, 0, value
)
51 CROSSMAP_KEY_EXTRACTOR( FileCollaborationSessionPointer
, CollabSessionId
, 0, value
->id() )
53 /**How file-collaboration should work:
55 * - Once developer starts a collaboration-session on an arbitrary set of files, he can invite an arbitrary count of collaborating developers to join it.
56 * - He can only host one collaboration-session at a time.
57 * - Once the session is closed, he is asked whether he'd like to store the changes of the session as a patch to the patches-list(that way he may also unapply the changes if he saved them).
59 * - A client gets an invitation and can accept it.
60 * - The client can only be part of one collaboration-session at a time.
61 * - Once the session is closed, he is asked whether the result of the session should be stored as a diff-file to the patches-list.(that way it may be applied to the local tree)
63 * Implementation: See DynamicText
67 /**Algorithm for the FileCollaboration:
68 * - Each FileCollaboration-message that is sent gets a personal sequence-number(which is like a timestamp, the "count of locally happened events")
69 * that can be used to see which state of the communication it was sent in(and can be used to resolve causalities)
70 * - Each FileCollaboration-session has a vector containing all current sequence-numbers
71 * - Each FileCollaboration-message is sent together with that vector, so conflicts may be resolved.
72 * - Whenever the master-session sends a FileResynchronize-message, the local version of the slave-session is totally updated
76 Q_DECLARE_METATYPE( Teamwork::UserPointer
)
80 FileCollaborationManager::FileCollaborationManager( CollaborationManager
* manager
) :
81 SafeLogger( manager
->teamwork() ->logger(),
82 "FileCollaborationManager: " ),
84 m_dispatcher( *this ) {
85 connect( m_manager
, SIGNAL( fillCollaboratingUserMenu( QMenu
*, const UserPointer
& ) ), this, SLOT( slotFillCollaboratingUserMenu( QMenu
*, const UserPointer
& ) ) );
86 m_startCollaborationSessionAction
= new QAction( i18n("Start File-Collaboration"), this );
87 connect( m_startCollaborationSessionAction
, SIGNAL( triggered() ), this, SLOT( slotStartCollaborationSession() ) );
88 connect( manager
, SIGNAL( updateModel( QStandardItemModel
* ) ), this, SLOT( updateCollaborationModel( QStandardItemModel
* ) ) );
92 KDevTeamwork
* FileCollaborationManager::teamwork() {
93 return m_manager
->teamwork();
96 const FileCollaborationManager::SessionSet
& FileCollaborationManager::sessions() {
100 FileCollaborationSession
* FileCollaborationManager::startSession( const QString
& name
, CollabFileList files
, uint primaryIndex
, CollabSessionId id
) {
101 FileCollaborationSessionPointer s
= new FileCollaborationSession( name
, files
, this, primaryIndex
, id
);
102 m_sessions
.insert( s
);
104 connect( s
.data(), SIGNAL( stateChanged( const FileCollaborationSessionPointer
& ) ), this, SLOT( slotSessionStateChanged( const FileCollaborationSessionPointer
& ) ) );
109 void FileCollaborationManager::updateCollaborationModel( QStandardItemModel
* model
) {
111 ///Add/update sessions
112 QMap
< FileCollaborationSessionPointer
, QPersistentModelIndex
> sessions
;
114 for ( int r
= model
->rowCount() - 1; r
>= 0 ; --r
) {
115 QModelIndex i
= model
->index( r
, 0 );
118 QVariant v
= i
.data( Qt::UserRole
);
119 if ( v
.canConvert
<CollaborationTreeActionPointer
>() ) {
120 CollaborationTreeActionPointer action
= v
.value
<CollaborationTreeActionPointer
>();
121 StandardCollaborationTreeAction
<FileCollaborationSession
> *session
= dynamic_cast< StandardCollaborationTreeAction
<FileCollaborationSession
>* >( action
.data() );
123 if ( !session
->target
) {
124 model
->removeRow( r
);
126 sessions
[ (FileCollaborationSession
*)session
->target
] = i
;
132 SessionSet::ValueMap::const_iterator it
= m_sessions
.begin();
133 for ( ; it
!= m_sessions
.end(); ++it
) {
134 FileCollaborationSessionPointer session
= ( *it
).second
.value
;
136 if ( sessions
.contains( session
) ) {
137 i
= sessions
[ session
];
139 model
->insertRow( 0 );
140 i
= model
->index( 0, 0 );
141 sessions
[ session
] = QPersistentModelIndex( i
);
142 disconnect( session
.data(), SIGNAL( stateChanged( const FileCollaborationSessionPointer
& ) ), m_manager
, SLOT( sessionStateChanged( const FileCollaborationSessionPointer
& ) ) );
143 connect( session
.data(), SIGNAL( stateChanged( const FileCollaborationSessionPointer
& ) ), m_manager
, SLOT( sessionStateChanged( const FileCollaborationSessionPointer
& ) ) );
146 QIcon icon
= session
->icon();
147 model
->setData( i
, session
->name(), Qt::DisplayRole
);
148 model
->setData( i
, icon
, Qt::DecorationRole
);
151 model->setData( i, icon, Qt::DecorationRole );*/
153 v
.setValue
<CollaborationTreeActionPointer
>( new StandardCollaborationTreeAction
<FileCollaborationSession
>( session
) );
154 model
->setData( i
, v
, Qt::UserRole
);
156 session
->updateTree( i
, model
);
160 void FileCollaborationManager::slotFillCollaboratingUserMenu( QMenu
* menu
, const UserPointer
& user
) {
161 if ( m_sessions
.empty() ) {
164 m_startCollaborationSessionAction
->setData( v
);
166 menu
->addAction( m_startCollaborationSessionAction
);
169 emit
fillCollaboratingUserMenu( menu
, user
);
174 void FileCollaborationManager::slotStartCollaborationSession() {
176 QAction
* act
= qobject_cast
<QAction
*>( sender() );
180 QVariant v
= act
->data();
181 if ( !v
.canConvert
<UserPointer
>() )
184 UserPointer::Locked lu
= v
.value
<UserPointer
>();
186 throw "could not lock user";
188 CollabFileList files
;
190 files
.push_back(CollabFile(0, currentDocumentPath() ) );
192 Ui_NewFileCollaborationSession s
;
194 d
.setButtons( KDialog::Ok
| KDialog::Cancel
);
195 s
.setupUi( d
.mainWidget() );
196 s
.sessionName
->setText( "Collaborate_on_" + QFileInfo( files
.front().file
).baseName() );
197 QString filesText
= "Files:";
198 for( CollabFileList::iterator it
= files
.begin(); it
!= files
.end(); ++it
) {
199 filesText
+= "\n" + it
->file
;
201 s
.files
->setText( filesText
);
203 QString usersText
= "Invite users:";
204 usersText
+= "\n" + ~lu
->name();
205 s
.users
->setText( usersText
);
207 if( d
.exec() == QDialog::Accepted
) {
208 QString name
= s
.sessionName
->text();
209 FileCollaborationSessionPointer p
= startSession( name
, files
);
211 p
->setAllowSentDocuments( s
.allowSentDocuments
->isChecked() );
212 p
->inviteUser( lu
.freeCast
<KDevTeamworkUser
>() );
214 } catch ( const char * str
) {
215 err() << QString( "Error in slotStartCollaborationSession(): " ) + str
;
216 } catch ( QString str
) {
217 err() << QString( "Error in slotStartCollaborationSession(): " ) + str
;
221 void FileCollaborationManager::processMessage( FileCollaborationMessagePointer msg
) {
222 FileCollaborationMessagePointer::Locked l
= msg
;
224 if ( l
->sessionId() == 0 || msg
.cast
<FileCollaborationRequest
>() ) {
227 FileCollaborationSessionPointer s
= m_sessions
.value
<CollabSessionId
>( l
->sessionId() );
229 s
->processMessage( l
.data() );
231 err() << "got message for unknown file-collaboration-session with id ~" << l
->sessionId() <<", type:" << msg
.unsafe()->name();
235 err() << "could not lock a FileCollaborationMessage";
239 int FileCollaborationManager::receiveMessage( MessageInterface
* msg
) {
240 out( Logger::Warning
) << "got unknown message-type" << msg
->name();
244 int FileCollaborationManager::receiveMessage( FileCollaborationRequest
* msg
) {
245 ///Since it is an AbstractGUIMessage, it can plug itself into the GUI and wait for an answer by the user.
246 m_manager
->teamwork() ->addMessageToList( msg
);
250 int FileCollaborationManager::receiveMessage( FileCollaborationMessage
* msg
) {
251 SessionSet::Iterator it
= m_sessions
.values
<CollabSessionId
>( msg
->sessionId() );
253 return const_cast<FileCollaborationSession
*>((*it
).data())->processMessage( msg
);
255 out( Logger::Warning
) << "got a FileCollaborationMessage of type" << msg
->name() << "for a non-existent session:" << (uint
)msg
->sessionId();
260 void FileCollaborationManager::slotSessionStateChanged( const FileCollaborationSessionPointer
& session
) {
261 m_sessions
.update( session
);
264 void FileCollaborationManager::denyCollaboration( const FileCollaborationRequestPointer
& msg
) {
265 FileCollaborationRequestPointer::Locked l
= msg
;
267 err() << "denyCollaboration(): could not lock message";
271 std::pair
< RequestMap::const_iterator
, RequestMap::const_iterator
> range
= m_requests
.equal_range( l
->sessionId() );
273 ///Deny all other requests for the same session
277 std::pair
< RequestMap::iterator
, RequestMap::iterator
> range
= m_requests
.equal_range( l
->sessionId() );
279 while( range
.first
!= range
.second
) {
280 if( (*range
.first
).second
== msg
) {
281 m_requests
.erase( range
.first
++ );
284 FileCollaborationRequestPointer::Locked lm
= (*range
.first
).second
;
286 int cnt
= m_requests
.size();
287 lm
->denyCollaboration();
288 if( m_requests
.size() != (uint
)cnt
) {
298 bool FileCollaborationManager::acceptCollaboration( const FileCollaborationRequestPointer
& msg
) {
300 FileCollaborationRequestPointer::Locked l
= msg
;
302 throw "could not lock message";
304 //Q_VERIFY( l->index() != 0 );
306 FileCollaborationSessionPointer session
;
307 if( m_sessions
.values( l
->sessionId() ) ) {
308 session
= m_sessions
[ l
->sessionId() ];
310 QString name
= l
->FileCollaborationRequestData::name();
311 if( name
.isEmpty() ) {
312 name
= "anonymous session";
313 if( l
->info().user() )
314 name
+= "@" + ~l
->info().user().unsafe()->safeName();
316 session
= startSession( name
, l
->files(), l
->index(), l
->sessionId() );
318 throw "could not create FileCollaborationSession";
321 session
->acceptMessage( msg
);
323 ///Now pull in all other requests waiting for this session
327 std::pair
< RequestMap::iterator
, RequestMap::iterator
> range
= m_requests
.equal_range( l
->sessionId() );
329 while( range
.first
!= range
.second
) {
330 if( (*range
.first
).second
== msg
) {
331 m_requests
.erase( range
.first
++ );
334 FileCollaborationRequestPointer::Locked lm
= (*range
.first
).second
;
336 int cnt
= m_requests
.size();
337 lm
->denyCollaboration();
338 if( m_requests
.size() != (uint
)cnt
) {
347 out( Logger::Debug
) << "collaboration accepted";
348 m_requests
.erase( l
->sessionId() );
350 } catch ( const QString
& str
) {
351 err() << "error in acceptCollaboration:" << str
;
353 } catch ( const char * str
) {
354 err() << "error in acceptCollaboration:" << str
;
359 void FileCollaborationManager::closeSession( const FileCollaborationSessionPointer
& session
) {
360 session
->aboutToClose();
361 m_sessions
.remove( session
);
365 #include "filecollaborationmanager.moc"
367 // kate: space-indent on; indent-width 2; tab-width 2; replace-tabs on