3 # Copyright 2016 RIFT.IO Inc
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
25 class ArchiveError(Exception):
30 """ Get number of bytes of content within file hdl
31 Set the file position to original position before returning
34 Number of bytes in the hdl file object
37 hdl
.seek(0, os
.SEEK_END
)
44 class TarPackageArchive(object):
45 """ This class represents a package stored within a tar.gz archive file """
46 def __init__(self
, log
, tar_file_hdl
, mode
="r"):
48 self
._tar
_filehdl
= tar_file_hdl
51 self
._tarfile
= tarfile
.open(fileobj
=tar_file_hdl
, mode
=mode
)
56 def from_package(cls
, log
, pkg
, tar_file_hdl
, top_level_dir
=None):
57 """ Creates a TarPackageArchive from a existing Package
61 pkg - a DescriptorPackage instance
62 tar_file_hdl - a writeable file handle to write tar archive data
63 top_level_dir - (opt.) top level dir under which the archive will be extracted
66 A TarPackageArchive instance
69 def set_common_tarinfo_fields(tar_info
):
70 tar_info
.uid
= os
.getuid()
71 tar_info
.gid
= os
.getgid()
72 tar_info
.mtime
= time
.time()
73 tar_info
.uname
= "rift"
74 tar_info
.gname
= "rift"
76 archive
= TarPackageArchive(log
, tar_file_hdl
, mode
='w:gz')
78 for pkg_file
in pkg
.files
:
79 filename
= "%s/%s" % (top_level_dir
, pkg_file
) if top_level_dir
else pkg_file
80 tar_info
= tarfile
.TarInfo(name
=filename
)
81 tar_info
.type = tarfile
.REGTYPE
82 tar_info
.mode
= pkg
.get_file_mode(pkg_file
)
83 set_common_tarinfo_fields(tar_info
)
84 with pkg
.open(pkg_file
) as pkg_file_hdl
:
85 tar_info
.size
= get_size(pkg_file_hdl
)
86 archive
.tarfile
.addfile(tar_info
, pkg_file_hdl
)
88 for pkg_dir
in pkg
.dirs
:
89 dirname
= "%s/%s" % (top_level_dir
, pkg_dir
) if top_level_dir
else pkg_dir
90 tar_info
= tarfile
.TarInfo(name
=dirname
)
91 tar_info
.type = tarfile
.DIRTYPE
93 set_common_tarinfo_fields(tar_info
)
94 archive
.tarfile
.addfile(tar_info
)
96 archive
.load_archive()
102 return "TarPackageArchive(%s)" % self
._tar
_filehdl
108 """ Close the opened tarfile"""
109 if self
._tarfile
is not None:
110 self
._tarfile
.close()
113 def load_archive(self
):
114 self
._tar
_infos
= {info
.name
: info
for info
in self
._tarfile
.getmembers() if info
.name
}
122 """ The list of file members within the tar file """
123 return [name
for name
in self
._tar
_infos
if tarfile
.TarInfo
.isfile(self
._tar
_infos
[name
])]
125 def open_file(self
, rel_file_path
):
126 """ Opens a file within the archive as read-only, byte mode.
129 rel_file_path - The file path within the archive to open
132 A file like object (see tarfile.extractfile())
135 FileNotFoundError - The file could not be found within the archive.
136 ArchiveError - The file could not be opened for some generic reason.
138 if rel_file_path
not in self
._tar
_infos
:
139 raise FileNotFoundError("Could not find %s in tar file", rel_file_path
)
142 return self
._tarfile
.extractfile(rel_file_path
)
143 except tarfile
.TarError
as e
:
144 msg
= "Failed to read file {} from tarfile {}: {}".format(
145 rel_file_path
, self
._tar
_filehdl
, str(e
)
148 raise ArchiveError(msg
) from e
150 def create_package(self
):
151 """ Creates a Descriptor package from the archive contents """
152 pkg
= package
.DescriptorPackage
.from_package_files(self
._log
, self
.open_file
, self
.filenames
)
153 for pkg_file
in self
.filenames
:
154 pkg
.add_file(pkg_file
, self
._tar
_infos
[pkg_file
].mode
)