<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title>Andrew Shay's Blog and Digital Garden</title>
  <link href="https://andrewshay.me"/>
  <link href="https://andrewshay.me/feed.xml" rel="self"/>
  <updated>2026-08-17T21:13:54Z</updated>
  <author>
    <name>Andrew Shay</name>
  </author>
  <id>andrewshay.me</id>

<entry>
    <title>Note Taking</title>
    <link href="https://andrewshay.me/blog/notes"/>
    <id>notes.txt</id>
    <updated>2026-08-09</updated>
    <content type="html">
        &lt;h2&gt;My Note Taking System&lt;/h2&gt;
There are many note taking systems and information organization tools and tips out there. I&#x27;m frequently changing my methods for what is most productive for me. I&#x27;ve also come across great ideas from other folks that I&#x27;ve tweaked and integrated into my work flow. Some I keep, and some I&#x27;ve dropped. &lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;The Starting Points&lt;/h2&gt;
&lt;h3&gt;Johnny Decimal &amp; PARA &amp; Never Ending Txt&lt;/h3&gt;
There were a few specific ideas that I have come across over time that I really like and have heavily influenced my work flow. &lt;br/&gt;
&lt;br/&gt;
1. &lt;a href=&quot;https://johnnydecimal.com/documentation/introduction&quot;&gt;Johnny Decimal&lt;/a&gt; - Organizing into a max of 10 groups&lt;br/&gt;
2. &lt;a href=&quot;https://fortelabs.com/blog/para/?ref=andrewshay.me&quot;&gt;PARA&lt;/a&gt; - Project Area Resource Archive &lt;br/&gt;
3. &lt;a href=&quot;https://jeffhuang.com/productivity_text_file/?ref=andrewshay.me&quot;&gt;Never Ending Txt File&lt;/a&gt; - One long text file that contains everything you do, each day.&lt;br/&gt;
&lt;br/&gt;
&lt;h3&gt;Obsidian&lt;/h3&gt;
The best note taking software that I&#x27;ve come across is &lt;a href=&quot;https://obsidian.md/&quot;&gt;Obsidian&lt;/a&gt;. It seems to be very popular in the software industry. &lt;br/&gt;
It is extensible with a large number of plug-ins available. &lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;My Setup&lt;/h2&gt;
&lt;h3&gt;Top Level Organization&lt;/h3&gt;
In Obsidian I have 10 folders which are basically the following:&lt;br/&gt;
&lt;br/&gt;
&lt;pre class=&quot;code-pre&quot;&gt;
00 Journal/
01 Claude/
02 Project A/
03 Project B/
04 Team A/
05 Team B/
... 
10 Helpers/
CLAUDE.md
&lt;/pre&gt;
&lt;br/&gt;
&lt;h3&gt;The Notes&lt;/h3&gt;
Each folder contains notes. Sometimes named with a starting number (e.g. 01 02 03 etc) to force order. &lt;br/&gt;
Each note has headings like Links, Meetings, Notes. &lt;br/&gt;
Each meeting gets a sub entry. Notes are for generic information. &lt;br/&gt;
Following a similar structure for each note makes it easy to find and reference information. &lt;br/&gt;
&lt;br/&gt;
&lt;h3&gt;00 Journal&lt;/h3&gt;
Journal is a special folder with a unique setup. &lt;br/&gt;
I have the &lt;code&gt;!Log&lt;/code&gt; note which starts with daily reminders on things I should check on. &lt;br/&gt;
Then a TODO section with bullet points that are on my TODO list to get done &quot;soon&quot;. Really just things I do not want to forget about. &lt;br/&gt;
A list for &quot;Sync&quot; which is what I want to mention in the next team sync. &lt;br/&gt;
&lt;br/&gt;
Then finally a long list of notes for each day. Notes, chat links, important information. &lt;br/&gt;
This idea has evolved for me over time. At first, I was diligently documenting every day. Every chat convo and every Jira ticket I made. &lt;br/&gt;
This was kind of helpful, but over time it just felt like busy work and did not add much usefulness. I was not coming back that often to this list to find something I needed. &lt;br/&gt;
Now I might have some entries for a day, but it&#x27;s rare. I think my usage with Claude Code has made this even less useful. &lt;br/&gt;
&lt;br/&gt;
In &lt;code&gt;00 Journal&lt;/code&gt;, I was also storing notes that I considered &quot;Artifacts&quot;. These were snippets of whatever I needed, but did not really want to categorize. I just wanted to capture it so that I could search it later. Like a stacktrace and some info on it. That way if I ever saw it again in the future, I could reference the existing note.&lt;br/&gt;
I use alphabetical ordering to keep the latest on top. So my oldest note is &lt;code&gt;a9999&lt;/code&gt; and my newest note would be &lt;code&gt;a9910&lt;/code&gt;. &lt;br/&gt;
&lt;br/&gt;
This is useful, but I&#x27;ve also stopped collecting these artifacts. I guess it&#x27;s not useful enough! Now that I think about it, a Claude integration to automatically create these notes with data from my clipboard might be useful. 🤔 &lt;br/&gt;
&lt;br/&gt;
&lt;h3&gt;Claude Code&lt;/h3&gt;
My latest integration is with Claude Code and I do find this to be very helpful. &lt;br/&gt;
&lt;br/&gt;
I have a &lt;code&gt;CLAUDE.md&lt;/code&gt; file in my root Obsidian directory that tells Claude its only job is to search my notes and not use plugins or external sources. &lt;br/&gt;
I can open a Claude Code session in this directory and have Claude find notes for me, and even open them in Obsidian! This has been great. &lt;br/&gt;
&lt;br/&gt;
I did not want another terminal tab open, so I had Claude vibe code an Obsidian extension that opens a new tab that has a terminal window inside the Obsidian page. I execute a bash alias that starts Claude code in that directory. &lt;br/&gt;
Now I have a really convenient way to keep Claude open in Obsidian. &lt;br/&gt;
&lt;br/&gt;
In the &lt;code&gt;CLAUDE.md&lt;/code&gt; I also explained to Claude that when I type &quot;mem: something&quot;, that it should store that text in the &lt;code&gt;!Mem&lt;/code&gt; note with the current date and time. &lt;br/&gt;
I am really liking this because it&#x27;s so easy for me to just quickly drop notes and links into a file quickly. And Claude can easily search it back up when I need it. It&#x27;s like a scratch pad. &lt;br/&gt;
&lt;br/&gt;
Having Claude integration is really useful because I can easily store data in Obsidian without thinking about how to organize it. Claude can just find it.&lt;br/&gt;
I&#x27;m definitely going to keep expanding my Claude usage in Obsidian. &lt;br/&gt;
&lt;br/&gt;
&lt;h3&gt;Helpers&lt;/h3&gt;
The next folder that I&#x27;ll mention is my &lt;code&gt;Helpers&lt;/code&gt; folder. Each note in this folder is usually a code snippet or a &quot;problem and solution&quot;. Like an error message, and a command that will resolve it. &lt;br/&gt;
Another great way to store this semi-random stuff that is easy to search up later. &lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;Other Note Taking Tools&lt;/h2&gt;
There are lots of other note taking tools out there  that are also nice. &lt;br/&gt;
&lt;br/&gt;
1. &lt;a href=&quot;https://tiddlywiki.com/&quot;&gt;Tiddly Wiki&lt;/a&gt; - A very cool single file Javascript based &quot;app&quot;. &lt;br/&gt;
2. &lt;a href=&quot;https://zim-wiki.org/&quot;&gt;Zim&lt;/a&gt; - Desktop wiki&lt;br/&gt;
3. Apple Notes - The default Apple Notes software is also really good. It&#x27;s pretty close to Obsidian if you just want basic note taking. When I tried it last, it did not have the ability to link to other notes though. &lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;Biggest Tips&lt;/h2&gt;
1. Just get the data into a note. It can be searched up later&lt;br/&gt;
2. Limit the number of folders to force organization and limit sprawl&lt;br/&gt;
3. A lot of information can go into one note. For instance for small projects, just put it all in one note. &lt;br/&gt;
4. Use software that you find useful&lt;br/&gt;
    </content>
</entry>
        
<entry>
    <title>BoxDiff: New project for detecting changes on Windows</title>
    <link href="https://andrewshay.me/blog/boxdiff"/>
    <id>boxdiff.txt</id>
    <updated>2025-04-02</updated>
    <content type="html">
        I created, &lt;a href=&quot;https://github.com/boxdiff/boxdiff&quot;&gt;BoxDiff&lt;/a&gt;, a simple Python script for detecting changes on Windows.  It shows you what was added, removed, or changed.  &lt;br/&gt;
