1# Copyright (c) 2012, Willow Garage, Inc.
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are met:
6#
7#     * Redistributions of source code must retain the above copyright
8#       notice, this list of conditions and the following disclaimer.
9#     * Redistributions in binary form must reproduce the above copyright
10#       notice, this list of conditions and the following disclaimer in the
11#       documentation and/or other materials provided with the distribution.
12#     * Neither the name of the Willow Garage, Inc. nor the names of its
13#       contributors may be used to endorse or promote products derived from
14#       this software without specific prior written permission.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26# POSSIBILITY OF SUCH DAMAGE.
27
28import hashlib
29import os
30import tempfile
31
32from .core import CachePermissionError
33
34try:
35    import cPickle as pickle
36except ImportError:
37    import pickle
38
39PICKLE_CACHE_EXT = '.pickle'
40
41
42def compute_filename_hash(key_filenames):
43    sha_hash = hashlib.sha1()
44    if isinstance(key_filenames, list):
45        for key in key_filenames:
46            sha_hash.update(key.encode())
47    else:
48        sha_hash.update(key_filenames.encode())
49    return sha_hash.hexdigest()
50
51
52def write_cache_file(source_cache_d, key_filenames, rosdep_data):
53    """
54    :param source_cache_d: directory to write cache file to
55    :param key_filenames: filename (or list of filenames) to be used in hashing
56    :param rosdep_data: dictionary of data to serialize as YAML
57    :returns: name of file where cache is stored
58    :raises: :exc:`OSError` if cannot write to cache file/directory
59    :raises: :exc:`IOError` if cannot write to cache file/directory
60    """
61    if not os.path.exists(source_cache_d):
62        os.makedirs(source_cache_d)
63    key_hash = compute_filename_hash(key_filenames)
64    filepath = os.path.join(source_cache_d, key_hash)
65    try:
66        write_atomic(filepath + PICKLE_CACHE_EXT, pickle.dumps(rosdep_data, 2), True)
67    except OSError as e:
68        raise CachePermissionError('Failed to write cache file: ' + str(e))
69    try:
70        os.unlink(filepath)
71    except OSError:
72        pass
73    return filepath
74
75
76def write_atomic(filepath, data, binary=False):
77    # write data to new file
78    fd, filepath_tmp = tempfile.mkstemp(prefix=os.path.basename(filepath) + '.tmp.', dir=os.path.dirname(filepath))
79
80    if (binary):
81        fmode = 'wb'
82    else:
83        fmode = 'w'
84
85    with os.fdopen(fd, fmode) as f:
86        f.write(data)
87        f.close()
88
89    try:
90        # switch file atomically (if supported)
91        os.rename(filepath_tmp, filepath)
92    except OSError:
93        # fall back to non-atomic operation
94        try:
95            os.unlink(filepath)
96        except OSError:
97            pass
98        try:
99            os.rename(filepath_tmp, filepath)
100        except OSError:
101            os.unlink(filepath_tmp)
102