1- import { FileOps } from '@adafruit/ circuitpython-repl-js' ;
1+ import { FileOps } from 'circuitpython-repl-js' ;
22
33class FileTransferClient {
44 constructor ( connectionStatusCB , repl ) {
55 this . connectionStatus = connectionStatusCB ;
66 this . _dirHandle = null ;
7- this . _fileops = new FileOps ( repl ) ;
7+ this . _fileops = new FileOps ( repl , false ) ;
8+ this . _isReadOnly = null ;
89 }
910
1011 async readOnly ( ) {
11- return await this . _readOnly ( ) ;
12- }
13-
14- async _readOnly ( path = null ) {
1512 await this . _checkConnection ( ) ;
16-
17- let folderHandle = this . _dirHandle ;
18- if ( path ) {
19- folderHandle = await this . _getSubfolderHandle ( path ) ;
20- }
21-
22- return ! ( await this . _verifyPermission ( folderHandle ) ) ;
13+ return this . _isReadOnly ;
2314 }
2415
2516 async _checkConnection ( ) {
26- //
2717 if ( ! this . connectionStatus ( true ) ) {
2818 throw new Error ( "Unable to perform file operation. Not Connected." ) ;
2919 }
3020
31- if ( ! this . _dirHandle ) {
32- await this . loadDirHandle ( ) ;
33-
34- if ( this . _dirHandle ) {
35- const info = await this . versionInfo ( ) ;
36- console . log ( info ) ;
37- console . log ( "Found via REPL: " + this . _uid ) ;
38- if ( info ) {
39- console . log ( "Found via boot_out.txt: " + info . uid ) ;
40- } else {
41- console . log ( "Unable to read boot_out.txt" ) ;
42- }
43-
44- // TODO: This needs to be more reliable before we stop the user from continuing
45- if ( info && info . uid && this . _uid ) {
46- if ( this . _uid == info . uid ) {
47- console . log ( "UIDs found in REPL and boot_out.txt match!" ) ;
48- }
49- }
50-
51- if ( ! info === null ) {
52- // We're likely not in the root directory of the device because
53- // boot_out.txt probably wasn't found
54- }
55-
56- // TODO: Verify this is a circuitpython drive
57- // Perhaps check boot_out.txt, Certain structural elements, etc.
58- // Not sure how to verify it's the same device that we are using webserial for
59- // Perhaps we can match something in boot_out.txt to the device name
60-
61- // For now we're just going to trust the user
62- }
63- }
64-
65- if ( ! this . _dirHandle ) {
66- throw new Error ( "Unable to perform file operation. No Working Folder Selected." ) ;
67- }
68- }
69-
70- async loadSavedDirHandle ( ) {
71- try {
72- const savedDirHandle = await get ( 'usb-working-directory' ) ;
73- // Request permission to make it writable
74- if ( savedDirHandle && ( await this . _verifyPermission ( savedDirHandle ) ) ) {
75- // Check if the stored directory is available. It will fail if not.
76- await savedDirHandle . getFileHandle ( "boot_out.txt" ) ;
77- this . _dirHandle = savedDirHandle ;
78- return true ;
79- }
80- } catch ( e ) {
81- console . error ( "Unable to access boot_out.txt in saved directory handle:" , e ) ;
82- }
83- return false ;
84- }
85-
86- async loadDirHandle ( preferSaved = true ) {
87- if ( preferSaved ) {
88- const result = await loadSavedDirHandle ( ) ;
89- if ( ! result ) {
90- return true ;
91- }
92- }
93-
94- const dirHandle = await window . showDirectoryPicker ( { mode : 'readwrite' } ) ;
95- if ( dirHandle ) {
96- await set ( 'usb-working-directory' , dirHandle ) ;
97- this . _dirHandle = dirHandle ;
98- return true ;
21+ if ( this . _isReadOnly === null ) {
22+ this . _isReadOnly = await this . _fileops . isReadOnly ( ) ;
9923 }
100- return false ;
10124 }
10225
103- getWorkingDirectoryName ( ) {
104- if ( this . _dirHandle ) {
105- return this . _dirHandle . name ;
26+ async _checkWritable ( ) {
27+ if ( await this . readOnly ( ) ) {
28+ throw new Error ( "File System is Read Only." ) ;
10629 }
107- return null ;
108- }
109-
110- async _verifyPermission ( folderHandle ) {
111- const options = { mode : 'readwrite' } ;
112-
113- if ( await folderHandle . queryPermission ( options ) === 'granted' ) {
114- return true ;
115- }
116-
117- if ( await folderHandle . requestPermission ( options ) === 'granted' ) {
118- return true ;
119- }
120-
121- return false ;
12230 }
12331
12432 async readFile ( path , raw = false ) {
12533 await this . _checkConnection ( ) ;
126-
127- const [ folder , filename ] = this . _splitPath ( path ) ;
128-
129- try {
130- const folderHandle = await this . _getSubfolderHandle ( folder ) ;
131- const fileHandle = await folderHandle . getFileHandle ( filename ) ;
132- const fileData = await fileHandle . getFile ( ) ;
133-
134- return raw ? fileData : await fileData . text ( ) ;
135- } catch ( e ) {
34+ let contents = await this . _fileops . readFile ( path , raw ) ;
35+ if ( contents === null ) {
13636 return raw ? null : "" ;
13737 }
38+ return contents ;
13839 }
13940
140- async _checkWritable ( ) {
141- if ( await this . readOnly ( ) ) {
142- throw new Error ( "File System is Read Only." ) ;
143- }
144- }
145-
146- async writeFile ( path , offset , contents , modificationTime = null , raw = false ) {
41+ async writeFile ( path , offset , contents , modificationTime , raw = false ) {
14742 await this . _checkConnection ( ) ;
14843 await this . _checkWritable ( ) ;
14944
150- /*if (modificationTime) {
151- console.warn("Setting modification time not currently supported in USB Workflow.");
152- }*/
153-
15445 if ( ! raw ) {
15546 let encoder = new TextEncoder ( ) ;
15647 let same = contents . slice ( 0 , offset ) ;
@@ -161,162 +52,36 @@ class FileTransferClient {
16152 contents = contents . slice ( offset ) ;
16253 }
16354
164- const [ folder , filename ] = this . _splitPath ( path ) ;
165-
166- const folderHandle = await this . _getSubfolderHandle ( folder ) ;
167- const fileHandle = await folderHandle . getFileHandle ( filename , { create : true } ) ;
168-
169- const writable = await fileHandle . createWritable ( ) ;
170- if ( offset > 0 ) {
171- await writable . seek ( offset ) ;
172- }
173- await writable . write ( contents ) ;
174- await writable . close ( ) ;
55+ return await this . _fileops . writeFile ( path , contents , offset , modificationTime , raw ) ;
17556 }
17657
177- _splitPath ( path ) {
178- let pathParts = path . split ( "/" ) ;
179- const filename = pathParts . pop ( ) ;
180- const folder = pathParts . join ( "/" ) ;
181-
182- return [ folder , filename ] ;
183- }
184-
185- // Makes the directory and any missing parents
186- async makeDir ( path , modificationTime = null ) {
58+ async makeDir ( path , modificationTime = Date . now ( ) ) {
18759 await this . _checkConnection ( ) ;
18860 await this . _checkWritable ( ) ;
18961
190- if ( modificationTime ) {
191- console . warn ( "Setting modification time not currently supported in USB Workflow." ) ;
192- }
193-
194- const [ parentFolder , folderName ] = this . _splitPath ( path ) ;
195- const parentFolderHandle = await this . _getSubfolderHandle ( parentFolder , true ) ;
196-
197- for await ( const [ entryName , entryHandle ] of parentFolderHandle . entries ( ) ) {
198- if ( entryName === folderName ) {
199- throw new Error ( "Folder already exists." ) ;
200- }
201- }
202-
203- await parentFolderHandle . getDirectoryHandle ( folderName , { create : true } ) ;
204-
205- return true ;
62+ return await this . _fileops . makeDir ( path , modificationTime ) ;
20663 }
20764
20865 // Returns an array of objects, one object for each file or directory in the given path
209- async listDir ( path , subfolderHandle = null ) {
66+ async listDir ( path ) {
21067 await this . _checkConnection ( ) ;
211-
212- let contents = [ ] ;
213- if ( ! subfolderHandle ) {
214- subfolderHandle = await this . _getSubfolderHandle ( path ) ;
215- }
216-
217- // Get all files and folders in the folder
218- for await ( const [ filename , entryHandle ] of subfolderHandle . entries ( ) ) {
219- let result = null ;
220- if ( entryHandle . kind === 'file' ) {
221- result = await entryHandle . getFile ( ) ;
222- contents . push ( {
223- path : result . name ,
224- isDir : false ,
225- fileSize : result . size ,
226- fileDate : Number ( result . lastModified ) ,
227- } ) ;
228- } else if ( entryHandle . kind === 'directory' ) {
229- result = await entryHandle ;
230- contents . push ( {
231- path : result . name ,
232- isDir : true ,
233- fileSize : 0 ,
234- fileDate : null ,
235- } ) ;
236- }
237- }
238-
239- return contents ;
240- }
241-
242- async _getSubfolderHandle ( path , createIfMissing = false ) {
243- if ( ! path . length || path . substr ( - 1 ) != "/" ) {
244- path += "/" ;
245- }
246-
247- // Navigate to folder
248- let currentDirHandle = this . _dirHandle ;
249- const subfolders = path . split ( "/" ) . slice ( 1 , - 1 ) ;
250- let currentPath = "/" ;
251-
252- if ( subfolders . length ) {
253- for ( const subfolder of subfolders ) {
254- try {
255- if ( ( await this . _getItemKind ( currentDirHandle , subfolder ) ) === 'directory' ) {
256- currentDirHandle = await currentDirHandle . getDirectoryHandle ( subfolder , { create : ! this . readOnly ( ) && createIfMissing } ) ;
257- currentPath += subfolder + "/" ;
258- } else {
259- return currentDirHandle ;
260- }
261- } catch ( e ) {
262- if ( e . name === 'NotFoundError' ) {
263- throw new Error ( `Folder ${ subfolder } not found in ${ currentPath } ` ) ;
264- } else {
265- console . log ( e . name ) ;
266- throw e ;
267- }
268- }
269- }
270- }
271-
272- return currentDirHandle ;
273- }
274-
275- async _getItemKind ( directoryHandle , itemName ) {
276- for await ( const [ filename , entryHandle ] of directoryHandle . entries ( ) ) {
277- if ( filename === itemName ) {
278- return entryHandle . kind ;
279- }
280- }
281-
282- return null ;
68+ return await this . _fileops . listDir ( path ) ;
28369 }
28470
28571 // Deletes the file or directory at the given path. Directories must be empty.
28672 async delete ( path ) {
28773 await this . _checkConnection ( ) ;
28874 await this . _checkWritable ( ) ;
28975
290- const [ parentFolder , itemName ] = this . _splitPath ( path ) ;
291- const parentFolderHandle = await this . _getSubfolderHandle ( parentFolder ) ;
292-
293- await parentFolderHandle . removeEntry ( itemName ) ;
294-
295- return true ;
76+ return await this . _fileops . delete ( path ) ;
29677 }
29778
29879 // Moves the file or directory from oldPath to newPath.
29980 async move ( oldPath , newPath ) {
30081 await this . _checkConnection ( ) ;
30182 await this . _checkWritable ( ) ;
30283
303- // Check that this is a file and not a folder
304- const [ oldPathFolder , oldItemName ] = this . _splitPath ( oldPath ) ;
305- const oldPathHandle = await this . _getSubfolderHandle ( oldPathFolder ) ;
306- if ( await this . _getItemKind ( oldPathHandle , oldItemName ) == "directory" ) {
307- throw new Error ( "Folder moving is not supported." ) ;
308- }
309-
310- // Copy the fileby reading from the old path and writing to the new one
311- const fileData = await this . readFile ( oldPath , true ) ;
312- await this . writeFile ( newPath , 0 , fileData , null , true ) ;
313-
314- // Delete the old file
315- await this . delete ( oldPath ) ;
316-
317- console . warn ( `Attempting to Move from ${ oldPath } to ${ newPath } ` ) ;
318-
319- return true ;
84+ return await this . _fileops . move ( oldPath , newPath ) ;
32085 }
32186
32287 async versionInfo ( ) {
0 commit comments