define ( [
'jquery' ,
'/customize/application_config.js'
] , function ( $ , AppConfig ) {
var module = { } ;
var ROOT = module . ROOT = "root" ;
var UNSORTED = module . UNSORTED = "unsorted" ;
var TRASH = module . TRASH = "trash" ;
var TEMPLATE = module . TEMPLATE = "template" ;
module . init = function ( files , config ) {
var exp = { } ;
var Cryptpad = config . Cryptpad ;
var Messages = Cryptpad . Messages ;
var FILES _DATA = module . FILES _DATA = exp . FILES _DATA = Cryptpad . storageKey ;
var OLD _FILES _DATA = module . OLD _FILES _DATA = exp . OLD _FILES _DATA = Cryptpad . oldStorageKey ;
var NEW _FOLDER _NAME = Messages . fm _newFolder ;
var NEW _FILE _NAME = Messages . fm _newFile ;
exp . ROOT = ROOT ;
exp . UNSORTED = UNSORTED ;
exp . TRASH = TRASH ;
exp . TEMPLATE = TEMPLATE ;
// 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
* /
exp . getStructure = function ( ) {
var a = { } ;
a [ ROOT ] = { } ;
a [ TRASH ] = { } ;
a [ FILES _DATA ] = { } ;
a [ TEMPLATE ] = [ ] ;
return a ;
} ;
var getHrefArray = function ( ) {
return [ TEMPLATE ] ;
} ;
var compareFiles = function ( fileA , fileB ) { return fileA === fileB ; } ;
var isFile = exp . isFile = function ( element , allowStr ) {
return typeof ( element ) === "number" ||
( ( typeof ( files [ OLD _FILES _DATA ] ) !== "undefined" || allowStr )
&& typeof ( element ) === "string" ) ;
} ;
exp . isReadOnlyFile = function ( element ) {
if ( ! isFile ( element ) ) { return false ; }
var data = exp . getFileData ( element ) ;
var parsed = Cryptpad . parsePadUrl ( data . href ) ;
if ( ! parsed ) { return false ; }
var pHash = parsed . hashData ;
if ( ! pHash || pHash . type !== "pad" ) { return ; }
return pHash && pHash . mode === 'view' ;
} ;
var isFolder = exp . isFolder = function ( element ) {
return typeof ( element ) === "object" ;
} ;
exp . isFolderEmpty = function ( element ) {
if ( ! isFolder ( element ) ) { return false ; }
return Object . keys ( element ) . length === 0 ;
} ;
exp . hasSubfolder = function ( element , trashRoot ) {
if ( ! isFolder ( element ) ) { return false ; }
var subfolder = 0 ;
var addSubfolder = function ( el ) {
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 ;
} ;
exp . hasFile = function ( element , trashRoot ) {
if ( ! isFolder ( element ) ) { return false ; }
var file = 0 ;
var addFile = function ( el ) {
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 ; }
return files [ FILES _DATA ] [ file ] || { } ;
} ;
// Data from filesData
var getTitle = exp . getTitle = function ( file , type ) {
if ( workgroup ) { debug ( "No titles in workgroups" ) ; return ; }
var data = getFileData ( file ) ;
if ( ! file || ! data || ! data . href ) {
error ( "getTitle called with a non-existing file id: " , file , data ) ;
return ;
}
if ( type === 'title' ) { return data . title ; }
if ( type === 'name' ) { return data . filename ; }
return data . filename || data . title || NEW _FILE _NAME ;
} ;
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 ;
cb ( null ) ;
} ;
// 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 ;
} ;
var isSubpath = exp . isSubpath = function ( path , parentPath ) {
var pathA = parentPath . slice ( ) ;
var pathB = path . slice ( 0 , pathA . length ) ;
return comparePath ( pathA , pathB ) ;
} ;
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 ( ) ) ;
}
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 ) {
if ( ! files [ cat ] ) { files [ cat ] = [ ] ; }
return files [ cat ] . slice ( ) ;
} ;
getHrefArray ( ) . forEach ( function ( c ) {
_getFiles [ c ] = function ( ) { return _getFiles [ 'array' ] ( c ) ; } ;
} ) ;
_getFiles [ 'hrefArray' ] = function ( ) {
var ret = [ ] ;
getHrefArray ( ) . forEach ( function ( c ) {
ret = ret . concat ( _getFiles [ c ] ( ) ) ;
} ) ;
return Cryptpad . deduplicateString ( ret ) ;
} ;
_getFiles [ ROOT ] = function ( ) {
var ret = [ ] ;
getFilesRecursively ( files [ ROOT ] , ret ) ;
return ret ;
} ;
_getFiles [ TRASH ] = function ( ) {
var root = files [ TRASH ] ;
var ret = [ ] ;
var addFiles = function ( el ) {
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 ;
} ;
_getFiles [ OLD _FILES _DATA ] = function ( ) {
var ret = [ ] ;
if ( ! files [ OLD _FILES _DATA ] ) { return ret ; }
files [ OLD _FILES _DATA ] . forEach ( function ( el ) {
if ( el . href && ret . indexOf ( el . href ) === - 1 ) {
ret . push ( el . href ) ;
}
} ) ;
return ret ;
} ;
_getFiles [ FILES _DATA ] = function ( ) {
var ret = [ ] ;
if ( ! files [ FILES _DATA ] ) { return ret ; }
return Object . keys ( files [ FILES _DATA ] ) . map ( Number ) ;
} ;
var getFiles = exp . getFiles = function ( categories ) {
var ret = [ ] ;
if ( ! categories || ! categories . length ) {
categories = [ ROOT , 'hrefArray' , TRASH , OLD _FILES _DATA , FILES _DATA ] ;
}
categories . forEach ( function ( c ) {
if ( typeof _getFiles [ c ] === "function" ) {
ret = ret . concat ( _getFiles [ c ] ( ) ) ;
}
} ) ;
return Cryptpad . deduplicateString ( ret ) ;
} ;
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 ;
} ;
// SEARCH
var _findFileInRoot = function ( path , file ) {
if ( ! isPathIn ( path , [ ROOT , TRASH ] ) ) { return [ ] ; }
var paths = [ ] ;
var root = find ( path ) ;
var addPaths = function ( p ) {
if ( paths . indexOf ( p ) === - 1 ) {
paths . push ( p ) ;
}
} ;
if ( isFile ( root ) ) {
if ( compareFiles ( file , root ) ) {
if ( paths . indexOf ( path ) === - 1 ) {
paths . push ( path ) ;
}
}
return paths ;
}
for ( var e in root ) {
var nPath = path . slice ( ) ;
nPath . push ( e ) ;
_findFileInRoot ( nPath , file ) . forEach ( addPaths ) ;
}
return paths ;
} ;
exp . findFileInRoot = function ( file ) {
return _findFileInRoot ( [ ROOT ] , file ) ;
} ;
var _findFileInHrefArray = function ( rootName , file ) {
if ( ! files [ rootName ] ) { return [ ] ; }
var unsorted = files [ rootName ] . slice ( ) ;
var ret = [ ] ;
var i = - 1 ;
while ( ( i = unsorted . indexOf ( file , i + 1 ) ) !== - 1 ) {
ret . push ( [ rootName , i ] ) ;
}
return ret ;
} ;
var _findFileInTrash = function ( path , file ) {
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 ) ;
_findFileInTrash ( nPath , file ) . forEach ( addPaths ) ;
} ) ;
}
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 ) ) {
if ( compareFiles ( file , el . element ) ) {
addPaths ( nPath ) ;
}
return ;
}
_findFileInTrash ( nPath , file ) . forEach ( addPaths ) ;
} ) ;
}
if ( path . length >= 4 ) {
_findFileInRoot ( path , file ) . forEach ( addPaths ) ;
}
return paths ;
} ;
var findFile = exp . findFile = function ( file ) {
var rootpaths = _findFileInRoot ( [ ROOT ] , file ) ;
var templatepaths = _findFileInHrefArray ( TEMPLATE , file ) ;
var trashpaths = _findFileInTrash ( [ TRASH ] , file ) ;
return rootpaths . concat ( templatepaths , trashpaths ) ;
} ;
exp . search = function ( value ) {
if ( typeof ( value ) !== "string" ) { return [ ] ; }
var res = [ ] ;
// Search title
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 ) ;
}
} ) ;
// Search Href
var href = Cryptpad . getRelativeHref ( value ) ;
if ( href ) {
var id = getIdFromHref ( href ) ;
if ( id ) { res . push ( id ) ; }
}
res = Cryptpad . deduplicateString ( res ) ;
var ret = [ ] ;
res . forEach ( function ( l ) {
//var paths = findFile(l);
ret . push ( {
paths : findFile ( l ) ,
data : exp . getFileData ( l )
} ) ;
} ) ;
return ret ;
} ;
exp . getRecentPads = function ( ) {
var allFiles = files [ FILES _DATA ] ;
var sorted = Object . keys ( allFiles )
. sort ( function ( a , b ) {
return allFiles [ a ] . atime < allFiles [ b ] . atime ;
} )
. map ( function ( str ) { return Number ( str ) ; } ) ;
return sorted ;
} ;
/ * *
* 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
exp . pushData = function ( data , cb ) {
if ( typeof cb !== "function" ) { cb = function ( ) { } ; }
var todo = function ( ) {
var id = Cryptpad . createRandomInteger ( ) ;
files [ FILES _DATA ] [ id ] = data ;
cb ( null , id ) ;
} ;
if ( ! Cryptpad . isLoggedIn ( ) || ! AppConfig . enablePinning || config . testMode ) {
return void todo ( ) ;
}
Cryptpad . pinPads ( [ Cryptpad . hrefToHexChannelId ( data . href ) ] , function ( e ) {
if ( e ) { return void cb ( e ) ; }
todo ( ) ;
} ) ;
} ;
var spliceFileData = exp . removeData = function ( id ) {
files [ FILES _DATA ] [ id ] = undefined ;
delete files [ FILES _DATA ] [ id ] ;
} ;
// 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 ) {
debug ( "Can't move an element from the trash to the trash: " , elementPath ) ;
return ;
}
var key = elementPath [ elementPath . length - 1 ] ;
var elName = isPathIn ( elementPath , [ 'hrefArray' ] ) ? getTitle ( element ) : key ;
var parentPath = elementPath . slice ( ) ;
parentPath . pop ( ) ;
pushToTrash ( elName , element , parentPath ) ;
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
var newName = isFile ( element ) ?
getAvailableName ( newParent , Cryptpad . createChannelId ( ) ) :
isInTrashRoot ( elementPath ) ?
elementPath [ 1 ] : elementPath . pop ( ) ;
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 ( ) ;
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
if ( copyElement ( p . slice ( ) , newPath ) ) {
toRemove . push ( p ) ;
}
} ) ;
exp . delete ( toRemove , cb ) ;
} ;
exp . restore = function ( path , cb ) {
if ( ! isInTrashRoot ( path ) ) { return ; }
var parentPath = path . slice ( ) ;
parentPath . pop ( ) ;
var oldPath = find ( parentPath ) . path ;
move ( [ path ] , oldPath , cb ) ;
} ;
// ADD
var add = exp . add = function ( id , path ) {
if ( ! Cryptpad . isLoggedIn ( ) && ! config . testMode ) { return ; }
var data = files [ FILES _DATA ] [ id ] ;
if ( ! data || typeof ( data ) !== "object" ) { return ; }
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 ) ;
parentEl . push ( id ) ;
return ;
}
// Add to root if path is ROOT or if no path
var filesList = getFiles ( [ ROOT , TRASH , 'hrefArray' ] ) ;
if ( path && isPathIn ( newPath , [ ROOT ] ) || filesList . indexOf ( id ) === - 1 ) {
parentEl = find ( newPath || [ ROOT ] ) ;
if ( parentEl ) {
var newName = getAvailableName ( parentEl , Cryptpad . createChannelId ( ) ) ;
parentEl [ newName ] = id ;
return ;
}
}
} ;
exp . addFolder = function ( folderPath , name , cb ) {
var parentEl = find ( folderPath ) ;
var folderName = getAvailableName ( parentEl , name || NEW _FOLDER _NAME ) ;
parentEl [ folderName ] = { } ;
var newPath = folderPath . slice ( ) ;
newPath . push ( folderName ) ;
cb ( void 0 , {
newPath : newPath
} ) ;
} ;
// FORGET (move with href not path)
exp . forget = function ( href ) {
var id = getIdFromHref ( href ) ;
if ( ! id ) { return ; }
if ( ! Cryptpad . isLoggedIn ( ) && ! config . testMode ) {
// delete permanently
exp . removePadAttribute ( href ) ;
spliceFileData ( id ) ;
return ;
}
var paths = findFile ( id ) ;
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)
var removePadAttribute = exp . removePadAttribute = function ( f ) {
if ( typeof ( f ) !== 'string' ) {
console . error ( "Can't find pad attribute for an undefined pad" ) ;
return ;
}
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 ( ) {
// Nothing in OLD_FILES_DATA for workgroups
if ( workgroup || ( ! Cryptpad . isLoggedIn ( ) && ! config . testMode ) ) { return ; }
var filesList = getFiles ( [ ROOT , 'hrefArray' , TRASH ] ) ;
var fData = files [ FILES _DATA ] ;
getFiles ( [ FILES _DATA ] ) . forEach ( function ( id ) {
if ( filesList . indexOf ( id ) === - 1 ) {
removePadAttribute ( fData [ id ] . href ) ;
spliceFileData ( id ) ;
}
} ) ;
} ;
var deleteHrefs = function ( ids ) {
ids . forEach ( function ( obj ) {
var idx = files [ obj . root ] . indexOf ( obj . id ) ;
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 ) ;
} ) ;
} ;
var deleteMultiplePermanently = function ( paths , nocheck ) {
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 ] ) ; } ) ;
var allFilesPaths = paths . filter ( function ( x ) { return isPathIn ( x , [ FILES _DATA ] ) ; } ) ;
if ( ! Cryptpad . isLoggedIn ( ) && ! config . testMode ) {
allFilesPaths . forEach ( function ( path ) {
var el = find ( path ) ;
if ( ! el ) { return ; }
var id = getIdFromHref ( el . href ) ;
if ( ! id ) { return ; }
spliceFileData ( id ) ;
removePadAttribute ( el . href ) ;
} ) ;
return ;
}
var ids = [ ] ;
hrefPaths . forEach ( function ( path ) {
var id = find ( path ) ;
ids . push ( {
root : path [ 0 ] ,
id : id
} ) ;
} ) ;
deleteHrefs ( ids ) ;
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 ) ;
// In some cases, we want to remove pads from a location without removing them from
// OLD_FILES_DATA (replaceHref)
if ( ! nocheck ) { checkDeletedFiles ( ) ; }
} ;
exp . delete = function ( paths , cb , nocheck ) {
deleteMultiplePermanently ( paths , nocheck ) ;
if ( typeof cb === "function" ) { cb ( ) ; }
} ;
exp . emptyTrash = function ( cb ) {
files [ TRASH ] = { } ;
checkDeletedFiles ( ) ;
if ( cb ) { cb ( ) ; }
} ;
// RENAME
exp . rename = function ( path , newName , cb ) {
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 ) ;
// 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 ( ) ; }
return ;
}
// 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 ( ) ; }
return ;
}
if ( getTitle ( element , 'name' ) === newName ) { return ; }
data . filename = newName ;
if ( typeof cb === "function" ) { cb ( ) ; }
} ;
// REPLACE
exp . replace = function ( o , n ) {
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 ) ;
// Remove all the occurences in the trash
// If all the occurences are in the trash or no occurence, add the pad to root
var allInTrash = true ;
paths . forEach ( function ( p ) {
if ( p [ 0 ] === TRASH ) {
exp . delete ( p , null , true ) ; // 3rd parameter means skip "checkDeletedFiles"
return ;
}
allInTrash = false ;
} ) ;
if ( allInTrash ) {
add ( idO ) ;
}
} ;
/ * *
* INTEGRITY CHECK
* /
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..." ) ;
Cryptpad . feedback ( 'Migrate-oldFilesData' , true ) ;
files . migrate = 1 ;
var next = function ( ) {
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 ( ) ;
} ;
if ( exp . rt ) {
exp . rt . sync ( ) ;
Cryptpad . whenRealtimeSyncs ( exp . rt , next ) ;
} else {
window . setTimeout ( next , 1000 ) ;
}
} catch ( e ) {
console . error ( e ) ;
todo ( ) ;
}
} ;
fixUnsorted ( ) ;
migrateToNewFormat ( cb ) ;
} ;
exp . fixFiles = function ( ) {
// 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:..}
// * OLD_FILES_DATA: - Data (title, cdate, adte) are stored in filesData. filesData contains only href keys linking to object with title, cdate, adate.
// - 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'
// * TEMPLATE: Contains only files (href), and does not contains files that are in ROOT
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 ) {
if ( ! isFile ( element [ el ] , true ) && ! isFolder ( element [ el ] ) ) {
debug ( "An element in ROOT was not a folder nor a file. " , element [ el ] ) ;
element [ el ] = undefined ;
delete element [ el ] ;
continue ;
}
if ( isFolder ( element [ el ] ) ) {
fixRoot ( element [ el ] ) ;
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 ( ) ;
files [ FILES _DATA ] [ id ] = { href : element [ el ] , filename : el } ;
element [ key ] = id ;
delete element [ el ] ;
}
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 ] ;
}
}
}
} ;
var fixTrashRoot = function ( ) {
if ( typeof ( files [ TRASH ] ) !== "object" ) { debug ( "TRASH was not an object" ) ; files [ TRASH ] = { } ; }
var tr = files [ TRASH ] ;
var toClean ;
var addToClean = function ( obj , idx , el ) {
if ( typeof ( obj ) !== "object" ) { toClean . push ( idx ) ; return ; }
if ( ! isFile ( obj . element , true ) && ! isFolder ( obj . element ) ) { toClean . push ( idx ) ; return ; }
if ( ! $ . isArray ( obj . path ) ) { toClean . push ( idx ) ; return ; }
if ( typeof obj . element === "string" ) {
// We have an old file (href) which is not in filesData: add it
var id = Cryptpad . createRandomInteger ( ) ;
files [ FILES _DATA ] [ id ] = { href : obj . element , filename : el } ;
obj . element = id ;
}
if ( isFolder ( obj . element ) ) { fixRoot ( obj . element ) ; }
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 ) ;
toClean . push ( idx ) ;
}
}
} ;
for ( var el in tr ) {
if ( ! Array . isArray ( tr [ el ] ) ) {
debug ( "An element in TRASH root is not an array. " , tr [ el ] ) ;
tr [ el ] = undefined ;
delete tr [ el ] ;
} else if ( tr [ el ] . length === 0 ) {
debug ( "Empty array in TRASH root. " , tr [ el ] ) ;
tr [ el ] = undefined ;
delete tr [ el ] ;
} else {
toClean = [ ] ;
for ( var j = 0 ; j < tr [ el ] . length ; j ++ ) {
addToClean ( tr [ el ] [ j ] , j , el ) ;
}
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 ] ;
var rootFiles = getFiles ( [ ROOT ] ) . slice ( ) ;
var toClean = [ ] ;
us . forEach ( function ( el , idx ) {
if ( ! isFile ( el , true ) || rootFiles . indexOf ( el ) !== - 1 ) {
toClean . push ( idx ) ;
}
if ( typeof el === "string" ) {
// We have an old file (href) which is not in filesData: add it
var id = Cryptpad . createRandomInteger ( ) ;
files [ FILES _DATA ] [ id ] = { href : el } ;
us [ idx ] = id ;
}
if ( typeof el === "number" ) {
var data = files [ FILES _DATA ] [ el ] ;
if ( ! data ) {
debug ( "An element in TEMPLATE doesn't have associated data" , el ) ;
toClean . push ( idx ) ;
}
}
} ) ;
toClean . forEach ( function ( idx ) {
us . splice ( idx , 1 ) ;
} ) ;
} ;
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 ( typeof ( files [ key ] ) !== "undefined" || typeof ( files [ key2 ] ) !== "undefined" ) {
debug ( "Migrating pad attribute" , attr , "for pad" , id ) ;
el [ attr ] = files [ key ] || files [ key2 ] ;
delete files [ key ] ;
delete files [ key2 ] ;
}
} ) ;
// Migration done
} ;
var fixFilesData = function ( ) {
if ( typeof files [ FILES _DATA ] !== "object" ) { debug ( "OLD_FILES_DATA was not an object" ) ; files [ FILES _DATA ] = { } ; }
var fd = files [ FILES _DATA ] ;
var rootFiles = getFiles ( [ ROOT , TRASH , 'hrefArray' ] ) ;
var root = find ( [ ROOT ] ) ;
var toClean = [ ] ;
for ( var id in fd ) {
id = Number ( id ) ;
var el = fd [ id ] ;
if ( ! el || typeof ( el ) !== "object" ) {
debug ( "An element in filesData was not an object." , el ) ;
toClean . push ( id ) ;
continue ;
}
if ( ! el . href ) {
debug ( "Removing an element in filesData with a missing href." , el ) ;
toClean . push ( id ) ;
continue ;
}
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 ) ;
if ( ( Cryptpad . isLoggedIn ( ) || config . testMode ) && rootFiles . indexOf ( id ) === - 1 ) {
debug ( "An element in filesData was not in ROOT, TEMPLATE or TRASH." , id , el ) ;
var newName = Cryptpad . createChannelId ( ) ;
root [ newName ] = id ;
continue ;
}
}
toClean . forEach ( function ( id ) {
spliceFileData ( id ) ;
} ) ;
} ;
var fixDrive = function ( ) {
Object . keys ( files ) . forEach ( function ( key ) {
if ( key . slice ( 0 , 1 ) === '/' ) { delete files [ key ] ; }
} ) ;
} ;
fixRoot ( ) ;
fixTrashRoot ( ) ;
if ( ! workgroup ) {
fixTemplate ( ) ;
fixFilesData ( ) ;
}
fixDrive ( ) ;
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 ;
} ) ;