Skip to main content
replaced http://codereview.stackexchange.com/ with https://codereview.stackexchange.com/
Source Link

This is a year later, but it has the same context: I wanted new reviews for the updated code from the original questionthe original question, but this time I wrote the code from scratch. I did this, not because the project is of any value or interest to me any more, as much as I wanted to compare my programming knowledge/style now to mine a year before. So, you will probably notice different algorithms and a different style of programming.

This is a year later, but it has the same context: I wanted new reviews for the updated code from the original question, but this time I wrote the code from scratch. I did this, not because the project is of any value or interest to me any more, as much as I wanted to compare my programming knowledge/style now to mine a year before. So, you will probably notice different algorithms and a different style of programming.

This is a year later, but it has the same context: I wanted new reviews for the updated code from the original question, but this time I wrote the code from scratch. I did this, not because the project is of any value or interest to me any more, as much as I wanted to compare my programming knowledge/style now to mine a year before. So, you will probably notice different algorithms and a different style of programming.

deleted 1 character in body
Source Link
Amr Ayman
  • 853
  • 1
  • 8
  • 24
#!/bin/bash

declare -r SettingsFile="$HOME/.config/PlayMusic/settings.conf"
declare -r _LOGFILE="$HOME/.config/PlayMusic/logfile.log"
declare -r Version=0.3.2

source bash_utilbash_lib

log "Program started: $1"

declare -A Settings=(
    ['Dropbox']=false               # Wether to copy downloaded tracks to the dropbox folder
    ['TimesToPlay']=1               # Number of times to play a track
    ['Download']=false              # Wether to download the track
    ['Play']=true                   # Wether to play the track
    ['All']=false                   # Wether to act on all tracks found, or just ask to specify track(s)
    ['Player']='mplayer'            # Command to invoke a CLI sound player
    ['Downloader']='wget -c -O'     # Command to invoke a CLI downloader
    ['Editor']="${EDITOR:-nano}"    # Command to invoke a CLI editor
    ['MusicPlace']="$HOME/Music"    # Directory to download music
)

declare Sites=(
    'http://ccmixter.org/view/media/samples/mixed'
    'http://ccmixter.org/view/media/remix'
    'http://mp3.com/top-downloads/genre/jazz/'
    'http://mp3.com/top-downloads/'
    'http://mp3.com/top-downloads/genre/rock/'
    'http://mp3.com/top-downloads/genre/hip hop/'
    'http://mp3.com/top-downloads/genre/emo/'
    'http://mp3.com/top-downloads/genre/pop/'
)   # Array of sites to search for tracks at

declare Extensions=(
    'mp3'
    'ogg'
)   # Array of extensions to look for

# Key: Track title. Value: Track url.
declare -A Tracks

# Displays settings in a user-friendly manner on the screen

info()
{
    cat << __EOF_

    # General Settings #
    Settings File:          $SettingsFile
    Log File:           ${_LOGFILE}
    Version:            $Version

    # Behaviour Settings #
    Dropbox Support:        ${Settings['Dropbox']}
    Music Place:            ${Settings['MusicPlace']}
    Player:             ${Settings['Player']}
    Editor:             ${Settings['Editor']}
    Player:             ${Settings['Player']}

    # Music Action Settings #
    Times to Play:          ${Settings['TimesToPlay']}
    Act on all tracks:      ${Settings['All']}
    Action:             $( get_music_action )

__EOF_
}

# Saves $Settings and $Sites to $SettingsFile in a valid format

save_settings()
{
    log "Creating settings file"

    mkdir -p "$( dirname "$SettingsFile" )"
    echo "Created on $( date )" > "$SettingsFile" || _FORCED_EXIT=true error "Fatal Error!" $?

    for option in "${!Settings[@]}" ; do
        echo "$option = ${Settings["$option"]}" >> "$SettingsFile"
    done

    echo "Sites" >> "$SettingsFile"
    for site in "${Sites[@]}" ; do
        echo "$site" >> "$SettingsFile"
    done

    log "Created settings file successfully"
}

# Extracts program name from a command

# @param $1 the cmd.

get_prog_name()
{
    cut -d ' ' -f 1 <<< "$1"
}

# Load $Settings and $Sites from the $SettingsFile

load_settings()
{
    log "Parsing settings file"

    local line_number=0 line
    local key value
    local correct
    local is_site=false site

    # Check Settings file
    if ! ([ -r "$SettingsFile" ] || [ -f "$SettingsFile" ]) ; then
        save_settings
        return
    fi

    while read -r line; do
        ((line_number++))

        if [ -z "$line" ] || [ $line_number = 1 ] ; then
            continue
        fi

        correct=false

        # Check line format
        if $is_site ; then
            Sites["${#Sites[@]}"]="$line"
            continue
        else
            if [ "$line" = "Sites" ] ; then
                is_site=true
                Sites=()
                continue
            elif ! egrep "^[a-zA-Z]+\s*=.+$" -q <<< "$line" ; then
                error "$SettingsFile:$line_number: Incorrect format of line" EX_CONFIG
            fi
        fi

        # Extract Key,Value pair
        key="$( egrep -o '^[a-zA-Z]+' <<< "$line" )"
        value="$( sed -E "s/^$key\s*=\s*(.+)$/\1/" <<< "$line" )"

        # Check if value is valid
        case "$key" in
            'Dropbox'|'Download'|'All'|'Play' )
                if egrep 'false|true' -q <<< "$value" ; then
                    correct=true
                else
                    error "Expected true or false"
                fi;;
            'TimesToPlay' )
                if is_num "$value" ; then
                    correct=true
                else
                    error "Expected a number"
                fi;;
            'Player'|'Editor'|'Downloader' )
                if is_prog_there "$( get_prog_name "$value" )" ; then
                    correct=true
                else
                    error "Expected an executable program"
                fi;;
            'MusicPlace' )
                if [ -d "$value" ] && [ -w "$value" ] ; then
                    correct=true;
                else
                    error "'$value' is not a writable directory"
                fi;;
            * ) error "$SettingsFile:$line_number: Invalid option: $key\nValid Options are:\n" \
            "\tDropbox, Download, All, Play, TimesToPlay, Player, Editor" EX_CONFIG;;
        esac

        if ! $correct ; then
            error "$SettingsFile:$line_number: Invalid value: '$value' to option: '$key'" EX_CONFIG
        fi

        Settings["$key"]="$value"
    done < "$SettingsFile"

    log "Parsed settings file successfully"
}

