@@ -6,7 +6,7 @@ import errorUtils from './utils/errorUtils'
66import linksUtils from './utils/linksUtils'
77
88const fetchLog = debug ( 'solid-file-client:fetch' )
9- const { getRootUrl, getParentUrl, getItemName, areFolders, areFiles, LINK } = apiUtils
9+ const { getRootUrl, getParentUrl, getItemName, areFolders, /* areFiles, */ LINK } = apiUtils
1010const { FetchError, assertResponseOk, composedFetch, toFetchError } = errorUtils
1111const { getLinksFromResponse } = linksUtils
1212const { parseFolderResponse } = folderUtils
@@ -974,25 +974,94 @@ class SolidAPI {
974974 }
975975
976976 /**
977- * Move a file (url ending with file name) or folder (url ending with "/").
978- * Per default existing folders will be deleted before moving and links will be moved.
979- * Shortcut for copying and deleting items
980- * @param {string } from
977+ * Moves Folder contents.
978+ * @param {Promise<Response> } getResoponse
981979 * @param {string } to
982980 * @param {WriteOptions } [options]
983- * @returns {Promise<Response[]> } Resolves with an array of creation (copy) responses.
981+ * @returns {Promise<Response[]> } Resolves to an array of responses to copy further or paste.
982+ * @private
983+ */
984+ async _moveFolderContents ( getResponse , to , options ) {
985+ const from = getResponse . url
986+
987+ const { folders, files } = await parseFolderResponse ( getResponse )
988+ const folderResponse = await this . createFolder ( to , options )
989+
990+ await this . _copyLinksForItem ( getResponse , folderResponse , options )
991+
992+ const foldersCreation = folders . map ( async ( { name } ) => {
993+ const folderResp = await this . get ( `${ from } ${ name } /` , { headers : { Accept : 'text/turtle' } } )
994+ return this . _moveFolderContents ( folderResp , `${ to } ${ name } /` , options )
995+ } )
996+
997+ const filesCreation = files . map ( async ( { name } ) => {
998+ let fileResp
999+ let copyResponse
1000+ try {
1001+ fileResp = await this . get ( `${ from } ${ name } ` )
1002+ copyResponse = await this . _pasteFile ( fileResp , `${ to } ${ name } ` , options )
1003+ } catch ( error ) {
1004+ if ( error . message . includes ( 'already existed' ) ) {
1005+ copyResponse = error // Don't throw when merge=KEEP_TARGET and it tried to overwrite a file
1006+ } else {
1007+ throw toFetchError ( error )
1008+ }
1009+ }
1010+ await this . _removeItemWithLinks ( fileResp )
1011+ return copyResponse
1012+ } )
1013+
1014+ const creationResults = await composedFetch ( [
1015+ ...foldersCreation ,
1016+ ...filesCreation
1017+ ] ) . then ( responses => responses . filter ( item => ! ( item instanceof FetchError ) ) )
1018+
1019+ await this . _removeItemWithLinks ( getResponse )
1020+
1021+ return [ folderResponse , ...creationResults ] // Alternative to Array.prototype.flat
1022+ }
1023+
1024+ /**
1025+ * Move a file or folder.
1026+ * @param {string } from source
1027+ * @param {string } to destination
1028+ * @param {WriteOptions } [options]
1029+ * @returns {Promise<Response[]> } Resolves with an array of creation responses.
9841030 * The first one will be the folder specified by "to".
9851031 * If it is a folder, the others will be creation responses from the contents in arbitrary order.
9861032 */
987- move ( from , to , options ) {
988- // TBD: Rewrite to detect folders not by url (ie remove areFolders)
989- if ( areFolders ( from , to ) ) {
990- return this . moveFolder ( from , to , options )
1033+ async move ( from , to , options ) {
1034+ const moveOptions = {
1035+ ... defaultWriteOptions ,
1036+ ... options
9911037 }
992- if ( areFiles ( from , to ) ) {
993- return this . moveFile ( from , to , options )
1038+ _checkInputs ( 'move' , from , to )
1039+
1040+ let fromItem = await this . get ( from )
1041+ // This check works only with a strict implementation of solid standards
1042+ // A bug in NSS prevents 'content-type' to be reported correctly in response to HEAD
1043+ // https://github.com/solid/node-solid-server/issues/454
1044+ // TBD: Obtain item type from the link header instead
1045+ const fromItemType = fromItem . url . endsWith ( '/' ) ? 'Container' : 'Resource'
1046+
1047+ if ( fromItemType === 'Resource' ) {
1048+ if ( to . endsWith ( '/' ) ) {
1049+ throw toFetchError ( new Error ( 'May not move file to a folder' ) )
1050+ }
1051+ const copyResponse = await this . _pasteFile ( fromItem , to , moveOptions )
1052+ await this . _removeItemWithLinks ( fromItem )
1053+ return copyResponse
1054+ } else if ( fromItemType === 'Container' ) {
1055+ // TBD: Add additional check to see if response can be converted to turtle
1056+ // and avoid this additional fetch.
1057+ if ( fromItem . headers . get ( 'content-type' ) !== 'text/turtle' ) {
1058+ fromItem = await this . get ( fromItem . url , { headers : { Accept : 'text/turtle' } } )
1059+ }
1060+ to = to . endsWith ( '/' ) ? to : `${ to } /`
1061+ return this . _moveFolderContents ( fromItem , to , moveOptions )
1062+ } else {
1063+ throw toFetchError ( new Error ( `Unrecognized item type ${ fromItemType } ` ) )
9941064 }
995- toFetchError ( new Error ( 'Cannot copy from a folder url to a file url or vice versa' ) )
9961065 }
9971066
9981067 /**
@@ -1042,7 +1111,7 @@ function catchError (callback) {
10421111}
10431112
10441113/**
1045- * Check Input Arguments for Copy
1114+ * Check Input Arguments for Copy & Move
10461115 * Used for error messages
10471116 * @param {string } op operation to tailor the message to
10481117 * @param {any } from
0 commit comments