There are no dependencies, and it&#x27;s also extendable with a single Python module.  &lt;br/&gt;
&lt;br/&gt;
A small subset of the data that is collected&lt;br/&gt;
&lt;ul&gt;
&lt;li&gt;Devices&lt;/li&gt;
&lt;li&gt;Services&lt;/li&gt;
&lt;li&gt;Installed Programs&lt;/li&gt;
&lt;li&gt;Startup Programs&lt;/li&gt;
&lt;li&gt;Users&lt;/li&gt;
&lt;li&gt;Tasks&lt;/li&gt;
&lt;li&gt;Certificates&lt;/li&gt;
&lt;/ul&gt;
&lt;a href=&quot;https://github.com/boxdiff/boxdiff&quot;&gt;Download from GitHub&lt;/a&gt;&lt;br/&gt;
&lt;br/&gt;
&lt;img alt=&quot;BoxDiff Cover&quot; src=&quot;https://raw.githubusercontent.com/boxdiff/boxdiff/refs/heads/main/cover.png&quot;&gt;&lt;br/&gt;
    </content>
</entry>
        
<entry>
    <title>Vendor Base64 Project for Rust</title>
    <link href="https://andrewshay.me/blog/rust-vendor-base64"/>
    <id>rust-vendor-base64.txt</id>
    <updated>2024-07-01</updated>
    <content type="html">
        &lt;h2&gt;What is Vendor Base64&lt;/h2&gt;
&lt;a href=&quot;https://github.com/Andrew-Shay/rust_vendor_base64&quot;&gt;rust_vendor_base64&lt;/a&gt; is my implementation of base64, in rust, that is meant to be vendored in rust projects.  &lt;br/&gt;
It is not available on crates.io to be installed.  &lt;br/&gt;
&lt;br/&gt;
I created it because it&#x27;s something so simple that I think it should be included in the rust stdlib.  &lt;br/&gt;
And when the &lt;a href=&quot;https://github.com/marshallpierce/rust-base64&quot;&gt;base64 crate&lt;/a&gt; broke their API, I decided I should implement it and have a module that others could vendor without risking the API breaking.&lt;br/&gt;
    </content>
</entry>
        
<entry>
    <title>Auto Magor: Python Program for Generating Print Magazines</title>
    <link href="https://andrewshay.me/blog/auto-magor"/>
    <id>auto-magor.txt</id>
    <updated>2024-02-29</updated>
    <content type="html">
        &lt;h2&gt;What is Auto Magor&lt;/h2&gt;
&lt;a href=&quot;https://github.com/AutoMagor/AutoMagor&quot;&gt;Auto Magor&lt;/a&gt; is a simple Python program that I created that can generate 3 column print magazines from text files and images.&lt;br/&gt;
The user just has to create simple text files with the article body, metadata about it, and an article image.&lt;br/&gt;
That is then turned into a 3 column magazine, as a pdf, that can be printed at home.&lt;br/&gt;
&lt;br/&gt;
I created it because I have been trying to use my phone less at night before going to sleep.  I don&#x27;t like reading on my phone, and my eBook is alright, but the blue light still isn&#x27;t great.&lt;br/&gt;
Reading physical books and magazines is really nice though. However, there is a still a lot of online content that I&#x27;d like to read offline.&lt;br/&gt;
I thought, if only I could take that online content and make a magazine out of it 🤔&lt;br/&gt;
Making it look like a print magazine (instead of just printing webpages outright) is just more fun.&lt;br/&gt;
&lt;br/&gt;
If I want it, and it can be built, I must build it.&lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;How Auto Magor Works&lt;/h2&gt;
Auto Magor is surprisingly simple, in general.&lt;br/&gt;
&lt;br/&gt;
Articles are stored in simple text files with the following format.&lt;br/&gt;
&lt;pre class=&quot;code-pre&quot;&gt;
title:The Title of This Article
subtitle:A subtitle for this Article
author:Foo Bar
date:January 1, 2024
image:article_01.png
source: Text that will appear after the date
body:
The first sentence of the first paragraph. The second sentence of the first paragraph.

The second paragraph is here. Another sentence in the second paragraph.
&lt;/pre&gt;
&lt;br/&gt;
The &lt;a href=&quot;https://pillow.readthedocs.io/en/stable/index.html&quot;&gt;Pillow&lt;/a&gt; library is used to generate each page.&lt;br/&gt;
Each column of text is drawn onto the image once the column is full of text.&lt;br/&gt;
A column of text is generated by appending one word at a time and calculating if it fits on the current line.&lt;br/&gt;
Once the current line is full, the next line is started. This continues until the max column height is reached, and then the next column is started.&lt;br/&gt;
I use the method &lt;code&gt;draw.multiline_textbbox()&lt;/code&gt; to get the dimensions before having to actually draw the text.&lt;br/&gt;
This way of generating columns makes Auto Magor very slow though.&lt;br/&gt;
&lt;br/&gt;
It&#x27;s simple, but along the way I ran into a few things that were a little tricky at first...&lt;br/&gt;
&lt;br/&gt;
The first thing I hit, was that some of the last lines of a paragraph were unexpectedly reaching the max column height, mid-sentence.&lt;br/&gt;
I realized that it was caused by &quot;low hanging&quot; characters like &lt;code&gt;pqgy,)&lt;/code&gt;.&lt;br/&gt;
Before writing a new line, I check if it will be able to fit. &lt;br/&gt;
At first, I was (re)checking the height as each word was added. This was just for simplicity. So, once a word with a low hanging character was attempted, it would push to the next line.&lt;br/&gt;
I had to change it to test the new line height one time, and with the low hanging characters instead.&lt;br/&gt;
&lt;br/&gt;
I also had to start tracking when I was at the start of a new column.&lt;br/&gt;
Blank lines between paragraphs in the article files were pushing the column start down.&lt;br/&gt;
New columns were also being started with a space since I was appending with &quot;a space&quot; followed by the next word.&lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;How to Make Auto Magor Faster&lt;/h2&gt;
Since I won&#x27;t be running Auto Magor very often, I prefer code simplicity to execution speed.&lt;br/&gt;
But there are some things that I assume could make Auto Magor faster.&lt;br/&gt;
&lt;br/&gt;
I get the width of the column by appending the word, then calculating the new column width.&lt;br/&gt;
But I do this after &lt;i&gt;every word&lt;/i&gt;. So, it&#x27;s constantly recalculating the line. i.e. every word that has already been applied.&lt;br/&gt;
I think I could maintain a width variable manually, get the width of each word by itself, and increment it until the max width is reached.&lt;br/&gt;
&lt;br/&gt;
If the above works, a cache of the widths for the most frequent words could be generated, and then maintained across runs for a given font and size.&lt;br/&gt;
&lt;br/&gt;
Download &lt;a href=&quot;https://github.com/AutoMagor/AutoMagor&quot;&gt;Auto Magor&lt;/a&gt;&lt;br/&gt;
&lt;br/&gt;
&lt;a href=&quot;https://github.com/AutoMagor/AutoMagor&quot;&gt;&lt;img alt=&quot;Auto Magor logo&quot; src=&quot;https://raw.githubusercontent.com/AutoMagor/AutoMagor/main/cover.png&quot;&gt;&lt;/a&gt;&lt;br/&gt;
    </content>
</entry>
        
<entry>
    <title>Making Transmitic Faster...By Doing Less!</title>
    <link href="https://andrewshay.me/blog/making-transmitic-faster"/>
    <id>making-transmitic-faster.txt</id>
    <updated>2023-11-12</updated>
    <content type="html">
        &lt;h2&gt;What is Transmitic &amp; The Prototype Phase&lt;/h2&gt;
