Added storing of cursor position so you do not get kicked to the beginning or end of file on join

This commit is contained in:
xevidos 2018-08-09 10:22:31 -04:00
parent 2f4727aad3
commit 397b656c87
3 changed files with 462 additions and 88 deletions

View file

@ -0,0 +1,307 @@
<?php
/*
* Copyright (c) Luc Verdier & Florent Galland, distributed
* as-is and without warranty under the MIT License. See
* [root]/license.txt for more. This information must remain intact.
*/
/*
* Suppose a user wants to register as a collaborator of file '/test/test.js'.
* He registers to a specific file by creating a marker file
* 'data/_test_test.js%%filename%%username%%registered', and he can
* unregister by deleting this file. Then his current selection will be in
* file 'data/_test_test.js%%username%%selection'.
* The collaborative editing algorithm is based on the differential synchronization
* algorithm by Neil Fraser. The text shadow and server text are stored
* respectively in 'data/_test_test.js%%filename%%username%%shadow' and
* 'data/_test_test.js%%filename%%text'.
* At regular time intervals, the user send an heartbeat which is stored in
* 'data/_test_test.js%%username%%heartbeat' .
*/
/* */
class file_db {
/* They should be the same as those
* in the file_db_entry class. */
private $separator = '|';
private $separator_regex = '\|';
private $key_value_separator = ':';
private $key_value_separator_regex = '\:';
private $index_name = 'index.db';
private $base_path;
function __construct($base_path) {
$this->base_path = $base_path;
if(!is_dir($base_path)) {
mkdir($base_path);
}
}
/* Create a new entry into the data base. */
public function create($query, $group=null) {
$query = $this->_normalize_query($query);
if(!$this->_is_direct_query($query)) {
return null;
}
$base_path = $this->base_path;
if($group != null) {
$base_path .= '/' . $group;
}
$index_file = $base_path . '/' . $this->index_name;
if(!is_dir($base_path)) {
mkdir($base_path);
}
$entry_name = $this->_make_entry_name($query);
$entry_hash = md5($entry_name);
$entry_file = $base_path . '/' . $entry_hash;
if(!file_exists($entry_file)) {
if(!file_exists($index_file)) {
touch($index_file);
}
$entry = $entry_name . '>' . $entry_hash . '>' . PHP_EOL;
file_put_contents($index_file, $entry, FILE_APPEND | LOCK_EX);
touch($entry_file);
}
$entry = new file_db_entry($entry_name, $entry_file, $index_file, $group);
$entry->clear();
if(file_exists($entry_file)) {
return $entry;
}
return null;
}
/* Get the content for the given query. */
public function select($query, $group=null) {
$query = $this->_normalize_query($query);
$base_path = $this->base_path;
if($group != null) {
$base_path .= '/' . $group;
}
$index_file = $base_path . '/' . $this->index_name;
if($this->_is_direct_query($query)) {
$entry_name = $this->_make_entry_name($query);
$entry_hash = md5($entry_name);
$entry_file = $base_path . '/' . $entry_hash;
if(file_exists($entry_file)) {
return new file_db_entry($entry_name, $entry_file, $index_file, $group);
}
return null;
}
$entries = array();
if(file_exists($index_file)) {
$regex = $this->_make_regex($query);
$file = fopen($index_file, 'r');
while(!feof($file)) {
$line = fgets($file);
if (preg_match($regex, $line, $matches)) {
$entry_file = $base_path . '/' . $matches[2];
if(file_exists($entry_file)) {
$entries[] = new file_db_entry($matches[1], $entry_file, $index_file, $group);
}
}
}
fclose($file);
}
return $entries;
}
/* Select all entries into the given group. */
public function select_group($group) {
$entries = array();
$base_path = $this->base_path . '/' . $group;
$index_file = $base_path . '/' . $this->index_name;
if(file_exists($index_file)) {
$sep = $this->separator_regex;
$regex = '/(' . $sep . '.*?' . $sep . ')\>(.*?)\>/';
$file = fopen($index_file, 'r');
while(!feof($file)) {
$line = fgets($file);
if (preg_match($regex, $line, $matches)) {
$entry_file = $base_path . '/' . $matches[2];
$entries[] = new file_db_entry($matches[1], $entry_file, $index_file, $group);
}
}
fclose($file);
}
return $entries;
}
/* Make the regex for the given query. */
private function _make_regex($query) {
$regex = '/(' . $this->separator_regex;
foreach($query as $key => $value) {
$regex .= $key . $this->key_value_separator_regex;
if($value == '%2A') { // %2A=*
$regex .= '.*?';
}
else {
$regex .= $value;
}
$regex .= $this->separator_regex;
}
$regex .= ')' . '\>(.*?)\>' . '/';
return $regex;
}
/* Make an entry name from the given normalized query. */
private function _make_entry_name($query) {
$filename = $this->separator;
foreach($query as $key => $value) {
$filename .= $key . $this->key_value_separator . $value;
$filename .= $this->separator;
}
return $filename;
}
/* Check if the given query is a direct query. */
private function _is_direct_query($query) {
foreach($query as $key => $value) {
if($value == '%2A'){ // %2A=*
return false;
}
}
return true;
}
/* Normalize the given query. */
private function &_normalize_query(&$query) {
ksort($query);
foreach($query as $key => $value) {
$query[$key] = rawurlencode($value);
}
return $query;
}
}
/* */
class file_db_entry {
/* They should be the same as those
* in the file_db class. */
private $separator = '|';
private $separator_regex = '\|';
private $key_value_separator = ':';
private $key_value_separator_regex = '\:';
private $entry_name;
private $entry_hash;
private $entry_file;
private $index_file;
private $group;
private $handler;
/* Construct the entry with the given filename. */
public function __construct($entry_name, $entry_file, $index_file, $group) {
$this->entry_name = $entry_name;
$this->entry_file = $entry_file;
$this->index_file = $index_file;
$this->group = $group;
}
/* Get the value of the field with the given name. */
public function get_field($name) {
$regex = '/'
. $this->separator_regex
. rawurlencode($name)
. $this->key_value_separator_regex
. '(.*?)' . $this->separator_regex
. '/';
if(preg_match($regex, $this->entry_name, $matches)) {
return rawurldecode($matches[1]);
}
return null;
}
/* Get the group name of the entry. */
public function get_group() {
return $this->group;
}
/* Set the value of the entry. */
public function put_value($value) {
file_put_contents($this->entry_file, serialize($value), LOCK_EX);
}
/* Get the value of the entry. */
public function get_value() {
if(file_exists($this->entry_file)) {
return unserialize(file_get_contents($this->entry_file));
}
return null;
}
/* Remove the entry. */
public function remove() {
$success = false;
if(file_exists($this->index_file)) {
$lines = file($this->index_file, FILE_SKIP_EMPTY_LINES);
$file = fopen($this->index_file, 'w');
flock($file, LOCK_EX);
foreach($lines as $line) {
if (strpos($line, $this->entry_name) !== 0) {
fwrite($file, $line);
}
else {
$success = true;
}
}
flock($file, LOCK_UN);
fclose($file);
}
if($success && file_exists($this->entry_file)) {
unlink($this->entry_file);
return true;
}
return false;
}
/* Clear the value of the entry. */
public function clear() {
$this->put_value('');
}
/* Lock the entry. */
public function lock() {
$lock = $this->entry_file . '.lock';
while(file_exists($lock)) {
usleep(100);
}
touch($this->entry_file . '.lock');
}
/* Unlock the entry. */
public function unlock() {
$lock = $this->entry_file . '.lock';
if(file_exists($lock)) {
unlink($lock);
}
}
}
?>