# Displays program usage in a user-friendly manner to screen

usage()
{
cat << _USAGE_
    Usage: ./PlayMusic
        -v|--version            Output version then exit.
        -h|--help               View this help then exit.
        -x|--dropbox            Allow copying downloaded files to $HOME/Dropbox.
        -t|--playtimes [num]    Times to play a track.
        -d|--download           To download a track without asking.
        -D|--no-download        To not download a track without asking.
        -p|--play               To play a track without asking.
        -P|--no-play            To not play a track without asking.
        -k|--ask                To force ask what to do with a track.
        -a|--all                To act on all tracks found.
        -y|--player [cmd]       The command to run the music player.
        -e|--edtor [cmd]        The command to run the editor.
        -l|--downloader         The command to run the downloader.
        -m|--music-place [dir]  To specify a music directory other than the one found at the settings file.
        -r|--recreate-settings  To recreate the settings file to default then exit.
        -E|--edit-settings      To edit the settings file then exit.
        -s|--save               To save the given settings (runs after analyzing all options).
        -i|--info               To display given settings so far then exit.
_USAGE_
}

# Outputs the function name of what to do with a track

get_music_action()
{
    if ${Settings['Download']} && ! ${Settings['Play']} ; then
        echo "download"
    elif ! ${Settings['Download']} && ${Settings['Play']} ; then
        echo "play"
    elif ${Settings['Download']} && ${Settings['Play']} ; then
        echo "download_then_play"
#   else
#       echo "ask"
    fi
}

# Parses program arguments

# @param $1 program arguments

parse_args()
{
    log "Parsing Arguments"

    local save=false
    local args=`getopt -o vhxt:dpDPy:e:amX::islk --long version,help,dropbox,playtimes:,download,play,no-download,no-play,player:,editor:,all,music-place:,extensions::,info,save,downloader,ask -n 'PlayMusic' -- "$@"` || error "Internal Error!" EX_SOFTWARE

    eval set -- "$args"

    while true ; do
        case "$1" in
            -v|--version ) echo "$Version"; _exit;;
            -h|--help ) usage; _exit;;
            -i|--info ) info; _exit;;
            -x|--dropbox ) Settings['Dropbox']=true;;
            -t|--playtimes )
                if is_num "$2" ; then
                    Settings['TimesToPlay']="$2"
                else
                    if [ -n "$2" ] ; then
                        error "'$2' is not a number" EX_CONFIG
                    else
                        error "Please provide a number for the '$1' option" EX_CONFIG
                    fi
                fi; shift;;
            -d|--download ) Settings['Download']=true;;
            -p|--play ) Settings['Play']=true;;
            -D|--no-download ) Settings['Download']=false;;
            -P|--no-play ) Settings['Play']=false;;
            -y|--player ) require "$( get_prog_name "$2" )" 2; Settings['Player']="$2"; shift;;
            -e|--editor ) require "$( get_prog_name "$2" )" 2; Settings['Editor']="$2"; shift;;
            -a|--all ) Settings['All']=true;;
            -A|--selective ) Settings['All']=false;;
            -m|--music-place )
                if ! ([ -d "$2" ] && [ -w "$2" ]) ; then
                    error "'$2' is not a writable directory to store music in" EX_CONFIG
                fi;;
            -X|--extensions )
                if [ -n "$2" ] ; then
                    Extensions=(${2//,/ })
                else
                    echo "Extensions: ${Extensions[@]// /,}"
                fi;;
            -s|--save ) save=true;;
            -l|--downloader ) require "$( get_prog_name "$2" )" 2; Settings['Downloader']="$2"; shift;;
            -k|--ask ) Settings['Play']=false; Settings['Download']=false;;
            -- ) break;;
            * ) error "Unknown argument: $1" EX_CONFIG;;
        esac
        shift
    done

    if ${Settings['All']} ; then
        local act=$( get_music_action )
        [ -z act ] && act="ask what to do with"

        is_yes -p "Are you sure you want to $act all tracks [y/n] ? " || Settings['All']=false
    fi

    if $save ; then
        log "Saving settings"
        save_settings
        exit
    fi

    log "Parsed Arguments successfully"
}

# Fills $Tracks by looking through $Sites for tracks ending in $Extensions. This is the core backend functionality.