&lt;a href=&quot;https://github.com/transmitic/transmitic&quot;&gt;Transmitic&lt;/a&gt; is an encrypted, p2p, file transfer and sharing program.  I created it because, while there are many file transfer programs out there, there was nothing quite like Transmitic.  &lt;br/&gt;
The main difference is in the UI.  You add users you want to share with and which files and folders each user can download from you.  Then users can download those files whenever they want.  Completely p2p.&lt;br/&gt;
&lt;br/&gt;
A few years ago, I wanted to learn a new programming language and decided on Rust.  I then decided my first project with Rust should be Transmitic.  &lt;br/&gt;
Learning Rust was quite difficult at first, and when combined with creating a new p2p application, it was a lot to try and handle after work.  &lt;br/&gt;
Therefore, I could not try to optimize, cache, or really architect anything.  &lt;br/&gt;
In order to not get overwhelmed by the project, I decided that I would just learn, and code, little bits at a time, without being concerned about code quality (except for the crypto) or performance.  &lt;br/&gt;
&lt;br/&gt;
This worked out well because I have been able to get Transmitic to a very functional (yet still Beta) state.  It now includes all the primary features I wanted it to have.  &lt;br/&gt;
There is still much more I want to do though.  &lt;br/&gt;
&lt;br/&gt;
Until very recently, it was really more in an alpha/prototype state. There was wasted bandwidth and things that could be cached, but weren&#x27;t.  &lt;br/&gt;
I will discuss some of the enhancements that helped make Transmitic faster and use less bandwidth, which can be summarized as &quot;do less!&quot;  &lt;br/&gt;
&lt;br/&gt;
None of this is novel. It&#x27;s all obvious, and if you&#x27;re able to truly focus on a project and plan it, you won&#x27;t hit these.  &lt;br/&gt;
I just couldn&#x27;t think about it at the time.  Once the feature worked, ship it!  &lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;Overview of Features&lt;/h2&gt;
A quick overview of the features that will be discussed.  &lt;br/&gt;
&lt;ol&gt;
&lt;li&gt; The protocol is simple. It sends u16 messages to instruct the action to be performed, with a u32 payload size, followed by the payload itself.&lt;/li&gt;
&lt;li&gt; File Listings are JSON strings. They contain all file paths, and their sizes, that are shared with an individual user.  &lt;/li&gt;
&lt;li&gt; To determine when a file download is complete, the client sends the file path and current file size on disk to the server. The server sends more chunks of data or a download complete message.  &lt;/li&gt;
&lt;li&gt; &quot;Reverse Connection&quot; is a feature, that when enabled, Transmitic will periodically connect to each user (outgoing) to establish a connection for sharing. This feature is for users with networking limitations that cannot accept incoming connections. &lt;/li&gt; 
&lt;/ol&gt;
&lt;h2&gt;Cache File Listings&lt;/h2&gt;
When a user refreshes files that are shared with them, they download JSON data containing the file paths shared with them and the file sizes.  &lt;br/&gt;
This is the &quot;File Listing&quot;.  &lt;br/&gt;
Initially, when a user started downloading files, the file listing was requested &lt;i&gt;again&lt;/i&gt;. This ensured they had the latest, valid, list of files shared with them.  &lt;br/&gt;
The client could then determine locally if the file was invalid or not (of course, the server would still reject any invalid files that were requested).&lt;br/&gt;
However, this is wasted bandwidth. Especially if the file listing was large, like 50MB. A user downloads 50MB worth of JSON to see what&#x27;s available, and once they start downloading, they immediately download 50MB again. &lt;br/&gt;
Wasteful!  &lt;br/&gt;
&lt;br/&gt;
The same scenario occurred with the Reverse Connection feature.  &lt;br/&gt;
A user with Reverse Connection enabled, periodically connects to everyone they share with, and &quot;hands off&quot; that connection to allow file listings and downloads to occur.  &lt;br/&gt;
Because the File Listing was not cached, every Reverse Connection would trigger the file listing to be downloaded.  &lt;br/&gt;
Therefore, if you were sharing with anyone who used Reverse Connection, they would continually trigger your client to download the File Listing even if you already had the latest File Listing!  &lt;br/&gt;
Wasteful!  &lt;br/&gt;
&lt;br/&gt;
&lt;i&gt;Let&#x27;s cache it!&lt;/i&gt;&lt;br/&gt;
&lt;br/&gt;
Reducing this waste is simple. We just need to cache the File Listing, ensure the appropriate threads have access to it, and determine when we need a new copy.  &lt;br/&gt;
&lt;br/&gt;
When a client starts downloading files, instead of requesting a new File Listing immediately, first it checks if it already has one. If it does, it attempts to download the file.  &lt;br/&gt;
Now, it will only get a new File Listing if the server responds with the file choice being &quot;Invalid&quot; i.e. Not shared with you.  &lt;br/&gt;
&lt;br/&gt;
The Reverse Connection logic now maintains a list of users who have the latest File Listing. This list is reset when the user changes which files they&#x27;re sharing.  &lt;br/&gt;
When the Reverse Connection is established, there is now a new message that can be sent, &lt;code&gt;MSG_REVERSE_NO_LIST&lt;/code&gt;.  &lt;br/&gt;
This tells the client a Reverse Connection is being established, but the server thinks you have the latest File Listing.  &lt;br/&gt;
The client does not &lt;i&gt;need&lt;/i&gt; to request a new one.  &lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;If the Client Can Do It Alone, Do It Alone&lt;/h2&gt;
When a user downloads a directory, the client loops through every sub file in the File Listing and requests it from the server.  &lt;br/&gt;
If the download is interrupted, it can continue where it left off.  &lt;br/&gt;
It would NOT &lt;i&gt;redownload&lt;/i&gt; every file, but it would start from the first file in the directory and loop through them all again.  &lt;br/&gt;
Originally, the logic was simple. The next file is pulled from the file listing, check its size on disk, and send it to the server which would respond.  &lt;br/&gt;
This would happen for files that were already finished. The client would tell the server the current size and the server would respond, &lt;i&gt;download finished&lt;/i&gt;.  &lt;br/&gt;
Wasteful!  &lt;br/&gt;
&lt;br/&gt;
We have the File Listing locally, which already contains the expected file sizes!  &lt;br/&gt;
Now, when the client loops through the File Listing again, if the file size on disk matches the File Listing, it just moves onto the next file.  &lt;br/&gt;
The client does &lt;i&gt;not&lt;/i&gt; contact the server for finished files anymore!  &lt;br/&gt;
&lt;br/&gt;
After that change, a bug was introduced.  I noticed it when transferring some test directories.  &lt;br/&gt;
&lt;i&gt;0 byte files.&lt;/i&gt;  &lt;br/&gt;
When getting ready to process a file for download, the client sets the current size on disk, but if the file does not exist, it sets it to 0.  &lt;br/&gt;
That, combined with &quot;if the size on disk matches the File Listing, move on to the next file&quot;, meant 0 byte files were not being created anymore. Oops!  &lt;br/&gt;
That was an easy fix though. If the File Listing says a file&#x27;s size is 0 bytes, the client just creates the file locally. There is no need to ask the server anything!  &lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;Redundant Server Messages&lt;/h2&gt;
Originally, the server would send a &lt;code&gt;MSG_FILE_CHUNK&lt;/code&gt; message which contains a chunk of file as the payload.  &lt;br/&gt;
When the file completed, the server would then send a &lt;code&gt;MSG_FILE_FINISHED&lt;/code&gt; message with no payload.  &lt;br/&gt;
This means every file download ended with an extra message, &lt;code&gt;MSG_FILE_FINISHED&lt;/code&gt;.  And it has no data...  &lt;br/&gt;
Wasteful!  &lt;br/&gt;
&lt;br/&gt;
So, I extended &lt;code&gt;MSG_FILE_FINISHED&lt;/code&gt; to also contain a payload.  &lt;br/&gt;
Now, the last chunk of file would be &lt;code&gt;MSG_FILE_FINISHED&lt;/code&gt; instead of &lt;code&gt;MSG_FILE_CHUNK&lt;/code&gt;.  &lt;br/&gt;
I was able to remove the pointless extra message of &lt;code&gt;MSG_FILE_FINISHED&lt;/code&gt; with no payload.  &lt;br/&gt;
&lt;br/&gt;
I also uncovered another wasteful message.  &lt;br/&gt;
The code looked like this  &lt;br/&gt;
&lt;pre class=&quot;code-pre&quot;&gt;
bytes_read = -1
while bytes_read != 0:
    bytes_read = read_file_into_buffer(buf)
    send_to_client(MSG_FILE_CHUNK, buf)
&lt;/pre&gt;
The intent is to keep sending chunks until nothing left was read from the file, i.e. Everything has been sent.  &lt;br/&gt;
However, &lt;code&gt;while bytes_read != 0&lt;/code&gt; means loop UNTIL &lt;code&gt;bytes_read == 0&lt;/code&gt;, which means we need a pass through the loop that &lt;i&gt;has read&lt;/i&gt; nothing.  &lt;br/&gt;
This causes the server to send a &lt;code&gt;MSG_FILE_CHUNK&lt;/code&gt; with a payload of nothing!&lt;br/&gt;
The server tells the client, &quot;Here&#x27;s a 0 byte chunk of your download.&quot;  &lt;br/&gt;
Wasteful!  &lt;br/&gt;
So, I refactored the loop and the inner logic to not send this useless message.  &lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;In Summary&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Cache data where you can so you do not have to rerequest it&lt;/li&gt;
&lt;li&gt;Look for useless communication. i.e. Data with no payloads&lt;/li&gt;
&lt;li&gt;If you can operate with only local data, do not contact the server&lt;/li&gt;
&lt;/ol&gt;
It&#x27;s always fun to make these optimizations! Big or Small.  &lt;br/&gt;
Not worrying about them from the beginning allowed me to keep working and deliver value to users.  &lt;br/&gt;
Thinking about everything from the beginning would have been too much, burned me out, and I would have given up.  I was already close to giving up as is, but I&#x27;m so glad I didn&#x27;t!&lt;br/&gt;
&lt;br/&gt;
&lt;a href=&quot;https://transmitic.net/&quot;&gt;https://transmitic.net/&lt;/a&gt;&lt;br/&gt;
&lt;a href=&quot;https://github.com/transmitic/transmitic/&quot;&gt;https://github.com/transmitic/transmitic/&lt;/a&gt;&lt;br/&gt;
&lt;br/&gt;
&lt;img alt=&quot;Transmitic Screenshot&quot; src=&quot;https://github.com/transmitic/transmitic/raw/main/screenshot.png&quot;&gt;&lt;br/&gt;
    </content>