View file

@ -11,18 +11,19 @@
path = scripts[scripts.length-1].src.split('?')[0], path = scripts[scripts.length-1].src.split('?')[0],
curpath = path.split( '/' ).slice( 0, -1 ).join( '/ ') + '/'; curpath = path.split( '/' ).slice( 0, -1 ).join( '/ ') + '/';
var site_id = codiad.system.site_id;
var session_id = window.session_id;
var editor = null;
var collaborator = null;
var buffer_dumped = false; var buffer_dumped = false;
var last_applied_change = null; var collaborator = null;
var just_cleared_buffer = null;
var current_editor = codiad.active.getPath(); var current_editor = codiad.active.getPath();
var just_opened = false; var cursor = null;
var loaded = false; var editor = null;
var initial = false; var initial = false;
var just_cleared_buffer = null;
var just_opened = false;
var last_applied_change = null;
var loaded = false;
var loading = true; var loading = true;
var session_id = codiad.system.session_id;
var site_id = codiad.system.site_id;
function Collaborator( file_path, session_id ) { function Collaborator( file_path, session_id ) {
@ -58,26 +59,45 @@
if ( delta.initial === true ) { if ( delta.initial === true ) {
console.log( 'Setting initial content...' ); console.log( 'Setting initial content...' );
codiad.editor.setContent( '' ); //codiad.editor.setContent( '' );
codiad.editor.setContent( delta.content ) codiad.editor.setContent( delta.content )
inital = false; inital = false;
} }
setTimeout(function(){
if( cursor !== null ) {
console.log( 'Going to position: ' + cursor.row + ", " + cursor.column );
editor.gotoLine( cursor.row, cursor.column, true );
}
}, 256);
}.bind() ); }.bind() );
this.collaboration_socket.on( "recieve_content", function( ) { this.collaboration_socket.on( "recieve_content", function( ) {
console.log( 'Someone is joining ...' ); console.log( 'Someone is joining ...' );
console.log( 'Cursor is at ' + cursor.row + ", " + cursor.column );
// Remove change callback // Remove change callback
editor.removeEventListener( "change", handle_change ); editor.removeEventListener( "change", handle_change );
codiad.editor.disableEditing(); codiad.editor.disableEditing();
codiad.editor.setContent( '' ); codiad.editor.setContent( '' );
collaborator.dump_buffer(); collaborator.dump_buffer()
// registering change callback // registering change callback
editor.addEventListener( "change", handle_change ); editor.addEventListener( "change", handle_change );
setTimeout(function(){codiad.editor.enableEditing();}, 500); }.bind() );
this.collaboration_socket.on( "unlock", function( ) {
console.log( 'Unlocking editors and going to ' + cursor.row + ", " + cursor.column );
codiad.editor.enableEditing();
setTimeout(function(){
if( cursor !== null ) {
console.log( 'Going to position: ' + cursor.row + ", " + cursor.column );
editor.gotoLine( cursor.row, cursor.column, true );
}
}, 256);
}.bind() ); }.bind() );
window.collaboration_socket = this.collaboration_socket; window.collaboration_socket = this.collaboration_socket;
@ -107,6 +127,13 @@
function handle_change( e ) { function handle_change( e ) {
// TODO, we could make things more efficient and not likely to conflict by keeping track of change IDs // TODO, we could make things more efficient and not likely to conflict by keeping track of change IDs
coords = editor.getCursorPosition();
if( coords.row !== 0 && coords.column !== 0 ) {
cursor = editor.getCursorPosition();
cursor.row = cursor.row + 1
console.log( 'Cursor at: ' + cursor.row + ", " + cursor.column );
}
if( last_applied_change!=e && !just_cleared_buffer ) { if( last_applied_change!=e && !just_cleared_buffer ) {
collaborator.change( JSON.stringify(e) ); collaborator.change( JSON.stringify(e) );
@ -166,6 +193,7 @@
collaborator = new Collaborator( session_id ); collaborator = new Collaborator( session_id );
//codiad.editor.disableEditing();
//collaborator.open_file( ) //collaborator.open_file( )
content = codiad.editor.getContent() content = codiad.editor.getContent()
codiad.editor.setContent( '' ) codiad.editor.setContent( '' )
@ -201,9 +229,29 @@
}); });
$(window).blur(function() { $(window).blur(function() {
if( editor !== null ) {
coords = editor.getCursorPosition();
if( coords.row !== 0 && coords.column !== 0 ) {
cursor = editor.getCursorPosition();
cursor.row = cursor.row + 1
console.log( 'Cursor at: ' + cursor.row + ", " + cursor.column );
}
}
close(); close();
}); });
/* When the window is clicked get the cursor position.
$(window).click(function () {
coords = editor.getCursorPosition();
if( coords.row !== 0 && coords.column !== 0 ) {
//cursor = editor.getCursorPosition();
//cursor.row = cursor.row + 1
//console.log( 'Cursor at: ' + cursor.row + ", " + cursor.column );
}
});*/
/* Subscribe to know when a file become active. */ /* Subscribe to know when a file become active. */
amplify.subscribe('active.onFocus', function (path) { amplify.subscribe('active.onFocus', function (path) {
@ -211,7 +259,8 @@
if( current_editor !== codiad.active.getPath() && current_editor !== null ) { if( current_editor !== codiad.active.getPath() && current_editor !== null ) {
console.log( 'Closing Socket' );+ cursor = null;
console.log( 'Closing Socket' );
close(); close();
} }
console.log( 'Last Editor: ' + current_editor ); console.log( 'Last Editor: ' + current_editor );
@ -222,6 +271,13 @@
body_loaded(); body_loaded();
} }
console.log( 'Focused Editor: ' + codiad.active.getPath() ); console.log( 'Focused Editor: ' + codiad.active.getPath() );
coords = editor.getCursorPosition();
if( coords.row !== 0 && coords.column !== 0 ) {
cursor = editor.getCursorPosition();
cursor.row = cursor.row + 1
console.log( 'Cursor at: ' + cursor.row + ", " + cursor.column );
}
}); });
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
@ -234,6 +290,8 @@
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
codiad.collaborative = { codiad.collaborative = {
}; };
})(this, jQuery); })(this, jQuery);

