|
#!/usr/bin/env bash |
|
# |
|
# make-real-recents — a Finder "Recents" that actually shows recent files. |
|
# |
|
# Finder's built-in Recents only shows files you have OPENED (it queries |
|
# kMDItemLastUsedDate), so a freshly downloaded or created file is invisible |
|
# in it until you open the file at least once. This script creates a Finder |
|
# Smart Folder that shows files ADDED, CREATED, MODIFIED, or OPENED within |
|
# the last N days — any recent interaction counts — scoped to the folders |
|
# where new files actually land. |
|
# |
|
# Usage: |
|
# make-real-recents [-d DAYS] [-n NAME] [SCOPE_DIR ...] |
|
# |
|
# -d DAYS How far back to look (default: 30) |
|
# -n NAME Name of the Smart Folder (default: "Recents for Reals") |
|
# SCOPE_DIR Folders to search. Defaults to ~/Desktop, ~/Documents, |
|
# ~/Downloads, and the iCloud Drive Downloads folder if present. |
|
# Scoping matters: searching your whole home folder drowns you |
|
# in noise from git checkouts, caches, and package managers. |
|
# |
|
# The Smart Folder is written to ~/Library/Saved Searches/ and opened in |
|
# Finder. To finish setup, drag it from the title bar (or the Saved Searches |
|
# folder) into the Finder sidebar. Re-running the script overwrites the |
|
# saved search in place, so tweaking -d or the scopes is safe. |
|
# |
|
# NOTE: Finder caches a saved search's definition once opened, and won't |
|
# re-read the file after it changes. If results look stale after re-running |
|
# this script, relaunch Finder: killall Finder |
|
# |
|
# Why not "everywhere except ~/dev"? Spotlight can't. Its query language |
|
# filters on file metadata only — file paths are not a queryable attribute |
|
# (kMDItemPath isn't indexed), and saved-search scopes are additive-only, |
|
# with no way to exclude a folder. To approximate an exclusion, enumerate |
|
# the folders you DO want: |
|
# |
|
# make-real-recents ~/Desktop ~/Documents ~/Downloads ~/Movies ~/Pictures |
|
# |
|
# (The blunt alternative — System Settings > Siri & Spotlight > Spotlight |
|
# Privacy — hides a folder from ALL Spotlight search, not just this one.) |
|
# |
|
set -euo pipefail |
|
|
|
DAYS=30 |
|
NAME="Recents for Reals" |
|
|
|
usage() { awk 'NR == 1 { next } !/^#/ { exit } { sub(/^# ?/, ""); print }' "$0"; exit "${1:-0}"; } |
|
|
|
while getopts ':d:n:h' opt; do |
|
case "$opt" in |
|
d) DAYS=$OPTARG ;; |
|
n) NAME=$OPTARG ;; |
|
h) usage ;; |
|
*) usage 1 >&2 ;; |
|
esac |
|
done |
|
shift $((OPTIND - 1)) |
|
|
|
case "$DAYS" in |
|
''|*[!0-9]*) echo "error: -d expects a positive integer, got '$DAYS'" >&2; exit 1 ;; |
|
esac |
|
|
|
# Default scopes: the places new files actually arrive. |
|
if [ "$#" -gt 0 ]; then |
|
scopes=("$@") |
|
else |
|
scopes=("$HOME/Desktop" "$HOME/Documents" "$HOME/Downloads") |
|
icloud_dl="$HOME/Library/Mobile Documents/com~apple~CloudDocs/Downloads" |
|
[ -d "$icloud_dl" ] && scopes+=("$icloud_dl") |
|
fi |
|
|
|
for dir in "${scopes[@]}"; do |
|
[ -d "$dir" ] || { echo "error: scope folder does not exist: $dir" >&2; exit 1; } |
|
done |
|
|
|
# A file is "actually recent" if ANY of these happened within the window: |
|
# kMDItemDateAdded = landed in its folder (downloads, moves) |
|
# kMDItemContentCreationDate = was created |
|
# kMDItemContentModificationDate = was modified |
|
# kMDItemLastUsedDate = was opened (all Apple's Recents checks) |
|
# MDSystemFile exclusion mirrors what Finder's own searches do. |
|
t="\$time.today(-$DAYS)" |
|
query="((kMDItemDateAdded >= $t) || (kMDItemContentCreationDate >= $t) || (kMDItemContentModificationDate >= $t) || (kMDItemLastUsedDate >= $t)) && (kMDItemSupportFileType != \"MDSystemFile\")" |
|
|
|
xml_escape() { printf '%s' "$1" | sed -e 's/&/\&/g' -e 's/</\</g' -e 's/>/\>/g'; } |
|
|
|
query_xml=$(xml_escape "$query") |
|
scopes_xml="" |
|
for dir in "${scopes[@]}"; do |
|
scopes_xml+=" <string>$(xml_escape "$dir")</string>"$'\n' |
|
done |
|
|
|
out_dir="$HOME/Library/Saved Searches" |
|
out_file="$out_dir/$NAME.savedSearch" |
|
mkdir -p "$out_dir" |
|
|
|
cat > "$out_file" <<EOF |
|
<?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"> |
|
<dict> |
|
<key>CompatibleVersion</key> |
|
<integer>1</integer> |
|
<key>RawQuery</key> |
|
<string>$query_xml</string> |
|
<key>RawQueryDict</key> |
|
<dict> |
|
<key>FinderFilesOnly</key> |
|
<true/> |
|
<key>RawQuery</key> |
|
<string>$query_xml</string> |
|
<key>SearchScopes</key> |
|
<array> |
|
$scopes_xml </array> |
|
<key>UserQuery</key> |
|
<string></string> |
|
</dict> |
|
<key>SearchCriteria</key> |
|
<dict> |
|
<key>FXScopeArrayOfPaths</key> |
|
<array> |
|
$scopes_xml </array> |
|
</dict> |
|
</dict> |
|
</plist> |
|
EOF |
|
|
|
plutil -lint "$out_file" > /dev/null |
|
|
|
echo "Created Smart Folder: $out_file" |
|
echo " looking back: $DAYS days" |
|
printf ' scope: %s\n' "${scopes[@]}" |
|
echo |
|
echo "Opening it in Finder. To keep it handy, drag the little folder icon" |
|
echo "in the window's title bar into the Finder sidebar." |
|
|
|
open "$out_file" |