find_tracks()
{
    log "Looking for tracks"

    local exts="${Extensions[@]// /|}"
    local num

    for site in "${Sites[@]}" ; do
        log "Checking site: '$site'"

        num=0
        [ "$1" = '-v' ] && echo "Parsing $site"

        for track in $( curl -Ls "$site" | egrep -o "\bhttp://.*\.("$exts")\b" ) ; do
            name="$( url_decode "$( basename "$track" )" auto )"
            Tracks["${name//+/ }"]="$track"
            ((num++))
        done

        log "Found $num track(s) at the site"
    done

    [ ${#Tracks[@]} = 0 ] && error "Couldn't find any tracks!" 0
}

# Edits $SettingsFile by ${Settings['Editor']}

edit_settings()
{
    log 'Edit settings requested'
    ${Settings['Editor']} "$SettingsFile"
}

# Handles music action

# @param $1 action
# @param $2 track url
# @param $3 track name (with extension)

handle_action()
{
    ([ -n "$1" ] && [ -n "$2" ] && [ -n "$3" ]) || return

    [ "$1" = 'download' ] || local name="$( sed -E 's/\.(\w+)$//' <<< "$3" )"
    $1 "$2" "$([ "$1" = 'play' ] && echo "$name" || echo "$3")" "$([ "$1" = 'download_then_play' ] && echo "$name")"
}

# Downloads a track to ${Settings['MusicPlace']} by ${Settings['Downloader']}
# If $3 is -v, Outputs the track location on disk.

# @param $1 the track url
# @param $2 the track name (with extension)
# @param $3 -v. optional.

download()
{
    local download_to="${Settings['MusicPlace']}/$2"

    log "Action: Download [$1] to [$download_to]"
    wget -c -O "$download_to" "$1"
    log "wget returned $?"

    if $Settings['Dropbox'] ; then
        cp "$download_to" "$HOME/Dropbox/Music"
        log "Copying file to dropbox"
    fi

    [ "$3" = '-v' ] && echo "$download_to"
}

# Plays a track by ${Settings['Player']}

# @param $1 the track location
# @param $2 the track name (without extension). optional.

play()
{
    log "Action: Play [$1]"
    [ -n "$2" ] && notify-send "PlayMusic: Playing $2"
    ${Settings['Player']} "$1"
}

# Downloads then plays a track

# @param $1 the track url
# @param $2 the track name (with extension).
# @param $3 the track name (without extension). optional.

download_then_play()
{
    log "Action: Download then Play [$1]"
    play $( download "$1" "$2" -v ) "$3"
}

# Asks the user what to do with a track

# @param $1 the other option, outputs nothing when chosen.

ask()
{
    local -A _MENU_CHOICES=(['Download']='download' ['Play']='play' ['Download then Play']='download_then_play' ["$1"]='')
    menu
}

# Main entry point

main()
{
    local site_number=0
    local com="$( get_music_action )"

    echo "Looking for Tracks in ${#Sites[@]} Site$([[ ${#Sites[@]} -gt 1 ]] && echo "s") .."
    find_tracks -v

    if ${Settings['All']} ; then
        [ -n "$com" ] || com="$( ask 'Exit' )"
        [ -n "$com" ] || _exit

        for track in "${Tracks[@]}" ; do
            handle_action "$com" "${Tracks["$track"]}" "$track"
        done
    else
        echo "Choose Track .."
        select track in "${!Tracks[@]}" 'Quit' ; do
            [ -n "$track" ] || continue
            [ "$track" = 'Quit' ] && _exit
            [ -n "$com" ] || com="$( ask 'Return back' )"
            [ -n "$com" ] || continue

            handle_action "$com" "${Tracks["$track"]}" "$track"
        done
    fi
}

# Handles signales. Should not be used directly.

forced_exit()
{
    _FORCED_EXIT=true error "Signal [$1] forced an exit." "$((128+$1))"
}

# Initializes the environment for processing

init_env()
{
    trap 'forced_exit 2' SIGINT
    trap 'forced_exit 3' SIGQUIT
    trap 'forced_exit 4' SIGABRT
    trap 'forced_exit 15' SIGTERM

    for i in "$@" ; do
        if [ "$i" = "-r" ] || [ "$i" = '--recreate-settings' ] ; then
            save_settings
            _exit
        fi

        if [ "$i" = "-E" ] || [ "$i" = '--edit-settings' ] ; then
            edit_settings
            _exit
        fi
    done

    mkdir -p "${Settings['MusicPlace']}"

    load_settings
    parse_args "$@"

    if [ ${#Sites[@]} = 0 ] ; then
        error "No Sites were found!" 0
    fi
}

ARGS="$@"
init_env "$ARGS"

require "$( get_prog_name "${Settings['Player']}" )" "$( get_prog_name "${Settings['Editor']}" )" "$( get_prog_name "${Settings['Downloader']}" )"

main "$ARGS"
#!/bin/bash

declare -r SettingsFile="$HOME/.config/PlayMusic/settings.conf"
declare -r _LOGFILE="$HOME/.config/PlayMusic/logfile.log"
declare -r Version=0.3.2

source bash_util

log "Program started: $1"

declare -A Settings=(
    ['Dropbox']=false               # Wether to copy downloaded tracks to the dropbox folder
    ['TimesToPlay']=1               # Number of times to play a track
    ['Download']=false              # Wether to download the track
    ['Play']=true                   # Wether to play the track
    ['All']=false                   # Wether to act on all tracks found, or just ask to specify track(s)
    ['Player']='mplayer'            # Command to invoke a CLI sound player
    ['Downloader']='wget -c -O'     # Command to invoke a CLI downloader
    ['Editor']="${EDITOR:-nano}"    # Command to invoke a CLI editor
    ['MusicPlace']="$HOME/Music"    # Directory to download music
)

declare Sites=(
    'http://ccmixter.org/view/media/samples/mixed'
    'http://ccmixter.org/view/media/remix'
    'http://mp3.com/top-downloads/genre/jazz/'
    'http://mp3.com/top-downloads/'
    'http://mp3.com/top-downloads/genre/rock/'
    'http://mp3.com/top-downloads/genre/hip hop/'
    'http://mp3.com/top-downloads/genre/emo/'
    'http://mp3.com/top-downloads/genre/pop/'
)   # Array of sites to search for tracks at

declare Extensions=(
    'mp3'
    'ogg'
)   # Array of extensions to look for

# Key: Track title. Value: Track url.
declare -A Tracks

# Displays settings in a user-friendly manner on the screen

info()
{
    cat << __EOF_

    # General Settings #
    Settings File:          $SettingsFile
    Log File:           ${_LOGFILE}
    Version:            $Version

    # Behaviour Settings #
    Dropbox Support:        ${Settings['Dropbox']}
    Music Place:            ${Settings['MusicPlace']}
    Player:             ${Settings['Player']}
    Editor:             ${Settings['Editor']}
    Player:             ${Settings['Player']}

    # Music Action Settings #
    Times to Play:          ${Settings['TimesToPlay']}
    Act on all tracks:      ${Settings['All']}
    Action:             $( get_music_action )

__EOF_
}

# Saves $Settings and $Sites to $SettingsFile in a valid format

save_settings()
{
    log "Creating settings file"

    mkdir -p "$( dirname "$SettingsFile" )"
    echo "Created on $( date )" > "$SettingsFile" || _FORCED_EXIT=true error "Fatal Error!" $?

    for option in "${!Settings[@]}" ; do
        echo "$option = ${Settings["$option"]}" >> "$SettingsFile"
    done

    echo "Sites" >> "$SettingsFile"
    for site in "${Sites[@]}" ; do
        echo "$site" >> "$SettingsFile"
    done

    log "Created settings file successfully"
}

# Extracts program name from a command

# @param $1 the cmd.

get_prog_name()
{
    cut -d ' ' -f 1 <<< "$1"
}

# Load $Settings and $Sites from the $SettingsFile

load_settings()
{
    log "Parsing settings file"

    local line_number=0 line
    local key value
    local correct
    local is_site=false site

    # Check Settings file
    if ! ([ -r "$SettingsFile" ] || [ -f "$SettingsFile" ]) ; then
        save_settings
        return
    fi

    while read -r line; do
        ((line_number++))

        if [ -z "$line" ] || [ $line_number = 1 ] ; then
            continue
        fi

        correct=false

        # Check line format
        if $is_site ; then
            Sites["${#Sites[@]}"]="$line"
            continue
        else
            if [ "$line" = "Sites" ] ; then
                is_site=true
                Sites=()
                continue
            elif ! egrep "^[a-zA-Z]+\s*=.+$" -q <<< "$line" ; then
                error "$SettingsFile:$line_number: Incorrect format of line" EX_CONFIG
            fi
        fi

        # Extract Key,Value pair
        key="$( egrep -o '^[a-zA-Z]+' <<< "$line" )"
        value="$( sed -E "s/^$key\s*=\s*(.+)$/\1/" <<< "$line" )"

        # Check if value is valid
        case "$key" in
            'Dropbox'|'Download'|'All'|'Play' )
                if egrep 'false|true' -q <<< "$value" ; then
                    correct=true
                else
                    error "Expected true or false"
                fi;;
            'TimesToPlay' )
                if is_num "$value" ; then
                    correct=true
                else
                    error "Expected a number"
                fi;;
            'Player'|'Editor'|'Downloader' )
                if is_prog_there "$( get_prog_name "$value" )" ; then
                    correct=true
                else
                    error "Expected an executable program"
                fi;;
            'MusicPlace' )
                if [ -d "$value" ] && [ -w "$value" ] ; then
                    correct=true;
                else
                    error "'$value' is not a writable directory"
                fi;;
            * ) error "$SettingsFile:$line_number: Invalid option: $key\nValid Options are:\n" \
            "\tDropbox, Download, All, Play, TimesToPlay, Player, Editor" EX_CONFIG;;
        esac

        if ! $correct ; then
            error "$SettingsFile:$line_number: Invalid value: '$value' to option: '$key'" EX_CONFIG
        fi

        Settings["$key"]="$value"
    done < "$SettingsFile"

    log "Parsed settings file successfully"
}