</entry>
        
<entry>
    <title>Transmitic: Encrypted p2p file transfer and sharing. Built with Rust.</title>
    <link href="https://andrewshay.me/blog/transmitic"/>
    <id>transmitic.txt</id>
    <updated>2022-08-09</updated>
    <content type="html">
        I created, &lt;a href=&quot;https://transmitic.net/&quot;&gt;Transmitic&lt;/a&gt;, desktop software for encrypted, peer to peer, file transfer and sharing.&lt;br/&gt;
It&#x27;s built with Rust and the GUI is created with &lt;a href=&quot;https://sciter.com/&quot;&gt;sciter&lt;/a&gt;.&lt;br/&gt;
I&#x27;m working to make it as easy as possible to use.&lt;br/&gt;
&lt;br/&gt;
It automatically generates an x25519 public signing key. Users must then exchange their public keys and IPs out of band.&lt;br/&gt;
They can choose files and folders on their computer to share, and select which users they want to share those files with.&lt;br/&gt;
Connections are encrypted with AES-GCM.&lt;br/&gt;
&lt;br/&gt;
Users can download the files, and the downloads will resume if it gets disconnected.&lt;br/&gt;
This means it&#x27;s easy to transfer huge files (no file size limits) without needing to compress them, break them into smaller files etc., or worry about disconnects while uploading.&lt;br/&gt;
&lt;br/&gt;
There&#x27;s no third party servers involved and no cloud storage, so there is no cost associated with transferring, other than each user&#x27;s bandwidth. &lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;Nothing else quite like Transmitic&lt;/h2&gt;
I&#x27;ve always wanted software like Transmitic, especially for transferring 100GB files, but could never find anything quite like it.&lt;br/&gt;
I would think to myself &lt;i&gt;&quot;I just want to send these files directly from this computer to another.&quot;&lt;/i&gt;&lt;br/&gt;
Why is that so difficult and not secure?&lt;br/&gt;
&lt;br/&gt;
There is BitTorrent, but that&#x27;s difficult to use (in comparison), no out of the box encryption, no easy way to select files to share with specific users.&lt;br/&gt;
&lt;br/&gt;
There is file syncing software, but that syncs whole folders, is harder to quickly add users, and harder to choose what to share with them (more like all or nothing).&lt;br/&gt;
&lt;br/&gt;
&lt;a href=&quot;https://transmitic.net/&quot;&gt;Transmitic&lt;/a&gt;&lt;br/&gt;
&lt;a href=&quot;https://github.com/transmitic/transmitic/releases&quot;&gt;Download from GitHub&lt;/a&gt;&lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;Demo&lt;/h2&gt;
&lt;img alt=&quot;Transmitic Screenshot&quot; src=&quot;https://github.com/transmitic/transmitic/raw/main/screenshot.png&quot;&gt;&lt;br/&gt;
&lt;br/&gt;
&lt;video controls style=&quot;max-width: 100%;&quot;&gt;&lt;br/&gt;
&lt;source src=&quot;https://transmitic.net/demo.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;br/&gt;
&lt;/video&gt;&lt;br/&gt;
    </content>
</entry>
        
<entry>
    <title>Python How to Replace Multiple Regexes Without Overlaps</title>
    <link href="https://andrewshay.me/blog/python-multiple-regex"/>
    <id>python-multiple-regex.txt</id>
    <updated>2022-08-06</updated>
    <content type="html">
        When I was building a new blog generator for this site, I did not want to use any third party packages, therefore I needed some simple markdown like parsing.&lt;br/&gt;
