Pointless Waymarks Software

Software from the Desert Southwest

Created and Updated by Charles on 1/20/2025.

The Pointless Waymarks Cloud Backup and Pointless Waymarks PowerShell Runner have been running happily on my main computer for a bit without me thinking about them.

2025 January Pointless Waymarks Cloud Backup Main Window before LiveCharts2
2025 January Pointless Waymarks Cloud Backup Main Window before LiveCharts2.
2025 January Power Shell Runner Job List before LiveCharts2
2025 January Power Shell Runner Job List before LiveCharts2.

I was glad that the programs had been doing their job - but I was disappointed that I couldn't verify they had done their job more quickly. The data about runs needed a better presentation - enter LiveCharts2 (on GitHub): "Simple, flexible, interactive & powerful charts, maps and gauges for .Net, LiveCharts2 can now practically run everywhere Maui, Uno Platform, Blazor-wasm, WPF, WinForms, Xamarin, Avalonia, WinUI, UWP."

The LiveCharts2 site site has galleries, code and documentation that make it quick to add simple charts. The charts are fairly new, so I'm still testing/working, but I'm happy with the first round of results!

2025 January Pointless Waymarks Cloud Backup Job List with Active Upload
2025 January Pointless Waymarks Cloud Backup Job List with Active Upload.
2025 January Pointless Waymarks PowerShell Runner Job List
2025 January Pointless Waymarks PowerShell Runner Job List.

Created and Updated by Charles on 1/12/2025.

My happy feeling of being caught up on a personal project was recently crushed when I realized that dragging a file from my Android phone onto the Pointless Waymarks CMS resulted in ... nothing.

Files on my Windows computer and files from various network shares dropped without issues into the app - and the files from the Android phone worked fine in Windows Explorer between locations - but dragging Android to a WPF app didn't work.

