From 397b656c87c748244fb62af3f68531d818940d59 Mon Sep 17 00:00:00 2001 From: xevidos Date: Thu, 9 Aug 2018 10:22:31 -0400 Subject: [PATCH] Added storing of cursor position so you do not get kicked to the beginning or end of file on join --- .../Codiad-Collaborative-master/file_db.php | 307 ++++++++++++++++++ plugins/Codiad-Collaborative-master/init.js | 92 +++++- plugins/Codiad-Collaborative-master/server.js | 151 +++++---- 3 files changed, 462 insertions(+), 88 deletions(-) create mode 100755 plugins/Codiad-Collaborative-master/file_db.php diff --git a/plugins/Codiad-Collaborative-master/file_db.php b/plugins/Codiad-Collaborative-master/file_db.php new file mode 100755 index 0000000..a6e9a30 --- /dev/null +++ b/plugins/Codiad-Collaborative-master/file_db.php @@ -0,0 +1,307 @@ +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); + } + } +} + +?> diff --git a/plugins/Codiad-Collaborative-master/init.js b/plugins/Codiad-Collaborative-master/init.js index 940d960..1a808ee 100755 --- a/plugins/Codiad-Collaborative-master/init.js +++ b/plugins/Codiad-Collaborative-master/init.js @@ -9,20 +9,21 @@ var codiad = global.codiad, scripts = document.getElementsByTagName('script'), 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 last_applied_change = null; - var just_cleared_buffer = null; + var collaborator = null; var current_editor = codiad.active.getPath(); - var just_opened = false; - var loaded = false; + var cursor = null; + var editor = null; var initial = false; + var just_cleared_buffer = null; + var just_opened = false; + var last_applied_change = null; + var loaded = false; var loading = true; + var session_id = codiad.system.session_id; + var site_id = codiad.system.site_id; function Collaborator( file_path, session_id ) { @@ -58,26 +59,45 @@ if ( delta.initial === true ) { console.log( 'Setting initial content...' ); - codiad.editor.setContent( '' ); + //codiad.editor.setContent( '' ); codiad.editor.setContent( delta.content ) 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() ); 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 editor.removeEventListener( "change", handle_change ); - codiad.editor.disableEditing(); codiad.editor.setContent( '' ); - collaborator.dump_buffer(); - + collaborator.dump_buffer() // registering change callback 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() ); window.collaboration_socket = this.collaboration_socket; @@ -107,6 +127,13 @@ function handle_change( e ) { // 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 ) { collaborator.change( JSON.stringify(e) ); @@ -166,6 +193,7 @@ collaborator = new Collaborator( session_id ); + //codiad.editor.disableEditing(); //collaborator.open_file( ) content = codiad.editor.getContent() codiad.editor.setContent( '' ) @@ -201,17 +229,38 @@ }); $(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(); }); + /* 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. */ amplify.subscribe('active.onFocus', function (path) { just_opened = true; if( current_editor !== codiad.active.getPath() && current_editor !== null ) { - - console.log( 'Closing Socket' );+ + + cursor = null; + console.log( 'Closing Socket' ); close(); } console.log( 'Last Editor: ' + current_editor ); @@ -222,6 +271,13 @@ body_loaded(); } 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 = { + + }; })(this, jQuery); \ No newline at end of file diff --git a/plugins/Codiad-Collaborative-master/server.js b/plugins/Codiad-Collaborative-master/server.js index 32cf085..973f19d 100755 --- a/plugins/Codiad-Collaborative-master/server.js +++ b/plugins/Codiad-Collaborative-master/server.js @@ -1,19 +1,19 @@ // config variables verbose = false ; -session_directory = "./sessions" ; // it has to exist +session_directory = "./sessions"; // it has to exist /* https specific */ -var https = require('https'), -fs = require('fs'); +var https = require( 'https' ), +fs = require( 'fs' ); var options = { -key: fs.readFileSync('/etc/letsencrypt/live/local.telaaedifex.com/privkey.pem'), -cert: fs.readFileSync('/etc/letsencrypt/live/local.telaaedifex.com/fullchain.pem'), -ca: fs.readFileSync('/etc/letsencrypt/live/local.telaaedifex.com/chain.pem') + key: fs.readFileSync( '/etc/letsencrypt/live/local.telaaedifex.com/privkey.pem' ), + cert: fs.readFileSync( '/etc/letsencrypt/live/local.telaaedifex.com/fullchain.pem' ), + ca: fs.readFileSync( '/etc/letsencrypt/live/local.telaaedifex.com/chain.pem' ) }; -var app = https.createServer(options); -io = require('socket.io').listen(app); //socket.io server listens to https connections -app.listen(1337, "0.0.0.0"); +var app = https.createServer( options ); +io = require( 'socket.io' ).listen( app ); //socket.io server listens to https connections +app.listen( 1337, "0.0.0.0" ); // will use the following for file IO var fs = require( "fs" ) ; @@ -27,10 +27,10 @@ socket_id_to_session_id = [] ; io.sockets.on('connection', function(socket) { + init = false var file = socket.handshake.query.file; //var session_id = socket.handshake.query.session_id; var session_id = socket.handshake.query.file; - socket_id_to_session_id[socket.id] = session_id ; if( verbose ) { console.log( session_id + "connected on socket" + socket.id ) ; } @@ -42,7 +42,7 @@ io.sockets.on('connection', function(socket) { if( verbose ) { console.log( "session terminated previously, pulling back from filesystem" ); } var data = read_file( session_directory + "/" + session_id ); - if( data!==false ) { + if( data !== false ) { init = false; collaborations[session_id] = {'cached_instructions':JSON.parse(data), 'participants':[]}; @@ -59,23 +59,31 @@ io.sockets.on('connection', function(socket) { } } - for( var i=0 ; i-1 ) { - //collaborations[socket_id_to_session_id[socket.id]].participants.splice( index, 1 ) ; - collaborations[socket_id_to_session_id[socket.id]]['participants'].splice( index, 1 ) ; - found_and_removed = true ; - //if( collaborations[socket_id_to_session_id[socket.id]].participants.length==0 ) { - if( collaborations[socket_id_to_session_id[socket.id]]['participants'].length==0 ) { - if( verbose ) { console.log( "last participant in collaboration, committing to disk & removing from memory" ) ; } - // no one is left in this session, we commit it to disk & remove it from memory - write_file( session_directory + "/" + socket_id_to_session_id[socket.id], JSON.stringify(collaborations[socket_id_to_session_id[socket.id]]['cached_instructions']) ) ; - delete collaborations[socket_id_to_session_id[socket.id]] ; - } - } + //var index = collaborations[socket_id_to_session_id[socket.id]].participants.indexOf( socket.id ) ; + var index = collaborations[socket_id_to_session_id[socket.id]]['participants'].indexOf( socket.id ) ; + if( index>-1 ) { + //collaborations[socket_id_to_session_id[socket.id]].participants.splice( index, 1 ) ; + collaborations[socket_id_to_session_id[socket.id]]['participants'].splice( index, 1 ) ; + found_and_removed = true ; + //if( collaborations[socket_id_to_session_id[socket.id]].participants.length==0 ) { + if( collaborations[socket_id_to_session_id[socket.id]]['participants'].length==0 ) { + if( verbose ) { console.log( "last participant in collaboration, committing to disk & removing from memory" ) ; } + // no one is left in this session, we commit it to disk & remove it from memory + write_file( session_directory + "/" + socket_id_to_session_id[socket.id], JSON.stringify(collaborations[socket_id_to_session_id[socket.id]]['cached_instructions']) ) ; + delete collaborations[socket_id_to_session_id[socket.id]] ; + } + } } if( !found_and_removed ) { console.log( "WARNING: could not tie socket_id to any collaboration" ) ; @@ -163,34 +172,34 @@ io.sockets.on('connection', function(socket) { function write_file( path, data ) { -try { -fs.writeFileSync( path, data ) ; -return true ; -} catch( e ) { -return false ; -} + try { + fs.writeFileSync( path, data ) ; + return true ; + } catch( e ) { + return false ; + } } function read_file( path ) { -try { -var data = fs.readFileSync( path ) ; -return data ; -} catch( e ) { -return false -} + try { + var data = fs.readFileSync( path ) ; + return data ; + } catch( e ) { + return false + } } function file_exists( path ) { -try { -stats = fs.lstatSync( path ) ; -if (stats.isFile()) { -return true ; -} -} catch( e ) { -return false ; -} -// we should not reach that point -return false ; + try { + stats = fs.lstatSync( path ) ; + if (stats.isFile()) { + return true ; + } + } catch( e ) { + return false ; + } + // we should not reach that point + return false ; } \ No newline at end of file