123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305 |
- /**
- * Copyright (c) 2006-2018, JGraph Ltd
- * Copyright (c) 2006-2018, Gaudenz Alder
- *
- * Realtime collaboration for any file.
- */
- DrawioFileSync = function(file)
- {
- mxEventSource.call(this);
- this.lastActivity = new Date();
- this.clientId = Editor.guid();
- this.ui = file.ui;
- this.file = file;
- // Listens to online state changes
- this.onlineListener = mxUtils.bind(this, function()
- {
- this.updateOnlineState();
- if (this.isConnected())
- {
- this.fileChangedNotify();
- }
- });
-
- mxEvent.addListener(window, 'online', this.onlineListener);
-
- // Listens to visible state changes
- this.visibleListener = mxUtils.bind(this, function()
- {
- if (document.visibilityState == 'hidden')
- {
- if (this.isConnected())
- {
- this.stop();
- }
- }
- else
- {
- this.start();
- }
- });
-
- mxEvent.addListener(document, 'visibilitychange', this.visibleListener);
-
- // Listens to visible state changes
- this.activityListener = mxUtils.bind(this, function(evt)
- {
- this.lastActivity = new Date();
- this.start();
- });
- mxEvent.addListener(document, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', this.activityListener);
- mxEvent.addListener(document, 'keypress', this.activityListener);
- mxEvent.addListener(window, 'focus', this.activityListener);
-
- if (!mxClient.IS_POINTER && mxClient.IS_TOUCH)
- {
- mxEvent.addListener(document, 'touchstart', this.activityListener);
- mxEvent.addListener(document, 'touchmove', this.activityListener);
- }
- // Listens to errors in the pusher API
- this.pusherErrorListener = mxUtils.bind(this, function(err)
- {
- if (err.error != null && err.error.data != null &&
- err.error.data.code === 4004)
- {
- EditorUi.logError('Error: Pusher Limit', null, this.file.getId());
- }
- });
- // Listens to connection state changes
- this.connectionListener = mxUtils.bind(this, function()
- {
- this.updateOnlineState();
- this.updateStatus();
-
- if (this.isConnected())
- {
- if (!this.announced)
- {
- var user = this.file.getCurrentUser();
- var join = {a: 'join'};
-
- if (user != null)
- {
- join.name = user.displayName;
- join.uid = user.id;
- }
- mxUtils.post(this.cacheUrl, this.getIdParameters() +
- '&msg=' + encodeURIComponent(this.objectToString(
- this.createMessage(join))));
- this.file.stats.msgSent++;
- this.announced = true;
- }
- // Catchup on any lost edits
- this.fileChangedNotify();
- }
- });
-
- // Listens to remove messages
- this.changeListener = mxUtils.bind(this, function(data)
- {
- this.file.stats.msgReceived++;
- this.lastActivity = new Date();
- if (this.enabled && !this.file.inConflictState &&
- !this.file.redirectDialogShowing)
- {
- try
- {
- var msg = this.stringToObject(data);
-
- if (msg != null)
- {
- EditorUi.debug('Sync.message', [this], msg, data.length, 'bytes');
- // Handles protocol mismatch
- if (msg.v > DrawioFileSync.PROTOCOL)
- {
- this.file.redirectToNewApp(mxUtils.bind(this, function()
- {
- // Callback adds cancel option
- }));
- }
- else if (msg.v === DrawioFileSync.PROTOCOL && msg.d != null)
- {
- this.handleMessageData(msg.d);
- }
- }
- }
- catch (e)
- {
- if (window.console != null && urlParams['test'] == '1')
- {
- console.log(e);
- }
- }
- }
- });
- };
- /**
- * Protocol version to be added to all communcations and diffs to check
- * if a client is out of date and force a refresh. Note that this must
- * be incremented if new messages are added or the format is changed.
- * This must be numeric to compare older vs newer protocol versions.
- */
- DrawioFileSync.PROTOCOL = 6;
- //Extends mxEventSource
- mxUtils.extend(DrawioFileSync, mxEventSource);
- /**
- * Maximum size in bytes for cache values.
- */
- DrawioFileSync.prototype.maxCacheEntrySize = 1000000;
- /**
- * Specifies if notifications should be sent and received for changes.
- */
- DrawioFileSync.prototype.enabled = true;
- /**
- * True if a change event is fired for a remote change.
- */
- DrawioFileSync.prototype.updateStatusInterval = 10000;
- /**
- * True if a change event is fired for a remote change.
- */
- DrawioFileSync.prototype.cacheUrl = (urlParams['dev'] == '1') ? '/cache' : 'https://rt.draw.io/cache';
- /**
- * Holds the channel ID for sending and receiving change notifications.
- */
- DrawioFileSync.prototype.channelId = null;
- /**
- * Holds the channel ID for sending and receiving change notifications.
- */
- DrawioFileSync.prototype.channel = null;
- /**
- * Specifies if descriptor change events should be ignored.
- */
- DrawioFileSync.prototype.catchupRetryCount = 0;
- /**
- * Specifies if descriptor change events should be ignored.
- */
- DrawioFileSync.prototype.maxCatchupRetries = 15;
- /**
- * Specifies if descriptor change events should be ignored.
- */
- DrawioFileSync.prototype.maxCacheReadyRetries = 2;
- /**
- * Specifies if descriptor change events should be ignored.
- */
- DrawioFileSync.prototype.cacheReadyDelay = 500;
- /**
- * Inactivity timeout is 1 hour.
- */
- DrawioFileSync.prototype.inactivityTimeoutSeconds = 3600;
- /**
- * Specifies if notifications should be sent and received for changes.
- */
- DrawioFileSync.prototype.lastActivity = null;
- /**
- * Adds all listeners.
- */
- DrawioFileSync.prototype.start = function()
- {
- if (this.channelId == null)
- {
- this.channelId = this.file.getChannelId();
- }
-
- if (this.key == null)
- {
- this.key = this.file.getChannelKey();
- }
-
- if (this.pusher == null && this.channelId != null &&
- document.visibilityState != 'hidden')
- {
- this.pusher = this.ui.getPusher();
-
- if (this.pusher != null)
- {
- try
- {
- // Error listener must be installed before trying to create channel
- if (this.pusher.connection != null)
- {
- this.pusher.connection.bind('error', this.pusherErrorListener);
- }
- }
- catch (e)
- {
- // ignore
- }
-
- try
- {
- this.pusher.connect();
- this.channel = this.pusher.subscribe(this.channelId);
- EditorUi.debug('Sync.start', [this]);
-
- if (this.file.stats.start == null)
- {
- this.file.stats.start = new Date().toISOString();
- if (!this.ui.isOffline())
- {
- var user = this.file.getCurrentUser();
- var uid = (user != null) ? this.ui.hashValue(user.id) : 'unknown';
-
- EditorUi.logEvent({category: 'RT-START-' + DrawioFile.SYNC,
- action: 'file-' + this.file.getId() +
- '-mode-' + this.file.getMode() +
- '-size-' +this.file.getSize() +
- '-user-' + uid +
- '-client-' + this.clientId,
- label: this.file.stats.start});
- }
- }
- }
- catch (e)
- {
- // ignore
- }
- this.installListeners();
- }
- window.setTimeout(mxUtils.bind(this, function()
- {
- this.lastModified = this.file.getLastModifiedDate();
- this.lastActivity = new Date();
- this.resetUpdateStatusThread();
- this.updateOnlineState();
- this.updateStatus();
- }, 0));
- }
- };
- /**
- * Draw function for the collaborator list.
- */
- DrawioFileSync.prototype.isConnected = function()
- {
- if (this.pusher != null && this.pusher.connection != null)
- {
- return this.pusher.connection.state == 'connected';
- }
- else
- {
- return false;
- }
- };
- /**
- * Draw function for the collaborator list.
- */
- DrawioFileSync.prototype.updateOnlineState = function()
- {
- var addClickHandler = mxUtils.bind(this, function(elt)
- {
- mxEvent.addListener(elt, 'click', mxUtils.bind(this, function(evt)
- {
- this.enabled = !this.enabled;
- this.ui.updateButtonContainer();
- this.resetUpdateStatusThread();
- this.updateOnlineState();
- this.updateStatus();
-
- if (!this.file.inConflictState && this.enabled)
- {
- this.fileChangedNotify();
- }
- }));
- });
- if (uiTheme == 'min' && this.ui.buttonContainer != null)
- {
- if (this.collaboratorsElement == null)
- {
- var elt = document.createElement('a');
- elt.className = 'geToolbarButton';
- elt.style.cssText = 'display:inline-block;position:relative;box-sizing:border-box;margin-right:4px;cursor:pointer;float:left;';
- elt.style.backgroundPosition = 'center center';
- elt.style.backgroundRepeat = 'no-repeat';
- elt.style.backgroundSize = '24px 24px';
- elt.style.height = '24px';
- elt.style.width = '24px';
-
- addClickHandler(elt);
- this.ui.buttonContainer.appendChild(elt);
- this.collaboratorsElement = elt;
- }
- }
- else if (this.ui.toolbarContainer != null)
- {
- if (this.collaboratorsElement == null)
- {
- var elt = document.createElement('a');
- elt.className = 'geButton';
- elt.style.position = 'absolute';
- elt.style.display = 'inline-block';
- elt.style.verticalAlign = 'bottom';
- elt.style.color = '#666';
- elt.style.top = '5px';
- elt.style.right = (uiTheme == 'atlas') ? '42px' : '60px';
- elt.style.padding = '2px';
- elt.style.fontSize = '8pt';
- elt.style.verticalAlign = 'middle';
- elt.style.textDecoration = 'none';
- elt.style.backgroundPosition = 'center center';
- elt.style.backgroundRepeat = 'no-repeat';
- elt.style.backgroundSize = '16px 16px';
- elt.style.width = '16px';
- elt.style.height = '16px';
- mxUtils.setOpacity(elt, 60);
-
- if (uiTheme == 'dark')
- {
- elt.style.filter = 'invert(100%)';
- }
-
- // Prevents focus
- mxEvent.addListener(elt, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown',
- mxUtils.bind(this, function(evt)
- {
- evt.preventDefault();
- }));
-
- addClickHandler(elt);
- this.ui.toolbarContainer.appendChild(elt);
- this.collaboratorsElement = elt;
- }
- }
-
- if (this.collaboratorsElement != null)
- {
- var status = '';
-
- if (!this.enabled)
- {
- status = mxResources.get('disconnected');
- }
- else if (this.file.invalidChecksum)
- {
- status = mxResources.get('error') + ': ' + mxResources.get('checksum');
- }
- else if (this.ui.isOffline() || !this.isConnected())
- {
- status = mxResources.get('offline');
- }
- else
- {
- status = mxResources.get('online');
- }
-
- this.collaboratorsElement.setAttribute('title', status);
- this.collaboratorsElement.style.backgroundImage = 'url(' + ((!this.enabled) ? Editor.syncDisabledImage :
- ((!this.ui.isOffline() && this.isConnected() && !this.file.invalidChecksum) ?
- Editor.syncImage : Editor.syncProblemImage)) + ')';
- }
- };
- /**
- * Updates the status bar with the latest change.
- */
- DrawioFileSync.prototype.updateStatus = function()
- {
- if (this.isConnected() && this.lastActivity != null &&
- (new Date().getTime() - this.lastActivity.getTime()) / 1000 >
- this.inactivityTimeoutSeconds)
- {
- this.stop();
- }
-
- if (!this.file.isModified() && !this.file.inConflictState &&
- this.file.autosaveThread == null && !this.file.savingFile &&
- !this.file.redirectDialogShowing)
- {
- if (this.enabled && this.ui.statusContainer != null)
- {
- // LATER: Write out modified date for more than 2 weeks ago
- var str = this.ui.timeSince(new Date(this.lastModified));
-
- if (str == null)
- {
- str = mxResources.get('lessThanAMinute');
- }
-
- var history = this.file.isRevisionHistorySupported();
- // Consumed and displays last message
- var msg = this.lastMessage;
- this.lastMessage = null;
-
- if (msg != null && msg.length > 40)
- {
- msg = msg.substring(0, 40) + '...';
- }
- var label = mxResources.get('lastChange', [str]);
-
- this.ui.editor.setStatus('<div title="'+ mxUtils.htmlEntities(label) +
- '" style="display:inline-block;">' + mxUtils.htmlEntities(label) + '</div>' +
- ((msg != null) ? ' <span style="opacity:0;" title="' + mxUtils.htmlEntities(msg) +
- '">(' + mxUtils.htmlEntities(msg) + ')</span>' : '') +
- (this.file.isEditable() ? '' : '<div class="geStatusAlert" style="margin-left:8px;display:inline-block;">' +
- mxUtils.htmlEntities(mxResources.get('readOnly')) + '</div>') +
- (this.isConnected() ? '' : '<div class="geStatusAlert geBlink" style="margin-left:8px;display:inline-block;">' +
- mxUtils.htmlEntities(mxResources.get('disconnected')) + '</div>'));
- var links = this.ui.statusContainer.getElementsByTagName('div');
-
- if (links.length > 0 && history)
- {
- links[0].style.cursor = 'pointer';
- links[0].style.textDecoration = 'underline';
-
- mxEvent.addListener(links[0], 'click', mxUtils.bind(this, function()
- {
- this.ui.actions.get('revisionHistory').funct();
- }));
- }
-
- // Fades in/out last message
- var spans = this.ui.statusContainer.getElementsByTagName('span');
-
- if (spans.length > 0)
- {
- var temp = spans[0];
- mxUtils.setPrefixedStyle(temp.style, 'transition', 'all 0.2s ease');
-
- window.setTimeout(mxUtils.bind(this, function()
- {
- mxUtils.setOpacity(temp, 100);
- mxUtils.setPrefixedStyle(temp.style, 'transition', 'all 1s ease');
-
- window.setTimeout(mxUtils.bind(this, function()
- {
- mxUtils.setOpacity(temp, 0);
- }), this.updateStatusInterval / 2);
- }), 0);
- }
-
- this.resetUpdateStatusThread();
- }
- else
- {
- this.file.addAllSavedStatus();
- }
- }
- };
- /**
- * Resets the thread to update the status.
- */
- DrawioFileSync.prototype.resetUpdateStatusThread = function()
- {
- if (this.updateStatusThread != null)
- {
- window.clearInterval(this.updateStatusThread);
- }
-
- if (this.channel != null)
- {
- this.updateStatusThread = window.setInterval(mxUtils.bind(this, function()
- {
- this.updateStatus();
- }), this.updateStatusInterval);
- }
- };
- /**
- * Installs all required listeners for syncing the current file.
- */
- DrawioFileSync.prototype.installListeners = function()
- {
- if (this.pusher != null && this.pusher.connection != null)
- {
- this.pusher.connection.bind('state_change', this.connectionListener);
- }
-
- if (this.channel != null)
- {
- this.channel.bind('changed', this.changeListener);
- }
- };
- /**
- * Adds the listener for automatically saving the diagram for local changes.
- */
- DrawioFileSync.prototype.handleMessageData = function(data)
- {
- if (data.a == 'desc')
- {
- if (!this.file.savingFile)
- {
- this.reloadDescriptor();
- }
- }
- else if (data.a == 'join' || data.a == 'leave')
- {
- if (data.a == 'join')
- {
- this.file.stats.joined++;
- }
-
- if (data.name != null)
- {
- this.lastMessage = mxResources.get((data.a == 'join') ?
- 'userJoined' : 'userLeft', [data.name]);
- this.resetUpdateStatusThread();
- this.updateStatus();
- }
- }
- else if (data.m != null)
- {
- var mod = new Date(data.m);
-
- // Ignores obsolete messages
- if (this.lastMessageModified == null || this.lastMessageModified < mod)
- {
- this.lastMessageModified = mod;
- this.fileChangedNotify();
- }
- }
- };
- /**
- * Adds the listener for automatically saving the diagram for local changes.
- */
- DrawioFileSync.prototype.isValidState = function()
- {
- return this.ui.getCurrentFile() == this.file &&
- this.file.sync == this && !this.file.invalidChecksum &&
- !this.file.redirectDialogShowing;
- };
- /**
- * Adds the listener for automatically saving the diagram for local changes.
- */
- DrawioFileSync.prototype.fileChangedNotify = function()
- {
- if (this.isValidState())
- {
- if (this.file.savingFile)
- {
- this.remoteFileChanged = true;
- }
- else
- {
- // It's possible that a request never returns so override
- // existing requests and abort them when they are active
- var thread = this.fileChanged(mxUtils.bind(this, function(err)
- {
- this.updateStatus();
- }),
- mxUtils.bind(this, function(err)
- {
- this.file.handleFileError(err);
- }), mxUtils.bind(this, function()
- {
- return !this.file.savingFile && this.notifyThread != thread;
- }));
- }
- }
- };
- /**
- * Adds the listener for automatically saving the diagram for local changes.
- */
- DrawioFileSync.prototype.fileChanged = function(success, error, abort)
- {
- var thread = window.setTimeout(mxUtils.bind(this, function()
- {
- if (abort == null || !abort())
- {
- if (!this.isValidState())
- {
- if (error != null)
- {
- error();
- }
- }
- else
- {
- this.file.loadPatchDescriptor(mxUtils.bind(this, function(desc)
- {
- if (abort == null || !abort())
- {
- if (!this.isValidState())
- {
- if (error != null)
- {
- error();
- }
- }
- else
- {
- this.catchup(this.file.getDescriptorEtag(desc),
- this.file.getDescriptorSecret(desc),
- success, error, abort);
- }
- }
- }), error);
- }
- }
- }), 0);
-
- this.notifyThread = thread;
-
- return thread;
- };
- /**
- * Adds the listener for automatically saving the diagram for local changes.
- */
- DrawioFileSync.prototype.reloadDescriptor = function()
- {
- this.file.loadDescriptor(mxUtils.bind(this, function(desc)
- {
- if (desc != null)
- {
- // Forces data to be updated
- this.file.setDescriptorEtag(desc, this.file.getCurrentEtag());
- this.updateDescriptor(desc);
- this.fileChangedNotify();
- }
- else
- {
- this.file.inConflictState = true;
- this.file.handleFileError();
- }
- }), mxUtils.bind(this, function(err)
- {
- this.file.inConflictState = true;
- this.file.handleFileError(err);
- }));
- };
- /**
- * Adds the listener for automatically saving the diagram for local changes.
- */
- DrawioFileSync.prototype.updateDescriptor = function(desc)
- {
- this.file.setDescriptor(desc);
- this.file.descriptorChanged();
- this.start();
- };
- /**
- * Adds the listener for automatically saving the diagram for local changes.
- */
- DrawioFileSync.prototype.catchup = function(etag, secret, success, error, abort)
- {
- if (abort == null || !abort())
- {
- var current = this.file.getCurrentEtag();
-
- if (current == etag)
- {
- if (success != null)
- {
- success();
- }
- }
- else if (!this.isValidState())
- {
- if (error != null)
- {
- error();
- }
- }
- else
- {
- // Cache entry may not have been uploaded to cache before new
- // etag is visible to client so retry once after cache miss
- var cacheReadyRetryCount = 0;
- var failed = false;
-
- var doCatchup = mxUtils.bind(this, function()
- {
- if (abort == null || !abort())
- {
- // Ignores patch if shadow has changed
- if (current != this.file.getCurrentEtag())
- {
- if (success != null)
- {
- success();
- }
- }
- else if (!this.isValidState())
- {
- if (error != null)
- {
- error();
- }
- }
- else
- {
- mxUtils.get(this.cacheUrl + '?id=' + encodeURIComponent(this.channelId) +
- '&from=' + encodeURIComponent(current) + '&to=' + encodeURIComponent(etag) +
- ((secret != null) ? '&secret=' + encodeURIComponent(secret) : ''),
- mxUtils.bind(this, function(req)
- {
- this.file.stats.bytesReceived += req.getText().length;
-
- if (abort == null || !abort())
- {
- // Ignores patch if shadow has changed
- if (current != this.file.getCurrentEtag())
- {
- if (success != null)
- {
- success();
- }
- }
- else if (!this.isValidState())
- {
- if (error != null)
- {
- error();
- }
- }
- else
- {
- var checksum = null;
- var details = [];
- var temp = [];
-
- if (req.getStatus() >= 200 && req.getStatus() <= 299 &&
- req.getText().length > 0)
- {
- try
- {
- var result = JSON.parse(req.getText());
-
- if (result != null && result.length > 0)
- {
- for (var i = 0; i < result.length; i++)
- {
- var value = this.stringToObject(result[i]);
-
- if (value.v > DrawioFileSync.PROTOCOL)
- {
- failed = true;
- temp = [];
- break;
- }
- else if (value.v === DrawioFileSync.PROTOCOL &&
- value.d != null)
- {
- checksum = value.d.checksum;
- temp.push(value.d.patch);
-
- if (value.d.details != null)
- {
- value.d.details.checksum = checksum;
- details.push(JSON.stringify(value.d.details));
- }
- }
- else
- {
- failed = true;
- temp = [];
- break;
- }
- }
- }
- }
- catch (e)
- {
- temp = [];
-
- if (window.console != null && urlParams['test'] == '1')
- {
- console.log(e);
- }
- }
- }
-
- try
- {
- if (temp.length > 0)
- {
- this.file.stats.cacheHits++;
- this.merge(temp, checksum, etag, success, error, abort, details);
- }
- // Retries if cache entry was not yet there
- else if (cacheReadyRetryCount <= this.maxCacheReadyRetries &&
- !failed && req.getStatus() != 401)
- {
- cacheReadyRetryCount++;
- this.file.stats.cacheMiss++;
- window.setTimeout(doCatchup, (cacheReadyRetryCount + 1) * this.cacheReadyDelay);
- }
- else
- {
- this.file.stats.cacheFail++;
- this.reload(success, error, abort);
- }
- }
- catch (e)
- {
- if (error != null)
- {
- error(e);
- }
- }
- }
- }
- }));
- }
- }
- });
-
- window.setTimeout(doCatchup, this.cacheReadyDelay);
- }
- }
- };
- /**
- * Adds the listener for automatically saving the diagram for local changes.
- */
- DrawioFileSync.prototype.reload = function(success, error, abort, shadow)
- {
- this.file.updateFile(mxUtils.bind(this, function()
- {
- this.lastModified = this.file.getLastModifiedDate();
- this.updateStatus();
- this.start();
-
- if (success != null)
- {
- success();
- }
- }), mxUtils.bind(this, function(err)
- {
- if (error != null)
- {
- error(err);
- }
- }), abort, shadow);
- };
- /**
- * Adds the listener for automatically saving the diagram for local changes.
- */
- DrawioFileSync.prototype.merge = function(patches, checksum, etag, success, error, abort, details)
- {
- try
- {
- this.file.stats.merged++;
- this.lastModified = new Date();
- this.file.shadowPages = (this.file.shadowPages != null) ?
- this.file.shadowPages : this.ui.getPagesForNode(
- mxUtils.parseXml(this.file.shadowData).documentElement)
- this.file.checkPages(this.file.shadowPages, 'merge init');
-
- // Creates a patch for backup if the checksum fails
- this.file.backupPatch = (this.file.isModified()) ?
- this.ui.diffPages(this.file.shadowPages,
- this.ui.pages) : null;
- if (!this.file.ignorePatches(patches))
- {
- // Patches the shadow document
- for (var i = 0; i < patches.length; i++)
- {
- this.file.shadowPages = this.ui.patchPages(this.file.shadowPages, patches[i]);
- }
-
- this.file.stats.shadowState = this.ui.hashValue(etag);
- this.file.checkPages(this.file.shadowPages, 'merge patched');
- var currentDetails = {};
- var current = (checksum != null) ? this.ui.getHashValueForPages(
- this.file.shadowPages, currentDetails) : null;
-
- if (urlParams['test'] == '1')
- {
- EditorUi.debug('Sync.merge', [this],
- 'from', this.file.getCurrentEtag(), 'to', etag,
- 'backup', this.file.backupPatch,
- 'attempt', this.catchupRetryCount,
- 'details', details, currentDetails,
- 'patches', patches,
- 'checksum', checksum == current, checksum);
- }
-
- // Compares the checksum
- if (checksum != null && checksum != current)
- {
- var from = this.ui.hashValue(this.file.getCurrentEtag());
- var to = this.ui.hashValue(etag);
-
- currentDetails.inConflictState = this.file.inConflictState;
- currentDetails.invalidChecksum = this.file.invalidChecksum;
-
- this.file.checksumError(error, patches,
- 'From: ' + from +
- '\nTo: ' + to +
- ((details != null && details.length > 0) ? ('\nDetails: ' +
- details.join(', ')) : '') +
- '\nChecksum: ' + checksum +
- '\nCurrent: ' + current +
- ((currentDetails != null) ? ('\nCurrent Details: ' +
- JSON.stringify(currentDetails)) : ''), etag);
- // Uses current state as shadow to compute diff since
- // shadowPages has been modified in-place above
- // LATER: Check if fallback to reload is possible
- // this.reload(success, error, abort, this.ui.pages);
-
- // Abnormal termination
- return;
- }
- else
- {
- // Patches the current document
- this.file.patch(patches,
- (DrawioFile.LAST_WRITE_WINS) ?
- this.file.backupPatch : null);
- }
- }
- else
- {
- this.file.stats.shadowState = this.ui.hashValue(etag);
- }
- this.file.stats.lastMergeTime = new Date().toISOString();
- this.file.stats.lastMerge = details;
- this.file.invalidChecksum = false;
- this.file.inConflictState = false;
- this.file.setCurrentEtag(etag);
- this.file.backupPatch = null;
- this.file.checkPages(this.ui.pages, 'merge done');
-
- if (success != null)
- {
- success();
- }
- }
- catch (e)
- {
- this.file.inConflictState = true;
- this.file.invalidChecksum = true;
- this.file.descriptorChanged();
-
- if (error != null)
- {
- error(e);
- }
-
- try
- {
- var from = this.ui.hashValue(this.file.getCurrentEtag());
- var to = this.ui.hashValue(etag);
-
- this.file.sendErrorReport('Error in merge',
- 'From: ' + from +
- '\nTo: ' + to +
- ((details != null && details.length > 0) ? ('\nDetails: ' +
- details.join(', ')) : '') +
- '\nChecksum: ' + checksum +
- '\nPatches:\n' + this.file.compressReportData(
- JSON.stringify(patches, null, 2)), e);
- }
- catch (e2)
- {
- // ignore
- }
- }
- };
- /**
- * Invokes after a file was saved to add cache entry (which in turn notifies
- * collaborators).
- */
- DrawioFileSync.prototype.descriptorChanged = function(etag)
- {
- this.lastModified = this.file.getLastModifiedDate();
-
- if (this.channelId != null)
- {
- var msg = this.objectToString(this.createMessage({a: 'desc',
- m: this.lastModified.getTime()}));
- var current = this.file.getCurrentEtag();
- var data = this.objectToString({});
- mxUtils.post(this.cacheUrl, this.getIdParameters() +
- '&from=' + encodeURIComponent(etag) + '&to=' + encodeURIComponent(current) +
- '&msg=' + encodeURIComponent(msg) + '&data=' + encodeURIComponent(data));
- this.file.stats.bytesSent += data.length;
- this.file.stats.msgSent++;
- }
-
- this.updateStatus();
- };
- /**
- * Invokes after a file was saved to add cache entry (which in turn notifies
- * collaborators).
- */
- DrawioFileSync.prototype.objectToString = function(obj)
- {
- var data = this.ui.editor.graph.compress(JSON.stringify(obj));
-
- if (this.key != null && typeof CryptoJS !== 'undefined')
- {
- data = CryptoJS.AES.encrypt(data, this.key).toString();
- }
-
- return data;
- };
- /**
- * Invokes after a file was saved to add cache entry (which in turn notifies
- * collaborators).
- */
- DrawioFileSync.prototype.stringToObject = function(data)
- {
- if (this.key != null && typeof CryptoJS !== 'undefined')
- {
- data = CryptoJS.AES.decrypt(data, this.key).toString(CryptoJS.enc.Utf8);
- }
-
- return JSON.parse(this.ui.editor.graph.decompress(data));
- };
- /**
- * Invokes after a file was saved to add cache entry (which in turn notifies
- * collaborators).
- */
- DrawioFileSync.prototype.fileSaved = function(pages, lastDesc, success, error)
- {
- this.lastModified = this.file.getLastModifiedDate();
- this.resetUpdateStatusThread();
- this.catchupRetryCount = 0;
-
- if (!this.ui.isOffline() && !this.file.inConflictState && !this.file.redirectDialogShowing)
- {
- this.start();
- if (this.channelId != null && this.isConnected())
- {
- // Computes diff and checksum
- var shadow = (this.file.shadowPages != null) ?
- this.file.shadowPages : this.ui.getPagesForNode(
- mxUtils.parseXml(this.file.shadowData).documentElement)
- var details = {v: EditorUi.VERSION, t: new Date().toISOString(), ua: navigator.userAgent};
- var checksum = this.ui.getHashValueForPages(pages, details);
- var diff = this.ui.diffPages(shadow, pages);
-
- // Data is stored in cache and message is sent to all listeners
- var etag = this.file.getDescriptorEtag(lastDesc);
- var current = this.file.getCurrentEtag();
-
- details.from = this.ui.hashValue(etag);
- details.to = this.ui.hashValue(current);
-
- var data = this.objectToString(this.createMessage({patch: diff, checksum: checksum, details: details}));
- var msg = this.objectToString(this.createMessage({m: this.lastModified.getTime()}));
- var secret = this.file.getDescriptorSecret(this.file.getDescriptor());
- this.file.stats.bytesSent += data.length;
- this.file.stats.msgSent++;
-
- mxUtils.post(this.cacheUrl, this.getIdParameters() +
- '&from=' + encodeURIComponent(etag) + '&to=' + encodeURIComponent(current) +
- '&msg=' + encodeURIComponent(msg) + ((secret != null) ? '&secret=' + encodeURIComponent(secret) : '') +
- ((data.length < this.maxCacheEntrySize) ? '&data=' + encodeURIComponent(data) : ''),
- mxUtils.bind(this, function(req)
- {
- // Ignores response
- }));
-
- if (urlParams['test'] == '1')
- {
- EditorUi.debug('Sync.fileSaved', [this],
- 'from', etag, 'to', current, data.length,
- 'bytes', 'diff', diff, 'checksum', checksum);
- }
- }
- }
-
- this.file.shadowPages = pages;
-
- if (success != null)
- {
- success();
- }
- };
- /**
- * Creates the properties for the file descriptor.
- */
- DrawioFileSync.prototype.getIdParameters = function()
- {
- var result = 'id=' + this.channelId;
-
- if (this.pusher != null && this.pusher.connection != null)
- {
- result += '&sid=' + this.pusher.connection.socket_id;
- }
-
- return result;
- };
- /**
- * Creates the properties for the file descriptor.
- */
- DrawioFileSync.prototype.createMessage = function(data)
- {
- return {v: DrawioFileSync.PROTOCOL, d: data, c: this.clientId};
- };
- /**
- * Creates the properties for the file descriptor.
- */
- DrawioFileSync.prototype.fileConflict = function(desc, success, error)
- {
- this.catchupRetryCount++;
-
- if (this.catchupRetryCount < this.maxCatchupRetries)
- {
- this.file.stats.conflicts++;
-
- if (desc != null)
- {
- var etag = this.file.getDescriptorEtag(desc);
- var secret = this.file.getDescriptorSecret(desc);
- this.catchup(etag, secret, success, error);
- }
- else
- {
- this.fileChanged(success, error);
- }
- }
- else
- {
- this.catchupRetryCount = 0;
- this.file.stats.timeouts++;
-
- if (error != null)
- {
- error({message: mxResources.get('timeout')});
- }
- }
- };
- /**
- * Adds the listener for automatically saving the diagram for local changes.
- */
- DrawioFileSync.prototype.stop = function()
- {
- if (this.pusher != null)
- {
- EditorUi.debug('Sync.stop', [this]);
-
- if (this.pusher.connection != null)
- {
- this.pusher.connection.unbind('state_change', this.connectionListener);
- this.pusher.connection.unbind('error', this.pusherErrorListener);
- }
-
- if (this.channel != null)
- {
- this.channel.unbind('changed', this.changeListener);
-
- // See https://github.com/pusher/pusher-js/issues/75
- // this.pusher.unsubscribe(this.channelId);
- this.channel = null;
- }
-
- this.pusher.disconnect();
- this.pusher = null;
- }
-
- this.updateOnlineState();
- this.updateStatus();
- };
- /**
- * Adds the listener for automatically saving the diagram for local changes.
- */
- DrawioFileSync.prototype.destroy = function()
- {
- if (this.channelId != null)
- {
- var user = this.file.getCurrentUser();
- var leave = {a: 'leave'};
-
- if (user != null)
- {
- leave.name = user.displayName;
- leave.uid = user.id;
- }
-
- mxUtils.post(this.cacheUrl, this.getIdParameters() +
- '&msg=' + encodeURIComponent(this.objectToString(
- this.createMessage(leave))));
- this.file.stats.msgSent++;
- }
-
- this.stop();
- if (this.updateStatusThread != null)
- {
- window.clearInterval(this.updateStatusThread);
- this.updateStatusThread = null;
- }
-
- if (this.onlineListener != null)
- {
- mxEvent.removeListener(window, 'online', this.onlineListener);
- this.onlineListener = null;
- }
- if (this.visibleListener != null)
- {
- mxEvent.removeListener(document, 'visibilitychange', this.visibleListener);
- this.visibleListener = null;
- }
- if (this.activityListener != null)
- {
- mxEvent.removeListener(document, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', this.activityListener);
- mxEvent.removeListener(document, 'keypress', this.activityListener);
- mxEvent.removeListener(window, 'focus', this.activityListener);
-
- if (!mxClient.IS_POINTER && mxClient.IS_TOUCH)
- {
- mxEvent.removeListener(document, 'touchstart', this.activityListener);
- mxEvent.removeListener(document, 'touchmove', this.activityListener);
- }
-
- this.activityListener = null;
- }
-
- if (this.collaboratorsElement != null)
- {
- this.collaboratorsElement.parentNode.removeChild(this.collaboratorsElement);
- this.collaboratorsElement = null;
- }
- };
|