I need to convert markdown in a line, to html.&lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;Problem: Overlapping Regex Replacements&lt;/h2&gt;
I started with multiple regexes using &lt;code&gt;re.search()&lt;/code&gt;, and doing a simple replacement on the each line.&lt;br/&gt;
However, I was running each regex on the line, one after the other, which performed multiple replacements on the substrings which was bad.&lt;br/&gt;
Let&#x27;s start with these two:&lt;br/&gt;
&lt;code&gt;[some url](https://andrewshay.me/__somewebsite__/)&lt;/code&gt;. This should get converted to an html a href with text.&lt;br/&gt;
&lt;code&gt;__text__&lt;/code&gt;. This should get converted to italics.&lt;br/&gt;
&lt;br/&gt;
Notice that the url text contains the regex for italics. What was happening, first the markdown url would be converted to html a, and then part of that url would get html tags for italics injected. Bad.&lt;br/&gt;
What I needed to do was apply all the regexes, but not overlap them. Once one is used, do not apply another.&lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;Mega Regex with re.findall()&lt;/h2&gt;
I have no idea if this is the correct way to handle this, or if there a term for this, but I couldn&#x27;t find it online.&lt;br/&gt;
This is easiest to use by using the debugger and looking at the output of &lt;code&gt;re.findall()&lt;/code&gt;&lt;br/&gt;
&lt;br/&gt;
The process is:&lt;br/&gt;
&lt;ol&gt;
&lt;li&gt;Create variables for each of your patterns (for readability).&lt;/li&gt;
&lt;li&gt;If a regex has multiple groups (e.g. my img pattern), wrap the entire regex as a group too.&lt;/li&gt;
&lt;li&gt;Create a new variable that ORs them together with &lt;code&gt;|&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Execute &lt;code&gt;re.findall(combined_pattern, line)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Each index in the match objects is the match group(s) for the patterns, in order, from the combined regex.&lt;/li&gt;
&lt;li&gt;My img pattern will always be index 0: the entire img markdown. index 1: the alt text. index 2: the img src.&lt;/li&gt;
&lt;li&gt;I can trim off the markdown characters from 1 and 2, form my html string, then on the line, do a replace of 0 with my new html.&lt;/li&gt;
&lt;li&gt;And the rest of the regexes will not run on the text that already matched.&lt;/li&gt;
&lt;li&gt;Order of the patterns in your combined regex is important.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;
Here is the code from this blog.&lt;br/&gt;
&lt;pre class=&quot;code-pre&quot;&gt;
img_pattern = r&amp;#x27;((!\[.+\])(\(.+\)))&amp;#x27;
url_pattern = r&amp;quot;((\[.+\])(\(.+\)))&amp;quot;
i_pattern = r&amp;#x27;(__.*?__)&amp;#x27;
strong_pattern = r&amp;#x27;(\*\*.*\*\*)&amp;#x27;
code_pattern = r&amp;#x27;(`.+?`)&amp;#x27;
# 0=img total group / 1=img text / 2=img link / 3=url group / 4=url text / 5=url link / 6=code text / 7=i text / 8=strong text / 
combine = img_pattern + r&amp;#x27;|&amp;#x27; + url_pattern + r&amp;#x27;|&amp;#x27; + code_pattern + r&amp;#x27;|&amp;#x27; + i_pattern + r&amp;#x27;|&amp;#x27; + strong_pattern
match = re.findall(combine, new_line)

if match:
    for m in match:
        if m[0]:
            img_alt = m[1][2:-1]
            img_src = m[2][1:-1]
            if img_src.startswith(&amp;quot;http&amp;quot;):
                new_text = f&amp;#x27;&amp;lt;img alt=&amp;quot;{img_alt}&amp;quot; src=&amp;quot;{img_src}&amp;quot;&amp;gt;&amp;#x27;
            else:
                new_text = f&amp;#x27;&amp;lt;img alt=&amp;quot;{img_alt}&amp;quot; src=&amp;quot;images/{img_src}&amp;quot;&amp;gt;&amp;#x27;
            new_line = new_line.replace(m[0], new_text)

        if m[3]:
            url_text = m[4][1:-1]
            url_href = m[5][1:-1]
            new_text = f&amp;#x27;&amp;lt;a href=&amp;quot;{url_href}&amp;quot;&amp;gt;{url_text}&amp;lt;/a&amp;gt;&amp;#x27;
            new_line = new_line.replace(m[3], new_text)

        if m[6]:
            code_text = m[6][1:-1]
            new_line = new_line.replace(m[6], f&amp;#x27;&amp;lt;code&amp;gt;{code_text}&amp;lt;/code&amp;gt;&amp;#x27;)

        if m[7]:
            i_text = m[7][2:-2]
            new_line = new_line.replace(m[7], f&amp;#x27;&amp;lt;i&amp;gt;{i_text}&amp;lt;/i&amp;gt;&amp;#x27;)

        if m[8]:
            strong_text = m[8][2:-2]
            new_line = new_line.replace(m[8], f&amp;#x27;&amp;lt;strong&amp;gt;{strong_text}&amp;lt;/strong&amp;gt;&amp;#x27;)
&lt;/pre&gt;
    </content>
</entry>
        
<entry>
    <title>Rust Convert u32 to bytes. Convert bytes to u32.</title>
    <link href="https://andrewshay.me/blog/rust-convert-u32-to-bytes-convert-bytes-to-u32"/>
    <id>rust-convert-u32-to-bytes-convert-bytes-to-u32.txt</id>
    <updated>2020-05-25</updated>
    <content type="html">
        Here is how to convert u32 to bytes and then back again in Rust.&lt;br/&gt;
&lt;br/&gt;
&lt;pre class=&quot;code-pre&quot;&gt;
fn main() {
    let original_u32: u32 = 1048572;
    println!(&amp;quot;{}&amp;quot;, original_u32);
    
    let u32_as_bytes: [u8; 4] = original_u32.to_be_bytes();
    println!(&amp;quot;{:?}&amp;quot;, u32_as_bytes);
    
    let back_to_u32: u32 = u32::from_be_bytes(u32_as_bytes);
    println!(&amp;quot;{}&amp;quot;, back_to_u32);
}

/*
https://play.rust-lang.org/?version=stable&amp;amp;mode=debug&amp;amp;edition=2018&amp;amp;gist=0bc90209eb1df96ad76d23490d34e8be
to_be_bytes
https://doc.rust-lang.org/std/primitive.u32.html#method.to_be_bytes
from_be_bytes 
https://doc.rust-lang.org/std/primitive.u32.html#method.from_be_bytes
*/
&lt;/pre&gt;
&lt;br/&gt;
On GitHub &lt;a href=&quot;https://gist.github.com/Andrew-Shay/040fd84a9c557be8451e28e32784d8f4&quot;&gt;https://gist.github.com/Andrew-Shay/040fd84a9c557be8451e28e32784d8f4&lt;/a&gt;&lt;br/&gt;
    </content>
</entry>
        
<entry>
    <title>Find Most Common Color in an Image with Python</title>
    <link href="https://andrewshay.me/blog/find-most-common-color-in-image-with-python"/>
    <id>find-most-common-color-in-image-with-python.txt</id>
    <updated>2019-04-25</updated>
    <content type="html">
        A while ago I wanted my Philips Hue light bulbs to change colors based on the most common color on my monitor.  &lt;br/&gt;
I thought this would be cool when playing video games, which it is!&lt;br/&gt;
&lt;br/&gt;
In this article I will explain a simple way for detecting the most common color in an image using Python.&lt;br/&gt;
&lt;br/&gt;
Note: This article describes my method that I came up with for fun. It is not &quot;the right way&quot; to do this, the fastest, or most efficient. But it is simple!  &lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;Pixels&lt;/h2&gt;
An image is made up of individual pixels.  Each pixel is a color.  &lt;br/&gt;
To find the most common color we can simply loop through every pixel, get the color, and keep a count of how many times the color appears.  &lt;br/&gt;
&lt;br/&gt;
I found an issue where for instance the color &lt;i&gt;green&lt;/i&gt; and &lt;i&gt;just barely slightly darker green&lt;/i&gt; are technically two different colors.  &lt;br/&gt;
I couldn&#x27;t see the difference between these two colors though, so I want them to be counted as a single color.  &lt;br/&gt;
I need similar colors to be counted together because there are too many possible colors.  &lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;RGB&lt;/h2&gt;
A color can be represented in RGB.  RGB stands for RED GREEN BLUE.  &lt;br/&gt;
An integer from 0 through 255 is used to represent R, G, and B.  e.g. &lt;code&gt;255, 7, 125&lt;/code&gt;.  &lt;br/&gt;
There are 16,777,216 total possible colors represented by RGB.  &lt;br/&gt;
0 is no color (i.e. black) and 255 is all color.  &lt;br/&gt;
&lt;code&gt;0, 0, 0&lt;/code&gt; would be black and &lt;code&gt;255, 255, 255&lt;/code&gt; would be white.   &lt;br/&gt;
So &lt;code&gt;0, 0, 255&lt;/code&gt; would be no red, no green, and all blue.  &lt;br/&gt;
&lt;code&gt;0, 0, 254&lt;/code&gt; would be just slightly darker blue, but not even perceptible.  &lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;Allowed RGB Values&lt;/h2&gt;
What I decided to do was to create a set of &quot;allowed&quot; integers for R, G, and B.  &lt;br/&gt;
I would then look at the actual integer, determine which allowed integer it was closest to, and change it to that.  &lt;br/&gt;
This integer &quot;snapping&quot; technically changes the color of the pixel, but it greatly reduces the possible colors that can occur.  &lt;br/&gt;
The pixels almost always still represent the correct color so determining the most common color is much simpler.  &lt;br/&gt;
&lt;br/&gt;
A decent set of evenly distributed allowed integers is &lt;code&gt;[255, 223, 191, 159, 127, 95,  63,  31, 0]&lt;/code&gt;.  &lt;br/&gt;
This reduces our total possible color count to 729.  &lt;br/&gt;
Let&#x27;s say we have two pixels &lt;code&gt;254, 120, 5&lt;/code&gt; and &lt;code&gt;250, 126, 20&lt;/code&gt;.  &lt;br/&gt;
After we snap the integers we get &lt;code&gt;255, 127, 0&lt;/code&gt; for both pixels.   &lt;br/&gt;
We took two technically different colors and made them the same so they are counted together.  &lt;br/&gt;
&lt;br/&gt;
If you&#x27;re looking for &quot;color&quot; you can also remove black, white and gray, from the count.  &lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;Python Script&lt;/h2&gt;
Let&#x27;s take a look at the code.  &lt;br/&gt;
This is a simple straight forward script that can be read from top to bottom.  &lt;br/&gt;
The &lt;a href=&quot;https://pillow.readthedocs.io/en/stable/&quot;&gt;Pillow&lt;/a&gt; library handles working with the images.  &lt;br/&gt;
&lt;code&gt;pip install pillow&lt;/code&gt;  &lt;br/&gt;
&lt;br/&gt;
&lt;pre class=&quot;code-pre&quot;&gt;
import os

from PIL import Image

# Open image and get data
image_path = r&amp;quot;IMAGE_PATH.jpg&amp;quot;
img = Image.open(image_path)
width, height = img.size

# The RGB values we will &amp;quot;snap&amp;quot; to
colors = [255, 223, 191, 159, 127, 95, 63, 31, 0]

original_color_count = {}
color_count = {}
# Loop through every pixel in the image and modify it
for w in range(width):
    for h in range(height):
        current_color = img.getpixel((w, h))

        if current_color in original_color_count:
            original_color_count[current_color] += 1
        else:
            original_color_count[current_color] = 1

        r, g, b = current_color
        r_set = False
        g_set = False
        b_set = False

        #  Loop through our allowed values and find the closest value to snap to
        for i in range(len(colors)):
            color_one = colors[i]
            color_two = colors[i + 1]

            if not r_set:
                if color_one &amp;gt;= r &amp;gt;= color_two:
                    distance_one = color_one - r
                    distance_two = r - color_two
                    r = color_one if distance_one &amp;lt;= distance_two else color_two
                    r_set = True

            if not g_set:
                if color_one &amp;gt;= g &amp;gt;= color_two:
                    distance_one = color_one - g
                    distance_two = g - color_two
                    g = color_one if distance_one &amp;lt;= distance_two else color_two
                    g_set = True

            if not b_set:
                if color_one &amp;gt;= b &amp;gt;= color_two:
                    distance_one = color_one - b
                    distance_two = b - color_two
                    b = color_one if distance_one &amp;lt;= distance_two else color_two
                    b_set = True

            if all((r_set, g_set, b_set)):
                break

        # Set our new pixel back on the image to see the difference
        new_rgb = (r, g, b)
        img.putpixel((w, h), new_rgb)

        if new_rgb in color_count:
            color_count[new_rgb] += 1
        else:
            color_count[new_rgb] = 1

# Save our new image
filename, file_extension = os.path.splitext(image_path)
new_path = &amp;quot;{}_new{}&amp;quot;.format(filename, &amp;#x27;.png&amp;#x27;)
img.save(new_path)

# Count and sort the colors
all_colors = color_count.items()
all_colors = sorted(all_colors, key=lambda tup: tup[1], reverse=True)

# Print out the colors
print(&amp;quot;## All Colors ##&amp;quot;)
for i in range(10):
    print(all_colors[i])

# Remove black, white and gray
print(&amp;quot;\n## All Colors Filtered ##&amp;quot;)
filtered_colors = [color for color in all_colors if not color[0][0] == color[0][1] == color[0][2]]
for i in range(10):
    print(filtered_colors[i])

print(&amp;quot;&amp;quot;)
original_color_count = len(original_color_count)
print(&amp;quot;Original Color Count: {}&amp;quot;.format(original_color_count))
new_color_count = len(color_count)
print(&amp;quot;New Color Count: {}&amp;quot;.format(new_color_count))
color_diff = original_color_count - new_color_count
print(&amp;quot;Reduced Color Count By: {}&amp;quot;.format(color_diff))
&lt;/pre&gt;
&lt;br/&gt;
&lt;h2&gt;Examples&lt;/h2&gt;
Let&#x27;s compare the changes using some images that I found on &lt;a href=&quot;https://unsplash.com/&quot;&gt;Unsplash&lt;/a&gt;.  &lt;br/&gt;
&lt;br/&gt;
&lt;h3&gt;Jungle&lt;/h3&gt;
&lt;code&gt;Photo by Jacob Plumb on Unsplash&lt;/code&gt;  &lt;br/&gt;
&lt;a href=&quot;https://unsplash.com/photos/UghHZmnJw58&quot;&gt;https://unsplash.com/photos/UghHZmnJw58&lt;/a&gt;&lt;br/&gt;
&lt;br/&gt;
&lt;img alt=&quot;jacob-plumb-560046-unsplash.jpg&quot; src=&quot;{ROOT}images/jacob-plumb-560046-unsplash.jpg&quot;&gt;  &lt;br/&gt;
&lt;br/&gt;
&lt;img alt=&quot;jacob-plumb-560046-unsplash_new.png&quot; src=&quot;{ROOT}images/jacob-plumb-560046-unsplash_new.png&quot;&gt;&lt;br/&gt;
&lt;br/&gt;
Top 3 Colors  &lt;br/&gt;
RGB: &lt;code&gt;0, 31, 0&lt;/code&gt; Count: 61,167  &lt;br/&gt;
RGB: &lt;code&gt;0, 0, 0&lt;/code&gt; Count: 36,716  &lt;br/&gt;
RGB: &lt;code&gt;31, 31, 31&lt;/code&gt; Count: 23,208  &lt;br/&gt;
&lt;br/&gt;
Original Color Count: 62,963  &lt;br/&gt;
New Color Count: 167  &lt;br/&gt;
Reduced Color Count By: 62,796  &lt;br/&gt;
&lt;br/&gt;
&lt;h3&gt;Airplane Cockpit&lt;/h3&gt;
&lt;code&gt;Photo by JC Gellidon on Unsplash&lt;/code&gt;  &lt;br/&gt;
&lt;a href=&quot;https://unsplash.com/photos/7KFVkL1cV0w&quot;&gt;https://unsplash.com/photos/7KFVkL1cV0w&lt;/a&gt;  &lt;br/&gt;
&lt;br/&gt;
&lt;img alt=&quot;jc-gellidon-712663-unsplash.jpg&quot; src=&quot;{ROOT}images/jc-gellidon-712663-unsplash.jpg&quot;&gt;  &lt;br/&gt;
&lt;br/&gt;
&lt;img alt=&quot;jc-gellidon-712663-unsplash_new.png&quot; src=&quot;{ROOT}images/jc-gellidon-712663-unsplash_new.png&quot;&gt;&lt;br/&gt;
&lt;br/&gt;
Top 3 Colors  &lt;br/&gt;
RGB: &lt;code&gt;31, 31, 31&lt;/code&gt; Count: 166,090  &lt;br/&gt;
RGB: &lt;code&gt;63, 63, 63&lt;/code&gt; Count: 28,134  &lt;br/&gt;
RGB: &lt;code&gt;255, 255, 255&lt;/code&gt; Count: 4,943  &lt;br/&gt;
&lt;br/&gt;
Original Color Count: 21,216  &lt;br/&gt;
New Color Count: 168  &lt;br/&gt;
Reduced Color Count By: 21,048  &lt;br/&gt;
&lt;br/&gt;
&lt;h3&gt;Train Station&lt;/h3&gt;
&lt;code&gt;Photo by Patrick Schöpflin on Unsplash&lt;/code&gt;  &lt;br/&gt;
&lt;a href=&quot;https://unsplash.com/photos/BnlNjW3AQZ0&quot;&gt;https://unsplash.com/photos/BnlNjW3AQZ0&lt;/a&gt;  &lt;br/&gt;
&lt;br/&gt;
&lt;img alt=&quot;patrick-schopflin-83447-unsplash.jpg&quot; src=&quot;{ROOT}images/patrick-schopflin-83447-unsplash.jpg&quot;&gt;  &lt;br/&gt;
&lt;br/&gt;
&lt;img alt=&quot;patrick-schopflin-83447-unsplash_new.png&quot; src=&quot;{ROOT}images/patrick-schopflin-83447-unsplash_new.png&quot;&gt;&lt;br/&gt;
&lt;br/&gt;
Top 3 Colors  &lt;br/&gt;
RGB: &lt;code&gt;31, 31, 31&lt;/code&gt; Count: 57,880  &lt;br/&gt;
RGB: &lt;code&gt;63, 63, 63&lt;/code&gt; Count: 29,510  &lt;br/&gt;
RGB: &lt;code&gt;0, 0, 0&lt;/code&gt; Count: 27,606  &lt;br/&gt;
&lt;br/&gt;
Original Color Count: 31,211  &lt;br/&gt;
New Color Count: 117  &lt;br/&gt;
Reduced Color Count By: 31,094  &lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
- Loop through every pixel  &lt;br/&gt;
- Snap the RGB values to a smaller subset&lt;br/&gt;
- Count each color  &lt;br/&gt;
&lt;br/&gt;
Pillow &lt;code&gt;Image&lt;/code&gt; docs &lt;a href=&quot;https://pillow.readthedocs.io/en/stable/reference/Image.html&quot;&gt;https://pillow.readthedocs.io/en/stable/reference/Image.html&lt;/a&gt;  &lt;br/&gt;
    </content>
</entry>
        
<entry>
    <title>TDD vs BDD. Does TDD test implementation or behavior?</title>
    <link href="https://andrewshay.me/blog/tdd-vs-bdd-does-tdd-test-implementation-or-behavior"/>
    <id>tdd-vs-bdd-does-tdd-test-implementation-or-behavior.txt</id>
    <updated>2020-01-01</updated>
    <content type="html">
        About a year ago I wanted to learn more about good testing habits and specifically Test Driven Development (TDD) because I had heard about it so much.  While reading about these topics I came across Behavior Driven Development (BDD).  I came to the conclusion that there is confusion around TDD and especially when compared to BDD, however TDD is simple and a highly effective development strategy when understood correctly.&lt;br/&gt;
The first thing I did when trying to learn more about TDD was to of course Google it.  I read the Wikipedia page, Stack Overflow questions, development/testing websites&#x27; explanations, and people&#x27;s personal blogs.  Someone looking up TDD as I did will quickly find many contradictions and inaccurate interpretations.  Many of the inaccuracies are asserted as truth, which makes it hard to determine what is correct.&lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;Common Statements&lt;/h2&gt;
Listed below are the 5 most common statements that I found about TDD:&lt;br/&gt;
&lt;br/&gt;
1) TDD tests implementation  &lt;br/&gt;
2) TDD tests behavior  &lt;br/&gt;
3) TDD means writing a test for every function  &lt;br/&gt;
4) TDD tests implementation and BDD tests behavior  &lt;br/&gt;
5) BDD is an improved version of TDD  &lt;br/&gt;
&lt;br/&gt;
I am going to go through these assertions and discuss where confusion around them comes from, why I think some are simply incorrect, and why some depend on the context of development.  &lt;br/&gt;
&lt;br/&gt;
After reading conflicting opinions on TDD, I decided the best thing to do is read the commonly recognized source on TDD, &quot;Test Driven Development: By Example by Kent Beck&quot;.  I felt the aspects of TDD became clear and obvious, after reading &quot;Test Driven Development: By Example&quot;.  My discussion and thoughts will be based on what I read in this book.  This book is my &quot;source of truth&quot; on the topic of TDD.  &lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;Sources&lt;/h2&gt;
Before getting into my discussion I will list my sources from the book that state TDD is for testing &lt;strong&gt;behavior&lt;/strong&gt;.  &lt;br/&gt;
&lt;br/&gt;
1) page 4 &lt;br/&gt;
&lt;code&gt;What behavior will we need to produce the revised report? Put another way, what set of tests, when passed, will demonstrate the presence of code we are confident will compute the report correctly?&lt;/code&gt;&lt;br/&gt;
&lt;br/&gt;
By &lt;strong&gt;page 4&lt;/strong&gt; Kent has explicitly stated we need to be testing behavior.  &lt;br/&gt;
&lt;br/&gt;
2) page 11 #1&lt;br/&gt;
&lt;code&gt;Invent the interface you wish you had. Include all of the elements in the story that you imagine will be necessary to calculate the right answers.&lt;/code&gt;&lt;br/&gt;
&lt;br/&gt;
We are thinking about the &lt;i&gt;interface&lt;/i&gt;, the behavior. We are not thinking about the &lt;i&gt;implementation&lt;/i&gt;.  &lt;br/&gt;
We are thinking about how to &quot;calculate the right answer&quot;, the &lt;i&gt;behavior&lt;/i&gt;.&lt;br/&gt;
&lt;br/&gt;
3) page 14 &lt;br/&gt;
&lt;code&gt;First we can talk about whether the system should work like this or like that. Once we decide on the correct behavior, we can talk about the best way of achieving that behavior.&lt;/code&gt;&lt;br/&gt;
&lt;br/&gt;
Again stating we need to be thinking about the &lt;i&gt;behavior&lt;/i&gt; and how the system &lt;i&gt;should work&lt;/i&gt;.&lt;br/&gt;
&lt;br/&gt;
4) page 16 paragraph 2&lt;br/&gt;
&lt;code&gt;You aren&#x27;t thinking about the implementation of equals(), are you?...I&#x27;m thinking about how to test equality.&lt;/code&gt;&lt;br/&gt;
&lt;br/&gt;
Explicitly stating to &lt;strong&gt;not&lt;/strong&gt; think about the &lt;i&gt;implementation&lt;/i&gt;.&lt;br/&gt;
&lt;br/&gt;
5) page 39 paragraph 2&lt;br/&gt;
&lt;code&gt;How do we want to implement currencies at the moment?...I&#x27;ll rephrase: How do we want to test for currencies at the moment?&lt;/code&gt;&lt;br/&gt;
&lt;br/&gt;
Emphasizing to not think about the implementation. Think about how to test the behavior.&lt;br/&gt;
&lt;br/&gt;
6) page 62 middle of page&lt;br/&gt;
&lt;code&gt;The test above is not one I would expect to live a long time. It is deeply concerned with the implementation of our operation, rather than its externally visible behavior&lt;/code&gt;&lt;br/&gt;
&lt;br/&gt;
Tests that test implementation do not last.  The tests need to think about the &lt;i&gt;behavior&lt;/i&gt;.&lt;br/&gt;
&lt;br/&gt;
7) page 71 Bullet 2&lt;br/&gt;
&lt;code&gt;Introduced a private helper class without distinct tests of its own.&lt;/code&gt;&lt;br/&gt;
&lt;br/&gt;
We do not write tests for every function and class.&lt;br/&gt;
&lt;br/&gt;
8) page 103 &lt;br/&gt;
&lt;code&gt;Now we are ready to implement tearDown(). Got you! Now we are ready to test for tearDown:&lt;/code&gt;&lt;br/&gt;
&lt;br/&gt;
Test the behavior.&lt;br/&gt;
&lt;br/&gt;
Now onto my discussion and thoughts...&lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;Test Implementation or Behavior?&lt;/h2&gt;
The biggest overarching question/concern around TDD is whether it&#x27;s testing the implementation or behavior.  &lt;br/&gt;
I think the &lt;i&gt;word&lt;/i&gt; chosen to answer this question depends on the context of development, and TDD is usually discussed around developing unit tests.  &lt;br/&gt;
If the context is around developer unit tests: The answer is &lt;i&gt;behavior&lt;/i&gt;.  &lt;br/&gt;
If the context is around higher level tests like system tests, end-to-end tests, etc.: The answer is &lt;i&gt;implementation&lt;/i&gt;.  &lt;br/&gt;
&lt;br/&gt;
Here are some examples of the context to make sure I&#x27;m clear.  &lt;br/&gt;
Let&#x27;s say we are building a calculator application with a GUI, and let&#x27;s only consider the following two sets of tests.  &lt;br/&gt;
1) We will have some unit tests to test functions like add(x, y): x plus y, subtract(x, y): x minus y, multiply(x, y) and divide(x, y).  &lt;br/&gt;
2) We will have some UI tests to test the GUI when the user is using the application (if the user presses &quot;+&quot; then they see the addition of the two numbers).  &lt;br/&gt;
&lt;br/&gt;
When working on our unit tests for add, subtract, multiply and divide, we want to make sure their &lt;i&gt;behavior&lt;/i&gt; is correct.  &lt;br/&gt;
Our test should be &quot;When I call subtract(5, 1), I get 4&quot;.  &lt;br/&gt;
We &lt;i&gt;do not&lt;/i&gt; test &quot;When I call subtract(5, 1), I get 4, and subtract() called add(x, negative y).&quot;  &lt;br/&gt;
With the above two examples, the first one tests the behavior of the subtract function.  That is what we care about.  We want it to be correct.  &lt;br/&gt;
The second example is now dealing with the &lt;i&gt;implementation&lt;/i&gt;. We do not care if subtract used &quot;add(x, (y * -1))&quot; or if it used &quot;x - y&quot;, or &quot;x + -y&quot;.  &lt;br/&gt;
&lt;br/&gt;
&lt;h3&gt;Tests give Confidence&lt;/h3&gt;
One of the purposes of TDD is to give confidence that your code is correct after refactoring, because you will rerun your tests and if all your tests pass, then everything is still working correctly.    &lt;br/&gt;
So if our test includes asserting &quot;add() was called one time&quot;, what happens when we refactor subtract() to use &quot;x + -y&quot; instead of calling add()?  &lt;br/&gt;
Our test fails!  Well then subtract() is broken right? Our test failed, so it must be broken. Nope. subtract() still works 100% correctly. Now we waste our time examining why it failed and updating this failing test.  &lt;br/&gt;
What happens if we update the test to reflect this new specific implementation (somehow), then we refactor &lt;i&gt;AGAIN&lt;/i&gt; to &quot;x - y&quot;?  The test breaks again, but subtract() is still working! subtract()&#x27;s &lt;i&gt;behavior&lt;/i&gt; never changed.  &lt;br/&gt;
When our unit tests are testing implementation, we end up testing &lt;i&gt;what the system does&lt;/i&gt;, not &lt;i&gt;what the behavior of the system should be&lt;/i&gt;.  &lt;br/&gt;
&lt;br/&gt;
The above makes a strong case for testing &lt;i&gt;behavior&lt;/i&gt; and NOT &lt;i&gt;implementation&lt;/i&gt;.  &lt;br/&gt;
However, when we discuss the UI tests, &lt;i&gt;I&lt;/i&gt; think TDD can be considered to be testing the &lt;i&gt;implementation&lt;/i&gt; because TDD is usually discussed with respect to the developer&#x27;s unit tests, and the code being tested by the unit tests are &lt;i&gt;implementing&lt;/i&gt; the functionality of the UI.  &lt;br/&gt;
This is where I think &lt;strong&gt;some&lt;/strong&gt; of the confusion can arise. Even though the &lt;i&gt;word&lt;/i&gt; chosen can change, how the code is tested, doesn&#x27;t change.&lt;br/&gt;
&lt;br/&gt;
So far the above has directly addressed the main statements #1 and #2.  &lt;br/&gt;
&lt;br/&gt;
How about #3, &quot;TDD means writing a test for every function&quot;? This is incorrect.  &lt;br/&gt;
We care about testing the &lt;i&gt;behavior&lt;/i&gt;.  We just need enough tests, testing enough functions, that give us confidence that the behavior of our system is correct.  &lt;br/&gt;
Let&#x27;s say we remove all functionality from the calculator except multiply.  So our UI no longer has +, -, and /.  &lt;br/&gt;
Let&#x27;s say we decide to implement multiply(x, y) by using a for loop and continually calling add().  &lt;br/&gt;
If we wrote a test for every function we would have a test(s) for add().  &lt;br/&gt;
Now we decide to refactor multiply() so it calls &quot;x * y&quot; instead, and we delete add() because we do not need it anymore.  &lt;br/&gt;
We rerun our tests and what happens? Our tests fail, but the behavior of multiplying has not changed.  It is the same scenario we saw above.  &lt;br/&gt;
Simply writing a test(s) for &lt;i&gt;every function&lt;/i&gt; is testing the &lt;i&gt;implementation&lt;/i&gt;.  &lt;br/&gt;
This does not increase our confidence that the behavior of the system has not changed.  &lt;br/&gt;
&lt;br/&gt;
Now we are on to main statements #4 and #5, &quot;TDD tests implementation and BDD tests behavior&quot; and &quot;BDD is an improved version of TDD&quot;.  &lt;br/&gt;
You can find different definitions of BDD, but these statements are comparing BDD to TDD so the context is around the developer unit tests (especially since some claim BDD is an improved version of TDD).  These statements are incorrect.  &lt;br/&gt;
TDD &lt;i&gt;does&lt;/i&gt; test behavior. BDD is not improving anything.  BDD certainly improves test development for people who do not understand TDD correctly.  &lt;br/&gt;
Now, some people might define BDD differently.  They might say BDD is not only the developer who is involved and BDD tests are not developer unit tests.  That is not what I am comparing and discussing here.    &lt;br/&gt;
&lt;br/&gt;
&lt;h2&gt;Final Thoughts&lt;/h2&gt;
Why is there confusion around what TDD is testing and how?  &lt;br/&gt;
Maybe Kent should have called it Behavior Test Driven Development instead.  &lt;br/&gt;
&lt;br/&gt;
After I read Kent&#x27;s book everything clicked.  I had taken people&#x27;s bad advice and wrote tests for &lt;i&gt;every function&lt;/i&gt; and ran into problems and slow downs during refactoring.  I questioned if asserting method call counts was really doing anything.  Turns out I was wasting my time. I had been testing implementation instead of behavior.  &lt;br/&gt;
&lt;br/&gt;
I highly recommend &quot;Test Driven Development: By Example by Kent Beck&quot; to anyone who has not read it.  He thoroughly covers TDD by building some example projects and it&#x27;s very helpful to see the process. He also goes over general testing tips and good code quality tips.&lt;br/&gt;
&lt;br/&gt;
&lt;br/&gt;
    </content>
