So, the BASH downloader worked well enough for what it was. Then I decided to do some functional upgrades. It got so complex for a BASH script that I decided to make it a C# app. The whole dotnet platform is kind of the new Java, so why not roll with it? Besides, I’m getting pretty good at this dotnet on Linux thing–all my servers that I run are Linux (mostly Debian). I used to be pretty big on PHP on that platform, but as dotnet has matured that’s gone by the wayside.

The end result was pretty good, I think. Fairly simple service-layer architecture, with one service for dealing with episode management, and one for managing the metadata. I have it set up as a scheduled task that runs every four hours on my Plex box. It’s done fairly well, after ironing out the kinks over a couple weeks.

The neat little part of it was Main was as simple as:

private static async Task Main(string[] args)
    {
        var serviceProvider = ServiceConfiguration
            .AddServices()
            .BuildServiceProvider();

        using (var scope = serviceProvider.CreateScope())
        {
            var episodeProcessing = scope.ServiceProvider.GetService<IEpisodeProcessing>();
            if (episodeProcessing != null)
            {
                await episodeProcessing.ProcessEpisodes();
            }
        }
    }

Much like the BASH script, it pulls from a list that I define–though, I threw that list into a SQLite db. Still thinking about how I want to manage the list. Possibly a web app, possibly an Electron app. A web app would be pretty simple, but I’ve poked at Electron a bit and I do dig how it’s pretty much a web app in spirit. We’ll see. It’s not like I add podcasts to my list very often, so I can ponder a bit.

A long time ago, in a galaxy far, far away…in the late 20th century…a young chap started out in Computer Science at the University of Colorado in Denver.

Things were going pretty well. Second semester hit, and the adventure really began. There was a course in object oriented development in C++ in this lad’s early CS career. He hadn’t seen objects before, and only had a notional understanding of what they were—if even that.

A project that took pretty much all of the semester was to create a set of classes to manipulate and display a linked list. This was his first foray into abstract data types. It was quite the effort across that semester, but it got done—and, not only was a program which handled CRUD for a linked list the end result, but an indexed one—to allow you to access values non-sequentially as if it were an array.

It was a cool project in the end, and this lad looks back with fondness at those early days, when all of it was so new.

Flash forward nearly three decades to the advent of LLM, Large Language Models.

It’s become a bit of a hobby of mine to throw things at ChatGPT, one of the more popular LLM generators these days, to see what it can do.

I gave it the basic requirements of that very same semester project freshman year. It generated a whole class, complete with a main driver, in mere seconds. You can’t help but feel a mix of fascination and a bit of melancholy as you watch all of that effort (which I could now do in less than an hour, myself) do it all in just a mere moment.

Also makes me wonder how creative professors have to get these days to come up with a project that can’t be easily generated 😂

ChatGPT

ChatGPT helps you get answers, find inspiration and be more productive. It is free to use and easy to try. Just ask and ChatGPT can help with writing, learning, brainstorming and more.

I’m sure I’ll be modifying this more over time. But, it reads from a text file (podcasts.txt) in the given DOWNLOAD_PATH–this file contains the RSS URL(s) for the podcast(s). It organizes the podcasts into directories, if those directories don’t already exist. It uses eyeD3 to add metadata (including cover art). Works fairly well, though I’m still stomping bugs.

Maybe next I’ll put together a nice little frontend for it–maybe convert the whole thing to a Blazor app. But, for now, it does the thing and it’s fun being a script kiddie 🙂

#!/bin/bash

create_path() {
    TITLE_PATH="${DOWNLOAD_PATH}/${1}"

    if [ ! -d "${TITLE_PATH}" ]; then
       mkdir "${TITLE_PATH}"
    fi
}

sanitize_filename() {
  local filename="$1"
  # Replace unfriendly characters with underscore
  sanitized=$(echo "$filename" | sed 's/[\/:*?"<>|]/_/g')
  echo "$sanitized"
}

