| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482 |
- #!/bin/bash
- # BART Import Script for Retro AIM Server
- # This script imports BART (Buddy ART) assets from an AIM client's bartcache
- # directory (usually found under %APPDATA%\acccore\caches\bart) into Retro AIM
- # Server via the management API.
- #
- # Compatible with macOS and Linux terminals
- set -e
- # Ensure we're using bash and have proper error handling
- if [ -z "$BASH_VERSION" ]; then
- echo "Error: This script requires bash" >&2
- exit 1
- fi
- # Default values
- API_BASE_URL="http://localhost:8080"
- VERBOSE=false
- DRY_RUN=false
- BART_TYPE=""
- TARGET_DIRS=()
- # Colors for output
- RED='\033[0;31m'
- GREEN='\033[0;32m'
- YELLOW='\033[1;33m'
- BLUE='\033[0;34m'
- NC='\033[0m' # No Color
- usage() {
- echo "Usage: $0 [OPTIONS] -t <type> <directory_path> [directory_path...]"
- echo ""
- echo "Import BART assets from bartcache directories into Retro AIM Server"
- echo ""
- echo "Arguments:"
- echo " directory_path Path to bartcache directory containing BART assets"
- echo " Directory should contain subdirectories named by BART type (0, 1, 2, etc.)"
- echo " Each type directory should contain files named by their hash"
- echo " Multiple directories can be specified for bulk import"
- echo ""
- echo "Options:"
- echo " -t, --type TYPE BART type to import (required)"
- echo " Valid types: buddy_icon_small, buddy_icon, status_str, arrive_sound,"
- echo " rich_text, superbuddy_icon, radio_station, buddy_icon_big,"
- echo " status_str_tod, current_av_track, depart_sound, im_chrome,"
- echo " im_sound, im_chrome_xml, im_chrome_immers, emoticon_set,"
- echo " encr_cert_chain, sign_cert_chain, gateway_cert"
- echo " -u, --url URL API base URL (default: http://localhost:8080)"
- echo " -v, --verbose Enable verbose output"
- echo " -d, --dry-run Show what would be uploaded without actually uploading"
- echo " -h, --help Show this help message"
- echo ""
- echo "Examples:"
- echo " $0 -t buddy_icon /Users/mike/Downloads/aim\\ barts/eigdbvye/bartcache"
- echo " $0 --type status_str --verbose --dry-run /path/to/bartcache"
- echo " $0 -t arrive_sound /path/to/bartcache/*"
- }
- log_info() {
- echo -e "${BLUE}[INFO]${NC} $1"
- }
- log_success() {
- echo -e "${GREEN}[SUCCESS]${NC} $1"
- }
- log_warning() {
- echo -e "${YELLOW}[WARNING]${NC} $1"
- }
- log_error() {
- echo -e "${RED}[ERROR]${NC} $1"
- }
- log_verbose() {
- if [ "$VERBOSE" = true ]; then
- echo -e "${BLUE}[VERBOSE]${NC} $1"
- fi
- }
- command_exists() {
- command -v "$1" >/dev/null 2>&1
- }
- is_hex_string() {
- local string="$1"
- # Check if string contains only hexadecimal characters (0-9, a-f, A-F)
- if [[ "$string" =~ ^[0-9a-fA-F]+$ ]]; then
- return 0
- else
- return 1
- fi
- }
- # Normalize path for cross-platform compatibility
- normalize_path() {
- local path="$1"
- # Remove trailing slashes and normalize the path
- echo "$path" | sed 's|/*$||'
- }
- get_bart_type_number() {
- case "$1" in
- "buddy_icon_small") echo "0" ;;
- "buddy_icon") echo "1" ;;
- "status_str") echo "2" ;;
- "arrive_sound") echo "3" ;;
- "rich_text") echo "4" ;;
- "superbuddy_icon") echo "5" ;;
- "radio_station") echo "6" ;;
- "buddy_icon_big") echo "12" ;;
- "status_str_tod") echo "13" ;;
- "current_av_track") echo "15" ;;
- "depart_sound") echo "96" ;;
- "im_chrome") echo "129" ;;
- "im_sound") echo "131" ;;
- "im_chrome_xml") echo "136" ;;
- "im_chrome_immers") echo "137" ;;
- "emoticon_set") echo "1024" ;;
- "encr_cert_chain") echo "1026" ;;
- "sign_cert_chain") echo "1027" ;;
- "gateway_cert") echo "1028" ;;
- *) echo "" ;;
- esac
- }
- get_bart_type_name() {
- case "$1" in
- "0") echo "buddy_icon_small" ;;
- "1") echo "buddy_icon" ;;
- "2") echo "status_str" ;;
- "3") echo "arrive_sound" ;;
- "4") echo "rich_text" ;;
- "5") echo "superbuddy_icon" ;;
- "6") echo "radio_station" ;;
- "12") echo "buddy_icon_big" ;;
- "13") echo "status_str_tod" ;;
- "15") echo "current_av_track" ;;
- "96") echo "depart_sound" ;;
- "129") echo "im_chrome" ;;
- "131") echo "im_sound" ;;
- "136") echo "im_chrome_xml" ;;
- "137") echo "im_chrome_immers" ;;
- "1024") echo "emoticon_set" ;;
- "1026") echo "encr_cert_chain" ;;
- "1027") echo "sign_cert_chain" ;;
- "1028") echo "gateway_cert" ;;
- *) echo "unknown_type_$1" ;;
- esac
- }
- check_prerequisites() {
- if ! command_exists curl; then
- log_error "curl is required but not installed"
- exit 1
- fi
- if ! command_exists jq; then
- log_warning "jq is not installed. JSON responses will not be formatted"
- fi
- }
- test_api() {
- log_info "Testing API connectivity..."
- local response
- local http_code
- if response=$(curl -s -w "%{http_code}" "$API_BASE_URL/bart?type=0" 2>/dev/null); then
- # Extract HTTP code from response (last 3 characters)
- http_code=$(echo "$response" | tail -c 4)
- if [ "$http_code" = "200" ]; then
- log_success "API is accessible"
- return 0
- else
- log_error "API returned HTTP $http_code"
- return 1
- fi
- else
- log_error "Failed to connect to API at $API_BASE_URL"
- return 1
- fi
- }
- upload_bart_asset() {
- local file_path="$1"
- local bart_type="$2"
- local hash="$3"
- log_verbose "Uploading $file_path (type: $bart_type, hash: $hash)"
- if [ "$DRY_RUN" = true ]; then
- log_info "[DRY RUN] Would upload: $file_path -> type=$bart_type, hash=$hash"
- return 0
- fi
- local response
- local http_code
- # Upload the file
- if response=$(curl -s -w "%{http_code}" \
- -X POST \
- -H "Content-Type: application/octet-stream" \
- --data-binary "@$file_path" \
- "$API_BASE_URL/bart/$hash?type=$bart_type" 2>/dev/null); then
- # Extract HTTP code from response (last 3 characters)
- http_code=$(echo "$response" | tail -c 4)
- # Extract response body (all except last 3 characters)
- # Use a more portable approach that works on both macOS and Linux
- response_length=$(echo "$response" | wc -c)
- response_body_length=$((response_length - 4))
- response_body=$(echo "$response" | head -c "$response_body_length")
- case "$http_code" in
- 201)
- log_success "Uploaded $hash"
- if [ "$VERBOSE" = true ] && command_exists jq; then
- echo "$response_body" | jq .
- fi
- return 0
- ;;
- 409)
- log_warning "Asset $hash already exists"
- return 0
- ;;
- 400)
- log_error "Bad request for $hash (type: $bart_type)"
- if command_exists jq; then
- echo "$response_body" | jq .
- else
- echo "$response_body"
- fi
- return 1
- ;;
- 413)
- log_error "File too large for $hash (type: $bart_type)"
- return 1
- ;;
- *)
- log_error "Upload failed for $hash (type: $bart_type) - HTTP $http_code"
- if command_exists jq; then
- echo "$response_body" | jq .
- else
- echo "$response_body"
- fi
- return 1
- ;;
- esac
- else
- log_error "Failed to upload $hash (type: $bart_type)"
- return 1
- fi
- }
- process_bart_type_directory() {
- local type_dir="$1"
- local bart_type="$2"
- local type_name="$3"
- log_info "Processing BART type $bart_type ($type_name)..."
- if [ ! -d "$type_dir" ]; then
- log_warning "Type directory $type_dir does not exist, skipping"
- return 0
- fi
- local file_count=0
- local success_count=0
- local error_count=0
- # Find all files in the type directory (excluding directories)
- # Use a more portable approach that works on both macOS and Linux
- # Store results in a temporary file to avoid subshell variable scoping issues
- local temp_file=$(mktemp)
- find "$type_dir" -maxdepth 1 -type f 2>/dev/null > "$temp_file"
-
- while read -r file_path; do
- if [ -f "$file_path" ]; then
- local filename=$(basename "$file_path")
- file_count=$((file_count + 1))
- log_verbose "Found file: $filename"
- # Validate that filename is a hexadecimal string
- if ! is_hex_string "$filename"; then
- log_warning "Skipping file '$filename' - filename is not a valid hexadecimal string"
- error_count=$((error_count + 1))
- continue
- fi
- if upload_bart_asset "$file_path" "$bart_type" "$filename"; then
- success_count=$((success_count + 1))
- else
- error_count=$((error_count + 1))
- fi
- fi
- done < "$temp_file"
-
- # Clean up temporary file
- rm -f "$temp_file"
- log_info "Type $bart_type ($type_name): $file_count files, $success_count successful, $error_count errors"
- return $error_count
- }
- process_directory() {
- local base_dir="$1"
- base_dir=$(normalize_path "$base_dir")
- if [ ! -d "$base_dir" ] && [ ! -f "$base_dir" ]; then
- log_error "Path $base_dir does not exist"
- return 1
- fi
- # Check if this is a single file
- if [ -f "$base_dir" ]; then
- log_info "Processing file: $base_dir"
- local filename=$(basename "$base_dir")
-
- # Validate that filename is a hexadecimal string
- if ! is_hex_string "$filename"; then
- log_error "Cannot process file '$filename' - filename is not a valid hexadecimal string"
- return 1
- fi
-
- if upload_bart_asset "$base_dir" "$BART_TYPE_NUMBER" "$filename"; then
- log_success "Successfully processed file: $base_dir"
- return 0
- else
- log_error "Failed to process file: $base_dir"
- return 1
- fi
- fi
- # For directories, show the BART type info
- log_info "Processing BART assets from directory: $base_dir"
- log_info "BART type: $BART_TYPE (type number: $BART_TYPE_NUMBER)"
- # Look for the specific type directory first (nested structure)
- local type_dir="$base_dir/$BART_TYPE_NUMBER"
- if [ -d "$type_dir" ]; then
- log_info "Found type directory: $type_dir"
- # Process the single type directory
- if process_bart_type_directory "$type_dir" "$BART_TYPE_NUMBER" "$BART_TYPE"; then
- local file_count=$(find "$type_dir" -maxdepth 1 -type f | wc -l)
- log_success "Successfully processed $file_count files for type $BART_TYPE"
- return 0
- else
- log_error "Failed to process files for type $BART_TYPE"
- return 1
- fi
- fi
- # If no type directory found, check if this is a flat structure (files directly in directory)
- local file_count=$(find "$base_dir" -maxdepth 1 -type f | wc -l)
- if [ $file_count -gt 0 ]; then
- log_info "Found flat file structure with $file_count files"
- # Process files directly in the directory
- if process_bart_type_directory "$base_dir" "$BART_TYPE_NUMBER" "$BART_TYPE"; then
- log_success "Successfully processed $file_count files for type $BART_TYPE"
- return 0
- else
- log_error "Failed to process files for type $BART_TYPE"
- return 1
- fi
- fi
- # No files or directories found
- log_error "No files found in $base_dir"
- log_error "Expected either:"
- log_error " - Nested structure: $base_dir/$BART_TYPE_NUMBER/"
- log_error " - Flat structure: files directly in $base_dir/"
- return 1
- }
- while [[ $# -gt 0 ]]; do
- case $1 in
- -t|--type)
- BART_TYPE="$2"
- shift 2
- ;;
- -u|--url)
- API_BASE_URL="$2"
- shift 2
- ;;
- -v|--verbose)
- VERBOSE=true
- shift
- ;;
- -d|--dry-run)
- DRY_RUN=true
- shift
- ;;
- -h|--help)
- usage
- exit 0
- ;;
- -*)
- log_error "Unknown option $1"
- usage
- exit 1
- ;;
- *)
- TARGET_DIRS+=("$1")
- shift
- ;;
- esac
- done
- if [ -z "$BART_TYPE" ]; then
- log_error "BART type is required"
- usage
- exit 1
- fi
- if [ ${#TARGET_DIRS[@]} -eq 0 ]; then
- log_error "No directory path provided"
- usage
- exit 1
- fi
- # Validate BART type
- BART_TYPE_NUMBER=$(get_bart_type_number "$BART_TYPE")
- if [ -z "$BART_TYPE_NUMBER" ]; then
- log_error "Invalid BART type: $BART_TYPE"
- 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"
- exit 1
- fi
- main() {
- log_info "BART Import Script for Retro AIM Server"
- log_info "========================================"
- log_info "Target paths: ${TARGET_DIRS[*]}"
- log_info "BART type: $BART_TYPE (type number: $BART_TYPE_NUMBER)"
- if [ "$DRY_RUN" = true ]; then
- log_warning "DRY RUN MODE - No files will be uploaded"
- fi
- check_prerequisites
- if [ "$DRY_RUN" = false ]; then
- test_api
- fi
- local total_errors=0
- local total_dirs=0
- # Process each file/directory
- # Ensure array is properly expanded for cross-platform compatibility
- if [ ${#TARGET_DIRS[@]} -gt 0 ]; then
- for target_path in "${TARGET_DIRS[@]}"; do
- total_dirs=$((total_dirs + 1))
- if process_directory "$target_path"; then
- # Success is logged by process_directory
- :
- else
- total_errors=$((total_errors + 1))
- fi
- done
- fi
- # Summary
- log_info "Import completed!"
- log_info "Total files processed: $total_dirs"
- log_info "Total errors: $total_errors"
- if [ $total_errors -eq 0 ]; then
- log_success "All operations completed successfully"
- exit 0
- else
- log_error "Some operations failed"
- exit 1
- fi
- }
- main
|