tzh
2024-08-20 ca8393c352368485bcb8b277004fdb0c6cb572c6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# Copyright (C) 2018 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Functions to process archive files."""
 
import os
import tempfile
import tarfile
import urllib.parse
import zipfile
 
 
class ZipFileWithPermission(zipfile.ZipFile):
    """Subclassing Zipfile to preserve file permission.
 
    See https://bugs.python.org/issue15795
    """
 
    def _extract_member(self, member, targetpath, pwd):
        ret_val = super()._extract_member(member, targetpath, pwd)
 
        if not isinstance(member, zipfile.ZipInfo):
            member = self.getinfo(member)
        attr = member.external_attr >> 16
        if attr != 0:
            os.chmod(ret_val, attr)
        return ret_val
 
 
def unzip(archive_path, target_path):
    """Extracts zip file to a path.
 
    Args:
        archive_path: Path to the zip file.
        target_path: Path to extract files to.
    """
 
    with ZipFileWithPermission(archive_path) as zfile:
        zfile.extractall(target_path)
 
 
def untar(archive_path, target_path):
    """Extracts tar file to a path.
 
    Args:
        archive_path: Path to the tar file.
        target_path: Path to extract files to.
    """
 
    with tarfile.open(archive_path, mode='r') as tfile:
        tfile.extractall(target_path)
 
 
ARCHIVE_TYPES = {
    '.zip': unzip,
    '.tar.gz': untar,
    '.tar.bz2': untar,
    '.tar.xz': untar,
}
 
 
def is_supported_archive(url):
    """Checks whether the url points to a supported archive."""
    return get_extract_func(url) is not None
 
 
def get_extract_func(url):
    """Gets the function to extract an archive.
 
    Args:
        url: The url to the archive file.
 
    Returns:
        A function to extract the archive. None if not found.
    """
 
    parsed_url = urllib.parse.urlparse(url)
    filename = os.path.basename(parsed_url.path)
    for ext, func in ARCHIVE_TYPES.items():
        if filename.endswith(ext):
            return func
    return None
 
 
def download_and_extract(url):
    """Downloads and extracts an archive file to a temporary directory.
 
    Args:
        url: Url to download.
 
    Returns:
        Path to the temporary directory.
    """
 
    print('Downloading {}'.format(url))
    archive_file, _headers = urllib.request.urlretrieve(url)
 
    temporary_dir = tempfile.mkdtemp()
    print('Extracting {} to {}'.format(archive_file, temporary_dir))
    get_extract_func(url)(archive_file, temporary_dir)
 
    return temporary_dir
 
 
def find_archive_root(path):
    """Finds the real root of an extracted archive.
 
    Sometimes archives has additional layers of directories. This function tries
    to guess the right 'root' path by entering all single sub-directories.
 
    Args:
        path: Path to the extracted archive.
 
    Returns:
        The root path we found.
    """
    for root, dirs, files in os.walk(path):
        if files or len(dirs) > 1:
            return root
    return path