Don't keep compiling/run if something failed.
[kdevelopdvcssupport.git] / plugins / teamwork / filecollaborationmanager.cpp
blob1b66f9434a31e05cf24a1ffd22febac9a3e5ccda
1 /***************************************************************************
2 Copyright 2006 David Nolden <david.nolden.kdevelop@art-master.de>
3 ***************************************************************************/
5 /***************************************************************************
6 * *
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. *
11 * *
12 ***************************************************************************/
14 #include "filecollaborationmanager.h"
15 #include <QAction>
16 #include <QMenu>
17 #include <QTimer>
18 #include <QFileInfo>
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
42 std::string*/
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:
54 * Host:
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).
58 * Client:
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
65 * */
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
73 * */
76 Q_DECLARE_METATYPE( Teamwork::UserPointer )
80 FileCollaborationManager::FileCollaborationManager( CollaborationManager* manager ) :
81 SafeLogger( manager->teamwork() ->logger(),
82 "FileCollaborationManager: " ),
83 m_manager( manager ),
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() {
97 return m_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& ) ) );
106 return s;
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 );
116 if ( !i.isValid() )
117 continue;
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() );
122 if( session ) {
123 if ( !session->target ) {
124 model->removeRow( r );
125 } else {
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;
135 QModelIndex i;
136 if ( sessions.contains( session ) ) {
137 i = sessions[ session ];
138 } else {
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 );
150 /*QVariant
151 model->setData( i, icon, Qt::DecorationRole );*/
152 QVariant v;
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() ) {
162 QVariant v;
163 v.setValue( user );
164 m_startCollaborationSessionAction->setData( v );
166 menu->addAction( m_startCollaborationSessionAction );
169 emit fillCollaboratingUserMenu( menu, user );
174 void FileCollaborationManager::slotStartCollaborationSession() {
175 try {
176 QAction * act = qobject_cast<QAction*>( sender() );
177 if ( !act )
178 throw "no action";
180 QVariant v = act->data();
181 if ( !v.canConvert<UserPointer>() )
182 throw "wrong data";
184 UserPointer::Locked lu = v.value<UserPointer>();
185 if ( !lu )
186 throw "could not lock user";
188 CollabFileList files;
190 files.push_back(CollabFile(0, currentDocumentPath() ) );
192 Ui_NewFileCollaborationSession s;
193 KDialog d;
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;
223 if ( l ) {
224 if ( l->sessionId() == 0 || msg.cast<FileCollaborationRequest>() ) {
225 m_dispatcher( l );
226 } else {
227 FileCollaborationSessionPointer s = m_sessions.value<CollabSessionId>( l->sessionId() );
228 if ( s ) {
229 s->processMessage( l.data() );
230 } else {
231 err() << "got message for unknown file-collaboration-session with id ~" << l->sessionId() <<", type:" << msg.unsafe()->name();
234 } else {
235 err() << "could not lock a FileCollaborationMessage";
239 int FileCollaborationManager::receiveMessage( MessageInterface* msg ) {
240 out( Logger::Warning ) << "got unknown message-type" << msg->name();
241 return 0;
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 );
247 return 1;
250 int FileCollaborationManager::receiveMessage( FileCollaborationMessage* msg ) {
251 SessionSet::Iterator it = m_sessions.values<CollabSessionId>( msg->sessionId() );
252 if( it ) {
253 return const_cast<FileCollaborationSession*>((*it).data())->processMessage( msg );
254 } else {
255 out( Logger::Warning ) << "got a FileCollaborationMessage of type" << msg->name() << "for a non-existent session:" << (uint)msg->sessionId();
256 return 0;
260 void FileCollaborationManager::slotSessionStateChanged( const FileCollaborationSessionPointer & session ) {
261 m_sessions.update( session );
264 void FileCollaborationManager::denyCollaboration( const FileCollaborationRequestPointer& msg ) {
265 FileCollaborationRequestPointer::Locked l = msg;
266 if ( !l ) {
267 err() << "denyCollaboration(): could not lock message";
268 return;
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
274 bool ready = false;
275 while( !ready ) {
276 ready = true;
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++ );
282 continue;
284 FileCollaborationRequestPointer::Locked lm = (*range.first).second;
285 if( lm ) {
286 int cnt = m_requests.size();
287 lm->denyCollaboration();
288 if( m_requests.size() != (uint)cnt ) {
289 ready = false;
290 break;
293 ++range.first;
298 bool FileCollaborationManager::acceptCollaboration( const FileCollaborationRequestPointer& msg ) {
299 try {
300 FileCollaborationRequestPointer::Locked l = msg;
301 if ( !l )
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() ];
309 } else {
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() );
317 if ( !session )
318 throw "could not create FileCollaborationSession";
321 session->acceptMessage( msg );
323 ///Now pull in all other requests waiting for this session
324 bool ready = false;
325 while( !ready ) {
326 ready = true;
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++ );
332 continue;
334 FileCollaborationRequestPointer::Locked lm = (*range.first).second;
335 if( lm ) {
336 int cnt = m_requests.size();
337 lm->denyCollaboration();
338 if( m_requests.size() != (uint)cnt ) {
339 ready = false;
340 break;
343 ++range.first;
347 out( Logger::Debug ) << "collaboration accepted";
348 m_requests.erase( l->sessionId() );
349 return true;
350 } catch ( const QString & str ) {
351 err() << "error in acceptCollaboration:" << str;
352 return false;
353 } catch ( const char * str ) {
354 err() << "error in acceptCollaboration:" << str;
355 return false;
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