Skip to content

Commit 86ecf42

Browse files
committed
Update scan_resource to get and save data to cache
Signed-off-by: Jono Yang <jyang@nexb.com>
1 parent 9c842b5 commit 86ecf42

3 files changed

Lines changed: 113 additions & 102 deletions

File tree

src/scancode/cli.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class WindowsError(Exception):
6565
from scancode import notice
6666
from scancode import print_about
6767
from scancode import Scanner
68+
from scancode import resource_cache
6869
from scancode.help import epilog_text
6970
from scancode.help import examples_text
7071
from scancode.interrupt import DEFAULT_TIMEOUT
@@ -1430,6 +1431,7 @@ def scan_resource(
14301431
results = {}
14311432
scan_errors = []
14321433
timings = {} if with_timing else None
1434+
scanners_to_run = []
14331435

14341436
if not with_threading:
14351437
interruptor = fake_interruptible
@@ -1440,8 +1442,24 @@ def scan_resource(
14401442
# and start returning values. The kill timeout is otherwise there
14411443
# as a gatekeeper for runaway processes.
14421444

1443-
# run each scanner in sequence in its own interruptible
1445+
# compute resource_cache_index
1446+
resource_cache_index = resource_cache.compute_resource_cache_index(location=location, path=path)
1447+
1448+
# update `results` with cached data or add scanner to scanners_to_run if no
1449+
# cache data is available
14441450
for scanner in scanners:
1451+
# get resource_cache_data
1452+
resource_cache_data = resource_cache.get_resource_cache_data(
1453+
resource_cache_index=resource_cache_index,
1454+
plugin_name=scanner.name
1455+
)
1456+
if resource_cache_data:
1457+
results.update(resource_cache_data)
1458+
else:
1459+
scanners_to_run.append(scanner)
1460+
1461+
# run each scanner in sequence in its own interruptible
1462+
for scanner in scanners_to_run:
14451463
if with_timing:
14461464
start = time()
14471465

@@ -1460,6 +1478,10 @@ def scan_resource(
14601478
# the return value of a scanner fun MUST be a mapping
14611479
if values_mapping:
14621480
results.update(values_mapping)
1481+
resource_cache.update_resource_cache_data(
1482+
resource_cache_index=resource_cache_index,
1483+
plugin_name=scanner.name
1484+
)
14631485

14641486
except Exception:
14651487
msg = 'ERROR: for scanner: ' + scanner.name + ':\n' + traceback.format_exc()

src/scancode/plugin_resource_cache.py

Lines changed: 0 additions & 101 deletions
This file was deleted.

src/scancode/resource_cache.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# ScanCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/nexB/scancode-toolkit for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
10+
import hashlib
11+
import json
12+
import os
13+
14+
from commoncode.hash import binary_chunks
15+
from scancode_config import scancode_cache_dir
16+
17+
18+
RESOURCE_CACHE_DIR = os.path.join(scancode_cache_dir, "resource_cache_index")
19+
20+
21+
def hasher_from_chunks(chunks):
22+
"""
23+
Return a sha256 hasher loaded with `chunks`.
24+
"""
25+
hasher = hashlib.sha256()
26+
for chunk in chunks:
27+
hasher.update(chunk)
28+
return hasher
29+
30+
31+
def compute_resource_cache_index(location, path):
32+
"""
33+
Compute resource_cache_index value for Resource at `location`.
34+
"""
35+
chunks = binary_chunks(location=location)
36+
sha256_hasher = hasher_from_chunks(chunks=chunks)
37+
# TODO: consider using filename instead of path
38+
sha256_hasher.update(path.encode('utf-8', 'surrogateescape'))
39+
return sha256_hasher.hexdigest()
40+
41+
42+
def get_resource_cache_directory_location(resource_cache_index):
43+
"""
44+
Return the location of the directory containing the cache files for a given
45+
`resource_cache_index` hexstring.
46+
"""
47+
# Split the hash into two subdirectories using the first two prefix pairs
48+
prefix1 = resource_cache_index[:2]
49+
prefix2 = resource_cache_index[2:4]
50+
directory_name = resource_cache_index[4:]
51+
return os.path.join(RESOURCE_CACHE_DIR, prefix1, prefix2, directory_name)
52+
53+
54+
def get_resource_cache_file_location(resource_cache_index, plugin_name):
55+
"""
56+
Return the location of the file containing the cached results of the scanner
57+
`plugin_name` for a resource keyed by `resource_cache_index` hexstring.
58+
"""
59+
resource_cache_directory_location = get_resource_cache_directory_location(resource_cache_index=resource_cache_index)
60+
return os.path.join(resource_cache_directory_location, plugin_name)
61+
62+
63+
def get_resource_cache_data(resource_cache_index, plugin_name):
64+
"""
65+
Return a mapping containing the results of scan plugin, `plugin_name`, for a
66+
resource keyed by `resource_cache_index` hexstring. If the cache file does
67+
not exist, an empty mapping is returned.
68+
"""
69+
resource_cache_file_location = get_resource_cache_file_location(
70+
resource_cache_index=resource_cache_index,
71+
plugin_name=plugin_name
72+
)
73+
if os.path.exists(resource_cache_file_location):
74+
with open(resource_cache_file_location) as f:
75+
return json.load(f)
76+
else:
77+
return {}
78+
79+
80+
def update_resource_cache_data(resource_cache_index, plugin_name, results):
81+
"""
82+
Update the resource cache with the `results` of the scanner `plugin_name`
83+
for the resource keyed by `resource_cache_index`.
84+
"""
85+
resource_cache_file_location = get_resource_cache_file_location(
86+
resource_cache_index=resource_cache_index,
87+
plugin_name=plugin_name
88+
)
89+
with open(resource_cache_file_location, 'w') as f:
90+
json.dump(results, f)

0 commit comments

Comments
 (0)