# Displays program usage in a user-friendly manner to screen

usage()
{
cat << _USAGE_
    Usage: ./PlayMusic
        -v|--version            Output version then exit.
        -h|--help               View this help then exit.
        -x|--dropbox            Allow copying downloaded files to $HOME/Dropbox.
        -t|--playtimes [num]    Times to play a track.
        -d|--download           To download a track without asking.
        -D|--no-download        To not download a track without asking.
        -p|--play               To play a track without asking.
        -P|--no-play            To not play a track without asking.
        -k|--ask                To force ask what to do with a track.
        -a|--all                To act on all tracks found.
        -y|--player [cmd]       The command to run the music player.
        -e|--edtor [cmd]        The command to run the editor.
        -l|--downloader         The command to run the downloader.
        -m|--music-place [dir]  To specify a music directory other than the one found at the settings file.
        -r|--recreate-settings  To recreate the settings file to default then exit.
        -E|--edit-settings      To edit the settings file then exit.
        -s|--save               To save the given settings (runs after analyzing all options).
        -i|--info               To display given settings so far then exit.
_USAGE_
}

# Outputs the function name of what to do with a track

get_music_action()
{
    if ${Settings['Download']} && ! ${Settings['Play']} ; then
        echo "download"
    elif ! ${Settings['Download']} && ${Settings['Play']} ; then
        echo "play"
    elif ${Settings['Download']} && ${Settings['Play']} ; then
        echo "download_then_play"
#   else
#       echo "ask"
    fi
}

# Parses program arguments

# @param $1 program arguments

