isaacschemm: A cartoon of myself as a snail (snail8)
[personal profile] isaacschemm

Pandacap: Part 5 - ActivityPub

A          screenshot of part of Pandacap's favorites page, showing          gallery-style thumbnails

If I'm thinking about how often I use it, the ActivityPub integration in Pandacap really isn't the most relevant part of the application to me on a regular basis (that would be the inbox, and its integration with DeviantArt and Bluesky). It was, however, my inspiration for building Pandacap in the first place. There were a few reasons I felt this was important:

  • I believe decentralized social media will continue to be useful, regardless of whether it achieves mainstream success - maybe not to replace big social media platforms, but for its own distinct merits. To hopefully facilitate the adoption of such platforms, I wanted to ensure that the ability to view and reply to my work would be available there, and I wanted to know I could follow and reply to their work as well.
  • The ActivityPub server-to-server protocol - though it may be somewhat broad and vague - was within my ability to implement (it only uses HTTP requests and is agnostic to the underlying data model), which gives me a feeling of control over my interaction with the fediverse.
  • Joining an existing server would make me nervous about conforming to unwritten community norms, while using my own Mastodon or Pixelfed instance would increase the cost and saddle me with a tech stack I'm not familiar with. (Pandacap can run on a free web app plan and a database billed only by usage.)
Read more... )
isaacschemm: A cartoon of myself as a snail (snail8)
[personal profile] isaacschemm

Pandacap: Part 4 - Inbox

Although it's not public-facing, the Inbox is perhaps the most useful part of the Pandacap web app. As a descendant of Artwork Inbox (Pandacap is built on EF Core + Cosmos DB in a very similar manner), the Pandacap inbox pulls in new posts from ActivityPub, Bluesky, DeviantArt, RSS/Atom, and Weasyl, and allows the logged-in user (me) to view and dismiss them, kind of like an email inbox.

Posts from users and feeds you follow are split between four different inboxes:

  • Image posts: DeviantArt and Weasyl art submissions, and any ActivityPub, Bluesky, and RSS/Atom posts that have an image attached. (Unlike the Pandacap gallery, these aren't called "artwork" posts, because Pandacap can't tell whether an image post is "art" or not.)
  • Text posts: DeviantArt journal entries and status updates, and any ActivityPub, Bluesky, and RSS/Atom posts that don't have an image.
  • Shares: If an ActivityPub and Bluesky post is showing up in your feed because it was shared / reposted / boosted by the user you follow, it will be sent here, instead of to the image post or text post sections. Pandacap will group shared posts by the user who shared them, not by the user who originally posted them.
  • Podcasts: RSS/Atom feeds that have an attached audio file will be sent here.

The user experience here is heavily inspired by the Fur Affinity and Weasyl inboxes: posts are shown roughly in chronological order; image posts have thumbnails and text posts only have a title; you have to click through to see the description / body of the post; checkboxes are used to remove posts from your inbox; and a "next page" button is used instead of a dynamic loading of new content.

Read more... )
isaacschemm: A cartoon of myself as a snail (snail8)
[personal profile] isaacschemm

Pandacap: Part 3 - Creating Posts

Ascreenshot of Pandacap's main page, with the user'savatar andname, links to other sites and protocols, a searchbox, 8artwork thumbnails, and 5 status updates (2 with theirownthumbnails)

From a public-facing perspective, Pandacap is essentially just a single person's art gallery (and blog, microblog, and profile, I suppose). One of Pandacap's philosophies is that fundamentally different kinds of content are separated, so there are three types of public posts:

  • Artwork - a single image, with a title and description.
  • Journal entry - essentially a blog post, with a title and text.
  • Status update - text, with an optional attached image.

These are, not coincidentally, three of the four DeviantArt post types. The paradigm of Pandacap - the context in which it assumes you're creating and uploading your posts - is heavily based on art sharing platforms like it, some of which predate the rise of general-purpose microblogging.

Read more... )
isaacschemm: A cartoon of myself as a snail (snail8)
[personal profile] isaacschemm

Pandacap: Part 2 - Authorization

Since Pandacap is a single-user application, I really didn't want to write my own authentication and authorization system. The only goal was to allow myself to log in, and no one else. So instead of using an email/password combo, like the default Identity template, I've limited it to just this:


Read more... )
isaacschemm: A cartoon of myself as a snail (snail8)
[personal profile] isaacschemm

Pandacap

This has been my hobby project for the better part of the past year, and it's something I've been wanting to make a series of blog posts about for a while. Pandacap is my personal Swiss Army knife web app; it hosts my art gallery and microblog and collects incoming posts and notifications across five different sites and protocols.

The code for Pandacap is open-source (AGPL v3). I don't imagine it will be that useful to many people; trying to ask a non-Microsoft-stack developer to make tweaks to it would be like asking me to contribute to, well, anything in Python. (Plus, the code itself is not very robust, and not at all scalable.) But I've made some very deliberate decisions in the UI of this app, with an eye towards my own psychological well-being. The context collapse of traditional social media kept me away from it for years, and this app (and one of its predecessors, Artwork Inbox) is the reason I can follow artists on Bluesky and Mastodon without giving up in frustration. I'm hoping that someday, these design principles could be useful to other people who find themselves in the same situation.

