Passed
Push — develop ( 3c901c...de3ad7 )
by Dean
02:43
created

CacheManager   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 146
Duplicated Lines 0 %

Test Coverage

Coverage 12.07%
Metric Value
dl 0
loc 146
ccs 7
cts 58
cp 0.1207
rs 10
wmc 23

5 Methods

Rating   Name   Duplication   Size   Complexity  
B execute() 0 30 5
B get_paths() 0 45 6
A process() 0 8 3
B sync() 0 29 6
B discover() 0 38 6
1 1
from plugin.core.environment import Environment
2 1
from plugin.core.libraries.constants import CONTENTS_PATH
3 1
from plugin.core.libraries.helpers import StorageHelper, SystemHelper
4
5 1
import filecmp
6 1
import logging
7 1
import os
8
9 1
log = logging.getLogger(__name__)
10
11
12 1
class CacheManager(object):
13 1
    @classmethod
14 1
    def sync(cls, cache_path=None):
15
        """Synchronize native libraries cache, adding/updating/removing items to match bundled libraries.
16
17
        :param cache_path: Directory to store cached libraries
18
        :type cache_path: str
19
20
        :rtype: str
21
        """
22
23
        # Set `cache_path` default
24
        if cache_path is None:
25
            cache_path = os.path.join(Environment.path.plugin_data, 'Libraries')
26
27
        # Retrieve paths for system libraries
28
        source, destination = cls.get_paths(cache_path)
29
30
        if not source or not destination:
31
            return None
32
33
        # Compare directories, discover tasks
34
        changes = filecmp.dircmp(source, destination)
35
        tasks = cls.discover(changes)
36
37
        # Execute tasks
38
        if tasks and not cls.execute(source, destination, tasks):
39
            return None
40
41
        return cache_path
42
43 1
    @classmethod
44 1
    def discover(cls, changes, base_path='', tasks=None):
45
        """"Discover actions required to update the cache.
46
47
        :param changes: Changes between bundle + cache directories
48
        :type changes: filecmp.dircmp
49
50
        :param base_path: Current directory of changes
51
        :type base_path: str
52
53
        :param tasks: Current tasks
54
        :type tasks: list or None
55
56
        :rtype: list
57
        """
58
        if tasks is None:
59
            tasks = []
60
61
        def process(action, names):
62
            for name in names:
63
                # Ignore "*.pyc" files
64
                if name.endswith('.pyc'):
65
                    continue
66
67
                # Append task to list
68
                tasks.append((action, os.path.join(base_path, name)))
69
70
        # Create tasks from `changes`
71
        process('add', changes.left_only)
72
        process('delete', changes.right_only)
73
        process('update', changes.diff_files)
74
75
        # Process sub directories
76
        for name, child in changes.subdirs.items():
77
            cls.discover(child, os.path.join(base_path, name), tasks)
78
79
        # Tasks retrieved
80
        return tasks
81
82 1
    @classmethod
83
    def execute(cls, source, destination, tasks):
84
        """Execute tasks on directories
85
86
        :param source: Native libraries source directory
87
        :type source: str
88
89
        :param destination: Native libraries cache directory
90
        :type destination: str
91
92
        :param tasks: Tasks to execute
93
        :type tasks: list
94
95
        :rtype: bool
96
        """
97
        success = True
98
99
        for action, path in tasks:
100
            if action in ['add', 'update']:
101
                action_success = StorageHelper.copy(os.path.join(source, path), os.path.join(destination, path))
102
            elif action == 'delete':
103
                action_success = StorageHelper.delete(os.path.join(destination, path))
104
            else:
105
                log.warn('Unknown task: %r - %r', action, path)
106
                action_success = False
107
108
            if not action_success:
109
                success = False
110
111
        return success
112
113 1
    @staticmethod
114
    def get_paths(cache_path):
115
        """Retrieve system-specific native libraries source + destination path
116
117
        :param cache_path: Directory to store cached libraries
118
        :type cache_path: str
119
120
        :rtype: (str, str)
121
        """
122
        # Retrieve system details
123
        system = SystemHelper.name()
124
        architecture = SystemHelper.architecture()
125
126
        if not architecture:
127
            return None, None
128
129
        # Build list of acceptable architectures
130
        architectures = [architecture]
131
132
        if architecture == 'i686':
133
            # Fallback to i386
134
            architectures.append('i386')
135
136
        # Look for matching libraries
137
        for arch in architectures + ['universal']:
138
            # Build source path
139
            source = os.path.join(CONTENTS_PATH, 'Libraries', system, arch)
140
141
            # Ensure `source` directory exists
142
            if not os.path.exists(source):
143
                continue
144
145
            # Build path for native dependencies
146
            destination = os.path.join(cache_path, system, arch)
147
148
            # Ensure `destination` directory has been created
149
            if not StorageHelper.create_directories(destination):
150
                # Directory couldn't be created
151
                return None, None
152
153
            return source, destination
154
155
        # No libraries could be found
156
        log.error('Unable to find native libraries for platform (name: %r, architecture: %r)', system, architecture)
157
        return None, None
158