parse_args()
{
    log "Parsing Arguments"

    local save=false
    local args=`getopt -o vhxt:dpDPy:e:amX::islk --long version,help,dropbox,playtimes:,download,play,no-download,no-play,player:,editor:,all,music-place:,extensions::,info,save,downloader,ask -n 'PlayMusic' -- "$@"` || error "Internal Error!" EX_SOFTWARE

    eval set -- "$args"

    while true ; do
        case "$1" in
            -v|--version ) echo "$Version"; _exit;;
            -h|--help ) usage; _exit;;
            -i|--info ) info; _exit;;
            -x|--dropbox ) Settings['Dropbox']=true;;
            -t|--playtimes )
                if is_num "$2" ; then
                    Settings['TimesToPlay']="$2"
                else
                    if [ -n "$2" ] ; then
                        error "'$2' is not a number" EX_CONFIG
                    else
                        error "Please provide a number for the '$1' option" EX_CONFIG
                    fi
                fi; shift;;
            -d|--download ) Settings['Download']=true;;
            -p|--play ) Settings['Play']=true;;
            -D|--no-download ) Settings['Download']=false;;
            -P|--no-play ) Settings['Play']=false;;
            -y|--player ) require "$( get_prog_name "$2" )" 2; Settings['Player']="$2"; shift;;
            -e|--editor ) require "$( get_prog_name "$2" )" 2; Settings['Editor']="$2"; shift;;
            -a|--all ) Settings['All']=true;;
            -A|--selective ) Settings['All']=false;;
            -m|--music-place )
                if ! ([ -d "$2" ] && [ -w "$2" ]) ; then
                    error "'$2' is not a writable directory to store music in" EX_CONFIG
                fi;;
            -X|--extensions )
                if [ -n "$2" ] ; then
                    Extensions=(${2//,/ })
                else
                    echo "Extensions: ${Extensions[@]// /,}"
                fi;;
            -s|--save ) save=true;;
            -l|--downloader ) require "$( get_prog_name "$2" )" 2; Settings['Downloader']="$2"; shift;;
            -k|--ask ) Settings['Play']=false; Settings['Download']=false;;
            -- ) break;;
            * ) error "Unknown argument: $1" EX_CONFIG;;
        esac
        shift
    done

    if ${Settings['All']} ; then
        local act=$( get_music_action )
        [ -z act ] && act="ask what to do with"

        is_yes -p "Are you sure you want to $act all tracks [y/n] ? " || Settings['All']=false
    fi

    if $save ; then
        log "Saving settings"
        save_settings
        exit
    fi

    log "Parsed Arguments successfully"
}

# Fills $Tracks by looking through $Sites for tracks ending in $Extensions. This is the core backend functionality.