Read more... )
isaacschemm: A cartoon of myself as a snail (snail8)
[personal profile] isaacschemm

Being (loosely) based on the original Visual Basic, it's not surprising that Visual Basic .NET has features specifically targeted towards Windows Forms development. When I want to make a quick GUI app to run on my PC, I often find it easiest to build the main code in a C# or F# library, and to build a thin frontend layer in VB.NET, for two reasons: the incredibly aggressive (in a good way) auto-formatting that keeps me from being distracted by code style, and the nice set of quality-of-life helpers the language gives you for this exact use case.

Read more... )
isaacschemm: A cartoon of myself as a snail (snail8)
[personal profile] isaacschemm

When working in .NET, it can be awkward to figure out whether two objects are "equal". With reference types, two variables might refer to the same object on the heap, or to two different objects with identical properties. In the picture below, changing Snail C's shirt would also change Snail B's, because they are the same snail, while the otherwise identical Snail A is unchanged.

A points to one drawing, B and C both point to another        identical drawing; A is not equal to B, but B is equal to C

Value types are more like C structs. So giving Snail C a jacket and hat does not affect Snail B. It wouldn't make sense to do a "reference comparison" on a value type; the objects are always stored in different places (setting aside explicit pointers to objects in memory, of course - something C# also allows for).

A      and B point to separate but identical drawings, and are equal; C      points to a different drawing, where the snail has a hat, and is      not equal to B

Immutable types with built-in structural equality checks are a great way to work around this issue, but there's still the question of what to do for collection types - the standard .NET HashSet, List and Array types aren't immutable, and even ImmutableList and ImmutableSet don't consider two lists or two sets with the same items to be "equal". But there's a very easy way to handle this problem: you can use the collection types in the F# standard library (FSharp.Core), even without using F#.

Read more... )
isaacschemm: A cartoon of myself as a snail (snail8)
[personal profile] isaacschemm

Working in .NET, I've been in a situation where I need to implement a function in an interface that's supposed to return an IEnumerable<T>. (In some cases, I'd argue that IReadOnlyList<T> might make more sense - this way, the caller knows it's not going to be a lazily-evaluated sequence, and you can still return a .NET list, F# list, or array - but I digress).

Maybe the most obvious way to do this is by calling a function that explicitly returns an empty enumerable for you:

IEnumerable<string> SampleInterface.getAll() => Enumerable.Empty<string>();

In F#, it would be even shorter, because of the aggressive type resolution:

interface SampleInterface with
    member _.getAll() = Seq.empty

There are other clever ways to do the same thing, though, and it might just depend on what you think is the clearest or most readable - which might just mean keeping it consistent with the code around it. First, you can always expand out the function (because clearer isn't always shorter - I think it really does depend on the context of what's around it):

IEnumerable<string> SampleInterface.getAll() {
    return Enumerable.Empty<string>();
}

But there's also something clever you can do here, if you want to think of your code in a different way - where instead of resulting in "an empty list", it results in "no elements". C# lets you build iterator functions, where your code defines an IEnumerable<T> (and runs every time the resulting object is enumerated). Any function with a yield return or a yield break is treated in this way by the compiler. This means you can implement a function that returns "no elements" just by doing this:

IEnumerable<string> SampleInterface.getAll() {
    yield break;
}

It's a bit different in VB.NET, where iterator functions are denoted explicitly - so the yield break isn't needed:

Public Iterator Function getAll() As IEnumerable(Of String) Implements SampleInterface.getAll

End Function

That is, in a very literal sense, a function that returns no elements!

Funny thing is that there's no real equivalent to an empty iterator function in F# (not that you'd need it); the compiler won't allow a seq { } workflow without any elements in it, and suggests you use Seq.empty or the empty list [] instead.

isaacschemm: A cartoon of myself as a snail (snail8)
[personal profile] isaacschemm
// UTF-32 is a Unicode encoding that uses exactly four bytes for each
// codepoint. Unlike UTF-8 (used widely on the Web) or UTF-16 (used in Java
// and .NET), each codepoint / character takes up the same number of bytes,
// making it much easier to do string processing (counting, substrings) based
// on character count.

// One common application is applying styling or custom behavior to Twitter
// posts based on the data provided by the API:
// https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/entities
Read more... )
Tags:
isaacschemm: Drawing of myself as a snail (snail)
[personal profile] isaacschemm
Ascreenshot of the Artwork Inbox home page

Artwork Inbox is a web app I developed for my own use a few years ago. The idea behind Artwork Inbox is to let the user (a.k.a. me) view a social media feed in a way that separates the presentation of visual art (such as drawings or photographs), from other types of posts (like journal or status updates). Instead of simply sorting all posts by date and time, the app splits the feed into pages of 200 items each, and groups those items - first by type (visual or text) and then by author/artist. The current version is built on ASP.NET Core 7, with EF Core, Identity, and Cosmos DB.

