2017-12-05 16:40:04 +01:00
define ([
'/customize/application_config.js' ,
'/common/common-util.js' ,
'/common/common-hash.js' ,
'/common/common-realtime.js' ,
2018-02-16 12:33:33 +01:00
'/common/common-feedback.js' ,
2017-12-05 16:40:04 +01:00
'/customize/messages.js'
2018-02-16 12:33:33 +01:00
], function ( AppConfig , Util , Hash , Realtime , Feedback , Messages ) {
2017-12-05 16:40:04 +01:00
var module = {};
var clone = function ( o ) {
try { return JSON . parse ( JSON . stringify ( o )); }
catch ( e ) { return undefined ; }
};
module . init = function ( config , exp , files ) {
2018-01-29 12:45:38 +01:00
var removeOwnedChannel = config . removeOwnedChannel || function () {
console . error ( "removeOwnedChannel was not provided" );
};
2017-12-05 16:40:04 +01:00
var loggedIn = config . loggedIn ;
2018-07-05 10:37:06 +02:00
var sharedFolder = config . sharedFolder ;
2018-01-29 12:45:38 +01:00
var edPublic = config . edPublic ;
2017-12-05 16:40:04 +01:00
2019-10-11 18:15:48 +02:00
var readOnly = config . readOnly ;
2017-12-05 16:40:04 +01:00
var ROOT = exp . ROOT ;
var FILES_DATA = exp . FILES_DATA ;
var OLD_FILES_DATA = exp . OLD_FILES_DATA ;
var UNSORTED = exp . UNSORTED ;
var TRASH = exp . TRASH ;
var TEMPLATE = exp . TEMPLATE ;
2018-07-05 10:37:06 +02:00
var SHARED_FOLDERS = exp . SHARED_FOLDERS ;
2019-10-21 15:22:59 +02:00
var SHARED_FOLDERS_TEMP = exp . SHARED_FOLDERS_TEMP ;
2017-12-05 16:40:04 +01:00
var debug = exp . debug ;
2019-10-11 18:15:48 +02:00
exp . _setReadOnly = function ( state ) {
readOnly = state ;
if ( ! readOnly ) { exp . fixFiles (); }
};
2019-10-07 18:30:46 +02:00
exp . setHref = function ( channel , id , href ) {
if ( ! id && ! channel ) { return ; }
2019-10-11 18:15:48 +02:00
if ( readOnly ) { return ; }
2019-10-07 18:30:46 +02:00
var ids = id ? [ id ] : exp . findChannels ([ channel ]);
ids . forEach ( function ( i ) {
var data = exp . getFileData ( i , true );
2019-11-18 14:56:46 +01:00
var oldHref = exp . getHref ( data );
if ( oldHref === href ) { return ; }
2019-10-07 18:30:46 +02:00
data . href = exp . cryptor . encrypt ( href );
});
};
2017-12-05 16:40:04 +01:00
exp . setPadAttribute = function ( href , attr , value , cb ) {
cb = cb || function () {};
2019-10-11 18:15:48 +02:00
if ( readOnly ) { return void cb ( 'EFORBIDDEN' ); }
2017-12-05 16:40:04 +01:00
var id = exp . getIdFromHref ( href );
if ( ! id ) { return void cb ( "E_INVAL_HREF" ); }
if ( ! attr || ! attr . trim ()) { return void cb ( "E_INVAL_ATTR" ); }
2019-10-07 14:35:11 +02:00
var data = exp . getFileData ( id , true );
2019-10-07 18:30:46 +02:00
if ( attr === "href" ) {
exp . setHref ( null , id , value );
} else {
data [ attr ] = clone ( value );
}
2017-12-05 16:40:04 +01:00
cb ( null );
};
exp . getPadAttribute = function ( href , attr , cb ) {
cb = cb || function () {};
var id = exp . getIdFromHref ( href );
if ( ! id ) { return void cb ( null , undefined ); }
var data = exp . getFileData ( id );
cb ( null , clone ( data [ attr ]));
};
2019-10-24 16:06:33 +02:00
exp . pushData = function ( _data , cb ) {
2017-12-05 16:40:04 +01:00
if ( typeof cb !== "function" ) { cb = function () {}; }
2019-10-11 18:15:48 +02:00
if ( readOnly ) { return void cb ( 'EFORBIDDEN' ); }
2018-07-10 18:23:16 +02:00
var id = Util . createRandomInteger ();
2019-10-24 16:06:33 +02:00
var data = clone ( _data );
2019-10-07 18:30:46 +02:00
// If we were given an edit link, encrypt its value if needed
2019-10-29 11:44:00 +01:00
if ( data . href && data . href . indexOf ( '#' ) !== - 1 ) { data . href = exp . cryptor . encrypt ( data . href ); }
2018-07-10 18:23:16 +02:00
files [ FILES_DATA ][ id ] = data ;
cb ( null , id );
2017-12-05 16:40:04 +01:00
};
2019-10-24 16:06:33 +02:00
exp . pushSharedFolder = function ( _data , cb ) {
2018-07-09 14:36:55 +02:00
if ( typeof cb !== "function" ) { cb = function () {}; }
2019-10-11 18:15:48 +02:00
if ( readOnly ) { return void cb ( 'EFORBIDDEN' ); }
2019-10-24 16:06:33 +02:00
var data = clone ( _data );
2018-07-10 18:23:16 +02:00
// Check if we already have this shared folder in our drive
2019-10-11 18:15:48 +02:00
var exists ;
2018-07-10 14:41:37 +02:00
if ( Object . keys ( files [ SHARED_FOLDERS ]). some ( function ( k ) {
2019-10-11 18:15:48 +02:00
if ( files [ SHARED_FOLDERS ][ k ]. channel === data . channel ) {
// We already know this shared folder. Check if we can get better access rights
if ( data . href && ! files [ SHARED_FOLDERS ][ k ]. href ) {
files [ SHARED_FOLDERS ][ k ]. href = data . href ;
}
exists = k ;
return true ;
}
2018-07-10 14:41:37 +02:00
})) {
2019-10-11 18:15:48 +02:00
return void cb ( 'EEXISTS' , exists );
2018-07-10 14:41:37 +02:00
}
// Add the folder
2019-05-27 15:29:36 +02:00
if ( ! loggedIn || config . testMode ) {
2018-07-09 14:36:55 +02:00
return void cb ( "EAUTH" );
2017-12-05 16:40:04 +01:00
}
2018-07-10 18:23:16 +02:00
var id = Util . createRandomInteger ();
2019-10-29 11:44:00 +01:00
if ( data . href && data . href . indexOf ( '#' ) !== - 1 ) { data . href = exp . cryptor . encrypt ( data . href ); }
2018-07-10 18:23:16 +02:00
files [ SHARED_FOLDERS ][ id ] = data ;
cb ( null , id );
2017-12-05 16:40:04 +01:00
};
2019-10-21 15:22:59 +02:00
exp . deprecateSharedFolder = function ( id ) {
2019-11-12 17:38:00 +01:00
if ( readOnly ) { return ; }
2019-10-21 15:22:59 +02:00
var data = files [ SHARED_FOLDERS ][ id ];
if ( ! data ) { return ; }
2019-11-12 17:38:00 +01:00
var ro = ! data . href || exp . cryptor . decrypt ( data . href ). indexOf ( '#' ) === - 1 ;
if ( ! ro ) {
files [ SHARED_FOLDERS_TEMP ][ id ] = JSON . parse ( JSON . stringify ( data ));
}
2019-10-21 15:22:59 +02:00
var paths = exp . findFile ( Number ( id ));
exp . delete ( paths , null , true );
delete files [ SHARED_FOLDERS ][ id ];
};
2017-12-05 16:40:04 +01:00
// FILES DATA
var spliceFileData = function ( id ) {
2019-10-11 18:15:48 +02:00
if ( readOnly ) { return ; }
2017-12-05 16:40:04 +01:00
delete files [ FILES_DATA ][ id ];
};
2018-02-14 19:41:07 +01:00
// Find files in FILES_DATA that are not anymore in the drive, and remove them from
2018-07-20 18:27:59 +02:00
// FILES_DATA. If there are owned pads, remove them from server too.
exp . checkDeletedFiles = function ( cb ) {
2018-07-12 17:27:16 +02:00
if ( ! loggedIn && ! config . testMode ) { return void cb (); }
2019-10-11 18:15:48 +02:00
if ( readOnly ) { return void cb ( 'EFORBIDDEN' ); }
2017-12-05 16:40:04 +01:00
var filesList = exp . getFiles ([ ROOT , 'hrefArray' , TRASH ]);
2017-12-12 16:00:05 +01:00
var toClean = [];
2018-07-16 14:05:36 +02:00
var ownedRemoved = [];
2018-07-12 17:27:16 +02:00
exp . getFiles ([ FILES_DATA , SHARED_FOLDERS ]). forEach ( function ( id ) {
2017-12-05 16:40:04 +01:00
if ( filesList . indexOf ( id ) === - 1 ) {
2018-07-12 17:27:16 +02:00
var fd = exp . isSharedFolder ( id ) ? files [ SHARED_FOLDERS ][ id ] : exp . getFileData ( id );
2018-04-27 17:23:23 +02:00
var channelId = fd . channel ;
2018-01-29 12:45:38 +01:00
// If trying to remove an owned pad, remove it from server also
2018-07-20 18:27:59 +02:00
if ( ! sharedFolder && fd . owners && fd . owners . indexOf ( edPublic ) !== - 1
&& channelId ) {
2018-07-16 14:05:36 +02:00
if ( channelId ) { ownedRemoved . push ( channelId ); }
2019-03-05 11:17:31 +01:00
Feedback . send ( 'REMOVE_OWNED_CHANNEL' );
2018-01-29 15:17:53 +01:00
removeOwnedChannel ( channelId , function ( obj ) {
2018-02-16 12:33:33 +01:00
if ( obj && obj . error ) {
2018-02-20 10:21:15 +01:00
// If the error is that the file is already removed, nothing to
// report, it's a normal behavior (pad expired probably)
if ( obj . error . code === 'ENOENT' ) { return ; }
2018-02-16 12:33:33 +01:00
// RPC may not be responding
// Send a report that can be handled manually
2018-02-20 10:21:15 +01:00
console . error ( obj . error );
2018-06-07 10:47:47 +02:00
Feedback . send ( 'ERROR_DELETING_OWNED_PAD=' + channelId + '|' + obj . error , true );
2018-02-16 12:33:33 +01:00
}
2018-01-29 15:17:53 +01:00
});
2019-02-05 15:58:49 +01:00
// Also remove the realtime channel for onlyoffice
if ( fd . rtChannel ) {
removeOwnedChannel ( fd . rtChannel , function () {});
}
2017-12-12 16:00:05 +01:00
}
2018-04-03 11:00:46 +02:00
if ( fd . lastVersion ) { toClean . push ( Hash . hrefToHexChannelId ( fd . lastVersion )); }
2019-02-05 15:58:49 +01:00
if ( fd . rtChannel ) { toClean . push ( fd . rtChannel ); }
2018-01-29 12:45:38 +01:00
if ( channelId ) { toClean . push ( channelId ); }
2018-07-12 17:27:16 +02:00
if ( exp . isSharedFolder ( id )) {
delete files [ SHARED_FOLDERS ][ id ];
2019-10-28 18:41:00 +01:00
if ( config . removeProxy ) { config . removeProxy ( id ); }
2018-07-12 17:27:16 +02:00
} else {
spliceFileData ( id );
}
2017-12-05 16:40:04 +01:00
}
});
2018-07-12 17:27:16 +02:00
if ( ! toClean . length ) { return void cb (); }
2018-07-16 14:05:36 +02:00
cb ( null , toClean , ownedRemoved );
2017-12-05 16:40:04 +01:00
};
var deleteHrefs = function ( ids ) {
2019-10-11 18:15:48 +02:00
if ( readOnly ) { return ; }
2017-12-05 16:40:04 +01:00
ids . forEach ( function ( obj ) {
var idx = files [ obj . root ]. indexOf ( obj . id );
files [ obj . root ]. splice ( idx , 1 );
});
};
var deleteMultipleTrashRoot = function ( roots ) {
2019-10-11 18:15:48 +02:00
if ( readOnly ) { return ; }
2017-12-05 16:40:04 +01:00
roots . forEach ( function ( obj ) {
var idx = files [ TRASH ][ obj . name ]. indexOf ( obj . el );
files [ TRASH ][ obj . name ]. splice ( idx , 1 );
});
};
2018-07-20 18:27:59 +02:00
exp . deleteMultiplePermanently = function ( paths , nocheck , cb ) {
2019-10-11 18:15:48 +02:00
if ( readOnly ) { return void cb ( 'EFORBIDDEN' ); }
2017-12-05 16:40:04 +01:00
var allFilesPaths = paths . filter ( function ( x ) { return exp . isPathIn ( x , [ FILES_DATA ]); });
if ( ! loggedIn && ! config . testMode ) {
allFilesPaths . forEach ( function ( path ) {
2018-06-28 18:16:34 +02:00
var id = path [ 1 ];
2017-12-05 16:40:04 +01:00
if ( ! id ) { return ; }
spliceFileData ( id );
});
2018-07-10 18:23:16 +02:00
return void cb ();
2017-12-05 16:40:04 +01:00
}
2019-10-11 18:15:48 +02:00
var hrefPaths = paths . filter ( function ( x ) { return exp . isPathIn ( x , [ 'hrefArray' ]); });
var rootPaths = paths . filter ( function ( x ) { return exp . isPathIn ( x , [ ROOT ]); });
var trashPaths = paths . filter ( function ( x ) { return exp . isPathIn ( x , [ TRASH ]); });
2017-12-05 16:40:04 +01:00
var ids = [];
hrefPaths . forEach ( function ( path ) {
var id = exp . 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 = exp . find ( parentPath );
delete parentEl [ key ];
});
var trashRoot = [];
trashPaths . forEach ( function ( path ) {
var parentPath = path . slice ();
var key = parentPath . pop ();
var parentEl = exp . 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
delete parentEl [ key ];
});
deleteMultipleTrashRoot ( trashRoot );
// In some cases, we want to remove pads from a location without removing them from
2018-07-05 10:37:06 +02:00
// FILES_DATA (replaceHref)
2018-07-20 18:27:59 +02:00
if ( ! nocheck ) { exp . checkDeletedFiles ( cb ); }
2018-07-10 18:23:16 +02:00
else { cb (); }
2017-12-05 16:40:04 +01:00
};
// Move
2018-07-05 10:37:06 +02:00
// From another drive
2018-07-20 18:27:59 +02:00
exp . copyFromOtherDrive = function ( path , element , data , key ) {
2019-10-11 18:15:48 +02:00
if ( readOnly ) { return ; }
2018-07-05 10:37:06 +02:00
// Copy files data
// We have to remove pads that are already in the current proxy to make sure
// we won't create duplicates
var toRemove = [];
Object . keys ( data ). forEach ( function ( id ) {
2018-07-09 14:36:55 +02:00
id = Number ( id );
2018-07-05 10:37:06 +02:00
// Find and maybe update existing pads with the same channel id
var d = data [ id ];
2019-10-07 18:30:46 +02:00
// If we were given an edit link, encrypt its value if needed
if ( d . href ) { d . href = exp . cryptor . encrypt ( d . href ); }
2018-07-05 10:37:06 +02:00
var found = false ;
for ( var i in files [ FILES_DATA ]) {
if ( files [ FILES_DATA ][ i ]. channel === d . channel ) {
// Update href?
2019-10-07 18:30:46 +02:00
if ( ! files [ FILES_DATA ][ i ]. href ) {
files [ FILES_DATA ][ i ]. href = d . href ;
}
2018-07-05 10:37:06 +02:00
found = true ;
break ;
}
}
if ( found ) {
toRemove . push ( id );
return ;
}
2019-10-07 18:30:46 +02:00
files [ FILES_DATA ][ id ] = d ;
2018-07-05 10:37:06 +02:00
});
// Remove existing pads from the "element" variable
if ( exp . isFile ( element ) && toRemove . indexOf ( element ) !== - 1 ) {
2018-07-17 15:38:23 +02:00
exp . log ( Messages . sharedFolders_duplicate );
2018-07-05 10:37:06 +02:00
return ;
} else if ( exp . isFolder ( element )) {
var _removeExisting = function ( root ) {
for ( var k in root ) {
if ( exp . isFile ( root [ k ])) {
if ( toRemove . indexOf ( root [ k ]) !== - 1 ) {
2018-07-17 15:38:23 +02:00
exp . log ( Messages . sharedFolders_duplicate );
2018-07-05 10:37:06 +02:00
delete root [ k ];
}
} else if ( exp . isFolder ( root [ k ])) {
_removeExisting ( root [ k ]);
}
}
};
_removeExisting ( element );
}
// Copy file or folder
var newParent = exp . find ( path );
2018-07-20 18:27:59 +02:00
var tempName = exp . isFile ( element ) ? Hash . createChannelId () : key ;
var newName = exp . getAvailableName ( newParent , tempName );
2018-07-05 10:37:06 +02:00
newParent [ newName ] = element ;
};
// From the same drive
2017-12-05 16:40:04 +01:00
var pushToTrash = function ( name , element , path ) {
2019-10-11 18:15:48 +02:00
if ( readOnly ) { return ; }
2017-12-05 16:40:04 +01:00
var trash = files [ TRASH ];
if ( typeof ( trash [ name ]) === "undefined" ) { trash [ name ] = []; }
var trashArray = trash [ name ];
var trashElement = {
element : element ,
path : path
};
trashArray . push ( trashElement );
};
exp . copyElement = function ( elementPath , newParentPath ) {
2019-10-11 18:15:48 +02:00
if ( readOnly ) { return ; }
2017-12-05 16:40:04 +01:00
if ( exp . comparePath ( elementPath , newParentPath )) { return ; } // Nothing to do...
var element = exp . find ( elementPath );
var newParent = exp . find ( newParentPath );
// Move to Trash
if ( exp . 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 = exp . isPathIn ( elementPath , [ 'hrefArray' ]) ? exp . getTitle ( element ) : key ;
var parentPath = elementPath . slice ();
parentPath . pop ();
pushToTrash ( elName , element , parentPath );
return true ;
}
// Move to hrefArray
if ( exp . isPathIn ( newParentPath , [ 'hrefArray' ])) {
if ( exp . isFolder ( element )) {
exp . 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 = exp . isFile ( element ) ?
exp . getAvailableName ( newParent , Hash . createChannelId ()) :
exp . isInTrashRoot ( elementPath ) ?
elementPath [ 1 ] : elementPath . pop ();
if ( typeof ( newParent [ newName ]) !== "undefined" ) {
exp . log ( Messages . fo_unavailableName );
return ;
}
newParent [ newName ] = element ;
return true ;
};
// FORGET (move with href not path)
exp . forget = function ( href ) {
2019-10-11 18:15:48 +02:00
if ( readOnly ) { return ; }
2017-12-05 16:40:04 +01:00
var id = exp . getIdFromHref ( href );
if ( ! id ) { return ; }
if ( ! loggedIn && ! config . testMode ) {
// delete permanently
spliceFileData ( id );
2019-09-06 15:45:56 +02:00
return true ;
2017-12-05 16:40:04 +01:00
}
var paths = exp . findFile ( id );
exp . move ( paths , [ TRASH ]);
2019-09-06 15:45:56 +02:00
return true ;
2017-12-05 16:40:04 +01:00
};
// REPLACE
2018-06-28 18:16:34 +02:00
// If all the occurences of an href are in the trash, remove them and add the file in root.
2017-12-05 16:40:04 +01:00
// This is use with setPadTitle when we open a stronger version of a deleted pad
exp . restoreHref = function ( href ) {
2019-10-11 18:15:48 +02:00
if ( readOnly ) { return ; }
2017-12-05 16:40:04 +01:00
var idO = exp . getIdFromHref ( href );
if ( ! idO || ! exp . isFile ( idO )) { return ; }
var paths = exp . 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 ) {
exp . add ( idO );
}
};
exp . add = function ( id , path ) {
2019-10-11 18:15:48 +02:00
if ( readOnly ) { return ; }
2017-12-05 16:40:04 +01:00
if ( ! loggedIn && ! config . testMode ) { return ; }
2018-07-09 14:36:55 +02:00
id = Number ( id );
var data = files [ FILES_DATA ][ id ] || files [ SHARED_FOLDERS ][ id ];
2017-12-05 16:40:04 +01:00
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 && exp . isPathIn ( newPath , [ 'hrefArray' ])) {
parentEl = exp . find ( newPath );
parentEl . push ( id );
return ;
}
2020-01-28 13:43:31 +01:00
// Add to root if no path
2017-12-05 16:40:04 +01:00
var filesList = exp . getFiles ([ ROOT , TRASH , 'hrefArray' ]);
2020-01-28 13:43:31 +01:00
if ( filesList . indexOf ( id ) === - 1 && ! newPath ) {
newPath = [ ROOT ];
}
// Add to root
if ( path && exp . isPathIn ( newPath , [ ROOT ])) {
2020-01-29 16:37:33 +01:00
parentEl = exp . find ( newPath );
2017-12-05 16:40:04 +01:00
if ( parentEl ) {
var newName = exp . getAvailableName ( parentEl , Hash . createChannelId ());
parentEl [ newName ] = id ;
return ;
2020-01-28 13:43:31 +01:00
} else {
parentEl = exp . find ([ ROOT ]);
newPath . slice ( 1 ). forEach ( function ( folderName ) {
parentEl = parentEl [ folderName ] = parentEl [ folderName ] || {};
});
parentEl [ Hash . createChannelId ()] = id ;
2017-12-05 16:40:04 +01:00
}
}
};
2019-06-18 10:44:27 +02:00
exp . setFolderData = function ( path , key , value , cb ) {
2019-10-11 18:15:48 +02:00
if ( readOnly ) { return ; }
2019-06-18 10:44:27 +02:00
var folder = exp . find ( path );
if ( ! exp . isFolder ( folder ) || exp . isSharedFolder ( folder )) { return ; }
if ( ! exp . hasFolderData ( folder )) {
var hashKey = "000" + Hash . createChannelId (). slice ( 0 , - 3 );
folder [ hashKey ] = {
metadata : true
};
}
exp . getFolderData ( folder )[ key ] = value ;
cb ();
};
2017-12-05 16:40:04 +01:00
/**
* INTEGRITY CHECK
*/
2019-10-10 12:35:01 +02:00
var onSync = function ( next ) {
if ( exp . rt ) {
exp . rt . sync ();
Realtime . whenRealtimeSyncs ( exp . rt , next );
} else {
window . setTimeout ( next , 1000 );
}
};
exp . migrateReadOnly = function ( cb ) {
2019-10-11 18:15:48 +02:00
if ( readOnly || ! config . editKey ) { return void cb ({ error : 'EFORBIDDEN' }); }
2019-10-10 12:35:01 +02:00
if ( files . version >= 2 ) { return void cb (); } // Already migrated, nothing to do
files . migrateRo = 1 ;
var next = function () {
var copy = JSON . parse ( JSON . stringify ( files ));
2019-10-28 18:22:42 +01:00
exp . reencrypt ( config . editKey , config . editKey , copy );
2019-11-07 17:02:22 +01:00
setTimeout ( function () {
if ( files . version >= 2 ) {
// Already migrated by another user while we were re-encrypting
return void cb ();
}
Object . keys ( copy ). forEach ( function ( k ) {
files [ k ] = copy [ k ];
});
files . version = 2 ;
delete files . migrateRo ;
2019-10-10 12:35:01 +02:00
2019-11-07 17:02:22 +01:00
onSync ( cb );
}, 1000 );
2019-10-10 12:35:01 +02:00
};
onSync ( next );
};
2017-12-05 16:40:04 +01:00
exp . migrate = function ( cb ) {
2019-10-11 18:15:48 +02:00
if ( readOnly ) { return void cb (); }
2017-12-05 16:40:04 +01:00
// 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 ;
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 = Util . createRandomInteger ();
var paths = exp . findFile ( href );
var data = obj ;
var key = Hash . 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 = exp . find ( parentPath );
if ( exp . isInTrashRoot ( p )) {
parent . element = id ;
newData [ id ]. filename = p [ 1 ];
return ;
}
if ( exp . isPathIn ( p , [ 'hrefArray' ])) {
parent [ okey ] = id ;
return ;
}
// else root or trash (not trashroot)
parent [ key ] = id ;
newData [ id ]. filename = okey ;
delete parent [ okey ];
});
});
delete files [ OLD_FILES_DATA ];
delete files . migrate ;
todo ();
};
2019-10-10 12:35:01 +02:00
onSync ( next );
2017-12-05 16:40:04 +01:00
} catch ( e ) {
console . error ( e );
todo ();
}
};
fixUnsorted ();
migrateToNewFormat ( cb );
};
2018-06-11 11:23:11 +02:00
exp . fixFiles = function ( silent ) {
2017-12-05 16:40:04 +01: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:..}
// * 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
2018-06-11 11:23:11 +02:00
2019-10-11 18:15:48 +02:00
// We can't fix anything in read-only mode: abort
if ( readOnly ) { return ; }
2018-06-11 11:23:11 +02:00
if ( silent ) { debug = function () {}; }
2019-10-07 18:30:46 +02:00
var t0 = + new Date ();
2017-12-05 16:40:04 +01: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 ];
2019-07-08 12:12:42 +02:00
if ( ! element ) { return console . error ( "Invalid element in root" ); }
2019-06-18 10:44:27 +02:00
var nbMetadataFolders = 0 ;
2017-12-05 16:40:04 +01:00
for ( var el in element ) {
2019-07-08 12:10:57 +02:00
if ( element [ el ] === null ) {
console . error ( 'element[%s] is null' , el );
delete element [ el ];
continue ;
}
2019-06-18 10:44:27 +02:00
if ( exp . isFolderData ( element [ el ])) {
if ( nbMetadataFolders !== 0 ) {
debug ( "Multiple metadata files in folder" );
delete element [ el ];
}
nbMetadataFolders ++ ;
continue ;
}
2017-12-05 16:40:04 +01:00
if ( ! exp . isFile ( element [ el ], true ) && ! exp . isFolder ( element [ el ])) {
debug ( "An element in ROOT was not a folder nor a file. " , element [ el ]);
delete element [ el ];
continue ;
}
if ( exp . 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 = Util . createRandomInteger ();
var key = Hash . createChannelId ();
2019-10-07 18:30:46 +02:00
files [ FILES_DATA ][ id ] = {
href : exp . cryptor . encrypt ( element [ el ]),
filename : el
};
2017-12-05 16:40:04 +01:00
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 () {
2018-07-05 10:37:06 +02:00
if ( sharedFolder ) { return ; }
2017-12-05 16:40:04 +01:00
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 ( ! exp . isFile ( obj . element , true ) &&
! exp . isFolder ( obj . element )) { toClean . push ( idx ); return ; }
if ( ! Array . 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 = Util . createRandomInteger ();
2019-10-07 18:30:46 +02:00
files [ FILES_DATA ][ id ] = {
href : exp . cryptor . encrypt ( obj . element ),
filename : el
};
2017-12-05 16:40:04 +01:00
obj . element = id ;
}
if ( exp . 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 ]);
delete tr [ el ];
} else if ( tr [ el ]. length === 0 ) {
debug ( "Empty array in TRASH root. " , tr [ el ]);
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 () {
2018-07-05 10:37:06 +02:00
if ( sharedFolder ) { return ; }
2017-12-05 16:40:04 +01:00
if ( ! Array . isArray ( files [ TEMPLATE ])) { debug ( "TEMPLATE was not an array" ); files [ TEMPLATE ] = []; }
2020-02-26 14:05:29 +01:00
var dedup = Util . deduplicateString ( files [ TEMPLATE ]);
if ( dedup . length !== files [ TEMPLATE ]. length ) {
files [ TEMPLATE ] = dedup ;
}
2020-03-04 15:48:40 +01:00
var us = files [ TEMPLATE ];
2020-02-26 14:05:29 +01:00
var rootFiles = exp . getFiles ([ ROOT ]);
2017-12-05 16:40:04 +01:00
var toClean = [];
us . forEach ( function ( el , idx ) {
if ( ! exp . isFile ( el , true ) || rootFiles . indexOf ( el ) !== - 1 ) {
toClean . push ( el );
2020-02-26 14:05:29 +01:00
return ;
2017-12-05 16:40:04 +01:00
}
if ( typeof el === "string" ) {
// We have an old file (href) which is not in filesData: add it
var id = Util . createRandomInteger ();
2019-10-07 18:30:46 +02:00
files [ FILES_DATA ][ id ] = {
href : exp . cryptor . encrypt ( el )
};
2017-12-05 16:40:04 +01:00
us [ idx ] = id ;
2020-02-26 14:05:29 +01:00
return ;
2017-12-05 16:40:04 +01:00
}
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 ( el );
}
}
});
toClean . forEach ( function ( el ) {
var idx = us . indexOf ( el );
if ( idx !== - 1 ) {
us . splice ( idx , 1 );
}
});
};
var fixFilesData = function () {
2018-07-09 14:36:55 +02:00
if ( typeof files [ FILES_DATA ] !== "object" ) { debug ( "FILES_DATA was not an object" ); files [ FILES_DATA ] = {}; }
2017-12-05 16:40:04 +01:00
var fd = files [ FILES_DATA ];
var rootFiles = exp . getFiles ([ ROOT , TRASH , 'hrefArray' ]);
var root = exp . find ([ ROOT ]);
var toClean = [];
for ( var id in fd ) {
2019-06-24 12:28:46 +02:00
if ( String ( id ) !== String ( Number ( id ))) {
2019-06-24 12:17:08 +02:00
debug ( "Invalid file ID in filesData." , id );
toClean . push ( id );
continue ;
}
2019-06-24 12:28:46 +02:00
id = Number ( id );
2017-12-05 16:40:04 +01:00
var el = fd [ id ];
2018-04-27 11:54:23 +02:00
// Clean corrupted data
2017-12-05 16:40:04 +01:00
if ( ! el || typeof ( el ) !== "object" ) {
debug ( "An element in filesData was not an object." , el );
toClean . push ( id );
continue ;
}
2018-04-27 11:54:23 +02:00
// Clean missing href
2018-06-28 18:16:34 +02:00
if ( ! el . href && ! el . roHref ) {
2017-12-05 16:40:04 +01:00
debug ( "Removing an element in filesData with a missing href." , el );
toClean . push ( id );
continue ;
}
2019-11-29 14:32:40 +01:00
var decryptedHref ;
2019-10-07 18:30:46 +02:00
try {
2019-11-29 14:32:40 +01:00
decryptedHref = el . href && (( el . href . indexOf ( '#' ) !== - 1 ) ? el . href : exp . cryptor . decrypt ( el . href ));
2019-10-07 18:30:46 +02:00
} catch ( e ) {}
2019-11-29 14:32:40 +01:00
if ( decryptedHref && decryptedHref . indexOf ( '#' ) === - 1 ) {
2019-10-08 18:47:54 +02:00
// If we can't decrypt the href, it means we don't have the correct secondaryKey and we're in readOnly mode:
// abort now, we won't be able to fix anything anyway
continue ;
}
2019-11-29 14:32:40 +01:00
var parsed = Hash . parsePadUrl ( decryptedHref || el . roHref );
2018-06-28 18:16:34 +02:00
var secret ;
2018-04-27 11:54:23 +02:00
// Clean invalid hash
2017-12-05 16:40:04 +01:00
if ( ! parsed . hash ) {
debug ( "Removing an element in filesData with a invalid href." , el );
toClean . push ( id );
continue ;
}
2018-04-27 11:54:23 +02:00
// Clean invalid type
2017-12-05 16:40:04 +01:00
if ( ! parsed . type ) {
debug ( "Removing an element in filesData with a invalid type." , el );
toClean . push ( id );
continue ;
}
2018-06-28 18:16:34 +02:00
// If we have an edit link, check the view link
2019-11-29 14:32:40 +01:00
if ( decryptedHref && parsed . hashData . type === "pad" && parsed . hashData . version ) {
2018-06-29 18:16:04 +02:00
if ( parsed . hashData . mode === "view" ) {
2019-11-29 14:32:40 +01:00
el . roHref = decryptedHref ;
2018-06-29 18:16:04 +02:00
delete el . href ;
} else if ( ! el . roHref ) {
2018-06-28 18:16:34 +02:00
secret = Hash . getSecrets ( parsed . type , parsed . hash , el . password );
2018-06-29 18:16:04 +02:00
el . roHref = '/' + parsed . type + '/#' + Hash . getViewHashFromKeys ( secret );
2018-06-28 18:16:34 +02:00
} else {
var parsed2 = Hash . parsePadUrl ( el . roHref );
if ( ! parsed2 . hash || ! parsed2 . type ) {
2018-06-29 18:16:04 +02:00
secret = Hash . getSecrets ( parsed . type , parsed . hash , el . password );
el . roHref = '/' + parsed . type + '/#' + Hash . getViewHashFromKeys ( secret );
2018-06-28 18:16:34 +02:00
}
}
}
2019-05-03 15:07:04 +02:00
// v0 hashes don't support read-only
if ( parsed . hashData . version === 0 ) {
delete el . roHref ;
}
2018-06-28 18:16:34 +02:00
2018-04-27 11:54:23 +02:00
// Fix href
2019-11-29 14:32:40 +01:00
if ( decryptedHref && decryptedHref . slice ( 0 , 1 ) !== '/' ) {
el . href = exp . cryptor . encrypt ( Hash . getRelativeHref ( decryptedHref ));
}
2018-04-27 11:54:23 +02:00
// Fix creation time
if ( ! el . ctime ) { el . ctime = el . atime ; }
// Fix title
2019-09-05 12:22:30 +02:00
if ( ! el . title ) { el . title = exp . getDefaultName ( parsed ); }
2018-04-27 11:54:23 +02:00
// Fix channel
if ( ! el . channel ) {
2018-05-18 10:28:31 +02:00
try {
2018-06-28 18:16:34 +02:00
if ( ! secret ) {
secret = Hash . getSecrets ( parsed . type , parsed . hash , el . password );
}
2018-04-27 11:54:23 +02:00
el . channel = secret . channel ;
2018-07-09 14:36:55 +02:00
console . log ( el );
debug ( 'Adding missing channel in filesData ' , el . channel );
2018-05-18 10:28:31 +02:00
} catch ( e ) {
console . error ( e );
}
2018-04-27 11:54:23 +02:00
}
2017-12-05 16:40:04 +01:00
if (( loggedIn || config . testMode ) && rootFiles . indexOf ( id ) === - 1 ) {
debug ( "An element in filesData was not in ROOT, TEMPLATE or TRASH." , id , el );
var newName = Hash . createChannelId ();
root [ newName ] = id ;
continue ;
}
}
toClean . forEach ( function ( id ) {
spliceFileData ( id );
});
};
2018-07-05 10:37:06 +02:00
var fixSharedFolders = function () {
if ( sharedFolder ) { return ; }
if ( typeof ( files [ SHARED_FOLDERS ]) !== "object" ) { debug ( "SHARED_FOLDER was not an object" ); files [ SHARED_FOLDERS ] = {}; }
2018-07-12 17:27:16 +02:00
var sf = files [ SHARED_FOLDERS ];
var rootFiles = exp . getFiles ([ ROOT ]);
var root = exp . find ([ ROOT ]);
2020-02-27 13:40:48 -05:00
var parsed /*, secret */ , el ;
2018-07-12 17:27:16 +02:00
for ( var id in sf ) {
2018-12-17 15:49:48 +01:00
el = sf [ id ];
2018-07-12 17:27:16 +02:00
id = Number ( id );
2018-12-17 15:49:48 +01:00
2019-10-07 18:30:46 +02:00
var href ;
try {
href = el . href && (( el . href . indexOf ( '#' ) !== - 1 ) ? el . href : exp . cryptor . decrypt ( el . href ));
} catch ( e ) {}
2018-12-17 15:49:48 +01:00
// Fix undefined hash
2019-10-07 18:30:46 +02:00
parsed = Hash . parsePadUrl ( href || el . roHref );
2020-02-26 14:05:29 +01:00
if ( ! parsed || ! parsed . hash || parsed . hash === "undefined" ) {
2018-12-17 15:49:48 +01:00
delete sf [ id ];
continue ;
}
// Fix shared folder not displayed in root
2018-07-12 17:27:16 +02:00
if ( rootFiles . indexOf ( id ) === - 1 ) {
console . log ( 'missing' + id );
var newName = Hash . createChannelId ();
root [ newName ] = id ;
}
}
2018-07-05 10:37:06 +02:00
};
2019-10-21 17:26:52 +02:00
var fixSharedFoldersTemp = function () {
if ( sharedFolder ) { return ; }
if ( typeof ( files [ SHARED_FOLDERS_TEMP ]) !== "object" ) {
debug ( "SHARED_FOLDER_TEMP was not an object" );
files [ SHARED_FOLDERS_TEMP ] = {};
}
// Remove deprecated shared folder if they were already added back
var sft = files [ SHARED_FOLDERS_TEMP ];
var sf = files [ SHARED_FOLDERS ];
for ( var id in sft ) {
if ( sf [ id ]) {
delete sft [ id ];
}
}
};
2017-12-05 16:40:04 +01:00
var fixDrive = function () {
Object . keys ( files ). forEach ( function ( key ) {
if ( key . slice ( 0 , 1 ) === '/' ) { delete files [ key ]; }
});
};
fixRoot ();
fixTrashRoot ();
2018-07-05 10:37:06 +02:00
fixTemplate ();
fixFilesData ();
2017-12-05 16:40:04 +01:00
fixDrive ();
2018-07-20 12:04:42 +02:00
fixSharedFolders ();
2019-10-21 17:26:52 +02:00
fixSharedFoldersTemp ();
2017-12-05 16:40:04 +01:00
2019-10-07 18:30:46 +02:00
var ms = ( + new Date () - t0 ) + 'ms' ;
2017-12-05 16:40:04 +01:00
if ( JSON . stringify ( files ) !== before ) {
2019-10-07 18:30:46 +02:00
debug ( "Your file system was corrupted. It has been cleaned so that the pads you visit can be stored safely." , ms );
2017-12-05 16:40:04 +01:00
return ;
}
2019-10-07 18:30:46 +02:00
debug ( "File system was clean." , ms );
2017-12-05 16:40:04 +01:00
};
return exp ;
};
return module ;
});