2017-04-12 18:54:22 +02:00
define ([
2017-04-18 12:14:32 +02:00
'jquery' ,
2017-04-28 12:01:47 +02:00
'/customize/application_config.js'
], function ( $ , AppConfig ) {
2017-04-12 18:54:22 +02:00
var module = {};
var ROOT = module . ROOT = "root" ;
var UNSORTED = module . UNSORTED = "unsorted" ;
var TRASH = module . TRASH = "trash" ;
var TEMPLATE = module . TEMPLATE = "template" ;
2017-05-04 16:16:09 +02:00
module . init = function ( files , config ) {
2017-04-13 14:06:40 +02:00
var exp = {};
2017-04-12 18:54:22 +02:00
var Cryptpad = config . Cryptpad ;
var Messages = Cryptpad . Messages ;
2017-04-13 14:06:40 +02:00
var FILES_DATA = module . FILES_DATA = exp . FILES_DATA = Cryptpad . storageKey ;
2017-06-08 17:52:00 +02:00
var OLD_FILES_DATA = module . OLD_FILES_DATA = exp . OLD_FILES_DATA = Cryptpad . oldStorageKey ;
2017-04-12 18:54:22 +02:00
var NEW_FOLDER_NAME = Messages . fm_newFolder ;
var NEW_FILE_NAME = Messages . fm_newFile ;
// Logging
var logging = function () {
console . log . apply ( console , arguments );
};
var log = config . log || logging ;
var logError = config . logError || logging ;
var debug = config . debug || logging ;
var error = exp . error = function () {
exp . fixFiles ();
console . error . apply ( console , arguments );
};
// TODO: workgroup
var workgroup = config . workgroup ;
/*
* UTILS
*/
2017-05-04 16:16:09 +02:00
exp . getStructure = function () {
2017-04-12 18:54:22 +02:00
var a = {};
a [ ROOT ] = {};
a [ TRASH ] = {};
2017-06-08 17:52:00 +02:00
a [ FILES_DATA ] = {};
2017-04-12 18:54:22 +02:00
a [ TEMPLATE ] = [];
return a ;
};
2017-04-13 14:06:40 +02:00
var getHrefArray = function () {
2017-04-26 18:46:40 +02:00
return [ TEMPLATE ];
2017-04-13 14:06:40 +02:00
};
2017-04-12 18:54:22 +02:00
var compareFiles = function ( fileA , fileB ) { return fileA === fileB ; };
2017-06-08 17:52:00 +02:00
var isFile = exp . isFile = function ( element , allowStr ) {
return typeof ( element ) === "number" ||
(( typeof ( files [ OLD_FILES_DATA ]) !== "undefined" || allowStr )
&& typeof ( element ) === "string" );
2017-04-12 18:54:22 +02:00
};
2017-05-04 16:16:09 +02:00
exp . isReadOnlyFile = function ( element ) {
2017-04-12 18:54:22 +02:00
if ( ! isFile ( element )) { return false ; }
2017-06-08 17:52:00 +02:00
var data = exp . getFileData ( element );
var parsed = Cryptpad . parsePadUrl ( data . href );
2017-04-12 18:54:22 +02:00
if ( ! parsed ) { return false ; }
2017-05-12 16:33:45 +02:00
var pHash = parsed . hashData ;
2017-06-08 17:52:00 +02:00
if ( ! pHash || pHash . type !== "pad" ) { return ; }
2017-04-12 18:54:22 +02:00
return pHash && pHash . mode === 'view' ;
};
var isFolder = exp . isFolder = function ( element ) {
2017-04-28 18:23:41 +02:00
return typeof ( element ) === "object" ;
2017-04-12 18:54:22 +02:00
};
2017-05-04 16:16:09 +02:00
exp . isFolderEmpty = function ( element ) {
2017-06-08 17:52:00 +02:00
if ( ! isFolder ( element )) { return false ; }
2017-04-12 18:54:22 +02:00
return Object . keys ( element ). length === 0 ;
};
2017-05-04 16:16:09 +02:00
exp . hasSubfolder = function ( element , trashRoot ) {
2017-06-08 17:52:00 +02:00
if ( ! isFolder ( element )) { return false ; }
2017-04-12 18:54:22 +02:00
var subfolder = 0 ;
2017-05-04 16:16:09 +02:00
var addSubfolder = function ( el ) {
2017-04-12 18:54:22 +02:00
subfolder += isFolder ( el . element ) ? 1 : 0 ;
};
for ( var f in element ) {
if ( trashRoot ) {
if ( $ . isArray ( element [ f ])) {
element [ f ]. forEach ( addSubfolder );
}
} else {
subfolder += isFolder ( element [ f ]) ? 1 : 0 ;
}
}
return subfolder ;
};
2017-05-04 16:16:09 +02:00
exp . hasFile = function ( element , trashRoot ) {
2017-06-08 17:52:00 +02:00
if ( ! isFolder ( element )) { return false ; }
2017-04-12 18:54:22 +02:00
var file = 0 ;
2017-05-04 16:16:09 +02:00
var addFile = function ( el ) {
2017-04-12 18:54:22 +02:00
file += isFile ( el . element ) ? 1 : 0 ;
};
for ( var f in element ) {
if ( trashRoot ) {
if ( $ . isArray ( element [ f ])) {
element [ f ]. forEach ( addFile );
}
} else {
file += isFile ( element [ f ]) ? 1 : 0 ;
}
}
return file ;
};
// Get data from AllFiles (Cryptpad_RECENTPADS)
var getFileData = exp . getFileData = function ( file ) {
if ( ! file ) { return ; }
2017-06-08 17:52:00 +02:00
return files [ FILES_DATA ][ file ] || {};
2017-04-12 18:54:22 +02:00
};
// Data from filesData
2017-06-08 17:52:00 +02:00
var getTitle = exp . getTitle = function ( file , type ) {
2017-04-12 18:54:22 +02:00
if ( workgroup ) { debug ( "No titles in workgroups" ); return ; }
2017-06-08 17:52:00 +02:00
var data = getFileData ( file );
if ( ! file || ! data || ! data . href ) {
error ( "getTitle called with a non-existing file id: " , file , data );
2017-04-12 18:54:22 +02:00
return ;
}
2017-06-08 17:52:00 +02:00
if ( type === 'title' ) { return data . title ; }
if ( type === 'name' ) { return data . filename ; }
return data . filename || data . title || NEW_FILE_NAME ;
2017-04-12 18:54:22 +02:00
};
2017-07-05 12:21:01 +02:00
exp . getAttribute = function ( href , attr , cb ) {
cb = cb || $ . noop ;
var id = exp . getIdFromHref ( href );
if ( ! id ) { return void cb ( null , undefined ); }
var data = getFileData ( id );
cb ( null , data [ attr ]);
};
exp . setAttribute = function ( href , attr , value , cb ) {
cb = cb || $ . noop ;
var id = exp . getIdFromHref ( href );
if ( ! id ) { return void cb ( "E_INVAL_HREF" ); }
if ( ! attr || ! attr . trim ()) { return void cb ( "E_INVAL_ATTR" ); }
var data = getFileData ( id );
data [ attr ] = value ;
};
2017-04-12 18:54:22 +02:00
// PATHS
var comparePath = exp . comparePath = function ( a , b ) {
if ( ! a || ! b || ! $ . isArray ( a ) || ! $ . isArray ( b )) { return false ; }
if ( a . length !== b . length ) { return false ; }
var result = true ;
var i = a . length - 1 ;
while ( result && i >= 0 ) {
result = a [ i ] === b [ i ];
i -- ;
}
return result ;
};
2017-04-13 14:06:40 +02:00
var isSubpath = exp . isSubpath = function ( path , parentPath ) {
2017-04-12 18:54:22 +02:00
var pathA = parentPath . slice ();
var pathB = path . slice ( 0 , pathA . length );
return comparePath ( pathA , pathB );
};
2017-04-13 14:06:40 +02:00
var isPathIn = exp . isPathIn = function ( path , categories ) {
if ( ! categories ) { return ; }
var idx = categories . indexOf ( 'hrefArray' );
if ( idx !== - 1 ) {
categories . splice ( idx , 1 );
categories = categories . concat ( getHrefArray ());
}
2017-04-12 18:54:22 +02:00
return categories . some ( function ( c ) {
return Array . isArray ( path ) && path [ 0 ] === c ;
});
};
var isInTrashRoot = exp . isInTrashRoot = function ( path ) {
return path [ 0 ] === TRASH && path . length === 4 ;
};
// FIND
var findElement = function ( root , pathInput ) {
if ( ! pathInput ) {
error ( "Invalid path:\n" , pathInput , "\nin root\n" , root );
return ;
}
if ( pathInput . length === 0 ) { return root ; }
var path = pathInput . slice ();
var key = path . shift ();
if ( typeof root [ key ] === "undefined" ) {
debug ( "Unable to find the key '" + key + "' in the root object provided:" , root );
return ;
}
return findElement ( root [ key ], path );
};
var find = exp . find = function ( path ) {
return findElement ( files , path );
};
// GET FILES
var getFilesRecursively = function ( root , arr ) {
for ( var e in root ) {
if ( isFile ( root [ e ])) {
if ( arr . indexOf ( root [ e ]) === - 1 ) { arr . push ( root [ e ]); }
} else {
getFilesRecursively ( root [ e ], arr );
}
}
};
var _getFiles = {};
_getFiles [ 'array' ] = function ( cat ) {
2017-04-13 14:06:40 +02:00
if ( ! files [ cat ]) { files [ cat ] = []; }
2017-04-12 18:54:22 +02:00
return files [ cat ]. slice ();
};
2017-04-13 14:06:40 +02:00
getHrefArray (). forEach ( function ( c ) {
_getFiles [ c ] = function () { return _getFiles [ 'array' ]( c ); };
});
2017-04-12 18:54:22 +02:00
_getFiles [ 'hrefArray' ] = function () {
var ret = [];
2017-04-13 14:06:40 +02:00
getHrefArray (). forEach ( function ( c ) {
ret = ret . concat ( _getFiles [ c ]());
});
2017-04-12 18:54:22 +02:00
return Cryptpad . deduplicateString ( ret );
};
_getFiles [ ROOT ] = function () {
var ret = [];
getFilesRecursively ( files [ ROOT ], ret );
return ret ;
};
_getFiles [ TRASH ] = function () {
var root = files [ TRASH ];
var ret = [];
2017-05-04 16:16:09 +02:00
var addFiles = function ( el ) {
2017-04-12 18:54:22 +02:00
if ( isFile ( el . element )) {
if ( ret . indexOf ( el . element ) === - 1 ) { ret . push ( el . element ); }
} else {
getFilesRecursively ( el . element , ret );
}
};
for ( var e in root ) {
if ( ! $ . isArray ( root [ e ])) {
error ( "Trash contains a non-array element" );
return ;
}
root [ e ]. forEach ( addFiles );
}
return ret ;
};
2017-06-08 17:52:00 +02:00
_getFiles [ OLD_FILES_DATA ] = function () {
2017-04-12 18:54:22 +02:00
var ret = [];
2017-06-08 17:52:00 +02:00
if ( ! files [ OLD_FILES_DATA ]) { return ret ; }
files [ OLD_FILES_DATA ]. forEach ( function ( el ) {
2017-04-12 18:54:22 +02:00
if ( el . href && ret . indexOf ( el . href ) === - 1 ) {
ret . push ( el . href );
}
});
return ret ;
};
2017-06-08 17:52:00 +02:00
_getFiles [ FILES_DATA ] = function () {
2017-05-24 18:59:44 +02:00
var ret = [];
2017-06-08 17:52:00 +02:00
if ( ! files [ FILES_DATA ]) { return ret ; }
return Object . keys ( files [ FILES_DATA ]). map ( Number );
2017-05-24 18:59:44 +02:00
};
2017-04-13 14:06:40 +02:00
var getFiles = exp . getFiles = function ( categories ) {
2017-04-12 18:54:22 +02:00
var ret = [];
2017-04-13 14:06:40 +02:00
if ( ! categories || ! categories . length ) {
2017-06-08 17:52:00 +02:00
categories = [ ROOT , 'hrefArray' , TRASH , OLD_FILES_DATA , FILES_DATA ];
2017-04-13 14:06:40 +02:00
}
2017-04-12 18:54:22 +02:00
categories . forEach ( function ( c ) {
if ( typeof _getFiles [ c ] === "function" ) {
2017-04-13 14:06:40 +02:00
ret = ret . concat ( _getFiles [ c ]());
2017-04-12 18:54:22 +02:00
}
});
return Cryptpad . deduplicateString ( ret );
};
2017-06-09 16:39:44 +02:00
var getIdFromHref = exp . getIdFromHref = function ( href ) {
var result ;
getFiles ([ FILES_DATA ]). some ( function ( id ) {
if ( files [ FILES_DATA ][ id ]. href === href ) {
result = id ;
return true ;
}
return ;
});
return result ;
};
2017-04-12 18:54:22 +02:00
// SEARCH
2017-06-08 17:52:00 +02:00
var _findFileInRoot = function ( path , file ) {
2017-04-13 14:06:40 +02:00
if ( ! isPathIn ( path , [ ROOT , TRASH ])) { return []; }
2017-04-12 18:54:22 +02:00
var paths = [];
var root = find ( path );
var addPaths = function ( p ) {
if ( paths . indexOf ( p ) === - 1 ) {
paths . push ( p );
}
};
if ( isFile ( root )) {
2017-06-08 17:52:00 +02:00
if ( compareFiles ( file , root )) {
2017-04-12 18:54:22 +02:00
if ( paths . indexOf ( path ) === - 1 ) {
paths . push ( path );
}
}
return paths ;
}
for ( var e in root ) {
var nPath = path . slice ();
nPath . push ( e );
2017-06-08 17:52:00 +02:00
_findFileInRoot ( nPath , file ). forEach ( addPaths );
2017-04-12 18:54:22 +02:00
}
return paths ;
};
2017-06-08 17:52:00 +02:00
exp . findFileInRoot = function ( file ) {
return _findFileInRoot ([ ROOT ], file );
2017-04-26 18:46:40 +02:00
};
2017-06-08 17:52:00 +02:00
var _findFileInHrefArray = function ( rootName , file ) {
2017-05-24 18:59:44 +02:00
if ( ! files [ rootName ]) { return []; }
2017-04-12 18:54:22 +02:00
var unsorted = files [ rootName ]. slice ();
var ret = [];
var i = - 1 ;
2017-06-08 17:52:00 +02:00
while (( i = unsorted . indexOf ( file , i + 1 )) !== - 1 ){
2017-04-12 18:54:22 +02:00
ret . push ([ rootName , i ]);
}
return ret ;
};
2017-06-08 17:52:00 +02:00
var _findFileInTrash = function ( path , file ) {
2017-04-12 18:54:22 +02:00
var root = find ( path );
var paths = [];
var addPaths = function ( p ) {
if ( paths . indexOf ( p ) === - 1 ) {
paths . push ( p );
}
};
if ( path . length === 1 && typeof ( root ) === 'object' ) {
Object . keys ( root ). forEach ( function ( key ) {
var arr = root [ key ];
if ( ! Array . isArray ( arr )) { return ; }
var nPath = path . slice ();
nPath . push ( key );
2017-06-08 17:52:00 +02:00
_findFileInTrash ( nPath , file ). forEach ( addPaths );
2017-04-12 18:54:22 +02:00
});
}
if ( path . length === 2 ) {
if ( ! Array . isArray ( root )) { return []; }
root . forEach ( function ( el , i ) {
var nPath = path . slice ();
nPath . push ( i );
nPath . push ( 'element' );
if ( isFile ( el . element )) {
2017-06-08 17:52:00 +02:00
if ( compareFiles ( file , el . element )) {
2017-04-12 18:54:22 +02:00
addPaths ( nPath );
}
return ;
}
2017-06-08 17:52:00 +02:00
_findFileInTrash ( nPath , file ). forEach ( addPaths );
2017-04-12 18:54:22 +02:00
});
}
if ( path . length >= 4 ) {
2017-06-08 17:52:00 +02:00
_findFileInRoot ( path , file ). forEach ( addPaths );
2017-04-12 18:54:22 +02:00
}
return paths ;
};
2017-06-08 17:52:00 +02:00
var findFile = exp . findFile = function ( file ) {
var rootpaths = _findFileInRoot ([ ROOT ], file );
var templatepaths = _findFileInHrefArray ( TEMPLATE , file );
var trashpaths = _findFileInTrash ([ TRASH ], file );
2017-04-26 18:46:40 +02:00
return rootpaths . concat ( templatepaths , trashpaths );
2017-04-12 18:54:22 +02:00
};
2017-05-04 16:16:09 +02:00
exp . search = function ( value ) {
2017-04-12 18:54:22 +02:00
if ( typeof ( value ) !== "string" ) { return []; }
var res = [];
// Search title
2017-06-08 17:52:00 +02:00
var allFilesList = files [ FILES_DATA ];
var lValue = value . toLowerCase ();
getFiles ([ FILES_DATA ]). forEach ( function ( id ) {
var data = allFilesList [ id ];
if (( data . title && data . title . toLowerCase (). indexOf ( lValue ) !== - 1 ) ||
( data . filename && data . filename . toLowerCase (). indexOf ( lValue ) !== - 1 )) {
res . push ( id );
2017-04-12 18:54:22 +02:00
}
});
// Search Href
var href = Cryptpad . getRelativeHref ( value );
if ( href ) {
2017-06-08 17:52:00 +02:00
var id = getIdFromHref ( href );
if ( id ) { res . push ( id ); }
2017-04-12 18:54:22 +02:00
}
res = Cryptpad . deduplicateString ( res );
var ret = [];
res . forEach ( function ( l ) {
2017-05-04 16:16:09 +02:00
//var paths = findFile(l);
2017-04-12 18:54:22 +02:00
ret . push ({
paths : findFile ( l ),
data : exp . getFileData ( l )
});
});
return ret ;
};
/**
* OPERATIONS
*/
var getAvailableName = function ( parentEl , name ) {
if ( typeof ( parentEl [ name ]) === "undefined" ) { return name ; }
var newName = name ;
var i = 1 ;
while ( typeof ( parentEl [ newName ]) !== "undefined" ) {
newName = name + "_" + i ;
i ++ ;
}
return newName ;
};
// FILES DATA
2017-06-09 16:39:44 +02:00
exp . pushData = function ( data , cb ) {
2017-04-28 12:01:47 +02:00
if ( typeof cb !== "function" ) { cb = function () {}; }
var todo = function () {
2017-06-08 17:52:00 +02:00
var id = Cryptpad . createRandomInteger ();
files [ FILES_DATA ][ id ] = data ;
cb ( null , id );
2017-04-28 12:01:47 +02:00
};
2017-06-08 17:52:00 +02:00
if ( ! Cryptpad . isLoggedIn () || ! AppConfig . enablePinning || config . testMode ) {
return void todo ();
}
2017-05-04 16:16:09 +02:00
Cryptpad . pinPads ([ Cryptpad . hrefToHexChannelId ( data . href )], function ( e ) {
2017-04-28 12:01:47 +02:00
if ( e ) { return void cb ( e ); }
todo ();
2017-04-12 18:54:22 +02:00
});
};
2017-06-08 17:52:00 +02:00
var spliceFileData = exp . removeData = function ( id ) {
files [ FILES_DATA ][ id ] = undefined ;
delete files [ FILES_DATA ][ id ];
2017-04-12 18:54:22 +02:00
};
// MOVE
var pushToTrash = function ( name , element , path ) {
var trash = files [ TRASH ];
if ( typeof ( trash [ name ]) === "undefined" ) { trash [ name ] = []; }
var trashArray = trash [ name ];
var trashElement = {
element : element ,
path : path
};
trashArray . push ( trashElement );
};
var copyElement = function ( elementPath , newParentPath ) {
if ( comparePath ( elementPath , newParentPath )) { return ; } // Nothing to do...
var element = find ( elementPath );
var newParent = find ( newParentPath );
// Move to Trash
if ( isPathIn ( newParentPath , [ TRASH ])) {
if ( ! elementPath || elementPath . length < 2 || elementPath [ 0 ] === TRASH ) {
2017-04-13 15:04:17 +02:00
debug ( "Can't move an element from the trash to the trash: " , elementPath );
2017-04-12 18:54:22 +02:00
return ;
}
var key = elementPath [ elementPath . length - 1 ];
2017-04-13 15:04:17 +02:00
var elName = isPathIn ( elementPath , [ 'hrefArray' ]) ? getTitle ( element ) : key ;
2017-04-12 18:54:22 +02:00
var parentPath = elementPath . slice ();
parentPath . pop ();
2017-04-13 15:04:17 +02:00
pushToTrash ( elName , element , parentPath );
2017-04-12 18:54:22 +02:00
return true ;
}
// Move to hrefArray
if ( isPathIn ( newParentPath , [ 'hrefArray' ])) {
if ( isFolder ( element )) {
log ( Messages . fo_moveUnsortedError );
return ;
} else {
if ( elementPath [ 0 ] === newParentPath [ 0 ]) { return ; }
var fileRoot = newParentPath [ 0 ];
if ( files [ fileRoot ]. indexOf ( element ) === - 1 ) {
files [ fileRoot ]. push ( element );
}
return true ;
}
}
// Move to root
2017-06-08 17:52:00 +02:00
var newName = isFile ( element ) ?
getAvailableName ( newParent , Cryptpad . createChannelId ()) :
isInTrashRoot ( elementPath ) ?
elementPath [ 1 ] : elementPath . pop ();
2017-04-12 18:54:22 +02:00
if ( typeof ( newParent [ newName ]) !== "undefined" ) {
log ( Messages . fo_unavailableName );
return ;
}
newParent [ newName ] = element ;
return true ;
};
var move = exp . move = function ( paths , newPath , cb ) {
// Copy the elements to their new location
var toRemove = [];
paths . forEach ( function ( p ) {
var parentPath = p . slice ();
parentPath . pop ();
2017-04-24 15:38:03 +02:00
if ( comparePath ( parentPath , newPath )) { return ; }
if ( isSubpath ( newPath , p )) {
log ( Messages . fo_moveFolderToChildError );
return ;
}
// Try to copy, and if success, remove the element from the old location
2017-06-08 17:52:00 +02:00
if ( copyElement ( p . slice (), newPath )) {
2017-04-24 15:38:03 +02:00
toRemove . push ( p );
}
2017-04-12 18:54:22 +02:00
});
exp . delete ( toRemove , cb );
};
2017-05-04 16:16:09 +02:00
exp . restore = function ( path , cb ) {
2017-04-13 14:06:40 +02:00
if ( ! isInTrashRoot ( path )) { return ; }
var parentPath = path . slice ();
parentPath . pop ();
var oldPath = find ( parentPath ). path ;
move ([ path ], oldPath , cb );
};
2017-04-12 18:54:22 +02:00
// ADD
2017-06-08 17:52:00 +02:00
var add = exp . add = function ( id , path ) {
2017-06-12 17:49:33 +02:00
if ( ! Cryptpad . isLoggedIn () && ! config . testMode ) { return ; }
2017-06-08 17:52:00 +02:00
var data = files [ FILES_DATA ][ id ];
2017-04-26 18:46:40 +02:00
if ( ! data || typeof ( data ) !== "object" ) { return ; }
2017-04-12 18:54:22 +02:00
var newPath = path , parentEl ;
if ( path && ! Array . isArray ( path )) {
newPath = decodeURIComponent ( path ). split ( ',' );
}
// Add to href array
if ( path && isPathIn ( newPath , [ 'hrefArray' ])) {
parentEl = find ( newPath );
2017-06-08 17:52:00 +02:00
parentEl . push ( id );
2017-04-12 18:54:22 +02:00
return ;
}
2017-04-26 18:46:40 +02:00
// Add to root if path is ROOT or if no path
var filesList = getFiles ([ ROOT , TRASH , 'hrefArray' ]);
2017-06-09 16:39:44 +02:00
if ( path && isPathIn ( newPath , [ ROOT ]) || filesList . indexOf ( id ) === - 1 ) {
2017-04-26 18:46:40 +02:00
parentEl = find ( newPath || [ ROOT ]);
2017-04-12 18:54:22 +02:00
if ( parentEl ) {
2017-06-08 17:52:00 +02:00
var newName = getAvailableName ( parentEl , Cryptpad . createChannelId ());
parentEl [ newName ] = id ;
2017-04-12 18:54:22 +02:00
return ;
}
}
};
2017-05-04 16:16:09 +02:00
exp . addFolder = function ( folderPath , name , cb ) {
2017-04-12 18:54:22 +02:00
var parentEl = find ( folderPath );
var folderName = getAvailableName ( parentEl , name || NEW_FOLDER_NAME );
parentEl [ folderName ] = {};
var newPath = folderPath . slice ();
newPath . push ( folderName );
2017-05-02 17:14:53 +02:00
cb ( void 0 , {
2017-04-12 18:54:22 +02:00
newPath : newPath
});
};
// FORGET (move with href not path)
2017-05-04 16:16:09 +02:00
exp . forget = function ( href ) {
2017-06-08 17:52:00 +02:00
var id = getIdFromHref ( href );
if ( ! id ) { return ; }
2017-06-12 17:49:33 +02:00
if ( ! Cryptpad . isLoggedIn () && ! config . testMode ) {
2017-05-12 18:06:29 +02:00
// delete permanently
2017-06-08 17:52:00 +02:00
exp . removePadAttribute ( href );
spliceFileData ( id );
2017-05-12 18:06:29 +02:00
return ;
}
2017-06-08 17:52:00 +02:00
var paths = findFile ( id );
2017-04-12 18:54:22 +02:00
move ( paths , [ TRASH ]);
};
// DELETE
// Permanently delete multiple files at once using a list of paths
// NOTE: We have to be careful when removing elements from arrays (trash root, unsorted or template)
2017-05-12 18:06:29 +02:00
var removePadAttribute = exp . removePadAttribute = function ( f ) {
2017-04-28 16:09:46 +02:00
if ( typeof ( f ) !== 'string' ) {
console . error ( "Can't find pad attribute for an undefined pad" );
return ;
}
2017-04-12 18:54:22 +02:00
Object . keys ( files ). forEach ( function ( key ) {
var hash = f . indexOf ( '#' ) !== - 1 ? f . slice ( f . indexOf ( '#' ) + 1 ) : null ;
if ( hash && key . indexOf ( hash ) === 0 ) {
debug ( "Deleting pad attribute in the realtime object" );
files [ key ] = undefined ;
delete files [ key ];
}
});
};
var checkDeletedFiles = function () {
2017-06-08 17:52:00 +02:00
// Nothing in OLD_FILES_DATA for workgroups
2017-06-12 17:49:33 +02:00
if ( workgroup || ( ! Cryptpad . isLoggedIn () && ! config . testMode )) { return ; }
2017-04-12 18:54:22 +02:00
2017-04-13 14:06:40 +02:00
var filesList = getFiles ([ ROOT , 'hrefArray' , TRASH ]);
2017-06-08 17:52:00 +02:00
var fData = files [ FILES_DATA ];
getFiles ([ FILES_DATA ]). forEach ( function ( id ) {
if ( filesList . indexOf ( id ) === - 1 ) {
removePadAttribute ( fData [ id ]. href );
spliceFileData ( id );
2017-04-12 18:54:22 +02:00
}
});
};
2017-06-08 17:52:00 +02:00
var deleteHrefs = function ( ids ) {
ids . forEach ( function ( obj ) {
var idx = files [ obj . root ]. indexOf ( obj . id );
2017-04-12 18:54:22 +02:00
files [ obj . root ]. splice ( idx , 1 );
});
};
var deleteMultipleTrashRoot = function ( roots ) {
roots . forEach ( function ( obj ) {
var idx = files [ TRASH ][ obj . name ]. indexOf ( obj . el );
files [ TRASH ][ obj . name ]. splice ( idx , 1 );
});
};
2017-04-13 15:04:17 +02:00
var deleteMultiplePermanently = function ( paths , nocheck ) {
2017-04-13 14:06:40 +02:00
var hrefPaths = paths . filter ( function ( x ) { return isPathIn ( x , [ 'hrefArray' ]); });
var rootPaths = paths . filter ( function ( x ) { return isPathIn ( x , [ ROOT ]); });
var trashPaths = paths . filter ( function ( x ) { return isPathIn ( x , [ TRASH ]); });
2017-05-12 18:06:29 +02:00
var allFilesPaths = paths . filter ( function ( x ) { return isPathIn ( x , [ FILES_DATA ]); });
2017-06-12 17:49:33 +02:00
if ( ! Cryptpad . isLoggedIn () && ! config . testMode ) {
2017-05-12 18:06:29 +02:00
allFilesPaths . forEach ( function ( path ) {
var el = find ( path );
2017-06-08 17:52:00 +02:00
if ( ! el ) { return ; }
var id = getIdFromHref ( el . href );
if ( ! id ) { return ; }
spliceFileData ( id );
2017-05-12 18:06:29 +02:00
removePadAttribute ( el . href );
});
return ;
}
2017-04-12 18:54:22 +02:00
2017-06-08 17:52:00 +02:00
var ids = [];
2017-04-12 18:54:22 +02:00
hrefPaths . forEach ( function ( path ) {
2017-06-08 17:52:00 +02:00
var id = find ( path );
ids . push ({
2017-04-12 18:54:22 +02:00
root : path [ 0 ],
2017-06-08 17:52:00 +02:00
id : id
2017-04-12 18:54:22 +02:00
});
});
2017-06-08 17:52:00 +02:00
deleteHrefs ( ids );
2017-04-12 18:54:22 +02:00
rootPaths . forEach ( function ( path ) {
var parentPath = path . slice ();
var key = parentPath . pop ();
var parentEl = find ( parentPath );
parentEl [ key ] = undefined ;
delete parentEl [ key ];
});
var trashRoot = [];
trashPaths . forEach ( function ( path ) {
var parentPath = path . slice ();
var key = parentPath . pop ();
var parentEl = find ( parentPath );
// Trash root: we have array here, we can't just splice with the path otherwise we might break the path
// of another element in the loop
if ( path . length === 4 ) {
trashRoot . push ({
name : path [ 1 ],
el : parentEl
});
return ;
}
// Trash but not root: it's just a tree so remove the key
parentEl [ key ] = undefined ;
delete parentEl [ key ];
});
deleteMultipleTrashRoot ( trashRoot );
2017-04-13 15:04:17 +02:00
// In some cases, we want to remove pads from a location without removing them from
2017-06-08 17:52:00 +02:00
// OLD_FILES_DATA (replaceHref)
2017-04-13 15:04:17 +02:00
if ( ! nocheck ) { checkDeletedFiles (); }
2017-04-12 18:54:22 +02:00
};
2017-05-04 16:16:09 +02:00
exp . delete = function ( paths , cb , nocheck ) {
2017-04-13 15:04:17 +02:00
deleteMultiplePermanently ( paths , nocheck );
2017-04-12 18:54:22 +02:00
if ( typeof cb === "function" ) { cb (); }
};
2017-05-04 16:16:09 +02:00
exp . emptyTrash = function ( cb ) {
2017-04-12 18:54:22 +02:00
files [ TRASH ] = {};
checkDeletedFiles ();
if ( cb ) { cb (); }
};
// RENAME
2017-05-04 16:16:09 +02:00
exp . rename = function ( path , newName , cb ) {
2017-04-12 18:54:22 +02:00
if ( path . length <= 1 ) {
logError ( 'Renaming `root` is forbidden' );
return ;
}
// Copy the element path and remove the last value to have the parent path and the old name
var element = find ( path );
2017-06-08 17:52:00 +02:00
// Folders
if ( isFolder ( element )) {
var parentPath = path . slice ();
var oldName = parentPath . pop ();
if ( ! newName || ! newName . trim () || oldName === newName ) { return ; }
var parentEl = find ( parentPath );
if ( typeof ( parentEl [ newName ]) !== "undefined" ) {
log ( Messages . fo_existingNameError );
return ;
}
parentEl [ newName ] = element ;
parentEl [ oldName ] = undefined ;
delete parentEl [ oldName ];
if ( typeof cb === "function" ) { cb (); }
2017-04-12 18:54:22 +02:00
return ;
}
2017-06-08 17:52:00 +02:00
// Files
var data = files [ FILES_DATA ][ element ];
if ( ! data ) { return ; }
if ( ! newName || newName . trim () === "" ) {
data . filename = undefined ;
delete data . filename ;
if ( typeof cb === "function" ) { cb (); }
2017-04-12 18:54:22 +02:00
return ;
}
2017-06-09 16:39:44 +02:00
if ( getTitle ( element , 'name' ) === newName ) { return ; }
2017-06-08 17:52:00 +02:00
data . filename = newName ;
2017-04-28 17:11:50 +02:00
if ( typeof cb === "function" ) { cb (); }
2017-04-12 18:54:22 +02:00
};
2017-04-13 14:06:40 +02:00
// REPLACE
2017-05-04 16:16:09 +02:00
exp . replace = function ( o , n ) {
2017-06-08 17:52:00 +02:00
var idO = getIdFromHref ( o );
if ( ! idO || ! isFile ( idO )) { return ; }
var data = getFileData ( idO );
if ( ! data ) { return ; }
data . href = n ;
};
// If all the occurences of an href are in the trash, remvoe them and add the file in root.
// This is use with setPadTitle when we open a stronger version of a deleted pad
exp . restoreHref = function ( href ) {
var idO = getIdFromHref ( href );
if ( ! idO || ! isFile ( idO )) { return ; }
var paths = findFile ( idO );
2017-04-13 14:06:40 +02:00
// Remove all the occurences in the trash
2017-06-08 17:52:00 +02:00
// If all the occurences are in the trash or no occurence, add the pad to root
2017-04-13 14:06:40 +02:00
var allInTrash = true ;
paths . forEach ( function ( p ) {
if ( p [ 0 ] === TRASH ) {
2017-04-13 15:04:17 +02:00
exp . delete ( p , null , true ); // 3rd parameter means skip "checkDeletedFiles"
2017-04-13 14:06:40 +02:00
return ;
}
2017-06-08 17:52:00 +02:00
allInTrash = false ;
2017-04-13 14:06:40 +02:00
});
if ( allInTrash ) {
2017-06-08 17:52:00 +02:00
add ( idO );
2017-04-13 14:06:40 +02:00
}
};
2017-04-12 18:54:22 +02:00
/**
* INTEGRITY CHECK
*/
2017-06-09 12:29:19 +02:00
exp . migrate = function ( cb ) {
// Make sure unsorted doesn't exist anymore
// Note: Unsorted only works with the old structure where pads are href
// It should be called before the migration code
var fixUnsorted = function () {
if ( ! files [ UNSORTED ] || ! files [ OLD_FILES_DATA ]) { return ; }
debug ( "UNSORTED still exists in the object, removing it..." );
var us = files [ UNSORTED ];
if ( us . length === 0 ) {
delete files [ UNSORTED ];
return ;
}
us . forEach ( function ( el ) {
if ( typeof el !== "string" ) {
return ;
}
var data = files [ OLD_FILES_DATA ]. filter ( function ( x ) {
return x . href === el ;
});
if ( data . length === 0 ) {
files [ OLD_FILES_DATA ]. push ({
href : el
});
}
return ;
});
delete files [ UNSORTED ];
};
// mergeDrive...
var migrateToNewFormat = function ( todo ) {
if ( ! files [ OLD_FILES_DATA ]) {
return void todo ();
}
try {
debug ( "Migrating file system..." );
files . migrate = 1 ;
2017-06-12 16:19:45 +02:00
var next = function () {
2017-06-09 12:29:19 +02:00
var oldData = files [ OLD_FILES_DATA ]. slice ();
if ( ! files [ FILES_DATA ]) {
files [ FILES_DATA ] = {};
}
var newData = files [ FILES_DATA ];
//var oldFiles = oldData.map(function (o) { return o.href; });
oldData . forEach ( function ( obj ) {
if ( ! obj || ! obj . href ) { return ; }
var href = obj . href ;
var id = Cryptpad . createRandomInteger ();
var paths = findFile ( href );
var data = obj ;
var key = Cryptpad . createChannelId ();
if ( data ) {
newData [ id ] = data ;
} else {
newData [ id ] = { href : href };
}
paths . forEach ( function ( p ) {
var parentPath = p . slice ();
var okey = parentPath . pop (); // get the parent
var parent = find ( parentPath );
if ( isInTrashRoot ( p )) {
parent . element = id ;
newData [ id ]. filename = p [ 1 ];
return ;
}
if ( isPathIn ( p , [ 'hrefArray' ])) {
parent [ okey ] = id ;
return ;
}
// else root or trash (not trashroot)
parent [ key ] = id ;
newData [ id ]. filename = okey ;
delete parent [ okey ];
});
});
files [ OLD_FILES_DATA ] = undefined ;
delete files [ OLD_FILES_DATA ];
files . migrate = undefined ;
delete files . migrate ;
console . log ( 'done' );
todo ();
2017-06-12 16:19:45 +02:00
};
if ( exp . rt ) {
exp . rt . sync ();
Cryptpad . whenRealtimeSyncs ( exp . rt , next );
} else {
window . setTimeout ( next , 1000 );
}
2017-06-09 12:29:19 +02:00
} catch ( e ) {
console . error ( e );
todo ();
}
};
fixUnsorted ();
migrateToNewFormat ( cb );
};
2017-05-04 16:16:09 +02:00
exp . fixFiles = function () {
2017-04-12 18:54:22 +02:00
// Explore the tree and check that everything is correct:
// * 'root', 'trash', 'unsorted' and 'filesData' exist and are objects
// * ROOT: Folders are objects, files are href
// * TRASH: Trash root contains only arrays, each element of the array is an object {element:.., path:..}
2017-06-08 17:52:00 +02:00
// * OLD_FILES_DATA: - Data (title, cdate, adte) are stored in filesData. filesData contains only href keys linking to object with title, cdate, adate.
2017-04-12 18:54:22 +02:00
// - Dates (adate, cdate) can be parsed/formatted
// - All files in filesData should be either in 'root', 'trash' or 'unsorted'. If that's not the case, copy the fily to 'unsorted'
2017-04-26 18:46:40 +02:00
// * TEMPLATE: Contains only files (href), and does not contains files that are in ROOT
2017-04-12 18:54:22 +02:00
debug ( "Cleaning file system..." );
var before = JSON . stringify ( files );
var fixRoot = function ( elem ) {
if ( typeof ( files [ ROOT ]) !== "object" ) { debug ( "ROOT was not an object" ); files [ ROOT ] = {}; }
var element = elem || files [ ROOT ];
for ( var el in element ) {
2017-06-08 17:52:00 +02:00
if ( ! isFile ( element [ el ], true ) && ! isFolder ( element [ el ])) {
2017-04-12 18:54:22 +02:00
debug ( "An element in ROOT was not a folder nor a file. " , element [ el ]);
element [ el ] = undefined ;
delete element [ el ];
2017-05-24 18:59:44 +02:00
continue ;
}
if ( isFolder ( element [ el ])) {
2017-04-12 18:54:22 +02:00
fixRoot ( element [ el ]);
2017-05-24 18:59:44 +02:00
continue ;
}
if ( typeof element [ el ] === "string" ) {
// We have an old file (href) which is not in filesData: add it
var id = Cryptpad . createRandomInteger ();
var key = Cryptpad . createChannelId ();
2017-06-08 17:52:00 +02:00
files [ FILES_DATA ][ id ] = { href : element [ el ], filename : el };
2017-05-24 18:59:44 +02:00
element [ key ] = id ;
delete element [ el ];
2017-04-12 18:54:22 +02:00
}
2017-06-20 12:42:30 +02:00
if ( typeof element [ el ] === "number" ) {
var data = files [ FILES_DATA ][ element [ el ]];
if ( ! data ) {
debug ( "An element in ROOT doesn't have associated data" , element [ el ], el );
delete element [ el ];
}
}
2017-04-12 18:54:22 +02:00
}
};
var fixTrashRoot = function () {
if ( typeof ( files [ TRASH ]) !== "object" ) { debug ( "TRASH was not an object" ); files [ TRASH ] = {}; }
var tr = files [ TRASH ];
var toClean ;
2017-05-24 18:59:44 +02:00
var addToClean = function ( obj , idx , el ) {
2017-04-12 18:54:22 +02:00
if ( typeof ( obj ) !== "object" ) { toClean . push ( idx ); return ; }
2017-06-08 17:52:00 +02:00
if ( ! isFile ( obj . element , true ) && ! isFolder ( obj . element )) { toClean . push ( idx ); return ; }
2017-04-12 18:54:22 +02:00
if ( ! $ . isArray ( obj . path )) { toClean . push ( idx ); return ; }
2017-05-24 18:59:44 +02:00
if ( typeof obj . element === "string" ) {
// We have an old file (href) which is not in filesData: add it
var id = Cryptpad . createRandomInteger ();
2017-06-08 17:52:00 +02:00
files [ FILES_DATA ][ id ] = { href : obj . element , filename : el };
2017-05-24 18:59:44 +02:00
obj . element = id ;
}
if ( isFolder ( obj . element )) { fixRoot ( obj . element ); }
2017-06-20 12:42:30 +02:00
if ( typeof obj . element === "number" ) {
var data = files [ FILES_DATA ][ obj . element ];
if ( ! data ) {
debug ( "An element in TRASH doesn't have associated data" , obj . element , el );
2017-06-20 12:59:44 +02:00
toClean . push ( idx );
2017-06-20 12:42:30 +02:00
}
}
2017-04-12 18:54:22 +02:00
};
for ( var el in tr ) {
2017-06-08 17:52:00 +02:00
if ( ! Array . isArray ( tr [ el ])) {
2017-04-12 18:54:22 +02:00
debug ( "An element in TRASH root is not an array. " , tr [ el ]);
tr [ el ] = undefined ;
delete tr [ el ];
2017-06-08 17:52:00 +02:00
} else if ( tr [ el ]. length === 0 ) {
debug ( "Empty array in TRASH root. " , tr [ el ]);
tr [ el ] = undefined ;
delete tr [ el ];
2017-04-12 18:54:22 +02:00
} else {
toClean = [];
2017-06-09 16:39:44 +02:00
for ( var j = 0 ; j < tr [ el ]. length ; j ++ ) {
addToClean ( tr [ el ][ j ], j , el );
}
2017-04-12 18:54:22 +02:00
for ( var i = toClean . length - 1 ; i >= 0 ; i -- ) {
tr [ el ]. splice ( toClean [ i ], 1 );
}
}
}
};
var fixTemplate = function () {
if ( ! Array . isArray ( files [ TEMPLATE ])) { debug ( "TEMPLATE was not an array" ); files [ TEMPLATE ] = []; }
files [ TEMPLATE ] = Cryptpad . deduplicateString ( files [ TEMPLATE ]. slice ());
var us = files [ TEMPLATE ];
2017-04-26 18:46:40 +02:00
var rootFiles = getFiles ([ ROOT ]). slice ();
2017-04-12 18:54:22 +02:00
var toClean = [];
us . forEach ( function ( el , idx ) {
2017-06-08 17:52:00 +02:00
if ( ! isFile ( el , true ) || rootFiles . indexOf ( el ) !== - 1 ) {
2017-04-12 18:54:22 +02:00
toClean . push ( idx );
}
2017-05-24 18:59:44 +02:00
if ( typeof el === "string" ) {
// We have an old file (href) which is not in filesData: add it
var id = Cryptpad . createRandomInteger ();
2017-06-08 17:52:00 +02:00
files [ FILES_DATA ][ id ] = { href : el };
2017-05-24 18:59:44 +02:00
us [ idx ] = id ;
}
2017-06-20 12:42:30 +02:00
if ( typeof el === "number" ) {
var data = files [ FILES_DATA ][ el ];
if ( ! data ) {
debug ( "An element in TEMPLATE doesn't have associated data" , el );
2017-06-20 12:59:44 +02:00
toClean . push ( idx );
2017-06-20 12:42:30 +02:00
}
}
2017-04-12 18:54:22 +02:00
});
toClean . forEach ( function ( idx ) {
us . splice ( idx , 1 );
});
};
2017-07-05 12:21:01 +02:00
var migrateAttributes = function ( el , id , parsed ) {
// Migrate old pad attributes
[ 'userid' , 'previewMode' ]. forEach ( function ( attr ) {
var key = parsed . hash + '.' + attr ;
var key2 = parsed . hash . slice ( 0 , - 1 ) + '.' + attr ; // old pads not ending with /
if ( files [ key ] || files [ key2 ]) {
debug ( "Migrating pad attribute" , attr , "for pad" , id );
el [ attr ] = files [ key ] || files [ key2 ];
}
delete files [ key ];
delete files [ key2 ];
});
// Migration done
};
2017-04-12 18:54:22 +02:00
var fixFilesData = function () {
2017-06-08 17:52:00 +02:00
if ( typeof files [ FILES_DATA ] !== "object" ) { debug ( "OLD_FILES_DATA was not an object" ); files [ FILES_DATA ] = {}; }
var fd = files [ FILES_DATA ];
2017-04-12 18:54:22 +02:00
var rootFiles = getFiles ([ ROOT , TRASH , 'hrefArray' ]);
2017-04-26 18:46:40 +02:00
var root = find ([ ROOT ]);
2017-04-12 18:54:22 +02:00
var toClean = [];
2017-05-24 18:59:44 +02:00
for ( var id in fd ) {
id = Number ( id );
var el = fd [ id ];
2017-04-12 18:54:22 +02:00
if ( ! el || typeof ( el ) !== "object" ) {
debug ( "An element in filesData was not an object." , el );
2017-06-08 17:52:00 +02:00
toClean . push ( id );
2017-05-24 18:59:44 +02:00
continue ;
2017-04-12 18:54:22 +02:00
}
2017-05-05 11:55:19 +02:00
if ( ! el . href ) {
2017-05-24 18:59:44 +02:00
debug ( "Removing an element in filesData with a missing href." , el );
2017-06-08 17:52:00 +02:00
toClean . push ( id );
2017-05-24 18:59:44 +02:00
continue ;
2017-05-05 11:55:19 +02:00
}
2017-07-05 12:21:01 +02:00
var parsed = Cryptpad . parsePadUrl ( el . href );
if ( ! parsed . hash ) {
debug ( "Removing an element in filesData with a invalid href." , el );
toClean . push ( id );
continue ;
}
migrateAttributes ( el , id , parsed );
2017-06-12 17:49:33 +02:00
if (( Cryptpad . isLoggedIn () || config . testMode ) && rootFiles . indexOf ( id ) === - 1 ) {
2017-05-24 18:59:44 +02:00
debug ( "An element in filesData was not in ROOT, TEMPLATE or TRASH." , id , el );
var newName = Cryptpad . createChannelId ();
root [ newName ] = id ;
continue ;
2017-04-12 18:54:22 +02:00
}
2017-06-09 16:39:44 +02:00
}
2017-06-08 17:52:00 +02:00
toClean . forEach ( function ( id ) {
spliceFileData ( id );
2017-04-12 18:54:22 +02:00
});
};
2017-07-05 12:21:01 +02:00
var fixDrive = function () {
Object . keys ( files ). forEach ( function ( key ) {
if ( key . slice ( 0 , 1 ) === '/' ) { delete files [ key ]; }
});
};
2017-04-12 18:54:22 +02:00
fixRoot ();
fixTrashRoot ();
if ( ! workgroup ) {
fixTemplate ();
fixFilesData ();
}
2017-07-05 12:21:01 +02:00
fixDrive ();
2017-04-12 18:54:22 +02:00
if ( JSON . stringify ( files ) !== before ) {
debug ( "Your file system was corrupted. It has been cleaned so that the pads you visit can be stored safely" );
return ;
}
debug ( "File system was clean" );
};
return exp ;
};
return module ;
});