I quickly turned to my buddy GitHub Copilot, who has given me some nice drag-and-drop answers in the past. My buddy produced some code that looked probable - and failed first try (hard to believe that this isn't a standard behavior most people experience these days). In a bit of behavior that might prove that GitHub Copilot is fundamentally human when asked about the exception from its code, the response was to blame the (probably corrupt) incoming data!

I thrashed around for a while looking for different ideas and eventually ended up on a human answer from the depths of Stack Overflow - c# - Dropped zip file causes e.Data.GetData("FileContents") to throw an exception - Stack Overflow (as per usual be sure to read the comments on the answer - the bottom comments will save you some time).

As with many things, it is a little hard to understand why the .NET drag-and-drop abstractions don't help more in this case and leaves you mucking around with FORMATETC and binary formats.

My code based on c# - Dropped zip file causes e.Data.GetData("FileContents") to throw an exception - Stack Overflow (note that I use the GongSolutions.WPF.DragDrop in my WPF projects) can currently be found in the PointlessWaymarksTools Project:


Created by Charles on 1/6/2025. Updated on 1/7/2025.

Two recent posts on PointlessWaymarks.com - Camera and Lens Going into 2025 and 2024 Photographs - caused me to look a little more closely at the at the 'Details' presentation generated by the Pointless Waymarks CMS for photographs.

2025 January Photo Content Page Details Block - Before Update
Photo Content Page Details Block - Before Update.

Before considering any changes I took a quick look at the details blocks of some well known photo sites: Flickr, 500px, SmugMug, PhotoPrism (you will have to find example pages to see the position and interaction associated with these).

2025 January Flickr Photo Details Block
Flickr Photo Details Block.
2025 January 500px Photo Details Block
500px Photo Details Block.
2025 January Smugmug Compact Photo Details
Smugmug Compact Photo Details.
2025 January Photoprism Details Block
Photoprism Details Block.

There are nice details in all of the examples above but ultimately for the purposes and currently style of a Pointless Waymarks CMS site I think the basic setup of the current block holds up reasonably well.

But it is certainly not perfect and I did find some improvements to make:

  • Move the focal length to a more useful/logical position
  • Correct the Aperture from f to ƒ
  • Remove the 'Details:' text/header. In the big picture this below the fold block of data is obscure information for curious photographers. There could be some utility in links or other help for someone who is curious but doesn't understand - however the text 'Details:' was not helping anyone.
2025 January Photo Content Page Details Band - After Update
2025 January Photo Content Page Details Band - After Update.

The most interesting programing detail in this update was cleaning up the Aperture presentation. I don't want to limit what can be entered as Aperture and instead just cleanup well known formats to get a consistent string as possible. A simple example might be turning f9.0 into ƒ/9. The code I came up with:

    public static string ApertureCleanup(string? aperture)
    {
        if (string.IsNullOrWhiteSpace(aperture))
            return string.Empty;

        // Remove f, ƒ, f/ or ƒ/ at the start of the aperture string
        var apertureForCleaning = aperture.Trim();
        if (apertureForCleaning.StartsWith("f/", StringComparison.OrdinalIgnoreCase) || apertureForCleaning.StartsWith("ƒ/", StringComparison.OrdinalIgnoreCase))
            apertureForCleaning = apertureForCleaning.Substring(2);
        else if (apertureForCleaning.StartsWith("f", StringComparison.OrdinalIgnoreCase) || apertureForCleaning.StartsWith("ƒ", StringComparison.OrdinalIgnoreCase))
            apertureForCleaning = apertureForCleaning.Substring(1);

        if (decimal.TryParse(apertureForCleaning, out var apertureValue))
        {
            var cultureSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
            var apertureStringDecimal = apertureValue.ToString(CultureInfo.CurrentCulture);
            apertureStringDecimal = apertureStringDecimal.Contains(cultureSeparator) ? apertureStringDecimal.TrimEnd('0').TrimEnd(cultureSeparator.ToCharArray()) : apertureStringDecimal;
            
            return $"ƒ/{apertureStringDecimal}";
        }

        // Return the original string if it is not a recognizable format and value
        return aperture.TrimNullToEmpty();
    }

The detail that 'got me' was turning the decimal to a no trailing zeros string. I thought the solution was a 'G0' format - but ".00009" would become 9E-05. As far as I know it is incredibly unlikely a valid Aperture value would ever hit that, but I ended up on Stack Overflow looking at c# - Remove trailing zeros. With all the format options available I was a bit surprised that the solution ultimately involved string manipulation...

The recent Gear Post post on PointlessWaymarks.com is the first time I wanted to present the Photo Details outside of a Photo Page/Post. Pushing the full Photo Details Block into a post would be too much of a distraction, so I added a photo variation that includes the Photo Details in the caption.

The detail variation is made available by referring to the photo with {{photowdetails ....}} instead of the normal {{photo ...}} bracket code - example below.

2024 December Rail Feather
2024 December Rail Feather. Charles Miles. 12/3/2024. ƒ/9, 1/160, ISO 800, 70 mm, E 70-300mm F4.5-6.3 A047, SONY ILCE-7RM4, © 2024 Charles Miles.

These changes are barely large enough to write about but photography and photographs are important to me. The Pointless Waymarks CMS does not create dedicated photography sites - it does support all the pieces that you might need to tell stories with photography: files and images to reference, posts to bring things together, maps and geographic information...


Created by Charles on 12/28/2024. Updated on 12/30/2024.

2024 December Pointless Waymarks Feed Reader Feed Editor with Auto Mark Options
Pointless Waymarks Feed Reader Feed Editor with Auto Mark Options.

Using your software is a great way to make it better - three personal experiences with the Pointless Waymarks Windows Desktop Feed Reader:


Bruce Percy creates stunning images and - unusual these days - actually writes frequently on www.brucepercy.co.uk. The photos and thoughtful words are exactly the content I wrote the Pointless Waymarks Feed Reader to follow - high-value content, not a stream of attention-economy clickbait.

But lately I haven't been able to keep up - great content or not Bruce Percy's posts have made my Feed Reader Items seem more like a list to manage than a source of daily inspiration.


As a .NET Developer I consider the Morning Dew by Alvin Ashcraft – Daily links for Windows and .NET developers essential reading. The daily links are an easy way to keep in touch with the constantly evolving world of .NET and occasionally link to some package or concept that are stunningly valuable - and that I might otherwise not have found.

The Morning Dew hasn't been in my Feed Reader list because with nearly every workday publication it makes more sense just to visit the website. There isn't any mystery about whether there is a new post, and I long ago gave up on keeping up with every single day.


Matt Payne - Fine Art Landscape Photography Prints of Colorado and Beyond - creates excellent images, produces and hosts the F-Stop Collaborate and Listen Photography Podcast and has made his website an important part of his online presence.

I recently tried to add his site to my feeds via his Atom feed, but it only resulted in an error...


I initially imagined that the content I would follow in the Pointless Waymarks Windows Desktop Feed Reader would be infrequent enough that the program didn't need any options around automatically managing feed items. That has largely proved to be true, but some important outliers like the ones above have crept in, and it is now obvious that borrowing a few features found in other readers would be helpful - the Feeds now have options for:

  • Mark Read if More Than __ Items: Inspired by Bruce Percy this setting allows me to see some content but without the list ever growing too long if I don't catch up for a bit.
  • Auto Mark Read After __ Days: Inspired by Alvin Ashcraft's Morning Dew I'm using this set to '1' so that I only see the current post in my reader items - a nice compromise so that only the latest is ever in my morning reading.

Coding these features reminded me that I've been using FluentMigrator: Fluent migrations framework for .NET for over 4 years. I found FluentMigrator after being puzzled about how to make EF Core Migrations work nicely for my scenarios. In recent versions, EF Core Migrations would probably be less of a puzzle - both packages have improved. Some FluentMigrator code related to the changes above are included below. This simple usage/example isn't a tour-de-force (or maybe even best practices) example of FluentMigrator, but it is a simple pattern that has been very stable/easy/reliable for me over several years:

using System.Data;
using FluentMigrator;

namespace PointlessWaymarks.FeedReaderData.Migrations;

[Migration(202412260000)]
public class AddAutoMarkReadMigration : Migration
{
    public override void Down()
    {
        throw new DataException($"No Down Available for Migration {nameof(AddAutoMarkReadMigration)}");
    }

    public override void Up()
    {
        if (!Schema.Table("Feeds").Column("AutoMarkReadAfterDays").Exists())
            Execute.Sql(@"ALTER TABLE Feeds 
                    ADD COLUMN AutoMarkReadAfterDays INTEGER NULL");
        if (!Schema.Table("HistoricFeeds").Column("AutoMarkReadAfterDays").Exists())
            Execute.Sql(@"ALTER TABLE HistoricFeeds 
                    ADD COLUMN AutoMarkReadAfterDays INTEGER NULL");
    }
}

Line 71 from Matt Payne's (feed)[https://www.mattpaynephotography.com/rss.xml] on 12/28/2024:

<title>Kidney Transplant and Artistic Vision: Tracey Halladay&rsquo;s Powerful Photography Journey</title>
2024 December Pointless Waymarks Feed Reader Undeclared Entity Exception
An Xml Exception in the Feed Reader Parsing Code.

That line triggers a System.Xml.XmlException 'Reference to undeclared entity 'rsquo''. In many situations it could be productive to work with the producer of the XML to explore and resolve the error. In this situation I want to make the most of whatever is offered, so I added code to catch this specific error, parse out the entity that is causing problems, modify the feed, and try again.


    /// <summary>
    ///     Extracts the undeclared entity from and XML exception message - the intent is that
    ///     this can then be removed from the XML content and the parsing retried.
    /// </summary>
    /// <param name="exception"></param>
    /// <returns></returns>
    private static (string entity, int lineNumber, int linePosition) ExtractUndeclaredEntityInformation(
        XmlException exception)
    {
        var startIndex = exception.Message.IndexOf("undeclared entity '", StringComparison.Ordinal) +
                         "undeclared entity '".Length;
        var endIndex = exception.Message.IndexOf("'", startIndex, StringComparison.Ordinal);
        if (startIndex > 0 && endIndex > startIndex)
            return ($"&{exception.Message[startIndex..endIndex]};", exception.LineNumber, exception.LinePosition - 1);
        return (string.Empty, exception.LineNumber, exception.LinePosition - 1);
    }

    /// <summary>
    ///     Replaces a string in the content at the given line and position - but if the value to replace is not found at
    ///     the line and position, it replaces it everywhere in the content. This is very specific to parsing the feed
    ///     content and the fallback is just-in-case there is some unexpected difference between the line and position
    ///     from the exception and the parsing done in this method.
    /// </summary>
    /// <param name="content"></param>
    /// <param name="toReplace"></param>
    /// <param name="lineNumber"></param>
    /// <param name="linePosition"></param>
    /// <param name="replacement"></param>
    /// <returns></returns>
    private static string ReplaceStringAtLineAndPositionOrAllContentsIfNotFound(string content, string toReplace,
        int lineNumber, int linePosition, string replacement)
    {
        var lines = content.Split('\n');
        if (lineNumber - 1 < lines.Length)
        {
            var line = lines[lineNumber - 1];
            if (linePosition - 1 < line.Length && line.Substring(linePosition - 1, toReplace.Length) == toReplace)
            {
                var start = line.Substring(0, linePosition - 1);
                var end = line.Substring(linePosition - 1 + toReplace.Length);
                lines[lineNumber - 1] = start + replacement + end;
                return string.Join('\n', lines);
            }
        }

        // If the value to replace is not found at the given line and position, replace it everywhere in the content
        return content.Replace(toReplace, replacement);
    }

        XDocument? feedDoc = null; // 2.) read document to get the used encoding
        var tryProcess = true;
        var tryProcessCount = 0;

        //2024-12-27: 50 is arbitrary...
        while (tryProcess && tryProcessCount < 50)
        {
            tryProcessCount++;
            tryProcess = false;

            try
            {
                feedDoc = XDocument.Parse(feedContent);
            }
            catch (XmlException ex) when (ex.Message.Contains("Reference to undeclared entity"))
            {
                // Extract the undeclared entity from the exception message
                var entityExceptionInformation = ExtractUndeclaredEntityInformation(ex);
                if (!string.IsNullOrEmpty(entityExceptionInformation.entity))
                {
                    var undeclaredEntities = new Dictionary<string, string>
                    {
                        { "&rsquo;", "&#8217;" },
                        { "&lsquo;", "&#8216;" },
                        { "&rdquo;", "&#8221;" },
                        { "&ldquo;", "&#8220;" },
                        { "&nbsp;", "&#160;" }
                    };

                    // Remove the specific undeclared entity and retry parsing
                    var replacement = undeclaredEntities.GetValueOrDefault(entityExceptionInformation.entity, " ");
                    feedContent = ReplaceStringAtLineAndPositionOrAllContentsIfNotFound(feedContent,
                        entityExceptionInformation.entity, entityExceptionInformation.lineNumber,
                        entityExceptionInformation.linePosition, replacement);
                    tryProcess = true;
                }
                else
                {
                    throw;
                }
            }
        }

With only one feed sample to test for the Undeclared Entity issues I can't say whether this is best/right/most useful/most durable - but this code solves the current test case!

If you have any comments or suggestions contact me at [email protected] - Pointless Waymarks Windows Desktop Feed Reader use-at-your-own-risk Beta Version Installers available here.


Posts Before:
2024 December Pointless Waymarks Feed Reader Feed Editor with Auto Mark Options
2024 December Pi Sliced Day Photos Solstice to Solstice Sunsets Video Cover Image
2024 December CMS Photo Direction Display
2024 November Saguaros Against Tanque Verde Ridge