-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathignore_folders_in_spotlight.py
executable file
·196 lines (154 loc) · 6.05 KB
/
ignore_folders_in_spotlight.py
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#!/usr/bin/env python3
"""
Usage:
sudo python3 ignore_folders_in_spotlight.py [-n NAME] [-s SPOTLIGHT_PLIST_PATH] [PATH]
This script will tell Spotlight to ignore any folder in/under PATH
that's named NAME.
If PATH is omitted, it uses the current working directory.
Written by Alex Chan.
See https://alexwlchan.net/2021/08/ignore-lots-of-folders-in-spotlight/
"""
# TODO
import argparse
import datetime
import os
import subprocess
import sys
import xml.etree.ElementTree as ET
# The list of directory names that should be ignored, e.g. ["node_modules", "target"]
IGNORE_DIRECTORIES = []
# Path to the Spotlight plist on Catalina
SPOTLIGHT_PLIST_PATH = "/System/Volumes/Data/.Spotlight-V100/VolumeConfiguration.plist"
def get_dir_paths_under(root):
"""
Generates the paths of every directory under ``root``.
"""
for dirpath, dirnames, _ in os.walk(root):
for name in dirnames:
yield os.path.abspath(os.path.join(root, dirpath, name))
def create_backup_of_spotlight_plist():
"""
Save a backup of the Spotlight configuration to the Desktop before
we start.
This gives us a way to rollback, and also is a nondestructive check
that we have the right permissions to modify the Spotlight config.
"""
now = datetime.datetime.now().strftime("%Y-%m-%d.%H-%M-%S")
backup_path = os.path.join(
os.environ["HOME"], "Desktop", f"Spotlight-V100.VolumeConfiguration.{now}.plist"
)
try:
subprocess.check_call(["cp", SPOTLIGHT_PLIST_PATH, backup_path])
except subprocess.CalledProcessError as err:
print(f"*** Could not create backup of Spotlight configuration",
file=sys.stderr)
print(f"*** You need to run this script with 'sudo'", file=sys.stderr)
sys.exit(1)
print("*** Created backup of Spotlight configuration",
file=sys.stderr)
print("*** If this script goes wrong or you want to revert, run:", file=sys.stderr)
print("***",
file=sys.stderr)
print(
f"*** sudo cp {backup_path} {SPOTLIGHT_PLIST_PATH}", file=sys.stderr)
print("*** sudo launchctl stop com.apple.metadata.mds", file=sys.stderr)
print("*** sudo launchctl start com.apple.metadata.mds", file=sys.stderr)
print("***",
file=sys.stderr)
def get_paths_to_ignore(root):
"""
Generates a list of paths to ignore in Spotlight.
"""
for path in get_dir_paths_under(root):
if os.path.basename(path) in IGNORE_DIRECTORIES:
# Check this path isn't going to be ignored by a parent path.
#
# e.g. consider the path
#
# ./dotorg/node_modules/koa/node_modules
#
# We're also going to ignore the path
#
# ./dotorg/node_modules
#
# so adding an ignore for this deeper path is unnecessary.
relative_parts = os.path.relpath(path, root).split("/")
this_name = relative_parts.pop(-1)
if any(parent_dir in IGNORE_DIRECTORIES for parent_dir in relative_parts):
continue
yield path
def get_current_ignores():
"""
Returns a list of paths currently ignored by Spotlight.
"""
output = subprocess.check_output([
"plutil",
# Get the value of the "Exclusions" key as XML
"-extract", "Exclusions", "xml1",
# Send the result to stdout
"-o", "-",
SPOTLIGHT_PLIST_PATH
])
# The result of this call will look something like:
#
#
# <?xml version="1.0" encoding="UTF-8"?>
# <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
# <plist version="1.0">
# <array>
# <string>/Users/alexwlchan/repos/pipeline/target</string>
# <string>/Users/alexwlchan/repos/pipeline/node_modules</string>
# </array>
# </plist>
#
return {s.text for s in ET.fromstring(output).iter("string")}
def ignore_path_in_spotlight(path):
"""
Ignore a path in Spotlight, if it's not already ignored.
"""
already_ignored = get_current_ignores()
if path in already_ignored:
return
subprocess.check_call([
"plutil",
# Insert at the end of the Exclusions list
"-insert", f"Exclusions.{len(already_ignored)}",
# The path to exclude
"-string", os.path.abspath(path),
# Path to the Spotlight plist on Catalina
SPOTLIGHT_PLIST_PATH
])
if __name__ == '__main__':
# parse cli arguments
parser = argparse.ArgumentParser(description="Ignore a path in Spotlight")
parser.add_argument("-n", "--name", help="Name of the path to ignore")
parser.add_argument("-s", "--spotlight-plist-path",
help="Path to the Spotlight plist, default: /System/Volumes/Data/.Spotlight-V100/VolumeConfiguration.plist")
parser.add_argument("path", help="Path to ignore", default=".")
args = parser.parse_args()
if args.name:
IGNORE_DIRECTORIES.append(args.name)
if args.spotlight_plist_path:
SPOTLIGHT_PLIST_PATH = args.spotlight_plist_path
if args.path:
root = args.path
else:
root = "."
if not IGNORE_DIRECTORIES:
print("*** You need to specify a name for the path to ignore by -n NAME", file=sys.stderr)
sys.exit(1)
if not os.path.exists(SPOTLIGHT_PLIST_PATH):
print(
f"*** Could not find Spotlight plist at {SPOTLIGHT_PLIST_PATH}, "
"please set it by -s SPOTLIGHT_PLIST_PATH", file=sys.stderr)
sys.exit(1)
if not os.path.exists(root):
print(f"*** Could not find path {root}", file=sys.stderr)
sys.exit(1)
create_backup_of_spotlight_plist()
print("*** The following paths will be ignored by Spotlight")
for path in get_paths_to_ignore(root=root):
ignore_path_in_spotlight(path)
print(path)
subprocess.check_call("launchctl stop com.apple.metadata.mds", shell=True)
subprocess.check_call("launchctl start com.apple.metadata.mds", shell=True)