View file

@ -27,10 +27,10 @@ socket_id_to_session_id = [] ;
io.sockets.on('connection', function(socket) { io.sockets.on('connection', function(socket) {
init = false
var file = socket.handshake.query.file; var file = socket.handshake.query.file;
//var session_id = socket.handshake.query.session_id; //var session_id = socket.handshake.query.session_id;
var session_id = socket.handshake.query.file; var session_id = socket.handshake.query.file;
socket_id_to_session_id[socket.id] = session_id ; socket_id_to_session_id[socket.id] = session_id ;
if( verbose ) { console.log( session_id + "connected on socket" + socket.id ) ; } if( verbose ) { console.log( session_id + "connected on socket" + socket.id ) ; }
@ -59,13 +59,21 @@ io.sockets.on('connection', function(socket) {
} }
} }
for( var i=0 ; i<collaborations[session_id]['participants'].length ; i++ ) { var i = 0;
for( i=0 ; i<collaborations[session_id]['participants'].length ; i++ ) {
if( socket.id!=collaborations[session_id]['participants'][i] ) { if( socket.id!=collaborations[session_id]['participants'][i] ) {
io.sockets.connected[collaborations[session_id]['participants'][i]].emit( "recieve_content" ); io.sockets.connected[collaborations[session_id]['participants'][i]].emit( "recieve_content" );
} }
} }
collaborations[session_id]['participants'].push( socket.id ); collaborations[session_id]['participants'].push( socket.id );
for( i=0 ; i<collaborations[session_id]['participants'].length ; i++ ) {
if( socket.id!=collaborations[session_id]['participants'][i] ) {
io.sockets.connected[collaborations[session_id]['participants'][i]].emit( "unlock" );
}
}
socket.on('change', function( delta ) { socket.on('change', function( delta ) {
if( verbose ) { console.log( "change " + socket_id_to_session_id[socket.id] + " " + delta ) ; } if( verbose ) { console.log( "change " + socket_id_to_session_id[socket.id] + " " + delta ) ; }
@ -113,6 +121,7 @@ io.sockets.on('connection', function(socket) {
socket.on('dump_buffer', function() { socket.on('dump_buffer', function() {
if( verbose ) { console.log( "dump_buffer " + socket_id_to_session_id[socket.id] ) ; } if( verbose ) { console.log( "dump_buffer " + socket_id_to_session_id[socket.id] ) ; }
if( socket_id_to_session_id[socket.id] in collaborations ) { if( socket_id_to_session_id[socket.id] in collaborations ) {
for( var i=0 ; i<collaborations[socket_id_to_session_id[socket.id]]['cached_instructions'].length ; i++ ) { for( var i=0 ; i<collaborations[socket_id_to_session_id[socket.id]]['cached_instructions'].length ; i++ ) {
socket.emit( collaborations[socket_id_to_session_id[socket.id]]['cached_instructions'][i][0], collaborations[socket_id_to_session_id[socket.id]]['cached_instructions'][i][1] ) ; socket.emit( collaborations[socket_id_to_session_id[socket.id]]['cached_instructions'][i][0], collaborations[socket_id_to_session_id[socket.id]]['cached_instructions'][i][1] ) ;