@@ -10,7 +10,9 @@ import project.Enso_Cloud.Data_Link
10
10
import project.Enso_Cloud.Enso_User.Enso_User
11
11
import project.Enso_Cloud.Errors.Enso_Cloud_Error
12
12
import project.Enso_Cloud.Internal.Enso_Path.Enso_Path
13
+ import project.Enso_Cloud.Internal.Enso_File_Write_Strategy
13
14
import project.Enso_Cloud.Internal.Existing_Enso_Asset.Existing_Enso_Asset
15
+ import project.Enso_Cloud.Internal.Existing_Enso_Asset.Asset_Cache
14
16
import project.Enso_Cloud.Internal.Utils
15
17
import project.Error.Error
16
18
import project.Errors.Common.Not_Found
@@ -21,19 +23,23 @@ import project.Errors.Time_Error.Time_Error
21
23
import project.Errors.Unimplemented.Unimplemented
22
24
import project.Network.HTTP.HTTP
23
25
import project.Network.HTTP.HTTP_Method.HTTP_Method
26
+ import project.Network.HTTP.Request_Error
24
27
import project.Network.URI.URI
25
28
import project.Nothing.Nothing
29
+ import project.Panic.Panic
26
30
import project.Runtime
27
31
import project.Runtime.Context
28
32
import project.System.Environment
29
33
import project.System.File.Data_Link_Access.Data_Link_Access
34
+ import project.System.File.File
30
35
import project.System.File.File_Access.File_Access
31
36
import project.System.File.Generic.File_Like.File_Like
32
37
import project.System.File.Generic.Writable_File.Writable_File
33
38
import project.System.File_Format_Metadata.File_Format_Metadata
34
39
import project.System.Input_Stream.Input_Stream
35
40
import project.System.Output_Stream.Output_Stream
36
41
from project.Data.Boolean import Boolean, False, True
42
+ from project.Data.Index_Sub_Range.Index_Sub_Range import Last
37
43
from project.Data.Text.Extensions import all
38
44
from project.Enso_Cloud.Public_Utils import get_required_field
39
45
from project.System.File_Format import Auto_Detect, Bytes, File_Format, Plain_Text_Format
@@ -178,8 +184,17 @@ type Enso_File
178
184
value. The value is returned from this method.
179
185
with_output_stream : Vector File_Access -> (Output_Stream -> Any ! File_Error) -> Any ! File_Error
180
186
with_output_stream self (open_options : Vector) action =
181
- _ = [open_options, action]
182
- Unimplemented.throw "Writing to Enso_Files is not currently implemented."
187
+ Context.Output.if_enabled disabled_message="Writing to an Enso_File is forbidden as the Output context is disabled." panic=False <|
188
+ open_as_data_link = (open_options.contains Data_Link_Access.No_Follow . not) && (Data_Link.is_data_link self)
189
+ if open_as_data_link then Data_Link.write_data_link_as_stream self open_options action else
190
+ if open_options.contains File_Access.Append then Unimplemented.throw "Enso_File currently does not support appending to a file. Instead you may read it, modify and then write the new contents." else
191
+ File_Access.ensure_only_allowed_options "with_output_stream" [File_Access.Write, File_Access.Create_New, File_Access.Truncate_Existing, File_Access.Create, Data_Link_Access.No_Follow] open_options <|
192
+ allow_existing = open_options.contains File_Access.Create_New . not
193
+ tmp_file = File.create_temporary_file "enso-cloud-write-tmp"
194
+ Panic.with_finalizer tmp_file.delete <|
195
+ perform_upload self allow_existing <|
196
+ result = tmp_file.with_output_stream [File_Access.Write] action
197
+ result.if_not_error [tmp_file, result]
183
198
184
199
## PRIVATE
185
200
ADVANCED
@@ -240,7 +255,6 @@ type Enso_File
240
255
Enso_Asset_Type.File -> File_Format.handle_format_missing_arguments format <|
241
256
read_with_format effective_format =
242
257
metadata = File_Format_Metadata.from self
243
- # TODO maybe avoid fetching asset id twice here? maybe move with_input_stream to asset? or helper?
244
258
self.with_input_stream [File_Access.Read] (stream-> effective_format.read_stream stream metadata)
245
259
246
260
if format != Auto_Detect then read_with_format format else
@@ -292,33 +306,40 @@ type Enso_File
292
306
# Remove secrets from the list - they are handled separately in `Enso_Secret.list`.
293
307
assets = list_assets self . filter f-> f.asset_type != Enso_Asset_Type.Secret
294
308
assets.map asset->
295
- # TODO we could cache the asset maybe? but for how long should we do that?
296
- Enso_File.Value (self.enso_path.resolve asset.name)
309
+ file = Enso_File.Value (self.enso_path.resolve asset.name)
310
+ Asset_Cache.update file asset
311
+ file
297
312
298
313
## UNSTABLE
299
314
GROUP Output
300
315
Creates a subdirectory in a specified directory.
301
316
create_directory : Text -> Enso_File
302
317
create_directory self (name : Text) =
318
+ effective_name = if name.ends_with "/" then name.drop (Last 1) else name
303
319
asset = Existing_Enso_Asset.get_asset_reference_for self
304
320
if asset.is_directory.not then Error.throw (Illegal_Argument.Error "Only directories can contain subdirectories.") else
305
321
parent_field = if self.is_current_user_root then [] else
306
- [["parentId", [asset.id]]]
307
- body = JS_Object.from_pairs [["title", name]]+parent_field
322
+ [["parentId", asset.id]]
323
+ body = JS_Object.from_pairs [["title", effective_name]]+parent_field
324
+ created_directory = Enso_File.Value (self.enso_path.resolve name)
325
+ Asset_Cache.invalidate created_directory
308
326
response = Utils.http_request_as_json HTTP_Method.Post Utils.directory_api body
309
- # TODO we could cache the asset maybe? but for how long should we do that?
310
327
created_asset = Existing_Enso_Asset.from_json response
311
328
created_asset.if_not_error <|
312
- Enso_File.Value (self.enso_path.resolve name)
329
+ Asset_Cache.update created_directory created_asset
330
+ created_directory
313
331
314
332
## UNSTABLE
315
333
GROUP Output
316
334
Deletes the file or directory.
317
335
delete : Nothing
318
336
delete self = if self.enso_path.is_root then Error.throw (Illegal_Argument.Error "The root directory cannot be deleted.") else
319
337
asset = Existing_Enso_Asset.get_asset_reference_for self
320
- uri = URI.from asset.asset_uri . add_query_argument "force" "true"
338
+ # The `if_not_error` here is a workaround for bug https://github.com/enso-org/enso/issues/9669
339
+ uri = asset.if_not_error <|
340
+ URI.from asset.asset_uri . add_query_argument "force" "true"
321
341
response = Utils.http_request HTTP_Method.Delete uri
342
+ if asset.is_directory then Asset_Cache.invalidate_subtree self else Asset_Cache.invalidate self
322
343
response.if_not_error Nothing
323
344
324
345
## ICON data_output
@@ -350,9 +371,9 @@ type Enso_File
350
371
destination file already exists. Defaults to `False`.
351
372
move_to : Writable_File -> Boolean -> Nothing ! File_Error
352
373
move_to self (destination : Writable_File) (replace_existing : Boolean = False) =
353
- _ = [destination, replace_existing]
354
- Context.Output.if_enabled disabled_message="File moving is forbidden as the Output context is disabled." panic=False <|
355
- Unimplemented.throw "Enso_File.move_to is not implemented"
374
+ # TODO we could have a fast path if Cloud will support renaming files
375
+ generic_copy self destination.file replace_existing . if_not_error <|
376
+ self.delete . if_not_error <| destination.file
356
377
357
378
## GROUP Operators
358
379
ICON folder
@@ -371,6 +392,11 @@ type Enso_File
371
392
to_text self -> Text =
372
393
"Enso_File "+self.path
373
394
395
+ ## PRIVATE
396
+ to_js_object : JS_Object
397
+ to_js_object self =
398
+ JS_Object.from_pairs [["type", "Enso_File"], ["constructor", "new"], ["path", self.path.to_text]]
399
+
374
400
## PRIVATE
375
401
list_assets (parent : Enso_File) -> Vector Existing_Enso_Asset =
376
402
Existing_Enso_Asset.get_asset_reference_for parent . list_directory
@@ -402,16 +428,53 @@ Enso_Asset_Type.from (that:Text) = case that of
402
428
403
429
## PRIVATE
404
430
File_Format_Metadata.from (that:Enso_File) =
405
- # TODO this is just a placeholder, until we implement the proper path
406
- path = Nothing
407
- case that.asset_type of
408
- Enso_Asset_Type.Data_Link ->
409
- File_Format_Metadata.Value path=path name=that.name content_type=Data_Link.data_link_content_type
410
- Enso_Asset_Type.Directory ->
411
- File_Format_Metadata.Value path=path name=that.name extension=(that.extension.catch _->Nothing)
412
- Enso_Asset_Type.File ->
413
- File_Format_Metadata.Value path=path name=that.name extension=(that.extension.catch _->Nothing)
414
- other_asset_type -> Error.throw (Illegal_Argument.Error "`File_Format_Metadata` is not available for: "+other_asset_type.to_text+".")
431
+ asset_type = that.asset_type.catch File_Error _->Nothing
432
+ if asset_type == Enso_Asset_Type.Data_Link then File_Format_Metadata.Value path=that.path name=that.name content_type=Data_Link.data_link_content_type else
433
+ File_Format_Metadata.Value path=that.path name=that.name extension=(that.extension.catch _->Nothing)
415
434
416
435
## PRIVATE
417
436
File_Like.from (that : Enso_File) = File_Like.Value that
437
+
438
+ ## PRIVATE
439
+ Writable_File.from (that : Enso_File) =
440
+ Writable_File.Value that Enso_File_Write_Strategy.instance
441
+
442
+ ## PRIVATE
443
+ upload_file (local_file : File) (destination : Enso_File) (replace_existing : Boolean) -> Enso_File =
444
+ result = perform_upload destination replace_existing [local_file, destination]
445
+ result.catch Enso_Cloud_Error error->
446
+ is_source_file_not_found = case error of
447
+ Enso_Cloud_Error.Connection_Error cause -> case cause of
448
+ request_error : Request_Error -> request_error.error_type == 'java.io.FileNotFoundException'
449
+ _ -> False
450
+ _ -> False
451
+ if is_source_file_not_found then Error.throw (File_Error.Not_Found local_file) else result
452
+
453
+ ## PRIVATE
454
+ `generate_request_body_and_result` should return a pair,
455
+ where the first element is the request body and the second element is the result to be returned.
456
+ It is executed lazily, only after all pre-conditions are successfully met.
457
+ perform_upload (destination : Enso_File) (allow_existing : Boolean) (~generate_request_body_and_result) =
458
+ parent_directory = destination.parent
459
+ if parent_directory.is_nothing then Error.throw (Illegal_Argument.Error "The root directory cannot be a destination for upload. The destination must be a path to a file.") else
460
+ parent_directory_asset = Existing_Enso_Asset.get_asset_reference_for parent_directory
461
+ # If the parent directory does not exist, we fail.
462
+ parent_directory_asset.if_not_error <|
463
+ existing_asset = Existing_Enso_Asset.get_asset_reference_for destination
464
+ . catch File_Error _->Nothing
465
+ if existing_asset.is_nothing.not && allow_existing.not then Error.throw (File_Error.Already_Exists destination) else
466
+ if existing_asset.is_nothing.not && existing_asset.asset_type != Enso_Asset_Type.File then Error.throw (Illegal_Argument.Error "The destination must be a path to a file, not "+existing_asset.asset_type.to_text+".") else
467
+ existing_asset_id = existing_asset.if_not_nothing <| existing_asset.id
468
+ file_name = destination.name
469
+ base_uri = URI.from Utils.files_api
470
+ . add_query_argument "parent_directory_id" parent_directory_asset.id
471
+ . add_query_argument "file_name" file_name
472
+ full_uri = case existing_asset_id of
473
+ Nothing -> base_uri
474
+ _ -> base_uri . add_query_argument "file_id" existing_asset_id
475
+ pair = generate_request_body_and_result
476
+ Asset_Cache.invalidate destination
477
+ response = Utils.http_request HTTP_Method.Post full_uri pair.first
478
+ response.if_not_error <|
479
+ Asset_Cache.update destination (Existing_Enso_Asset.from_json response)
480
+ pair.second
0 commit comments