@@ -14,9 +14,11 @@ import {
1414import { DocumentContentProvider } from "../providers/DocumentContentProvider" ;
1515import {
1616 cspAppsForUri ,
17+ CurrentBinaryFile ,
1718 currentFile ,
1819 CurrentFile ,
1920 currentFileFromContent ,
21+ CurrentTextFile ,
2022 isClassDeployed ,
2123 notNull ,
2224 outputChannel ,
@@ -25,6 +27,7 @@ import {
2527import { PackageNode } from "../explorer/models/packageNode" ;
2628import { NodeBase } from "../explorer/models/nodeBase" ;
2729import { RootNode } from "../explorer/models/rootNode" ;
30+ import { isText } from "istextorbinary" ;
2831
2932async function compileFlags ( ) : Promise < string > {
3033 const defaultFlags = config ( ) . compileFlags ;
@@ -46,7 +49,7 @@ async function compileFlags(): Promise<string> {
4649 * @param force If passed true, use server mtime.
4750 * @return mtime timestamp or -1.
4851 */
49- export async function checkChangedOnServer ( file : CurrentFile , force = false ) : Promise < number > {
52+ export async function checkChangedOnServer ( file : CurrentTextFile | CurrentBinaryFile , force = false ) : Promise < number > {
5053 if ( ! file || ! file . uri ) {
5154 return - 1 ;
5255 }
@@ -57,11 +60,16 @@ export async function checkChangedOnServer(file: CurrentFile, force = false): Pr
5760 . getDoc ( file . name )
5861 . then ( ( data ) => data . result )
5962 . then ( async ( { ts, content } ) => {
60- const fileContent = file . content . split ( / \r ? \n / ) ;
6163 const serverTime = Number ( new Date ( ts + "Z" ) ) ;
62- const sameContent = force
63- ? false
64- : content . every ( ( line , index ) => line . trim ( ) == ( fileContent [ index ] || "" ) . trim ( ) ) ;
64+ let sameContent : boolean ;
65+ if ( typeof file . content === "string" ) {
66+ const fileContent = file . content . split ( / \r ? \n / ) ;
67+ sameContent = force
68+ ? false
69+ : ( content as string [ ] ) . every ( ( line , index ) => line . trim ( ) == ( fileContent [ index ] || "" ) . trim ( ) ) ;
70+ } else {
71+ sameContent = force ? false : Buffer . compare ( content as Buffer , file . content ) === 0 ;
72+ }
6573 const mtime =
6674 force || sameContent ? serverTime : Math . max ( ( await vscode . workspace . fs . stat ( file . uri ) ) . mtime , serverTime ) ;
6775 return mtime ;
@@ -71,23 +79,43 @@ export async function checkChangedOnServer(file: CurrentFile, force = false): Pr
7179 return mtime ;
7280}
7381
74- async function importFile ( file : CurrentFile , ignoreConflict ?: boolean , skipDeplCheck = false ) : Promise < any > {
82+ async function importFile (
83+ file : CurrentTextFile | CurrentBinaryFile ,
84+ ignoreConflict ?: boolean ,
85+ skipDeplCheck = false
86+ ) : Promise < any > {
7587 const api = new AtelierAPI ( file . uri ) ;
7688 if ( file . name . split ( "." ) . pop ( ) . toLowerCase ( ) === "cls" && ! skipDeplCheck ) {
7789 if ( await isClassDeployed ( file . name , api ) ) {
7890 vscode . window . showErrorMessage ( `Cannot import ${ file . name } because it is deployed on the server.` , "Dismiss" ) ;
7991 return Promise . reject ( ) ;
8092 }
8193 }
82- const content = file . content . split ( / \r ? \n / ) ;
94+ let enc : boolean ;
95+ let content : string [ ] ;
96+ if ( typeof file . content === "string" ) {
97+ enc = false ;
98+ content = file . content . split ( / \r ? \n / ) ;
99+ } else {
100+ // Base64 encoding must be in chunk size multiple of 3 and within the server's potential 32K string limit
101+ // Output is 4 chars for each 3 input, so 24573/3*4 = 32764
102+ const chunkSize = 24573 ;
103+ let start = 0 ;
104+ content = [ ] ;
105+ enc = true ;
106+ while ( start < file . content . byteLength ) {
107+ content . push ( file . content . toString ( "base64" , start , start + chunkSize ) ) ;
108+ start += chunkSize ;
109+ }
110+ }
83111 const mtime = await checkChangedOnServer ( file ) ;
84112 ignoreConflict = ignoreConflict || mtime < 0 || ( file . uri . scheme === "file" && config ( "overwriteServerChanges" ) ) ;
85113 return api
86114 . putDoc (
87115 file . name ,
88116 {
89117 content,
90- enc : false ,
118+ enc,
91119 mtime,
92120 } ,
93121 ignoreConflict
@@ -112,14 +140,16 @@ async function importFile(file: CurrentFile, ignoreConflict?: boolean, skipDeplC
112140 } )
113141 . catch ( ( error ) => {
114142 if ( error ?. statusCode == 409 ) {
143+ const choices : string [ ] = [ ] ;
144+ if ( ! enc ) {
145+ choices . push ( "Compare" ) ;
146+ }
147+ choices . push ( "Overwrite on Server" , "Pull Server Changes" , "Cancel" ) ;
115148 return vscode . window
116149 . showErrorMessage (
117150 `Failed to import '${ file . name } ': The version of the file on the server is newer.
118151What do you want to do?` ,
119- "Compare" ,
120- "Overwrite on Server" ,
121- "Pull Server Changes" ,
122- "Cancel"
152+ ...choices
123153 )
124154 . then ( ( action ) => {
125155 switch ( action ) {
@@ -200,7 +230,7 @@ function updateOthers(others: string[], baseUri: vscode.Uri) {
200230 } ) ;
201231}
202232
203- export async function loadChanges ( files : CurrentFile [ ] ) : Promise < any > {
233+ export async function loadChanges ( files : ( CurrentTextFile | CurrentBinaryFile ) [ ] ) : Promise < any > {
204234 if ( ! files . length ) {
205235 return ;
206236 }
@@ -210,11 +240,19 @@ export async function loadChanges(files: CurrentFile[]): Promise<any> {
210240 api
211241 . getDoc ( file . name )
212242 . then ( async ( data ) => {
213- const content = ( data . result . content || [ ] ) . join ( file . eol === vscode . EndOfLine . LF ? "\n" : "\r\n" ) ;
214243 const mtime = Number ( new Date ( data . result . ts + "Z" ) ) ;
215244 workspaceState . update ( `${ file . uniqueId } :mtime` , mtime > 0 ? mtime : undefined ) ;
216245 if ( file . uri . scheme === "file" ) {
217- await vscode . workspace . fs . writeFile ( file . uri , new TextEncoder ( ) . encode ( content ) ) ;
246+ if ( Buffer . isBuffer ( data . result . content ) ) {
247+ // This is a binary file
248+ await vscode . workspace . fs . writeFile ( file . uri , data . result . content ) ;
249+ } else {
250+ // This is a text file
251+ const content = ( data . result . content || [ ] ) . join (
252+ ( file as CurrentTextFile ) . eol === vscode . EndOfLine . LF ? "\n" : "\r\n"
253+ ) ;
254+ await vscode . workspace . fs . writeFile ( file . uri , new TextEncoder ( ) . encode ( content ) ) ;
255+ }
218256 } else if ( file . uri . scheme === FILESYSTEM_SCHEMA || file . uri . scheme === FILESYSTEM_READONLY_SCHEMA ) {
219257 fileSystemProvider . fireFileChanged ( file . uri ) ;
220258 }
@@ -410,23 +448,37 @@ export async function namespaceCompile(askFlags = false): Promise<any> {
410448 ) ;
411449}
412450
413- function importFiles ( files : string [ ] , noCompile = false ) {
414- return Promise . all < CurrentFile > (
451+ async function importFiles ( files : string [ ] , noCompile = false ) {
452+ const toCompile : CurrentFile [ ] = [ ] ;
453+ await Promise . all < void > (
415454 files . map (
416- throttleRequests ( ( file : string ) =>
417- vscode . workspace . fs
418- . readFile ( vscode . Uri . file ( file ) )
419- . then ( ( contentBytes ) => new TextDecoder ( ) . decode ( contentBytes ) )
420- . then ( ( content ) => currentFileFromContent ( vscode . Uri . file ( file ) , content ) )
455+ throttleRequests ( ( file : string ) => {
456+ const uri = vscode . Uri . file ( file ) ;
457+ return vscode . workspace . fs
458+ . readFile ( uri )
459+ . then ( ( contentBytes ) => {
460+ if ( isText ( file , Buffer . from ( contentBytes ) ) ) {
461+ const textFile = currentFileFromContent ( uri , new TextDecoder ( ) . decode ( contentBytes ) ) ;
462+ toCompile . push ( textFile ) ;
463+ return textFile ;
464+ } else {
465+ return currentFileFromContent ( uri , Buffer . from ( contentBytes ) ) ;
466+ }
467+ } )
421468 . then ( ( curFile ) =>
422469 importFile ( curFile ) . then ( ( data ) => {
423470 outputChannel . appendLine ( "Imported file: " + curFile . fileName ) ;
424- return curFile ;
471+ return ;
425472 } )
426- )
427- )
473+ ) ;
474+ } )
428475 )
429- ) . then ( noCompile ? Promise . resolve : compile ) ;
476+ ) ;
477+
478+ if ( ! noCompile && toCompile . length > 0 ) {
479+ return compile ( toCompile ) ;
480+ }
481+ return ;
430482}
431483
432484export async function importFolder ( uri : vscode . Uri , noCompile = false ) : Promise < any > {
@@ -437,7 +489,7 @@ export async function importFolder(uri: vscode.Uri, noCompile = false): Promise<
437489 let globpattern = "*.{cls,inc,int,mac}" ;
438490 if ( cspAppsForUri ( uri ) . findIndex ( ( cspApp ) => uri . path . includes ( cspApp + "/" ) || uri . path . endsWith ( cspApp ) ) != - 1 ) {
439491 // This folder is a CSP application, so import all files
440- // We need to include eveything becuase CSP applications can
492+ // We need to include eveything because CSP applications can
441493 // include non-InterSystems files
442494 globpattern = "*" ;
443495 }
0 commit comments