find_tracks()
{
    log "Looking for tracks"

    local exts="${Extensions[@]// /|}"
    local num

    for site in "${Sites[@]}" ; do
        log "Checking site: '$site'"

        num=0
        [ "$1" = '-v' ] && echo "Parsing $site"

        for track in $( curl -Ls "$site" | egrep -o "\bhttp://.*\.("$exts")\b" ) ; do
            name="$( url_decode "$( basename "$track" )" auto )"
            Tracks["${name//+/ }"]="$track"
            ((num++))
        done

        log "Found $num track(s) at the site"
    done

    [ ${#Tracks[@]} = 0 ] && error "Couldn't find any tracks!" 0
}

# Edits $SettingsFile by ${Settings['Editor']}

edit_settings()
{
    log 'Edit settings requested'
    ${Settings['Editor']} "$SettingsFile"
}

# Handles music action

# @param $1 action
# @param $2 track url
# @param $3 track name (with extension)

handle_action()
{
    ([ -n "$1" ] && [ -n "$2" ] && [ -n "$3" ]) || return

    [ "$1" = 'download' ] || local name="$( sed -E 's/\.(\w+)$//' <<< "$3" )"
    $1 "$2" "$([ "$1" = 'play' ] && echo "$name" || echo "$3")" "$([ "$1" = 'download_then_play' ] && echo "$name")"
}

# Downloads a track to ${Settings['MusicPlace']} by ${Settings['Downloader']}
# If $3 is -v, Outputs the track location on disk.

# @param $1 the track url
# @param $2 the track name (with extension)
# @param $3 -v. optional.

download()
{
    local download_to="${Settings['MusicPlace']}/$2"

    log "Action: Download [$1] to [$download_to]"
    wget -c -O "$download_to" "$1"
    log "wget returned $?"

    if $Settings['Dropbox'] ; then
        cp "$download_to" "$HOME/Dropbox/Music"
        log "Copying file to dropbox"
    fi

    [ "$3" = '-v' ] && echo "$download_to"
}

# Plays a track by ${Settings['Player']}

# @param $1 the track location
# @param $2 the track name (without extension). optional.

play()
{
    log "Action: Play [$1]"
    [ -n "$2" ] && notify-send "PlayMusic: Playing $2"
    ${Settings['Player']} "$1"
}

# Downloads then plays a track

# @param $1 the track url
# @param $2 the track name (with extension).
# @param $3 the track name (without extension). optional.

download_then_play()
{
    log "Action: Download then Play [$1]"
    play $( download "$1" "$2" -v ) "$3"
}

# Asks the user what to do with a track

# @param $1 the other option, outputs nothing when chosen.

ask()
{
    local -A _MENU_CHOICES=(['Download']='download' ['Play']='play' ['Download then Play']='download_then_play' ["$1"]='')
    menu
}

# Main entry point

main()
{
    local site_number=0
    local com="$( get_music_action )"

    echo "Looking for Tracks in ${#Sites[@]} Site$([[ ${#Sites[@]} -gt 1 ]] && echo "s") .."
    find_tracks -v

    if ${Settings['All']} ; then
        [ -n "$com" ] || com="$( ask 'Exit' )"
        [ -n "$com" ] || _exit

        for track in "${Tracks[@]}" ; do
            handle_action "$com" "${Tracks["$track"]}" "$track"
        done
    else
        echo "Choose Track .."
        select track in "${!Tracks[@]}" 'Quit' ; do
            [ -n "$track" ] || continue
            [ "$track" = 'Quit' ] && _exit
            [ -n "$com" ] || com="$( ask 'Return back' )"
            [ -n "$com" ] || continue

            handle_action "$com" "${Tracks["$track"]}" "$track"
        done
    fi
}

# Handles signales. Should not be used directly.

forced_exit()
{
    _FORCED_EXIT=true error "Signal [$1] forced an exit." "$((128+$1))"
}

# Initializes the environment for processing

init_env()
{
    trap 'forced_exit 2' SIGINT
    trap 'forced_exit 3' SIGQUIT
    trap 'forced_exit 4' SIGABRT
    trap 'forced_exit 15' SIGTERM

    for i in "$@" ; do
        if [ "$i" = "-r" ] || [ "$i" = '--recreate-settings' ] ; then
            save_settings
            _exit
        fi

        if [ "$i" = "-E" ] || [ "$i" = '--edit-settings' ] ; then
            edit_settings
            _exit
        fi
    done

    mkdir -p "${Settings['MusicPlace']}"

    load_settings
    parse_args "$@"

    if [ ${#Sites[@]} = 0 ] ; then
        error "No Sites were found!" 0
    fi
}

ARGS="$@"
init_env "$ARGS"

require "$( get_prog_name "${Settings['Player']}" )" "$( get_prog_name "${Settings['Editor']}" )" "$( get_prog_name "${Settings['Downloader']}" )"

main "$ARGS"
#!/bin/bash

declare -r SettingsFile="$HOME/.config/PlayMusic/settings.conf"
declare -r _LOGFILE="$HOME/.config/PlayMusic/logfile.log"
declare -r Version=0.3.2

source bash_lib

log "Program started: $1"

declare -A Settings=(
    ['Dropbox']=false               # Wether to copy downloaded tracks to the dropbox folder
    ['TimesToPlay']=1               # Number of times to play a track
    ['Download']=false              # Wether to download the track
    ['Play']=true                   # Wether to play the track
    ['All']=false                   # Wether to act on all tracks found, or just ask to specify track(s)
    ['Player']='mplayer'            # Command to invoke a CLI sound player
    ['Downloader']='wget -c -O'     # Command to invoke a CLI downloader
    ['Editor']="${EDITOR:-nano}"    # Command to invoke a CLI editor
    ['MusicPlace']="$HOME/Music"    # Directory to download music
)

declare Sites=(
    'http://ccmixter.org/view/media/samples/mixed'
    'http://ccmixter.org/view/media/remix'
    'http://mp3.com/top-downloads/genre/jazz/'
    'http://mp3.com/top-downloads/'
    'http://mp3.com/top-downloads/genre/rock/'
    'http://mp3.com/top-downloads/genre/hip hop/'
    'http://mp3.com/top-downloads/genre/emo/'
    'http://mp3.com/top-downloads/genre/pop/'
)   # Array of sites to search for tracks at

declare Extensions=(
    'mp3'
    'ogg'
)   # Array of extensions to look for

# Key: Track title. Value: Track url.
declare -A Tracks

# Displays settings in a user-friendly manner on the screen

info()
{
    cat << __EOF_

    # General Settings #
    Settings File:          $SettingsFile
    Log File:           ${_LOGFILE}
    Version:            $Version

    # Behaviour Settings #
    Dropbox Support:        ${Settings['Dropbox']}
    Music Place:            ${Settings['MusicPlace']}
    Player:             ${Settings['Player']}
    Editor:             ${Settings['Editor']}
    Player:             ${Settings['Player']}

    # Music Action Settings #
    Times to Play:          ${Settings['TimesToPlay']}
    Act on all tracks:      ${Settings['All']}
    Action:             $( get_music_action )

__EOF_
}

# Saves $Settings and $Sites to $SettingsFile in a valid format

save_settings()
{
    log "Creating settings file"

    mkdir -p "$( dirname "$SettingsFile" )"
    echo "Created on $( date )" > "$SettingsFile" || _FORCED_EXIT=true error "Fatal Error!" $?

    for option in "${!Settings[@]}" ; do
        echo "$option = ${Settings["$option"]}" >> "$SettingsFile"
    done

    echo "Sites" >> "$SettingsFile"
    for site in "${Sites[@]}" ; do
        echo "$site" >> "$SettingsFile"
    done

    log "Created settings file successfully"
}

# Extracts program name from a command

# @param $1 the cmd.

get_prog_name()
{
    cut -d ' ' -f 1 <<< "$1"
}

# Load $Settings and $Sites from the $SettingsFile

load_settings()
{
    log "Parsing settings file"

    local line_number=0 line
    local key value
    local correct
    local is_site=false site

    # Check Settings file
    if ! ([ -r "$SettingsFile" ] || [ -f "$SettingsFile" ]) ; then
        save_settings
        return
    fi

    while read -r line; do
        ((line_number++))

        if [ -z "$line" ] || [ $line_number = 1 ] ; then
            continue
        fi

        correct=false

        # Check line format
        if $is_site ; then
            Sites["${#Sites[@]}"]="$line"
            continue
        else
            if [ "$line" = "Sites" ] ; then
                is_site=true
                Sites=()
                continue
            elif ! egrep "^[a-zA-Z]+\s*=.+$" -q <<< "$line" ; then
                error "$SettingsFile:$line_number: Incorrect format of line" EX_CONFIG
            fi
        fi

        # Extract Key,Value pair
        key="$( egrep -o '^[a-zA-Z]+' <<< "$line" )"
        value="$( sed -E "s/^$key\s*=\s*(.+)$/\1/" <<< "$line" )"

        # Check if value is valid
        case "$key" in
            'Dropbox'|'Download'|'All'|'Play' )
                if egrep 'false|true' -q <<< "$value" ; then
                    correct=true
                else
                    error "Expected true or false"
                fi;;
            'TimesToPlay' )
                if is_num "$value" ; then
                    correct=true
                else
                    error "Expected a number"
                fi;;
            'Player'|'Editor'|'Downloader' )
                if is_prog_there "$( get_prog_name "$value" )" ; then
                    correct=true
                else
                    error "Expected an executable program"
                fi;;
            'MusicPlace' )
                if [ -d "$value" ] && [ -w "$value" ] ; then
                    correct=true;
                else
                    error "'$value' is not a writable directory"
                fi;;
            * ) error "$SettingsFile:$line_number: Invalid option: $key\nValid Options are:\n" \
            "\tDropbox, Download, All, Play, TimesToPlay, Player, Editor" EX_CONFIG;;
        esac

        if ! $correct ; then
            error "$SettingsFile:$line_number: Invalid value: '$value' to option: '$key'" EX_CONFIG
        fi

        Settings["$key"]="$value"
    done < "$SettingsFile"

    log "Parsed settings file successfully"
}

