import_bart.sh 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. #!/bin/bash
  2. # BART Import Script for Retro AIM Server
  3. # This script imports BART (Buddy ART) assets from an AIM client's bartcache
  4. # directory (usually found under %APPDATA%\acccore\caches\bart) into Retro AIM
  5. # Server via the management API.
  6. #
  7. # Compatible with macOS and Linux terminals
  8. set -e
  9. # Ensure we're using bash and have proper error handling
  10. if [ -z "$BASH_VERSION" ]; then
  11. echo "Error: This script requires bash" >&2
  12. exit 1
  13. fi
  14. # Default values
  15. API_BASE_URL="http://localhost:8080"
  16. VERBOSE=false
  17. DRY_RUN=false
  18. BART_TYPE=""
  19. TARGET_DIRS=()
  20. # Colors for output
  21. RED='\033[0;31m'
  22. GREEN='\033[0;32m'
  23. YELLOW='\033[1;33m'
  24. BLUE='\033[0;34m'
  25. NC='\033[0m' # No Color
  26. usage() {
  27. echo "Usage: $0 [OPTIONS] -t <type> <directory_path> [directory_path...]"
  28. echo ""
  29. echo "Import BART assets from bartcache directories into Retro AIM Server"
  30. echo ""
  31. echo "Arguments:"
  32. echo " directory_path Path to bartcache directory containing BART assets"
  33. echo " Directory should contain subdirectories named by BART type (0, 1, 2, etc.)"
  34. echo " Each type directory should contain files named by their hash"
  35. echo " Multiple directories can be specified for bulk import"
  36. echo ""
  37. echo "Options:"
  38. echo " -t, --type TYPE BART type to import (required)"
  39. echo " Valid types: buddy_icon_small, buddy_icon, status_str, arrive_sound,"
  40. echo " rich_text, superbuddy_icon, radio_station, buddy_icon_big,"
  41. echo " status_str_tod, current_av_track, depart_sound, im_chrome,"
  42. echo " im_sound, im_chrome_xml, im_chrome_immers, emoticon_set,"
  43. echo " encr_cert_chain, sign_cert_chain, gateway_cert"
  44. echo " -u, --url URL API base URL (default: http://localhost:8080)"
  45. echo " -v, --verbose Enable verbose output"
  46. echo " -d, --dry-run Show what would be uploaded without actually uploading"
  47. echo " -h, --help Show this help message"
  48. echo ""
  49. echo "Examples:"
  50. echo " $0 -t buddy_icon /Users/mike/Downloads/aim\\ barts/eigdbvye/bartcache"
  51. echo " $0 --type status_str --verbose --dry-run /path/to/bartcache"
  52. echo " $0 -t arrive_sound /path/to/bartcache/*"
  53. }
  54. log_info() {
  55. echo -e "${BLUE}[INFO]${NC} $1"
  56. }
  57. log_success() {
  58. echo -e "${GREEN}[SUCCESS]${NC} $1"
  59. }
  60. log_warning() {
  61. echo -e "${YELLOW}[WARNING]${NC} $1"
  62. }
  63. log_error() {
  64. echo -e "${RED}[ERROR]${NC} $1"
  65. }
  66. log_verbose() {
  67. if [ "$VERBOSE" = true ]; then
  68. echo -e "${BLUE}[VERBOSE]${NC} $1"
  69. fi
  70. }
  71. command_exists() {
  72. command -v "$1" >/dev/null 2>&1
  73. }
  74. is_hex_string() {
  75. local string="$1"
  76. # Check if string contains only hexadecimal characters (0-9, a-f, A-F)
  77. if [[ "$string" =~ ^[0-9a-fA-F]+$ ]]; then
  78. return 0
  79. else
  80. return 1
  81. fi
  82. }
  83. # Normalize path for cross-platform compatibility
  84. normalize_path() {
  85. local path="$1"
  86. # Remove trailing slashes and normalize the path
  87. echo "$path" | sed 's|/*$||'
  88. }
  89. get_bart_type_number() {
  90. case "$1" in
  91. "buddy_icon_small") echo "0" ;;
  92. "buddy_icon") echo "1" ;;
  93. "status_str") echo "2" ;;
  94. "arrive_sound") echo "3" ;;
  95. "rich_text") echo "4" ;;
  96. "superbuddy_icon") echo "5" ;;
  97. "radio_station") echo "6" ;;
  98. "buddy_icon_big") echo "12" ;;
  99. "status_str_tod") echo "13" ;;
  100. "current_av_track") echo "15" ;;
  101. "depart_sound") echo "96" ;;
  102. "im_chrome") echo "129" ;;
  103. "im_sound") echo "131" ;;
  104. "im_chrome_xml") echo "136" ;;
  105. "im_chrome_immers") echo "137" ;;
  106. "emoticon_set") echo "1024" ;;
  107. "encr_cert_chain") echo "1026" ;;
  108. "sign_cert_chain") echo "1027" ;;
  109. "gateway_cert") echo "1028" ;;
  110. *) echo "" ;;
  111. esac
  112. }
  113. get_bart_type_name() {
  114. case "$1" in
  115. "0") echo "buddy_icon_small" ;;
  116. "1") echo "buddy_icon" ;;
  117. "2") echo "status_str" ;;
  118. "3") echo "arrive_sound" ;;
  119. "4") echo "rich_text" ;;
  120. "5") echo "superbuddy_icon" ;;
  121. "6") echo "radio_station" ;;
  122. "12") echo "buddy_icon_big" ;;
  123. "13") echo "status_str_tod" ;;
  124. "15") echo "current_av_track" ;;
  125. "96") echo "depart_sound" ;;
  126. "129") echo "im_chrome" ;;
  127. "131") echo "im_sound" ;;
  128. "136") echo "im_chrome_xml" ;;
  129. "137") echo "im_chrome_immers" ;;
  130. "1024") echo "emoticon_set" ;;
  131. "1026") echo "encr_cert_chain" ;;
  132. "1027") echo "sign_cert_chain" ;;
  133. "1028") echo "gateway_cert" ;;
  134. *) echo "unknown_type_$1" ;;
  135. esac
  136. }
  137. check_prerequisites() {
  138. if ! command_exists curl; then
  139. log_error "curl is required but not installed"
  140. exit 1
  141. fi
  142. if ! command_exists jq; then
  143. log_warning "jq is not installed. JSON responses will not be formatted"
  144. fi
  145. }
  146. test_api() {
  147. log_info "Testing API connectivity..."
  148. local response
  149. local http_code
  150. if response=$(curl -s -w "%{http_code}" "$API_BASE_URL/bart?type=0" 2>/dev/null); then
  151. # Extract HTTP code from response (last 3 characters)
  152. http_code=$(echo "$response" | tail -c 4)
  153. if [ "$http_code" = "200" ]; then
  154. log_success "API is accessible"
  155. return 0
  156. else
  157. log_error "API returned HTTP $http_code"
  158. return 1
  159. fi
  160. else
  161. log_error "Failed to connect to API at $API_BASE_URL"
  162. return 1
  163. fi
  164. }
  165. upload_bart_asset() {
  166. local file_path="$1"
  167. local bart_type="$2"
  168. local hash="$3"
  169. log_verbose "Uploading $file_path (type: $bart_type, hash: $hash)"
  170. if [ "$DRY_RUN" = true ]; then
  171. log_info "[DRY RUN] Would upload: $file_path -> type=$bart_type, hash=$hash"
  172. return 0
  173. fi
  174. local response
  175. local http_code
  176. # Upload the file
  177. if response=$(curl -s -w "%{http_code}" \
  178. -X POST \
  179. -H "Content-Type: application/octet-stream" \
  180. --data-binary "@$file_path" \
  181. "$API_BASE_URL/bart/$hash?type=$bart_type" 2>/dev/null); then
  182. # Extract HTTP code from response (last 3 characters)
  183. http_code=$(echo "$response" | tail -c 4)
  184. # Extract response body (all except last 3 characters)
  185. # Use a more portable approach that works on both macOS and Linux
  186. response_length=$(echo "$response" | wc -c)
  187. response_body_length=$((response_length - 4))
  188. response_body=$(echo "$response" | head -c "$response_body_length")
  189. case "$http_code" in
  190. 201)
  191. log_success "Uploaded $hash"
  192. if [ "$VERBOSE" = true ] && command_exists jq; then
  193. echo "$response_body" | jq .
  194. fi
  195. return 0
  196. ;;
  197. 409)
  198. log_warning "Asset $hash already exists"
  199. return 0
  200. ;;
  201. 400)
  202. log_error "Bad request for $hash (type: $bart_type)"
  203. if command_exists jq; then
  204. echo "$response_body" | jq .
  205. else
  206. echo "$response_body"
  207. fi
  208. return 1
  209. ;;
  210. 413)
  211. log_error "File too large for $hash (type: $bart_type)"
  212. return 1
  213. ;;
  214. *)
  215. log_error "Upload failed for $hash (type: $bart_type) - HTTP $http_code"
  216. if command_exists jq; then
  217. echo "$response_body" | jq .
  218. else
  219. echo "$response_body"
  220. fi
  221. return 1
  222. ;;
  223. esac
  224. else
  225. log_error "Failed to upload $hash (type: $bart_type)"
  226. return 1
  227. fi
  228. }
  229. process_bart_type_directory() {
  230. local type_dir="$1"
  231. local bart_type="$2"
  232. local type_name="$3"
  233. log_info "Processing BART type $bart_type ($type_name)..."
  234. if [ ! -d "$type_dir" ]; then
  235. log_warning "Type directory $type_dir does not exist, skipping"
  236. return 0
  237. fi
  238. local file_count=0
  239. local success_count=0
  240. local error_count=0
  241. # Find all files in the type directory (excluding directories)
  242. # Use a more portable approach that works on both macOS and Linux
  243. # Store results in a temporary file to avoid subshell variable scoping issues
  244. local temp_file=$(mktemp)
  245. find "$type_dir" -maxdepth 1 -type f 2>/dev/null > "$temp_file"
  246. while read -r file_path; do
  247. if [ -f "$file_path" ]; then
  248. local filename=$(basename "$file_path")
  249. file_count=$((file_count + 1))
  250. log_verbose "Found file: $filename"
  251. # Validate that filename is a hexadecimal string
  252. if ! is_hex_string "$filename"; then
  253. log_warning "Skipping file '$filename' - filename is not a valid hexadecimal string"
  254. error_count=$((error_count + 1))
  255. continue
  256. fi
  257. if upload_bart_asset "$file_path" "$bart_type" "$filename"; then
  258. success_count=$((success_count + 1))
  259. else
  260. error_count=$((error_count + 1))
  261. fi
  262. fi
  263. done < "$temp_file"
  264. # Clean up temporary file
  265. rm -f "$temp_file"
  266. log_info "Type $bart_type ($type_name): $file_count files, $success_count successful, $error_count errors"
  267. return $error_count
  268. }
  269. process_directory() {
  270. local base_dir="$1"
  271. base_dir=$(normalize_path "$base_dir")
  272. if [ ! -d "$base_dir" ] && [ ! -f "$base_dir" ]; then
  273. log_error "Path $base_dir does not exist"
  274. return 1
  275. fi
  276. # Check if this is a single file
  277. if [ -f "$base_dir" ]; then
  278. log_info "Processing file: $base_dir"
  279. local filename=$(basename "$base_dir")
  280. # Validate that filename is a hexadecimal string
  281. if ! is_hex_string "$filename"; then
  282. log_error "Cannot process file '$filename' - filename is not a valid hexadecimal string"
  283. return 1
  284. fi
  285. if upload_bart_asset "$base_dir" "$BART_TYPE_NUMBER" "$filename"; then
  286. log_success "Successfully processed file: $base_dir"
  287. return 0
  288. else
  289. log_error "Failed to process file: $base_dir"
  290. return 1
  291. fi
  292. fi
  293. # For directories, show the BART type info
  294. log_info "Processing BART assets from directory: $base_dir"
  295. log_info "BART type: $BART_TYPE (type number: $BART_TYPE_NUMBER)"
  296. # Look for the specific type directory first (nested structure)
  297. local type_dir="$base_dir/$BART_TYPE_NUMBER"
  298. if [ -d "$type_dir" ]; then
  299. log_info "Found type directory: $type_dir"
  300. # Process the single type directory
  301. if process_bart_type_directory "$type_dir" "$BART_TYPE_NUMBER" "$BART_TYPE"; then
  302. local file_count=$(find "$type_dir" -maxdepth 1 -type f | wc -l)
  303. log_success "Successfully processed $file_count files for type $BART_TYPE"
  304. return 0
  305. else
  306. log_error "Failed to process files for type $BART_TYPE"
  307. return 1
  308. fi
  309. fi
  310. # If no type directory found, check if this is a flat structure (files directly in directory)
  311. local file_count=$(find "$base_dir" -maxdepth 1 -type f | wc -l)
  312. if [ $file_count -gt 0 ]; then
  313. log_info "Found flat file structure with $file_count files"
  314. # Process files directly in the directory
  315. if process_bart_type_directory "$base_dir" "$BART_TYPE_NUMBER" "$BART_TYPE"; then
  316. log_success "Successfully processed $file_count files for type $BART_TYPE"
  317. return 0
  318. else
  319. log_error "Failed to process files for type $BART_TYPE"
  320. return 1
  321. fi
  322. fi
  323. # No files or directories found
  324. log_error "No files found in $base_dir"
  325. log_error "Expected either:"
  326. log_error " - Nested structure: $base_dir/$BART_TYPE_NUMBER/"
  327. log_error " - Flat structure: files directly in $base_dir/"
  328. return 1
  329. }
  330. while [[ $# -gt 0 ]]; do
  331. case $1 in
  332. -t|--type)
  333. BART_TYPE="$2"
  334. shift 2
  335. ;;
  336. -u|--url)
  337. API_BASE_URL="$2"
  338. shift 2
  339. ;;
  340. -v|--verbose)
  341. VERBOSE=true
  342. shift
  343. ;;
  344. -d|--dry-run)
  345. DRY_RUN=true
  346. shift
  347. ;;
  348. -h|--help)
  349. usage
  350. exit 0
  351. ;;
  352. -*)
  353. log_error "Unknown option $1"
  354. usage
  355. exit 1
  356. ;;
  357. *)
  358. TARGET_DIRS+=("$1")
  359. shift
  360. ;;
  361. esac
  362. done
  363. if [ -z "$BART_TYPE" ]; then
  364. log_error "BART type is required"
  365. usage
  366. exit 1
  367. fi
  368. if [ ${#TARGET_DIRS[@]} -eq 0 ]; then
  369. log_error "No directory path provided"
  370. usage
  371. exit 1
  372. fi
  373. # Validate BART type
  374. BART_TYPE_NUMBER=$(get_bart_type_number "$BART_TYPE")
  375. if [ -z "$BART_TYPE_NUMBER" ]; then
  376. log_error "Invalid BART type: $BART_TYPE"
  377. log_error "Valid types: buddy_icon_small, buddy_icon, status_str, arrive_sound, rich_text, superbuddy_icon, radio_station, buddy_icon_big, status_str_tod, current_av_track, depart_sound, im_chrome, im_sound, im_chrome_xml, im_chrome_immers, emoticon_set, encr_cert_chain, sign_cert_chain, gateway_cert"
  378. exit 1
  379. fi
  380. main() {
  381. log_info "BART Import Script for Retro AIM Server"
  382. log_info "========================================"
  383. log_info "Target paths: ${TARGET_DIRS[*]}"
  384. log_info "BART type: $BART_TYPE (type number: $BART_TYPE_NUMBER)"
  385. if [ "$DRY_RUN" = true ]; then
  386. log_warning "DRY RUN MODE - No files will be uploaded"
  387. fi
  388. check_prerequisites
  389. if [ "$DRY_RUN" = false ]; then
  390. test_api
  391. fi
  392. local total_errors=0
  393. local total_dirs=0
  394. # Process each file/directory
  395. # Ensure array is properly expanded for cross-platform compatibility
  396. if [ ${#TARGET_DIRS[@]} -gt 0 ]; then
  397. for target_path in "${TARGET_DIRS[@]}"; do
  398. total_dirs=$((total_dirs + 1))
  399. if process_directory "$target_path"; then
  400. # Success is logged by process_directory
  401. :
  402. else
  403. total_errors=$((total_errors + 1))
  404. fi
  405. done
  406. fi
  407. # Summary
  408. log_info "Import completed!"
  409. log_info "Total files processed: $total_dirs"
  410. log_info "Total errors: $total_errors"
  411. if [ $total_errors -eq 0 ]; then
  412. log_success "All operations completed successfully"
  413. exit 0
  414. else
  415. log_error "Some operations failed"
  416. exit 1
  417. fi
  418. }
  419. main