</entry>
        
<entry>
    <title>How to create a gauge / speedometer graphic with Python</title>
    <link href="https://andrewshay.me/blog/how-to-create-a-gauge-speedometer-graphic-with-python"/>
    <id>how-to-create-a-gauge-speedometer-graphic-with-python.txt</id>
    <updated>2018-06-30</updated>
    <content type="html">
        This article discusses how to easily create a speedometer / gauge image in Python with only the Pillow library.  &lt;br/&gt;
&lt;br/&gt;
View source code and images on GitHub: &lt;a href=&quot;https://github.com/Andrew-Shay/python-gauge&quot;&gt;python-gauge&lt;/a&gt;  &lt;br/&gt;
&lt;br/&gt;
I needed to create a gauge graphic in Python but I found many libraries did not have the graphic I needed or required that the gauge be rendered in a browser since the underlying library used was JavaScript. This script only requires the Pillow library since it is generated using two png images.  &lt;br/&gt;
&lt;br/&gt;
The gauge graphic is separated into two images. The first image is the needle that points to a number on the gauge.  The second image is the gauge without the needle.  The final gauge image is generated by rotating the needle image then pasting it on the gauge.  &lt;br/&gt;
&lt;br/&gt;
The script is simple.  The gauge is half a circle therefore the needle needs to rotate between 0 and 180 degrees.  The gauge goes from 0 to 100.  So if you want the gauge to point to 20, the script just takes 20% of 180 degrees to rotate the needle.&lt;br/&gt;
&lt;br/&gt;
&lt;code&gt;python_gauge.py&lt;/code&gt;&lt;br/&gt;
&lt;br/&gt;
&lt;pre class=&quot;code-pre&quot;&gt;
&amp;quot;&amp;quot;&amp;quot;
Copyright (C) 2018 FireEye, Inc., created by Andrew Shay. All Rights Reserved.
&amp;quot;&amp;quot;&amp;quot;