Read more... )
Tags:
isaacschemm: Drawing of myself as a snail (snail)
[personal profile] isaacschemm

Let's say you want to embed some videos on your website, and you want to put them in a list so people can click through and watch them.

Aset of three YouTube thumbnails. 1: "Summer CampIsland". 2: "Connecting a Bluetooth tape adapter to aBluetooth adapter for tape players". 3: "Are QuantumLeap and Gilmore Girls CONNECTED?"

The title, description, and thumbnail you give to the video are largely subjective decisions, but the duration - in minutes and seconds - is an objective property of the media itself, which means you should be able to extract it if you know the video's URL. But how exactly do you do that?

Well, the first thing you'll need to do is figure out what exactly you're dealing with: a raw video file (or stream) that plays in the browser's <video> tag, or a link to a page on a site like YouTube or Vimeo that hosts embeddable content. Technologically speaking, it's an entirely different beast. A YouTube page gives you the code for a player, and wraps all of it up with copy protection and a variety of other features specific to the platform. In other words, it's not handing the end user something to play; it's playing it for them. It's kind of like the difference between having a record of a song, and having a band come over with their own instruments to play it on.

SoI've got "Forever Your Girl" on CD, and also PaulaAbdul is in my kitchen. Not sure why.

That doesn't mean they can't serve the same purpose for the end user of your site, though, and in both cases it should be possible to programmatically determine the duration of the media. I've written a .NET library (ISchemm.DurationFinder) that handles this for you for a variety of common video types with just a URL; I'll walk through how it works overall, and how it finds the duration for each type of media that it supports.

Read more... )
isaacschemm: Drawing of myself as a snail (snail)
[personal profile] isaacschemm

Take a look at this folder:

A directory listing of the files in Auto Injectuwad Injector v3, including several small executables and a couple of large libraries

This is an application for Wii Virtual Console ROM injection from 2008, but that's not really important. What I want to talk about is the DLLs in this folder - in particular, which executables need which DLLs - and how, when I wanted to use wadpacker myself, I took a different approach, just so I didn't have to learn how linking works.

Auto Injectuwad Injector v3.exe is the main GUI, which calls injectuwad.exe. There's also sha1.exe and the WAD tools (wadpacker, wadunpacker, imet_signer, and wadsigncheck). The dependency tree looks like this:

  • Auto Injectuwad Injector v3
    • MBVBM60.DLL (Visual Basic 6)
  • injectuwad
    • zlib1.dll
    • kernel32.dll (Windows API)
    • MSVCP80.dll (Visual C++ 8.0)
    • MSVCR80.dll (Visual C++ 8.0)
  • sha1
    • kernel32.dll (Windows API)
    • MSVCRT.dll (Visual C++ 6.0)
  • wadpacker / wadunpacker / imet_signer / wadsigncheck
    • cygcrypto-0.9.8.dll
    • cygwin1.dll
    • kernel32.dll (Windows API)

The Visual Basic 6 runtime is included with Windows. So is the "old" Windows C runtime in MSVCRT.dll, which MinGW uses as well. Later Visual C++ versions split their standard libraries from the OS - you can download them here, but they're so commonly used that you probably have some of them installed already.

The other libraries are included with the application. zlib1 implements the compression algorithm used in ZIP, gzip, and PNG, among others; it's only about 60 KB. The other two are much larger though, at about 3 MB combined: cygwin1 provides a POSIX API, and cygcrypto provides encryption and hashing functions from OpenSSL. Both come from the Cygwin project, which implements a Unix environment on top of Windows (as opposed to something like WSL, which runs alongside Windows in a hypervisor; Cygwin compiles apps to Windows binaries, albeit ones that reference its own runtime libraries).

Is this a problem? Not really. Besides, since their site calls out cygwin1.dll by the name right at the top, I think including it with an application is accepted practice. But it did cause a couple of issues for me when I wanted to tweak wadpacker by adding a couple of parameters. First, I like it when I'm able to have a bunch of EXEs in one folder, without worrying about conflicting DLLs, or trying to figure out which version of a library is newer. But also, I just think it's more elegant to take advantage of Microsoft's libraries when you can. If wadpacker were compiled for Linux, it would use the C and OpenSSL libraries from the distro's repository, and I wanted my Windows apps to do the same kind of thing.

Read more... )
Tags:
isaacschemm: Drawing of myself as a snail (snail)
[personal profile] isaacschemm

Sometimes I need to take a version number - something like 5 or 6.4 or 2.25.1 - and see whether it's newer or older than another version number, or maybe just take a list of version numbers like this and put them in order.

The projects I'm working on usually have at least a bit of F# code, or a dependency on an F# library (even if most of the code is C#), and once I've taken the dependency on FSharp.Core, I might as well use it to create an equatable, sortable, and immutable "version number" type.

This might be absolute favorite line of code:

type ProductVersion = { components: int list }
Read more... )
Tags:

Snail#

A programming blog where the gimmick is that I pretend to be a snail.

Syndicate

RSS Atom

Expand Cut Tags

No cut tags

Style Credit

Page generated Aug. 30th, 2025 10:30 pm
Powered by Dreamwidth Studios