/**
 * This file is part of the vscode-remote-workspace distribution.
 * Copyright (c) Marcel Joachim Kloubert.
 *
 * vscode-remote-workspace is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, version 3.
 *
 * vscode-remote-workspace is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */ import * as Dropbox from 'dropbox' ; import * as FSExtra from 'fs-extra' ; const IsomorphicFetch = require ( 'isomorphic-fetch' ) ; import * as Moment from 'moment' ; import * as vscode from 'vscode' ; import * as vscode_helpers from 'vscode-helpers' ; import * as vscrw from '../extension' ; import * as vscrw_fs from '../fs' ; interface DropboxConnection { client : Dropbox . Dropbox ; } interface DropboxFileEntry { '.tag' : string ; name : string ; } interface DropboxFileStat extends vscode . FileStat { } const NO_CURSOR_YET = Symbol ( 'NO_CURSOR_YET' ) ; /**
 * Dropbox file system.
 */ export class DropboxFileSystem extends vscrw_fs . FileSystemBase { inheritdoc public async copy ( source , destination , options ) { await this . forConnection ( source , async ( conn ) => { const SRC_STAT = await this . statInner ( source ) ; const DEST_STAT = await this . tryGetStat ( source ) ; if ( false !== DEST_STAT ) { if ( options . overwrite ) { await conn . client . filesDeleteV2 ( { path : toDropboxPath ( destination . path ) , } ) ; } else { throw vscode . FileSystemError . FileExists ( destination ) ; } } await conn . client . filesCopyV2 ( { from_path : toDropboxPath ( source . path ) , to_path : toDropboxPath ( destination . path ) , } ) ; } ) ; } inheritdoc public async createDirectory ( uri ) { await this . forConnection ( uri , async ( conn ) => { const STAT = await this . tryGetStat ( uri ) ; if ( false !== STAT ) { throw vscode . FileSystemError . FileExists ( uri ) ; } await conn . client . filesCreateFolderV2 ( { autorename : false , path : toDropboxPath ( uri . path ) , } ) ; } ) ; } inheritdoc public async delete ( uri , options ) { await this . forConnection ( uri , async ( conn ) => { const STAT = await this . statInner ( uri ) ; if ( vscode . FileType . Directory === STAT . type ) { if ( ! options . recursive ) { const LIST = await this . list ( uri ) ; const HAS_SUB_DIRS = LIST . filter ( i => { return 'folder' === vscode_helpers . normalizeString ( i [ '.tag' ] ) ; } ) . length > 0 ; if ( HAS_SUB_DIRS ) { throw vscode . FileSystemError . NoPermissions ( uri ) ; } } } await conn . client . filesDeleteV2 ( { path : toDropboxPath ( uri . path ) } ) ; } ) ; } private async forConnection < TResult = any > ( uri , action ) { try { const CONN = await this . openConnection ( uri ) ; if ( action ) { return await Promise . resolve ( action ( CONN ) ) ; } } catch ( e ) { this . logger . trace ( e , 'fs.dropbox.DropboxFileSystem.forConnection()' ) ; throw e ; } } private list ( uri ) { return this . forConnection ( uri , async ( conn ) => { const ALL_ENTRIES = [ ] ; let cursor = NO_CURSOR_YET ; const NEXT_SEGMENT = async ( ) => { let result ; if ( cursor === NO_CURSOR_YET ) { result = await conn . client . filesListFolder ( { include_media_info : true , include_mounted_folders : true , path : toDropboxPath ( uri . path ) , recursive : false , } ) ; } else { result = await conn . client . filesListFolderContinue ( { cursor : < string > cursor , } ) ; } vscode_helpers . asArray ( result . entries ) . forEach ( e => { ALL_ENTRIES . push ( e ) ; } ) ; if ( result . has_more ) { cursor = result . cursor ; if ( ! vscode_helpers . isEmptyString ( cursor ) ) { await NEXT_SEGMENT ( ) ; } } } ; await NEXT_SEGMENT ( ) ; return ALL_ENTRIES ; } ) ; } private async openConnection ( uri ) { const PARAMS = vscrw . getUriParams ( uri ) ; let accessToken = false ; { let authFile = vscode_helpers . toStringSafe ( PARAMS [ 'auth' ] ) ; if ( ! vscode_helpers . isEmptyString ( authFile ) ) { authFile = vscrw . mapToUsersHome ( authFile ) ; if ( await vscode_helpers . isFile ( authFile ) ) { accessToken = ( await FSExtra . readFile ( authFile , 'utf8' ) ) . trim ( ) ; } } } if ( false === accessToken ) { accessToken = vscode_helpers . toStringSafe ( uri . authority ) . trim ( ) ; } if ( vscode_helpers . isEmptyString ( accessToken ) ) { accessToken = undefined ; } return { client : new Dropbox . Dropbox ( { accessToken : < string > accessToken } ) , } ; } inheritdoc public async readDirectory ( uri ) { const ENTRIES = [ ] ; const LIST = await this . list ( uri ) ; for ( const ITEM of LIST ) { if ( vscode_helpers . isEmptyString ( ITEM . name ) ) { continue ; } let type = vscode . FileType . Unknown ; const TAG = vscode_helpers . normalizeString ( ITEM [ '.tag' ] ) ; if ( 'file' === TAG ) { type = vscode . FileType . File ; } else if ( 'folder' === TAG ) { type = vscode . FileType . Directory ; } ENTRIES . push ( [ vscode_helpers . toStringSafe ( ITEM . name ) , type ] ) ; } return ENTRIES ; } inheritdoc public async readFile ( uri ) { return this . forConnection ( uri , async ( conn ) => { try { const DATA = await conn . client . filesDownload ( { path : toDropboxPath ( uri . path ) } ) ; return DATA [ 'fileBinary' ] ; } catch { throw vscode . FileSystemError . FileNotFound ( uri ) ; } } ) ; } param { vscode . ExtensionContext } context return { DropboxFileSystem } public static register ( context ) { const NEW_FS = new DropboxFileSystem ( ) ; try { context . subscriptions . push ( vscode . workspace . registerFileSystemProvider ( DropboxFileSystem . scheme , NEW_FS , { isCaseSensitive : false } ) ) ; } catch ( e ) { vscode_helpers . tryDispose ( NEW_FS ) ; throw e ; } return NEW_FS ; } inheritdoc public async rename ( oldUri , newUri , options ) { await this . forConnection ( oldUri , async ( conn ) => { const STAT = await this . tryGetStat ( newUri ) ; if ( false !== STAT ) { if ( options . overwrite ) { await conn . client . filesDeleteV2 ( { path : toDropboxPath ( newUri . path ) , } ) ; } else { throw vscode . FileSystemError . FileExists ( newUri ) ; } } await conn . client . filesMoveV2 ( { from_path : toDropboxPath ( oldUri . path ) , to_path : toDropboxPath ( newUri . path ) , } ) ; } ) ; } /**
     * Stores the name of the scheme.
     */ public static readonly scheme = 'dropbox' ; inheritdoc public async stat ( uri ) { return this . statInner ( uri ) ; } private async statInner ( uri ) { if ( '/' === vscrw . normalizePath ( uri . path ) ) { return { type : vscode . FileType . Directory , ctime : 0 , mtime : 0 , size : 0 , } ; } return this . forConnection ( uri , async ( conn ) => { try { const META = await conn . client . filesGetMetadata ( { include_media_info : false , path : toDropboxPath ( uri . path ) } ) ; const STAT = { ctime : undefined , mtime : undefined , size : undefined , type : vscode . FileType . Unknown , } ; const TAG = vscode_helpers . normalizeString ( META [ '.tag' ] ) ; if ( 'file' === TAG ) { const FILE_META = < Dropbox . files . FileMetadataReference > META ; STAT . type = vscode . FileType . File ; STAT . size = parseInt ( vscode_helpers . toStringSafe ( FILE_META . size ) . trim ( ) ) ; if ( ! vscode_helpers . isEmptyString ( FILE_META . server_modified ) ) { let mtime = Moment ( FILE_META . server_modified ) ; if ( mtime . isValid ( ) ) { mtime = vscode_helpers . asUTC ( mtime ) ; STAT . mtime = mtime . unix ( ) ; } } } else if ( 'folder' === TAG ) { const FOLDER_META = < Dropbox . files . FolderMetadataReference > META ; STAT . type = vscode . FileType . Directory ; } if ( isNaN ( STAT . mtime ) ) { STAT . mtime = 0 ; } STAT . ctime = STAT . mtime ; if ( isNaN ( STAT . size ) ) { STAT . size = 0 ; } return STAT ; } catch ( e ) { throw vscode . FileSystemError . FileNotFound ( uri ) ; } } ) ; } private async tryGetStat ( uri ) { let stat ; try { stat = await this . statInner ( uri ) ; } catch { stat = false ; } return stat ; } inheritdoc public watch ( uri , options ) { return { dispose : ( ) => { } } ; } inheritdoc public async writeFile ( uri , content , options ) { await this . forConnection ( uri , async ( conn ) => { this . throwIfWriteFileIsNotAllowed ( await this . tryGetStat ( uri ) , options , uri ) ; await conn . client . filesUpload ( { autorename : false , contents : vscrw . asBuffer ( content ) , mode : { '.tag' : 'overwrite' } , mute : false , path : toDropboxPath ( uri . path ) , } ) ; } ) ; } } function toDropboxPath ( p ) { p = vscrw . normalizePath ( p ) ; if ( '/' === p ) { p = '' ; } return p ; }