dl_podcast() {
    local RSS_FEED_URL=$1

    curl -s "$RSS_FEED_URL" > /tmp/rss_feed.xml

    title=$(get_podcast_title "${RSS_FEED_URL}")

    create_path "${title}"

    # Initialize arrays to store episode titles and enclosure URLs
    titles=()
    urls=()

    # Extract episode titles and enclosure URLs
    mapfile -t titles < <(xmllint --xpath '//item/title/text()' /tmp/rss_feed.xml)
    mapfile -t urls < <(xmllint --xpath '//item/enclosure/@url' /tmp/rss_feed.xml)

    # Print the episode titles and enclosure URLs
    j=${#titles[@]}
    padtowidth=${#j}
    for ((i=0; i<${#titles[@]}; i++)); do
	ep_title=$(echo "${titles[$i]}" | sed -e 's/<!\[CDATA\[//' -e 's/\]\]>//')
        url=`echo ${urls[$i]} | sed "s/url=//g"`
        url=`echo ${url} | sed "s/\"//g"`
        filenum=`printf "%0*d\n" $padtowidth $j`
        file="${filenum} $ep_title.mp3"
        file=$(sanitize_filename "${file}")

        if [ ! -f "${TITLE_PATH}/${file}" ]; then
            echo "Downloading: ${file} (${url})"
            wget -qO "${TITLE_PATH}/${file}" "${url}"
	    /usr/bin/eyeD3 --track "${filenum}" --disc-num "1" "${TITLE_PATH}/${file}"
	    /usr/bin/eyeD3 --add-image="/tmp/cover.jpg":FRONT_COVER "${TITLE_PATH}/${file}"
        fi
        j=`expr ${j} - 1`
    done

    unset ${TITLE_PATH}
}

get_podcast_title() {
    local url="${1}"
    local title="$(curl -s "$url" | grep -oP '<title>\K[^<]+' | sed -n '2p')"
    echo "${title}"
}

get_podcast_cover() {
    local url=$1
    local cover_url=$(curl -s "$url" | grep -oP '<itunes:image\s+href="\K[^"]+' | head -n 1)

    curl -s -o /tmp/cover.jpg ${cover_url}
}

cleanup() {
    rm /tmp/cover.jpg
    rm /tmp/rss_feed.xml
}

read_lines_into_array() {
    local file=$1

    # Check if file exists
    if [ ! -f "$file" ]; then
        echo "Error: File $file not found."
        exit 1
    fi

    # Read the file line by line and append each line to the array
    mapfile -t lines_array < "${file}"

    # Return the array
    echo "${lines_array[@]}"
}

process_podcasts() {
    local -n podcasts=$1

    for link in "${podcasts[@]}"; do
        get_podcast_cover "$link"

        title=$(get_podcast_title "$link")

        dl_podcast $link
    done
}

main()
{
    DOWNLOAD_PATH="<PODCAST DIRECTORY GOES HERE>"
    TITLE_PATH=${DOWNLOAD_PATH}

    if [ $# -ne 1 ]; then
        echo "Usage: $0 <rss url>"
        exit 1
    fi

    file=$1

    # Call the function and store the result in an array
    declare -A links
    links=$(read_lines_into_array "$file")

    # Print the contents of the resulting array
    process_podcasts links
    cleanup
}

main "$@"

Been working on a podcast downloader for use with Plex, since Plex decided to take away podcasts–basically I keep the ones I wanna retain long-term in a library collection now. The downloader is a simple BASH script. Seems to be nearly perfected, even adds metadata (track #, cover art, etc) to the downloaded mp3.

I think my last little issue is that I need to have it rename all the files whenever episodes hit a factor of 10 (e.g., if there are 10 episodes, episode one should be “01 – Title”, when it hits 100, episode one should be renamed to “001 – Title”). It’s a little thing–I just like my files sorting properly by name, no matter what OS I’m listing them in. And Linux sorts in lexicographical by default, as opposed to natural order 🙄

Some guys work in the garage on Saturday mornings–I work on code 🤣

So, in my last post I outlined a method of packaging MS-DOS games for convenient usage in macOS.

Another method I had read of packaging in macOS, but hadn’t really played with much, had been using BASH as the launcher as opposed to AppleScript.

A friend of mine was playing around with it so I decided to, as well. My biggest difficulty was getting path straight. Ultimately, it wasn’t all that big of a deal (just a little grumbling here, maybe a little cussing there), and once I got that down I spent an afternoon fashioning a script to automate the process of packaging DOS programs. I’m pretty sure this would have been a bit steeper a hill to climb had I stayed with AppleScript. Another plus side of this method over the last one is that you don’t need permissions in Finder to get your favorite nostalgic games and such up and running. The structure of the packages are a bit more simple, too, lending to the slight decrease in total size.

It’s a living and breathing script, but I have it up on github. It’s pretty simple at this point, and I’ll probably go back in and bulletproof it a bit and make a few tweaks, but it helped me repackage all my DOS games just today.

Overall, I think this is a better method, definitely with simpler setup and a comparatively tiny BASH script driving app launching. The resultant apps are a good bit smaller, and I can even make them self-contained launchers with their own instances of DOSBox as I did before.

I’m bundling DOSBox with mine, but you can always install dosbox via brew and use one common instance for all of your packages in your execute script, thereby reducing size even more–saves about 11-12MB, which is a whole heaping amount of space in DOS terms 😉

Don’t get me wrong, though, it was really cool learning some AppleScript. Still, that one doesn’t need to be bothered by a lot of configuration on the user end definitely appeals. Plus, I do love me some BASH scripting.

One thing I enjoy doing, as far as gaming (and even computing in general goes) is looking back to where we came from during my lifetime, thus far. Back in the early 90s, DOS was king and that was my introduction to the x86 world. On our 486DX-33, I explored a whole new world of computing. Around the same time, Macintoshes were big in schools and thus I had a pretty balanced upbringing in the platform world. I didn’t really get to Linux until college, but that was already–sadly–so long ago that I’m finding myself looking back similarly on that platform.

But, DOS is in the title, so we’ll look at that one. DOSBox is a popular platform for DOS emulation and will, hopefully, remain so for a long time. So, here I’d like to chronicle how I package DOS games for play on macOS. Totally a scriptable process, I think, and maybe one day I’ll get to it. But, for now, it’s a pretty manual one. I’ve been doing it this way since probably OS X Tiger, and the process hasn’t really changed much, outside of some added security features getting in the way since–for which there are valid workarounds.

Pre-requisites

  • You’re going to need a local copy of DOSBox
  • A DOSBox config file, tailored to your liking
  • A copy of a directory containing the DOS game that you will want to be playing

First off, when I started on this little journey I had to learn a little bit about AppleScript. Specifically, functionality geared around Finder so as to determine the package’s current path so as to access the resources needed for this little package. The basic form of this AppleScript is pretty simple.

tell application "Finder"
	set current_path to container of (path to me) as alias
	set posix_path to POSIX path of current_path
end tell

set dosbox_path to "\"" & posix_path & "/***INSERT APP NAME HERE***.app/Contents/Resources/DOSBox\"" as string
set config_path to "\"" & posix_path & "/***INSERT APP NAME HERE***.app/Contents/Resources/dosbox.conf\"" as string
set executable_path to "\"" & posix_path & "/***INSERT APP NAME HERE***.app/Contents/Resources/***APP FOLDER***/***EXECUTABLE NAME***\"" as string

do shell script dosbox_path & " -exit -conf " & config_path & " " & executable_path

The first lines set current path relative to where the script currently resides. The lines below this set the various paths that will be needed to run the DOS program properly–the path to the DOSBox executable, the configuration file and the DOS executable (EXE/BAT).

“APP NAME” is the name of the app in question (e.g., “Doom II”, “Quake”, “Wolfenstein 3D”, etc). “APP FOLDER” is the folder in which the game executable resides (e.g., WOLF3D, DOOM2, QUAKE, etc). And then, of course, “EXECUTABLE NAME” is the name of the executable file (with .exe or .bat extension).

The last line is the meat and potatoes of the script. Pretty simple. It just tells the instance of DOSBox within the package to run, with the configuration file given on the exe/bat file that you want to launch.

So, what we want to do is crack open Script Editor, which resides in Applications -> Utilities. Paste this code into a new script window, substituting the app, folder and executable names (delimited by *** … ***). Save this script. When you go to save, you’ll find a dialog that looks like this

The key here is to use “Application” as the file format, and also to save as the “APP NAME” you define above in the script.

Once this is done, Right-click on the file wherever you may have saved it and click “Show Package Contents”. In Finder, you’ll see a “Contents” folder. Click into that and you’ll see a few more folders/files. The one we’re concerned with here is “Resources”. Click into that folder and you’ll see a few files and folders–including a “Scripts” folder which contains the script that you just saved. We don’t really have to worry about that any more, at this point, though.

What you want to next is copy over your DOS directory over into this Resources folder.

After this, you’re going to want to snag a copy of the DOSBox executable from its package (default install folder for that is in “Applications”). Right-click on that app, and again select “Show Package Contents”. Go into Contents->MacOS, select the DOSBox file that’s in that folder, copy it, and paste it into the Resources folder of your new shiny app that you made. We’re doing this step so as to make your package self-contained, so that it doesn’t have to necessarily rely on DOSBox being installed on your machine (e.g., if you throw it on another machine). It’s a relatively small executable (11MB), so it doesn’t generally bother me to have multiple copies of it–though, you could probably use soft links to achieve the same goals without multiplying the number of executables.

So, now that we have that out of the way, we need to copy over our DOSBox config file into that Resources directory.

In the end, you should have your DOS folder containing the game resources and executables, the DOSBox executable, and DOSBox config file all in that same Resource folder.

At this point, your package should be runnable. One thing I like to do to cap it off is to add an icon, so that it doesn’t have that funny default script icon. To do this, you’ll need an icon file (.icns). You can find an image you want to use on the vast Internet, and convert it to .icns. There are a number of online converters out there or, if you really don’t trust them, you could always use your image editor of choice and/or the SIPS utility from the Terminal.

Once you have an ICNS file, no matter how you slice it, all you have to do is right-click on your app and select “Get Info”. Take your ICNS file and drag it up into the image area of this dialog and that will give your app the icon you’ve always wanted. Since there weren’t really icons back in DOS days, it’ll never be completely authentic. Personally, I just generally find images which can be recognizable as being associated with the game in question and can be seen at that kind of a scale and just roll with it.

The last thing we need to look at is the fact that this script is going to access Finder.

You can click OK to continue, but you’ll get this dialog every other time you run your program if you don’t allow access. To do this, once you’ve first launched the program, exit out of it and you should see a dialog that looks a little something like this:

Click “Open System Preferences”, and you’ll be in the “Security & Privacy” section. Make sure that the program you want to run has a checkbox next to it (after unlocking the pane with your password or fingerprint, whichever may apply).

And, voila! You shouldn’t be pestered with this particular dialog again–unless Apple starts clamping down further. I really can’t blame them. We’re doing script kiddie things here, and macOS has no idea who we are–as an enthusiast, I don’t necessarily see the need to digitally sign this kind of stuff…so, I won’t for as long as I can avoid it 😉

So, that’s about it. That’s a method of setting up nice, neat little app packages for all of your old DOS games that you thought you’d never see again. Obviously, you can really do this for any DOS program, not just games, but I don’t exactly find much utility in Word for DOS anymore 😉 But, if you’re the nostalgic type who has some favorite DOS program out there, this process should work just fine for those.

Alas, at this point, it’s not nearly so neat and tidy a process for Windows or Linux–just simple shortcuts for those, with a dependency on a DOSBox installation somewhere on the machine. Ultimately, the same effect, though.

So, an annoying little thing about TextEdit on macOS in the later versions is that opening it up defaults to a dialog which asks you if you want to open an existing document or create a new one. I want my text editors to behave like Notepad, gEdit, etc. and just give me a freaking blank window.

To this end, all one needs to do is the following. Crack open a Terminal window and put in the following:

defaults write com.apple.TextEdit NSShowAppCentricOpenPanelInsteadOfUntitledFile -bool false

Then, the next time you open TextEdit, you’ll be welcomed by a nice, blank and new document.