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
|
|
|
|