# Displays program usage in a user-friendly manner to screen

usage()
{
cat << _USAGE_
    Usage: ./PlayMusic
        -v|--version            Output version then exit.
        -h|--help               View this help then exit.
        -x|--dropbox            Allow copying downloaded files to $HOME/Dropbox.
        -t|--playtimes [num]    Times to play a track.
        -d|--download           To download a track without asking.
        -D|--no-download        To not download a track without asking.
        -p|--play               To play a track without asking.
        -P|--no-play            To not play a track without asking.
        -k|--ask                To force ask what to do with a track.
        -a|--all                To act on all tracks found.
        -y|--player [cmd]       The command to run the music player.
        -e|--edtor [cmd]        The command to run the editor.
        -l|--downloader         The command to run the downloader.
        -m|--music-place [dir]  To specify a music directory other than the one found at the settings file.
        -r|--recreate-settings  To recreate the settings file to default then exit.
        -E|--edit-settings      To edit the settings file then exit.
        -s|--save               To save the given settings (runs after analyzing all options).
        -i|--info               To display given settings so far then exit.
_USAGE_
}

# Outputs the function name of what to do with a track

get_music_action()
{
    if ${Settings['Download']} && ! ${Settings['Play']} ; then
        echo "download"
    elif ! ${Settings['Download']} && ${Settings['Play']} ; then
        echo "play"
    elif ${Settings['Download']} && ${Settings['Play']} ; then
        echo "download_then_play"
#   else
#       echo "ask"
    fi
}

# Parses program arguments

# @param $1 program arguments

parse_args()
{
    log "Parsing Arguments"

    local save=false
    local args=`getopt -o vhxt:dpDPy:e:amX::islk --long version,help,dropbox,playtimes:,download,play,no-download,no-play,player:,editor:,all,music-place:,extensions::,info,save,downloader,ask -n 'PlayMusic' -- "$@"` || error "Internal Error!" EX_SOFTWARE

    eval set -- "$args"

    while true ; do
        case "$1" in
            -v|--version ) echo "$Version"; _exit;;
            -h|--help ) usage; _exit;;
            -i|--info ) info; _exit;;
            -x|--dropbox ) Settings['Dropbox']=true;;
            -t|--playtimes )
                if is_num "$2" ; then
                    Settings['TimesToPlay']="$2"
                else
                    if [ -n "$2" ] ; then
                        error "'$2' is not a number" EX_CONFIG
                    else
                        error "Please provide a number for the '$1' option" EX_CONFIG
                    fi
                fi; shift;;
            -d|--download ) Settings['Download']=true;;
            -p|--play ) Settings['Play']=true;;
            -D|--no-download ) Settings['Download']=false;;
            -P|--no-play ) Settings['Play']=false;;
            -y|--player ) require "$( get_prog_name "$2" )" 2; Settings['Player']="$2"; shift;;
            -e|--editor ) require "$( get_prog_name "$2" )" 2; Settings['Editor']="$2"; shift;;
            -a|--all ) Settings['All']=true;;
            -A|--selective ) Settings['All']=false;;
            -m|--music-place )
                if ! ([ -d "$2" ] && [ -w "$2" ]) ; then
                    error "'$2' is not a writable directory to store music in" EX_CONFIG
                fi;;
            -X|--extensions )
                if [ -n "$2" ] ; then
                    Extensions=(${2//,/ })
                else
                    echo "Extensions: ${Extensions[@]// /,}"
                fi;;
            -s|--save ) save=true;;
            -l|--downloader ) require "$( get_prog_name "$2" )" 2; Settings['Downloader']="$2"; shift;;
            -k|--ask ) Settings['Play']=false; Settings['Download']=false;;
            -- ) break;;
            * ) error "Unknown argument: $1" EX_CONFIG;;
        esac
        shift
    done

    if ${Settings['All']} ; then
        local act=$( get_music_action )
        [ -z act ] && act="ask what to do with"

        is_yes -p "Are you sure you want to $act all tracks [y/n] ? " || Settings['All']=false
    fi

    if $save ; then
        log "Saving settings"
        save_settings
        exit
    fi

    log "Parsed Arguments successfully"
}

