-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFileOrganizer.command
More file actions
executable file
·200 lines (177 loc) · 6.99 KB
/
Copy pathFileOrganizer.command
File metadata and controls
executable file
·200 lines (177 loc) · 6.99 KB
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
197
198
199
200
#!/bin/bash
# FileOrganizer.command — Sorts files into folders by type
# Compatible with macOS default bash 3.2+
# Works standalone, wrapped with Platypus, or scheduled via cron
#
# Directory resolution priority:
# 1. ORGANIZE_DIR environment variable
# 2. CLI argument ($1)
# 3. DIRECTORY in ~/.fileorganizer.conf
# 4. GUI folder picker (unless SILENT=true)
# ─── Configuration ───────────────────────────────────────────────────────────
CONFIG_FILE="$HOME/.fileorganizer.conf"
SILENT=false
LOG_FILE="$HOME/.fileorganizer.log"
DIRECTORY=""
# Parse config file safely (only accept known keys)
if [ -f "$CONFIG_FILE" ]; then
while IFS= read -r line; do
case "$line" in
\#*|"") continue ;;
esac
key="${line%%=*}"
value="${line#*=}"
case "$key" in
DIRECTORY) DIRECTORY="$value" ;;
SILENT) SILENT="$value" ;;
LOG_FILE) LOG_FILE="$value" ;;
esac
done < "$CONFIG_FILE"
fi
# Logging helper — always prints to stdout; also writes to log in silent mode
log_msg() {
echo "$1"
if [ "$SILENT" = "true" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE"
fi
}
# ─── Directory resolution ────────────────────────────────────────────────────
if [ -n "${ORGANIZE_DIR:-}" ] && [ -d "$ORGANIZE_DIR" ]; then
TARGET_DIR="$ORGANIZE_DIR"
elif [ $# -ge 1 ] && [ -d "$1" ]; then
TARGET_DIR="$1"
elif [ -n "$DIRECTORY" ] && [ -d "$DIRECTORY" ]; then
TARGET_DIR="$DIRECTORY"
elif [ "$SILENT" = "true" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Error: No directory configured. Set DIRECTORY in $CONFIG_FILE" >> "$LOG_FILE"
exit 1
else
TARGET_DIR=$(osascript -e '
tell application "Finder"
set theFolder to choose folder with prompt "Select a folder to organize:"
return POSIX path of theFolder
end tell
' 2>/dev/null) || {
osascript -e 'display dialog "No folder selected. Exiting." buttons {"OK"} default button "OK" with icon stop' 2>/dev/null
exit 0
}
fi
# Strip trailing slash
TARGET_DIR="${TARGET_DIR%/}"
if [ ! -d "$TARGET_DIR" ]; then
log_msg "Error: Invalid directory: $TARGET_DIR"
if [ "$SILENT" != "true" ]; then
osascript -e "display dialog \"Invalid directory.\" buttons {\"OK\"} default button \"OK\" with icon stop" 2>/dev/null
fi
exit 1
fi
log_msg "Organizing: $TARGET_DIR"
# ─── Category lookup ─────────────────────────────────────────────────────────
get_category() {
local ext
ext=$(echo "$1" | tr '[:upper:]' '[:lower:]')
case "$ext" in
jpg|jpeg|png|gif|bmp|svg|webp|tiff|tif|ico|heic|heif|raw|cr2|nef|arw|dng|orf|rw2|psd|ai|eps)
echo "Images" ;;
mp4|avi|mov|mkv|wmv|flv|webm|m4v|mpg|mpeg|3gp|ogv|vob|m2ts|m2v|ts)
echo "Videos" ;;
mp3|wav|aac|flac|ogg|m4a|wma|aiff|ape|opus|amr|m4b|mid|midi)
echo "Audio" ;;
pdf|doc|docx|xls|xlsx|ppt|pptx|odt|ods|odp|rtf|pages|numbers|key|tex|epub|mobi|xps|wpd)
echo "Documents" ;;
txt|md|markdown|csv|log|nfo)
echo "Text Files" ;;
json|xml|yaml|yml|toml|ini|cfg|conf|properties|env)
echo "Data Files" ;;
py|js|jsx|ts|tsx|html|htm|css|scss|sass|less|java|c|cpp|cc|cxx|h|hpp|swift|rb|php|sh|bash|zsh|fish|go|rs|kt|kts|scala|pl|pm|r|m|sql|lua|hs|elm|dart|vbs|ps1|bat|cmd|make|cmake|dockerfile|gradle|vue|svelte|zig|nim)
echo "Code Files" ;;
zip|rar|7z|tar|gz|bz2|xz|tgz|tbz2|txz|zst|lz|lzma|cab|cbr|cbz)
echo "Archives" ;;
dmg|iso|img|vmdk|vdi|vhd|vhdx|qcow2)
echo "Disk Images" ;;
stl|obj|fbx|blend|3ds|dae|gltf|glb|ply|step|stp|iges|igs|dwg|dxf|scad)
echo "3D Models" ;;
ttf|otf|woff|woff2|eot|fon)
echo "Fonts" ;;
exe|msi|app|pkg|deb|rpm|apk|ipa|aab)
echo "Executables" ;;
torrent)
echo "Torrents" ;;
db|sqlite|sqlite3|mdb|accdb)
echo "Databases" ;;
*)
echo "Other" ;;
esac
}
# ─── Main logic ──────────────────────────────────────────────────────────────
moved=0
for filepath in "$TARGET_DIR"/*; do
[ -e "$filepath" ] || continue
[ -f "$filepath" ] || continue
filename=$(basename "$filepath")
# Skip hidden files
case "$filename" in
.*) continue ;;
esac
# Get extension
case "$filename" in
*.*) ext="${filename##*.}" ;;
*) ext="" ;;
esac
# Determine category
if [ -z "$ext" ]; then
category="Other"
else
category=$(get_category "$ext")
fi
# Create category folder
category_dir="$TARGET_DIR/$category"
mkdir -p "$category_dir"
# Handle filename collisions
dest="$category_dir/$filename"
if [ -e "$dest" ]; then
base="${filename%.*}"
counter=1
if [ -z "$ext" ]; then
while [ -e "$category_dir/${filename}_${counter}" ]; do
counter=$((counter + 1))
done
dest="$category_dir/${filename}_${counter}"
else
while [ -e "$category_dir/${base}_${counter}.${ext}" ]; do
counter=$((counter + 1))
done
dest="$category_dir/${base}_${counter}.${ext}"
fi
fi
mv "$filepath" "$dest"
moved=$((moved + 1))
log_msg "Moved: $filename → $category/"
done
# ─── Summary ─────────────────────────────────────────────────────────────────
if [ "$moved" -eq 0 ]; then
log_msg "No files found to organize in $TARGET_DIR"
if [ "$SILENT" != "true" ]; then
osascript -e "display dialog \"No files found to organize.\" buttons {\"OK\"} default button \"OK\" with icon note" 2>/dev/null
fi
exit 0
fi
# Build summary of category counts
summary_lines=""
for dir in "$TARGET_DIR"/*/; do
[ -d "$dir" ] || continue
dname=$(basename "$dir")
count=$(find "$dir" -maxdepth 1 -type f | wc -l | tr -d ' ')
if [ "$count" -gt 0 ]; then
summary_lines="${summary_lines}${dname}: ${count} file(s)\n"
fi
done
log_msg "Organized $moved file(s) in $TARGET_DIR"
if [ "$SILENT" = "true" ]; then
log_msg "--- Summary ---"
# Write summary to log (echo -e for newlines)
echo "$(date '+%Y-%m-%d %H:%M:%S') $(echo -e "$summary_lines")" >> "$LOG_FILE"
else
osascript -e "display dialog \"Organized $moved file(s):\n\n$summary_lines\" buttons {\"Done\"} default button \"Done\" with icon note" 2>/dev/null
fi
exit 0