import PIL

from PIL import Image

percent = 20  # Percent for gauge
output_file_name = &amp;#x27;new_gauge.png&amp;#x27;

# X and Y coordinates of the center bottom of the needle starting from the top left corner
#   of the image
x = 825
y = 825
loc = (x, y)

percent = percent / 100
rotation = 180 * percent  # 180 degrees because the gauge is half a circle
rotation = 90 - rotation  # Factor in the needle graphic pointing to 50 (90 degrees)

dial = Image.open(&amp;#x27;needle.png&amp;#x27;)
dial = dial.rotate(rotation, resample=PIL.Image.BICUBIC, center=loc)  # Rotate needle

gauge = Image.open(&amp;#x27;gauge.png&amp;#x27;)
gauge.paste(dial, mask=dial)  # Paste needle onto gauge
gauge.save(output_file_name)

&lt;/pre&gt;
&lt;br/&gt;
To modify the gauge image, modify &lt;code&gt;gauge.psd&lt;/code&gt;.  &lt;code&gt;gauge.png&lt;/code&gt; should be the gauge without the needle (hide the needle layer). &lt;code&gt;needle.png&lt;/code&gt; should only be the needle on a transparent background and should point at 50 (hide all layers except needle).&lt;br/&gt;
&lt;br/&gt;
    </content>
</entry>
        
<entry>
    <title>Device Checker: New project for detecting changes on Windows</title>
    <link href="https://andrewshay.me/blog/device-checker-new-project"/>
    <id>device-checker-new-project.txt</id>
    <updated>2018-03-24</updated>
    <content type="html">
        I recently wanted software to notify me when services, installed programs, and startup programs were added, removed, or changed.  &lt;br/&gt;
I found some wmic commands to give me that information, as well as much more information.  &lt;br/&gt;
&lt;br/&gt;
I created a program to simply gather information about a Windows system and then notify the user when it has changed, and view the differences.  &lt;br/&gt;
&lt;br/&gt;
It is currently released in beta.  &lt;br/&gt;
&lt;br/&gt;
View on GitHub:   &lt;a href=&quot;https://github.com/shayConcepts/device-checker&quot;&gt;shayConcepts/device-checker&lt;/a&gt;&lt;br/&gt;
    </content>
</entry>
        
<entry>
    <title>How to get the data from msinfo32 with wmic commands</title>
    <link href="https://andrewshay.me/blog/how-to-get-the-data-from-msinfo32"/>
    <id>how-to-get-the-data-from-msinfo32.txt</id>
    <updated>2018-03-23</updated>
    <content type="html">
        I was recently looking to gather the same data that msinfo32 reports and I found a couple wmic commands that provided me with what I needed.  &lt;br/&gt;
&lt;br/&gt;
The first is a basic wmic command eg &lt;code&gt;wmic service get /format:list&lt;/code&gt;.  This will output the services on Windows.  &lt;br/&gt;
Run &lt;code&gt;wmic /?&lt;/code&gt; to see a list of other information you can gather.  &lt;br/&gt;
Other formatting options &lt;code&gt;/format:table&lt;/code&gt; and &lt;code&gt;/format:csv&lt;/code&gt;  &lt;br/&gt;
&lt;br/&gt;
The second wmic command uses the CIM WMI Provider.  eg &lt;code&gt;wmic path CIM_LogicialDisk get /value&lt;/code&gt;.  This outputs the logical disks that Windows detects.  &lt;br/&gt;
To find a list of possible options see the MSDN docs &lt;a href=&quot;https://msdn.microsoft.com/en-us/library/aa386541.aspx&quot;&gt;https://msdn.microsoft.com/en-us/library/aa386541.aspx&lt;/a&gt;&lt;br/&gt;
    </content>
</entry>
        
<entry>
    <title>Apache disable TLS_RSA_WITH_3DES_EDE_CBC_SHA after LetsEncrypt</title>
    <link href="https://andrewshay.me/blog/apache-disable-tls_rsa_with_3des_ede_cbc_sha-after-letsencrypt"/>
    <id>apache-disable-tls_rsa_with_3des_ede_cbc_sha-after-letsencrypt.txt</id>
    <updated>2017-05-09</updated>
    <content type="html">
        &lt;p&gt;
&lt;i&gt;This guide is for Apache but might be similar for other web servers&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;
If you&#x27;ve just installed &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let&#x27;s Encrypt&lt;/a&gt; certificates with &lt;a href=&quot;https://certbot.eff.org/&quot;&gt;Certbot&lt;/a&gt; on your web server and ran &lt;a href=&quot;https://www.ssllabs.com/ssltest/&quot;&gt;SSL Server Test&lt;/a&gt;, you might have warning saying your server supports &lt;span class=&quot;hightlight&quot;&gt;TLS_RSA_WITH_3DES_EDE_CBC_SHA&lt;/span&gt;.  
&lt;/p&gt;

&lt;p&gt;
To drop support for this protocol you must
&lt;ul&gt;
&lt;li&gt; Open &lt;span class=&quot;hightlight&quot;&gt;/etc/letsencrypt/options-ssl-apache.conf&lt;/span&gt;&lt;/li&gt;
&lt;li&gt; Delete &lt;span class=&quot;hightlight&quot;&gt;:DES-CBC3-SHA&lt;/span&gt; from the &lt;span class=&quot;hightlight&quot;&gt;SSLCipherSuite&lt;/span&gt; line&lt;/li&gt;
&lt;li&gt; Restart apache &lt;span class=&quot;hightlight&quot;&gt;sudo service apache2 restart&lt;/span&gt;&lt;/li&gt;  
&lt;/ul&gt;
&lt;/p&gt;

&lt;p&gt;
Also, here is the default conf file  
&lt;a href=&quot;https://github.com/certbot/certbot/blob/master/certbot-apache/certbot_apache/_internal/tls_configs/old-options-ssl-apache.conf&quot;&gt;https://github.com/certbot/certbot/blob/master/certbot-apache/certbot_apache/_internal/tls_configs/old-options-ssl-apache.conf&lt;/a&gt;
&lt;/p&gt;
    </content>
</entry>
        </feed>