# Fills $Tracks by looking through $Sites for tracks ending in $Extensions. This is the core backend functionality.

find_tracks()
{
    log "Looking for tracks"

    local exts="${Extensions[@]// /|}"
    local num

    for site in "${Sites[@]}" ; do
        log "Checking site: '$site'"

        num=0
        [ "$1" = '-v' ] && echo "Parsing $site"

        for track in $( curl -Ls "$site" | egrep -o "\bhttp://.*\.("$exts")\b" ) ; do
            name="$( url_decode "$( basename "$track" )" auto )"
            Tracks["${name//+/ }"]="$track"
            ((num++))
        done

        log "Found $num track(s) at the site"
    done

    [ ${#Tracks[@]} = 0 ] && error "Couldn't find any tracks!" 0
}

# Edits $SettingsFile by ${Settings['Editor']}

edit_settings()
{
    log 'Edit settings requested'
    ${Settings['Editor']} "$SettingsFile"
}

# Handles music action

# @param $1 action
# @param $2 track url
# @param $3 track name (with extension)

handle_action()
{
    ([ -n "$1" ] && [ -n "$2" ] && [ -n "$3" ]) || return

    [ "$1" = 'download' ] || local name="$( sed -E 's/\.(\w+)$//' <<< "$3" )"
    $1 "$2" "$([ "$1" = 'play' ] && echo "$name" || echo "$3")" "$([ "$1" = 'download_then_play' ] && echo "$name")"
}

# Downloads a track to ${Settings['MusicPlace']} by ${Settings['Downloader']}
# If $3 is -v, Outputs the track location on disk.

# @param $1 the track url
# @param $2 the track name (with extension)
# @param $3 -v. optional.

download()
{
    local download_to="${Settings['MusicPlace']}/$2"

    log "Action: Download [$1] to [$download_to]"
    wget -c -O "$download_to" "$1"
    log "wget returned $?"

    if $Settings['Dropbox'] ; then
        cp "$download_to" "$HOME/Dropbox/Music"
        log "Copying file to dropbox"
    fi

    [ "$3" = '-v' ] && echo "$download_to"
}

# Plays a track by ${Settings['Player']}

# @param $1 the track location
# @param $2 the track name (without extension). optional.

play()
{
    log "Action: Play [$1]"
    [ -n "$2" ] && notify-send "PlayMusic: Playing $2"
    ${Settings['Player']} "$1"
}

# Downloads then plays a track

# @param $1 the track url
# @param $2 the track name (with extension).
# @param $3 the track name (without extension). optional.

download_then_play()
{
    log "Action: Download then Play [$1]"
    play $( download "$1" "$2" -v ) "$3"
}

# Asks the user what to do with a track

# @param $1 the other option, outputs nothing when chosen.

ask()
{
    local -A _MENU_CHOICES=(['Download']='download' ['Play']='play' ['Download then Play']='download_then_play' ["$1"]='')
    menu
}

# Main entry point

main()
{
    local site_number=0
    local com="$( get_music_action )"

    echo "Looking for Tracks in ${#Sites[@]} Site$([[ ${#Sites[@]} -gt 1 ]] && echo "s") .."
    find_tracks -v

    if ${Settings['All']} ; then
        [ -n "$com" ] || com="$( ask 'Exit' )"
        [ -n "$com" ] || _exit

        for track in "${Tracks[@]}" ; do
            handle_action "$com" "${Tracks["$track"]}" "$track"
        done
    else
        echo "Choose Track .."
        select track in "${!Tracks[@]}" 'Quit' ; do
            [ -n "$track" ] || continue
            [ "$track" = 'Quit' ] && _exit
            [ -n "$com" ] || com="$( ask 'Return back' )"
            [ -n "$com" ] || continue

            handle_action "$com" "${Tracks["$track"]}" "$track"
        done
    fi
}

# Handles signales. Should not be used directly.

forced_exit()
{
    _FORCED_EXIT=true error "Signal [$1] forced an exit." "$((128+$1))"
}

# Initializes the environment for processing

init_env()
{
    trap 'forced_exit 2' SIGINT
    trap 'forced_exit 3' SIGQUIT
    trap 'forced_exit 4' SIGABRT
    trap 'forced_exit 15' SIGTERM

    for i in "$@" ; do
        if [ "$i" = "-r" ] || [ "$i" = '--recreate-settings' ] ; then
            save_settings
            _exit
        fi

        if [ "$i" = "-E" ] || [ "$i" = '--edit-settings' ] ; then
            edit_settings
            _exit
        fi
    done

    mkdir -p "${Settings['MusicPlace']}"

    load_settings
    parse_args "$@"

    if [ ${#Sites[@]} = 0 ] ; then
        error "No Sites were found!" 0
    fi
}

ARGS="$@"
init_env "$ARGS"

require "$( get_prog_name "${Settings['Player']}" )" "$( get_prog_name "${Settings['Editor']}" )" "$( get_prog_name "${Settings['Downloader']}" )"

main "$ARGS"
deleted 45 characters in body; edited title
Source Link
Jamal
  • 34.9k
  • 13
  • 133
  • 237

Simple Bash Music Player - follow-up

The program uses a bash utility library I've created for common tasks, called bash_lib. Here it is:

Thanks in advance.

Simple Bash Music Player

The program uses a bash utility library I've created for common tasks, called bash_lib. Here it is:

Thanks in advance.

Simple Bash Music Player - follow-up

The program uses a bash utility library I've created for common tasks, called bash_lib:

edited body
Source Link
Amr Ayman
  • 853
  • 1
  • 8
  • 24
Loading
Source Link
Amr Ayman
  • 853
  • 1
  • 8
  • 24
Loading