Skip to content

Commit 3523e92

Browse files
authored
Merge pull request #6 from heliumdatacommons/5-irods-ref-resource
5 irods ref resource
2 parents 16d0ef1 + 72a5357 commit 3523e92

15 files changed

Lines changed: 285 additions & 96 deletions

hs_core/hydroshare/resource.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ def create_resource(
295295
resource_type, owner, title,
296296
edit_users=None, view_users=None, edit_groups=None, view_groups=None,
297297
keywords=(), metadata=None, extra_metadata=None,
298-
files=(), source_names=[], fed_res_path='', move=False,
298+
files=(), source_names=[], source_sizes=[], fed_res_path='', move=False, is_file_reference=False,
299299
create_metadata=True,
300300
create_bag=True, unpack_file=False, **kwargs):
301301
"""
@@ -341,11 +341,16 @@ def create_resource(
341341
:param files: list of Django File or UploadedFile objects to be attached to the resource
342342
:param source_names: a list of file names from a federated zone to be
343343
used to create the resource in the federated zone, default is empty list
344+
:param source_sizes: a list of file sizes corresponding to source_names if if_file_reference is True; otherwise,
345+
it is not of any use and should be empty.
344346
:param fed_res_path: the federated zone path in the format of
345347
/federation_zone/home/localHydroProxy that indicate where the resource
346348
is stored, default is empty string
347349
:param move: a value of False or True indicating whether the content files
348350
should be erased from the source directory. default is False.
351+
:param is_file_reference: a value of False or True indicating whether the files stored in
352+
source_files are references to external files without being physically stored in
353+
HydroShare internally. default is False.
349354
:param create_bag: whether to create a bag for the newly created resource or not.
350355
By default, the bag is created.
351356
:param unpack_file: boolean. If files contains a single zip file, and unpack_file is True,
@@ -392,7 +397,7 @@ def create_resource(
392397
resource.save()
393398

394399
# TODO: It would be safer to require an explicit zone path rather than harvesting file path
395-
elif len(source_names) > 0:
400+
elif len(source_names) > 0 and fed_res_path:
396401
fed_zone_home_path = utils.get_federated_zone_home_path(source_names[0])
397402
resource.resource_federation_path = fed_zone_home_path
398403
resource.save()
@@ -408,8 +413,8 @@ def create_resource(
408413
# few seconds. We may want to add the option to do this
409414
# asynchronously if the file size is large and would take
410415
# more than ~15 seconds to complete.
411-
add_resource_files(resource.short_id, *files, source_names=source_names,
412-
move=move)
416+
add_resource_files(resource.short_id, *files, source_names=source_names, source_sizes=source_sizes,
417+
move=move, is_file_reference=is_file_reference)
413418

414419
# by default resource is private
415420
resource_access = ResourceAccess(resource=resource)
@@ -625,6 +630,8 @@ def add_resource_files(pk, *files, **kwargs):
625630
resource = utils.get_resource_by_shortkey(pk)
626631
ret = []
627632
source_names = kwargs.pop('source_names', [])
633+
source_sizes = kwargs.pop('source_sizes', [])
634+
is_file_reference = kwargs.pop('is_file_reference', False)
628635

629636
if __debug__:
630637
assert(isinstance(source_names, list))
@@ -641,14 +648,24 @@ def add_resource_files(pk, *files, **kwargs):
641648
ret.append(utils.add_file_to_resource(resource, f, folder=folder))
642649

643650
if len(source_names) > 0:
651+
if len(source_names) != len(source_sizes):
652+
# if length is not equal, there is an issue with source_sizes input parameter, so it will not be
653+
# used by setting it to be empty
654+
source_sizes = []
655+
656+
idx = 0
644657
for ifname in source_names:
658+
s_size = source_sizes[idx] if source_sizes else 0
659+
idx += 1
645660
ret.append(utils.add_file_to_resource(resource, None,
646661
folder=folder,
647662
source_name=ifname,
648-
move=move))
649-
if not ret:
650-
# no file has been added, make sure data/contents directory exists if no file is added
651-
utils.create_empty_contents_directory(resource)
663+
source_size=s_size,
664+
move=move,
665+
is_file_reference=is_file_reference))
666+
667+
# make sure data/contents directory exists if not exist already
668+
utils.create_empty_contents_directory(resource)
652669

653670
return ret
654671

hs_core/hydroshare/utils.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -896,14 +896,16 @@ def resource_file_add_pre_process(resource, files, user, extract_metadata=False,
896896

897897
# TODO: make this part of resource api. resource --> self.
898898
def resource_file_add_process(resource, files, user, extract_metadata=False,
899-
source_names=[], **kwargs):
899+
source_names=[], source_sizes=[], is_file_reference=False, **kwargs):
900900

901901
from .resource import add_resource_files
902902
if __debug__:
903903
assert(isinstance(source_names, list))
904904
folder = kwargs.pop('folder', None)
905905
resource_file_objects = add_resource_files(resource.short_id, *files, folder=folder,
906-
source_names=source_names)
906+
source_names=source_names,
907+
source_sizes=source_sizes,
908+
is_file_reference=is_file_reference)
907909

908910
# receivers need to change the values of this dict if file validation fails
909911
# in case of file validation failure it is assumed the resource type also deleted the file
@@ -929,8 +931,8 @@ def create_empty_contents_directory(resource):
929931
istorage.session.run("imkdir", None, '-p', res_contents_dir)
930932

931933

932-
def add_file_to_resource(resource, f, folder=None, source_name='',
933-
move=False):
934+
def add_file_to_resource(resource, f, folder=None, source_name='', source_size=0,
935+
move=False, is_file_reference=False):
934936
"""
935937
Add a ResourceFile to a Resource. Adds the 'format' metadata element to the resource.
936938
:param resource: Resource to which file should be added
@@ -943,12 +945,16 @@ def add_file_to_resource(resource, f, folder=None, source_name='',
943945
disk, or from the federated zone directly where f is empty
944946
but source_name has the whole data object
945947
iRODS path in the federated zone
948+
:param source_size: the size of the reference file in source_name if is_file_reference is True; otherwise, it is
949+
set to 0 and useless.
946950
:param move: indicate whether the file should be copied or moved from private user
947951
account to proxy user account in federated zone; A value of False
948952
indicates copy is needed, a value of True indicates no copy, but
949953
the file will be moved from private user account to proxy user account.
950954
The default value is False.
951-
955+
:param is_file_reference: indicate whether the file being added is a reference to an external
956+
file stored in an external zone or URL. source_name will hold
957+
the reference file path or url
952958
:return: The identifier of the ResourceFile added.
953959
"""
954960

@@ -966,7 +972,8 @@ def add_file_to_resource(resource, f, folder=None, source_name='',
966972
elif source_name:
967973
try:
968974
# create from existing iRODS file
969-
ret = ResourceFile.create(resource, None, folder=folder, source=source_name, move=move)
975+
ret = ResourceFile.create(resource, None, folder=folder, source=source_name, source_size=source_size,
976+
is_file_reference=is_file_reference, move=move)
970977
except SessionException as ex:
971978
try:
972979
ret.delete()
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('hs_core', '0035_remove_deprecated_fields'),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name='resourcefile',
16+
name='reference_file_path',
17+
field=models.CharField(max_length=255, null=True, blank=True),
18+
),
19+
]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('hs_core', '0036_resourcefile_reference_file_path'),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name='resourcefile',
16+
name='reference_file_size',
17+
field=models.CharField(max_length=15, null=True, blank=True),
18+
),
19+
]

hs_core/models.py

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2618,10 +2618,10 @@ class ResourceFile(ResourceFileIRODSMixin):
26182618
fed_resource_file = models.FileField(upload_to=get_path, max_length=4096,
26192619
null=True, blank=True, storage=FedStorage())
26202620

2621-
# DEPRECATED: utilize resfile.set_storage_path(path) and resfile.storage_path.
2622-
# fed_resource_file_name_or_path = models.CharField(max_length=255, null=True, blank=True)
2623-
# DEPRECATED: use native size routine
2624-
# fed_resource_file_size = models.CharField(max_length=15, null=True, blank=True)
2621+
# This is used to hold the reference path to an external file, e.g., a logical iRODS
2622+
# path refering to a file stored in an external iRODS zone, a URL that points to an external file
2623+
reference_file_path = models.CharField(max_length=255, null=True, blank=True)
2624+
reference_file_size = models.CharField(max_length=15, null=True, blank=True)
26252625

26262626
# we are using GenericForeignKey to allow resource file to be associated with any
26272627
# CommonsShare defined LogicalFile types (e.g., GeoRasterFile, NetCdfFile etc)
@@ -2640,7 +2640,7 @@ def __str__(self):
26402640
return self.resource_file.name
26412641

26422642
@classmethod
2643-
def create(cls, resource, file, folder=None, source=None, move=False):
2643+
def create(cls, resource, file, folder=None, source=None, source_size=0, is_file_reference=False, move=False):
26442644
"""Create custom create method for ResourceFile model.
26452645
26462646
Create takes arguments that are invariant of storage medium.
@@ -2651,6 +2651,9 @@ def create(cls, resource, file, folder=None, source=None, move=False):
26512651
:param file: a File or a iRODS path to an existing file already copied.
26522652
:param folder: the folder in which to store the file.
26532653
:param source: an iRODS path in the same zone from which to copy the file.
2654+
:param source_size: the size of the referenced source is if_file_reference is True, otherwise, it is set to 0
2655+
and useless.
2656+
:param is_file_reference: if True, source will hold the reference path to an external file
26542657
:param move: if True, move the file rather than copying.
26552658
26562659
There are two main usages to this constructor:
@@ -2697,23 +2700,31 @@ def create(cls, resource, file, folder=None, source=None, move=False):
26972700
if file is None and source is not None:
26982701
if __debug__:
26992702
assert(isinstance(source, basestring))
2700-
# source is a path to an iRODS file to be copied here.
2701-
root, newfile = os.path.split(source) # take file from source path
2702-
# newfile is where it should be copied to.
2703-
target = get_resource_file_path(resource, newfile, folder=folder)
2704-
istorage = resource.get_irods_storage()
2705-
if not istorage.exists(source):
2706-
raise ValidationError("ResourceFile.create: source {} of copy not found"
2707-
.format(source))
2708-
if not move:
2709-
istorage.copyFiles(source, target)
2703+
2704+
if is_file_reference:
2705+
kwargs['resource_file'] = None
2706+
kwargs['fed_resource_file'] = None
2707+
kwargs['reference_file_path'] = source
2708+
kwargs['reference_file_size'] = str(source_size)
2709+
return ResourceFile.objects.create(**kwargs)
27102710
else:
2711-
istorage.moveFile(source, target)
2712-
if not istorage.exists(target):
2713-
raise ValidationError("ResourceFile.create: copy to target {} failed"
2714-
.format(target))
2715-
if move and istorage.exists(source):
2716-
raise ValidationError("ResourceFile.create: move did not work")
2711+
# source is a path to an iRODS file to be copied here.
2712+
root, newfile = os.path.split(source) # take file from source path
2713+
# newfile is where it should be copied to.
2714+
target = get_resource_file_path(resource, newfile, folder=folder)
2715+
istorage = resource.get_irods_storage()
2716+
if not istorage.exists(source):
2717+
raise ValidationError("ResourceFile.create: source {} of copy not found"
2718+
.format(source))
2719+
if not move:
2720+
istorage.copyFiles(source, target)
2721+
else:
2722+
istorage.moveFile(source, target)
2723+
if not istorage.exists(target):
2724+
raise ValidationError("ResourceFile.create: copy to target {} failed"
2725+
.format(target))
2726+
if move and istorage.exists(source):
2727+
raise ValidationError("ResourceFile.create: move did not work")
27172728
elif file is not None and source is None:
27182729
# file points to an existing iRODS file
27192730
# no need to verify whether the file exists in iRODS since the file
@@ -2766,11 +2777,13 @@ def size(self):
27662777
assert self.resource_file.name is None or \
27672778
self.resource_file.name == ''
27682779
return self.fed_resource_file.size
2769-
else:
2780+
elif not self.reference_file_path:
27702781
if __debug__:
27712782
assert self.fed_resource_file.name is None or \
27722783
self.fed_resource_file.name == ''
27732784
return self.resource_file.size
2785+
else:
2786+
return 0
27742787

27752788
# TODO: write unit test
27762789
@property
@@ -2874,6 +2887,8 @@ def short_path(self):
28742887
"""
28752888
if self.resource.is_federated:
28762889
folder, base = self.path_is_acceptable(self.fed_resource_file.name, test_exists=False)
2890+
elif self.reference_file_path:
2891+
return self.reference_file_path
28772892
else:
28782893
folder, base = self.path_is_acceptable(self.resource_file.name, test_exists=False)
28792894
if folder is not None:
@@ -2895,7 +2910,7 @@ def set_short_path(self, path):
28952910
if self.resource.is_federated:
28962911
self.resource_file = None
28972912
self.fed_resource_file = get_path(self, base)
2898-
else:
2913+
elif not self.reference_file_path:
28992914
self.resource_file = get_path(self, base)
29002915
self.fed_resource_file = None
29012916
self.save()

0 commit comments

Comments
 (0)