RYMC https://rymc.io Personal Site and Portfolio of Ryan McGrath RYMC en Wed, 21 May 2025 00:00:00 +0000 Forgejo index.js woes Wed, 21 May 2025 00:00:00 +0000 https://rymc.io/blog/2025/forgejo-gitea-fix-indexjs/ https://rymc.io/blog/2025/forgejo-gitea-fix-indexjs/ <p>I've run a few small git forges that've gone through the various fork paths over the years, from <a href="https://gogs.io">Gogs</a> to <a href="https://about.gitea.com">Gitea</a> to (now) <a href="http://forgejo.org">Forgejo</a>. It all runs remarkably well and has been mostly set-and-forget for as long as I can remember, and easily trumps the few times I've had to deal with self hosting something like Gitlab. One weird issue would come and go over the years, though: on first load of a page for Gitea and Forgejo, I'd randomly get an alert about being unable to load the core JS file.</p> <p>If you just reloaded the page, things would work fine - and frankly that was simpler than bothering to debug it when it appeared. Presumably, the file was just getting cached somehow and subsequent loads would not have issues. I had a few minutes earlier this week to debug it and figured I'd note it down for anyone who's getting confused by it. In my case, this error was actually specific to nginx as a reverse proxy sitting in front of everything.</p> <h2 id="the-fix">The Fix</h2> <p>When nginx is proxying a response that's deemed too large, it attempts to buffer it in temporary file. For whatever reason, on my stock Debian 12 image nginx did not have the right permissions to do this. A simple <code>chown</code> to the user that nginx runs under fixes this:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#f8f8f2;">sudo chown -R www-data:www-data /var/cache/nginx </span><span class="lol"></span></code></pre> <p>This error as presented in the browser can be confusing; if you have <code>http2</code> enabled on your host, Chrome &amp; co will report it as an http2 specific issue. If you disable that, it correctly reports it as invalid chunking - i.e, the response is being interrupted due to nginx not being able to write when proxying.</p> Connecting tokio-postgres over an SSH tunnel Tue, 29 Apr 2025 00:00:00 +0000 https://rymc.io/blog/2025/tokio-postgres-over-ssh/ https://rymc.io/blog/2025/tokio-postgres-over-ssh/ <p>I recently found myself writing a quick script to toggle some jobs on a remote server. The server itself runs a bog-standard PostgreSQL instance to store and manage the jobs, and the setup itself isn't so important that I wanted to spend the effort cobbling together an HTTP server and endpoint - just a quick call over SSH should be more than enough. Now, you could do this in any language, framework, or environment - but the overall project is written in Rust, and I like to keep things uniform where it makes sense to do so. After perusing some docs for a minute or two, it seemed relatively straightforward to do - but I realized I hadn't seen any good examples of this floating around, and so I figured I may as well throw one up here.</p> <p>This won't be as in-depth or complete as other posts on this site, but the general approach is correct and I'm confident that interested parties can glean what they need from here. It's expected that you know what/why you're reading here.</p> <h2 id="crates-we-need">Crates We Need</h2> <p>There's really only two or three crates that we need to be dealing with here.</p> <ul> <li><a href="https://crates.io/crates/tokio">tokio (1.0.x)</a>: The async runtime that most things in Rust piggyback off of.</li> <li><a href="https://crates.io/crates/tokio-postgres">tokio-postgres (0.7.x)</a>: A library for working with Postgres backed by a <code>tokio</code> runtime.</li> <li><a href="https://crates.io/crates/async-ssh2-tokio">async-ssh2-tokio (0.8.x)</a>: An async SSH client library.</li> </ul> <p>The latter two crates are key: how do we make <code>tokio-postgres</code> call over SSH?</p> <h2 id="step-1-ssh">Step 1: SSH</h2> <p>Let's get our SSH tunnel sorted. A few key variables we need up-front:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;">// Remember, this is an example - you don&#39;t typically want to shove these </span><span class="lol"></span><span style="color:#6272a4;">// (some or all) into your codebase. You probably want to load these </span><span class="lol"></span><span style="color:#6272a4;">// from your environment, or however you typically handle sensitive keys. </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">const </span><span style="color:#bd93f9;">SERVER</span><span style="color:#f8f8f2;">: (</span><span style="color:#ff79c6;">&amp;</span><span style="font-style:italic;color:#8be9fd;">str</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#8be9fd;">u16</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&quot;your.server.ip.address&quot;</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">22</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">const </span><span style="color:#bd93f9;">USER</span><span style="color:#f8f8f2;">: </span><span style="color:#ff79c6;">&amp;</span><span style="font-style:italic;color:#8be9fd;">str </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&quot;your_ssh_username_here&quot;</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">const </span><span style="color:#bd93f9;">KEY_PATH</span><span style="color:#f8f8f2;">: </span><span style="color:#ff79c6;">&amp;</span><span style="font-style:italic;color:#8be9fd;">str </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&quot;path_to_your_ssh_key_here&quot;</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">const </span><span style="color:#bd93f9;">DB_SERVER</span><span style="color:#f8f8f2;">: (</span><span style="color:#ff79c6;">&amp;</span><span style="font-style:italic;color:#8be9fd;">str</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#8be9fd;">u16</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&quot;127.0.0.1&quot;</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">5432</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">const </span><span style="color:#bd93f9;">DB_USER</span><span style="color:#f8f8f2;">: </span><span style="color:#ff79c6;">&amp;</span><span style="font-style:italic;color:#8be9fd;">str </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&quot;your_postgres_username_here&quot;</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">const </span><span style="color:#bd93f9;">DB_PASSWORD</span><span style="color:#f8f8f2;">: </span><span style="color:#ff79c6;">&amp;</span><span style="font-style:italic;color:#8be9fd;">str </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&quot;your_postgres_password_here&quot;</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">const </span><span style="color:#bd93f9;">DB_NAME</span><span style="color:#f8f8f2;">: </span><span style="color:#ff79c6;">&amp;</span><span style="font-style:italic;color:#8be9fd;">str </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&quot;your_database_name_here&quot;</span><span style="color:#f8f8f2;">; </span><span class="lol"></span></code></pre> <p>Next, we can go ahead and start the SSH tunnel. The flow is relatively straightforward: open the connection, connect to Postgres, and then pass the stream to <code>tokio-postgres</code> to use. At time of writing, <code>open_direct_tcpip_channel</code> has no documentation - but it's thankfully an explanatory enough method name that has the same idea as what you'd find in other languages and ecosystems.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">async_ssh2_tokio</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">Client, AuthMethod, ServerCheckMethod</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> key </span><span style="color:#ff79c6;">= </span><span style="text-decoration:underline;color:#66d9ef;">AuthMethod</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">with_key_file(</span><span style="color:#bd93f9;">KEY_PATH</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#66d9ef;">None</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">match </span><span style="text-decoration:underline;color:#66d9ef;">Client</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">connect(</span><span style="color:#bd93f9;">SERVER</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">USER</span><span style="color:#f8f8f2;">, key, </span><span style="text-decoration:underline;color:#66d9ef;">ServerCheckMethod</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">NoCheck)</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">await </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span style="color:#f8f8f2;">(client) </span><span style="color:#ff79c6;">=&gt; match</span><span style="color:#f8f8f2;"> client</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">open_direct_tcpip_channel</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">DB_SERVER</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#66d9ef;">None</span><span style="color:#f8f8f2;">)</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">await </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span style="color:#f8f8f2;">(channel) </span><span style="color:#ff79c6;">=&gt; </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if </span><span style="font-style:italic;color:#8be9fd;">let </span><span style="font-style:italic;color:#66d9ef;">Err</span><span style="color:#f8f8f2;">(error) </span><span style="color:#ff79c6;">= </span><span style="color:#8be9fd;">do_pg_stuff</span><span style="color:#f8f8f2;">(channel</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">into_stream</span><span style="color:#f8f8f2;">())</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">await </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">eprintln!(</span><span style="color:#f1fa8c;">&quot;Failed to do PG stuff: {:?}&quot;</span><span style="color:#f8f8f2;">, error); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">Err</span><span style="color:#f8f8f2;">(error) </span><span style="color:#ff79c6;">=&gt; </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">eprintln!(</span><span style="color:#f1fa8c;">&quot;Unable to open TCP/IP channel: {:?}&quot;</span><span style="color:#f8f8f2;">, error); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">Err</span><span style="color:#f8f8f2;">(error) </span><span style="color:#ff79c6;">=&gt; </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">eprintln!(</span><span style="color:#f1fa8c;">&quot;Unable to connect via SSH: {:?}&quot;</span><span style="color:#f8f8f2;">, error); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span></code></pre><h2 id="step-2-postgres">Step 2: Postgres</h2> <p>With a successful tunnel going, we can complete our Postgres client and execute a simple select to make sure things work. The key part is calling <code>connect_raw</code>, which accepts a stream interface. Rather than deal with type hell, we're going to just take the easy route and let the compiler infer things for us through our parameter <code>S</code>.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">std</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">marker</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Unpin; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">tokio</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">io</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">AsyncRead, AsyncWrite</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">tokio_postgres</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">Config, Error, NoTls</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;">async </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">do_pg_stuff</span><span style="color:#f8f8f2;">&lt;S&gt;(</span><span style="font-style:italic;color:#ffb86c;">stream</span><span style="color:#f8f8f2;">: S) </span><span style="color:#ff79c6;">-&gt; Result&lt;(), Error&gt; </span><span class="lol"></span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">S: AsyncRead + AsyncWrite + Unpin + Send + </span><span style="color:#ff79c6;">&#39;static</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let </span><span style="color:#ff79c6;">mut</span><span style="color:#f8f8f2;"> config </span><span style="color:#ff79c6;">= </span><span style="text-decoration:underline;color:#66d9ef;">Config</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">new(); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">config</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">user</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">DB_USER</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">config</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">password</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">DB_PASSWORD</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">config</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">dbname</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">DB_NAME</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let </span><span style="color:#f8f8f2;">(client, connection) </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> config</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">connect_raw</span><span style="color:#f8f8f2;">(stream, NoTls)</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">await</span><span style="color:#ff79c6;">?</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="text-decoration:underline;color:#66d9ef;">tokio</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">spawn(async </span><span style="color:#ff79c6;">move </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if </span><span style="font-style:italic;color:#8be9fd;">let </span><span style="font-style:italic;color:#66d9ef;">Err</span><span style="color:#f8f8f2;">(error) </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> connection</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">await </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">eprintln!(</span><span style="color:#f1fa8c;">&quot;Connection error: {:?}&quot;</span><span style="color:#f8f8f2;">, error); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> rows </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> client </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">query</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&quot;SELECT $1::TEXT&quot;</span><span style="color:#f8f8f2;">, </span><span style="color:#ff79c6;">&amp;</span><span style="color:#f8f8f2;">[</span><span style="color:#ff79c6;">&amp;</span><span style="color:#f1fa8c;">&quot;hello world&quot;</span><span style="color:#f8f8f2;">]) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">await</span><span style="color:#ff79c6;">?</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> value: </span><span style="color:#ff79c6;">&amp;</span><span style="font-style:italic;color:#8be9fd;">str </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> rows[</span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">]</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">get</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">println!(</span><span style="color:#f1fa8c;">&quot;</span><span style="color:#bd93f9;">{}</span><span style="color:#f1fa8c;">&quot;</span><span style="color:#f8f8f2;">, value); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span style="color:#f8f8f2;">(()) </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span></code></pre> <p>Put it all together, and you have a Postgres client in Rust over an SSH tunnel. This is definitely simpler with <code>tokio-postgres</code>; if you find yourself wanting to do this with <code>diesel</code> or <code>sqlx</code>, you'll likely need to do some extra legwork with their respective <code>Connection</code> traits to ferry things back and forth.</p> UUIDv7 TypeIDs in Diesel Wed, 10 Jul 2024 00:00:00 +0000 https://rymc.io/blog/2024/uuidv7-typeids-in-diesel/ https://rymc.io/blog/2024/uuidv7-typeids-in-diesel/ <p>In a project I'm putting together, I made the choice to utilize UUIDs as identifiers early on. PostgreSQL has strong support for them, and if you're using UUIDv7, you avoid some of the issues that existed with earlier versions regarding indexing and sortability. In representing these IDs, it's common nowadays to want to utilize &quot;Stripe-style&quot; identifiers, something akin to:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#f8f8f2;">// {slug}_{base32encodedUUID} </span><span class="lol"></span><span style="color:#f8f8f2;">user_1j2d6y6h2f29awcs1fz7axq8s </span><span class="lol"></span></code></pre> <blockquote> <p>On the Postgres side, as of writing this you'll need to add and load an extension for generating UUIDv7 identifiers - or you can search around for a (potentially slower) PSQL creation function.</p> </blockquote> <h2 id="typeids-in-diesel">TypeIDs in Diesel</h2> <p>When representing these identifiers in <a href="https://diesel.rs/">diesel</a> backed types, I wanted a type that wrapped <code>uuid::Uuid</code> and was specific to the type and necessary <code>{slug}_</code> prefix. I came up with the following solution:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;">/// A generic struct that we can implement the prefix over. </span><span class="lol"></span><span style="color:#f8f8f2;">#[derive(Clone, Copy, Debug, Eq, PartialEq)] </span><span class="lol"></span><span style="color:#ff79c6;">pub </span><span style="font-style:italic;color:#8be9fd;">struct </span><span style="color:#f8f8f2;">UserIDPrefix; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl </span><span style="color:#f8f8f2;">TypedIDPrefix </span><span style="color:#ff79c6;">for </span><span style="color:#f8f8f2;">UserIDPrefix </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// The prefix that this should use for public representation. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">const </span><span style="color:#bd93f9;">PREFIX</span><span style="color:#f8f8f2;">: </span><span style="color:#ff79c6;">&amp;&#39;static </span><span style="font-style:italic;color:#8be9fd;">str </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&quot;user&quot;</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">/// Export the complete type for use everywhere else. </span><span class="lol"></span><span style="color:#ff79c6;">pub </span><span style="font-style:italic;color:#8be9fd;">type </span><span style="color:#f8f8f2;">UserID </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">TypedID&lt;UserIDPrefix&gt;; </span><span class="lol"></span></code></pre> <p>Your model or struct might then look as simple as:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#f8f8f2;">#[derive(Clone, Identifiable, Queryable, Selectable)] </span><span class="lol"></span><span style="color:#f8f8f2;">#[diesel(table_name </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> crate::schema::users)] </span><span class="lol"></span><span style="color:#ff79c6;">pub </span><span style="font-style:italic;color:#8be9fd;">struct </span><span style="color:#f8f8f2;">User </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">pub </span><span style="color:#ffffff;">id</span><span style="color:#f8f8f2;">: UserID, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">pub </span><span style="color:#ffffff;">name</span><span style="color:#f8f8f2;">: String, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">pub </span><span style="color:#ffffff;">email</span><span style="color:#f8f8f2;">: String, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// etc... </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span></code></pre> <p><code>TypedID</code> itself is a type that transparently handles the following scenarios:</p> <ul> <li>Loading UUIDs from Postgres via Diesel</li> <li>Writing the UUID back to Postgres, or using it in queries</li> <li>Serializing the value in &quot;public&quot; form, with the <code>{slug_ base32encodedUUID}</code> format</li> <li>Deserializing the value from the &quot;public&quot; form into the underlying <code>uuid::Uuid</code>.</li> </ul> <blockquote> <p>With the serde implementations, you can also use this type in HTTP handlers when using frameworks like <a href="https://crates.io/crates/axum">axum</a>.</p> </blockquote> <blockquote> <p>Note that this also uses the <a href="https://crates.io/crates/fast32"><code>fast32</code></a> crate for base32 conversion. You can, of course, substitute whatever you'd like here.</p> </blockquote> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">std</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">fmt; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">std</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">hash</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Hash; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">std</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">io</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Write; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">std</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">marker</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">PhantomData; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">std</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">ops</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Deref; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">deserialize</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">FromSql; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">pg</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">Pg, PgValue</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">serialize</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">IsNull, Output, ToSql</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">sql_types</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Uuid </span><span style="color:#ff79c6;">as</span><span style="color:#f8f8f2;"> PgUuid; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">fast32</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">base32</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#bd93f9;">CROCKFORD_LOWER</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">serde</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">Serialize, Serializer</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">serde</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">de</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#bd93f9;">self</span><span style="color:#f8f8f2;">, Deserialize, Deserializer, Unexpected</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">uuid</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Uuid; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">/// A trait for denoting the prefix that a `TypedID&lt;&gt;` </span><span class="lol"></span><span style="color:#6272a4;">/// should use when displaying/formatting. </span><span class="lol"></span><span style="color:#ff79c6;">pub </span><span style="font-style:italic;color:#8be9fd;">trait </span><span style="color:#f8f8f2;">TypedIDPrefix: Clone + Copy + fmt::Debug </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// The prefix to use in IDs, 4 characters or less. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// (Ideally 4 characters) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">const </span><span style="color:#bd93f9;">PREFIX</span><span style="color:#f8f8f2;">: </span><span style="color:#ff79c6;">&amp;&#39;static </span><span style="font-style:italic;color:#8be9fd;">str</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">/// A `TypedID` is made up of two parts: </span><span class="lol"></span><span style="color:#6272a4;">/// </span><span class="lol"></span><span style="color:#6272a4;">/// - A `prefix`, a 4-character `&amp;&#39;static str` that&#39;s prepended </span><span class="lol"></span><span style="color:#6272a4;">/// - A (base32 encoded) `uuid` (v7) that forms the back half of the ID </span><span class="lol"></span><span style="color:#6272a4;">/// </span><span class="lol"></span><span style="color:#6272a4;">/// The ID effectively ends up looking like (e.g): </span><span class="lol"></span><span style="color:#6272a4;">/// </span><span class="lol"></span><span style="color:#6272a4;">/// ```no_run </span><span class="lol"></span><span style="color:#6272a4;">/// user_3LKQhvGUcADgqoEM3bh6psl </span><span class="lol"></span><span style="color:#6272a4;">/// ``` </span><span class="lol"></span><span style="color:#f8f8f2;">#[derive(Clone, Copy, Debug, PartialEq, FromSqlRow, AsExpression, Eq)] </span><span class="lol"></span><span style="color:#f8f8f2;">#[diesel(sql_type </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> PgUuid)] </span><span class="lol"></span><span style="color:#ff79c6;">pub </span><span style="font-style:italic;color:#8be9fd;">struct </span><span style="color:#f8f8f2;">TypedID&lt;PrefixType&gt;(PhantomData&lt;PrefixType&gt;, Uuid); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl</span><span style="color:#f8f8f2;">&lt;PrefixType&gt; </span><span style="text-decoration:underline;color:#66d9ef;">fmt</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Display </span><span style="color:#ff79c6;">for </span><span style="color:#f8f8f2;">TypedID&lt;PrefixType&gt; </span><span class="lol"></span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">PrefixType: TypedIDPrefix, </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// Formats the value. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">fmt</span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">&amp;</span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">f</span><span style="color:#f8f8f2;">: </span><span style="color:#ff79c6;">&amp;mut </span><span style="text-decoration:underline;color:#66d9ef;">fmt</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Formatter&lt;&#39;</span><span style="color:#ff79c6;">_</span><span style="color:#f8f8f2;">&gt;) </span><span style="color:#ff79c6;">-&gt; </span><span style="text-decoration:underline;color:#66d9ef;">fmt</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ff79c6;">Result </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">write!(f, </span><span style="color:#f1fa8c;">&quot;</span><span style="color:#bd93f9;">{}</span><span style="color:#f1fa8c;">_</span><span style="color:#bd93f9;">{}</span><span style="color:#f1fa8c;">&quot;</span><span style="color:#f8f8f2;">, </span><span style="text-decoration:underline;color:#66d9ef;">PrefixType</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#bd93f9;">PREFIX</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">CROCKFORD_LOWER</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">encode_uuid</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#bd93f9;">1</span><span style="color:#f8f8f2;">)) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl</span><span style="color:#f8f8f2;">&lt;PrefixType&gt; Hash </span><span style="color:#ff79c6;">for </span><span style="color:#f8f8f2;">TypedID&lt;PrefixType&gt; </span><span class="lol"></span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">PrefixType: TypedIDPrefix, </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// Enables using this type as a hashable value. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">hash</span><span style="color:#f8f8f2;">&lt;H: </span><span style="text-decoration:underline;color:#66d9ef;">std</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">hash</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Hasher&gt;(</span><span style="color:#ff79c6;">&amp;</span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">state</span><span style="color:#f8f8f2;">: </span><span style="color:#ff79c6;">&amp;mut</span><span style="color:#f8f8f2;"> H) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#bd93f9;">0.</span><span style="color:#8be9fd;">hash</span><span style="color:#f8f8f2;">(</span><span style="text-decoration:underline;color:#66d9ef;">PrefixType</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#bd93f9;">PREFIX</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#bd93f9;">1.</span><span style="color:#8be9fd;">hash</span><span style="color:#f8f8f2;">(state); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl</span><span style="color:#f8f8f2;">&lt;PrefixType&gt; Serialize </span><span style="color:#ff79c6;">for </span><span style="color:#f8f8f2;">TypedID&lt;PrefixType&gt; </span><span class="lol"></span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">PrefixType: TypedIDPrefix, </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// Serializes the identifier as a String, in the form of </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// `{prefix}_{base32encodeduuidv7}`. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">serialize</span><span style="color:#f8f8f2;">&lt;S&gt;(</span><span style="color:#ff79c6;">&amp;</span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">serializer</span><span style="color:#f8f8f2;">: S) </span><span style="color:#ff79c6;">-&gt; Result&lt;</span><span style="font-style:italic;color:#8be9fd;">S</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#ff79c6;">Ok, </span><span style="font-style:italic;color:#8be9fd;">S</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#ff79c6;">Error&gt; </span><span class="lol"></span><span style="color:#ff79c6;"> </span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">S: Serializer, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> id </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">to_string</span><span style="color:#f8f8f2;">(); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">serializer</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">serialize_str</span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">&amp;</span><span style="color:#f8f8f2;">id) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl</span><span style="color:#f8f8f2;">&lt;</span><span style="color:#ff79c6;">&#39;de</span><span style="color:#f8f8f2;">, PrefixType&gt; Deserialize&lt;</span><span style="color:#ff79c6;">&#39;de</span><span style="color:#f8f8f2;">&gt; </span><span style="color:#ff79c6;">for </span><span style="color:#f8f8f2;">TypedID&lt;PrefixType&gt; </span><span class="lol"></span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">PrefixType: TypedIDPrefix, </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// Attempts to deserialize the value. This checks the prefix on </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// an ID string before attempting to deserialize the actual UUID portion. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">deserialize</span><span style="color:#f8f8f2;">&lt;D&gt;(</span><span style="font-style:italic;color:#ffb86c;">deserializer</span><span style="color:#f8f8f2;">: D) </span><span style="color:#ff79c6;">-&gt; Result&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#ff79c6;">, </span><span style="font-style:italic;color:#8be9fd;">D</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#ff79c6;">Error&gt; </span><span class="lol"></span><span style="color:#ff79c6;"> </span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">D: Deserializer&lt;</span><span style="color:#ff79c6;">&#39;de</span><span style="color:#f8f8f2;">&gt;, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// @TODO: Could this ever be &amp;str? </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> value: </span><span style="font-style:italic;color:#66d9ef;">String </span><span style="color:#ff79c6;">= </span><span style="text-decoration:underline;color:#66d9ef;">Deserialize</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">deserialize(deserializer)</span><span style="color:#ff79c6;">?</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">for </span><span style="color:#f8f8f2;">(index, part) </span><span style="color:#ff79c6;">in</span><span style="color:#f8f8f2;"> value</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">split</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&quot;_&quot;</span><span style="color:#f8f8f2;">)</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">enumerate</span><span style="color:#f8f8f2;">() </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;"> index </span><span style="color:#ff79c6;">== </span><span style="color:#bd93f9;">0 </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if !</span><span style="color:#f8f8f2;">part</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">starts_with</span><span style="color:#f8f8f2;">(</span><span style="text-decoration:underline;color:#66d9ef;">PrefixType</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#bd93f9;">PREFIX</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return </span><span style="font-style:italic;color:#66d9ef;">Err</span><span style="color:#f8f8f2;">(</span><span style="text-decoration:underline;color:#66d9ef;">de</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">Error</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">invalid_value( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="text-decoration:underline;color:#66d9ef;">Unexpected</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Str(</span><span style="color:#ff79c6;">&amp;</span><span style="color:#f8f8f2;">value), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">&amp;</span><span style="text-decoration:underline;color:#66d9ef;">PrefixType</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#bd93f9;">PREFIX </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">)); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">continue</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return match </span><span style="color:#bd93f9;">CROCKFORD_LOWER</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">decode_uuid_str</span><span style="color:#f8f8f2;">(part) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span style="color:#f8f8f2;">(id) </span><span style="color:#ff79c6;">=&gt; </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#f8f8f2;">(PhantomData, id)), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">Err</span><span style="color:#f8f8f2;">(error) </span><span style="color:#ff79c6;">=&gt; </span><span style="font-style:italic;color:#66d9ef;">Err</span><span style="color:#f8f8f2;">(</span><span style="text-decoration:underline;color:#66d9ef;">de</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">Error</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">custom(error)) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">Err</span><span style="color:#f8f8f2;">(</span><span style="text-decoration:underline;color:#66d9ef;">de</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">Error</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">invalid_value(</span><span style="text-decoration:underline;color:#66d9ef;">Unexpected</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Str(</span><span style="color:#ff79c6;">&amp;</span><span style="color:#f8f8f2;">value), </span><span style="color:#ff79c6;">&amp;</span><span style="text-decoration:underline;color:#66d9ef;">PrefixType</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#bd93f9;">PREFIX</span><span style="color:#f8f8f2;">)) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl</span><span style="color:#f8f8f2;">&lt;PrefixType&gt; ToSql&lt;PgUuid, Pg&gt; </span><span style="color:#ff79c6;">for </span><span style="color:#f8f8f2;">TypedID&lt;PrefixType&gt; </span><span class="lol"></span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">PrefixType: TypedIDPrefix, </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// Writes the underlying uuid bytes to the output. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">to_sql</span><span style="color:#f8f8f2;">&lt;</span><span style="color:#ff79c6;">&#39;b</span><span style="color:#f8f8f2;">&gt;(</span><span style="color:#ff79c6;">&amp;&#39;b </span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">out</span><span style="color:#f8f8f2;">: </span><span style="color:#ff79c6;">&amp;mut </span><span style="color:#f8f8f2;">Output&lt;</span><span style="color:#ff79c6;">&#39;b</span><span style="color:#f8f8f2;">, &#39;</span><span style="color:#ff79c6;">_</span><span style="color:#f8f8f2;">, Pg&gt;) </span><span style="color:#ff79c6;">-&gt; </span><span style="text-decoration:underline;color:#66d9ef;">serialize</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ff79c6;">Result </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">out</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">write_all</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#bd93f9;">1.</span><span style="color:#8be9fd;">as_bytes</span><span style="color:#f8f8f2;">()) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">map</span><span style="color:#f8f8f2;">(|_| </span><span style="text-decoration:underline;color:#66d9ef;">IsNull</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">No) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">map_err</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#66d9ef;">Into</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">into) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl</span><span style="color:#f8f8f2;">&lt;PrefixType&gt; FromSql&lt;PgUuid, Pg&gt; </span><span style="color:#ff79c6;">for </span><span style="color:#f8f8f2;">TypedID&lt;PrefixType&gt; </span><span class="lol"></span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">PrefixType: TypedIDPrefix, </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// Convert from the SQL byte payload into our custom type. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">from_sql</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">bytes</span><span style="color:#f8f8f2;">: PgValue&lt;&#39;</span><span style="color:#ff79c6;">_</span><span style="color:#f8f8f2;">&gt;) </span><span style="color:#ff79c6;">-&gt; </span><span style="text-decoration:underline;color:#66d9ef;">deserialize</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ff79c6;">Result&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#ff79c6;">&gt; </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">match </span><span style="text-decoration:underline;color:#66d9ef;">Uuid</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">from_slice(bytes</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">as_bytes</span><span style="color:#f8f8f2;">()) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span style="color:#f8f8f2;">(id) </span><span style="color:#ff79c6;">=&gt; </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#f8f8f2;">(PhantomData, id)), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// For whatever reason, Diesel is able to just call `Into::into` </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// for this error - but doing that here fails? </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// @TODO: Probably look into this at some point. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">Err</span><span style="color:#f8f8f2;">(error) </span><span style="color:#ff79c6;">=&gt; </span><span style="font-style:italic;color:#66d9ef;">Err</span><span style="color:#f8f8f2;">(error</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">to_string</span><span style="color:#f8f8f2;">()</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">into</span><span style="color:#f8f8f2;">()) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl</span><span style="color:#f8f8f2;">&lt;PrefixType&gt; Deref </span><span style="color:#ff79c6;">for </span><span style="color:#f8f8f2;">TypedID&lt;PrefixType&gt; </span><span class="lol"></span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">PrefixType: TypedIDPrefix, </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">type </span><span style="color:#f8f8f2;">Target </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> Uuid; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">deref</span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">&amp;</span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">-&gt; &amp;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#ff79c6;">Target </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">&amp;</span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#bd93f9;">1 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl</span><span style="color:#f8f8f2;">&lt;PrefixType&gt; TypedID&lt;PrefixType&gt; </span><span class="lol"></span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">PrefixType: TypedIDPrefix, </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// Allocates and returns a new `String` in the </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// `{prefix}_{base32encodeduuidv7}` format. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">pub </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">to_string</span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">&amp;</span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">-&gt; String </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">format!(</span><span style="color:#f1fa8c;">&quot;</span><span style="color:#bd93f9;">{}</span><span style="color:#f1fa8c;">&quot;</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">self</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// Returns a copy of the underlying `Uuid`. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">pub </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">uuid</span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">&amp;</span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">-&gt; Uuid </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#bd93f9;">1 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span></code></pre> ORM-ish Diesel Helpers Tue, 09 Jul 2024 00:00:00 +0000 https://rymc.io/blog/2024/orm-ish-diesel-helpers/ https://rymc.io/blog/2024/orm-ish-diesel-helpers/ <p>In the Rust ecosystem, one of the more popular methods of interfacing with a database is <a href="https://diesel.rs/">diesel</a>. It's not an ORM, but it has some qualities that can make it feel like one.</p> <p>I'm working on a project where I wanted to add an admin interface for records that are more or less flat objects. Admin interfaces tend to very boilerplate-heavy, and this was no exception - so I wound up cobbling together a trait that enabled a slightly cleaner import and query structure for fetching and deleting entities.</p> <p>It's not a solution for every use case, and you probably don't want to blanket-apply this in all scenarios - but I've found it more handy than I originally figured it'd be and so decided I'd dump it here in case anyone else is looking (especially since it seems people have been poking at this topic around the internet for a few years now).</p> <p>As a bonus, jump to the next block for an <code>async</code> implementation. Hope it helps!</p> <h2 id="sync-helpers">Sync Helpers</h2> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">Connection, OptionalExtension</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">associations</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">HasTable; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">dsl</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">Find, Limit</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">query_builder</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">DeleteStatement, IntoUpdateTarget</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">query_dsl</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">LoadQuery, RunQueryDsl</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">query_dsl</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">methods</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">FindDsl, LimitDsl</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">/// A type alias just to make the trait bounds easier to read. </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">type </span><span style="color:#f8f8f2;">DeleteFindStatement</span><span style="color:#ff79c6;">&lt;</span><span style="color:#f8f8f2;">F</span><span style="color:#ff79c6;">&gt; = </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">DeleteStatement&lt;&lt;F </span><span style="color:#ff79c6;">as</span><span style="color:#f8f8f2;"> HasTable&gt;</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Table, &lt;F </span><span style="color:#ff79c6;">as</span><span style="color:#f8f8f2;"> IntoUpdateTarget&gt;</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">WhereClause&gt;; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">/// A trait that contains default implementations for common </span><span class="lol"></span><span style="color:#6272a4;">/// routines that we call very often. As long as a type derives </span><span class="lol"></span><span style="color:#6272a4;">/// `diesel::HasTable`, the methods on this trait will &quot;just work&quot; </span><span class="lol"></span><span style="color:#6272a4;">/// for certain common operations that we wind up polluting everywhere </span><span class="lol"></span><span style="color:#6272a4;">/// else. `HasTable` is generally derived on the core types that point </span><span class="lol"></span><span style="color:#6272a4;">/// to tables, so this comes &quot;for free&quot; in most cases. </span><span class="lol"></span><span style="color:#ff79c6;">pub </span><span style="font-style:italic;color:#8be9fd;">trait </span><span style="color:#f8f8f2;">GenericModelMethods: HasTable </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// Given an entity primary key, attempts to locate and return a record from </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// the database. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// If no entity is found, this will return `Ok(None)` - it&#39;s effectively </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// just masking the need to check for a diesel error type of `does not exist` </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// or by filtering for something when we know it&#39;s either one or none. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">find_by_pk</span><span style="color:#f8f8f2;">&lt;</span><span style="color:#ff79c6;">&#39;a</span><span style="color:#f8f8f2;">, PK, Conn&gt;( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">conn</span><span style="color:#f8f8f2;">: </span><span style="color:#ff79c6;">&amp;mut</span><span style="color:#f8f8f2;"> Conn, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">primary_key</span><span style="color:#f8f8f2;">: PK, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">-&gt; Result&lt;Option&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#ff79c6;">&gt;, </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">result</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ff79c6;">Error&gt; </span><span class="lol"></span><span style="color:#ff79c6;"> </span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#f8f8f2;">: Sized, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">Conn: Connection, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Table: FindDsl&lt;PK&gt;, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Table </span><span style="color:#ff79c6;">as </span><span style="color:#f8f8f2;">FindDsl&lt;PK&gt;&gt;</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Output: RunQueryDsl&lt;Conn&gt;, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">Find&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Table, PK&gt;: LimitDsl, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">Limit&lt;Find&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Table, PK&gt;&gt;: LoadQuery&lt;</span><span style="color:#ff79c6;">&#39;a</span><span style="color:#f8f8f2;">, Conn, </span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#f8f8f2;">&gt;, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> entity </span><span style="color:#ff79c6;">= </span><span style="text-decoration:underline;color:#66d9ef;">FindDsl</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">find(</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">table(), primary_key) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">first</span><span style="color:#f8f8f2;">(conn) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">optional</span><span style="color:#f8f8f2;">()</span><span style="color:#ff79c6;">?</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span style="color:#f8f8f2;">(entity) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// Attempts to delete a row for the specified `primary_key`. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// This will return the deleted row for any last minute usage. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">delete_by_pk</span><span style="color:#f8f8f2;">&lt;</span><span style="color:#ff79c6;">&#39;a</span><span style="color:#f8f8f2;">, PK, Conn&gt;( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">conn</span><span style="color:#f8f8f2;">: </span><span style="color:#ff79c6;">&amp;mut</span><span style="color:#f8f8f2;"> Conn, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">primary_key</span><span style="color:#f8f8f2;">: PK </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">-&gt; Result&lt;Option&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#ff79c6;">&gt;, </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">result</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ff79c6;">Error&gt; </span><span class="lol"></span><span style="color:#ff79c6;"> </span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#f8f8f2;">: Sized, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">Conn: Connection, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Table: FindDsl&lt;PK&gt;, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">Find&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Table, PK&gt;: IntoUpdateTarget, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">DeleteFindStatement&lt;Find&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Table, PK&gt;&gt;: LoadQuery&lt;</span><span style="color:#ff79c6;">&#39;a</span><span style="color:#f8f8f2;">, Conn, </span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#f8f8f2;">&gt; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> query </span><span style="color:#ff79c6;">= </span><span style="text-decoration:underline;color:#66d9ef;">FindDsl</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">find(</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">table(), primary_key); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> entity </span><span style="color:#ff79c6;">= </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">delete(query) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">get_result</span><span style="color:#f8f8f2;">(conn) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">optional</span><span style="color:#f8f8f2;">()</span><span style="color:#ff79c6;">?</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span style="color:#f8f8f2;">(entity) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">/// Implement generic methods for all types that implement `HasTable`. </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl</span><span style="color:#f8f8f2;">&lt;T&gt; GenericModelMethods </span><span style="color:#ff79c6;">for </span><span style="color:#f8f8f2;">T </span><span class="lol"></span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">T: HasTable, </span><span class="lol"></span><span style="color:#ffffff;">{} </span><span class="lol"></span></code></pre><h2 id="async-helpers">Async Helpers</h2> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">OptionalExtension; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">associations</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">HasTable; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">dsl</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">Find, Limit</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">query_builder</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">DeleteStatement, IntoUpdateTarget</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">query_dsl</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">methods</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">FindDsl, LimitDsl</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel_async</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">AsyncPgConnection, RunQueryDsl</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel_async</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">methods</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">LoadQuery; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">/// A type alias just to make the trait bounds easier to read. </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">type </span><span style="color:#f8f8f2;">DeleteFindStatement</span><span style="color:#ff79c6;">&lt;</span><span style="color:#f8f8f2;">F</span><span style="color:#ff79c6;">&gt; = </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">DeleteStatement&lt;&lt;F </span><span style="color:#ff79c6;">as</span><span style="color:#f8f8f2;"> HasTable&gt;</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Table, &lt;F </span><span style="color:#ff79c6;">as</span><span style="color:#f8f8f2;"> IntoUpdateTarget&gt;</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">WhereClause&gt;; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">/// A trait that contains default implementations for common </span><span class="lol"></span><span style="color:#6272a4;">/// routines that we call very often. As long as a type derives </span><span class="lol"></span><span style="color:#6272a4;">/// `diesel::HasTable`, the methods on this trait will &quot;just work&quot; </span><span class="lol"></span><span style="color:#6272a4;">/// for certain common operations that we wind up polluting everywhere </span><span class="lol"></span><span style="color:#6272a4;">/// else. `HasTable` is generally derived on the core types that point </span><span class="lol"></span><span style="color:#6272a4;">/// to tables, so this comes &quot;for free&quot; in most cases. </span><span class="lol"></span><span style="color:#f8f8f2;">#[allow(async_fn_in_trait)] </span><span class="lol"></span><span style="color:#ff79c6;">pub </span><span style="font-style:italic;color:#8be9fd;">trait </span><span style="color:#f8f8f2;">GenericModelMethods: HasTable </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// Given an entity primary key, attempts to locate and return a record from </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// the database. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// If no entity is found, this will return `Ok(None)` - it&#39;s effectively </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// just masking the need to check for a diesel error type of `does not exist` </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// or by filtering for something when we know it&#39;s either one or none. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// Note that this method cannot currently be generic over `AsyncConnection` </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// due to a bug in lifetime inference in the Rust compiler. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// (Sub your connection type, basically) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">async </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">find_by_pk</span><span style="color:#f8f8f2;">&lt;</span><span style="color:#ff79c6;">&#39;a</span><span style="color:#f8f8f2;">, PK&gt;( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">conn</span><span style="color:#f8f8f2;">: </span><span style="color:#ff79c6;">&amp;mut</span><span style="color:#f8f8f2;"> AsyncPgConnection, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">primary_key</span><span style="color:#f8f8f2;">: PK, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">-&gt; Result&lt;Option&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#ff79c6;">&gt;, </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">result</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ff79c6;">Error&gt; </span><span class="lol"></span><span style="color:#ff79c6;"> </span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#f8f8f2;">: Sized + Send, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Table: FindDsl&lt;PK&gt;, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Table </span><span style="color:#ff79c6;">as </span><span style="color:#f8f8f2;">FindDsl&lt;PK&gt;&gt;</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Output: RunQueryDsl&lt;AsyncPgConnection&gt;, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">Find&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Table, PK&gt;: LimitDsl, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">Limit&lt;Find&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Table, PK&gt;&gt;: LoadQuery&lt;</span><span style="color:#ff79c6;">&#39;a</span><span style="color:#f8f8f2;">, AsyncPgConnection, </span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#f8f8f2;">&gt; + Send + </span><span style="color:#ff79c6;">&#39;a</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> entity </span><span style="color:#ff79c6;">= </span><span style="text-decoration:underline;color:#66d9ef;">FindDsl</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">find(</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">table(), primary_key) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">first</span><span style="color:#f8f8f2;">(conn)</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">await </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">optional</span><span style="color:#f8f8f2;">()</span><span style="color:#ff79c6;">?</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span style="color:#f8f8f2;">(entity) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// Attempts to delete a row for the specified `primary_key`. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// This will return the deleted row for any last minute usage. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// Note that this method cannot currently be generic over `AsyncConnection` </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// due to a bug in lifetime inference in the Rust compiler. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/// (Sub your connection type, basically) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">async </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">delete_by_pk</span><span style="color:#f8f8f2;">&lt;</span><span style="color:#ff79c6;">&#39;a</span><span style="color:#f8f8f2;">, PK&gt;( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">conn</span><span style="color:#f8f8f2;">: </span><span style="color:#ff79c6;">&amp;mut</span><span style="color:#f8f8f2;"> AsyncPgConnection, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">primary_key</span><span style="color:#f8f8f2;">: PK </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">-&gt; Result&lt;Option&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#ff79c6;">&gt;, </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">result</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ff79c6;">Error&gt; </span><span class="lol"></span><span style="color:#ff79c6;"> </span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#f8f8f2;">: Sized + Send, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Table: FindDsl&lt;PK&gt;, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">Find&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Table, PK&gt;: IntoUpdateTarget, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">DeleteFindStatement&lt;Find&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="font-style:italic;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Table, PK&gt;&gt;: LoadQuery&lt;</span><span style="color:#ff79c6;">&#39;a</span><span style="color:#f8f8f2;">, AsyncPgConnection, </span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#f8f8f2;">&gt; + Send + </span><span style="color:#ff79c6;">&#39;a</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> query </span><span style="color:#ff79c6;">= </span><span style="text-decoration:underline;color:#66d9ef;">FindDsl</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">find(</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">table(), primary_key); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> entity </span><span style="color:#ff79c6;">= </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">delete(query) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">get_result</span><span style="color:#f8f8f2;">(conn)</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">await </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">optional</span><span style="color:#f8f8f2;">()</span><span style="color:#ff79c6;">?</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span style="color:#f8f8f2;">(entity) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">/// Implement async generic methods for all types that implement `HasTable`. </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl</span><span style="color:#f8f8f2;">&lt;T&gt; GenericModelMethods </span><span style="color:#ff79c6;">for </span><span style="color:#f8f8f2;">T </span><span class="lol"></span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">T: </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">associations</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">HasTable, </span><span class="lol"></span><span style="color:#ffffff;">{} </span><span class="lol"></span></code></pre> Pagination in Async Diesel Fri, 10 May 2024 00:00:00 +0000 https://rymc.io/blog/2024/pagination-in-diesel-async/ https://rymc.io/blog/2024/pagination-in-diesel-async/ <p>In the Rust ecosystem, there are quite a few ways to approach interacting with Postgres. One of the older and more established libraries is <a href="https://diesel.rs/">diesel</a>, which effectively gives you a Rust-wrapped path to constructing and executing queries. If it helps, you can think of it as a &quot;light&quot; ORM.</p> <p>One of the bigger questions over the years has been whether <code>diesel</code> not being &quot;async by default&quot; is an issue. You could always wrap your queries in tasks and just await those, and that's probably fine - in fact, if I understand correctly, this is what <a href="https://crates.io/">crates.io</a> does. However, <a href="https://crates.io/crates/diesel-async">diesel-async</a> also exists for those who want to have a &quot;pure&quot; approach to the async interaction pattern. It's a smaller crate that effectively just requires importing a few extra traits to &quot;override&quot; the core <code>diesel</code> traits, and changing your connection type to be an asynchronous one. Overall, I've found it works pretty well.</p> <p>One thing I did run into recently was needing some pagination for an admin interface I was throwing together. <a href="https://github.com/diesel-rs/diesel/blob/master/examples/postgres/advanced-blog-cli/src/pagination.rs">diesel actually has an example for this</a> that works well if you're using synchronous calls or wrapping in tasks, but if you're using <code>diesel-async</code>, you'll need to tweak some things to make it work. I figured I'd share my changes here in case anyone needs them; yes, these <em>could</em> be on GitHub, but I'm in a weird phase of re-evaluating my usage of the platform, so I'm dumping it here.</p> <p>You're smart, you can figure it out. The only real changes needed were importing some <code>diesel-async</code> items and tweaking the bounds on <code>load_and_count_pages</code> to accomodate the async differences. With this, you should be able to just call <code>.paginate(page)</code> on your queries and have mostly automatic paging. Enjoy.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">pg</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Pg; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">prelude</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">query_builder</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">sql_types</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">BigInt; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">// Import these, since we&#39;ll need them. </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel_async</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">AsyncPgConnection; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">diesel_async</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">methods</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">LoadQuery; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">pub </span><span style="font-style:italic;color:#8be9fd;">trait </span><span style="color:#f8f8f2;">Paginate: Sized </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">paginate</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">page</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#8be9fd;">i64</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">-&gt; Paginated&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#ff79c6;">&gt;</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl</span><span style="color:#f8f8f2;">&lt;T&gt; Paginate </span><span style="color:#ff79c6;">for </span><span style="color:#f8f8f2;">T </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">paginate</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">page</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#8be9fd;">i64</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">-&gt; Paginated&lt;</span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#ff79c6;">&gt; </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">Paginated </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">query: </span><span style="color:#bd93f9;">self</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">per_page: </span><span style="color:#bd93f9;">DEFAULT_PER_PAGE</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">page, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">offset: (page </span><span style="color:#ff79c6;">- </span><span style="color:#bd93f9;">1</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">* </span><span style="color:#bd93f9;">DEFAULT_PER_PAGE</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">const </span><span style="color:#bd93f9;">DEFAULT_PER_PAGE</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#8be9fd;">i64 </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">10</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;">#[derive(Debug, Clone, Copy, QueryId)] </span><span class="lol"></span><span style="color:#ff79c6;">pub </span><span style="font-style:italic;color:#8be9fd;">struct </span><span style="color:#f8f8f2;">Paginated&lt;T&gt; </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">query</span><span style="color:#f8f8f2;">: T, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">page</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#8be9fd;">i64</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">per_page</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#8be9fd;">i64</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">offset</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#8be9fd;">i64</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl</span><span style="color:#f8f8f2;">&lt;T&gt; Paginated&lt;T&gt; </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">pub </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">per_page</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">per_page</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#8be9fd;">i64</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">-&gt; </span><span style="font-style:italic;color:#8be9fd;">Self </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">Paginated </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">per_page, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">offset: (</span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">page </span><span style="color:#ff79c6;">- </span><span style="color:#bd93f9;">1</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;"> per_page, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">..</span><span style="color:#bd93f9;">self </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// Mark the bounds on `T` and `U` to ensure the async calls compile correctly, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// and have `results` go through the async `RunQueryDsl`. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">pub</span><span style="color:#f8f8f2;"> async </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">load_and_count_pages</span><span style="color:#f8f8f2;">&lt;</span><span style="color:#ff79c6;">&#39;a</span><span style="color:#f8f8f2;">, U&gt;( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">conn</span><span style="color:#f8f8f2;">: </span><span style="color:#ff79c6;">&amp;mut</span><span style="color:#f8f8f2;"> AsyncPgConnection </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">-&gt; QueryResult&lt;(Vec&lt;U&gt;, </span><span style="font-style:italic;color:#8be9fd;">i64</span><span style="color:#ff79c6;">)&gt; </span><span class="lol"></span><span style="color:#ff79c6;"> </span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">T: </span><span style="color:#ff79c6;">&#39;a</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">U: Send + </span><span style="color:#ff79c6;">&#39;a</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">Self</span><span style="color:#f8f8f2;">: LoadQuery&lt;</span><span style="color:#ff79c6;">&#39;a</span><span style="color:#f8f8f2;">, AsyncPgConnection, (U, </span><span style="font-style:italic;color:#8be9fd;">i64</span><span style="color:#f8f8f2;">)&gt;, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> per_page </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">per_page; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> results </span><span style="color:#ff79c6;">= </span><span style="text-decoration:underline;color:#66d9ef;">diesel_async</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">RunQueryDsl</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">load</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">&lt;(U, </span><span style="font-style:italic;color:#8be9fd;">i64</span><span style="color:#f8f8f2;">)&gt;(</span><span style="color:#bd93f9;">self</span><span style="color:#f8f8f2;">, conn)</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">await</span><span style="color:#ff79c6;">?</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> total </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> results</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">first</span><span style="color:#f8f8f2;">()</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">map</span><span style="color:#f8f8f2;">(|</span><span style="font-style:italic;color:#ffb86c;">x</span><span style="color:#f8f8f2;">| x</span><span style="color:#ff79c6;">.</span><span style="color:#bd93f9;">1</span><span style="color:#f8f8f2;">)</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">unwrap_or</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> records </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> results</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">into_iter</span><span style="color:#f8f8f2;">()</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">map</span><span style="color:#f8f8f2;">(|</span><span style="font-style:italic;color:#ffb86c;">x</span><span style="color:#f8f8f2;">| x</span><span style="color:#ff79c6;">.</span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">)</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">collect</span><span style="color:#f8f8f2;">(); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> total_pages </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">(total </span><span style="color:#ff79c6;">as </span><span style="font-style:italic;color:#8be9fd;">f64 </span><span style="color:#ff79c6;">/</span><span style="color:#f8f8f2;"> per_page </span><span style="color:#ff79c6;">as </span><span style="font-style:italic;color:#8be9fd;">f64</span><span style="color:#f8f8f2;">)</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">ceil</span><span style="color:#f8f8f2;">() </span><span style="color:#ff79c6;">as </span><span style="font-style:italic;color:#8be9fd;">i64</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span style="color:#f8f8f2;">((records, total_pages)) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl</span><span style="color:#f8f8f2;">&lt;T: Query&gt; Query </span><span style="color:#ff79c6;">for </span><span style="color:#f8f8f2;">Paginated&lt;T&gt; </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">type </span><span style="color:#f8f8f2;">SqlType </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">(</span><span style="text-decoration:underline;color:#66d9ef;">T</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">SqlType, BigInt); </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl</span><span style="color:#f8f8f2;">&lt;T&gt; RunQueryDsl&lt;PgConnection&gt; </span><span style="color:#ff79c6;">for </span><span style="color:#f8f8f2;">Paginated&lt;T&gt; </span><span style="color:#ffffff;">{} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl</span><span style="color:#f8f8f2;">&lt;T&gt; QueryFragment&lt;Pg&gt; </span><span style="color:#ff79c6;">for </span><span style="color:#f8f8f2;">Paginated&lt;T&gt; </span><span class="lol"></span><span style="color:#ff79c6;">where </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">T: QueryFragment&lt;Pg&gt;, </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">walk_ast</span><span style="color:#f8f8f2;">&lt;</span><span style="color:#ff79c6;">&#39;b</span><span style="color:#f8f8f2;">&gt;(</span><span style="color:#ff79c6;">&amp;&#39;b </span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">, </span><span style="color:#ff79c6;">mut </span><span style="font-style:italic;color:#ffb86c;">out</span><span style="color:#f8f8f2;">: AstPass&lt;&#39;</span><span style="color:#ff79c6;">_</span><span style="color:#f8f8f2;">, </span><span style="color:#ff79c6;">&#39;b</span><span style="color:#f8f8f2;">, Pg&gt;) </span><span style="color:#ff79c6;">-&gt; QueryResult&lt;()&gt; </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">out</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">push_sql</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&quot;SELECT *, COUNT(*) OVER () FROM (&quot;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">query</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">walk_ast</span><span style="color:#f8f8f2;">(out</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">reborrow</span><span style="color:#f8f8f2;">())</span><span style="color:#ff79c6;">?</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">out</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">push_sql</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&quot;) t LIMIT &quot;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">out</span><span style="color:#ff79c6;">.</span><span style="text-decoration:underline;color:#66d9ef;">push_bind_param</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">&lt;BigInt, </span><span style="color:#ff79c6;">_</span><span style="color:#f8f8f2;">&gt;(</span><span style="color:#ff79c6;">&amp;</span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">per_page)</span><span style="color:#ff79c6;">?</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">out</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">push_sql</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&quot; OFFSET &quot;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">out</span><span style="color:#ff79c6;">.</span><span style="text-decoration:underline;color:#66d9ef;">push_bind_param</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">&lt;BigInt, </span><span style="color:#ff79c6;">_</span><span style="color:#f8f8f2;">&gt;(</span><span style="color:#ff79c6;">&amp;</span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">offset)</span><span style="color:#ff79c6;">?</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span style="color:#f8f8f2;">(()) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span></code></pre> Plant-Based Berlin Recommendations Mon, 15 Jan 2024 00:00:00 +0000 https://rymc.io/blog/2024/plant-based-berlin-recs/ https://rymc.io/blog/2024/plant-based-berlin-recs/ <p>2023 has come and gone, and with it, a decent chunk of travel. The list below is an assortment of plant-based/vegan options in and around Berlin, Germany. I visited most of these during a recent trip, but never compiled them into a list until recently. I figure they're as good as anything for content to update this old still creaking along site - and it beats writing about tech for the millionth time.</p> <h2 id="eat">Eat</h2> <ul> <li><a href="https://www.google.com/maps/place/Dervish/@52.5106171,13.4548452,17z/data=!3m1!4b1!4m6!3m5!1s0x47a84ffd1870e09f:0x70f063e14ce377bb!8m2!3d52.5106139!4d13.4574201!16s%2Fg%2F11jyg9t3l1?entry=ttu">Dervish</a><br /> Vegan Uzbek cuisine. In a nightlife hotspot and open decently late, so you could do either lunch or dinner here. We never needed to make a reservation, and there are a number of other vegan restaurants on the stretch should they be full. It's not the best thing in the world, but it is cool just because it's pretty rare to find a vegan place that does this.</li> <li><a href="https://www.google.com/maps/place/FREA+Bakery/@52.5302164,13.3873343,17z/data=!3m2!4b1!5s0x47a851eee2b0d22d:0xd4a38fac073e75f!4m6!3m5!1s0x47a8519d38279af3:0xed6b8ef1dde78ba8!8m2!3d52.5302132!4d13.3919477!16s%2Fg%2F11rp4lsyfm?entry=ttu">Frea Bakery</a> / <a href="https://www.google.com/maps/place/FREA/@52.5283775,13.3913755,17z/data=!3m1!4b1!4m6!3m5!1s0x47a85111316f39c5:0x168aa62f90f8231c!8m2!3d52.5283743!4d13.3939504!16s%2Fg%2F11g2mnmf87?entry=ttu">Frea Restaurant</a><br /> Zero-waste concept, all vegan. The bakery is two or so blocks from the restaurant, both are worth going to. The bakery is great for working out of if you're in need of space. Sanctuary - a vegan italian bakery - is around the corner, but if I had to choose one to go to, it'd probably be Frea. If you want the fine dining here but can't grab a booking, Cookies Cream or Lucky Leek are good second options.</li> <li><a href="https://www.google.com/maps/place/V%C3%B6ner/@52.5067172,13.4661184,17z/data=!3m1!4b1!4m6!3m5!1s0x47a84ef6cefe7425:0xdab4ad88690e4864!8m2!3d52.506714!4d13.4686933!16s%2Fg%2F1tczjhnq?entry=ttu">Vöner - doner kebab</a><br /> The original &quot;vegan kebab&quot; and pretty much still the best. I tried quite a few alternatives before finally coming here and wish I had just come here to begin with. Some reviews say the staff are rude or whatever, but I wound up talking to them for 15 minutes and they were super chill - YMMV but I wouldn't be put off by it.</li> <li><a href="https://goo.gl/maps/yGctzvZNiJMYvuJ97">Chay Long P-Berg</a> / <a href="https://goo.gl/maps/5LNxZW1uvEsVXnAn9">Chay Long Hasenheide</a><br /> Vegan Vietnamese with two locations. The menu is slightly different depending on which location you go to; we went to the Hasenheide location since it has a Bún Cá Hải Phòng on the menu that we hadn't seen elsewhere. I remember liking it, though my wife said it was good but not perfect. It's kind of a tough one to vegan-ize though.</li> <li><a href="https://www.google.com/maps/place/Tsu+Tsu+%7C+%E3%83%84%E3%83%84/@52.4952947,13.4192238,15z/data=!4m2!3m1!1s0x0:0x1bf92566ef603eab?sa=X&amp;ved=2ahUKEwiWk7nLweGDAxVfrlYBHXyWAXUQ_BJ6BAgLEAA">Tsu Tsu Kaarage</a><br /> We found this while walking around, pleasantly surprised since it gets close enough to what you'd want from kaarage. Small place with a few seats. There's also a pie shop around the corner from here that serves Oatly soft serve, but the name escapes me unfortunately.</li> <li><a href="https://www.google.com/maps/place/Kimchi+Princess/@52.4985903,13.4233968,17z/data=!3m1!4b1!4m6!3m5!1s0x47a84f960c920b29:0x959cc128e2c3e316!8m2!3d52.4985871!4d13.4259717!16s%2Fg%2F1tj9b0k2?entry=ttu">Kimchi Princess KBBQ</a><br /> KBBQ spot that - surprisingly - has two entirely vegan options. We tried the seitan one and it's decent enough, and it <em>is</em> grilled in front of you, but they seem to not want you to handle the grill yourself which annoys me to no end. That said, I feel like finding this kind of thing with vegan options is already hard, so it was nice to experience again. They have tables for 2 if you don't want to be at a long table with others who grill meat or anything.</li> <li><a href="https://www.google.com/maps/place/la+petite+v%C3%A9ganerie+-+brasserie/@52.498802,13.4423695,20z/data=!4m6!3m5!1s0x47a84fde6bd795e7:0xa0d4a3f7af259e14!8m2!3d52.4987015!4d13.4428628!16s%2Fg%2F11v3xdx4_h?entry=ttu">la petite véganerie - brasserie</a><br /> This one is newer and we found it while looking for vegan steak. They basically try to properly recreate a French cafe style experience and have various cheeses/steaks/etc to pull it off. If you're looking into trying some plant-based steak options that we don't have in the USA - or are hard to get over here - this place can be fun. When we went the service was slower than I can possibly put into words, but it just seemed like growing pains and the staff were really chill. You can technically walk from here to Becketts Kopf if you want cocktails after.</li> <li><a href="https://www.google.com/maps/place/Tbilisi/@52.5448138,13.419016,14z/data=!3m1!5s0x47a84de37d025637:0xd049fba3cca7274f!4m6!3m5!1s0x47a852053a3736d7:0x4aa8bfe34d6c1743!8m2!3d52.5518041!4d13.4080059!16s%2Fg%2F1tdj_mkc?entry=ttu">Tbilisi</a><br /> Georgian restaurant that has vegan options. We <em>wanted</em> to go here as several friends recommended it, but it unfortunately got screwed schedule-wise. Listing it mostly on recs from others and for variety.</li> <li><a href="https://www.google.com/maps/place/F%C3%B6rsters+-+Das+Vegane+Restaurant/@52.5407723,13.4195099,15z/data=!4m6!3m5!1s0x47a84fad4d20c6e1:0xaa6ef96ee72636f3!8m2!3d52.5407723!4d13.4195099!16s%2Fg%2F11h91wbpr2?entry=ttu">Försters - Das Vegane Restaurant</a><br /> Because it's Berlin and you probably want some German food at some point. The website for this place is like... useless, but the food is decent and it's got massive portions for schnitzel. They also have options for the type of schnitzel, ranging from soy-based to... something else. I forget what. (Every list I read says to go to Vaust, but when we tried to go there they were really standoff-ish and said they weren't open anymore... well before their closed time... with no sign of an event going on. YMMV though.)</li> </ul> <p>(I <em>did</em> want to add in some easy vegan breakfast spots here, but my two go-tos - No Milk Today and Cafe Nullpunkt - unfortunately shuttered at the end of 2023. That said, most coffee shops I've been in there have <em>some</em> vegan option nowadays due to how widespread the concept is.)</p> <h2 id="drink">Drink</h2> <p>Hope you enjoy your clothes reaking of smoke. For some of the below, reservations are a good idea.</p> <ul> <li><a href="https://www.google.com/maps/place/Timber+Doodle/@52.5090231,13.4589453,17z/data=!3m1!4b1!4m6!3m5!1s0x47a84e5928444cb3:0xcef96736769e4aaa!8m2!3d52.5090231!4d13.4589453!16s%2Fg%2F11c31qqlmb?hl=en-GB&amp;entry=ttu">Timber Doodle</a> is a casual cocktail bar around the corner from Dervish that, if it's not too crowded, has some creative cocktails and good energy.</li> <li><a href="https://www.google.com/maps/place/Bar+Becketts+Kopf/@52.5455885,13.4168726,15z/data=!4m6!3m5!1s0x47a85200eb02aa4b:0x11200437143b4ec3!8m2!3d52.5455885!4d13.4168726!16s%2Fg%2F1td5_rym?entry=ttu">Bar Becketts Kopf</a><br /> Yes, it's on the world's 50 best bars list or whatever. It's legitimately good for cocktail stuff though, and the wall art inside is... well, surreal. Pretty sure the door is unmarked.</li> <li><a href="https://www.google.com/maps/place/Buck+and+Breck/@52.5321302,13.3989457,15z/data=!4m2!3m1!1s0x0:0xcfb18e2ef21f5502?sa=X&amp;ved=2ahUKEwiY0eTo1OGDAxUjrlYBHcFFD_YQ_BJ6BAgLEAA">Buck and Breck</a><br /> Also on 50 best list <em>but</em> drinks are legitimately good. No sign or anything so another unmarked bar, phones banned inside.</li> <li><a href="https://www.google.com/maps/place/Velvet/@52.4789857,13.4358819,17z/data=!3m2!4b1!5s0x47a84fa3f31bbe23:0x814b900708308e56!4m6!3m5!1s0x47a84fa3f3ae3589:0x609820859715900e!8m2!3d52.4789825!4d13.4384568!16s%2Fg%2F11b6gm17yh?entry=ttu">Velvet</a><br /> Another cocktail bar but tends to focus more on seasonal/local ingredients. Near-ish to the Chay Long Hasenheide location if you want to stack them. We wandered in on an industry night and somehow got seated, so dunno what the normal menu is - but the vibe is great.</li> <li><a href="https://www.google.com/maps/place/Protokoll+Taproom/@52.5131501,13.4570661,17z/data=!3m2!4b1!5s0x47a84e5dd7c23adb:0x51cb62857143bb31!4m6!3m5!1s0x47a84e5dd7e5e46d:0xdce8920e0e3ee2f3!8m2!3d52.5131501!4d13.4570661!16s%2Fg%2F11d_bbc075?entry=ttu">Protokoll</a><br /> Craft beer options in Berlin generally suck but this place is decent with variety and vibe.</li> <li><a href="https://www.google.com/maps/place/Motif+Wein/@52.4863148,13.3965507,13z/data=!4m8!3m7!1s0x47a84fb017567e57:0x93a4929d74a24f7e!8m2!3d52.4863148!4d13.4351745!9m1!1b1!16s%2Fg%2F11g8xk6f1_?entry=ttu">Motif</a><br /> Natural wine bar + record shop. Out of the way, but if you're in the area it's cool to know about. <em>Should</em> have vegan wines but you should of course ask.</li> </ul> <h2 id="clubs">Clubs</h2> <ul> <li><a href="https://www.google.com/maps/place/:%2F%2Fabout+blank/@52.5025323,13.4663728,15z/data=!4m2!3m1!1s0x0:0x9ba3bce436e2b6ae?sa=X&amp;ved=2ahUKEwjDn7P50-GDAxVRrlYBHVIiBoMQ_BJ6BAgVEAA"><code>://about blank</code></a><br /> Industrial club that is larger than it seems. Indoor area with rotating genres but usually house, outdoor chill area, back building that rotates genres like 70s remixes or local artists. The back building is either really great or really &quot;where the actual fuck am I&quot; experience-wise, but the club overall is great albeit out of the way. They're strict about phone use, don't be a dick to the bouncers.</li> </ul> <h2 id="shop">Shop</h2> <ul> <li>Veganz grocery store.<br /> It's easy to find - right off a large train station - and you can't miss it. Fun to walk through if you like seeing what other vegan options are available outside the USA.</li> </ul> DriverKit, /Applications, and Quarantine Wed, 15 Sep 2021 00:00:00 +0000 https://rymc.io/blog/2021/macos-driverkit-applications-quarantine/ https://rymc.io/blog/2021/macos-driverkit-applications-quarantine/ <p>Earlier this year, I released a <a href="https://secretkeys.io/gcadapterdriver/">DriverKit-based port of GCAdapterDriver</a>. For the most part it's been pretty smooth sailing, once you get accustomed to how things work in a DriverKit-based world rather than Kext-based world. There's one odd bug that's sprouted up recently that only seems to be related to distribution, though - and I've not seen it noted anywhere else, so I figured I'd throw up a quick entry about it in case any other developers start trying to debug this.</p> <h2 id="the-confusing-bug">The Confusing Bug</h2> <p>The bug is simple (ish) in nature, but only shows up when you're shipping your application. Some quick context: DriverKit-based applications <em>must</em> be in <code>/Applications</code> in order to load an extension (at least, when a user has System Integrity Protection enabled). If your user doesn't place the app in there and attempts to install the extension, the resulting error will be <code>OSSystemExtensionErrorDomain Code 3</code>, which roughly corresponds to: <em>Application is not in /Applications</em>.</p> <p>Now, users of GCAdapterDriver started getting this message even though the app <em>was</em> in the <code>/Applications</code> folder. Odd, right? I started going through the usual debugging experience, making sure it was installed correctly, and that they had restarted the app after moving it into the folder (if they had started previously). All this turned up fine, though.</p> <p>So what's going on?</p> <h2 id="enter-quarantine">Enter: Quarantine</h2> <p>A fitting title for the past year and some change, I guess.</p> <p>I recalled from other projects that if the <code>com.apple.quarantine</code> attribute was on an Application bundle, it could cause some odd errors to occur. This generally happens when an app is downloaded from the internet and flagged; for whatever reason, it sometimes doesn't get cleared when the user trusts the app. I've mostly seen this with Chrome, but I see no reason for it to not happen with any other browser.</p> <p>In this case, I asked the user to check their status with the following:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#50fa7b;">xattr</span><span style="color:#f8f8f2;"> /Applications/GCAdapterDriver.app </span><span class="lol"></span></code></pre> <p>Which, sure enough, produced the following:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#50fa7b;">com.apple.quarantine </span><span class="lol"></span></code></pre> <p>On a hunch, I asked them to clear the attribute - which you can do with the following:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#50fa7b;">sudo</span><span style="color:#f8f8f2;"> xattr</span><span style="font-style:italic;color:#ffb86c;"> -r -d</span><span style="color:#f8f8f2;"> com.apple.quarantine /Applications/GCAdapterDriver.app </span><span class="lol"></span></code></pre> <p>Lo and behold, after starting the app in a <em>non-quarantined</em> state, the driver activation worked just fine.</p> <h2 id="is-this-really-a-bug">Is this really a bug?</h2> <p>It's hard to say. I can see some logic for not activating a quarantined app's driver, but at the very least I think the error should be updated to spot the case where the application is in <code>/Applications</code> and the quarantine attribute is what's blocking driver activation. I've filed feedback (FB9628611) for this, so here's hoping it gets updated in Monterey at least.</p> Debugging Broken Signed/Notarized Apps on macOS Thu, 24 Jun 2021 00:00:00 +0000 https://rymc.io/blog/2021/macos-app-signing-notarizing-failure-debugging/ https://rymc.io/blog/2021/macos-app-signing-notarizing-failure-debugging/ <p>Consider the following: you've built a macOS app (perhaps outside of Xcode), signed and notarized it, and see no errors in your build log. This is a common scenario for CI (Continuous Integration) build pipelines.</p> <p>Now you run the resulting <code>.app</code> bundle... and the OS screams at you that the bundle is broken, and should be moved to the trash. What gives?</p> <h2 id="a-green-frog">A Green Frog</h2> <p>One project I poke at from time to time - mostly with odd and/or esoteric macOS-related things - is <a href="https://github.com/project-slippi">Project Slippi</a>. Earlier this week, they released a new <a href="https://twitter.com/_vinceau/status/1407507120275165184">Launcher</a>, which helps with a number of things related to viewing replays, automatic updating, and more. Initial testing across the various Operating Systems seemingly went okay, but on release users started reporting that the macOS build started throwing up the aforementioned <em>&quot;bundle is broken&quot;</em> notification.</p> <p><a href="https://github.com/NikhilNarayana">Nikhil</a> reached out to me fairly quickly, and we went back and forth for a few minutes debugging. Poking and prodding at the bundle structure brought up nothing special, with nothing out of place (i.e, it was a well-formed bundle). I decided to then see if maybe signing or notarizing had broken somehow. The steps I tend to default to are below, but if you want to do some more reading about the various incantations, I highly recommend <a href="https://eclecticlight.co/2019/05/31/can-you-tell-whether-code-has-been-notarized/">this Eclectic Light post</a>.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;"># Checks and displays signature information </span><span class="lol"></span><span style="color:#50fa7b;">codesign</span><span style="font-style:italic;color:#ffb86c;"> -dvvv</span><span style="color:#f8f8f2;"> /path/to/app </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;"># Checks and displays notarization status </span><span class="lol"></span><span style="color:#50fa7b;">spctl</span><span style="font-style:italic;color:#ffb86c;"> -a -vv -t</span><span style="color:#f8f8f2;"> install /path/to/app </span><span class="lol"></span></code></pre> <p>Running the latter ultimately produced something useful to go on: <code>a sealed resource is missing or invalid</code>.</p> <h2 id="resource-oddities">Resource Oddities</h2> <p>Unfortunately, that error message is a little light on details. Still, we can debug further. Scanning CI build pipeline instructions verified it hadn't been changed, and the output had no warning or error messages. We also reviewed the CI build to make sure the bundle wasn't tampered with after being built/signed/notarized, and this too checked out... so what on earth is this error?</p> <p>One piece of Apple documentation that never seems to show up in search engines (for me, at least) is <a href="https://developer.apple.com/library/archive/technotes/tn2318/_index.html">how to actually troubleshoot failing signatures</a>. It's a shame, too, because there's an incredibly useful codesign incantation in here that leads right to the error:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#50fa7b;">codesign</span><span style="font-style:italic;color:#ffb86c;"> --verify </span><span style="color:#f8f8f2;">\ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">-vvvv </span><span style="color:#f8f8f2;">\ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">-R</span><span style="color:#ff79c6;">=</span><span style="color:#f1fa8c;">&#39;anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.1] exists and (certificate leaf[field.1.2.840.113635.100.6.1.2] exists or certificate leaf[field.1.2.840.113635.100.6.1.4] exists)&#39; </span><span style="color:#f8f8f2;">\ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">/path/to/the.app </span><span class="lol"></span></code></pre> <p>Running the above brings us a slightly confusing (but still useful) error message:</p> <p><img src="/img/pokemonlol.png" style="width: 100%;"/></p> <p>Wat.</p> <h2 id="special-character-issues">Special Character Issues...?</h2> <p>These are files used in showing what stage a game was played on. The app uses the filename as a key to display the appropriate image... and for whatever reason, the accented <code>é</code> causes issues in signature verification.</p> <p><a href="https://github.com/vinceau/">Vinceau</a> thankfully had a <a href="https://github.com/project-slippi/slippi-launcher/commit/409528c42228aab919b32e2767f9a280bcf700e3">quick fix</a>: just rename all the images to key on stage ID. After kicking CI to rebuild, the resulting bundle successfully passed signature verification.</p> <p>This post illustrates a seemingly simple to debug issue, but one that I've found elusive on details for discerning the root cause. Hopefully this helps someone else in the future.</p> Cacao: Building macOS (and iOS) Apps in Rust Thu, 10 Jun 2021 00:00:00 +0000 https://rymc.io/blog/2021/cacao-rs-macos-ios-rust/ https://rymc.io/blog/2021/cacao-rs-macos-ios-rust/ <p>Building native macOS apps outside the normal Apple ecosystem is sometimes a not-so-obvious task. The shifting ecosystem of AppKit, Catalyst, and SwiftUI - coupled with documentation that's sometimes non-existent, or spanning multiple operating system versions that each have their own nuance - makes it tricky to know just what you need to tie into.</p> <p>One language that I've felt hasn't had a great solution to this yet is Rust. I've released an initial version of <a href="https://crates.io/crates/cacao">Cacao</a>, which is my effort to enable building macOS (and iOS, see below) apps in Rust. This post showcases a bit of what Cacao is, how it works, and includes some opinions on GUI work sprinkled throughout.</p> <h2 id="history-rust-and-objective-c">History, Rust and Objective-C</h2> <p>I should clarify something before going any further. When I say that Rust hasn't had a great solution to this yet, I mean that there hasn't been an elegant way to build a macOS app in Rust. There is a litany of prior work though - some of which Cacao uses.</p> <ul> <li>The <a href="https://crates.io/crates/objc">objc</a> crate by <a href="https://crates.io/users/SSheldon">SSheldon</a> has been around for years now, and does a very good job of providing a way to bridge Rust with Objective-C. Cacao relies on this heavily for bridging with the Apple side of things.</li> <li><a href="https://www.bugsnag.com/blog/building-macos-apps-with-rust">Delisa Mason wrote</a> about building macOS apps in Rust as far as back as 2016, and the <a href="https://github.com/kattrali/rust-mac-app-examples">examples repository</a> still holds up.</li> <li>The <a href="https://crates.io/crates/core-foundation">core-foundation</a> crate is often passed around as the &quot;way&quot; to build macOS apps in Rust. As far as I know, this was originally built to help Mozilla and Servo. It's a good crate, but I feel it's not as simple, and doesn't expose certain patterns that help make a macOS app feel &quot;right&quot;.</li> <li><a href="https://crates.io/crates/fruity">fruity</a> and <a href="https://crates.io/crates/objrs">objrs</a> both attempt to offer better interop with the Objective-C side of things. Neither is used by Cacao, but I felt they're worth mentioning as they both are decent reading anyway.</li> </ul> <p>Each of these are great works on their own, but none of them contribute to what I feel is a &quot;native&quot; macOS API style in Rust.</p> <h2 id="feeling-native">Feeling Native</h2> <p>So, let's define <em>native</em>.</p> <p>When you build a macOS app in the standard, conventional way (AppKit), your primary building block is your <em>Application Delegate</em>. This is effectively where you receive varying messages from the OS (notifications, lifecycle events), and then handle them accordingly. The above examples don't provide a simple way to do this, and feel very procedural. Trying to go against the grain on macOS development often becomes annoying very quickly, and thus I define <em>native</em> as being able to replicate the intended pattern of development.</p> <p>Let's look at a basic Swift macOS window example. Note that we're doing this in a no-storyboards or interface-builder fashion, as that's what Rust equivalent code will be doing; it's entirely fair to say that this could be shorter if you're the type to use Interface Builder.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">import </span><span style="font-style:italic;color:#66d9ef;">Cocoa </span><span class="lol"></span><span style="color:#ff79c6;">import </span><span style="font-style:italic;color:#66d9ef;">Foundation </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">class </span><span style="font-style:italic;color:#66d9ef;">AppDelegate</span><span style="color:#ff79c6;">: </span><span style="font-style:italic;color:#66d9ef;">NSObject</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#66d9ef;">NSApplicationDelegate</span><span style="color:#f8f8f2;"> { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">var </span><span style="font-style:italic;color:#ffb86c;">window</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">NSWindow</span><span style="color:#ff79c6;">? </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">func </span><span style="color:#50fa7b;">applicationDidFinishLaunching</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">_ </span><span style="font-style:italic;color:#ffb86c;">aNotification</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">Notification</span><span style="color:#f8f8f2;">) { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> frame = </span><span style="font-style:italic;color:#66d9ef;">CGRect</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">x</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">10.0</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">y</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">10.0</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">width</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">400.0</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">height</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">400.0 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> win = </span><span style="font-style:italic;color:#66d9ef;">NSWindow</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">contentRect</span><span style="color:#f8f8f2;">: frame, </span><span style="font-style:italic;color:#ffb86c;">styleMask</span><span style="color:#f8f8f2;">: [ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">.</span><span style="color:#bd93f9;">closable</span><span style="color:#f8f8f2;">, .</span><span style="color:#bd93f9;">fullSizeContentView</span><span style="color:#f8f8f2;">, .</span><span style="color:#bd93f9;">miniaturizable</span><span style="color:#f8f8f2;">, .</span><span style="color:#bd93f9;">fullScreen</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">.</span><span style="color:#bd93f9;">resizable</span><span style="color:#f8f8f2;">, .</span><span style="color:#bd93f9;">unifiedTitleAndToolbar </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">], </span><span style="font-style:italic;color:#ffb86c;">backing</span><span style="color:#f8f8f2;">: .</span><span style="color:#bd93f9;">buffered</span><span style="color:#f8f8f2;">, </span><span style="color:#ff79c6;">defer</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">true</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">win</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">title = </span><span style="color:#f1fa8c;">&quot;Hello world&quot; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">win</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">makeKeyAndOrderFront(</span><span style="color:#bd93f9;">nil</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">window = win </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">// Just for running the app without all the Xcode @main items. </span><span class="lol"></span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> delegate = </span><span style="font-style:italic;color:#66d9ef;">AppDelegate</span><span style="color:#f8f8f2;">() </span><span class="lol"></span><span style="font-style:italic;color:#66d9ef;">NSApplication</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">shared</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">delegate = delegate </span><span class="lol"></span><span style="color:#bd93f9;">_</span><span style="color:#f8f8f2;"> = </span><span style="font-style:italic;color:#66d9ef;">NSApplicationMain</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#66d9ef;">CommandLine</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">argc, </span><span style="font-style:italic;color:#66d9ef;">CommandLine</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">unsafeArgv) </span><span class="lol"></span></code></pre> <p>All things considered, that's pretty succinct - even the Objective-C version wouldn't be much longer! What I take note of here is specifically the <code>applicationDidFinishLaunching(_:)</code> method: this is a delegate method, and is a prime example of what I wanted in Rust. With Cacao, I feel like it gets fairly close:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">cacao</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">macos</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">App, AppDelegate</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">cacao</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">macos</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">window</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Window; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;">#[derive(Default)] </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">struct </span><span style="color:#f8f8f2;">BasicApp </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">window</span><span style="color:#f8f8f2;">: Option&lt;Window&gt; </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl </span><span style="color:#f8f8f2;">AppDelegate </span><span style="color:#ff79c6;">for </span><span style="color:#f8f8f2;">BasicApp </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">did_finish_launching</span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">&amp;</span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> window </span><span style="color:#ff79c6;">= </span><span style="text-decoration:underline;color:#66d9ef;">Window</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">default(); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">window</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">set_minimum_content_size</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">400.</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">400.</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">window</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">set_title</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&quot;Hello World!&quot;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">window</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">show</span><span style="color:#f8f8f2;">(); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">window </span><span style="color:#ff79c6;">= </span><span style="font-style:italic;color:#66d9ef;">Some</span><span style="color:#f8f8f2;">(window); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">main</span><span style="color:#f8f8f2;">() </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="text-decoration:underline;color:#66d9ef;">App</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">new(</span><span style="color:#f1fa8c;">&quot;com.hello.world&quot;</span><span style="color:#f8f8f2;">, </span><span style="text-decoration:underline;color:#66d9ef;">BasicApp</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">default())</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">run</span><span style="color:#f8f8f2;">(); </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span></code></pre> <p>Here, we do roughly the same thing as our Swift example, albeit even more succinct due to being able to take advantage of <code>Default</code> in Rust. <code>App</code> wraps <code>NSApplication</code>, and marshals events and method calls over to the <code>AppDelegate</code> trait implementation. For macOS, most of the <code>NSApplicationDelegate</code> methods and events are supported - although I haven't gotten around to wrapping <code>NSNotification</code> and passing it in to some methods. Pull requests welcome. :)</p> <p>From here, you could continue with Cacao, or defer to another framework of your choice and simply rely on Cacao for the &quot;mac&quot; pieces. In fact, each Cacao control exposes a public <code>objc</code> property, which you can lock on and message pass to directly. This enables usage of other Rust wrappers (e.g, Metal), so you're not locked in to any one thing about Cacao.</p> <h2 id="on-subclassing">On Subclassing</h2> <p>Rust doesn't have a concept of a <code>Class</code> built in to the language, instead favoring more of a composition-based approach with trait implementations. Many existing GUI models are subclass-heavy, though, and working with them can feel a bit odd from the Rust side. <a href="https://gtk-rs.org/docs/glib/subclass/index.html">gtk-rs</a> provides some utilities for working with subclasses from the Rust side, but what if we could keep it composition-based?</p> <p>Cacao supports this, in a sense. Most widget types can be declared one of two ways: stock, in which you can call into it and set your properties, and treat it like a normal control - or delegated, where you can provide a struct that implements a trait for a given widget. For example, the <code>View</code> type could be constructed this way:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> view </span><span style="color:#ff79c6;">= </span><span style="text-decoration:underline;color:#66d9ef;">View</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">default(); </span><span class="lol"></span><span style="color:#6272a4;">// Customize your view </span><span class="lol"></span></code></pre> <p>This works fine - you could build up a tree from here and slap it wherever you want. If we did it as a delegate pattern, it'd look like this:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#f8f8f2;">#[derive(Debug)] </span><span class="lol"></span><span style="color:#ff79c6;">pub </span><span style="font-style:italic;color:#8be9fd;">struct </span><span style="color:#f8f8f2;">MyView </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">handle</span><span style="color:#f8f8f2;">: Option&lt;View&gt; </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">impl </span><span style="color:#f8f8f2;">ViewDelegate </span><span style="color:#ff79c6;">for </span><span style="color:#f8f8f2;">MyView </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">did_load</span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">&amp;mut </span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">view</span><span style="color:#f8f8f2;">: View) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// Customize your view </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">handle </span><span style="color:#ff79c6;">= </span><span style="font-style:italic;color:#66d9ef;">Some</span><span style="color:#f8f8f2;">(view); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// You could implement further methods here for handling drop and drop, etc </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">// elsewhere... </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">let</span><span style="color:#f8f8f2;"> view </span><span style="color:#ff79c6;">= </span><span style="text-decoration:underline;color:#66d9ef;">View</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">with(</span><span style="text-decoration:underline;color:#66d9ef;">MyView</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">default()); </span><span class="lol"></span></code></pre> <p>Feels kind of familiar, right?</p> <p>A friend of mine once told me over beers: <em>&quot;If you go more than one subclass deep, you've done something wrong&quot;</em>. Over the years, I've come around to this point of view, and I feel this approach fits that mantra perfectly - it feels like a subclass, in that you have a place to localize the logic and control setup, implement event handlers, and so on - but you can't go further into subclass hell.</p> <h2 id="the-state-of-things">The State of Things</h2> <p>As I'm writing this, Cacao is <code>0.2.0</code>. It <em>would</em> be <code>0.1.0</code>, but crates.io has a bit of a squatting issue, so <code>0.1.0</code> didn't truly exist.</p> <p>It currently supports enough widgets on macOS to be usable. You can check out the <a href="https://rymc.io/blog/2021/cacao-rs-macos-ios-rust/">examples</a> folder to see more.</p> <p><img src="/img/cacao_calc.png" style="display: block; width: 80%; margin: 16px auto;" /></p> <p>iOS support... it's more of a demo, but there's nothing really blocking controls from being ported other than the time investment.</p> <h2 id="who-s-this-even-for">Who's this even for...?</h2> <p>I don't expect that anybody would say to themselves &quot;alright, time to write/rewrite macOS apps in Rust&quot;. I would expect that Cacao would never be able to be as usable as the blessed layers and frameworks that can be found with Swift and Objective-C, and I don't see the crossover between Apple-ecosystem developers and Rust-developers needing GUI tools to be massive.</p> <p>I do see Cacao being useful for some things, though:</p> <h3 id="rust-based-utilities">Rust-based Utilities</h3> <p>I think it's conceivable that there are Rust-based utilities (scripts, etc) that could be useful with a GUI, but bridging is (frankly) boring work, and frustrating when it goes wrong. Cacao is useful in this scenario as it enables building a native macOS GUI without having to leave the Rust ecosystem.</p> <h3 id="cross-platform-gui-framework">Cross-Platform GUI Framework</h3> <p>My dream for a Rust GUI framework is simple:</p> <ul> <li>Target only the big three (Windows, macOS, Gnome). Anything else can be community supported. <em>Maybe</em> iOS and/or Android, if the effort isn't too signficant.</li> <li>Offering more than the basic controls makes the framework unmaintainable over time. Follow the web model and keep the kitchen sink barebones, even if it's annoying. Long term maintainability &gt; a million widgets.</li> <li>Always expose a native hook on each control for the cases where someone knows what they want. AKA, get out of the way when asked.</li> <li>A declarative (non-JSX-ish) model. Basically, copy SwiftUI.</li> </ul> <p>I think some combination of <a href="https://crates.io/crates/winsafe">winsafe</a>, <a href="https://crates.io/crates/gtk">gtk-rs</a>, and <a href="https://crates.io/crates/cacao">cacao</a> could achieve this. If it happens, I'm happy to repurpose <a href="https://github.com/ryanmcgrath/alchemy">alchemy</a> for this and make it a community thing - it was my original attempt at building a Rust cross-platform GUI, until I was sidelined by life events.</p> <h2 id="on-ios">On iOS</h2> <p>A cool thing about Cacao is that the approach can work on iOS (and, presumably, tvOS) too. While support is currently very alpha in comparison to macOS, you are able to <a href="https://github.com/ryanmcgrath/cacao/tree/trunk/examples/ios-beta">build a proper iOS app and run it</a>:</p> <p><img src="/img/cacao_ios.jpeg" style="display: block; width: 80%; margin: 16px auto;" /></p> <p>This implements the newer <code>UIScene</code> API, meaning you should have a modern app that works well across both iOS and iPad (and in particular supports the iPad multi-app capability).</p> <h2 id="going-forwards">Going Forwards</h2> <p>Part of why I wanted to push out <code>0.2.0</code> is for community usage. I'm curious to see how people like it, whether it sees adoption, and so on. The documentation isn't perfect by any means (I welcome pull requests on this, although I'll continue to work on it as well), but I'm hopeful that feedback drives it forward.</p> <p>In the long (long) term, I think frameworks like this will fade into the background and become building blocks for something like a RustUI (akin to SwiftUI with AppKit/UIKit). This is fine, and I consider it a win if Cacao acts as a bridge between UI generations.</p> What Melee Gave Me Fri, 20 Nov 2020 00:00:00 +0000 https://rymc.io/blog/2020/what-melee-gave-me/ https://rymc.io/blog/2020/what-melee-gave-me/ <p>On or around November 19th, 2020, The Big House - one of the largest and storied tournaments for Super Smash Bros. Melee - received an unfortunate <a href="https://twitter.com/TheBigHouseSSB/status/1329521081577857036" title="The Big House Online: Cease and Desist Notice on Twitter">cease and desist notice</a> from Nintendo of America, Inc.</p> <p>Referenced in the matter is <a href="https://slippi.gg/" title="Slippi Homepage">Slippi</a>, a fan made, and supported, method of playing Melee online utilizing <a href="https://arstechnica.com/gaming/2019/10/explaining-how-fighting-games-use-delay-based-and-rollback-netcode/" title="Rollback vs Delay-based Netcode">rollback</a>, providing an almost console-level experience when it comes to netplay latency. In a year full of unpredictable events, and the worst pandemic of our lifetimes, Slippi has been vital in ensuring the health of the Melee scene <em>without</em> risking the health of individuals.</p> <p>I'm not a lawyer, nor do I know the specifics of the cease and desist that The Big House received, so I can't comment on that. However, I wish Nintendo would reconsider their stance - I've been playing Melee since launch day, and it's meant the world to me, so I think it's worth sharing this.</p> <p>For full disclosure, I've contributed to the Slippi project over the past few months - primarily on the Mac side of things, helping to ensure things work well there. I do it mostly because it feels like giving back to a game and community that gave me what you'll read below. With that said: none of the opinions in this post should be taken as representative of the Slippi team at all, though - mine, and mine alone.</p> <h2 id="melee-and-me">Melee and Me</h2> <p>Super Smash Bros. Melee was released on Dec 3rd, 2001. I got my Gamecube shortly after, and with it, a copy of Melee. While I'd been playing fighting games for as long as I could remember (going all the way back to Street Fighter 2, then moving into the era of Third Strike, Marvel vs Street Fighter/Capcom, and so on - along with the requisite Smash 64 experience), this game immediately felt <em>different</em>. Not necessarily <em>better</em>, but expressive in a way they weren't - from graphics, to sound design, to movement options, to stage design and more, I was enthralled in a way no game had managed before.</p> <p>I come from a rather large family (I'm the oldest of 7...), so we always had enough people to play with. We started, like many, with your standard Free-For-All matches on Hyrule Temple, moving on to trying every item option possible, exhausting the single player modes, and finding every trophy. At a certain point, the game felt complete... but fighting games have a competitive aspect to them, and there's always someone to try and beat.</p> <p>So I found the competitive scene, and dabbled on and off. In the interest of keeping this readable, we're going to skip ahead a bit - just know that Melee was there for some of the most difficult and formative periods of my life.</p> <h2 id="melee-and-jamss">Melee and Jamss</h2> <p>My youngest brother, Jake (or as he was known online, <a href="https://twitter.com/notjamss">@notjamss</a>), first found Melee by chance: walking through the living room while I was watching a tournament, and asking what it was. So we hung out there, and I started to explain the game, from tournament lore, to techniques and more. He was hooked - <a href="https://jamssy.com/rants/#p3">and wrote about it once, if you wanted his take</a>.</p> <p>Our family is from Northern Virginia, so it was perhaps natural that we'd want to attend <a href="https://supersmashcon.com">SmashCon</a>. We first went in 2016; for Jake, it was his first exposure to the competitive scene in real life, and we made plans to attend again. We missed 2017 due to my travel schedule, but we did hit 2018... albeit under some tougher circumstances.</p> <p>You see, in February of 2018, Jake was diagnosed with a combination of Acute Myleoid and Acute Lymphoid Leukemia. This required an immediate, and ultimately long, stay in the hospital. Our Father was (literally) always there with him, but the rest of us siblings would take turns staying with him. His room was almost amusing, considering everything we moved in there to make it better for him - including, most importantly, a working Melee setup.</p> <p>And so play Melee, we did. A ton.</p> <p><img src="/img/jake-melee-hospital-1.jpg" style="width: 60%; display: block; margin: 0 auto;"></p> <h2 id="the-community">The Community</h2> <p>In an attempt to keep his spirits up in the hospital, I decided to see if I could put together some surprises for him. Through a friend of my lovely wife, we got Twitch to send him a care package. He had a fledgling interest in coding, so I sat with him and we put together a basic <a href="https://github.com/ryanmcgrath/memelee" title="Memelee on GitHub">Smash.gg app</a>, which wound up bringing a swag package from them as well. He'd alternate Twitch and Smash.gg hoodies for his walks around the hospital, and always seemed happy to explain them to anyone curious.</p> <p>Most notably, though, was reaching out to Panda Global, who sent him a jersey that he could be found wearing... pretty much every day.</p> <p><img src="/img/jake-pg-jersey.jpg" style="width: 80%; display: block; margin: 0 auto;"></p> <p>Graciously, they also said they'd be happy to meet and play with him if SmashCon 2018 was able to work out. I didn't tell Jake this, but I did tell him that if he made it out of the hospital by SmashCon I'd be happy to take him. His doctors worked tirelessly - and amazingly, he was able to go to outpatient treatment, albeit wearing a mask all the time (before it was popular).</p> <p>The timing was tight, but I flew back, and off to SmashCon we went.</p> <h2 id="smashcon-2018">SmashCon 2018</h2> <p>Much has been written about this tournament elsewhere, and I encourage you to read (or watch!) it if you haven't: pretty much every game had fun sets and great Grand Finals, coupled with SmashCon's generally great setup for chilling and playing.</p> <p><img src="/img/jake-wobbles-2018.jpg" style="width: 30%; float: right; margin: 10px 0 16px 16px; border-radius: 4px;">Jake and I spent our time roaming around, playing various games and browsing the merchandise stalls. Jake got to meet up with some members of the community he'd been friends with for a bit, and the Panda Global crew made him feel like a VIP - MVD and Wobbles (Panda Global players at the time) in particular were great, with MVD somehow finding Jake in the crowd and welcoming him personally. Wobbles welcomed Jake into some friendlies and spent time showing him tech, and signed his new controller to replace the one from 2016.</p> <p>A very friendly Panda Global representative, who I've unfortunately forgotten the name of, eventually found us and asked Jake if he'd like to meet Plup and play some games with him. Jake jumped at the chance, and they played for about an hour or so. If I recall correctly, Plup wasn't feeling the best, but still went out of his way to do this - and it meant a lot to Jake.</p> <p><img src="/img/jake-plup-1.jpg" style="width: 80%; border-radius: 4px; display: block; margin: 0 auto;" /></p> <p>To top it all off, we watched what is one of the greatest Grand Finals set of all time (and the final US singles set before Armada's sudden retirement). Here, Melee gave me one of the best weekends of my life.</p> <p><img src="/img/jake-netplay.jpg" style="width: 32%; border-radius: 4px; float: left; margin-right: 16px; margin-bottom: 100px;" /> We left SmashCon 2018 feeling great, and we'd continue our general tournament watching and netplay sessions. Since I was located in Seattle, this became our way of staying close even when I couldn't hang with him in-person.</p> <p>Eventually, Jake made it to a stable enough place to go for a bone marrow transplant (and thankfully, one other sibling was a good match). He got it, and we waited to see if it worked.</p> <h2 id="saying-goodbye">Saying Goodbye</h2> <p>Unfortunately, science just sucks sometimes. The doctors did everything they could, but it looked as if it just wasn't taking - Jake's cancer was pretty rare, and options became limited to comfort to ride it out. Jake would ultimately pass at home, surrounded by family and without pain. We miss him every day.</p> <p>For me, I've struggled to write about this experience since it happened, and it's the subject of another blog post that I'll eventually finish writing, when the words come. What I wanted to showcase here, though, happened shortly before Jake passed.</p> <p>Before death, some experience what's known as a &quot;surge&quot; or &quot;rally&quot; - a noticeable, but short, return to energy and form. It can be jarring to witness, mostly due to the juxtaposition of watching the decline beforehand - but in a way, it's beautiful too. Jake had a rally, and he looked up, and asked if we wanted to play Melee.</p> <p>We all kind of just looked around, and then said yes. We played a few one on one, and then we swapped out and played group games with the cast of siblings.</p> <p><img src="/img/jake-surge.jpg" style="width: 80%; display: block; border-radius: 4px; margin: 0 auto;" /></p> <p>To this day, they were the most surreal games of Melee I've ever played.</p> <p>Some quick additional context might explain. Fighting games in general (and Melee in particular) are incredibly complex games, and Melee served as a great test for eyeing decline. When we'd play in the hospital, or over netplay, Jake and I would often ask each other if his reactions started to get slower, or if the game became difficult to play - for example, hands not listening, or being unable to process things on screen. Against the odds (and likely thanks to the support of his incredible doctors), he never really declined.</p> <p>Except towards the end of those last games.</p> <p>Jake had never really talked much about the concept of death, other than being proud that any research or efforts on his condition might be helpful to others down the road. I spent a few months struggling to parse these games, until one day I realized that Melee had transcended just being a game for us - it was a language, and that was our goodbye. We were given closure in a package I didn't understand, but to this day I'm thankful for it.</p> <h2 id="on-melee">On Melee</h2> <p>This post leans heavily personal, and I'd forgive anybody for wondering how this pertains to the Big House Online cease and desist.</p> <p>Simply put: I wanted to showcase what Melee gave me. The game is approaching <em>20 years old</em>, and while I understand Nintendo wanting to protect copyright and trademark and so on, there comes a time where, like any child, it ceases to just be a product of your own creation.</p> <p>Melee as an experience is <em>defined</em> by the 20 year rollercoaster of a community it developed. There is no other Smash Bros. game that has reached the level that Melee has, and there is no technological achievement, or cast of characters, or quirky mechanics that can match it - because Nintendo, frankly, did not create the part of Melee that matters now. But they could be a <em>part</em> of it.</p> <p>Nintendo, if you happen to read this: I get that you're the &quot;family friendly&quot; company, and I understand you're a business. But families come in different shapes and sizes, and Melee meant more to mine than you could possibly imagine.</p> <p>I implore you to reconsider your position, and find a way to get okay with streaming tournaments.</p> You Did Not EARN IT Sun, 15 Mar 2020 00:00:00 +0000 https://rymc.io/blog/2020/you-did-not-EARN-IT/ https://rymc.io/blog/2020/you-did-not-EARN-IT/ <p>In the midst of a global pandemic, there's a bill moving through the Senate that has massive ramifications for private companies and platforms utilizing encryption to secure user data. Smarter people than me have written in depth about it, and I urge you to read their take and contact your representatives to take action against this bill.</p> <h2 id="essential-reading">Essential Reading</h2> <ul> <li><a href="https://blog.cryptographyengineering.com/2020/03/06/earn-it-is-an-attack-on-encryption/">EARN IT is a direct attack on end-to-end encryption</a></li> <li><a href="http://cyberlaw.stanford.edu/blog/2020/03/earn-it-act-here-surprise-it%E2%80%99s-still-bad-news">THE EARN IT ACT IS HERE. SURPRISE, IT’S STILL BAD NEWS.</a></li> <li><a href="https://americansforprosperity.org/aclu-and-afp-to-congress-earn-it-act-jeopardizes-every-americans-private-communications/">ACLU and AFP to Congress: “EARN IT” Act Jeopardizes Every Americans’ Private Communications</a></li> <li><a href="https://cdt.org/press/earn-it-act-threatens-encryption-free-expression-and-prosecutions-of-child-exploitation/">EARN IT Act Threatens Encryption, Free Expression, and Prosecutions of Child Exploitation</a></li> </ul> <h2 id="contact-your-representatives">Contact Your Representatives</h2> <p>If you can take action, contact your representatives and ask them to vote against or condemn this bill. The passage of this would cause serious harm to users, ranging from the general web-browsing population to activists and targeted groups (e.g, minorities). I've included a copy of the letter I sent to my representatives earlier this week - feel free to use it as a base when contacting your own. If you're unsure where to look or how to contact your representatives, <a href="https://act.eff.org/action/protect-our-speech-and-security-online-reject-the-graham-blumenthal-bill">check out this EFF tool for getting started</a>.</p> <blockquote> <br /> <p>I'm writing today to ask you to consider voting against the EARN IT act currently making its way through the Senate. While the act is targeting the very serious issue of child exploitation, it does so in an overly broad way that threatens the foundations of the internet. The bill effectively proposes the creation of a government-funded commission that would be tasked with establishing U.S. government dictated best practices for private-sector companies and services in regards to detecting Child Sexual Abuse Material, or &quot;CSAM&quot;.</p> <p>Private companies that would be threatened by this already work to aggressively detect, flag, and address harmful content (CSAM), and are actively engaged in working with the government to further this work. Passage of this bill, however, would ensure that the freedoms the internet has held, which allowed it to grow and innovate on an unprecedented scale, would effectively be tied to Attorney General signoff.</p> <p>The bill (indirectly, but notably) curtails the ability for private companies to design secure products and services. These types of development require strong encryption, both for systems like digital commerce, as well as the safety of users - including children.</p> <p>Allowing this type of legislation to pass under an adminstration that has seen fit to strip away civil liberties is a frightening prospect that must be rejected.</p> </blockquote> Wobbling in Rust Wed, 01 Jan 2020 00:00:00 +0000 https://rymc.io/blog/2020/wobbling-in-rust/ https://rymc.io/blog/2020/wobbling-in-rust/ <p>In 2019, one of my goals was to get back to writing here more regularly. I'm proud to say I was mostly successful in that endeavor: it was certainly an increase from the amount written in 2018, and absolutely a win over 2017 (which saw... no posts). I think personal websites still have value in the modern era, and so I'm going to continue this goal in 2020 and try to write at least a post a month.</p> <p>To start things off, I figured this'd be an amusing topic - equal parts stupid, dangerous, and potentially useful.</p> <h2 id="how-to-never-fail-in-rust">How to never fail in Rust</h2> <p>Let's say that you've got a task you need to run. For whatever reason, a multitude of runtime errors can occur where you need to just keep retrying - for example, an upstream network endpoint is finnicky and drops the connection sometimes, and resuming is not an option. It can panic due to a third party library, too - Rust is still a newer language, even with the insane growth it's seen the past few years, so this isn't too out there concept-wise.</p> <p>Assuming we have a method like the following:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">std</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">error</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Error; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">do_something</span><span style="color:#f8f8f2;">() </span><span style="color:#ff79c6;">-&gt; Result&lt;(), Box&lt;dyn Error&gt;&gt; </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// Code that could potentially Err() or panic </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span style="color:#f8f8f2;">(()) </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span></code></pre> <p>Our goal is simple: how do we just keep retrying this thing, results be damned?</p> <h2 id="unwinding-a-panic">Unwinding a panic</h2> <p>In the standard library, we can find some methods that are useful for this problem. The one we use below is <a href="https://doc.rust-lang.org/std/panic/fn.catch_unwind.html"><code>std::panic::catch_unwind</code></a>, which will catch <em>unwinding</em> panic events. <em>Note that this doesn't cover every type of panic event - as the docs point out, one that aborts the process will bypass this entirely.</em> It returns a <code>Result</code>, where the <code>Ok</code> variant is just the result of a passed in closure, and the <code>Err</code> variant being the cause of the panic.</p> <div class="blog-edit-note"> <h4>Hey, listen! This is a Footgun.</h4> <p>The docs do point out that you should not use this as a general try/catch mechanism. <strong>They're right.</strong></p> <p>My use case for this was a panic happening somewhere in <code><a href="https://crates.io/crates/ssh2" title="SSH2 on crates.io">ssh2</a></code>, where I'd occassionally run into a panic due (seemingly) to it linking against C code. In my particular case, it was easier to just keep trying since it was randomly failing, but would work 100% fine the rest of the time.</p> <p>It's entirely possible this was a bug in an older version of <code>ssh2</code> and would not happen today. Consider this trick a footgun and only contemplate using it if you're truly in a situation that requires it.</p> <p>You've been warned!</p> </div> <div class="blog-edit-note"> <h4>Corrections as of March 19th, 2020</h4> <p>A few weeks after this was posted, it wound up <a href="https://old.reddit.com/r/rust/">/r/rust</a>, where <a href="https://old.reddit.com/r/rust/comments/flaabh/wobbling_in_rust/fkxiaa7/">/u/najamelan and /u/viaxxdev</a> pointed out some issues with the original code in this post. This has been updated to take their points into account, and major thanks to them for catching it!</p> <p>Later in the day, <a href="https://old.reddit.com/r/rust/comments/flaabh/wobbling_in_rust/fkypjdx/">type_N_is_N_to_Never</a> also noted that this could be reduced down - so the code block below now uses that approach, as it's cleaner and arguably easier to digest. Nice!</p> </div> <p>Disclaimers out of the way, we now know how to catch and loop a panic. The code below illustrates a naive way to loop and handle panic events. Notice we mark the passed in closure as requiring <a href="https://doc.rust-lang.org/std/panic/trait.RefUnwindSafe.html"><code>RefUnwindSafe</code></a>.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">std</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">error</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">Error; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">std</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="text-decoration:underline;color:#66d9ef;">panic</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">catch_unwind, RefUnwindSafe</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">/// Given a closure, will attempt to run it and unwind from panics... infinitely </span><span class="lol"></span><span style="color:#6272a4;">/// retrying until it works. </span><span class="lol"></span><span style="color:#6272a4;">/// </span><span class="lol"></span><span style="color:#6272a4;">/// Maybe. </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">wobble</span><span style="color:#f8f8f2;">&lt; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">F: RefUnwindSafe </span><span style="color:#ff79c6;">+</span><span style="color:#f8f8f2;"> Fn() -&gt; Result&lt;(), Box&lt;dyn Error&gt;&gt; </span><span class="lol"></span><span style="color:#f8f8f2;">&gt;(</span><span style="font-style:italic;color:#ffb86c;">process</span><span style="color:#f8f8f2;">: F) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">while </span><span style="font-style:italic;color:#8be9fd;">let </span><span style="font-style:italic;color:#66d9ef;">Err</span><span style="color:#f8f8f2;">(err) </span><span style="color:#ff79c6;">= </span><span style="color:#8be9fd;">catch_unwind</span><span style="color:#f8f8f2;">(|| </span><span style="color:#8be9fd;">process</span><span style="color:#f8f8f2;">()) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">eprintln!(</span><span style="color:#f1fa8c;">&quot;{:?}&quot;</span><span style="color:#f8f8f2;">, err); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span></code></pre> <p>Usage is relatively straightforward. For example:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">do_something</span><span style="color:#f8f8f2;">() </span><span style="color:#ff79c6;">-&gt; Result&lt;(), Box&lt;dyn Error&gt;&gt; </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">panic!(</span><span style="color:#f1fa8c;">&quot;Induce a panic here, for testing&quot;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">main</span><span style="color:#f8f8f2;">() </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#8be9fd;">wobble</span><span style="color:#f8f8f2;">(|| </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#8be9fd;">do_something</span><span style="color:#f8f8f2;">() </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span></code></pre><h2 id="why-s-it-called-wobbling">Why's it called Wobbling?</h2> <p>It's an &quot;infinite&quot; move (or glitch, some might say) in Super Smash Bros Melee for the Nintendo Gamecube. I chose the name because the move is divisive, not something most people enjoy seeing in competitive tournaments, and while valid (depending on the ruleset), might not be the best way to play the game. It kind of symbolically fits here: you probably don't want to use this, and it's arguably not a smart way of doing things... but hey, you might make top 8 once in awhile.</p> <h2 id="great-should-i-use-this">Great! Should I use this?</h2> <p>Probably not.</p> <p>I mean, sure, you can. For a small subset of problems it can be a useful trick. It's probably not what you want to do, though. It almost feels like a lurking <code>unsafe {}</code> without explicitly being so. The code could also be slightly less verbose, for sure, albeit code golf wasn't the point here.</p> <p>With that said, it definitely worked for me. Lately I've come to value my time more highly than I did in the past, and this let me focus on other things while still logging errors to determine what the actual issue was. I'll call it a win.</p> Decoupling, or: where's my data? Sat, 07 Dec 2019 00:00:00 +0000 https://rymc.io/blog/2019/decoupling/ https://rymc.io/blog/2019/decoupling/ <p>Privacy, data rights and ownership are hot topics in 2019. The increased focus on these topics grew over the past few years from a few different things. Snowden's 2013 leaks showed the general public that the data you put out there is not as private as once assumed. An increasing number of database leaks over the past few years have made more people aware that not all data on the internet is necessarily safe and secure. Europe's <em>General Data Protection Regulation (GDPR)</em> woke many companies up to legal liabilities of not taking this stuff seriously, and an increasing number of countries and locales are starting to follow suit (examples include Japan's <em>Act on the Protection of Personal Information (APPI)</em>, becoming more restrictive, and California pushing through with the <em>California Consumer Privacy Act (CCPA)</em>).</p> <p>This is all generally net good for consumers. It pushes forward towards building products that have more respect for users and their rights, and normalizes the conversation in the public eye. However, there's an interesting area of study that I haven't seen as much attention paid to, though - back in the early years of the iPhone and App Store, there was an explosion of apps released that took user data and withered on the vine, to to speak. What happened to the data these apps held and had access to?</p> <p>I started looking at this for an app I had personal experience with: <a href="https://couple.me/">Couple</a>.</p> <h2 id="what-is-or-was-couple">What is (or was) Couple?</h2> <p>Couple (originally branded at launch as &quot;Pair&quot;) was an app that debuted in the 2012 <a href="https://www.ycombinator.com/">Y Combinator</a> batch. The general target market was people in relationships: a supposedly closed off, intimate place to chat, leave media, notes, drawings, and thoughts for each other, along with a shared calendar and a map to send each other your location. It described itself in 2014 with the following text:</p> <p><img src="/img/couple-screen.jpeg" alt="Couple Homepage circa 2014" class="fullwidth-img" /></p> <blockquote> An Intimate Place for Two.<br> Keep all your moments private &amp; make your memories last forever. </blockquote> <p>You might be thinking to yourself: <em>why would anybody use this</em>? Well, remember that this was 2012 - a few years into the App Store, where various social networks and services were still fighting it out. The appeal of having a special app on your homescreen to differentiate your partner from the myriad of other chats and social networks was very enticing. It launched on Android as well, so it found a decent enough market of connecting people across the two platforms (over 4 billion downloads and seemingly billions of messages).</p> <p>Now, back in 2012, I was fortunate enough to have met the woman who'd eventually become my wife. She was studying overseas, and I was on a business trip. We started dating. It became a situation that will sound familiar to many people: <em>long distance</em>, an excruciatingly annoying experience when you're utterly infatuated with someone. An app like Couple seemed like a great idea - a way to put more of an emphasis on the relationship than another chat line in your app of choice.</p> <p>We used it until we were no longer long distance. The app eventually disappeared from the App Store, and while the servers would periodically crash, it seemingly still worked - I know I was able to sync the data to my current phone, and apparently <a href="https://old.reddit.com/r/LongDistance/comments/ai24wr/anyone_else_still_using_the_couple_app_it_stopped/">people</a> <a href="https://old.reddit.com/r/LongDistance/comments/bg5ax1/couple_app_appears_to_be_broken_yet_again/">were</a> <a href="https://old.reddit.com/r/LongDistance/comments/b8t5f2/couple_app_alternatives/">using</a> <a href="https://old.reddit.com/r/LongDistance/comments/buy6g8/has_anyone_else_had_issues_with_the_couple_app/">it</a> <a href="https://old.reddit.com/r/LongDistance/comments/91aqiv/couple_app/">as</a> <a href="https://old.reddit.com/r/LongDistance/comments/bg3z7d/couple_app_not_available_on_ios_in_the_usa/">late</a> <a href="https://old.reddit.com/r/LongDistance/comments/88na7b/couple_app_forgot_password_anyone_have_any_luck/">as</a> <a href="https://old.reddit.com/r/LongDistance/comments/aioeax/anyone_know_about_the_couple_app_coupleme_is_not/">this year</a>.</p> <p><a href="https://old.reddit.com/r/LongDistance/comments/d8g108/couple_app_m29/">Others, like me</a>, wondered about what actually happened to all the data that was on Couple's servers. <em>Note that this thread has a comment mentioning the service had been hacked - I've been unable to verify that this was the case, and as it's one comment on one thread I'm inclined to not believe it until determined otherwise. Take it with a grain of salt</em>.</p> <h2 id="so-what-happened-here">So what happened here?</h2> <p>If we try to trace this, a few things become apparent:</p> <ul> <li>Couple launches in 2012, under four cofounders.</li> <li>Around October 2014, <a href="http://valleywag.gawker.com/former-y-combinator-stars-secretly-slink-off-to-dropbox-1642793803">three of the four cofounders leave to go work at Dropbox</a>.</li> <li>On Feburary 12th, 2016, <a href="https://techcrunch.com/2016/02/12/family-app-life360-acquires-couple-a-private-messaging-app-for-two/">Couple was acquired by Life360</a>, a company focused on providing a &quot;family network&quot; experience. It seems like an acquihire, and after looking around some of the former Couple staff still work there.</li> <li>On June 27th, 2018, <a href="https://twitter.com/life360/status/1012042805328297984">Couple was apparently transfered or sold</a> without much fanfare (seriously, this tweet is all I could find).</li> <li>I contacted Life360 via Twitter on <a href="https://twitter.com/ryanmcgrath/status/1203058772328890368">Dec 6th, 2019</a>. They state, accordingly, that they have no relationship with the app anymore, and refer you to contact <a href="mailto:support@couple.me">support@couple.me</a>.</li> <li>The company that Couple was transfered to is <a href="http://www.buzzfile.com/business/Coupleapp,-Inc.-901-337-1738">Coupleapp, Inc</a>. According to that business filing, the founder is <a href="https://twitter.com/kwylez">Corey Wiles (@kwylez on Twitter)</a>. He's the creator of a an app known as &quot;Significant Other&quot;, that has similiar goals to those of Couple originally.</li> <li>According to <a href="http://www.corywiles.com/ios-apps/">his website</a>, he was a contractor at Life360 for a period.</li> <li>Emails to the Couple support email (<a href="mailto:support@couple.me">support@couple.me</a>) go unanswered. <a href="https://twitter.com/ryanmcgrath/status/1203200143169769472">Poking on Twitter</a> similarly goes unanswered. The website has not been updated since the original owners (Tenthbit, Inc) had posession of it.</li> </ul> <p><img src="/img/life360-couple.png" alt="" class="fullwidth-img" /></p> <p>Now, it's important to state this: <em>the data sent over and stored on Couple's servers was often very personal data</em>. To the best of my knowledge, and having searched quite a bit, there's never been <em>any</em> communication from <em>any</em> owner of Couple regarding what exactly happened to the data. The service <code>503</code>'s now; if you try to delete your account or data from within the app, it simply won't work. Some might consider this to mean it's just gone, but if you've built any service like this, you know very well that there's a real possibility the data is still sitting on some S3 bucket somewhere.</p> <p>Why on earth is it acceptable that Life360 can spin this back out without any notice beyond a quick tweet? It blows my mind that this just happened without much notice or coverage.</p> <h2 id="what-to-do-from-here">What to do from here?</h2> <p>So, here's the thing - I'm absolutely, totally fine with the service shutting down. I'd just like to know what happened to the data that was held by this service. Former users who trusted the service with their data deserve to know if the new owners were given everything; a transfer like that is not something that should be announced over Twitter alone. The type of content or data that's involved here is very personal in nature - personal thoughts, photos, videos and more.</p> <p>Furthermore, in a world of GDPR, CCPA and so on, I'd love to know: <em>where does something like this fall</em>? Considering the download numbers that this app did, and given that it was available worldwide... I have to think that it absolutely falls within the realm of GDPR. CCPA comes into enforcement on January 1st, 2020 - around 3 weeks from time of writing this. When the new entity doesn't respond, what's next? Kick this up the chain to state or national agencies?</p> <p>If someone involved in the app could publicly clarify what exactly happened here, it would do every former user a world of good.</p> <p>Ultimately, it's worth remembering that the focus on privacy and user rights that we have today weren't always at the forefront years ago. Many services existed then that might still contain your digital footprint, and it's good to review and take stock of these. You don't always know where your data wound up.</p> Automatic Old Reddit Redirect Safari Extension Thu, 01 Aug 2019 00:00:00 +0000 https://rymc.io/blog/2019/old-reddit-safari-redirect-extension/ https://rymc.io/blog/2019/old-reddit-safari-redirect-extension/ <p>Miss the old Reddit browsing experience? You're not alone. While I've no doubt that they'll eventually produce something really smooth and cool (they're too good of an engineering team not to), in the meantime I've found myself preferring to use the old Reddit design. It's sleeker, faster to load, and easier to skim large amounts of posts with.</p> <p>You can set your preference in your profile to use the old Reddit design, but I've noticed that it doesn't automatically push you to it - you'll still wind up waiting for a behemoth of a UI to load if you click a random Reddit link <em>while not on <code>old.reddit.com</code> already</em>. Chrome and Firefox have convenient extensions to force-rewrite the URL, but Safari is lacking... maybe due to the APIs being different, or maybe due to the up-front cost for the developer program.</p> <p>At any rate, I wanted that extension in Safari, so I ported it over. <a href="https://apps.apple.com/us/app/oldr-for-reddit/id1475048161?ls=1&amp;mt=12">I call it &quot;Oldr for Reddit&quot;, and you can download it here.</a> Once it's installed, check your <code>Safari preferences</code> -&gt; <code>Extensions</code> list and enable it, and it should &quot;just work&quot; from there.</p> <h2 id="privacy-policy-and-terms-of-service">Privacy Policy and Terms of Service</h2> <p>The software is released as-is, and by using it, you agree that you understand this. I don't record ay data, and have no analytics beyond anything Apple gives me in terms of download numbers. If you find anywhere that I've errored in this, get in touch.</p> <p>Questions or comments? Feel free to email me or catch me on Twitter.</p> On Sign-In with Apple Wed, 05 Jun 2019 00:00:00 +0000 https://rymc.io/blog/2019/on-sign-in-with-apple/ https://rymc.io/blog/2019/on-sign-in-with-apple/ <p>At WWDC this year, Apple announced a new feature for user registration with applications. Called simply <a href="https://developer.apple.com/sign-in-with-apple/">&quot;Sign In with Apple&quot;</a>, it's effectively an OAuth2 authentication ritual similar to what Facebook, Google, Twitter (and more) currently offer. The catch is that Apple wants to do it in a way that protects your privacy, by acting as an in-between agent to avoid your email address being used for spam, data targeting or profiling, and more. It's really significant, since a company the size of Apple pushing this could enable it to take off in a way that just wouldn't happen elsewhere.</p> <p>However, I've noticed more than a few people throwing comments around the web to the tune of:</p> <ul> <li>&quot;I already do this by providing a fake email, like <code>myemail+lol@gmail.com</code>, to everyone!&quot;</li> <li>&quot;I'm protected already because I run my own domain with a catch-all email, like <code>abc@mydomain.com</code>!&quot;</li> </ul> <p>These imply that what Apple is doing is easily replicated, which is somewhat far from the case. Let's examine why.</p> <h2 id="the-special-email">The Special Email</h2> <p>You sign up to a provider for some service that you just want to scope out, and you're worried about the safety of it. You use Gmail, so you decide &quot;let's just give it a special one that I can block later, like <code>myemail+lol@gmail.com</code>&quot;. Later on the service database leaks. You're safe, right?</p> <p>Not so much. Removing the special character bits from Gmail is not inherently difficult, so matching your email across database dumps becomes relatively straightforward for any cleaning script worth its salt. You can block all the emails coming from that special email, but nothing is stopping you from getting them... and nothing is stopping the provider from emailing your real one, which as noted, there's a good chance they've got now. Linking your data sets together for profiling is also relatively easy at this point.</p> <p>How's this change with Apple's approach? When you sign in with Apple, you get assigned a special email address that acts as a relay to your true email. Reading the documentation further, this becomes even more useful than it sounds - to send email via that relay, it has to come from a domain that the developer explicitly proves they control. Anything else is outright ignored. This means that, should database leaks occur, you have a per-app-unique email that can't be tied across leaks, and can't be arbitrarily emailed or spammed. It's providing privacy in a way that your home-grown special email case simply can't.</p> <h2 id="the-catch-all-email">The Catch-All Email</h2> <p>So you're savvy enough to run your own domain-based email, and you decide &quot;alright, let's just use a <code>catch-all@mydomain.com</code> email and weed out the bad actors this way&quot;. This works slightly better than the special email case above, but at the end of the day, I'm going to be honest with you: I've seen more than my fair share of data cleaning scripts, and if your domain isn't a known email provider, you're just going to be attributed as the same user across leaks. You're not big enough to matter to a bad actor, and it's easy enough to lump anything matching your domain together. You're still going to be profiled if you go this route.</p> <h2 id="big-corp">Big-Corp?</h2> <p>The other thing I keep seeing come up is paranoia around big corporations being the one to vend out these solutions. This is also sometimes phrased as &quot;privacy shouldn't come at a price&quot;. This is correct, it shouldn't... but Apple should be applauded for this move, because your home-grown solution isn't actually doing anything to solve the bigger issue. Apple didn't invent email relays, privacy tools, or what have you, but they're able to push them at a scale that forces the tech industry to change for the better.</p> <p>We will not move past this era of user-targeting unless there's a big entity that steps in, be it government regulation (seemingly, currently, unlikely) or a large corporation with enough muscle to make it happen (in this case, Apple). Dislike them for whatever reason you want, but they really do deserve credit for being willing to stand up and push this issue.</p> One Last Ride Wed, 15 May 2019 00:00:00 +0000 https://rymc.io/blog/2019/one-last-ride/ https://rymc.io/blog/2019/one-last-ride/ <p>When did redesigning a personal website become such hell? I've had this site running for almost two decades now, and while it's been a point of (perhaps misplaced) pride, I try to keep it looking decent and up to date. The modern landscape is crazy, though - a million-ish screen sizes and densities, different appearance APIs depending on whether you're running a dark mode or not, and much higher standards for what &quot;looks good&quot;.</p> <p>I think I settled on something I'm happy with for the next few years, and with any luck, the remainder of my time on these here internets.</p> <h2 id="rymc-4-0">RYMC 4.0</h2> <p>The previous design for this site was made towards the tail-end of my time living in Tokyo. I realized one day that I'd kept the design from... 2008... up for almost ten years. We're talking pre-iPhone here. I redesigned it to effectively look like a Medium blog with some elements of personality, and was happy with it for awhile: it did the job and stayed out of my way.</p> <p>Then earlier this year, a family member was unfortunately diagnosed with cancer. Sitting in a hospital room with someone you care about while they battle something like that isn't easy - it makes you think a lot about how you live your own life. I remember pulling up my site one day and thinking to myself: <em>&quot;wow, this is incredibly devoid of personality. Is this mine?&quot;</em></p> <p>Since I had time to pass while sitting there, I just kind of started throwing stuff at the screen and seeing what I liked.</p> <ul> <li>I wanted something with dynamic-ish shapes. A growing current trend in (web-based) design is softer shapes and more variation in element positioning.</li> <li>I wanted to keep it loading insanely fast. I personally like a CSS-Zen-Garden approach (I'm dating myself here) to see what all can be done without needing a litany of network connections.</li> <li>I wanted to spruce up the old content, for better SEO and reading comfort.</li> <li>I wanted to support dark mode, for browsers that support the new <code>@media</code> query for appearance preferences.</li> <li>I wanted a smooth code reading experience, for the more technical articles.</li> <li>I wanted personality in here somewhere.</li> </ul> <h2 id="activity-feed-s">Activity Feed(s)</h2> <p>That last point dovetails into the other big thing I wanted. The web I grew up on was people owning their content, and we lost that somewhere along the way. It's a difficult thing to walk back, too, but I figure I can at least have it for myself - hence why this site now automatically grabs my tweets, GitHub activity, and Dribbble work and displays it throughout as you browse. In keeping with ensuring this thing loads like demonically fast, the data is scraped and repackaged into what's effectly a static site every few minutes.</p> <p>If you're interested, I open sourced the code I used for this part <a href="https://github.com/ryanmcgrath/activity-scraper/">here</a>. Feel free to use!</p> <h2 id="moving-forward">Moving Forward</h2> <p>I'm hoping to continue posting content here, since it's nice to have your own space. 2019 has been a pretty good clip for this! Also, never let me do this again.</p> Vim, ALE, Docker, and Per-Project Linting Thu, 07 Mar 2019 00:00:00 +0000 https://rymc.io/blog/2019/vim-ale-docker-per-project-linting/ https://rymc.io/blog/2019/vim-ale-docker-per-project-linting/ <p>I've been using Vim for a little over ten years now. Up until Vim 8, I'd go so far as to say little changed for me... but Vim 8 actually changed the game in a pretty big way. The introduction of asynchronous jobs that can run in the background enables functionality like code linting and completion that don't block the editor, a rather stark contrast to the old days. In fact, I outright didn't bother with code completion and the like prior to Vim 8 - it was never fast enough and just left me too annoyed to care.</p> <p>Anyway, thanks to this new functionality, we can use projects like <a href="https://github.com/w0rp/ale">ALE</a> to provide smooth linting, autocompletion, fixing, and so on. Setting up this and other Vim plugins is a bit outside the scope of this post, but I'd highly recommend it if you haven't tried it. ALE includes support for <a href="https://en.wikipedia.org/wiki/Language_Server_Protocol">LSP (language-server-protocol)</a>, originally developed by Microsoft and now supported by a litany of editors and IDEs. This support has made Vim feel like far less of a black-box at points!</p> <h2 id="ale-and-docker">ALE and Docker</h2> <p>One thing I ran into when setting ALE up was that various projects have their own rules. For example, a Japanese company I help has some rather peculiar style rules for their Python codebase. I recently converted their infrastructure to a Docker-based architecture, and as a result all Python code executes inside a virtual machine (at least, insofar as MacOS/Windows are concerned - Linux users might have a slightly easier time here!).</p> <p>In most cases, this is not too big of a problem - however, this particular case means that tools like flake8 are running inside the VM, and not in the userspace where you'd be running Vim. In the issues I glanced over, the author of ALE recommends just running Vim over SSH into the VM, which can be an alright solution... albeit a bit clunky, given your setup for Vim runs on your local machine. We really just need a way to communicate between the two layers, right?</p> <p>This is actually possible with just a bit of extra configuration work. We'll need two things before we can make it work, though:</p> <ul> <li>If you haven't already, I recommend setting up your Vim installation so that it supports some kind of local .vimrc setup. I use <a href="https://github.com/embear/vim-localvimrc">embear/vim-localvimrc</a> and whitelist the projects I know are safe, but you do you.</li> <li>A custom shell script to act as the bridge between Docker and the host environment.</li> </ul> <h2 id="the-shell-script">The Shell Script</h2> <p>This is much simpler than you'd think! Somewhere in your project, edit and throw the following:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;">#!/bin/bash </span><span class="lol"></span><span style="color:#50fa7b;">docker-compose</span><span style="color:#f8f8f2;"> exec</span><span style="font-style:italic;color:#ffb86c;"> -T </span><span style="color:#f8f8f2;">{{ Your docker env name here }} flake8 $</span><span style="color:#bd93f9;">@ </span><span class="lol"></span></code></pre> <p>This is inspired by <a href="https://qiita.com/acro5piano/items/dfc86c89be28f444a6d9">acro5piano's post over on qiita</a>, but fixed up slightly to work with what I presume are recent changes in Docker and/or ALE. Notably, our command has to specify <code>-T</code> to stop Docker from allocating a pseudo TTY. Save this and mark it as executable, and ensure your Docker environment is running if you want ALE to report errors.</p> <p><em>(I also figured I'd throw this post up in English, just so the knowledge is a bit more freely available)</em></p> <h2 id="local-lvimrc-configuration">Local .lvimrc Configuration</h2> <p>With the shell script in place, we just need to instruct ALE on how to call flake8. If you're using <code>vim-localvimrc</code>, you can throw a <code>.lvimrc</code> in your project root with the following:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#8be9fd;">let </span><span style="color:#ffffff;">g:ale_python_flake8_executable</span><span style="color:#f8f8f2;"> = </span><span style="color:#f1fa8c;">&#39;/path/to/flake8/shell/script&#39; </span><span class="lol"></span></code></pre> <p>Provided you did all the above correct, flake8 should now be properly reporting to ALE. You'd need to do this setup per-project, but to be honest I don't find it that annoying, as I find Docker is worth it over the old-school Virtualenv solutions. If you know of a better way to do this, I'm all ears!</p> Rust, Cargo, lcrypto/OpenSSL, Mac, and You Mon, 18 Feb 2019 00:00:00 +0000 https://rymc.io/blog/2019/rust-cargo-lcrypto-openssl-mac-and-you/ https://rymc.io/blog/2019/rust-cargo-lcrypto-openssl-mac-and-you/ <p>If you're trying to compile code on recent versions of macOS, and it tries to link to OpenSSL, you may find yourself driven a bit mad by how odd it all is. The long and short of it is that Apple, in a recent-ish release, removed the headers for their version of OpenSSL, and you need to install a modern version of OpenSSL via <a href="https://brew.sh">Homebrew</a>. This is really straightforward... but won't, in some cases, automatically make your project compile.</p> <p>This was the case for a project I was working on, which happened to be written in Rust. The resulting errors spewed from Cargo were, after parsing, pretty clear: it was trying and failing to find and link to an OpenSSL installation. This can be confusing to diagnose and fix, since over the years Rust moved pretty quickly, and there's a litany of strange GitHub issue threads devoted to the issue. It crosses over with some macOS issues, and... yeah.</p> <p>Thus, I'm going to dump my <code>.bashrc</code> fixes here. Throwing the following in your bash profile, then running a cargo clean + build should get your project compiling.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">export </span><span style="color:#ffffff;">OPENSSL_ROOT_DIR</span><span style="color:#ff79c6;">=</span><span style="color:#f1fa8c;">$(</span><span style="color:#50fa7b;">brew</span><span style="font-style:italic;color:#ffb86c;"> --prefix</span><span style="color:#f1fa8c;"> openssl) </span><span class="lol"></span><span style="color:#ff79c6;">export </span><span style="color:#ffffff;">OPENSSL_LIB_DIR</span><span style="color:#ff79c6;">=</span><span style="color:#f1fa8c;">&quot;$</span><span style="color:#ffffff;">OPENSSL_ROOT_DIR</span><span style="color:#f1fa8c;">/lib&quot; </span><span class="lol"></span><span style="color:#ff79c6;">export </span><span style="color:#ffffff;">OPENSSL_INCLUDE_DIR</span><span style="color:#ff79c6;">=</span><span style="color:#f1fa8c;">&quot;$</span><span style="color:#ffffff;">OPENSSL_ROOT_DIR</span><span style="color:#f1fa8c;">/include&quot; </span><span class="lol"></span><span style="color:#ff79c6;">export </span><span style="color:#ffffff;">LDFLAGS</span><span style="color:#ff79c6;">=</span><span style="color:#f1fa8c;">&quot;-L$</span><span style="color:#ffffff;">OPENSSL_ROOT_DIR</span><span style="color:#f1fa8c;">/lib&quot; </span><span class="lol"></span><span style="color:#ff79c6;">export </span><span style="color:#ffffff;">CPPFLAGS</span><span style="color:#ff79c6;">=</span><span style="color:#f1fa8c;">&quot;-I$</span><span style="color:#ffffff;">OPENSSL_ROOT_DIR</span><span style="color:#f1fa8c;">/include&quot; </span><span class="lol"></span><span style="color:#ff79c6;">export </span><span style="color:#ffffff;">PKG_CONFIG_PATH</span><span style="color:#ff79c6;">=</span><span style="color:#f1fa8c;">&quot;$</span><span style="color:#ffffff;">OPENSSL_ROOT_DIR</span><span style="color:#f1fa8c;">/lib/pkgconfig&quot; </span><span class="lol"></span><span style="color:#ff79c6;">export </span><span style="color:#ffffff;">LIBRARY_PATH</span><span style="color:#ff79c6;">=</span><span style="color:#f1fa8c;">&quot;$</span><span style="color:#ffffff;">LIBRARY_PATH</span><span style="color:#f1fa8c;">:$</span><span style="color:#ffffff;">OPENSSL_ROOT_DIR</span><span style="color:#f1fa8c;">/lib/&quot; </span><span class="lol"></span></code></pre> <p>Exporting these flags ensures that Rust, Cargo, LLVM and crew correctly grok where to find OpenSSL to link against. Hopefully this helps someone else out there, since this can be annoying to diagnose! Some tweaking may be needed depending on how you have your system configured.</p> Dynamic Images in iOS Push Notification Extensions Thu, 31 Jan 2019 00:00:00 +0000 https://rymc.io/blog/2019/dynamic-images-in-ios-push-notification-extensions/ https://rymc.io/blog/2019/dynamic-images-in-ios-push-notification-extensions/ <p>Some time ago, I read an interesting article from The Guardian about their work with a concept they call <a href="https://awards.journalists.org/entries/live-notifications/">&quot;Live Notifications&quot;</a>. The general idea is being able to do more with push notifications, turning them into a rich experience with dynamically generated or updated assets. I experimented with this on my own when I wanted a simple way of charting some personal data; I have a server that periodically checks a source, and notifies based on updated findings (yes, this is as generic a description as it can get - it's personal). Rather than generating and storing images on a server that'd only be needed once, couldn't I just dynamically generate them client side?</p> <p>Turns out, it's not super complicated in the grand scheme of things. Using an <a href="https://developer.apple.com/documentation/usernotificationsui/unnotificationcontentextension">iOS Notification Content Extension</a> or <a href="https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension">iOS Notification Service Extension</a>, it's possible to have a lightweight process running that can act dynamically on received push notifications. This is the key - we'll send a lightweight payload via Apple's Push Notification Service (APNS), and then build and attach an image to the notification before it displays.</p> <h2 id="limitations">Limitations</h2> <p>There are, surprisingly, not too many limitations... but there's one or two to know about.</p> <ul> <li>Usage of portions of <code>UIKit</code> is pretty much impossible - for instance, <code>UIApplication</code> is out, but you can use <code>CoreGraphics</code> et al as necessary.</li> <li>The memory limitations are much smaller and the system is more aggressive in killing your extension if you're not careful, so it's best to keep this efficient. I'd highly recommend sending a default notification with usable title and text, and then customize it as necessary when you do the image.</li> <li>If you want to access <code>NSUserDefaults</code>, you'll need to ensure you're using an App Group to communicate between processes properly, as the extension lives separately from your app.</li> <li>Oh, and if you use <a href="https://realm.io/">Realm</a>, it's a little tricky to read data in extensions (as of writing this, I don't believe it works properly). I've only used this in situations with <code>NSUserDefaults</code>, <code>Core Data</code>, or <code>SQLite</code>. I'm sure there's a method for Realm, but you're on your own for that.</li> </ul> <h2 id="building-the-extension">Building the Extension</h2> <p>For this example, we'll assume you have an iOS app that's properly configured for push notifications. If you're unsure of how to do this, there's enough guides around the internet to walk you through this, so run through one of those first. The example below also makes use of the excellent <a href="https://github.com/danielgindi/Charts">Charts library by Daniel Gindi</a>, so grab that if you need it.</p> <p>We'll start with a standard iOS Service Extension, and wire it up to attempt producing an image in the <code>didReceive(...)</code> method. We'll implement three methods, and support throwing up the chain to make things easier - it's less ideal if an extension crashes, because getting it restarted is... unlikely. We'll simply recover &quot;gracefully&quot; from any error, but due to this it's also worth getting right in testing.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">import </span><span style="font-style:italic;color:#66d9ef;">UIKit </span><span class="lol"></span><span style="color:#ff79c6;">import </span><span style="font-style:italic;color:#66d9ef;">UserNotifications </span><span class="lol"></span><span style="color:#ff79c6;">import </span><span style="font-style:italic;color:#66d9ef;">Charts </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">class </span><span style="font-style:italic;color:#66d9ef;">NotificationService</span><span style="color:#ff79c6;">: </span><span style="font-style:italic;color:#66d9ef;">UNNotificationServiceExtension</span><span style="color:#f8f8f2;"> { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">var </span><span style="font-style:italic;color:#ffb86c;">contentHandler</span><span style="color:#f8f8f2;">: ((</span><span style="font-style:italic;color:#66d9ef;">UNNotificationContent</span><span style="color:#f8f8f2;">) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Void</span><span style="color:#f8f8f2;">)</span><span style="color:#ff79c6;">? </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">var </span><span style="font-style:italic;color:#ffb86c;">bestAttemptContent</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">UNMutableNotificationContent</span><span style="color:#ff79c6;">? </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">override </span><span style="font-style:italic;color:#8be9fd;">func </span><span style="color:#50fa7b;">didReceive</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">_ </span><span style="font-style:italic;color:#ffb86c;">request</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">UNNotificationRequest</span><span style="color:#f8f8f2;">, withContentHandler </span><span style="font-style:italic;color:#ffb86c;">contentHandler</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#8be9fd;">@escaping</span><span style="color:#f8f8f2;"> (</span><span style="font-style:italic;color:#66d9ef;">UNNotificationContent</span><span style="color:#f8f8f2;">) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Void</span><span style="color:#f8f8f2;">) { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">self.</span><span style="color:#f8f8f2;">contentHandler = contentHandler </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">bestAttemptContent = (request</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">content</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">mutableCopy() </span><span style="color:#ff79c6;">as? </span><span style="font-style:italic;color:#66d9ef;">UNMutableNotificationContent</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if let</span><span style="color:#f8f8f2;"> bestAttemptContent = bestAttemptContent { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">bestAttemptContent</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">title = </span><span style="color:#f1fa8c;">&quot;\(</span><span style="color:#f8f8f2;">bestAttemptContent</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">title</span><span style="color:#f1fa8c;">) [modified]&quot; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">do</span><span style="color:#f8f8f2;"> { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">buildChartAttachment(request) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span style="color:#ff79c6;">catch</span><span style="color:#f8f8f2;"> { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// Assuming you sent a &quot;good enough&quot; notification by default, this should be </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// safe. We can log here to see what&#39;s wrong, though... </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">print(</span><span style="color:#f1fa8c;">&quot;Unexpected error building attachment! \(</span><span style="color:#f8f8f2;">error</span><span style="color:#f1fa8c;">).&quot;</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">contentHandler(bestAttemptContent) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">override </span><span style="font-style:italic;color:#8be9fd;">func </span><span style="color:#50fa7b;">serviceExtensionTimeWillExpire</span><span style="color:#f8f8f2;">() { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if let</span><span style="color:#f8f8f2;"> contentHandler = contentHandler, </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> bestAttemptContent = bestAttemptContent { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">contentHandler(bestAttemptContent) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// The three main methods we&#39;ll implement in a moment </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">func </span><span style="color:#50fa7b;">renderChartImage</span><span style="color:#f8f8f2;">() -&gt; </span><span style="font-style:italic;color:#66d9ef;">UIImage</span><span style="color:#ff79c6;">?</span><span style="color:#f8f8f2;"> {} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">func </span><span style="color:#50fa7b;">storeChartImage</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">_ </span><span style="font-style:italic;color:#ffb86c;">image</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">UIImage</span><span style="color:#ff79c6;">?</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">throws</span><span style="color:#f8f8f2;"> -&gt; </span><span style="font-style:italic;color:#66d9ef;">URL</span><span style="color:#f8f8f2;"> {} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">func </span><span style="color:#50fa7b;">buildChartAttachment</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">_ </span><span style="font-style:italic;color:#ffb86c;">request</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">UNNotificationRequest</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">throws</span><span style="color:#f8f8f2;"> {} </span><span class="lol"></span><span style="color:#f8f8f2;">} </span><span class="lol"></span></code></pre><h2 id="rendering-the-chart">Rendering the Chart</h2> <p>For the sake of example, we'll make a very basic <code>LineChart</code> using bogus data. In a real world scenario, you'd want your data to fit into the space of a push notification (2kb - 4kb, which is actually a good amount of space). You could also use a different type of chart, if you wanted. The use cases here are pretty cool - imagine if RobinHood allowed you to, say, see a chart at a glance of how your portfolio is doing. Depending on the performance, that chart could change color or appearance to convey more information at a glance.</p> <p>Granted, you might not want that much information being on a push notification. Maybe you have prying eyes around you, or something - privacy is probably good to consider if you're reading this and looking to implement it as a feature. The chart below has some settings pre-tuned for a &quot;nice enough&quot; display, but you can tinker with it to your liking.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">func </span><span style="color:#50fa7b;">renderChartImage</span><span style="color:#f8f8f2;">() -&gt; </span><span style="font-style:italic;color:#66d9ef;">UIImage</span><span style="color:#ff79c6;">?</span><span style="color:#f8f8f2;"> { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> chartView = </span><span style="font-style:italic;color:#66d9ef;">LineChartView</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">frame</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">CGRect</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">x</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">y</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">width</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">320</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">height</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">320</span><span style="color:#f8f8f2;">)) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">chartView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">minOffset = </span><span style="color:#bd93f9;">0 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">chartView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">chartDescription</span><span style="color:#ff79c6;">?.</span><span style="color:#f8f8f2;">enabled = </span><span style="color:#bd93f9;">false </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">chartView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">rightAxis</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">enabled = </span><span style="color:#bd93f9;">false </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">chartView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">leftAxes</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">enabled = </span><span style="color:#bd93f9;">false </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">chartView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">xAxis</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">drawLabelsEnabled = </span><span style="color:#bd93f9;">false </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">chartView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">xAxis</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">drawAxisLineEnabled = </span><span style="color:#bd93f9;">false </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">chartView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">xAxis</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">drawGridLinesEnabled = </span><span style="color:#bd93f9;">false </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">chartView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">legend</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">enabled = </span><span style="color:#bd93f9;">false </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">chartView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">drawGridBackgroundEnabled = </span><span style="color:#bd93f9;">true </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">chartView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">drawBordersEnabled = </span><span style="color:#bd93f9;">false </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">chartView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">setScaleEnabled(</span><span style="color:#bd93f9;">false</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">chartView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">contentScaleFactor = </span><span style="color:#bd93f9;">2 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">chartView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">backgroundColor = </span><span style="font-style:italic;color:#66d9ef;">UIColor</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">black </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">chartView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">gridBackgroundColor = </span><span style="font-style:italic;color:#66d9ef;">UIColor</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">green </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> dataSet = </span><span style="font-style:italic;color:#66d9ef;">LineChartDataSet</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">values</span><span style="color:#f8f8f2;">: [ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">ChartDataEntry</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">x</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">1</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">y</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">2</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">ChartDataEntry</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">x</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">2</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">y</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">5</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">ChartDataEntry</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">x</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">3</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">y</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">7</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">ChartDataEntry</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">x</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">4</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">y</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">12</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">ChartDataEntry</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">x</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">5</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">y</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">18</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">ChartDataEntry</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">x</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">6</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">y</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">7</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">ChartDataEntry</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">x</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">7</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">y</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">1</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">], </span><span style="font-style:italic;color:#ffb86c;">label</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&quot;&quot;</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">dataSet</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">lineWidth = </span><span style="color:#bd93f9;">4 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">dataSet</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">drawCirclesEnabled = </span><span style="color:#bd93f9;">false </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">dataSet</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">drawFilledEnabled = </span><span style="color:#bd93f9;">true </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">dataSet</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">setColor(</span><span style="font-style:italic;color:#66d9ef;">UIColor</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">green) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">dataSet</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">fillColor = </span><span style="font-style:italic;color:#66d9ef;">UIColor</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">green </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> data = </span><span style="font-style:italic;color:#66d9ef;">LineChartData</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">dataSets</span><span style="color:#f8f8f2;">: [dataSet]) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">data</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">setDrawValues(</span><span style="color:#bd93f9;">false</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">chartView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">data = data </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return</span><span style="color:#f8f8f2;"> chartView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">getChartImage(</span><span style="font-style:italic;color:#ffb86c;">transparent</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">false</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;">} </span><span class="lol"></span></code></pre> <p>Note that the size of the chart is hard-coded, and that the scale is manually set. Both are critical for pixel-perfect rendering; the logic could certainly be better (e.g, larger phones really need the scale to be 3), but the general idea is showcased here.</p> <h2 id="storing-the-image">Storing the Image</h2> <p>We now need to attach the image to the notification. We do this using a <code>UNNotificationAttachment</code>, which... requires a <code>URL</code>. Thus, we'll be writing this to the filesystem temporarily. This method attempts to create a temporary directory and write the PNG data from the chart image returned in our prior method.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">func </span><span style="color:#50fa7b;">storeChartImage</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">_ </span><span style="font-style:italic;color:#ffb86c;">image</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">UIImage</span><span style="color:#ff79c6;">?</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">throws</span><span style="color:#f8f8f2;"> -&gt; </span><span style="font-style:italic;color:#66d9ef;">URL</span><span style="color:#f8f8f2;"> { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> directory = </span><span style="font-style:italic;color:#66d9ef;">URL</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">fileURLWithPath</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">NSTemporaryDirectory</span><span style="color:#f8f8f2;">(), </span><span style="font-style:italic;color:#ffb86c;">isDirectory</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">true</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">try </span><span style="font-style:italic;color:#66d9ef;">FileManager</span><span style="color:#ff79c6;">.default.</span><span style="color:#f8f8f2;">createDirectory(</span><span style="font-style:italic;color:#ffb86c;">at</span><span style="color:#f8f8f2;">: directory, </span><span style="font-style:italic;color:#ffb86c;">withIntermediateDirectories</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">true</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">attributes</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">nil</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> url = directory</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">appendingPathComponent(</span><span style="color:#f1fa8c;">&quot;tmp.png&quot;</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">try</span><span style="color:#f8f8f2;"> image</span><span style="color:#ff79c6;">?.</span><span style="color:#f8f8f2;">pngData()</span><span style="color:#ff79c6;">?.</span><span style="color:#f8f8f2;">write(</span><span style="font-style:italic;color:#ffb86c;">to</span><span style="color:#f8f8f2;">: url, </span><span style="font-style:italic;color:#ffb86c;">options</span><span style="color:#f8f8f2;">: .</span><span style="color:#bd93f9;">atomic</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return</span><span style="color:#f8f8f2;"> url </span><span class="lol"></span><span style="color:#f8f8f2;">} </span><span class="lol"></span></code></pre> <p>Note that, in my testing, simply writing to the same URL over and over again didn't impact multiple notifications - i.e, you're not overwriting an old image that might be on the screen. I've no idea if this will change in later iOS revisions, though, so keep it in the back of your mind!</p> <h2 id="putting-it-all-together">Putting it all together</h2> <p>With the image saved and ready, we can attach it to the notification and let the system display it to the user.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">func </span><span style="color:#50fa7b;">buildChartAttachment</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">_ </span><span style="font-style:italic;color:#ffb86c;">request</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">UNNotificationRequest</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">throws</span><span style="color:#f8f8f2;"> { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> chartImage = renderChartImage() </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> url = </span><span style="color:#ff79c6;">try</span><span style="color:#f8f8f2;"> storeChartImage(chartImage) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> attachment = </span><span style="color:#ff79c6;">try </span><span style="font-style:italic;color:#66d9ef;">UNNotificationAttachment</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">identifier</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&quot;&quot;</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">url</span><span style="color:#f8f8f2;">: url, </span><span style="font-style:italic;color:#ffb86c;">options</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">nil</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">bestAttemptContent</span><span style="color:#ff79c6;">?.</span><span style="color:#f8f8f2;">attachments = [attachment] </span><span class="lol"></span><span style="color:#f8f8f2;">} </span><span class="lol"></span></code></pre> <p>And voila, you now have a dynamically generated chart. No need to worry about rendering images server side, storing and caching them, or anything like that!</p> <div style="text-align: center; margin: 10px auto; max-width: 400px; color: #777;"> <img src="/img/notification-chart.png" width="300" /> <p style="font-size: 14px;">Your chart hopefully looks better than this demo image I found laying around from my test runs. :)</p> </div> <h2 id="surely-there-must-be-a-catch">...surely there must be a catch...</h2> <p>Yeah, there's a few things to consider here.</p> <ul> <li>You're technically pushing the processing requirements to the user's device, but in my testing, this didn't cause significant battery drain over time. If you opt to do this, consider the time interval that you're pushing notifications on.</li> <li>As mentioned before, you should design notifications such that they're sent &quot;good enough&quot;, in case a notification extension crashes or is killed by the OS for whatever reason. This means ensuring a good default title, body, and so on are sent.</li> <li>If you use this for financial data, which would not surprise me as the chief interest here, you should consider making this feature &quot;opt-in&quot; rather than &quot;opt-out&quot;. Charts can convey a lot more than text at a glance, and people might not want their information being blown out like that.</li> </ul> <p>But with that all said, it's a pretty cool trick! Due credit goes to The Guardian for inspiring me to look into this. If you find issues with the code samples above, feel free to ping me over email or Twitter and let me know!</p> Using a Custom JSONEncoder for Pandas and Numpy Wed, 02 Jan 2019 00:00:00 +0000 https://rymc.io/blog/2019/using-a-custom-jsonencoder-for-pandas-and-numpy/ https://rymc.io/blog/2019/using-a-custom-jsonencoder-for-pandas-and-numpy/ <p>Recently, I had a friend ask me to glance at some data science work he was doing. He was puzzled why his output, upon attempting to send it to a remote server for processing, was crashing the entire thing. The project was using a pretty standard toolset - Pandas, Numpy, and so on. After looking at it for a minute, I realized he was running into a JSON encoding issue regarding certain data types in Pandas and Numpy.</p> <p>The fix is relatively straightforward, if you know what you're looking for. I didn't see too much concrete info floating around after a cursory search, so I figured I'd throw it here in case some other wayward traveler needs it.</p> <h2 id="creating-and-using-a-custom-jsonencoder">Creating and Using a Custom JSONEncoder</h2> <p>It all comes down to instructing your <code>json.dumps()</code> call to use a custom encoder. If you're familiar with the Django world, you've probably run into this with the <code>DjangoJSONEncoder</code> serializer. We essentially want to coerce Pandas and Numpy-specific types to core Python types, and then JSON generation more or less works. Here's an example of how to do so, with comments to explain what's going on.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">import </span><span style="color:#f8f8f2;">numpy </span><span class="lol"></span><span style="color:#ff79c6;">from </span><span style="color:#f8f8f2;">json </span><span style="color:#ff79c6;">import </span><span style="color:#f8f8f2;">JSONEncoder </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#ff79c6;">class </span><span style="text-decoration:underline;color:#8be9fd;">CustomJSONEncoder</span><span style="color:#f8f8f2;">(</span><span style="text-decoration:underline;font-style:italic;color:#8be9fd;">JSONEncoder</span><span style="color:#f8f8f2;">): </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">def </span><span style="color:#50fa7b;">default</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">obj_to_encode</span><span style="color:#f8f8f2;">): </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">&quot;&quot;&quot;Pandas and Numpy have some specific types that we want to ensure </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">are coerced to Python types, for JSON generation purposes. This attempts </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">to do so where applicable. </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">&quot;&quot;&quot; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># Pandas dataframes have a to_json() method, so we&#39;ll check for that and </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># return it if so. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if </span><span style="color:#8be9fd;">hasattr</span><span style="color:#f8f8f2;">(obj_to_encode, </span><span style="color:#f1fa8c;">&#39;to_json&#39;</span><span style="color:#f8f8f2;">): </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return </span><span style="color:#f8f8f2;">obj_to_encode</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">to_json</span><span style="color:#f8f8f2;">() </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># Numpy objects report themselves oddly in error logs, but this generic </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># type mostly captures what we&#39;re after. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if </span><span style="color:#8be9fd;">isinstance</span><span style="color:#f8f8f2;">(obj_to_encode, numpy</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">generic): </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return </span><span style="color:#f8f8f2;">numpy</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">asscalar</span><span style="color:#f8f8f2;">(obj_to_encode) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># ndarray -&gt; list, pretty straightforward. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if </span><span style="color:#8be9fd;">isinstance</span><span style="color:#f8f8f2;">(obj_to_encode, numpy</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">ndarray): </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return </span><span style="color:#f8f8f2;">obj_to_encode</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">to_list</span><span style="color:#f8f8f2;">() </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># If none of the above apply, we&#39;ll default back to the standard JSON encoding </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># routines and let it work normally. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return </span><span style="color:#8be9fd;">super</span><span style="color:#f8f8f2;">()</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">default</span><span style="color:#f8f8f2;">(obj_to_encode) </span><span class="lol"></span></code></pre> <p>With that, it's a one-line change to use it as our JSON encoder of choice:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#f8f8f2;">json</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">dumps</span><span style="color:#f8f8f2;">({ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;my_pandas_type&#39;</span><span style="color:#f8f8f2;">: pandas_value, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;my_numpy_type&#39;</span><span style="color:#f8f8f2;">: numpy_value </span><span class="lol"></span><span style="color:#f8f8f2;">}, </span><span style="font-style:italic;color:#ffb86c;">cls</span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;">CustomJSONEncoder) </span><span class="lol"></span></code></pre><h2 id="wrapping-up">Wrapping Up</h2> <p>Now, returning and serializing Pandas and Numpy-specific data types should <em>&quot;just work&quot;</em>. If you're the Django type, you could optionally subclass <code>DjangoJSONEncoder</code> and apply the same approach with easy serialization of your model instances.</p> My 2018 Reading List Mon, 31 Dec 2018 00:00:00 +0000 https://rymc.io/blog/2018/my-2018-reading-list/ https://rymc.io/blog/2018/my-2018-reading-list/ <p>It's the last day of 2018, and a few hours before midnight strikes. Looking back, I didn't get to do as much writing on here for the year as I wanted to (something I'm trying to change in 2019 and beyond). I did, however, manage to find time to do a lot of reading this year; mostly due to long flights, but also from a real effort on my part to get back into the habit.</p> <p>It seemed like a fitting way to close the year out, then, by listing the books I found insightful and worth grabbing a copy of. I'm opting not to link to Amazon for these, but if you prefer to buy books through there, you should be able to find any of them with a quick search. These are also lightly divided by topics, primarily for some structure.</p> <h2 id="privacy">Privacy</h2> <p>I've generally been a private person, but 2018 in general led to me looking for a better understanding of what privacy really means, both on a consumer and product development level. It's not a topic that can be approached with technology alone; the books below helped me to have an even better understanding of the history of privacy (insofar as US law goes, although much of it is applicable in general).</p> <h3 id="habeas-data-by-cyrus-farivar"><a href="https://www.mhpbooks.com/books/habeas-data/">Habeas Data, by Cyrus Farivar</a></h3> <p>I found this book while stumbling through <a href="https://www.elliottbaybook.com">the Elliot Bay Book Company</a> in Seattle, and bought it after skimming a few pages. Cyrus does an amazing job outlining the most significant court cases that have shaped the current legal view on privacy, while simultaneously showcasing how the government and general public have failed to keep up with the ethical questions and concerns that have come up in the past few decades as technology has exploded. In general, this was a tough book to put down: it easily messed up my sleep schedule, due to a night or two of reading into the morning hours, and I walked away feeling like I had a better understanding of the privacy landscape.</p> <p><strong>Rating</strong>: 10/10</p> <h3 id="future-crimes-by-marc-goodman"><a href="http://www.futurecrimesbook.com">Future Crimes, by Marc Goodman</a></h3> <p>This book was frustrating, is the best way I can put it. I picked it up on sale after noticing that it was listed as a best seller... and then I regretted it for the following few weeks as I labored through it. The author has a writing style that can be explained best as <em>&quot;stating the obvious for 500 pages&quot;</em>. If you have a background in technology, I would skip this; if you're wondering just what's possible with technology, it's probably an okay read, if not a bit hyperbolic.</p> <p><strong>Rating</strong>: 4/10</p> <h3 id="privacy-s-blueprint-by-woodrow-hartzog"><a href="http://www.hup.harvard.edu/catalog.php?isbn=9780674976009">Privacy's Blueprint, by Woodrow Hartzog</a></h3> <p>Much of the internet is opt-in, in a sense - there's generally a privacy policy and terms of service, but it's up to you to read the fine print and decide whether it's good or bad. This book that examines how privacy can be regulated and enforced from the legal side, by requiring product makers to respect privacy in how they build. If you work in tech, touch user data, or have a passing interest in privacy, then this is worth a read.</p> <p><strong>Rating</strong>: 7.5/10</p> <h2 id="food">Food</h2> <p>Cooking is a hobby of mine. I wouldn't consider myself professional, but it's amazing for stress relief. It's also a creative outlet for when I can't stand looking at a computer anymore.</p> <h3 id="how-to-taste-by-becky-selengut"><a href="https://www.penguinrandomhouse.com/books/559594/how-to-taste-by-becky-selengut/9781632171054/">How to Taste, by Becky Selengut</a></h3> <p>This book changed my life: it gave me the final secret to scrambled eggs that I didn't know I needed. While it's worth picking up for this fact alone, the author does a great job illustrating the link between salt, taste, and why so much of what you taste out there tastes bad. I started this on an 8 hour flight and was finished before the end, could not put it down.</p> <p><strong>Rating</strong>: 9/10</p> <h3 id="acid-trip-travels-in-the-world-of-vinegar-by-michael-harlan-turkell"><a href="https://www.abramsbooks.com/product/acid-trip-travels-in-the-world-of-vinegar_9781419724176/">Acid Trip: Travels in the World of Vinegar, by Michael Harlan Turkell</a></h3> <p>A strange book on this list, in that it's easy to look at it more as a cookbook than anything else. I received my copy from a dinner party I attended where the author was giving a presentation, and I guarantee you there's more to it: Turkell does a great job going into the history of vinegar, the different forms out there, and the insane amount of uses it serves. Features a ton of recipes (~100), some of which I still haven't gotten to trying. Highly recommended.</p> <p>Also, Michael, if you're reading this, my dog literally ate your business card. Hope you found everything you were looking for in Tokyo.</p> <p><strong>Rating</strong>: 7/10</p> <h2 id="self">Self</h2> <p>These are books I picked up on a whim, with some level of self improvement in mind.</p> <h3 id="harvard-business-review-s-10-must-reads-on-emotional-intelligence"><a href="https://hbr.org/product/hbrs-10-must-reads-on-emotional-intelligence-with-featured-article-what-makes-a-leader-by-daniel-goleman/15036-PBK-ENG">Harvard Business Review's 10 &quot;Must Reads&quot; on Emotional Intelligence</a></h3> <p>A collection of articles and essays that help define and increase understanding of emotional intelligence. I originally picked this up to broaden my skills regarding interacting with and leading other people, and I think it helped foster a better way of looking at situations that involve other people. A little bit less about the data and numbers, and more about understanding what it takes to effectively manage and work with people. Recommended.</p> <p><strong>Rating</strong>: 7/10</p> <h3 id="mastermind-how-to-think-like-sherlock-holmes-by-maria-konnikova"><a href="https://www.mariakonnikova.com/books/mastermind/">Mastermind: How to Think like Sherlock Holmes, by Maria Konnikova</a></h3> <p>This is another book where the writing style drove me slightly insane; it definitely felt like the same points being repeated for multiple paragraphs straight. With that said, the content was worth slogging through, and if you can put up with the writing, this is a fun read that'll leave you with some exercises for your brain.</p> <p><strong>Rating</strong>: 6.5/10</p> <h2 id="2019">2019</h2> <p>Most of these titles were, sadly, only read in the last six months. I'm hoping to make a bigger push in 2019, with a wider range of topics to boot. If you have any recommendations for titles similar to the above, feel free to let me know!</p> Forcing PNG for Twitter Profile Photos Tue, 18 Dec 2018 00:00:00 +0000 https://rymc.io/blog/2018/forcing-png-for-twitter-profile-photos/ https://rymc.io/blog/2018/forcing-png-for-twitter-profile-photos/ <div class="blog-edit-note"> <h4>Edit as of January 8th, 2019:</h4> <p>Twitter announced that they were modifying their last changes, ensuring that some images remain as PNGs! <a href="https://twittercommunity.com/t/upcoming-changes-to-png-image-support/118695" title="Read about upcoming changes to Twitter's PNG support">You can read the full announcement here</a>, which details when an image will remain a PNG. Thanks to this, you may not need the trick below - I'll keep it up in case anyone finds it interesting, though. </p> </div> <div class="blog-edit-note"> <h4>Edit as of December 26th, 2018:</h4> <p>Twitter announced recently that come February 11th, 2019, they'll be enforcing stricter conversion logic surrounding uploaded images. Until then, the below still works; a theoretical (and pretty good sounding) approach for post-Feb-11th is outlined <a href="https://twitter.com/FioraAeterna/status/1077786781133631489">here</a>. </p> </div> <p>A rather short entry, but one that I felt was necessary - a lot of the information floating in search engines on this is just plain wrong in 2018, and I spent longer than I wanted to figuring this out. It helped me to get <a href="https://twitter.com/ryanmcgrath">my twitter avatar</a> a bit nicer looking, though.</p> <h2 id="help-twitter-compressed-the-hell-out-of-my-image">Help, Twitter compressed the hell out of my image!</h2> <p>Yeah, that happens. If you upload an image for your profile or banner image, Twitter automatically kicks off background jobs to take that image and produce lightweight JP(E)G variants. These tend to look... very bad. There's a trick you can use for posting photos in tweets where, if you make an image have one pixel that's ~90% transparent, Twitter won't force it off of PNG. It doesn't appear to work on profile photos at first glance, though.</p> <h2 id="getting-around-the-problem">Getting around the problem</h2> <p>Before I explain how to fix this, there's a few things to note:</p> <ul> <li>Your profile photo should be 400 by 400 pixels. Any larger will trigger resizing. Any smaller, you're just doing yourself a disservice.</li> <li>Your profile photo should be less than 700kb, as noted in the API documentation. Anything over will trigger resizing.</li> <li>Your profile photo should be a truecolor PNG (24bit), with transparency included. Theoretically you can also leave it interlaced but this didn't impact anything in my testing.</li> </ul> <p>Now, the thing that I found is that the 1 pixel transparency trick doesn't work on profile photos, but if you crop it to be a circle with the transparency behind it, this seems to be enough. If I had to hazard a guess, I'd wager that Twitter ignores transparent pixels unless it deems they seriously matter... as in, there's a threshold you have to hit, or something.</p> <p>Something like this:</p> <p><img src="/img/example_twitter_crop.png" alt="Example cropped avatar" class="articleImg" /></p> <h2 id="uploading-properly">Uploading Properly</h2> <p>For some reason, uploading your profile photo on the main site incurs notably more distorted images than if you do it via an API call. I cannot fathom why this is, but it held true for me. If you're not the programming type, old school apps like <a href="https://tapbots.com/tweetbot/">Tweetbot</a> still use the API call, so changing the photo via Tweetbot should theoretically do it.</p> <p>If you're the programming type, here's a handy little script to do this for you:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;"># pip install twython to get this library </span><span class="lol"></span><span style="color:#ff79c6;">from </span><span style="color:#f8f8f2;">twython </span><span style="color:#ff79c6;">import </span><span style="color:#f8f8f2;">Twython </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;"># Create a new App at https://dev.twitter.com/, check off &quot;sign in via Twitter&quot;, and get your tokens there </span><span class="lol"></span><span style="color:#f8f8f2;">twitter </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">Twython</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;Consumer API Key&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&#39;Consumer API Secret&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&#39;Access Token&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&#39;Access Token Secret&#39;</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;"># Read and upload the image, see image guidelines in post above~ </span><span class="lol"></span><span style="color:#f8f8f2;">image </span><span style="color:#ff79c6;">= </span><span style="color:#8be9fd;">open</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;path/to/image.png&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&#39;rb&#39;</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;">twitter</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">update_profile_image</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">image</span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;">image) </span><span class="lol"></span></code></pre> <p>Keep in mind that Twitter will still make various sizes out of what you upload, so even this trick doesn't save you from their system - just helps make certain images a tiny bit more crisp. Hope it helps!</p> Swipeable NSCollectionView Mon, 03 Dec 2018 00:00:00 +0000 https://rymc.io/blog/2018/swipeable-nscollectionview/ https://rymc.io/blog/2018/swipeable-nscollectionview/ <p>If you've done any iOS development, you're likely familiar with <code>UICollectionView</code>, a data-driven way of building complex views that offer more flexibility than <code>UITableView</code>. If you've dabbled in Cocoa (macOS) development, you've probably seen <code>NSCollectionView</code> and <code>NSTableView</code>... and promptly wondered what era you stepped in to.</p> <p>I kid, but only somewhat. Over the past few macOS releases, Apple's been silently patching these classes to be more modern. <code>NSTableView</code> is actually super similar to <code>UITableView</code>; auto-sizing rows work fine, and you can get free row-swiping on trackpads. You'll be stuck working around a strange data source discrepancy (a flat list vs a 2-dimensional list), but it can work. <code>NSCollectionView</code> was mired by an old strange API for awhile, but it's now mostly compatible with <code>UICollectionView</code>, provided you do the proper ritual to force it into the new API.</p> <p>In iOS-land, there's a pretty great project by the name of <a href="https://github.com/SwipeCellKit/SwipeCellKit">SwipeCellKit</a> which brings swipe-to-reveal functionality to <code>UICollectionView</code>. It's nice, as one of the more annoying things about moving from <code>UITableView</code> to <code>UICollectionView</code> has been the lack of swipeable actions. In an older project I wound up looking into how difficult it'd be to bring this same API to <code>NSCollectionViewItem</code>; I didn't finish it as the design for the project wound up being easier to implement with <code>NSTableView</code>, but I figured I'd share my work here in case anyone out there wants to extend it further. A good chunk of this has been from digging through various disconnected docs and example projects from Apple, coupled with poking and prodding through Xcode, so I'd hate for it to fade back into obscurity.</p> <h2 id="just-give-me-the-code">Just Give Me the Code...</h2> <p>If you're the type who'd rather dig around in code, <a href="https://github.com/ryanmcgrath/holidaycalendar">feel free to jump directly to the project on GitHub</a>. It's a complete macOS Xcode project that you can run and mess around with, in the style of a holiday calendar. It's December, sue me.</p> <h2 id="swiping-on-mac">Swiping on Mac</h2> <p>Getting this working for Cocoa was a bit cumbersome, as there's a few different ways you can attempt it, all with their own pitfalls.</p> <ul> <li>Like UIKit, Cocoa and AppKit have the concept of Gesture Recognizers... but they're more limited in general, as they seemingly require a full click before you can act on mouse or gesture movement. I spent a bit of time testing this, and it seems impossible to disable. This means they ultimately don't work, as a trackpad on Mac can be configured to not be click-on-tap. In addition, a few things here seem specific to the Touch Bar found in newer MacBook Pros, which don't particularly help here.</li> <li>We could try the old school Mouse tracking <code>NSEvent</code> APIs, but they feel very cumbersome in 2018 (not that they don't have their place). Documentation also feels very spotty on them.</li> </ul> <p>Ultimately, the best approach I found was going with simple <code>touchesBegan()</code>, <code>touchesMoved()</code>, and <code>touchesEnded()</code> methods on the view controller item. The bulk of the logic happens in <code>touchesMoved()</code>, with the rest existing mostly as flow-control for everything.</p> <h2 id="opting-in">Opting In</h2> <p>Before implementing those methods, setting up an <code>NSCollectionViewItem</code> so that it'll report touch events requires a couple of lines of code. I don't use Interface Builder, so this is included in overriding <code>loadView()</code> below; if you're an Interface Builder user, you might opt for this to happen in <code>viewDidLoad()</code> instead.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">class </span><span style="font-style:italic;color:#66d9ef;">HolidayCalendarCollectionViewItem</span><span style="color:#ff79c6;">: </span><span style="font-style:italic;color:#66d9ef;">NSCollectionViewItem</span><span style="color:#f8f8f2;"> { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">var </span><span style="font-style:italic;color:#ffb86c;">leftAnchor</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">NSLayoutConstraint</span><span style="color:#ff79c6;">? </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">var </span><span style="font-style:italic;color:#ffb86c;">initialTouchOne</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">NSTouch</span><span style="color:#ff79c6;">? </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">var </span><span style="font-style:italic;color:#ffb86c;">initialTouchTwo</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">NSTouch</span><span style="color:#ff79c6;">? </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">var </span><span style="font-style:italic;color:#ffb86c;">currentTouchOne</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">NSTouch</span><span style="color:#ff79c6;">? </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">var </span><span style="font-style:italic;color:#ffb86c;">currentTouchTwo</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">NSTouch</span><span style="color:#ff79c6;">? </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">var </span><span style="font-style:italic;color:#ffb86c;">initialPoint</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">CGPoint</span><span style="color:#ff79c6;">? </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">var</span><span style="color:#f8f8f2;"> isTracking = </span><span style="color:#bd93f9;">false </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">public lazy var </span><span style="font-style:italic;color:#ffb86c;">contentView</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">NSView</span><span style="color:#f8f8f2;"> = { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> view = </span><span style="font-style:italic;color:#66d9ef;">NSView</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">frame</span><span style="color:#f8f8f2;">: .</span><span style="color:#bd93f9;">zero</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">view</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">translatesAutoresizingMaskIntoConstraints = </span><span style="color:#bd93f9;">false </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return</span><span style="color:#f8f8f2;"> view </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">}() </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">override </span><span style="font-style:italic;color:#8be9fd;">func </span><span style="color:#50fa7b;">loadView</span><span style="color:#f8f8f2;">() { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> itemView = </span><span style="font-style:italic;color:#66d9ef;">NSView</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">frame</span><span style="color:#f8f8f2;">: .</span><span style="color:#bd93f9;">zero</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">itemView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">postsFrameChangedNotifications = </span><span style="color:#bd93f9;">false </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">itemView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">postsBoundsChangedNotifications = </span><span style="color:#bd93f9;">false </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">itemView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">wantsLayer = </span><span style="color:#bd93f9;">true </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">itemView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">allowedTouchTypes = [.</span><span style="color:#bd93f9;">direct</span><span style="color:#f8f8f2;">, .</span><span style="color:#bd93f9;">indirect</span><span style="color:#f8f8f2;">] </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">itemView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">addSubview(contentView) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">leftAnchor = contentView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">leftAnchor</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">constraint(</span><span style="font-style:italic;color:#ffb86c;">equalTo</span><span style="color:#f8f8f2;">: itemView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">leftAnchor) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">NSLayoutConstraint</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">activate([ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">contentView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">topAnchor</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">constraint(</span><span style="font-style:italic;color:#ffb86c;">equalTo</span><span style="color:#f8f8f2;">: itemView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">topAnchor), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">leftAnchor</span><span style="color:#ff79c6;">!</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">contentView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">bottomAnchor</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">constraint(</span><span style="font-style:italic;color:#ffb86c;">equalTo</span><span style="color:#f8f8f2;">: itemView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">bottomAnchor), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">contentView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">widthAnchor</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">constraint(</span><span style="font-style:italic;color:#ffb86c;">equalTo</span><span style="color:#f8f8f2;">: itemView</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">widthAnchor), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">]) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">view = itemView </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;">} </span><span class="lol"></span></code></pre> <p>Of note here:</p> <ul> <li>We need to make sure that <code>allowedTouchTypes</code> supports direct and indirect touch types. Some of the docs allude to these being used more for the Touch Bar, but in my testing not having them resulted in the swipes not registering sometimes. Go figure.</li> <li>We add in a <code>contentView</code> property here; UICollectionViewCell already has this property, but NSCollectionViewItem is a View Controller and lacks it. Since we need two layers for swiping to reveal something, we'll just follow the UICollectionView API for comfort.</li> <li><code>postsFrameChangedNotifications</code> and <code>postsBoundsChangedNotifications</code> are something I disable, as they can make resizing and animating complex NSCollectionViews choppy. I learned of this from some Cocoa developer who threw it on Twitter, where it likely fell into the ether and doesn't surface much anymore. Helped me in early 2018, so I'm not inclined to believe it's changed. Friends don't let friends post this stuff on Twitter.</li> <li>We keep a <code>leftAnchor</code> reference to do swipe animations later, and rather than pin the right anchor to the item right anchor, we just map the width.</li> </ul> <h2 id="capturing-the-swipe">Capturing the Swipe</h2> <p>With the above in place, touches should properly register. We're primarily interested in mimicing the two-finger swipe-to-reveal that <code>NSTableView</code> has, so our <code>touchesBegan</code> should block anything other than that.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">override </span><span style="font-style:italic;color:#8be9fd;">func </span><span style="color:#50fa7b;">touchesBegan</span><span style="color:#f8f8f2;">(with </span><span style="font-style:italic;color:#ffb86c;">event</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">NSEvent</span><span style="color:#f8f8f2;">) { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(isTracking) { </span><span style="color:#ff79c6;">return</span><span style="color:#f8f8f2;"> } </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> initialTouches = event</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">touches(</span><span style="font-style:italic;color:#ffb86c;">matching</span><span style="color:#f8f8f2;">: .</span><span style="color:#bd93f9;">touching</span><span style="color:#f8f8f2;">, </span><span style="color:#ff79c6;">in</span><span style="color:#f8f8f2;">: view) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(initialTouches</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">count != </span><span style="color:#bd93f9;">2</span><span style="color:#f8f8f2;">) { </span><span style="color:#ff79c6;">return</span><span style="color:#f8f8f2;"> } </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">isTracking = </span><span style="color:#bd93f9;">true </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">initialPoint = view</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">convert(event</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">locationInWindow, </span><span style="font-style:italic;color:#ffb86c;">from</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">nil</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> touches = </span><span style="font-style:italic;color:#66d9ef;">Array</span><span style="color:#f8f8f2;">(initialTouches) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">initialTouchOne = touches[</span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">] </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">initialTouchTwo = touches[</span><span style="color:#bd93f9;">1</span><span style="color:#f8f8f2;">] </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">currentTouchOne = touches[</span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">] </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">currentTouchTwo = touches[</span><span style="color:#bd93f9;">1</span><span style="color:#f8f8f2;">] </span><span class="lol"></span><span style="color:#f8f8f2;">} </span><span class="lol"></span></code></pre> <p>When the first two-finger swipe begins, we grab the initial points for comparing to later movements.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">override </span><span style="font-style:italic;color:#8be9fd;">func </span><span style="color:#50fa7b;">touchesMoved</span><span style="color:#f8f8f2;">(with </span><span style="font-style:italic;color:#ffb86c;">event</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">NSEvent</span><span style="color:#f8f8f2;">) { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">!</span><span style="color:#f8f8f2;">isTracking) { </span><span style="color:#ff79c6;">return</span><span style="color:#f8f8f2;"> } </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> currentTouches = event</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">touches(</span><span style="font-style:italic;color:#ffb86c;">matching</span><span style="color:#f8f8f2;">: .</span><span style="color:#bd93f9;">touching</span><span style="color:#f8f8f2;">, </span><span style="color:#ff79c6;">in</span><span style="color:#f8f8f2;">: view) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(currentTouches</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">count != </span><span style="color:#bd93f9;">2</span><span style="color:#f8f8f2;">) { </span><span style="color:#ff79c6;">return</span><span style="color:#f8f8f2;"> } </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">currentTouchOne = </span><span style="color:#bd93f9;">nil </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">currentTouchTwo = </span><span style="color:#bd93f9;">nil </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">currentTouches</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">forEach { (</span><span style="font-style:italic;color:#ffb86c;">touch</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">NSTouch</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">in </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(touch</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">identity</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">isEqual(initialTouchOne</span><span style="color:#ff79c6;">?.</span><span style="color:#f8f8f2;">identity)) { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">currentTouchOne = touch </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span style="color:#ff79c6;">else</span><span style="color:#f8f8f2;"> { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">currentTouchTwo = touch </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> initialXPoint = [ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">initialTouchOne</span><span style="color:#ff79c6;">?.</span><span style="color:#f8f8f2;">normalizedPosition</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">x ?? </span><span style="color:#bd93f9;">0.0</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">initialTouchTwo</span><span style="color:#ff79c6;">?.</span><span style="color:#f8f8f2;">normalizedPosition</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">x ?? </span><span style="color:#bd93f9;">0.0 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">].</span><span style="color:#bd93f9;">min</span><span style="color:#f8f8f2;">() ?? </span><span style="color:#bd93f9;">0.0 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> currentXPoint = [ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">currentTouchOne</span><span style="color:#ff79c6;">?.</span><span style="color:#f8f8f2;">normalizedPosition</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">x ?? </span><span style="color:#bd93f9;">0.0</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">currentTouchTwo</span><span style="color:#ff79c6;">?.</span><span style="color:#f8f8f2;">normalizedPosition</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">x ?? </span><span style="color:#bd93f9;">0.0 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">].</span><span style="color:#bd93f9;">min</span><span style="color:#f8f8f2;">() ?? </span><span style="color:#bd93f9;">0.0 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> deviceWidth = initialTouchOne</span><span style="color:#ff79c6;">?.</span><span style="color:#f8f8f2;">deviceSize</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">width ?? </span><span style="color:#bd93f9;">0.0 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> oldX = (initialXPoint * deviceWidth)</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">rounded(.</span><span style="color:#bd93f9;">up</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let</span><span style="color:#f8f8f2;"> newX = (currentXPoint * deviceWidth)</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">rounded(.</span><span style="color:#bd93f9;">up</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">var </span><span style="font-style:italic;color:#ffb86c;">delta</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">CGFloat</span><span style="color:#f8f8f2;"> = </span><span style="color:#bd93f9;">0.0 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(oldX &gt; newX) { </span><span style="color:#6272a4;">// Swiping left </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">delta = (oldX - newX) * -</span><span style="color:#bd93f9;">1.0 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span style="color:#ff79c6;">else if</span><span style="color:#f8f8f2;">(newX &gt; oldX) { </span><span style="color:#6272a4;">// Swiping right </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">delta = newX - oldX </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">NSAnimationContext</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">runAnimationGroup { [</span><span style="color:#ff79c6;">weak self</span><span style="color:#f8f8f2;">] (</span><span style="font-style:italic;color:#ffb86c;">context</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">NSAnimationContext</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">in </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">context</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">timingFunction = </span><span style="font-style:italic;color:#66d9ef;">CAMediaTimingFunction</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">name</span><span style="color:#f8f8f2;">: .</span><span style="color:#bd93f9;">easeIn</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">context</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">duration = </span><span style="color:#bd93f9;">0.2 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">context</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">allowsImplicitAnimation = </span><span style="color:#bd93f9;">true </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">self?.</span><span style="color:#f8f8f2;">leftAnchor</span><span style="color:#ff79c6;">?.</span><span style="color:#f8f8f2;">animator()</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">constant = delta </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;">} </span><span class="lol"></span></code></pre> <p>As a drag occurs, this event will continually fire. We grab the newest (&quot;current&quot;) touches, and compare where they are in relation to the initial touches. There's a bit of math involved here to get this right, as the Trackpad on Mac isn't quite like a touch screen (<code>normalizedPosition</code> doesn't map to a pixel coordinate). Once we've calculated everything, we can begin animating the top (content) view to reveal the contents underneath.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">override </span><span style="font-style:italic;color:#8be9fd;">func </span><span style="color:#50fa7b;">touchesEnded</span><span style="color:#f8f8f2;">(with </span><span style="font-style:italic;color:#ffb86c;">event</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">NSEvent</span><span style="color:#f8f8f2;">) { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">self.</span><span style="color:#f8f8f2;">isTracking) { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">self.</span><span style="color:#f8f8f2;">endTracking(leftAnchor</span><span style="color:#ff79c6;">?.</span><span style="color:#f8f8f2;">constant ?? </span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">override </span><span style="font-style:italic;color:#8be9fd;">func </span><span style="color:#50fa7b;">touchesCancelled</span><span style="color:#f8f8f2;">(with </span><span style="font-style:italic;color:#ffb86c;">event</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">NSEvent</span><span style="color:#f8f8f2;">) { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">self.</span><span style="color:#f8f8f2;">isTracking) { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">self.</span><span style="color:#f8f8f2;">endTracking(leftAnchor</span><span style="color:#ff79c6;">?.</span><span style="color:#f8f8f2;">constant ?? </span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">func </span><span style="color:#50fa7b;">endTracking</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">_ </span><span style="font-style:italic;color:#ffb86c;">delta</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">CGFloat</span><span style="color:#f8f8f2;">) { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">initialTouchOne = </span><span style="color:#bd93f9;">nil </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">initialTouchTwo = </span><span style="color:#bd93f9;">nil </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">currentTouchOne = </span><span style="color:#bd93f9;">nil </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">currentTouchTwo = </span><span style="color:#bd93f9;">nil </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">isTracking = </span><span style="color:#bd93f9;">false </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let </span><span style="font-style:italic;color:#ffb86c;">leftThreshold</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">CGFloat</span><span style="color:#f8f8f2;"> = </span><span style="color:#bd93f9;">50.0 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">let </span><span style="font-style:italic;color:#ffb86c;">rightThreshold</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">CGFloat</span><span style="color:#f8f8f2;"> = -</span><span style="color:#bd93f9;">50.0 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">var </span><span style="font-style:italic;color:#ffb86c;">to</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">CGFloat</span><span style="color:#f8f8f2;"> = </span><span style="color:#bd93f9;">0.0 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(delta &gt; leftThreshold) { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">to = leftThreshold </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span style="color:#ff79c6;">else if</span><span style="color:#f8f8f2;">(delta &lt; rightThreshold) { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">to = rightThreshold </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">NSAnimationContext</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">runAnimationGroup { [</span><span style="color:#ff79c6;">weak self</span><span style="color:#f8f8f2;">] (</span><span style="font-style:italic;color:#ffb86c;">context</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#66d9ef;">NSAnimationContext</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">in </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">context</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">timingFunction = </span><span style="font-style:italic;color:#66d9ef;">CAMediaTimingFunction</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">name</span><span style="color:#f8f8f2;">: .</span><span style="color:#bd93f9;">easeIn</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">context</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">duration = </span><span style="color:#bd93f9;">0.5 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">context</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">allowsImplicitAnimation = </span><span style="color:#bd93f9;">true </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">self?.</span><span style="color:#f8f8f2;">leftAnchor</span><span style="color:#ff79c6;">?.</span><span style="color:#f8f8f2;">animator()</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">constant = to </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;">} </span><span class="lol"></span></code></pre> <p>The last necessary pieces are just handling when a drag event ends or is cancelled. We'll forward both of those events into <code>endTracking</code>, which determines the final resting state of the drag animation: if we've dragged far enough in either direction, it'll &quot;snap&quot; to the threshold and hang there until a new swipe gesture begins.</p> <h2 id="taking-it-further">Taking It Further</h2> <p>While the above implements swiping, it's not... great yet. As I noted over on the GitHub repository, this could definitely be tied into a SwipeCellKit-esque API (or just into SwipeCellKit entirely). It also doesn't take drag velocity into account, as calculating it on macOS isn't as simple as iOS, and I ended up scrapping this before using it in a shipping product. Feel free to crib whatever code or assets as necessary! If you end up building on this or taking it further, a line of credit would be cool.</p> <p><img src="/img/Unswiped.png" alt="Unswiped example image" width="320" /> <img src="/img/SwipedOpen.png" alt="Swiped example image" width="320" /></p> Rust, or: What's the Deal with GUIs? Fri, 29 Jun 2018 00:00:00 +0000 https://rymc.io/blog/2018/rust-or-whats-the-deal-with-guis/ https://rymc.io/blog/2018/rust-or-whats-the-deal-with-guis/ <h2 id="two-years-and-guis">Two Years and GUIs</h2> <p>One of the big (and kind of annoying) discussions that's been bantered about in the tech world over those two years has been whether Electron, a web-browser-masquerading-as-native-app project, is a plague on society or not. Resource wise, it's probably got some argument there, but in terms of productivity it's hands down the king of the castle - effectively trading memory pressure and less focus on platform conventions in favor of just shipping something out the door. You can open and scan any thread on Hacker News or /r/programming and find people bemoaning this repeatedly.</p> <p>I wouldn't keep those threads open, for what it's worth - there's generally little worth reading in them, beyond people on both sides completely misunderstanding one another. The tl;dr for pretty much every thread is: more traditional native developers tend not to understand web development, or they look down upon it as easier and less worthy of respect. Web developers tend to favor shipping faster to a wider audience, and being able to implement things across (just about) any platform. You'll see some native developers go on about Qt/GTK and co as ideal native approaches (they're not), or advocating tripling your development efforts and re-implementing a UI across n platforms (why would you bother to do this for most commerical projects? Do you enjoy wasting time and money?).</p> <p>With that all said, I had reason to build some stuff in Rust recently, and wound up wanting to throw together a basic UI for it. Rust is a wonderful language, and I genuinely enjoy using it, but it's very clear that the community doesn't have a GUI focus. I figured I'd end up basically packaging together some web view (Electron or [insert your choice here]), but wanted to see how easily I could finagle things together at a native level. Since I haven't written anything in some time, I figured this would be a good fit for a super short post.</p> <h2 id="going-down-a-gui-rabbit-hole">Going Down a GUI Rabbit Hole</h2> <p>I use a Mac pretty much exclusively, and am pretty familiar with Cocoa, so jumping to that from Rust seemed pretty reasonable. It's also ridiculously simple, thanks to Objective-C - the C interop in Rust makes this fairly transparent, provided you're willing to dabble in the unsafe side of things. What I came up with was a mix of <a href="https://github.com/SSheldon/rust-objc">rust-objc</a> and <a href="https://github.com/servo/core-foundation-rs/tree/master/cocoa">rust-cocoa, from core-foundation-rs</a>, along with some usage of Serde for a quickly hacked together CSS framework.</p> <p>Given the following example styling...</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#f8f8f2;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#eeeeee;">&quot;</span><span style="color:#cfcfc2;">window</span><span style="color:#eeeeee;">&quot;</span><span style="color:#f8f8f2;">: { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;backgroundColor&quot;</span><span style="color:#f8f8f2;">: {</span><span style="color:#f1fa8c;">&quot;r&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">35</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&quot;g&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">108</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&quot;b&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">218</span><span style="color:#f8f8f2;">}, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;defaultWidth&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">800</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;defaultHeight&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">600 </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">}, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#eeeeee;">&quot;</span><span style="color:#cfcfc2;">root</span><span style="color:#eeeeee;">&quot;</span><span style="color:#f8f8f2;">: { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;backgroundColor&quot;</span><span style="color:#f8f8f2;">: {</span><span style="color:#f1fa8c;">&quot;r&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">35</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&quot;g&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">108</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&quot;b&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">218</span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">}, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#eeeeee;">&quot;</span><span style="color:#cfcfc2;">sidebar</span><span style="color:#eeeeee;">&quot;</span><span style="color:#f8f8f2;">: { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;backgroundColor&quot;</span><span style="color:#f8f8f2;">: {</span><span style="color:#f1fa8c;">&quot;r&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">5</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&quot;g&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">5</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&quot;b&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">5</span><span style="color:#f8f8f2;">}, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;width&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">200</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;height&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">400</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;top&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&quot;root.top&quot;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;left&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&quot;root.left&quot;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;bottom&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&quot;root.bottom&quot; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">}, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#eeeeee;">&quot;</span><span style="color:#cfcfc2;">content</span><span style="color:#eeeeee;">&quot;</span><span style="color:#f8f8f2;">: { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;backgroundColor&quot;</span><span style="color:#f8f8f2;">: {</span><span style="color:#f1fa8c;">&quot;r&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">35</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&quot;g&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">108</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&quot;b&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">218</span><span style="color:#f8f8f2;">}, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;width&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">100</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;height&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">300</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;top&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&quot;root.top&quot;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;left&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&quot;sidebar.right&quot;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;right&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&quot;root.right&quot;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;bottom&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&quot;root.bottom&quot; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;">} </span><span class="lol"></span></code></pre> <p>A basic app could be constructed like so:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">extern crate</span><span style="color:#f8f8f2;"> shinekit; </span><span class="lol"></span><span style="color:#ff79c6;">use </span><span style="text-decoration:underline;color:#66d9ef;">shinekit</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">fn </span><span style="color:#50fa7b;">main</span><span style="color:#f8f8f2;">() </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="text-decoration:underline;color:#66d9ef;">shinekit</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">run(vec![ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="text-decoration:underline;color:#66d9ef;">StyleSheet</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">default(include_str!(</span><span style="color:#f1fa8c;">&quot;styles/default.json&quot;</span><span style="color:#f8f8f2;">)) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">], </span><span style="text-decoration:underline;color:#66d9ef;">App</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">new(</span><span style="color:#f1fa8c;">&quot;App&quot;</span><span style="color:#f8f8f2;">, </span><span style="text-decoration:underline;color:#66d9ef;">View</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">named(</span><span style="color:#f1fa8c;">&quot;root&quot;</span><span style="color:#f8f8f2;">)</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">subviews</span><span style="color:#f8f8f2;">(vec![ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="text-decoration:underline;color:#66d9ef;">View</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">named(</span><span style="color:#f1fa8c;">&quot;sidebar&quot;</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="text-decoration:underline;color:#66d9ef;">View</span><span style="text-decoration:underline;color:#ff79c6;">::</span><span style="color:#f8f8f2;">named(</span><span style="color:#f1fa8c;">&quot;content&quot;</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">]))); </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span></code></pre> <p>The declarative approach to the UI is inspired by React (and co). I threw the resulting package on Github as <a href="https://github.com/ryanmcgrath/shinekit">Shinekit</a>, in case anyone out there finds it interesting and would want to hack on it themselves. Since it's basically hacking through Objective-C, I've got a theory that it could be wired up to <a href="https://github.com/Microsoft/WinObjC">Microsoft's port of Objective-C for Windows</a>, which ultimately creates native UWP apps.</p> <p>Of note, some things for GUI programming are a pain in the ass in Rust - e.g, parent and child relationships. Shinekit pushes that stuff over to Cocoa/Objective-C where possible, rather than tackling it head-on. In a sense, it's cheating - but it creates a slightly nicer API, which (in my experience) is important in UI development.</p> <h2 id="what-s-next">What's Next?</h2> <p>Well, if you're a native fan, you're gonna hate to hear that yes, I did wind up just throwing a web browser into the mix to ship. Goal wasn't to build a full GUI framework.</p> <p>With that said... time permitting I'll probably hack on this some more, as the existing GUI options in Rust don't really fit my idea of what a GUI framework should look and function like. As Rust becomes more popular, a decent GUI approach (even for small toolkit apps and the like) would be great to have. If or when this becomes more mature, I'd throw it up on crates.io as something to be used more widely.</p> A Deep Dive into PL/v8 Tue, 22 Mar 2016 00:00:00 +0000 https://rymc.io/blog/2016/a-deep-dive-into-plv8/ https://rymc.io/blog/2016/a-deep-dive-into-plv8/ <p>Back in August, <a href="https://www.compose.io/articles/plv8-for-postgresql-and-cidr-for-all/" title="Link to Compose.io Blog Post">Compose.io announced</a> the addition of JavaScript as an internal language for all new PostgreSQL deployments. This was thanks to the <a href="http://pgxn.org/dist/plv8/doc/plv8.html#PL.v8" title="Link to the PL/v8 Project">PL/v8 project</a>, which straps Google's rocket of a JavaScript engine (V8) to PostgreSQL. This got me thinking - PL/v8 offers a rich and featureful way to write functions in PostgreSQL. Let's take a look at what it offers by building a mini JavaScript module system on top of it, complete with basic support for the CommonJS API.</p> <h2 id="enabling-pl-v8-on-your-deployment">Enabling PL/v8 on Your Deployment</h2> <p>First thing's first: you'll need to enable it by creating the extension. The quickest and easiest way to do this is likely using psql from a terminal (below), but if you prefer using another tool it shouldn't pose any problems.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;"># You can find your login details over on your deployment&#39;s overview page </span><span class="lol"></span><span style="color:#50fa7b;">psql </span><span style="color:#f1fa8c;">&quot;sslmode=require host=[host] port=[port] dbname=[dbname] user=[username]&quot; </span><span class="lol"></span></code></pre><pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;">-- Once you&#39;ve logged in, execute the statement below to enable plv8 </span><span class="lol"></span><span style="color:#f8f8f2;">create extension plv8; </span><span class="lol"></span></code></pre> <p>PL/v8 also supports two extra JavaScript &quot;dialects&quot; - <a href="http://coffeescript.org/" title="Link to CoffeeScript Website">CoffeeScript</a>, and <a href="http://livescript.net/" title="Link to LiveScript Website">LiveScript</a>. CoffeeScript offers a more Ruby-esque syntax, and LiveScript is a more functional successor to CoffeeScript. We'll be using pure JavaScript in this article, but if you'd like to use either of these languages you'll need to create their extensions below as well.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;">-- If you prefer a different JS dialect, both CoffeeScript and LiveScript </span><span class="lol"></span><span style="color:#6272a4;">-- are supported. Create their extensions to use them! </span><span class="lol"></span><span style="color:#f8f8f2;">create extension plcoffee; </span><span class="lol"></span><span style="color:#f8f8f2;">create extension plls; </span><span class="lol"></span></code></pre><h2 id="javascript-in-postgresql-the-basics">JavaScript in PostgreSQL: The Basics</h2> <p>Creating a function using PL/v8 looks like any other PostgreSQL function, with the exception of a language specifier change. Take the (basic) example below: we're simply incrementing each int in an Array by 2, and returning it as pure JSON.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">create or replace function </span><span style="color:#50fa7b;">addtwo</span><span style="color:#f8f8f2;">(vals </span><span style="font-style:italic;color:#8be9fd;">int</span><span style="color:#f8f8f2;">[]) </span><span class="lol"></span><span style="color:#f8f8f2;">returns json </span><span style="color:#ff79c6;">as</span><span style="color:#f8f8f2;"> $$ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">return </span><span style="color:#bd93f9;">vals</span><span style="color:#f8f8f2;">.</span><span style="color:#bd93f9;">map</span><span style="color:#f8f8f2;">(function(i) { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">return i </span><span style="color:#ff79c6;">+ </span><span style="color:#bd93f9;">2</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">}); </span><span class="lol"></span><span style="color:#f8f8f2;">$$ language plv8; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">select</span><span style="color:#f8f8f2;"> addtwo(array[</span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">47</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">30</span><span style="color:#f8f8f2;">]); </span><span class="lol"></span><span style="color:#6272a4;">-- Returns [2, 49, 32] </span><span class="lol"></span></code></pre> <p>JavaScript isn't technically a functional programming language, but it has many elements of one. Those features shine here - mapping over results is incredibly expressive and easy to work with. While you can't get all crazy with any ES6 code yet (the version of V8 currently used is a bit older), pretty much all the good parts of ES5 are up for grabs. Couple that with the native JSON support PostgreSQL offers, and you can get many of the features (e.g, Schema-less storage) from Document-oriented databases like MongoDB or RethinkDB.</p> <p>As far as working with SQL data in JavaScript goes, it's relatively straightforward. PL/v8 will convert most database types automatically for you, including polymorphic types (<code>anyelement</code>, <code>anyarray</code>, <code>anyenum</code> and <code>anynonarray</code>) and <code>bytea</code> (through JavaScript's Typed Arrays). The only real &quot;gotchas&quot; to be aware of are that any Array you return must be flattened (unless you're returning JSON, in which case go for it), and you can't create your own Typed Arrays for use in functions; you can, however, modify and return the ones passed to your function.</p> <p>While not strictly a &quot;gotcha&quot;, the ever-so-fun issue of context in JavaScript is present in PL/v8. Each SQL function is called with a different_this_ value, and it can be confusing at first. SQL functions do share context though, as far as accessing globally declared variables and functions. As long as you pay attention to scoping issues, you can avoid context binding issues and write reusable code.</p> <h2 id="hacking-together-a-module-system">Hacking Together a Module System</h2> <p>V8 is synonymous with Node.js for many developers, and inevitably the question of importing modules comes up. There is no baked-in module system, but we can simulate one using some of the features of PL/v8. It's important to note that while this works, we're in a sandboxed environment - modules involving network calls or browser-related functionality won't work. We'll be simulating the CommonJS <code>module.exports</code> API though, so many modules should &quot;just work&quot; right off <a href="https://npmjs.com/" title="Link to Node Package Manager">npm</a>.</p> <p>The first thing we'll need is a table to store our module source(s) in. We really only need two columns to start: the module name (<code>module</code>), and the source code (<code>source</code>). To sweeten the deal we'll add an autoload column (<code>autoload</code>) that we'll use to dictate whether a module should be transparently available at all times.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">create table </span><span style="color:#50fa7b;">plv8_js_modules</span><span style="color:#f8f8f2;"> ( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">module </span><span style="font-style:italic;color:#8be9fd;">text</span><span style="color:#f8f8f2;"> unique </span><span style="color:#ff79c6;">primary key</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">autoload bool </span><span style="color:#ff79c6;">default </span><span style="color:#f8f8f2;">true, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">source </span><span style="font-style:italic;color:#8be9fd;">text </span><span class="lol"></span><span style="color:#f8f8f2;">); </span><span class="lol"></span></code></pre> <p>We'll need a function to handle wrapping the <code>require()</code> API, and ideally we'll want a cache for module loading so we're not pulling from a database table every time we require a module. The global <code>plv8</code> object has a few things we'll make use of here - it brings important functionality like executing statements, subtransactions, logging and more to the table. We'll be <code>eval()</code>ing the source for each module, but we wrap it in a function to ensure nothing leaks into the global scope. Our autoloading of certain modules also takes place in this function, just to prime the module cache for later use.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">create or replace function </span><span style="color:#50fa7b;">plv8_require</span><span style="color:#f8f8f2;">() </span><span class="lol"></span><span style="color:#f8f8f2;">returns void </span><span style="color:#ff79c6;">as</span><span style="color:#f8f8f2;"> $$ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">moduleCache </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> {}; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">load </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> function(key, source) { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">var module </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> {exports: {}}; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">eval(</span><span style="color:#f1fa8c;">&quot;(function(module, exports) {&quot; </span><span style="color:#ff79c6;">+</span><span style="color:#f8f8f2;"> source </span><span style="color:#ff79c6;">+ </span><span style="color:#f1fa8c;">&quot;; })&quot;</span><span style="color:#f8f8f2;">)(module, </span><span style="color:#bd93f9;">module</span><span style="color:#f8f8f2;">.</span><span style="color:#bd93f9;">exports</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">//</span><span style="color:#f8f8f2;"> store </span><span style="color:#ff79c6;">in</span><span style="color:#f8f8f2;"> cache </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">moduleCache[key] </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">module</span><span style="color:#f8f8f2;">.</span><span style="color:#bd93f9;">exports</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">return </span><span style="color:#bd93f9;">module</span><span style="color:#f8f8f2;">.</span><span style="color:#bd93f9;">exports</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">}; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">require </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> function(module) { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">if(moduleCache[module]) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">return moduleCache[module]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">var rows </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">plv8</span><span style="color:#f8f8f2;">.</span><span style="color:#bd93f9;">execute</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;select source from plv8_js_modules where module = $1&quot;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">[module] </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">if(</span><span style="color:#bd93f9;">rows</span><span style="color:#f8f8f2;">.</span><span style="color:#bd93f9;">length </span><span style="color:#ff79c6;">=== </span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">) { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">plv8</span><span style="color:#f8f8f2;">.</span><span style="color:#bd93f9;">elog</span><span style="color:#f8f8f2;">(NOTICE, </span><span style="color:#f1fa8c;">&#39;Could not load module: &#39; </span><span style="color:#ff79c6;">+</span><span style="color:#f8f8f2;"> module); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">return </span><span style="color:#ff79c6;">null</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">return load(module, rows[</span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">].source); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">}; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">//</span><span style="color:#f8f8f2;"> Grab modules worth auto</span><span style="color:#ff79c6;">-</span><span style="color:#f8f8f2;">loading at context start </span><span style="color:#ff79c6;">and</span><span style="color:#f8f8f2;"> let them cache </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">var query </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&#39;select module, source from plv8_js_modules where autoload = true&#39;</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">plv8</span><span style="color:#f8f8f2;">.</span><span style="color:#bd93f9;">execute</span><span style="color:#f8f8f2;">(query).forEach(function(row) { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">load(</span><span style="color:#bd93f9;">row</span><span style="color:#f8f8f2;">.</span><span style="color:#bd93f9;">module</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">row</span><span style="color:#f8f8f2;">.</span><span style="color:#bd93f9;">source</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">}); </span><span class="lol"></span><span style="color:#f8f8f2;">$$ language plv8; </span><span class="lol"></span></code></pre> <p>Now in terms of using this, we have that dangling context problem to consider - how do we make sure that <code>require()</code> is available to each PL/v8 function that needs it? Well, it just so happens that PL/v8 supports setting a specific function to run before any other functions run. We can use this hook to bootstrap our environment - while ordinarily you could set it in your config files, you don't have access to those on Compose. We can, however, <code>SET</code> this value every time we open a connection. As long as you do this prior to any function call (including <code>CREATE FUNCTION</code> itself) you'll have access to <code>require()</code>.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;">-- PL/v8 supports a &quot;start proc&quot; variable that can act as a bootstrap </span><span class="lol"></span><span style="color:#6272a4;">-- function. Note the lack of quotes! </span><span class="lol"></span><span style="color:#ff79c6;">SET </span><span style="color:#bd93f9;">plv8</span><span style="color:#f8f8f2;">.</span><span style="color:#bd93f9;">start_proc </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> plv8_require; </span><span class="lol"></span></code></pre> <p>Let's try it out by throwing together a module that implements the Fisher-Yates shuffle algorithm - we'll name the module &quot;shuffle&quot;, to keep things simple, and go ahead and set it to autoload.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">insert into</span><span style="color:#f8f8f2;"> plv8_js_modules (module, autoload, source) </span><span style="color:#ff79c6;">values</span><span style="color:#f8f8f2;"> (</span><span style="color:#f1fa8c;">&#39;shuffle&#39;</span><span style="color:#f8f8f2;">, true, </span><span style="color:#f1fa8c;">&#39; </span><span class="lol"></span><span style="color:#f1fa8c;"> </span><span style="color:#f1fa8c;">module.exports = function(arr) { </span><span class="lol"></span><span style="color:#f1fa8c;"> </span><span style="color:#f1fa8c;">var length = arr.length, </span><span class="lol"></span><span style="color:#f1fa8c;"> </span><span style="color:#f1fa8c;">shuffled = Array(length); </span><span class="lol"></span><span style="color:#f1fa8c;"> </span><span style="color:#f1fa8c;"> </span><span class="lol"></span><span style="color:#f1fa8c;"> </span><span style="color:#f1fa8c;">for(var i = 0, rand; i &lt; length; i++) { </span><span class="lol"></span><span style="color:#f1fa8c;"> </span><span style="color:#f1fa8c;">rand = Math.floor(Math.random() * (i + 1)); </span><span class="lol"></span><span style="color:#f1fa8c;"> </span><span style="color:#f1fa8c;">if(rand !== i) </span><span class="lol"></span><span style="color:#f1fa8c;"> </span><span style="color:#f1fa8c;">shuffled[i] = shuffled[rand]; </span><span class="lol"></span><span style="color:#f1fa8c;"> </span><span style="color:#f1fa8c;">shuffled[rand] = arr[i]; </span><span class="lol"></span><span style="color:#f1fa8c;"> </span><span style="color:#f1fa8c;">} </span><span class="lol"></span><span style="color:#f1fa8c;"> </span><span style="color:#f1fa8c;"> </span><span class="lol"></span><span style="color:#f1fa8c;"> </span><span style="color:#f1fa8c;">return shuffled; </span><span class="lol"></span><span style="color:#f1fa8c;"> </span><span style="color:#f1fa8c;">}; </span><span class="lol"></span><span style="color:#f1fa8c;">&#39;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span></code></pre> <p>Now we should be able to <code>require()</code> this! We can try it immediately - a simple table of people and a super readable <code>random_person()</code> function works well.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">create table </span><span style="color:#50fa7b;">people</span><span style="color:#f8f8f2;"> ( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">id </span><span style="font-style:italic;color:#8be9fd;">serial</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">name </span><span style="font-style:italic;color:#8be9fd;">varchar</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">255</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">age </span><span style="font-style:italic;color:#8be9fd;">int </span><span class="lol"></span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">insert into</span><span style="color:#f8f8f2;"> people (name, age) </span><span style="color:#ff79c6;">values </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;Ryan&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">27</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;Daniel&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">25</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;Andrew&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">23</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;Sam&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">22</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">create or replace function </span><span style="color:#50fa7b;">random_person</span><span style="color:#f8f8f2;">() </span><span class="lol"></span><span style="color:#f8f8f2;">returns json </span><span style="color:#ff79c6;">as</span><span style="color:#f8f8f2;"> $$ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">var shuffle </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> require(</span><span style="color:#f1fa8c;">&#39;shuffle&#39;</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">people </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">plv8</span><span style="color:#f8f8f2;">.</span><span style="color:#bd93f9;">execute</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;select id, name, age from people&#39;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">return shuffle(people); </span><span class="lol"></span><span style="color:#f8f8f2;">$$ language plv8; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">select</span><span style="color:#f8f8f2;"> random_person(); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">-- Example Response: </span><span class="lol"></span><span style="color:#6272a4;">-- [{ </span><span class="lol"></span><span style="color:#6272a4;">-- &quot;id&quot;: 3, </span><span class="lol"></span><span style="color:#6272a4;">-- &quot;name&quot;: &quot;Andrew&quot;, </span><span class="lol"></span><span style="color:#6272a4;">-- &quot;age&quot;: 23 </span><span class="lol"></span><span style="color:#6272a4;">-- }, { </span><span class="lol"></span><span style="color:#6272a4;">-- &quot;id&quot;: 1, </span><span class="lol"></span><span style="color:#6272a4;">-- &quot;name&quot;: &quot;Ryan&quot;, </span><span class="lol"></span><span style="color:#6272a4;">-- &quot;age&quot;:27 </span><span class="lol"></span><span style="color:#6272a4;">-- }, { </span><span class="lol"></span><span style="color:#6272a4;">-- &quot;id&quot;: 4, </span><span class="lol"></span><span style="color:#6272a4;">-- &quot;name&quot;: &quot;Sam&quot;, </span><span class="lol"></span><span style="color:#6272a4;">-- &quot;age&quot;: 22 </span><span class="lol"></span><span style="color:#6272a4;">-- }, { </span><span class="lol"></span><span style="color:#6272a4;">-- &quot;id&quot;: 2, </span><span class="lol"></span><span style="color:#6272a4;">-- &quot;name&quot;: &quot;Daniel&quot;, </span><span class="lol"></span><span style="color:#6272a4;">-- &quot;age&quot;: 25 </span><span class="lol"></span><span style="color:#6272a4;">-- }] </span><span class="lol"></span></code></pre> <p>See how clean that becomes? A shuffle algorithm is only one application - modules like <a href="https://github.com/lodash/lodash" title="Link to lodash">lodash</a> are a prime candidate to check out for using here.</p> <h2 id="what-are-you-waiting-for">What Are You Waiting For?</h2> <p>Writing PostgreSQL functions in JavaScript can provide a few wins - if your stack is primarily JavaScript, there's less context-switching involved when dealing with your database. You can reap the benefits of Document-oriented, Schema-less databases while leaving yourself the option to get relational as necessary, and the native support for JSON marries perfectly with JavaScript. Modules can tie it all together and provide a method for organizing code that makes sense - you'll feel right at home.</p> <p>Even more in-depth documentation on PL/v8 can be found over on the <a href="http://pgxn.org/dist/plv8/doc/plv8.html" title="Link to PL/v8 Docs">official docs</a>. Try it out today!</p> RedNoise, a Django-centric WhiteNoise addon Thu, 04 Jun 2015 00:00:00 +0000 https://rymc.io/blog/2015/rednoise-a-django-centric-whitenoise-addon/ https://rymc.io/blog/2015/rednoise-a-django-centric-whitenoise-addon/ <p>I've developed with Django for a number of years - out of all the frameworks I've ever used, it strikes the best balance between &quot;let me get stuff done&quot; and &quot;don't try to provide me too much, get out of my way when I say so&quot;. I was ecstatic when <a href="https://whitenoise.readthedocs.org/en/latest/">WhiteNoise</a> was released, as it solved a very annoying part of the development process with Django - static files. Rather than uploading things to S3 (ala the old Storages route) it's now just easier to toss CloudFront in front of your files and let uwsgi serve it up. Since WhiteNoise debuted, I've used it on a few different projects, and over time I found myself wanting a few things that it lacked - django-rednoise provides them.</p> <p>Feel free to read on for details, <a href="https://github.com/ryanmcgrath/django-rednoise/%3E">or go find it on GitHub!</a> I published it as a separate module as the goals differ from the established WhiteNoise goals, but I'd be absolutely open to it being merged or pulled from.</p> <h2 id="debug-please">DEBUG Please</h2> <p>WhiteNoise has a Django-specific module that you can use, but it's essentially geared towards production use-cases. I tend to prefer having a development setup that mimics my production setup; patching Django urls to load static or media files in development just feels clunky to me.</p> <p>RedNoise respects whether Django is in <code>DEBUG</code> mode or not. If <code>DEBUG</code> is set to <code>True</code>, RedNoise will mimic Django's default static-file loading pattern, so you don't need to reload the entire server just to debug some frontend issues.</p> <h2 id="serve-media-files">Serve Media Files</h2> <p>WhiteNoise doesn't support serving user-uploaded media files, but I wound up having to throw together a CMS at one point and ran into this limitation. Content authors and editors wanted to be able to upload photos, but I didn't want to have to keep a separate S3 bucket for it all. RedNoise will serve media files, hooking into the associated settings.py parameters to make it &quot;just work&quot;.</p> <h2 id="should-you-use-it">Should You Use It?</h2> <p>I've run it for a bit with no real issues; I would say that whatever you do, tossing a CDN in front of it all is pretty efficient. Provided you do that, the CDN should absorb most of the requests, leaving your server to do its thing.</p> <p>If you use WhiteNoise, and wish you had the above features, give django-rednoise a shot. It's just a <code>pip install</code> away.</p> Recording Live Audio Streams on iOS Wed, 08 Jan 2014 00:00:00 +0000 https://rymc.io/blog/2014/recording-live-audio-streams-on-ios/ https://rymc.io/blog/2014/recording-live-audio-streams-on-ios/ <p>Some people might consider this strange, but I'm still a fan of radio. It's not my 24/7 music source, but I find myself getting annoyed with algorithms trying to predict music that I'd like and ultimately failing. In addition to music, you've got news and sports radio - getting a large mix is fun.</p> <p>These days tons of radio stations have moved their streams to be listen-able online, and recently I found myself wishing I had something that I could use to record these radio streams on a whim. My main device is an iPhone 5; sadly, I didn't find anything particularly pleasing or enjoyable to use in the App Store, so I set out to see about throwing together my own. What follows is a breakdown of a (relatively) easy way to download live audio streams using AVFoundation and Core Audio. It assumes you've got a working knowledge of Objective C, as well as a working knowledge in terms of building iOS apps in general.</p> <h2 id="disclaimer">Disclaimer</h2> <p>This code is here as an example and nothing more. In recording anything from a live stream that might possibly be copyrighted and/or legally protected you should ensure that you're allowed to do so. This post and the author holds no responsibility for what you do with the content hereafter.</p> <h2 id="update-2015">Update 2015</h2> <p>In the comments spg has shown a case wherein <code>.m3u8</code> files probably won't work with this method due to how they differ in playback. It would be cool to see if there's a way around this, but I don't have time at the moment to investigate sadly. Get in touch if you figure this out!</p> <h2 id="update-2016">Update 2016</h2> <p>I've sinced removed comments from my site due to their neglect. Feel free to email me with questions about this though!</p> <h2 id="audio-streaming">Audio Streaming</h2> <p>The basics of app structure/building aside, the first concern is the actual streaming of the audio. There are a myriad of factors here that are honestly just a massive chore to deal with - you've got network speed issues, you could have the network drop and pick back up, the list is endless. A cursory Google search tends to show that this is where people tripped themselves up a lot - the general consensus seems to be that relying on <code>AVPlayer</code> can't work for remote data, as there's seemingly no way to get at the <code>AudioBuffer</code> data - it only works with local files. This leads to everyone reinventing the wheel with the (arguably painful) Core Audio APIs.</p> <p>Here's the thing: I had no desire to deal with any of that. They are certainly <em>interesting</em> problems, but right now they're just in the way. If possible I would much rather use <code>AVPlayer</code> and let Apple handle all the intricacies/edge cases in regards to playback.</p> <h2 id="enter-mtaudioprocessingtap">Enter MTAudioProcessingTap</h2> <p>Now, <code>MTAudioProcessingTap</code> is no secret - it's what you'd use to, say, visualize audio data for local files played through <code>AVPlayer</code>. If you're not familiar with it, <a href="http://chritto.wordpress.com/2013/01/07/processing-avplayers-audio-with-mtaudioprocessingtap/">this is a pretty good writeup</a>. The general gist is that you create a tap, set up an audio mix for a track, and set it on the <code>AVPlayerItem</code> of the audio player. The problem with remote data is that <code>AVPlayer</code> just works differently there - you don't have access to any <code>AVAssetTrack</code>s with which to make an <code>AVMutableAudioMix</code>, presumably because streaming is just a different setup entirely behind the scenes.</p> <p><strong>However</strong>, if we look a bit further, after the streaming starts, you can access an <code>AVAssetTrack</code> on the player. Now it's a simple matter of <code>KVObserve</code>-ing the status of the player, grabbing the track when it's available, and setting up our stream handler. Given that it's <code>MTAudioProcessingTap</code> you could do any number of things here, but personally I just needed to pass the raw audio data through.</p> <p>Unlike other articles on this site, I'm opting to just include three gists at the bottom that act as a full (mini) library, along with example usage. There's a bit of Core Audio involved, but it's nothing too annoying - hopefully this helps anyone who's been wondering how to handle this. This code isn't meant to be drop-in-good-to-go; with this type of project it's kind of expected that you integrate it based on your own needs.</p> <p>If you have questions, you can always feel free to get in touch, be it email, Twitter, or GitHub. Generally down to help!</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">#import </span><span style="color:#f1fa8c;">&quot;GenericClassName.h&quot; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">@implementation </span><span style="color:#f8f8f2;">GenericClassName </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;">- (</span><span style="font-style:italic;color:#8be9fd;">void</span><span style="color:#f8f8f2;">)</span><span style="color:#50fa7b;">myMethodToStartRecording </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// Retain it, yo </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">streamer </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">[RMStreamer </span><span style="color:#8be9fd;">new</span><span style="color:#f8f8f2;">]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">[</span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">streamer</span><span style="color:#f8f8f2;"> recordStreamFromURL</span><span style="color:#ff79c6;">:</span><span style="color:#f8f8f2;">myURL onError</span><span style="color:#ff79c6;">:^</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#66d9ef;">NSError </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">error) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#8be9fd;">NSLog</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">@&quot;Ah junks some error happened: </span><span style="color:#bd93f9;">%@</span><span style="color:#f1fa8c;">&quot;</span><span style="color:#f8f8f2;">, error); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">]; </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;">- (</span><span style="font-style:italic;color:#8be9fd;">void</span><span style="color:#f8f8f2;">)</span><span style="color:#50fa7b;">myOtherMethodThatStopsStreaming </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">[</span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">streamer</span><span style="color:#f8f8f2;"> stopRecording]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// Do stuff </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">@end </span><span class="lol"></span></code></pre><pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">#import </span><span style="color:#f1fa8c;">&lt;AVFoundation/AVFoundation.h&gt; </span><span class="lol"></span><span style="color:#ff79c6;">#import </span><span style="color:#f1fa8c;">&lt;AudioToolbox/AudioToolbox.h&gt; </span><span class="lol"></span><span style="color:#ff79c6;">#import </span><span style="color:#f1fa8c;">&lt;Accelerate/Accelerate.h&gt; </span><span class="lol"></span><span style="color:#ff79c6;">#import </span><span style="color:#f1fa8c;">&lt;MediaToolbox/MediaToolbox.h&gt; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">typedef void </span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">^</span><span style="color:#f8f8f2;">RMStreamerErrorBlock)(</span><span style="font-style:italic;color:#66d9ef;">NSURL </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">@interface </span><span style="color:#f8f8f2;">RMStreamer : </span><span style="text-decoration:underline;font-style:italic;color:#8be9fd;">NSObject </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">@property </span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">nonatomic</span><span style="color:#f8f8f2;">, </span><span style="color:#ff79c6;">strong</span><span style="color:#f8f8f2;">, </span><span style="color:#ff79c6;">readonly</span><span style="color:#f8f8f2;">) </span><span style="font-style:italic;color:#66d9ef;">NSURL </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">outputURL; </span><span class="lol"></span><span style="color:#ff79c6;">@property </span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">nonatomic</span><span style="color:#f8f8f2;">, </span><span style="color:#ff79c6;">assign</span><span style="color:#f8f8f2;">, </span><span style="color:#ff79c6;">readonly</span><span style="color:#f8f8f2;">) ExtAudioFileRef captureFile; </span><span class="lol"></span><span style="color:#ff79c6;">@property </span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">nonatomic</span><span style="color:#f8f8f2;">, </span><span style="color:#ff79c6;">strong</span><span style="color:#f8f8f2;">, </span><span style="color:#ff79c6;">readonly</span><span style="color:#f8f8f2;">) AVPlayer </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">audioPlayer; </span><span class="lol"></span><span style="color:#ff79c6;">@property </span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">nonatomic</span><span style="color:#f8f8f2;">, </span><span style="color:#ff79c6;">copy</span><span style="color:#f8f8f2;">) RMStreamerErrorBlock onError; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;">- (</span><span style="font-style:italic;color:#8be9fd;">void</span><span style="color:#f8f8f2;">)</span><span style="color:#50fa7b;">recordStreamFromURL:</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#66d9ef;">NSURL </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">)</span><span style="font-style:italic;color:#ffb86c;">url </span><span style="color:#50fa7b;">onError:</span><span style="color:#f8f8f2;">(RMStreamerErrorBlock)</span><span style="font-style:italic;color:#ffb86c;">error</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;">- (</span><span style="font-style:italic;color:#8be9fd;">void</span><span style="color:#f8f8f2;">)</span><span style="color:#50fa7b;">stopRecording</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">@end </span><span class="lol"></span></code></pre><pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">#import </span><span style="color:#f1fa8c;">&quot;RMStreamer.h&quot; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">// Allows us to get access to our RMStreamer instance in the process method below~ </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">void </span><span style="color:#50fa7b;">init</span><span style="color:#f8f8f2;">(MTAudioProcessingTapRef </span><span style="font-style:italic;color:#ffb86c;">tap</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#8be9fd;">void </span><span style="color:#ff79c6;">*</span><span style="font-style:italic;color:#ffb86c;">clientInfo</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#8be9fd;">void </span><span style="color:#ff79c6;">**</span><span style="font-style:italic;color:#ffb86c;">tapStorageOut</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">tapStorageOut </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> clientInfo; </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">void </span><span style="color:#50fa7b;">finalize</span><span style="color:#f8f8f2;">(MTAudioProcessingTapRef </span><span style="font-style:italic;color:#ffb86c;">tap</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// Self explanatory - any final operations you need to handle. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// I didn&#39;t need much in the way of anything here. </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">// We defer the creation of our output file until this is called - by doing so, </span><span class="lol"></span><span style="color:#6272a4;">// we don&#39;t need to guess at the format it comes in as. </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">void </span><span style="color:#50fa7b;">prepare</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">MTAudioProcessingTapRef </span><span style="font-style:italic;color:#ffb86c;">tap</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">CMItemCount </span><span style="font-style:italic;color:#ffb86c;">maxFrames</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">const</span><span style="color:#f8f8f2;"> AudioStreamBasicDescription </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">processingFormat </span><span class="lol"></span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#8be9fd;">NSLog</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">@&quot;Preparing the Audio Tap Processor&quot;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">RMStreamer </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">streamer </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">(__bridge RMStreamer </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">) </span><span style="color:#50fa7b;">MTAudioProcessingTapGetStorage</span><span style="color:#f8f8f2;">(tap); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">[streamer </span><span style="color:#8be9fd;">createOutputFileForStreamWithFormat:</span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">processingFormat]; </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">void </span><span style="color:#50fa7b;">unprepare</span><span style="color:#f8f8f2;">(MTAudioProcessingTapRef </span><span style="font-style:italic;color:#ffb86c;">tap</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// Self explanatory - if you have things you need done here, do them. </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">void </span><span style="color:#50fa7b;">process</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">MTAudioProcessingTapRef </span><span style="font-style:italic;color:#ffb86c;">tap</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">CMItemCount </span><span style="font-style:italic;color:#ffb86c;">numberFrames</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">MTAudioProcessingTapFlags </span><span style="font-style:italic;color:#ffb86c;">flags</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">AudioBufferList </span><span style="color:#ff79c6;">*</span><span style="font-style:italic;color:#ffb86c;">bufferListInOut</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">CMItemCount </span><span style="color:#ff79c6;">*</span><span style="font-style:italic;color:#ffb86c;">numberFramesOut</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">MTAudioProcessingTapFlags </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">flagsOut </span><span class="lol"></span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">RMStreamer </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">streamer </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">(__bridge RMStreamer </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">) </span><span style="color:#50fa7b;">MTAudioProcessingTapGetStorage</span><span style="color:#f8f8f2;">(tap); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">OSStatus</span><span style="color:#f8f8f2;"> err </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">MTAudioProcessingTapGetSourceAudio</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">tap, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">numberFrames, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">bufferListInOut, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">flagsOut, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">NULL</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">numberFramesOut </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(err) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// There was an error getting audio buffers from the stream. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// React accordingly in your application! </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">streamer</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">onError</span><span style="color:#f8f8f2;">([</span><span style="font-style:italic;color:#66d9ef;">NSError </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">errorWithDomain</span><span style="color:#ff79c6;">:</span><span style="color:#f8f8f2;">NSOSStatusErrorDomain </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">code</span><span style="color:#ff79c6;">:</span><span style="color:#f8f8f2;">err </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">userInfo</span><span style="color:#ff79c6;">:</span><span style="color:#bd93f9;">nil </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">]); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">OSStatus</span><span style="color:#f8f8f2;"> f </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">ExtAudioFileWrite</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">streamer</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">captureFile</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">numberFramesOut, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">bufferListInOut </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(f) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// Writing to the audio file failed for some reason. Check why and react. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">streamer</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">onError</span><span style="color:#f8f8f2;">([</span><span style="font-style:italic;color:#66d9ef;">NSError </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">errorWithDomain</span><span style="color:#ff79c6;">:</span><span style="color:#f8f8f2;">NSOSStatusErrorDomain </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">code</span><span style="color:#ff79c6;">:</span><span style="color:#f8f8f2;">err </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">userInfo</span><span style="color:#ff79c6;">:</span><span style="color:#bd93f9;">nil </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">]); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">@implementation </span><span style="color:#f8f8f2;">RMStreamer </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;">- (</span><span style="font-style:italic;color:#8be9fd;">void</span><span style="color:#f8f8f2;">)</span><span style="color:#50fa7b;">recordStreamFromURL:</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#66d9ef;">NSURL </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">)</span><span style="font-style:italic;color:#ffb86c;">url </span><span style="color:#50fa7b;">onError:</span><span style="color:#f8f8f2;">(RMStreamerErrorBlock)</span><span style="font-style:italic;color:#ffb86c;">error </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">_onError </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> error; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">AVPlayerItem </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">item </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">[AVPlayerItem </span><span style="color:#8be9fd;">playerItemWithURL:</span><span style="color:#f8f8f2;">url]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">_audioPlayer </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">[AVPlayer </span><span style="color:#8be9fd;">playerWithPlayerItem:</span><span style="color:#f8f8f2;">item]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// Watch the status property - when this is good to go, we can access the </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// underlying AVAssetTrack we need. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">[item </span><span style="color:#8be9fd;">addObserver:</span><span style="color:#bd93f9;">self </span><span style="color:#8be9fd;">forKeyPath:</span><span style="color:#f1fa8c;">@&quot;status&quot; </span><span style="color:#8be9fd;">options:</span><span style="color:#bd93f9;">0 </span><span style="color:#8be9fd;">context:</span><span style="color:#bd93f9;">nil</span><span style="color:#f8f8f2;">]; </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;">- (</span><span style="font-style:italic;color:#8be9fd;">void</span><span style="color:#f8f8f2;">)</span><span style="color:#50fa7b;">observeValueForKeyPath:</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#66d9ef;">NSString </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">)</span><span style="font-style:italic;color:#ffb86c;">keyPath </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">ofObject:</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#8be9fd;">id</span><span style="color:#f8f8f2;">)</span><span style="font-style:italic;color:#ffb86c;">object </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">change:</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#66d9ef;">NSDictionary </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">)</span><span style="font-style:italic;color:#ffb86c;">change </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">context:</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#8be9fd;">void </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">)</span><span style="font-style:italic;color:#ffb86c;">context </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">!</span><span style="color:#f8f8f2;">[keyPath </span><span style="color:#8be9fd;">isEqualToString:</span><span style="color:#f1fa8c;">@&quot;status&quot;</span><span style="color:#f8f8f2;">]) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">AVPlayerItem </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">item </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">(AVPlayerItem </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">)object; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(item</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">status </span><span style="color:#ff79c6;">!=</span><span style="color:#f8f8f2;"> AVPlayerItemStatusReadyToPlay) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">AVURLAsset </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">asset </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">(AVURLAsset </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">)item</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">asset</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">AVAssetTrack </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">audioTrack </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">[asset </span><span style="color:#8be9fd;">tracksWithMediaType:</span><span style="color:#f8f8f2;">AVMediaTypeAudio][</span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">[</span><span style="color:#bd93f9;">self </span><span style="color:#8be9fd;">beginRecordingAudioFromTrack:</span><span style="color:#f8f8f2;">audioTrack]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">[_audioPlayer </span><span style="color:#8be9fd;">play</span><span style="color:#f8f8f2;">]; </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;">- (</span><span style="font-style:italic;color:#8be9fd;">void</span><span style="color:#f8f8f2;">)</span><span style="color:#50fa7b;">beginRecordingAudioFromTrack:</span><span style="color:#f8f8f2;">(AVAssetTrack </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">)</span><span style="font-style:italic;color:#ffb86c;">audioTrack </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// Configure an MTAudioProcessingTap to handle things. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">MTAudioProcessingTapRef tap; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">MTAudioProcessingTapCallbacks callbacks; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">callbacks</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">version </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">kMTAudioProcessingTapCallbacksVersion_0</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">callbacks</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">clientInfo </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">(__bridge </span><span style="font-style:italic;color:#8be9fd;">void </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">)(</span><span style="color:#bd93f9;">self</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">callbacks</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">init </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> init; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">callbacks</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">prepare </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> prepare; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">callbacks</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">process </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> process; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">callbacks</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">unprepare </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> unprepare; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">callbacks</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">finalize </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> finalize; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">OSStatus</span><span style="color:#f8f8f2;"> err </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">MTAudioProcessingTapCreate</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">kCFAllocatorDefault</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">&amp;</span><span style="color:#f8f8f2;">callbacks, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">kMTAudioProcessingTapCreationFlag_PostEffects</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">&amp;</span><span style="color:#f8f8f2;">tap </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(err) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#8be9fd;">NSLog</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">@&quot;Unable to create the Audio Processing Tap </span><span style="color:#bd93f9;">%ld</span><span style="color:#f1fa8c;">&quot;</span><span style="color:#f8f8f2;">, err); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">_onError</span><span style="color:#f8f8f2;">([</span><span style="font-style:italic;color:#66d9ef;">NSError </span><span style="color:#8be9fd;">errorWithDomain:</span><span style="color:#f8f8f2;">NSOSStatusErrorDomain </span><span style="color:#8be9fd;">code:</span><span style="color:#f8f8f2;">err </span><span style="color:#8be9fd;">userInfo:</span><span style="color:#bd93f9;">nil</span><span style="color:#f8f8f2;">]); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// Create an AudioMix and assign it to our currently playing &quot;item&quot;, which </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// is just the stream itself. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">AVMutableAudioMix </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">audioMix </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">[AVMutableAudioMix </span><span style="color:#8be9fd;">audioMix</span><span style="color:#f8f8f2;">]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">AVMutableAudioMixInputParameters </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">inputParams </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">[AVMutableAudioMixInputParameters </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">audioMixInputParametersWithTrack</span><span style="color:#ff79c6;">:</span><span style="color:#f8f8f2;">audioTrack]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">inputParams</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">audioTapProcessor </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> tap; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">audioMix</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">inputParameters </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> @[inputParams]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">_audioPlayer</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">currentItem</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">audioMix </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> audioMix; </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">// This you&#39;ll want to customize to your needs - it&#39;s pulled from my </span><span class="lol"></span><span style="color:#6272a4;">// own project and quickly revamped. Good luck! </span><span class="lol"></span><span style="color:#f8f8f2;">- (</span><span style="font-style:italic;color:#8be9fd;">void</span><span style="color:#f8f8f2;">)</span><span style="color:#50fa7b;">createOutputFileForStreamWithFormat:</span><span style="color:#f8f8f2;">(AudioStreamBasicDescription)</span><span style="font-style:italic;color:#ffb86c;">clientFormat </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// This is an incredibly generic file path. Customize as need be. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">NSError </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">nserr </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">nil</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">NSFileManager </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">fm </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">[</span><span style="font-style:italic;color:#66d9ef;">NSFileManager </span><span style="color:#8be9fd;">defaultManager</span><span style="color:#f8f8f2;">]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">NSArray </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">paths </span><span style="color:#ff79c6;">= </span><span style="color:#8be9fd;">NSSearchPathForDirectoriesInDomains</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6be5fd;">NSDocumentDirectory</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6be5fd;">NSUserDomainMask</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">YES </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">NSString </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">documentsDirectory </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">[paths </span><span style="color:#8be9fd;">objectAtIndex:</span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">NSString </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">dir </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">[documentsDirectory </span><span style="color:#8be9fd;">stringByAppendingPathComponent:</span><span style="color:#f1fa8c;">@&quot;rm_streamer&quot;</span><span style="color:#f8f8f2;">]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">!</span><span style="color:#f8f8f2;">[fm </span><span style="color:#8be9fd;">fileExistsAtPath:</span><span style="color:#f8f8f2;">dir]) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">[fm </span><span style="color:#8be9fd;">createDirectoryAtPath:</span><span style="color:#f8f8f2;">dir </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#8be9fd;">withIntermediateDirectories:</span><span style="color:#bd93f9;">YES </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#8be9fd;">attributes:</span><span style="color:#bd93f9;">nil </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#8be9fd;">error:</span><span style="color:#ff79c6;">&amp;</span><span style="color:#f8f8f2;">nserr]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">NSTimeInterval</span><span style="color:#f8f8f2;"> timestamp </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">[[</span><span style="font-style:italic;color:#66d9ef;">NSDate </span><span style="color:#8be9fd;">date</span><span style="color:#f8f8f2;">] timeIntervalSince1970]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">NSString </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">output </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">[</span><span style="font-style:italic;color:#66d9ef;">NSString </span><span style="color:#8be9fd;">stringWithFormat:</span><span style="color:#f1fa8c;">@&quot;</span><span style="color:#bd93f9;">%@</span><span style="color:#f1fa8c;">/</span><span style="color:#bd93f9;">%f</span><span style="color:#f1fa8c;">.caf&quot;</span><span style="color:#f8f8f2;">, dir, timestamp]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">unlink</span><span style="color:#f8f8f2;">([output </span><span style="color:#8be9fd;">UTF8String</span><span style="color:#f8f8f2;">]); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">_outputURL </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">[</span><span style="font-style:italic;color:#66d9ef;">NSURL </span><span style="color:#8be9fd;">fileURLWithPath:</span><span style="color:#f8f8f2;">]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">OSStatus</span><span style="color:#f8f8f2;"> err </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">ExtAudioFileCreateWithURL</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">(__bridge CFURLRef)_outputURL, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">kAudioFileCAFType</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">&amp;</span><span style="color:#f8f8f2;">clientFormat, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">NULL</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">kAudioFileFlags_EraseFile</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">&amp;</span><span style="color:#f8f8f2;">_captureFile </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(err) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">_onError</span><span style="color:#f8f8f2;">([</span><span style="font-style:italic;color:#66d9ef;">NSError </span><span style="color:#8be9fd;">errorWithDomain:</span><span style="color:#f8f8f2;">NSOSStatusErrorDomain </span><span style="color:#8be9fd;">code:</span><span style="color:#f8f8f2;">err </span><span style="color:#8be9fd;">userInfo:</span><span style="color:#bd93f9;">nil</span><span style="color:#f8f8f2;">]); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// An error occurred with creating the file. Go figure. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// This setting is... annoying. Some devices will randomly crap out </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// if the hardware audio support is wonky. Change as you need to. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">UInt32</span><span style="color:#f8f8f2;"> codecManf </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">kAppleHardwareAudioCodecManufacturer</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// UInt32 codecManf = kAppleSoftwareAudioCodecManufacturer; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">err </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">ExtAudioFileSetProperty</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">_captureFile, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">kExtAudioFileProperty_CodecManufacturer</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">sizeof</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#66d9ef;">UInt32</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">&amp;</span><span style="color:#f8f8f2;">codecManf </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(err) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">_onError</span><span style="color:#f8f8f2;">([</span><span style="font-style:italic;color:#66d9ef;">NSError </span><span style="color:#8be9fd;">errorWithDomain:</span><span style="color:#f8f8f2;">NSOSStatusErrorDomain </span><span style="color:#8be9fd;">code:</span><span style="color:#f8f8f2;">err </span><span style="color:#8be9fd;">userInfo:</span><span style="color:#bd93f9;">nil</span><span style="color:#f8f8f2;">]); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">err </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">ExtAudioFileSetProperty</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">_captureFile, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">kExtAudioFileProperty_ClientDataFormat</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">sizeof</span><span style="color:#f8f8f2;">(clientFormat), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">&amp;</span><span style="color:#f8f8f2;">clientFormat </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(err) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">_onError</span><span style="color:#f8f8f2;">([</span><span style="font-style:italic;color:#66d9ef;">NSError </span><span style="color:#8be9fd;">errorWithDomain:</span><span style="color:#f8f8f2;">NSOSStatusErrorDomain </span><span style="color:#8be9fd;">code:</span><span style="color:#f8f8f2;">err </span><span style="color:#8be9fd;">userInfo:</span><span style="color:#bd93f9;">nil</span><span style="color:#f8f8f2;">]); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">// Getting an MTAudioProcessingTapRef to properly release is kind of annoying in general. </span><span class="lol"></span><span style="color:#6272a4;">// This should more or less handle it~ </span><span class="lol"></span><span style="color:#f8f8f2;">- (</span><span style="font-style:italic;color:#8be9fd;">void</span><span style="color:#f8f8f2;">)</span><span style="color:#50fa7b;">stopRecording </span><span class="lol"></span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">[_audioPlayer </span><span style="color:#8be9fd;">pause</span><span style="color:#f8f8f2;">]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">ExtAudioFileDispose</span><span style="color:#f8f8f2;">(_captureFile); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">[_audioPlayer</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">currentItem</span><span style="color:#f8f8f2;"> removeObserver</span><span style="color:#ff79c6;">:</span><span style="color:#bd93f9;">self</span><span style="color:#f8f8f2;"> forKeyPath</span><span style="color:#ff79c6;">:</span><span style="color:#f1fa8c;">@&quot;status&quot;</span><span style="color:#f8f8f2;">]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">AVMutableAudioMixInputParameters </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">params </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">(AVMutableAudioMixInputParameters </span><span style="color:#ff79c6;">*</span><span style="color:#f8f8f2;">) _player</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">currentItem</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">audioMix</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">inputParameters</span><span style="color:#f8f8f2;">[</span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">MTAudioProcessingTapRef tap </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> params</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">audioTapProcessor</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">_player</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">currentItem</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">audioMix </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">nil</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">_player </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">nil</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">CFRelease</span><span style="color:#f8f8f2;">(tap); </span><span class="lol"></span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">@end </span><span class="lol"></span></code></pre> We are the New Blue Collar Wed, 10 Apr 2013 00:00:00 +0000 https://rymc.io/blog/2013/we-are-the-new-blue-collar/ https://rymc.io/blog/2013/we-are-the-new-blue-collar/ <p>Over the past year, there's been an explosion of interest in terms of teaching children how to program in schools. This is a great movement; the world is becoming more digital by the day, and as we automate away jobs with technology we certainly need more people to help maintain that automation. While it's not a 1:1 replacement for the jobs that get lost in the process, it's an industry that's not going anywhere. Organizations such as <a href="http://teamtreehouse.com/">Treehouse</a> or <a href="http://codecadamey.org/%3E">Codecademy</a>, and efforts such as <a href="http://www.code.org/">code.org</a> are helping educate others when it comes to the simple fact that these skills are useful in todays economy and cannot be ignored.</p> <p>The thing is, as someone who's lived and worked &quot;in the trenches&quot; for the past seven to eight years, the rhetoric coming out of some of these movements is somewhat alarming. I think this is doing a major disservice to the future generations of programmers, and I wanted to take a minute to explain what learning to program really does for you, career-wise.</p> <h2 id="the-hidden-side-of-programming">The Hidden Side of Programming</h2> <p>There's a few things these movements <em>don't</em> teach you about programming for a living.</p> <ul> <li>Simply learning to program does not automatically open doors for you - being successful in programming requires drive and hard work just like any other industry.</li> <li>A great deal of programming jobs are based in maintaining a code base, not &quot;writing the future&quot; or the latest amazing technology. Generally, we as programmers fear jobs that are just maintenance. It's not something that will be explained outside of the core of the industry - these jobs don't typically offer you advancement opportunities, they stifle your creativity and generally cause you to consider looking for a new job, often going against what can be (in many cases) a considerably decent salary.</li> <li>That aforementioned &quot;considerably decent&quot; salary will be driven down over time as more and more programmers become available. What's posited as a great reason to learn to code right now (money) won't necessarily be the case in a few years.</li> <li>Ultimately, what's often thrown around as you get further into this career is the fact that nobody wants to be doing this when they're 40 years old. At some point you either try to move up to management, or change careers (or retire, if you're lucky enough).</li> </ul> <p>If you're following along, this isn't really considered a long-term career path. As the world becomes more and more digital, being able to program becomes the same kind of skill as being good with your hands and being able to follow some directions. Yes, knowing how to control the devices and technology around you <em>enables</em> you to &quot;build out your dreams&quot; or &quot;own your destiny&quot;, but it's not a given - it requires levels of drive and dedication that some people aren't willing to put out there.</p> <p>The entire world cannot be artists, or it wouldn't function. If I talk to any programmer I know who actually enjoys doing this for work, they consider themselves an artist first and a data wrangler second; it's not a particularly enjoyable field to be in just for the money. These educational programs and efforts sell themselves as a way to realize your dreams, and while that's a lofty goal, it's just not a given because you can now write some code.</p> <h2 id="dissecting-some-lofty-quotes">Dissecting some Lofty Quotes</h2> <blockquote> "Coding is the American Dream. If you want to be the next Mark Zuckerberg or even want a high-paying job, those jobs are for programmers. … And yet the opportunity to be exposed to that is going to the top 10 percent, and that is just morally wrong." <br /><br /> Hadi Partovi, Founder, code.org </blockquote> <p>I would hesitate to refer to &quot;Coding&quot; as the &quot;American Dream&quot;. The role of a programmer is easily equated with any service-industry level job, albeit using more of your brain than your hands. You're still beholden to someone else at the end of the day, someone who controls when you get paid, someone who expects more and more with less and less. Programming is not a safe haven from the economic woes we face today.</p> <p>I believe the point made at the end of this quote to be very accurate, though: the top 10 percent shouldn't have the only access to essential knowledge deemed 'critical'. The thing is that this statement could be used for just about any industry out there today - Law, Medicine, even Craftsmanship; our society doesn't enable easy access to this information any more than it does with programming. It's a larger problem that needs to be solved.</p> <blockquote> "Coders change the world. They build new, amazing things faster than ever before. Anyone with imagination can learn to write code." <br /><br /> Jeff Wilke, SVP Consumer Business, Amazon.com (via code.org) </blockquote> <p>This is an incredibly bland quote that glazes over the fact that most programming jobs don't actually entail building new amazing things. It is very, very possible that you will wind up maintaining someone else's code or editing a trainwreck that the person before you created. It's not all innovation and aspirations.</p> <blockquote> "We need people from all walks of life to learn to code. Control your destiny, help your family, your community and your country." <br /><br /> Mitch Kapor, Founder, Lotus Development Corporation &amp; Partner, Kapor Capital (via code.org) </blockquote> <p>Another bland quote that implies that being able to program helps you control your destiny. <em>If you have a desire to learn</em>, you're in control of your destiny. Subscribing to a single discipline doesn't guarantee you success or even (necessarily) the chance to obtain your goals. It's a tool, a utility.</p> <blockquote> "Our programmers are our artists. They write beautiful code that millions of our travelers start to play with, to use, to help them travel better. It's a profound sense of power and satisfaction to have your work affect so many, so quickly, across the globe." <br /><br />Dara Khosrowshahi, CEO, Expedia, Inc. (via code.org) </blockquote> <p>I've worked with a ton of programmers in the past, at different companies of varying sizes (startups, bigger companies, freelancing/contracting), all over the world, and as a result I don't feel off the mark with this statement: you wind up not caring about this two weeks in. As long as you're creating for someone else and some company owns what you build, you begin to back off of the idea of going all in. You stop being wow'd by the idea that millions of people use what you wrote - you realize it's the network effect, and you could go work any number of other computer-related jobs and have the same thing. This isn't to say you're a passion-lacking worker-bee; you might have aspirations of helping people around the world, and that's a good thing I'm just illustrating what's not often shown.</p> <p>You'll see many deny this, though, as the tech industry has a problem with speaking out, and has what could arguably could be considered a <a href="http://thisjapaneselife.org/2011/03/09/japan-work-ethic/">very Japanese</a> <a href="http://www.japantimes.co.jp/news/2012/08/05/national/strong-work-ethic-is-no-path-to-better-standard-of-living/#.UWTstKtg9W0%3E">work ethic</a> - you live and die by the company you work for.</p> <p>Granted, we're not at the point of literally dying, but the hours and expected commitment have many sad comparisons. It's also not to say you have to fit that mould - it's just noting an environment you could easily wind up in. You're expected to care 110%, or they'll find someone else who can do what you won't.</p> <blockquote> "If you can program a computer, you can achieve your dreams. A computer doesn't care about your family background, your gender, just that you know how to code. But we're only teaching it in a small handful of schools, why?"<br /><br /> Dick Costolo, CEO, Twitter (via code.org) </blockquote> <p>Whether a computer cares about you or not is totally irrelevant in terms of whether you should learn how to program or not. We should be teaching it in more schools, but we should be teaching it for the right reasons - it's essential, and can be an art, but it is not some almighty savior.</p> <blockquote> "The extensive Treehouse library of step-by-step video courses and training exercises will give you a wide range of competitive, in-demand technology skills that will help you land your next dream job or build your startup idea"<br /><br /> Treehouse.com </blockquote> <blockquote> "Our mission is to bring affordable Technology education to people everywhere, in order to help them achieve their dreams and change the world."<br /><br /> Treehouse.com </blockquote> <p><a href="http://tech.co/coding-education-startup-treehouse-raises-7-million-2013-04%3E">The company that's going to get involved with schools and teach children how to program</a>... is presenting themselves as a way to land a dream job or become your own boss. The former isn't <em>always</em> the case, and the latter is in actuality such a rare occurrence that most people never have it. I <em>greatly</em> admire them for reducing the costs surrounding teaching this material, but positioning this as a world changing proposition for the people who choose to give it a shot is ultimately misleading.</p> <h2 id="so-should-i-learn-to-code">So Should I Learn to Code?</h2> <p>Yes. A million times yes. Just do it for yourself; don't do it for the money (because it won't always be there), and don't do it because people tell you it's job security - you can't possibly do it forever, and unless you plan your retirement correctly, good luck when you're 40. If you don't get into it as an art, then you're getting into nothing but new-age labor. You become a mechanic of the digital age.</p> <p>I believe programming should be taught in schools, and I 100% agree it's an essential skill in the world today. I do not, however, agree with the people who seem to believe that it's the most life changing thing you could do for yourself (of course, I encourage you to determine this for yourself). If we're not careful, we're going to do to the next generation what earlier ones did to us. We have a country of college graduates who were brought up on the idea that there'd be jobs for them, because it's what they were led to believe from those who came before them.</p> <p>They were wrong.</p> <p>Teaching programming is an important goal as we move forward, but it is not a fix for a broken educational system. If we want kids to succeed, we need to continue to focus on fixing that - rooting out the bad teachers and <em>paying teachers more</em>. We need to fix the problem in this country that people are going hundreds of thousands of dollars in debt just to have a decent paying job.</p> <p>The aforementioned programs don't fix these problems; they're simply training for the newest blue collar job.</p> <p><strong>Note:</strong> code.org is not full of bad quotes; I think it's a genuinely good idea as far as movements go. The quotes I pulled are indicative of the problem, a case of making the field out to be something it's not.</p> Where is Ryan now? (tl;dr: Quit myGengo) Sun, 08 Apr 2012 00:00:00 +0000 https://rymc.io/blog/2012/where-is-ryan-now-tldr-quit-mygengo/ https://rymc.io/blog/2012/where-is-ryan-now-tldr-quit-mygengo/ <p>Six months is quite a bit of time to leave a personal writing depot without updates, but when your life turns somewhat upside down you end up neglecting things you never really expected to. In the past few months, I've been asked &quot;what are you doing now?&quot; more times than I can count. So it's publicly noted (and so I stop getting emails...) I quit myGengo in February of 2012. They're mostly good people, and if you think you're a good fit you should go look at joining them or using their services. Now, as for what's next...</p> <p>I did yet another stint of traveling in the world, which has by far become the most interesting thing for me as of late. Switzerland (Zurich), England (London), Portugal (Lisbon/et al), Spain (Madrid, Bilbao, St. Sebastian), Germany (Stuttgart, Berlin)... a somewhat long list crammed into a few months. While it's fun and interesting, it's also still... somewhat unfulfilling. I've found that unless I'm working on something at all times, I'm just not happy. Remedying this has oddly led me down a path of figuring out what I want to be working on.</p> <h2 id="who-me-couldn-t-be">Who, me? Couldn't be...</h2> <p>I took a look at all the projects I've done over the past five or so years. In the beginning it was a lot of client work, with open source work dotting the landscape from time to time. As I gained more experience in terms of my overall programming skill, open source work took on more importance, and I eventually put down client work almost completely. Now, it's a new point - I've found that my brain is again interested in client work, although it's largely just one client - that being myself. It's become a game of justifying my output for someone else's vision or path.</p> <p>This isn't to say I can't see myself working for or with another company, party, person, or cause in the future, it just has to be the right fit. In the meantime, I'm mulling over various offers from companies around the world while working on personal projects that I'd like to release in the near future. It's a mix of things, as is my general style - you've got some design, some product, some iOS/Android App level work, and some basic web work.</p> <p>I'm also open to new contract work; if you've got an interesting project or idea, feel free to get in touch with me!</p> <h2 id="moving-forward">Moving Forward...</h2> <p>I want to check out Singapore. I've spent a bit too much time in Europe-ish regions recently; short of London, I could do with a bit of a break (seriously, people, open stores on Sunday or you will drive people like me away). I want to finish up the few mobile projects on my plate, and ultimately get back to updating this site more often, ideally with concrete projects to point to.</p> Using Javascript to control the Nintendo Wii Mon, 15 Aug 2011 00:00:00 +0000 https://rymc.io/blog/2011/using-javascript-to-control-the-nintendo-wii/ https://rymc.io/blog/2011/using-javascript-to-control-the-nintendo-wii/ <p>The Nintendo Wii was released around the end of 2006. That's a solid four years now; an amazing amount of time in the lifespan of a technological device these days. Often overlooked is the fact that the Wii has a web browser, which is in fact a build of Opera, offering support for canvas, CSS3, and more advanced aspects of HTML5. This should be incredible; why does nobody develop more for it?</p> <h2 id="the-ugly-truth">The Ugly Truth</h2> <p>The chief portion is the target market; with so many internet enabled devices laying around these days, the Wii's browsing experience is one that tends to fall a little short. This was further compounded by a small incident wherein, once the Wii's browser was released, an article went up on Opera's official website about responding to Wii remote commands in Javascript - Nintendo later demanded that they take it down, and to this date I've never seen any official reasoning put forth by either company.</p> <p>With that said, I don't think the Wii (and the browser therein) are 100% lost potential. One of my goals in life is to examine and improve the methods with which we teach programming to children, and I believe the Wii can work very well for these purposes. Typically, young children don't have their own computers, and from what I've found the recurring issue here is that when they're using their parents computers, they don't have creative freedom to do something that carries with it the idea of being possibly &quot;destructive&quot;.</p> <p>The Wii, on the other hand, is generally thought of as the &quot;kids&quot; device - it has a limited set of functionality that kids grasp pretty well right off the bat, and coupled with the concept of &quot;ownership&quot; they'd get out of this device it stands to reason they're more likely to experiment on it.</p> <p>There used to be various Wii javascript libraries/SDKs laying around, but most of them are woefully incomplete or no longer available. So with that all in mind, I set out to build a library that allows simple, easy, event-based interaction with the Wii's browser, hiding away the somewhat insane complexities of reading the remote codes.</p> <h2 id="enter-wii-js">Enter: wii-js</h2> <p>You can check out <a href="https://github.com/ryanmcgrath/wii-js">wii-js</a> over on GitHub; it's entirely open source and released under an MIT-style license. While the repository has in-depth documentation of library usage, check out the example below and see how simple this has become:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;">/** </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">* Two wii remote instances; first one tracks the first </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">* controller (held horizontally), the second one tracks </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">* the second controller (held vertically). </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">*/ </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#ffffff;">wiimote </span><span style="color:#ff79c6;">= new </span><span style="font-style:italic;color:#66d9ef;">Wii</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">Remote</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">1</span><span style="color:#f8f8f2;">, </span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">horizontal: </span><span style="color:#bd93f9;">true</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">wiimote2 </span><span style="color:#ff79c6;">= new </span><span style="font-style:italic;color:#66d9ef;">Wii</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">Remote</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">2</span><span style="color:#f8f8f2;">, </span><span style="color:#ffffff;">{</span><span style="color:#f8f8f2;">horizontal: </span><span style="color:#bd93f9;">false</span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">/** </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">* Listen for the &quot;A button pressed&quot; event on each wiimote. </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">*/ </span><span class="lol"></span><span style="color:#ffffff;">wiimote</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">when</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;pressed_a&#39;</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">() </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">alert</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;Wii Remote #1 pressed A!&#39;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ffffff;">wiimote2</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">when</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;pressed_a&#39;</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">() </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">alert</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;Wii Remote #2 pressed A!&#39;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">/** </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">* Start the system! </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">*/ </span><span class="lol"></span><span style="font-style:italic;color:#66d9ef;">Wii</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">listen</span><span style="color:#f8f8f2;">(); </span><span class="lol"></span></code></pre> <p>This example showcases how to set up basic controller instances, and respond to events that they fire. Most button combinations are supported (check the docs on GitHub for up-to-date event listings), but sadly this library can only work with the actual Wii-remote. Nintendo (or Opera, it's unknown who) opted not to provide controller mappings for anything else, be it classic controllers or Gamecube controllers. All Wii remote instances, with the exception of the first one, can get controller events from the nunchuk; there doesn't appear to be a way around this, which is nothing short of a shame.</p> <p>That said, even with these limitations, it remains a pretty versatile library. The next steps for it are bundling in some basic sound/game engine support to make it even easier for kids to jump in and create. Follow the project on GitHub to see updates!</p> <h2 id="a-note-about-performance">A Note About Performance</h2> <p>Sadly, the Wii isn't the most performant device. It has significantly less memory than most devices on the market today; due to this, it's possible to get under-performant pretty quickly. While it does support the canvas element, it appears you can't force a repaint any faster than in 100ms intervals - anything lower and the Wii begins to choke repeatedly. This isn't really fast enough for games; canvas may be useful for other techniques, but for the most part any game engine that's done outside of Flash needs to be using HTML or SVG. SVG seems promising in recent tests, and it has a side benefit of reacting to DOM events like HTML based scenarios do.</p> <p>Opera on the Wii also appears to have some level of support for <a href="http://en.wikipedia.org/wiki/Server-sent_events%3E">Server-Sent Events</a>, which could possibly prove useful for enabling lightweight two player interaction. The performance considerations here are currently unknown at this time.</p> <h2 id="the-future">The Future</h2> <p>Nintendo recently announced their new console, the <em>Wii U</em>. Whether it will keep the Opera web browser is anyone's guess; it's worth noting that the new 3DS replaced the Opera browser used on previous DS incarnations with an engine similar to that of the PSP. There aren't too many usage metrics which we can use to draw predictions from, either, so at the moment it's a bit of &quot;wait and see&quot;.</p> <p>I'm going to continue developing the library and concepts surrounding it during my free time, and ideally want to try teaching a small class or two once I've further refined it. If you're interested in helping out, fork away on GitHub!</p> Using the myGengo Translation API with Python Tue, 31 May 2011 00:00:00 +0000 https://rymc.io/blog/2011/using-the-mygengo-translation-api-with-python/ https://rymc.io/blog/2011/using-the-mygengo-translation-api-with-python/ <p>For those who haven't heard the news, <a href="http://googlecode.blogspot.com/2011/05/spring-cleaning-for-some-of-our-apis.html">Google has deprecated a slew of their APIs</a>, leaving many developers and services in a bit of a pinch. While there's admittedly still time for developers to transition, it's a good time to start considering alternatives. In my opinion, it's probably a good idea to choose an alternative that has the technology in question as a core competency, otherwise you're much more liable to have your provider pull the rug out from underneath you.</p> <p>With that said, many engineers are hit particularly hard by the deprecation of the Translation API that Google has so generously offered up to this point, and desire a solid alternative. While there are other machine translation APIs out there, I wanted to take a moment to show more developers how <a href="https://gengo.com/developers/">integrating with the myGengo API</a> can get them the best of both worlds.</p> <div class="blog-edit-note"> <h4>A Polite Heads Up</h4> <p> As of May 31, 2011 I am currently working with myGengo to further develop their translation services. However, this post represents my own thoughts and opinions, and in no way represents myGengo as a company. myGengo offers both <strong>free machine translation</strong> and paid human translation under one API.<!-- and is currently offering [$25 in free API credits to all new developers interested in trying it out](https://mygengo.com/auth/form/signup?ftg25c=y). --> I simply want to show other developers that this is very easy to use. </p> </div> <h2 id="getting-started-with-the-mygengo-api">Getting Started with the myGengo API</h2> <p>This takes all of 5 minutes to do, but it's required before you can start getting things translated. A <a href="http://mygengo.com/services/api/dev-docs/">full rundown</a> is available, which includes details on using the API Sandbox for extensive testing. For the code below, we're going to work on the normal account.</p> <ul> <li><a href="https://mygengo.com/auth/form/signup?ftg25c=y">Create a myGengo account</a>.</li> <li>Once you've done that, <a href="http://mygengo.com/account/api_settings">create some API keys</a>.</li> <li>Install python-mygengo: <code>pip install mygengo</code></li> </ul> <h2 id="a-basic-example">A Basic Example</h2> <p>The myGengo API is pretty simple to use, but the authentication and signing can be annoying to do at first (like many other APIs). To ease this, there's a few client libraries you can use - the one I advocate using (and to be fair, I also wrote it) is the <a href="https://github.com/myGengo/mygengo-python">mygengo-python</a> library, which you just installed. With this it becomes incredibly easy to start making calls and submitting things for translation:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">from </span><span style="color:#f8f8f2;">mygengo </span><span style="color:#ff79c6;">import </span><span style="color:#f8f8f2;">MyGengo </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;">gengo </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">MyGengo</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">public_key </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&#39;your_public_key&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">private_key </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&#39;your_private_key&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">sandbox </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">False</span><span style="color:#f8f8f2;">, </span><span style="color:#6272a4;"># possibly True, depending on your dev needs </span><span class="lol"></span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">print </span><span style="color:#f8f8f2;">gengo</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">getAccountBalance</span><span style="color:#f8f8f2;">()[</span><span style="color:#f1fa8c;">&#39;response&#39;</span><span style="color:#f8f8f2;">][</span><span style="color:#f1fa8c;">&#39;credits&#39;</span><span style="color:#f8f8f2;">] </span><span class="lol"></span></code></pre> <p>The above script should print out your current account credits.</p> <!-- **25.00** if you signed up with myGengo using that link above. --> <h2 id="actually-translating-text">Actually Translating Text</h2> <p>Extending the above bit of code to actually translate some text is very simple - the thing to realize up front is that myGengo works on a system of <strong>tiers</strong>, with said tiers being <em>machine</em>, <em>standard</em>, <em>pro</em>, and <em>ultra</em>. These dictate the type of translation you'll get back. Machine translations are the fastest and free, <em>but least accurate</em>; the latter three are all tiers of human translation, and their rates vary accordingly (see the website for current rates).</p> <p>For the example below, we're going to just use machine translation, since it's an effective 1:1 replacement for Google's APIs. A great feature of the myGengo API is that you can upgrade to a human translation <em>whenever you want</em>; while you're waiting for a human to translate your job, myGengo still returns the machine translation for any possible intermediary needs.</p> <blockquote> Note: It's your responsibility to determine what level you need - if you're translating something to be published in another country, for instance, human translation will inevitably work better since a native translator understands the cultural aspects that a machine won't. </blockquote> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;"># -*- coding: utf-8 -*- </span><span class="lol"></span><span style="color:#ff79c6;">from </span><span style="color:#f8f8f2;">mygengo </span><span style="color:#ff79c6;">import </span><span style="color:#f8f8f2;">MyGengo </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;">gengo </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">MyGengo</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">public_key </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&#39;your_mygengo_api_key&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">private_key </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&#39;your_mygengo_private_key&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">sandbox </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">False</span><span style="color:#f8f8f2;">, </span><span style="color:#6272a4;"># possibly False, depending on your dev needs </span><span class="lol"></span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;">translation </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">gengo</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">postTranslationJob</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">job </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;type&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;text&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#6272a4;"># REQUIRED. Type to translate, you&#39;ll probably always put &#39;text&#39; here (for now ;) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;slug&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;Translating English to Japanese with the myGengo API&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#6272a4;"># REQUIRED. For storing on the myGengo side </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;body_src&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;I love this music!&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#6272a4;"># REQUIRED. The text you&#39;re translating. ;P </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;lc_src&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;en&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#6272a4;"># REQUIRED. source_language_code (see getServiceLanguages() for a list of codes) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;lc_tgt&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;ja&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#6272a4;"># REQUIRED. target_language_code (see getServiceLanguages() for a list of codes) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;tier&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;machine&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#6272a4;"># REQUIRED. tier type (&quot;machine&quot;, &quot;standard&quot;, &quot;pro&quot;, or &quot;ultra&quot;) </span><span class="lol"></span><span style="color:#f8f8f2;">}) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;"># This will print out 私はこの音楽が大好き! </span><span class="lol"></span><span style="color:#ff79c6;">print </span><span style="color:#f8f8f2;">translation[</span><span style="color:#f1fa8c;">&#39;response&#39;</span><span style="color:#f8f8f2;">][</span><span style="color:#f1fa8c;">&#39;job&#39;</span><span style="color:#f8f8f2;">][</span><span style="color:#f1fa8c;">&#39;body_tgt&#39;</span><span style="color:#f8f8f2;">] </span><span class="lol"></span></code></pre> <p>This really couldn't be more straight-forward. We've just requested our text be translated from English to Japanese by a machine, and gotten our results instantly. This is only the tip of the iceberg, too - if you have multiple things you need translated, you can actually bundle them all up and post them all at once (see <a href="https://github.com/myGengo/mygengo-python/blob/master/examples/postTranslationJobs.py">this example</a> in the mygengo-python repository).</p> <h2 id="taking-it-one-step-further">Taking it One Step Further!</h2> <p>Remember the &quot;human translation is more accurate&quot; point I noted above? Well, it hasn't changed in the last paragraph or two, so let's see how we could integrate this into a web application. The problem with human translation has historically been the human factor itself; it's slower because it has to pass through a person or two. myGengo has gone a long way in alleviating this pain point, and their API is no exception: you can register a callback url to have a job POSTed back to when it's been completed by a human translator.</p> <p>This adds another field or two to the translation API call above, but it's overall nothing too new:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;"># -*- coding: utf-8 -*- </span><span class="lol"></span><span style="color:#ff79c6;">from </span><span style="color:#f8f8f2;">mygengo </span><span style="color:#ff79c6;">import </span><span style="color:#f8f8f2;">MyGengo </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;">gengo </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">MyGengo</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">public_key </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&#39;&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">private_key </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&#39;&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">sandbox </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">False</span><span style="color:#f8f8f2;">, </span><span style="color:#6272a4;"># possibly False, depending on your dev needs </span><span class="lol"></span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;">translation </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">gengo</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">postTranslationJob</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">job </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;type&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;text&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#6272a4;"># REQUIRED. Type to translate, you&#39;ll probably always put &#39;text&#39; here (for now ;) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;slug&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;Translating English to Japanese with Python and myGengo API&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#6272a4;"># REQUIRED. Slug for internally storing, can be generic. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;body_src&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;I love this music!&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#6272a4;"># REQUIRED. The text you&#39;re translating. ;P </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;lc_src&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;en&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#6272a4;"># REQUIRED. source_language_code (see getServiceLanguages() for a list of codes) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;lc_tgt&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;ja&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#6272a4;"># REQUIRED. target_language_code (see getServiceLanguages() for a list of codes) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;tier&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;standard&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#6272a4;"># REQUIRED. tier type (&quot;machine&quot;, &quot;standard&quot;, &quot;pro&quot;, or &quot;ultra&quot;) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># New pieces... </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;auto_approve&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;comment&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;This is an optional comment for a translator to see!&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;callback_url&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;http://yoursite.com/your/callback/view&#39; </span><span class="lol"></span><span style="color:#f8f8f2;">}) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;"># This will print out a machine translation (私はこの音楽が大好き!), and you can </span><span class="lol"></span><span style="color:#6272a4;"># set up a callback URL (see below) to get the translated text back when it&#39;s been </span><span class="lol"></span><span style="color:#6272a4;"># completed by a human. You can alternatively poll in intervals to check. </span><span class="lol"></span><span style="color:#ff79c6;">print </span><span style="color:#f8f8f2;">translation[</span><span style="color:#f1fa8c;">&#39;response&#39;</span><span style="color:#f8f8f2;">][</span><span style="color:#f1fa8c;">&#39;job&#39;</span><span style="color:#f8f8f2;">][</span><span style="color:#f1fa8c;">&#39;body_tgt&#39;</span><span style="color:#f8f8f2;">] </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;"># Credit for the note about machine translation goes to https://github.com/aehlke, who </span><span class="lol"></span><span style="color:#6272a4;"># pointed out where I forgot to note. ;) </span><span class="lol"></span></code></pre> <p>All we've done here is change the level we want, to use a human (standard level), and supplied a callback url to post the job to once it's completed. As you can see, the response from our submission includes a free machine translation to use in the interim, so you're not left completely high and dry. You can also specify a comment for the translator (e.g, if there's some context that should be taken into account).</p> <p>Now we need a view to handle the job being sent back to us when it's completed. Being a python-focused article, we'll use Django as our framework of choice below, but this should be fairly portable to any framework in general. I leave the routing up to the reader, as it's largely basic Django knowledge anyway:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">def </span><span style="color:#50fa7b;">update_job</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">request</span><span style="color:#f8f8f2;">): </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">&quot;&quot;&quot;Handles parsing and storing a POSTed completed job from myGengo. </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">&quot;&quot;&quot; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if </span><span style="color:#f8f8f2;">request</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">method </span><span style="color:#ff79c6;">== </span><span style="color:#f1fa8c;">&quot;POST&quot;</span><span style="color:#f8f8f2;">: </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># Load the POSTed object, it&#39;s JSON data. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">resp </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">json</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">loads</span><span style="color:#f8f8f2;">(resp) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># Your translated text is now available in resp[&#39;body_tgt&#39;] </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># Save it, process it, whatever! ;D </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return </span><span style="color:#50fa7b;">HttpResponse</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">200</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">else</span><span style="color:#f8f8f2;">: </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return </span><span style="color:#50fa7b;">HttpResponse</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">400</span><span style="color:#f8f8f2;">) </span><span class="lol"></span></code></pre> <p>Now, wasn't that easy? Human translations with myGengo are pretty fast, and you get the machine translation for free - it makes for a very bulletproof approach if you decide to use it.</p> <h2 id="room-for-improvement">Room for Improvement?</h2> <p><a href="https://github.com/myGengo/mygengo-python%3E">mygengo-python</a> is open source and fork-able over on GitHub. I'm the chief maintainer, and love seeing pull requests and ideas for new features. If you think something could be made better (or is lacking completely), don't hesitate to get in touch!</p> More Power to ExpressionEngine URLs Sat, 07 May 2011 00:00:00 +0000 https://rymc.io/blog/2011/when-expressionengine-defaults-are-not-enough/ https://rymc.io/blog/2011/when-expressionengine-defaults-are-not-enough/ <div class="blog-edit-note"> <h4>Please Excuse the Tone. :(</h4> <p>I wrote this when I was younger and, arguably, an asshole (pardon my French). There may still be technical content of note in here, so I'm keeping it up, but please ignore the harsh and unnecessary tone.</p> </div> <p>When playing &quot;contract engineer&quot;, you sometimes have to jump in and work with a less than ideal codebase. This was the case on a recent project I helped out on; the codebase is an install of <a href="http://expressionengine.com">ExpressionEngine 2 (EE2)</a>, a publishing system (CMS) developed by the fine people at <a href="http://ellislab.com/">EllisLab</a>, favored by web designers all over the place. While I personally find it too limiting for my tastes (I suspect this is due to my doing less design based work these days), I can understand why people choose to work with it - highly sensible defaults with a pleasing overall control panel design that you can sell to customers. We can all agree that not reinventing the wheel is a good thing.</p> <p>That said, I would be lying if I didn't note that there are a few things about EE2 that bug me. I'll write about them each in-depth in their own articles; the focus of this one is on the somewhat limiting URL structure that EE2 enforces on you, as well as how to get around this and obtain a much higher degree of flexibility while still re-using your same EE2 essentials (templates, session handling, etc).</p> <h2 id="the-scenario-to-fix">The Scenario to Fix</h2> <p>The way that <a href="http://expressionengine.com/user_guide/general/urls.html%3E">EE2 handles URL routing</a> is pretty simple, and works for a large majority of use cases. The short and sweet of it is this:</p> <p>http://example.com/index.php/\ <strong>template_group</strong>/<strong>template</strong>/</p> <p>That url will render a template named &quot;template&quot; that resides inside &quot;template_group&quot;, taking care of appropriate contextual data and such. Let's imagine, though, that for SEO-related purposes you want a little more dynamism in that url - the <code>template_group</code> should act as more of a controller, where it can be re-used based on a given data set. What to do about this...</p> <h2 id="wait-ee2-is-codeigniter">Wait! EE2 is CodeIgniter!</h2> <p>This is where things get interesting. EE2 is actually built on top of <a href="http://codeigniter.com/%3E">CodeIgniter</a>, an open source PHP framework maintained by <a href="http://ellislab.com/">EllisLab</a>. It's similar to Ruby on Rails in many regards.</p> <p>That said, if you're new to web development and reading this, please <a href="http://www.djangoproject.com/">go learn to use a real framework</a>. Learning PHP (and associated frameworks) first will only set you up for hardships later.</p> <p>Now, since we have a framework, we have to ask ourselves... why doesn't EE2's setup look like a CodeIgniter setup? Well, EE2 swaps some functionality into the CI build it runs on, so things are a bit different. This is done (presumably) to maintain some level of backwards compatibility with older ExpressionEngine installations.</p> <h2 id="exposing-the-underlying-components">Exposing the Underlying Components</h2> <p>The first thing we need to address is the fact that the CodeIgniter router functions are being overridden. If you open up the main index.php file used by EE2 and go to line 94-ish, you'll find something like the following:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">&lt;?php </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">// ... </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">/* </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">* --------------------------------------------------------------- </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">* Disable all routing, send everything to the frontend </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">* --------------------------------------------------------------- </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">*/ </span><span class="lol"></span><span style="color:#ffffff;">$routing</span><span style="color:#f8f8f2;">[</span><span style="color:#f1fa8c;">&#39;directory&#39;</span><span style="color:#f8f8f2;">] </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&#39;&#39;</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ffffff;">$routing</span><span style="color:#f8f8f2;">[</span><span style="color:#f1fa8c;">&#39;controller&#39;</span><span style="color:#f8f8f2;">] </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&#39;ee&#39;</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ffffff;">$routing</span><span style="color:#f8f8f2;">[</span><span style="color:#f1fa8c;">&#39;function&#39;</span><span style="color:#f8f8f2;">] </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&#39;index&#39;</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">// ... </span><span class="lol"></span></code></pre> <p>You're gonna want to just comment those lines out. What's basically going on there is that this is saying &quot;hey, let's just have every request go through this controller and function&quot;, but we really don't want this. By commenting these out, the full routing capabilities of CodeIgniter return to us.</p> <p>One thing to note here is that if our desired route isn't found, <strong>ExpressionEngine will still continue to work absolutely fine</strong>. This is due to a line in the config/routes.php file:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">&lt;?php </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">// ... </span><span class="lol"></span><span style="color:#ffffff;">$route</span><span style="color:#f8f8f2;">[</span><span style="color:#f1fa8c;">&#39;default_controller&#39;</span><span style="color:#f8f8f2;">] </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&quot;ee/index&quot;</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ffffff;">$route</span><span style="color:#f8f8f2;">[</span><span style="color:#f1fa8c;">&#39;404_override&#39;</span><span style="color:#f8f8f2;">] </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&quot;ee/index&quot;</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">// An example of a route we&#39;ll watch for </span><span class="lol"></span><span style="color:#ffffff;">$route</span><span style="color:#f8f8f2;">[</span><span style="color:#f1fa8c;">&#39;example/(:any)&#39;</span><span style="color:#f8f8f2;">] </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&quot;example_controller/test/$1&quot;</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">// ... </span><span class="lol"></span></code></pre> <p>The default controller, if no route is found matching the one we've specified, is the EE controller, so nothing will break.</p> <h2 id="controllers-and-re-using-assets">Controllers and Re-using Assets</h2> <p>So now that we've got a sane controller-based setup rolling, there's one more problem to tackle: layouts and/or views. Presumably all your view code is built to use the EE2 templating engine; it'd be insane to have to keep a separate set of view files around that are non-EE2 compatible, so let's see if we can't re-use this stuff.</p> <p>A basic controller example is below:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">&lt;?php </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">!</span><span style="color:#8be9fd;">defined</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;BASEPATH&#39;</span><span style="color:#f8f8f2;">)) </span><span style="color:#ff79c6;">exit</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;This cannot be hit directly.&#39;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#ff79c6;">class </span><span style="text-decoration:underline;color:#8be9fd;">Example_controller </span><span style="color:#ff79c6;">extends </span><span style="text-decoration:underline;font-style:italic;color:#8be9fd;">Controller </span><span style="color:#f8f8f2;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">function </span><span style="color:#8be9fd;">__construct</span><span style="color:#f8f8f2;">() </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">parent</span><span style="color:#ff79c6;">::</span><span style="color:#50fa7b;">Controller</span><span style="color:#f8f8f2;">(); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/* Need to initialize the EE2 core for this stuff to work! */ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">core</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#50fa7b;">_initialize_core</span><span style="color:#f8f8f2;">(); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">core</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/* This is required to initialize template rendering */ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">require </span><span style="color:#bd93f9;">APPPATH</span><span style="color:#ff79c6;">.</span><span style="color:#f1fa8c;">&#39;libraries/Template&#39;</span><span style="color:#ff79c6;">.</span><span style="color:#bd93f9;">EXT</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">function </span><span style="color:#50fa7b;">test</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">$ext</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#8be9fd;">echo </span><span style="color:#ffffff;">$ext</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">/* End of file */ </span><span class="lol"></span></code></pre> <p>Now, viewing &quot;/example/12345&quot; in your browser should bring up a page that simply prints &quot;12345&quot;. The noteworthy pieces of this happen inside the construct method; there's a few pieces that we need to establish in there so we have a reference to the EE2 components.</p> <p>Now, to use our template structures once more, we need to add in a little magic...</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">&lt;?php </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// ... </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">private </span><span style="font-style:italic;color:#8be9fd;">function </span><span style="color:#50fa7b;">_render</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">$template_group</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">$template</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">$opts </span><span style="color:#ff79c6;">= </span><span style="color:#8be9fd;">array</span><span style="color:#f8f8f2;">()) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/* Create a new EE Template Instance */ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">TMPL </span><span style="color:#ff79c6;">= new </span><span style="font-style:italic;color:#66d9ef;">EE_Template</span><span style="color:#f8f8f2;">(); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/* Run through the initial parsing phase, set output type */ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">TMPL</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#50fa7b;">fetch_and_parse</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">$template_group</span><span style="color:#f8f8f2;">, </span><span style="color:#ffffff;">$template</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">FALSE</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">output</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">out_type </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">TMPL</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">template_type</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/* Return source. If we were given opts to do template replacement, parse them in */ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(</span><span style="color:#8be9fd;">count</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">$opts</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">&gt; </span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">output</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#50fa7b;">set_output</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">TMPL</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#50fa7b;">parse_variables</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">TMPL</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">final_template</span><span style="color:#f8f8f2;">, </span><span style="color:#8be9fd;">array</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">$opts</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span style="color:#ff79c6;">else </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">output</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#50fa7b;">set_output</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">TMPL</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">final_template</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">// ... </span><span class="lol"></span></code></pre> <p>This render method should be added to the controller example above; it accepts three parameters - a template group, a template name, and an optional multi-dimensional array to use as a context for template rendering (i.e, your own tags). If the last argument confuses you, it's probably best to <a href="http://expressionengine.com/user_guide/development/usage/template.html#parsing_variables%3E">read the EE2 third_party documentation on parsing variables</a>, as it's actually just using that API. There's really less black magic here than it looks like.</p> <p>With that done, our final controller looks something like this...</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">&lt;?php </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">!</span><span style="color:#8be9fd;">defined</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;BASEPATH&#39;</span><span style="color:#f8f8f2;">)) </span><span style="color:#ff79c6;">exit</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;This cannot be hit directly.&#39;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ff79c6;">class </span><span style="text-decoration:underline;color:#8be9fd;">Example_controller </span><span style="color:#ff79c6;">extends </span><span style="text-decoration:underline;font-style:italic;color:#8be9fd;">Controller </span><span style="color:#f8f8f2;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">function </span><span style="color:#8be9fd;">__construct</span><span style="color:#f8f8f2;">() </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">parent</span><span style="color:#ff79c6;">::</span><span style="color:#50fa7b;">Controller</span><span style="color:#f8f8f2;">(); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/* Need to initialize the EE2 core for this stuff to work! */ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">core</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#50fa7b;">_initialize_core</span><span style="color:#f8f8f2;">(); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">core</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/* This is required to initialize template rendering */ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">require </span><span style="color:#bd93f9;">APPPATH</span><span style="color:#ff79c6;">.</span><span style="color:#f1fa8c;">&#39;libraries/Template&#39;</span><span style="color:#ff79c6;">.</span><span style="color:#bd93f9;">EXT</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">private </span><span style="font-style:italic;color:#8be9fd;">function </span><span style="color:#50fa7b;">_render</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">$template_group</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">$template</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">$opts </span><span style="color:#ff79c6;">= </span><span style="color:#8be9fd;">array</span><span style="color:#f8f8f2;">()) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/* Create a new EE Template Instance */ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">TMPL </span><span style="color:#ff79c6;">= new </span><span style="font-style:italic;color:#66d9ef;">EE_Template</span><span style="color:#f8f8f2;">(); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/* Run through the initial parsing phase, set output type */ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">TMPL</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#50fa7b;">fetch_and_parse</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">$template_group</span><span style="color:#f8f8f2;">, </span><span style="color:#ffffff;">$template</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">FALSE</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">output</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">out_type </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">TMPL</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">template_type</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/* Return source. If we were given opts to do template replacement, parse them in */ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(</span><span style="color:#8be9fd;">count</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">$opts</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">&gt; </span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">output</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#50fa7b;">set_output</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">TMPL</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#50fa7b;">parse_variables</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">TMPL</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">final_template</span><span style="color:#f8f8f2;">, </span><span style="color:#8be9fd;">array</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">$opts</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span style="color:#ff79c6;">else </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">output</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#50fa7b;">set_output</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">EE</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">TMPL</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#ffffff;">final_template</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">function </span><span style="color:#50fa7b;">test</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">$ext</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return </span><span style="color:#bd93f9;">$this</span><span style="color:#ff79c6;">-&gt;</span><span style="color:#50fa7b;">_render</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;my_template_group&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&#39;my_template&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#8be9fd;">array</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;template_variable_one&#39; </span><span style="color:#ff79c6;">=&gt; </span><span style="color:#f1fa8c;">&#39;SuperBus&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;repeatable_stuff&#39; </span><span style="color:#ff79c6;">=&gt; </span><span style="color:#8be9fd;">array</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#8be9fd;">array</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;id&#39; </span><span style="color:#ff79c6;">=&gt; </span><span style="color:#bd93f9;">1</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&#39;text&#39; </span><span style="color:#ff79c6;">=&gt; </span><span style="color:#f1fa8c;">&#39;This&#39;</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#8be9fd;">array</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;id&#39; </span><span style="color:#ff79c6;">=&gt; </span><span style="color:#bd93f9;">2</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&#39;text&#39; </span><span style="color:#ff79c6;">=&gt; </span><span style="color:#f1fa8c;">&#39;Will&#39;</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#8be9fd;">array</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;id&#39; </span><span style="color:#ff79c6;">=&gt; </span><span style="color:#bd93f9;">3</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&#39;text&#39; </span><span style="color:#ff79c6;">=&gt; </span><span style="color:#f1fa8c;">&#39;Be&#39;</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#8be9fd;">array</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;id&#39; </span><span style="color:#ff79c6;">=&gt; </span><span style="color:#bd93f9;">4</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&#39;text&#39; </span><span style="color:#ff79c6;">=&gt; </span><span style="color:#f1fa8c;">&#39;Repeatable&#39;</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">)); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">/* End of file */ </span><span class="lol"></span></code></pre><h2 id="awesome-now-what">Awesome! Now what?</h2> <p>Please go use a <a href="http://www.python.org/">more reasonable programming language</a> that enforces better practices. While you're at it, check out one of the <a href="http://djangoproject.com/">best web frameworks around</a>, conveniently written in said reasonable programming language.</p> <p>Of course, if you're stuck using PHP, then make the most of it I suppose. If this article was useful to you, I'd love to hear so!</p> Emitting Custom Events in Node.js Sat, 16 Apr 2011 00:00:00 +0000 https://rymc.io/blog/2011/emitting-custom-events-in-node-js/ https://rymc.io/blog/2011/emitting-custom-events-in-node-js/ <div class="blog-edit-note"> <h4>Note the Following!</h4> <p>This is an article I wrote for the March 2011th issue of (the now defunct) <a href="http://jsmag.com/">JSMag</a>. It was a great piece of literature released monthly, and a great way to keep up to date on the latest news in the Javascript community. Sad to see it go! </p> </div> <p>Node isn’t the first approach to event based programming, and with its explosion of interest it probably won’t be the last. Typical JavaScript patterns for callback functions involve passing around references to functions and managing odd scope levels. In many cases this is less than ideal; that said, there’s another option when you’re in Node: emit your own events, and let functions attach and respond to those. EventEmitter makes this incredibly easy!</p> <h2 id="the-typical-approach">The Typical Approach...</h2> <p>If you’ve written or even worked with JavaScript libraries before, you probably understand the callback function scenario – that is, functions that execute once a certain task is completed. A typical use might be something like what you see in the following example:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#ffffff;">x </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">1</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#50fa7b;">foo </span><span style="color:#ff79c6;">= </span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">callbackfn</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return </span><span style="color:#50fa7b;">callbackfn</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">x </span><span style="color:#ff79c6;">* </span><span style="color:#bd93f9;">2</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#50fa7b;">foo</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">x</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">console</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">log</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">x</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">); </span><span class="lol"></span></code></pre> <p>Here, we’ve defined a function that accepts another function as its main argument and passes the callback function a doubled version of <code>x</code>. Pretty simple, and many libraries use this technique for Ajax calls. Let’s take a minute and spin the globe, though – what if, instead of arbitrarily accepting a function and having to worry about possible scoping issues, we could just announce when an event of interest has occurred, and fire an attached function at that point? This would be so much cleaner than passing around function references everywhere.</p> <h2 id="enter-events-eventemitter">Enter: events.EventEmitter</h2> <p>The great thing about all this? We can actually do this in Node through use of the events library. This, in many ways, is core to how things in Node work. Everything is event based, so why shouldn’t we be able to fire off our own events? To showcase what’s possible with this, let’s build a basic library to connect to Twitter's Streaming API, which we can then filter results from as we see fit.</p> <h2 id="the-basics-exporting-an-eventemitter-instance">The Basics: exporting an EventEmitter instance</h2> <p>Before we get into anything Twitter-specific, we’ll demonstrate basic usage of <code>EventEmitter</code>. The code below shows how simple this can really be – it’s a contrived example that constantly increases numbers by one, and emits an event called “even” every time the number becomes even.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#f8f8f2;">events </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">require</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;events&#39;</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">util </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">require</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;util&#39;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#50fa7b;">Foo </span><span style="color:#ff79c6;">= </span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">initial_no</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span style="color:#bd93f9;">this</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">count </span><span style="color:#ff79c6;">= </span><span style="color:#ffffff;">initial_no</span><span style="color:#f8f8f2;">; </span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#66d9ef;">Foo</span><span style="color:#ff79c6;">.</span><span style="color:#6be5fd;">prototype </span><span style="color:#ff79c6;">= new </span><span style="color:#f8f8f2;">events</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">EventEmitter; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#66d9ef;">Foo</span><span style="color:#ff79c6;">.</span><span style="color:#6be5fd;">prototype</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">increment </span><span style="color:#ff79c6;">= </span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">() </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#bd93f9;">self </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">this</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">setInterval</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">() </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">count </span><span style="color:#ff79c6;">% </span><span style="color:#bd93f9;">2 </span><span style="color:#ff79c6;">=== </span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">) </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">emit</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;even&#39;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">count</span><span style="color:#ff79c6;">++</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">300</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#ffffff;">lol </span><span style="color:#ff79c6;">= new </span><span style="color:#f8f8f2;">Foo(</span><span style="color:#bd93f9;">1</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ffffff;">lol</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">on</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;even&#39;</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">() </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">util</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">puts</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;Number is even! :: &#39; </span><span style="color:#ff79c6;">+ </span><span style="color:#bd93f9;">this</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">count); </span><span class="lol"></span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">)</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">increment</span><span style="color:#f8f8f2;">(); </span><span class="lol"></span></code></pre> <p>Usage of <code>EventEmitter</code> is pretty simple – you basically want to inherit all the properties from <code>EventEmitter</code> itself into your object, giving it all the properties it needs to emit events on its own. Events are sent off as keywords (‘<code>even</code>’, ‘<code>error</code>’, etc), called directly on the object. You can extend the prototype chain further, and <code>EventEmitter</code> should work fine and dandy.</p> <h2 id="changing-tracks-for-a-moment">Changing Tracks for a Moment...</h2> <p>Now that we’ve shown how <code>EventEmitter</code> works, we want to go ahead and use it for Twitter's Streaming API. For the unfamiliar, the Streaming API is essentially a never ending flood of tweets. You open a connection, and you keep it open; data is pushed to you, reversing the typical model of “request/response” a bit in that you only really make one request. <code>EventEmitter</code> is perfect for this task, but to satisfy some basic needs for interacting with Twitter's API, we’ll need a base library, like what’s shown in the example below:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#f8f8f2;">util </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">require</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;util&#39;</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">http </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">require</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;http&#39;</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">events </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">require</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;events&#39;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#50fa7b;">TwitterStream </span><span style="color:#ff79c6;">= </span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">opts</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">this</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">username </span><span style="color:#ff79c6;">= </span><span style="color:#ffffff;">opts</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">username; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">this</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">password </span><span style="color:#ff79c6;">= </span><span style="color:#ffffff;">opts</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">password; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">this</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">track </span><span style="color:#ff79c6;">= </span><span style="color:#ffffff;">opts</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">track; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">this</span><span style="color:#ff79c6;">.</span><span style="color:#6be5fd;">data </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&#39;&#39;</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#66d9ef;">TwitterStream</span><span style="color:#ff79c6;">.</span><span style="color:#6be5fd;">prototype </span><span style="color:#ff79c6;">= new </span><span style="color:#f8f8f2;">events</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">EventEmitter; </span><span class="lol"></span><span style="font-style:italic;color:#66d9ef;">module</span><span style="font-style:italic;color:#ff79c6;">.</span><span style="font-style:italic;color:#66d9ef;">exports </span><span style="color:#ff79c6;">= </span><span style="color:#ffffff;">TwitterStream</span><span style="color:#f8f8f2;">; </span><span class="lol"></span></code></pre> <p>Here we require the three main resources we’ll need (<code>util</code>, <code>http</code> and <code>events</code>), and set up a new Function Object that’s essentially an instance of <code>EventEmitter</code>. We’ll throw it over to <code>exports</code>, too, so it plays nicely when relying on it in outside code. Creating instances of our Twitter object requires a few things – ‘<code>track</code>’, which is a keyword to filter tweets by, and a ‘<code>username</code>’/’<code>password</code>’ combination which should be self explanatory (in terms of what they are).</p> <p>Why ‘<code>username</code>/<code>password</code>’, though? Twitter's Streaming API requires some form of authentication; for the sake of brevity in this article, we’re going to rely on Basic Authentication, but moving forward it’s recommended that you use OAuth for authenticating with Twitter, as it relies on the user granting you privileges instead of actually handing over their password. The OAuth ritual is much longer and more intricate to pull off, though, and would push the length and scope of this article far beyond its intentions.</p> <h2 id="emitting-a-tweet-event">Emitting a &quot;Tweet&quot; Event</h2> <p>Now that we’ve got the basic scaffolding for our library set up, let’s throw in a function to actually connect, receive tweets, and emit an event or two that other code can catch. Check out the following for a prime example of how we can do this:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="font-style:italic;color:#66d9ef;">TwitterStream</span><span style="color:#ff79c6;">.</span><span style="color:#6be5fd;">prototype</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">getTweets </span><span style="color:#ff79c6;">= </span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">() </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#ffffff;">opts </span><span style="color:#ff79c6;">= </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">host: </span><span style="color:#f1fa8c;">&#39;stream.twitter.com&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">port: </span><span style="color:#bd93f9;">80</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">path: </span><span style="color:#f1fa8c;">&#39;/1/statuses/filter.json?track=&#39; </span><span style="color:#ff79c6;">+ </span><span style="color:#bd93f9;">this</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">track, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">method: </span><span style="color:#f1fa8c;">&#39;POST&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">headers: </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;Connection&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;keep-alive&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;Accept&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;*/*&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;User-Agent&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;Example Twitter Streaming Client&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;Authorization&#39;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&#39;Basic &#39; </span><span style="color:#ff79c6;">+ new </span><span style="font-style:italic;color:#66d9ef;">Buffer</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">this</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">username </span><span style="color:#ff79c6;">+ </span><span style="color:#f1fa8c;">&#39;:&#39; </span><span style="color:#ff79c6;">+ </span><span style="color:#bd93f9;">this</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">password)</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">toString</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;base64&#39;</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">self </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">this</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">this</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">connection </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">http</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">request</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">opts</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">response</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">response</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">setEncoding</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;utf8&#39;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">response</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">on</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;data&#39;</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">chunk</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#6be5fd;">data </span><span style="color:#ff79c6;">+= </span><span style="color:#ffffff;">chunk</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">toString</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;utf8&#39;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#ffffff;">index</span><span style="color:#f8f8f2;">, </span><span style="color:#ffffff;">json</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">while</span><span style="color:#f8f8f2;">((</span><span style="color:#ffffff;">index </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#6be5fd;">data</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">indexOf</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;</span><span style="color:#ff79c6;">\r\n</span><span style="color:#f1fa8c;">&#39;</span><span style="color:#f8f8f2;">)) </span><span style="color:#ff79c6;">&gt; </span><span style="color:#bd93f9;">-1</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">json </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#6be5fd;">data</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">slice</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">, </span><span style="color:#ffffff;">index</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#6be5fd;">data </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#6be5fd;">data</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">slice</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">index </span><span style="color:#ff79c6;">+ </span><span style="color:#bd93f9;">2</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">json</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">length </span><span style="color:#ff79c6;">&gt; </span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">try </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">emit</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;tweet&#39;</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#66d9ef;">JSON</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">parse</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">json</span><span style="color:#f8f8f2;">)); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span style="color:#ff79c6;">catch</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">e</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">emit</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;error&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#ffffff;">e</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">this</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">connection</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">write</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;?track=&#39; </span><span style="color:#ff79c6;">+ </span><span style="color:#bd93f9;">this</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">track); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">this</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">connection</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">end</span><span style="color:#f8f8f2;">(); </span><span class="lol"></span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span></code></pre> <p>If you’ve worked with Node before, this code shouldn’t be too daunting, but we’ll summarize it just in case. We’re extending the prototype of our Twitter object that we created before, and adding a method to start the stream of tweets coming in. We set up an object detailing the <code>host</code>, <code>port</code>, <code>path</code> and <code>method</code>, as well as some custom headers (notably, setting ‘<code>keep-alive</code>’ and <code>Basic Authentication</code> headers). This is passed to an <code>http.request()</code> call, and we then write our tracking data and end the connection.</p> <p>The response function has some logic to handle putting together tweets that are sent in by Twitter. The API dictates that a tweet object will end on the two characters ‘<code>\\r</code>’ and ‘<code>\\n</code>’, so we basically walk the built up JSON strings as they come in and separate them out. If a JSON string is successfully pulled out, we emit a ‘tweet’ event, and pass it the parsed JSON data. If something went horribly wrong, we emit an ‘<code>error</code>’ event and pass it the associated object.</p> <h2 id="usage-and-application">Usage and Application</h2> <p>Alright, so now we should have a pretty functional library once we put those two together. The code below shows how we can now use this library in a simple script.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#ffffff;">TwitterStream </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">require</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;./twitterstream&#39;</span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">util </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">require</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;util&#39;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#ffffff;">twitter </span><span style="color:#ff79c6;">= new </span><span style="color:#f8f8f2;">TwitterStream(</span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">username: </span><span style="color:#f1fa8c;">&#39;username&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">password: </span><span style="color:#f1fa8c;">&#39;password&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">track: </span><span style="color:#f1fa8c;">&#39;JavaScript&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ffffff;">twitter</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">on</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;tweet&#39;</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">tweet</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">util</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">puts</span><span style="color:#f8f8f2;">(util</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">inspect</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">tweet</span><span style="color:#f8f8f2;">)); </span><span class="lol"></span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ffffff;">twitter</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">on</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;error&#39;</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">e</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">util</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">puts</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">e</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ffffff;">twitter</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">getTweets</span><span style="color:#f8f8f2;">(); </span><span class="lol"></span></code></pre><h2 id="wrapping-things-up">Wrapping Things Up</h2> <p><code>EventEmitter</code> is an excellent, easy to implement option for dealing with cases where you might want to defer an action until data is ready. Readers with further questions should check out the Node.js documentation on <code>EventEmitter</code>.</p> Hacking the Human Brain Sun, 06 Mar 2011 00:00:00 +0000 https://rymc.io/blog/2011/hacking-the-human-brain/ https://rymc.io/blog/2011/hacking-the-human-brain/ <p>Back in 2008 I was frequently riding a train twice a day for a ridiculous ~3 hour (each way) commute that nobody on this planet should ever have to do. Needless to say, I did a lot of reading, particularly issues of Wired Magazine. To this day, one article still stands fresh in my mind, which essentially dealt with the concept of <a href="http://www.wired.com/medtech/health/magazine/16-05/ff_wozniak">surrendering your brain to an algorithmic approach to memorization</a>. The man behind the core of the theory is Piotr Wozniak, a gentleman out of Poland who still somewhat astounds me to this day.</p> <p>I won't reproduce the theory in full here, as the Wired article does a much better job writing things up, but the core things to take down are that the human brain tends to have times when it memorizes better or worse, and it's possible to capitalize on these moments to increase your potential for solidly committing something to memory. <a href="http://www.supermemo.com/">SuperMemo</a> is an effort to implement this in software. It's cool and all, but I'm not sure I'm in 100% total agreement.</p> <h2 id="hack-faster-please">Hack Faster, Please.</h2> <p>You see the thing about the theory is that your core memory might work on a two week cycle; learn something today, see it again in two weeks, and if everything holds true you'll probably never forget it. However, I disagree with the concept that short term memory commitment can't be stronger for certain concepts.</p> <p>Take something like teaching yourself a new language. If it's something truly foreign to you, the characters won't make sense, the pronunciations will sound totally off, and there's a good chance that anyone who's not forced through it will give up in about a week or two. Long term memory won't have a shot in that case; maybe not due to any particular flaw in the theory, but merely due to the lack of willpower some people have. In addition you have to factor in the concept of age: as we get older, our memory and the way it works through concepts changes. Short term memory is nowhere near as slighted when up against these two conceptual foes; are we certain there's no good middle ground to hit?</p> <h2 id="can-it-be-proven">Can It Be Proven?</h2> <p>So let's provide a short bit of backstory here. This past week (beginning of March, 2011), I got the awesome opportunity to work with the folks at <a href="https://gengo.com/">myGengo</a>, a company that builds tools to help ease translation efforts. This required heading to Tokyo - for the astute, <a href="https://rymc.io/2010/12/05/everlasting-fight-travel/">I had visited Tokyo</a> some months prior, so I wasn't a total stranger to what I'd experience upon arrival. I do want to learn the language, though.</p> <p>A typical approach for character memorization would be to make flash cards, sit down and repeatedly run through them. I won't lie, this <em>bores the hell out of me</em>. I'd much rather have something portable that I can use on the train when I'm traveling in the mornings. To that end, I just went ahead and built an Android application (app) to do this.</p> <p><a href="https://market.android.com/details?id=com.phonegap.Katanakana">Katakana on the Android Market</a></p> <p>Now, since I was already creating an app for this, I figured I could take some liberties. With the theory still lingering in the back of my head, I began to muse: what's my own learning pattern like? Well, for the past (roughly) seven years I've learned things incredibly quickly. In some cases this was by design, in other cases... well, you get the idea.</p> <p>The thing is that it's worked for me so far, and it's the same for many other programmers I know. Programmers far and wide can attest to the fact that while there's no doubt benefits to the long-term memorization benefits, we consistently rely on short term to do our jobs. We accrue a sometimes ridiculous amount of information in a short period of time that will instantly come back to us when we need it. The key is, of course, <em>when we need it</em>, generally through a trigger (a linked piece of information, for example).</p> <h2 id="the-theory-itself">The Theory Itself</h2> <p>So this theory started formulating in my mind. What if I could apply elements of Wozniaks theory to short term memory, and then rely on the trigger to pick up the rest? Even in short term memory I found that I, personally, had a few-minutes window where if I reviewed the same concept, I'd commit it pretty quickly. The triggers, in this case, will be as I walk down the streets of Tokyo or read a menu.</p> <p>I got down to building the app. The core details of building an Android app are outside the scope of this article; the algorithm I threw in is worth detailing a bit, though. In my mind, when you use an app on your phone, you're going to use it, at most, for five minutes. The app concept just lends itself to this, a bunch of miniature worlds that you can hop in and out of at will. So with that in mind, I set the high barrier for this experiment at five minutes - roughly the maximum amount of time I expect someone to stay engaged.</p> <p>I'm assuming, based on my own use cases and the trials of a few friends, that on average I can expect people to get through roughly three cards in a minute. At five minutes, fifteen cards, not too bad. The question of where to 're-appear' content then came up - for this, I first settled on throwing it back in the users face every couple of minutes. The number of minutes is variable; it starts off set at one minute, but will adapt based on whether or not you answer correctly as things re-appear. If you memorize things at the four minute mark, for instance, it'll edge towards that - never 100% four minutes due to the relative inaccuracy of Android's timing, mind you, but it gets the job done.</p> <p>I've been using the application myself for roughly two days now, and it's easily topped any effort I put in with books or traditional methods over the past two months. It's worth noting that I still can't write Japanese to save my life, but that's also a two fold issue: characters can be quite complex (kanji), and don't lend themselves well to a trigger-based scenario for recall. However, if I'm looking at a character screen, I can at least make some sense of what I'm seeing now.</p> <h2 id="taking-this-further">Taking This Further</h2> <p>My theories aren't proven, but then again, it's the human brain we're dealing with. I released the Android app as a test of my take on Wozniak's theory with a bit of my own magic; based on how well it does, I'll release apps for Hiragana, Kanji, and anything else applicable. I personally believe that the effects of memory commitment through short term memory optimization can be optimized, and this is a pretty great and open way to give it a whirl.</p> The Everlasting Fight To Expand My Metaphorical Sight (Part 2) Mon, 07 Feb 2011 00:00:00 +0000 https://rymc.io/blog/2011/the-everlasting-fight-to-expand-my-metaphorical-sight-part-2/ https://rymc.io/blog/2011/the-everlasting-fight-to-expand-my-metaphorical-sight-part-2/ <p>Well, time certainly flies by quickly. Since the last entry in this little mini-series, I've globe-trotted some more (London, New Jersey, New York City, DC, San Francisco, Seattle... San Francisco again...) and released some new projects that've been in the pipeline for some time. What's next?</p> <h2 id="on-traveling">On Traveling</h2> <p>London was a very, very interesting experience. I had the fun experience of being stuck there well past my intended departure date due to a massive snow storm that shut most of Europe down; London Heathrow, why you refused the help of the Army to clear away snow is simply beyond me. That said, the city of London itself is a nice place, one that I could see myself spending more time in. The surrounding area is equally cool and worth checking out! Yet again this was a country where public transportation is pretty slick. Notice a recurring theme here?</p> <p>The rest of my travels have been pretty US-centric; nothing noteworthy, sans shooting up to Seattle for a week to visit with my younger brother. Now, enough of all this personal drivel, there's work to discuss.</p> <h2 id="progprofessor">ProgProfessor</h2> <p>I think kids should be taught programming at a young age, but <strong>with absolutely no initial focus on mathematics</strong>. People can fight it all they want, but math doesn't interest kids, and a direct approach to trying to make it interesting so more come into the subject field won't work. Programming, if taught with a creative and artistic edge, is well suited to fix this problem.</p> <p>At least, that's my theory, and the entire line of reason behind my efforts with <a href="http://progprofessor.com/">ProgProfessor</a>. This'll be followed up soon with a few other new projects, stay tuned!</p> <h2 id="feedbackbar">FeedBackBar</h2> <p>When I got back into San Francisco, I met up with my good friend <a href="http://shiftb.com/">Brandon Leonardo</a>. Awhile back he had conceived of this pretty cool idea to distribute a &quot;feedback bar&quot; type widget, where any site could sign up, throw some code on, and get immediate feedback from users. It's an idea somewhat in the same realm as <a href="http://uservoice.com/">UserVoice</a> or <a href="http://getsatisfaction.com/">Get Satisfaction</a>, but much more stripped down and to the point. I thought it was pretty cool, and we managed to hack it out in a night.</p> <p><a href="http://feedbackbar.com/">FeedBackBar</a> is free and quick to implement. Check it out and let us know what you think!</p> <h2 id="pygengo-pythonic-mygengo-translation-api-library">pyGengo - Pythonic myGengo Translation API Library</h2> <p>The other notable release I've thrown out in the past month is <a href="https://github.com/ryanmcgrath/pygengo">pyGengo</a>, a library that wraps the <a href="http://mygengo.com/services/api/dev-docs">myGengo API</a>. myGengo is a cool service that offers an easy, reliable way to get text translated into other languages by <em>other humans</em>. Machine translation alone can be notoriously incorrect, so having a human back it up is quite the awesome technique to have up your sleeve.</p> <p>pyGengo is fully documented and has a test suite to boot. Issues can be filed on the <a href="https://github.com/ryanmcgrath/pygengo/issues">Github Issue Tracker</a>, give it a shot and let me know what you think!</p> <h2 id="so-what-s-next">So... What's Next?</h2> <p>I've got a few projects coming up that should be pretty significant releases, so at the moment I'm working towards those. You should <a href="https://twitter.com/ryanmcgrath/">follow me on Twitter</a> to know when they're released!</p> The Everlasting Fight To Expand My Metaphorical Sight (Part 1) Sun, 05 Dec 2010 00:00:00 +0000 https://rymc.io/blog/2010/everlasting-fight-travel/ https://rymc.io/blog/2010/everlasting-fight-travel/ <p>For the past few weeks, I've had the incredibly fun experience of living in Tokyo, Japan. I've already fielded quite a few questions as to the &quot;why&quot; I decided to do this, and in an effort to not re-type the story another hundred times, I figured I'd throw it down here along with my experiences thus far. This post is not programming centric; for those of you who follow the feeds for them, you'll have something soon, no worries.</p> <h2 id="the-backstory">The Backstory</h2> <p>Back in early November, I was at somewhat of a crossroads. I had recently come to the conclusion that <a href="http://venodesigns.net/2010/10/06/and-whats-the-deal-with-san-francisco-along-with-my-travels/%3E">I'm not a huge fan of San Francisco</a>. I went ahead and checked out Chicago after that experience, and while it's an incredible city in its own right (and certainly one of my favorite US cities), I found myself wanting more. The funny part about traveling in the US is that for all its regions and supposed differences, at the end of the day you'll find largely the same things no matter where you go.</p> <p>So I figured, alright, let's see what other countries have to offer. I applied, and (somehow) got my passport within a week. The question then became &quot;where to?&quot;. I could've gone somewhere like England, France, Germany... the standard &quot;alright, let's backpack for a year and see the world&quot; destinations that are often suggested.</p> <p>Thing is, I'm not like that. I enjoy jumping off the deep end; the way I see it, if I can survive the biggest changes and come out fine, then anything else in between is a cakewalk. This is largely one of the reasons I chose Tokyo/Japan; not only is the language incredibly different than anything I know at the moment*, but it is literally on the other side of the planet.</p> <p><em>(...though some would argue German is on the same level, if not higher, but I digress...)</em></p> <p>One of the exciting parts of this journey is the path I've taken. I flew off the west coast, came to Japan, and will head to England and/or The Netherlands for about a week after this. From there, I fly back to the east coast. <em>I will have fully circled the globe by the age of 23, under my own power</em>. This has been a goal of mine for quite some time now, and it's really satisfying to see it come about.</p> <h2 id="initial-life-in-japan">Initial Life In Japan</h2> <p>The minute the plane touched down (after a lovely 12 hour flight) was interesting, because the culture shock hit me immediately. Not &quot;uncomfortable, get me out of here&quot; style, but definitely &quot;wow, there is definitely something different here&quot;. I'll state up front that this has been evident the entire time I've been here, too; back home, I obviously grew accustomed to a lot of things, and the way they're done here is almost always radically different in some way. It definitely took some getting used to, but overall I've enjoyed it so far.</p> <p>I rented an apartment a little bit outside central Tokyo, in an area known as Nishimagome. Trains into the central part from there are cheap, and take about ~15 minutes, so overall it worked out incredibly well. One part that I found very interesting was the feeling of being a minority - while I'm sure this comes across as potentially ignorant to some who'll read this, I've never actually felt that before in my life. I've lived in areas as a kid where some might say I was, but there was very little social divide in those scenarios. Being here incurred a language barrier and a customs barrier (i.e, society here has their own customs which the US doesn't necessarily follow), which sits on top of the typical social divides that we as human beings tend to exhibit in general.</p> <p>For the first two weeks, almost nobody in Nishimagome wanted to deal with me. It was very fascinating; some appeared to do it out of a lack of understanding in regards to my home culture (and believing that I didn't understand theirs), whereas others seem steeped in what some have described to me as the &quot;old Japan&quot; ways. It's very evident the way foreigners are treated differently here; while I had read about it prior to arrival, I wasn't quite sure what to expect. Getting over that initial barrier in the area I was staying in required a lot of careful conversations, as if walking on a field of landmines. At the time of this writing (about 3 weeks in), this has largely disappeared, but it definitely took some effort - a very eye opening experience indeed.</p> <h2 id="cleanliness-is-godliness">Cleanliness Is Godliness</h2> <p>The streets of this country are <em>incredibly clean</em>. To be completely blunt, living here for a stint has made me feel like shit as an American. Tokyo is an insanely huge city, and the amount of trash on the ground <em>pales</em> in comparison to that of New York City (NYC) or San Francisco (SF). I believe that a large part of the reasoning behind this is that you are literally looked down upon if you litter - in America, we're far too accepting of waste and littering.</p> <p>The ironic part is that it's nigh impossible to find a trash can half the time here. Your best bet is to, hey, recycle everything at the bins you'll find around vending machines and food marts. On top of this, having this mentality around you the entire time brings a sense of wanting things to be clean to you. I'll say what a lot of Americans will deny out of pride: much of the time, in the US, I feel as though it's perfectly fine to litter because <em>everyone else is doing it</em>. Here, I can't bring myself to do it; the gravity of the act is taken to such a higher level here.</p> <p>Say what you will about peer pressure and such; none of it will matter, because you'd be arguing the wrong point here. That scenario exists for <em>many</em> Americans, and trying to solve things through peer pressure dissolution efforts will go nowhere. Small scale deployments of the Japanese recycling system in the US could potentially work, provided people help to enforce and spread the effort around - I believe that many recycling efforts in the US largely fail because <em>people are given the alternative</em>. Having one right choice doesn't make you a communist, people, it just means it's the best fit for the task.</p> <h2 id="convenience-is-underrated">Convenience Is Underrated</h2> <p>I've spent a decent amount of time in just about every American city, and I can say with a relative amount of certainty that Tokyo easily beats them all out when it comes to convenience. As I type this, it's 3 in the morning, and I have no less than 4 different shops open and <em>20 different vending machines</em> within a one mile radius, all giving me access to coffee, food, emergency supplies, and whatever else I need. NYC comes closest in this regard, but Tokyo's ability to provide at all hours is staggering, and still surpasses anything I've seen in NYC.</p> <h2 id="cover-charges-at-bars-are-annoying">Cover Charges At Bars Are Annoying</h2> <p>Drinking in Tokyo can be quite an expensive task if you're not careful. Most bars and clubs you'll hit up require a cover charge (anywhere from $8 - $30, in my experiences), and if you're lucky that includes a free drink up front. While I don't particularly care about this, it inadvertently becomes a social barrier when out with a group - some people aren't always game to pay for two drinks when they're only getting one. In the US, the ability to just stroll into a bar and have a drink with a friend is a nice luxury when compared with here.</p> <h2 id="public-transportation-rocks-but-should-stay-open-later">Public Transportation Rocks, But Should Stay Open Later</h2> <p>I find public transportation here to be a catch-22 of hilarious proportions. The ability to get just about anywhere <em>in the entire country</em> by public transportation is incredible. This has nothing to do with the overall size of the country, it's simply a situation where they've paid attention and <em>built the necessary infrastructure</em>. If trains were open past midnight, I'd call it a perfect situation, but having to choose to stay in central Tokyo or run to catch a train home around midnight can be quite the bummer if you're having a blast.</p> <h2 id="what-s-next">What's Next?</h2> <p>This is the first entry in a multi-part series, as I couldn't hope to cover everything I've experienced in the past few weeks in one post. In another week, I head to Europe for a stint, and then back into the US. Traveling is an incredible experience, and I'll continue to do this as long as it benefits me and helps me grow as a person.</p> <p>Keep an eye out for the next entry in this series!</p> Emulating Ruby's "method_missing" in Python Tue, 02 Nov 2010 00:00:00 +0000 https://rymc.io/blog/2010/emulating-rubys-method_missing-in-python/ https://rymc.io/blog/2010/emulating-rubys-method_missing-in-python/ <p>I don't pretend to be a huge fan of Ruby. That said, I can respect when a language has a feature that's pretty damn neat and useful. For the uninformed, <em><code>method_missing</code></em> in Ruby is something like the following:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;"># Basic class, no methods, but method_missing love </span><span class="lol"></span><span style="color:#ff79c6;">class </span><span style="text-decoration:underline;color:#8be9fd;">Rapture </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">def </span><span style="color:#50fa7b;">initialize </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffb86c;">@missing_msg </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&quot;Oh god, calling method_missing&quot; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">end </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># Called when a non-existent method call on an instance </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># of this class is made. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">def </span><span style="color:#50fa7b;">method_missing</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">method_name</span><span style="color:#f8f8f2;">, </span><span style="color:#ff79c6;">*</span><span style="font-style:italic;color:#ffb86c;">args</span><span style="color:#f8f8f2;">, </span><span style="color:#ff79c6;">&amp;</span><span style="font-style:italic;color:#ffb86c;">block</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">puts </span><span style="color:#ffb86c;">@missing_msg </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">end </span><span class="lol"></span><span style="color:#ff79c6;">end </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;">bioshock </span><span style="color:#ff79c6;">= </span><span style="font-style:italic;color:#66d9ef;">Rapture</span><span style="color:#ff79c6;">.new </span><span class="lol"></span><span style="color:#f8f8f2;">bioshock</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">play </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;"># play doesn&#39;t exist, so this will output </span><span class="lol"></span><span style="color:#6272a4;"># &quot;Oh god, calling method_missing&quot; </span><span class="lol"></span></code></pre> <p>Obviously, this is a trick that should be used with caution. It can make for some unmaintainable code, as a class with many methods could get difficult to trace through and figure out just what the hell is happening. It can be put to good use, though - take an API wrapper, for instance. What's it consist of? Generally, nothing more than the same function calls made over and over to various service endpoints.</p> <h2 id="cool-let-s-use-this-in-python">Cool, let's use this in Python!</h2> <p>I recently rewrote <a href="https://github.com/ryanmcgrath/twython">Twython</a> to support OAuth authentication with Twitter (as of Twython 1.3). It ships with an example Django application to get people up and running quickly, and the adoption has been pretty awesome so far.</p> <p>The funny thing about the Twython 1.3.0 release is that it was largely a rewrite of the entire library. It had become somewhat unwieldy, some odd two thousand lines of code with each API endpoint getting its own method definition. The only differing aspect of these calls is the endpoint URL itself. This is a perfect case for a <em><code>method_missing</code></em> setup - let's catch the calls to non-existent methods, and grab them out of a dictionary mapping to every endpoint.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#f8f8f2;">method_dictionary </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;getPublicTimeline&quot;</span><span style="color:#f8f8f2;">: { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;endpoint&quot;</span><span style="color:#f8f8f2;">: </span><span style="color:#f1fa8c;">&quot;http://...&quot;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">}, </span><span class="lol"></span><span style="color:#f8f8f2;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#ff79c6;">class </span><span style="text-decoration:underline;color:#8be9fd;">Twython</span><span style="color:#f8f8f2;">(</span><span style="text-decoration:underline;font-style:italic;color:#8be9fd;">object</span><span style="color:#f8f8f2;">): </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">def </span><span style="color:#8be9fd;">__init__</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">params</span><span style="color:#f8f8f2;">): </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">&quot;&quot;&quot; </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">Store params and junk. Ordinarily more verbose. </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">&quot;&quot;&quot; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">self</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">params </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">params </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">def </span><span style="color:#8be9fd;">__getattr__</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">method_name</span><span style="color:#f8f8f2;">): </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;">&quot;&quot;&quot; </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">This is called every time a class method or property </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">is checked and/or called. </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;"> </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">In here we&#39;ll return a new function to handle what we </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">want to do. </span><span class="lol"></span><span style="color:#6272a4;"> </span><span style="color:#6272a4;">&quot;&quot;&quot; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">def </span><span style="color:#50fa7b;">get</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">self</span><span style="color:#f8f8f2;">, </span><span style="color:#ff79c6;">**</span><span style="font-style:italic;color:#ffb86c;">kwargs</span><span style="color:#f8f8f2;">): </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># Make our API calls, return data, etc </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if </span><span style="color:#f8f8f2;">method_name </span><span style="color:#ff79c6;">in </span><span style="color:#f8f8f2;">method_dictionary: </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">return </span><span style="color:#f8f8f2;">get</span><span style="color:#ff79c6;">.</span><span style="color:#8be9fd;">__get__</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">self</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">else</span><span style="color:#f8f8f2;">: </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># If the method isn&#39;t in our dictionary, act normal. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">raise </span><span style="font-style:italic;color:#66d9ef;">AttributeError</span><span style="color:#f8f8f2;">, method_name </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;"># Instantiate... </span><span class="lol"></span><span style="color:#f8f8f2;">twitter </span><span style="color:#ff79c6;">= </span><span style="color:#50fa7b;">Twython</span><span style="color:#f8f8f2;">() </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;"># Call an arbitrary method. </span><span class="lol"></span><span style="color:#f8f8f2;">twitter</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">getPublicTimeline</span><span style="color:#f8f8f2;">() </span><span class="lol"></span></code></pre> <p>The source above is fairly well commented, but feel free to ask in the comments if you need further explanation. This resulted in a much more maintainable version of Twython - for each function that's listed in a hash table, we can now just take any named parameter and url-encode/combine it. This makes Twython pretty API-change agnostic of the entire Twitter API. Pretty awesome sauce, no?</p> How to properly ask a girl to Homecoming Thu, 14 Oct 2010 00:00:00 +0000 https://rymc.io/blog/2010/how-to-properly-ask-a-girl-to-homecoming/ https://rymc.io/blog/2010/how-to-properly-ask-a-girl-to-homecoming/ <p><em>Before we get into this, I'll note that this post is actually about mobile web development, not the romantic adventures of anyone in particular. You've been forewarned.</em></p> <p>Sometimes in life, you feel as though doing things the &quot;ordinary&quot; way just doesn't cut it. I've found this to be a common theme in my work, and I'm not the only one who operates this way. One example is my brother (one of them, at least). I recently dropped by my parents house for a visit as I plan my next trip, and I was catching up with him as we walked up to a store. We somehow ended up discussing his upcoming Homecoming dance, and how there's some girl that he wanted to ask, but in doing so go above and beyond the normal methods.</p> <p>I sat there and sarcastically threw ideas at him for about 5 minutes, as I'm prone to do when I'm bored. One of them involved using a spare iPhone I recently came into possession of to display some message to her. He decided to take it one step further, and asked me if I could help him craft a simple game that, upon winning, would ask her to the dance.</p> <p>I found myself thinking the idea was pretty awesome. To make <em>that</em> long story short, we built it, she liked it, he's happy, I'm awesome, and the world keeps on a-spinnin'. The game can be found at the following link - best viewed on an iPhone, but should still be beautifully awesome on any device (Android, PC, WebOS, etc):</p> <p><a href="http://venodesigns.net/n/">Give the game a whirl</a></p> <h2 id="oh-hallo-thar-mobile-safari">Oh hallo thar Mobile Safari</h2> <p>So, making the game was an interesting experience. I opted to build it in a Webkit wrapper - HTML/CSS/Javascript are a much faster way to prototype, and I wasn't looking to spend 12 hours on this project (I somewhat did, but not for the reasons one would think offhand). The process we took was pretty simple - sat down, sketched out the game play and general UI, then started building; I, of course, actually implementing things in the tech sense, and my brother handling the artwork/color scheme/etc. Building the game, all said and done, took us around an hour and a half from start to finish.</p> <p>At this point, I figured, let's <a href="http://phonegap.com/">Phonegap</a> it up and call it a day. This would've worked, except for the fact that Apple has apparently blocked newer i(Phone|OS) SDKs from running on Leopard. I've since upgraded to Snow Leopard, but seriously, this seems a bit overbearing to me.</p> <p>Of course, at that point, I wasn't going to upgrade my OS then and there, as it was past midnight. I experimented with reverting the firmware back to the 2.x series, which I'm able to build and target to, but there were a myriad of problems there due (apparently) to some earlier jailbreaking that had occurred with the device. Go figure.</p> <p>As a last resort, I decided to bookmark the app on the phone to &quot;install&quot; it. The one caveat with this approach is that it'd require an internet connection upon opening it up every time. What'd be ideal is if we could jam the entire thing into the Mobile Safari cache - there's a simple trick here that'll make life easier. I found that Mobile Safari, by default, won't cache sites like you think it would, but you can supply it with an HTML5 Cache Manifest that'll be respected by the browser, forcing everything to get shoved in the cache.</p> <p>The cache isn't huge, though, and the initial load shouldn't be huge as a principal. There are a lot of external scripts used here, mainly to automate smooth CSS transitions so the device hardware accelerates animations, so the entire app is roughly ~130kb. Small, in this case, but the limitations were worth discussing.</p> <p>To get around cold cache loads, the single image used in the game (the strawberry) is Base64 encoded into the html file itself, and all scripts are also placed inline. Combine this with a few Apache directives, and it loads quick enough the first time. The final trick is the following:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#f8f8f2;"># At the top of your file, specify the cache manifest (your_app.html) </span><span class="lol"></span><span style="color:#f8f8f2;">&lt;html manifest=&quot;dragonfruit.manifest&quot;&gt; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"># The cache manifest itself (dragonfruit.manifest) </span><span class="lol"></span><span style="color:#f8f8f2;">CACHE MANIFEST </span><span class="lol"></span><span style="color:#f8f8f2;">index.html </span><span class="lol"></span><span style="color:#f8f8f2;">apple-touch-icon-precompressed.png </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"># Needs to be sent back to the browser as a header for the manifest (.htaccess, etc) </span><span class="lol"></span><span style="color:#f8f8f2;">AddType text/cache-manifest .manifest </span><span class="lol"></span></code></pre> <p>In there, we modify three files. In the app itself, specify the location of the cache manifest; the second set is an example cache manifest you can borrow (the icon is used in the bookmark flow for the iPhone); the final piece is a header that needs to be sent downstream to the browser when the manifest is requested.</p> <p>Voila, we've installed an app onto the iPhone without compiling anything. Shove it all into the cache, and it can sit there for quite some time - if we were to combine it with some localStorage action, it's an (almost) potent combination. It still doesn't beat the App Store for distribution, but hey, it worked.</p> ...and what's the deal with San Francisco? (along with my travels) Wed, 06 Oct 2010 00:00:00 +0000 https://rymc.io/blog/2010/and-whats-the-deal-with-san-francisco-along-with-my-travels/ https://rymc.io/blog/2010/and-whats-the-deal-with-san-francisco-along-with-my-travels/ <p>It's been a good two months since I last updated this. Ordinarily I'd be a little angry that I let it slide for that long, but in this case I think it's warranted. For those interested, here's a summary of what I've been up to, along with my thoughts on San Francisco in general.</p> <p>Since leaving Webs.com (see my <a href="http://venodesigns.net/2010/08/02/moving-right-along/">previous post</a> for more information on that), I've been pretty much all over the United States. About a week after leaving Webs, I embarked on an epic road trip with a friend that went from North Carolina all the way to California. Driving across the USA is a fun journey, one that I recommend everyone take at some point. The midwest is a surprisingly desolate place; never before have I seen so many sex shops randomly dotting the countryside.</p> <p>Out of all the states that we passed through, a choice few gave me some of the best memories. Louisville, Kentucky, was a fun stop, as I got to finally meet up with my long time friend (from my old IRC days) <a href="http://www.zachwinter.com/">Zach Winter</a>. We used to fight like brothers back in the day, but he's become a pretty awesome web developer/designer in the time I've known him, so between that and talking about old IRC crap we probably wasted a good four hours there. Riverton, Wyoming was pretty cool, as it's a totally small town in the middle of nowhere with an indian casino. Driving through Yellowstone was something to behold, and the same goes for the desert-esque areas. I could go on here, but there's enough to write a small novel at this point.</p> <p>California was an interesting time period, and is actually the primary reason I'm even writing this post. I enjoy getting out and trying new areas of the world, traveling and exploring is a truly exhilarating experience that, in many regards, can't be matched. As is (seemingly) the norm, being a developer, I naturally headed to San Francisco. The city (and the &quot;Valley&quot; in general) are routinely hyped as being an incredible experience if you're in the industry. Living there taught me two things: I have a lot of reservations about the &quot;startup&quot; industry as a whole, and San Francisco <em>as an area to live in</em> isn't all it's hyped up to be.</p> <p>I'm putting my reservations on hold for another post, as I'd like to more carefully formulate those into something more concise to read. In terms of the living area, though, I'll put it bluntly: San Francisco is a trashy, over-hyped city that masquerades as a clean, eco-friendly environment where there's a seemingly open exchange of new ideas every day.</p> <h2 id="before-the-inevitable-shit-storm-starts-here">Before the inevitable shit storm starts here...</h2> <p>Let's pick apart that statement.</p> <p>Walk down any street in the Mission, SOMA, or even North Beach, and look at the amount of trash on the ground. Now, with a straight face, tell me that San Francisco is a clean city - I'll bet money that you can't do it. This is counter to a city like New York City (NYC); with NYC, it's a trash fest, sure, but nobody is going to try and tell you that it's actually a clean city. After living in the DC area for most of my life, and checking out Chicago, Boston, and Seattle (all of which are fairly clean <em>in their nicer areas</em>, which I'm comparing to the nicer areas of San Francisco), I'd say the same sentiment goes for those cities.</p> <p>I'll cede the point that this issue can go either way depending on who you talk to, but my experience was that most people tried to claim it's a clean city, and it quite frankly astounded me. Your mileage on this one may vary, and I invite you to judge for yourself.</p> <p>What follows is a dissection of some of the allures of San Francisco living, at least from what I gathered through the various groups I hung out with in my time there. We'll go one by one here. Feel free to grab a snack if you're going to read any further, by the way.</p> <h2 id="the-tech-scene">The tech scene</h2> <p>This one could be construed as a personal preference, so I'll keep this short. The tech scene in San Francisco bothers me much more than I ever thought possible. Perhaps I just went to the Valley at a low point in history, but seriously, what the hell is with the amount of startups building on top of something like Twitter's API? Beyond that, how many god damn social networks need to re-invent the fucking wheel before someone finally wakes up and decides that enough is enough?</p> <p>Hell, I can even deal with the existence of these things, but the fact that you're <em>taking funding</em> for such concepts? You do realize there are more important things in the world than blowing a couple hundred grand on sharing pictures through Twitter, right?</p> <p>There are a lot of cool ideas and new technologies being developed out in San Francisco, but for every awesome idea, there's about four ridiculous ones that are setting themselves up for failure by doing something as stupid as relying on the API of a network that has a history of letting people do all the discovery work for them, then building their own version.</p> <p>Tech companies (both good and bad) can exist anywhere, but it's a myth that only great tech companies exist (and can exist) in San Francisco. Live where you actually enjoy living and don't feel pressured to accept and praise an area based on a set-forth notion that it's the end-all-be-all of an industry.</p> <h2 id="the-food-scene">The food scene</h2> <p>Huzzah, burritos. What's there to get for lunch? Oh... burritos. Oh, wait, no, there's about three different French places all serving the same stuff, too, so I suppose we could do that for lunch.</p> <p>Past nine o'clock? Nevermind half those places if you want a quick bite, they're either closed or going to be closing at that time. So your options then become... oh, go into a bar, order a pizza, or get another burrito. Such incredible choices in a city supposedly known for its food choices - I definitely never had those choices anywhere else! Why would I ever leave this place?</p> <h2 id="the-people">The people</h2> <p>I met a lot of awesome people in San Francisco, don't get me wrong. Thing is, I've met a lot of awesome people in <em>every</em> city I've been to. This is just part of society in general - no matter where you go, there's people you like, and people you don't like.</p> <p>With that in mind, let's take an objective look at San Francisco. 90% of the people I met were transplants, and they all came for one of two reasons: technology (the startup world specifically), or some artistic venture that hasn't panned out and they heard that San Francisco is an &quot;accepting&quot; city. This all ends up melding into a very &quot;me too&quot; culture, where it's nothing but people clamoring for attention around their newest idea.</p> <p>This wouldn't even be that big of a deal if it wasn't compounded by the fact that everyone essentially circle-jerks one another on these concepts to a ridiculous degree. Yeah, you think you can form a real business around something like a Firefox browser extension? Please take a seat over there, your head has been in the clouds for far too long (no pun intended).</p> <h2 id="get-to-the-point-please">Get to the point please</h2> <p>San Francisco feels incredibly fake, and gets a free pass due to the overall net worth of the Valley. The city is an incredibly trashy place to live, it has an incredibly overbearing &quot;me too&quot; culture, and almost everything that's supposedly great about the city can easily be found in other areas that are cleaner and provide a better overall living experience.</p> <p>It's funny, really. If you try to explain any of this to anyone living San Francisco, there's a good chance you'll send them into a mode where they feel the need to defend the aforementioned points. The only other city I've found where people get that extreme is - surprise, surprise - New York City. San Francisco is like the punk kid in school who, even though they're really no different, felt the need to dress completely different and act out to feel individual.</p> <p>I suppose that's worked for people throughout history, though, so there's not too much more to say on the matter. If you need me, though, I'll be traveling around to cities that are a bit more pleasant and fun overall (and, hey, if you enjoy living in San Francisco, more power to you, but you just make me scratch my head).</p> Moving right along... Mon, 02 Aug 2010 00:00:00 +0000 https://rymc.io/blog/2010/moving-right-along/ https://rymc.io/blog/2010/moving-right-along/ <p>For those who haven't heard the news, my last day at <a href="http://webs.com/">Webs.com</a> was this past Thursday (July 29th, 2010). After three and a half years, I figured it was finally time to move on.</p> <p>It's interesting to reflect on such a large portion of a lifespan. When I joined Webs (then Freewebs) back in 2007, I was actually homeless, and was working a part time job at a Blockbuster. I had gotten pretty tired of that routine, and started cold-emailing companies. Out of all of them, Freewebs was the one who responded and invited me out to interview with them.</p> <p>I remember being shocked when, a few weeks later, I received an offer to join up. Y'see, it's no secret that I'm pretty much self taught through and through when it comes to web design - with no formal training, I was surprised that anyone would consider me for a position in this field. Accepting that offer led me down a long path that's definitely shaped a lot of who I am today, both professionally and personally.</p> <p>Prior to joining Webs, I knew HTML and CSS well, but when it came to actual programming I had just dabbled here and there, never gaining a solid understanding of the craft in general. Working at Webs was, in many ways, a great substitution for a college experience. The amount of things I learned is staggering - data analysis, in depth design techniques, and more.</p> <p>Granted, while all that certainly contributed to my growth, I worked my ass off while I was there; I guess I'm simply stating that the environment is easily one of the best possible places to ever work if you're a developer. All those startup companies in San Francisco that seem so great to work at? Good luck finding anything similar in the Washington DC/Metro area... short of <a href="http://webs.com/">Webs.com</a>.</p> <h2 id="so-what-s-next">So what's next?</h2> <p>I'm tired of Washington DC. It's a great city with a culture all its own, but I've grown up around all of this, and I want to see more of the world in general. The west coast is very appealing to me, so I'm most likely headed out there. As my career situation develops, I'll update this space accordingly.</p> <p>Besides all of that, I've got my open source work to maintain, as well as some other cool projects in the pipeline. This isn't the end, but moreso the beginning of an epic awesome adventure.</p> <p>I'm awesome.</p> On Date/Time/DateTime in Ruby, and why they suck Sun, 18 Jul 2010 00:00:00 +0000 https://rymc.io/blog/2010/on-datetimedatetime-in-ruby-and-why-they-suck/ https://rymc.io/blog/2010/on-datetimedatetime-in-ruby-and-why-they-suck/ <div class="blog-edit-note"> <h4>This is Old!</h4> <p>I wrote this when I was much younger, and arguably, a bit of a jackass (pardon my French). I keep it up for personal reasons, and there might be some technical content of note here... so just please ignore the tone. </p> </div> <p>Yeah, there, I said it - this is a stupid situation for a language to be in. The concepts of <code>Date</code>, <code>Time</code>, and <code>DateTime</code> are all pretty well related. <code>Date</code> is a point in <code>Time</code>, <code>DateTime</code> is a really nice representation of <code>Date</code> and <code>Time</code> mashed together.</p> <p>In a world that actually makes sense, you'd be able to easily convert between these types (e.g: <code>DateTime</code> should be able to easily convert over to a <code>Time</code> object). Some people might suggest patching the aforementioned classes (like Rails does, for parsing relative dates), but this just feels like an incredibly hacky solution.</p> <p>Now, normally, I'm not much of a Ruby guy, give me Javascript any day. However, there are a lot of awesome projects written in Ruby, and it's hard to deny that they've easily got the best packaging solution for any language. As it so happens, earlier this week I was hacking on a little <a href="http://www.sinatrarb.com/">Sinatra</a> side project. <a href="http://datamapper.org/">DataMapper</a> was my ORM of choice here, because it's just so beautifully plug and play.</p> <p>In the example app we'll look at, it's your basic Post/Comment scenario. For each Post/Comment, we want to be able to render the relative time ago that the item in question was submitted (e.g, &quot;16 hours ago&quot;, etc). Rails has conventions for this baked in by default (because they, y'know, enjoy polluting namespaces, go figure). When you're outside of Rails and want to do this, it's somewhat more difficult.</p> <p>Originally I started by storing the creation time as <code>DateTime</code>; makes sense, we <em>should</em> be able to convert this to <code>Time</code> to do easy comparisons against <code>Time.now</code>, right?</p> <p>Well, uhh, no. Definitely not that simple. After some digging around, I discovered the <em><code>dm-types</code></em> gem, which adds some extra types to DataMapper. One of these types is known as <em><code>EpochTime</code></em>, which is (you guessed it) the time since Epoch that the entry was created.</p> <p>With that, we can pretty much stay within the realm of <code>Time</code>. Ripping apart <code>ActionView</code> gives us a good base to work with on the act of getting a relative string; putting it all together, we get the following Sinatra app (two files - the main Sinatra app, and our custom <code>date_helper</code> library).</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">require </span><span style="color:#f1fa8c;">&#39;rubygems&#39; </span><span class="lol"></span><span style="color:#ff79c6;">require </span><span style="color:#f1fa8c;">&#39;sinatra&#39; </span><span class="lol"></span><span style="color:#ff79c6;">require </span><span style="color:#f1fa8c;">&#39;dm-core&#39; </span><span class="lol"></span><span style="color:#ff79c6;">require </span><span style="color:#f1fa8c;">&#39;dm-migrations&#39; </span><span class="lol"></span><span style="color:#ff79c6;">require </span><span style="color:#f1fa8c;">&#39;dm-serializer&#39; </span><span class="lol"></span><span style="color:#ff79c6;">require </span><span style="color:#f1fa8c;">&#39;dm-types&#39; </span><span class="lol"></span><span style="color:#ff79c6;">require </span><span style="color:#f1fa8c;">&#39;date_helper&#39; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#66d9ef;">DataMapper</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">setup(</span><span style="color:#bd93f9;">:default</span><span style="color:#f8f8f2;">, { </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">:adapter </span><span style="color:#f8f8f2;">=&gt; </span><span style="color:#f1fa8c;">&#39;mysql&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">:database </span><span style="color:#f8f8f2;">=&gt; </span><span style="color:#f1fa8c;">&#39;database_name&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">:username </span><span style="color:#f8f8f2;">=&gt; </span><span style="color:#f1fa8c;">&#39;database_username&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">:password </span><span style="color:#f8f8f2;">=&gt; </span><span style="color:#f1fa8c;">&#39;database_password&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">:host </span><span style="color:#f8f8f2;">=&gt; </span><span style="color:#f1fa8c;">&#39;localhost&#39; </span><span class="lol"></span><span style="color:#f8f8f2;">}) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">class </span><span style="text-decoration:underline;color:#8be9fd;">Post </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">include </span><span style="font-style:italic;color:#66d9ef;">DataMapper</span><span style="color:#ff79c6;">::</span><span style="color:#f8f8f2;">Resource </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">property </span><span style="color:#bd93f9;">:id</span><span style="color:#f8f8f2;">, </span><span style="color:#ffffff;">Serial </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">property </span><span style="color:#bd93f9;">:username</span><span style="color:#f8f8f2;">, </span><span style="color:#ffffff;">String </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">property </span><span style="color:#bd93f9;">:entry</span><span style="color:#f8f8f2;">, </span><span style="color:#ffffff;">Text </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">property </span><span style="color:#bd93f9;">:created_at</span><span style="color:#f8f8f2;">, </span><span style="color:#ffffff;">EpochTime </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">has n, </span><span style="color:#bd93f9;">:comments </span><span class="lol"></span><span style="color:#ff79c6;">end </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">class </span><span style="text-decoration:underline;color:#8be9fd;">Comment </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">include </span><span style="font-style:italic;color:#66d9ef;">DataMapper</span><span style="color:#ff79c6;">::</span><span style="color:#f8f8f2;">Resource </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">property </span><span style="color:#bd93f9;">:id</span><span style="color:#f8f8f2;">, </span><span style="color:#ffffff;">Serial </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">property </span><span style="color:#bd93f9;">:username</span><span style="color:#f8f8f2;">, </span><span style="color:#ffffff;">String </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">property </span><span style="color:#bd93f9;">:entry</span><span style="color:#f8f8f2;">, </span><span style="color:#ffffff;">Text </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">property </span><span style="color:#bd93f9;">:created_at</span><span style="color:#f8f8f2;">, </span><span style="color:#ffffff;">EpochTime </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">belongs_to </span><span style="color:#bd93f9;">:post </span><span class="lol"></span><span style="color:#ff79c6;">end </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="font-style:italic;color:#66d9ef;">DataMapper</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">finalize </span><span class="lol"></span><span style="font-style:italic;color:#66d9ef;">DataMapper</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">auto_upgrade! </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;"># Requests/views/etc </span><span class="lol"></span><span style="color:#f8f8f2;">get </span><span style="color:#f1fa8c;">&#39;/&#39; </span><span style="color:#ff79c6;">do </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">erb </span><span style="color:#bd93f9;">:index </span><span class="lol"></span><span style="color:#ff79c6;">end </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;">post </span><span style="color:#f1fa8c;">&#39;/post/new&#39; </span><span style="color:#ff79c6;">do </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffb86c;">@post </span><span style="color:#ff79c6;">= </span><span style="font-style:italic;color:#66d9ef;">Post</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">create( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">:username </span><span style="color:#f8f8f2;">=&gt; params[</span><span style="color:#bd93f9;">:username</span><span style="color:#f8f8f2;">], </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">:entry </span><span style="color:#f8f8f2;">=&gt; params[</span><span style="color:#bd93f9;">:entry</span><span style="color:#f8f8f2;">], </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#bd93f9;">:created_at </span><span style="color:#f8f8f2;">=&gt; </span><span style="font-style:italic;color:#66d9ef;">Time</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">now</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">to_i </span><span style="color:#6272a4;"># See? Epoch-goodness. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if </span><span style="color:#ffb86c;">@post </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># .create automatically saves the post; now override the created_at </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># point before we pass back our JSON object to the view. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#6272a4;"># &quot;distance_of_time_in_words&quot; is from date_helper.rb </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffb86c;">@post</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">created_at </span><span style="color:#ff79c6;">=</span><span style="color:#f8f8f2;"> distance_of_time_in_words(</span><span style="color:#ffb86c;">@post</span><span style="color:#f8f8f2;">[</span><span style="color:#bd93f9;">:created_at</span><span style="color:#f8f8f2;">]</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">to_i</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffb86c;">@post</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">to_json </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">else </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">{</span><span style="color:#bd93f9;">:error </span><span style="color:#f8f8f2;">=&gt; </span><span style="color:#bd93f9;">true</span><span style="color:#f8f8f2;">}</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">to_json </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">end </span><span class="lol"></span><span style="color:#ff79c6;">end </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;"># Get views for comments, posts, etc </span><span class="lol"></span></code></pre><pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;"># Proudly (or shamefully, depending on how you look at it) ripped right out </span><span class="lol"></span><span style="color:#6272a4;"># of ActionView and modified to base it all on the Epoch. Give credit where credit is due. </span><span class="lol"></span><span style="color:#6272a4;"># from_time expects another judgement from Epoch (e.g, Time.whatever.to_i) </span><span class="lol"></span><span style="color:#ff79c6;">def </span><span style="color:#50fa7b;">distance_of_time_in_words</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">from_time</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">to_time </span><span style="color:#ff79c6;">= </span><span style="font-style:italic;color:#66d9ef;">Time</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">now</span><span style="color:#ff79c6;">.</span><span style="color:#ffffff;">to_i</span><span style="color:#f8f8f2;">, </span><span style="font-style:italic;color:#ffb86c;">include_seconds </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">false</span><span style="color:#f8f8f2;">) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">distance_in_minutes </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">(((to_time </span><span style="color:#ff79c6;">-</span><span style="color:#f8f8f2;"> from_time)</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">abs)</span><span style="color:#ff79c6;">/</span><span style="color:#bd93f9;">60</span><span style="color:#f8f8f2;">)</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">round </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">distance_in_seconds </span><span style="color:#ff79c6;">= </span><span style="color:#f8f8f2;">((to_time </span><span style="color:#ff79c6;">-</span><span style="color:#f8f8f2;"> from_time)</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">abs)</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">round </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">case</span><span style="color:#f8f8f2;"> distance_in_minutes </span><span class="lol"></span><span style="color:#ff79c6;">when </span><span style="color:#bd93f9;">0</span><span style="color:#ff79c6;">..</span><span style="color:#bd93f9;">1 </span><span class="lol"></span><span style="color:#ff79c6;">return </span><span style="color:#f8f8f2;">(distance_in_minutes</span><span style="color:#ff79c6;">==</span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">) </span><span style="color:#ff79c6;">? </span><span style="color:#f1fa8c;">&#39;less than a minute ago&#39; </span><span style="color:#ff79c6;">: </span><span style="color:#f1fa8c;">&#39;1 minute ago&#39; </span><span style="color:#ff79c6;">unless</span><span style="color:#f8f8f2;"> include_seconds </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">case</span><span style="color:#f8f8f2;"> distance_in_seconds </span><span class="lol"></span><span style="color:#ff79c6;">when </span><span style="color:#bd93f9;">0</span><span style="color:#ff79c6;">..</span><span style="color:#bd93f9;">5 </span><span style="color:#ff79c6;">then </span><span style="color:#f1fa8c;">&#39;less than 5 seconds ago&#39; </span><span class="lol"></span><span style="color:#ff79c6;">when </span><span style="color:#bd93f9;">6</span><span style="color:#ff79c6;">..</span><span style="color:#bd93f9;">10 </span><span style="color:#ff79c6;">then </span><span style="color:#f1fa8c;">&#39;less than 10 seconds ago&#39; </span><span class="lol"></span><span style="color:#ff79c6;">when </span><span style="color:#bd93f9;">11</span><span style="color:#ff79c6;">..</span><span style="color:#bd93f9;">20 </span><span style="color:#ff79c6;">then </span><span style="color:#f1fa8c;">&#39;less than 20 seconds ago&#39; </span><span class="lol"></span><span style="color:#ff79c6;">when </span><span style="color:#bd93f9;">21</span><span style="color:#ff79c6;">..</span><span style="color:#bd93f9;">40 </span><span style="color:#ff79c6;">then </span><span style="color:#f1fa8c;">&#39;half a minute ago&#39; </span><span class="lol"></span><span style="color:#ff79c6;">when </span><span style="color:#bd93f9;">41</span><span style="color:#ff79c6;">..</span><span style="color:#bd93f9;">59 </span><span style="color:#ff79c6;">then </span><span style="color:#f1fa8c;">&#39;less than a minute ago&#39; </span><span class="lol"></span><span style="color:#ff79c6;">else </span><span style="color:#f1fa8c;">&#39;1 minute ago&#39; </span><span class="lol"></span><span style="color:#ff79c6;">end </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">when </span><span style="color:#bd93f9;">2</span><span style="color:#ff79c6;">..</span><span style="color:#bd93f9;">45 </span><span style="color:#ff79c6;">then </span><span style="color:#f1fa8c;">&quot;</span><span style="color:#ff79c6;">#{distance_in_minutes}</span><span style="color:#f1fa8c;"> minutes ago&quot; </span><span class="lol"></span><span style="color:#ff79c6;">when </span><span style="color:#bd93f9;">46</span><span style="color:#ff79c6;">..</span><span style="color:#bd93f9;">90 </span><span style="color:#ff79c6;">then </span><span style="color:#f1fa8c;">&#39;about 1 hour ago&#39; </span><span class="lol"></span><span style="color:#ff79c6;">when </span><span style="color:#bd93f9;">90</span><span style="color:#ff79c6;">..</span><span style="color:#bd93f9;">1440 </span><span style="color:#ff79c6;">then </span><span style="color:#f1fa8c;">&quot;about </span><span style="color:#ff79c6;">#{(distance_in_minutes / </span><span style="color:#bd93f9;">60</span><span style="color:#ff79c6;">).round}</span><span style="color:#f1fa8c;"> hours ago&quot; </span><span class="lol"></span><span style="color:#ff79c6;">when </span><span style="color:#bd93f9;">1441</span><span style="color:#ff79c6;">..</span><span style="color:#bd93f9;">2880 </span><span style="color:#ff79c6;">then </span><span style="color:#f1fa8c;">&#39;1 day ago&#39; </span><span class="lol"></span><span style="color:#ff79c6;">when </span><span style="color:#bd93f9;">2881</span><span style="color:#ff79c6;">..</span><span style="color:#bd93f9;">43220 </span><span style="color:#ff79c6;">then </span><span style="color:#f1fa8c;">&quot;</span><span style="color:#ff79c6;">#{(distance_in_minutes / </span><span style="color:#bd93f9;">1440</span><span style="color:#ff79c6;">).round}</span><span style="color:#f1fa8c;"> days ago&quot; </span><span class="lol"></span><span style="color:#ff79c6;">when </span><span style="color:#bd93f9;">43201</span><span style="color:#ff79c6;">..</span><span style="color:#bd93f9;">86400 </span><span style="color:#ff79c6;">then </span><span style="color:#f1fa8c;">&#39;about 1 month ago&#39; </span><span class="lol"></span><span style="color:#ff79c6;">when </span><span style="color:#bd93f9;">86401</span><span style="color:#ff79c6;">..</span><span style="color:#bd93f9;">525960 </span><span style="color:#ff79c6;">then </span><span style="color:#f1fa8c;">&quot;</span><span style="color:#ff79c6;">#{(distance_in_minutes / </span><span style="color:#bd93f9;">43200</span><span style="color:#ff79c6;">).round}</span><span style="color:#f1fa8c;"> months ago&quot; </span><span class="lol"></span><span style="color:#ff79c6;">when </span><span style="color:#bd93f9;">525961</span><span style="color:#ff79c6;">..</span><span style="color:#bd93f9;">1051920 </span><span style="color:#ff79c6;">then </span><span style="color:#f1fa8c;">&#39;about 1 year ago&#39; </span><span class="lol"></span><span style="color:#ff79c6;">else </span><span style="color:#f1fa8c;">&quot;over </span><span style="color:#ff79c6;">#{(distance_in_minutes / </span><span style="color:#bd93f9;">525600</span><span style="color:#ff79c6;">).round}</span><span style="color:#f1fa8c;"> years ago&quot; </span><span class="lol"></span><span style="color:#ff79c6;">end </span><span class="lol"></span><span style="color:#ff79c6;">end </span><span class="lol"></span></code></pre> The perception of "Inception" Sun, 18 Jul 2010 00:00:00 +0000 https://rymc.io/blog/2010/the-perception-of-inception/ https://rymc.io/blog/2010/the-perception-of-inception/ <p>It's no secret that I primarily use this space to talk about programming and the web at large. It's a huge part of who I am, but it's not <em>all</em> that I am. Sometimes I see something so incredibly cool and inspiring that I just feel the need to talk about it; such is the case with the movie &quot;Inception&quot;.</p> <ul> <li><em>Please note: the following is full of spoilers if you haven't seen the movie yet!</em></li> <li><em>Believe me when I say that this is a movie that you should avoid having spoiled at all costs, because it's just such an incredibly fun ride. This post will also make little sense unless you've actually seen the movie. Seriously, go see the movie if you haven't already, then come on back, this post isn't going anywhere.</em></li> </ul> <h2 id="what-really-happened-in-this-movie">What really happened in this movie?</h2> <p>I'll start by making sure this is noted right off the bat: I believe that the <a href="http://en.wikipedia.org/wiki/Inception_(film)#Plot">Wikipedia explanation</a> of the ending of the movie is completely wrong. The article states that, in the end of the movie, everyone wakes up on the plane just fine. The genius part about this movie is that Christopher Nolan, the director, rarely strayed towards one ending; everything throughout the movie is meant to spark discussion about the final ending scene, where the top may or <em>may not have</em> fallen.</p> <p>Throughout the film, Nolan obviously took great care to make the audience aware of the mathematics surrounding the way the levels relate to one another. The idea with this is that, in the end, even though Cobb is left underwater in the van, the time that he spent in the sublevels beyond could be as little as a few minutes there, giving enough space where he could've magically gotten back up through the levels.</p> <p>That's all nothing but a ploy, though. Here's the thing - in the ending, I believe Cobb never actually woke up. Let's look at the film this way...</p> <h2 id="cobb-and-his-children">Cobb and his children</h2> <p>First off, the kids and the shot at the end. Did anybody else notice anything strange about it? <em>The kids never aged!</em>. Cobb goes back to his place and it's as if he never left. We're purposely never given a timeline as to <em>when</em> Cobb and Mal had their little incident with Mal having a bad case of PMS and jumping out a window. I'd say it's safe to assume that this has to have been at least a few months to a year, though; we're shown shots of Cobb talking to various psychiatrists, attempting to persuade people that he did not, in fact, kill Mal. If this is in relation to court proceedings of some kind... well, we all know how long those acts can take.</p> <p>That said, we really don't even need to consider that, it's just a nice piece to note. What we <em>should really</em> be looking at is that, throughout the film, every time Cobb sees his kids in a dream, they're the same (just like in the end of the film). It's his last memory of his kids; in some way, they're his inception.</p> <p>Keep this in mind, because the rabbit hole goes a bit deeper.</p> <h2 id="cobb-and-saito-honoring-the-agreement">Cobb and Saito: honoring the agreement</h2> <p>When the team is in the first sublevel, Saito takes a bullet to the chest. There's a scene where he specifically states that no matter what happens, he'll be sure to <em>honor their agreement</em>. Of course, at that point in time, we all have reason to believe that Saito is going to make it out alright, Cobb will be able to get back into the US, and everyone will go on their merry way.</p> <p>As the film continues, though, Saito's condition worsens, and he eventually passes away in the third sublevel. This is a huge fact: Saito is now in <em>limbo</em>. Cobb, after finally killing Mal, stays behind in limbo to find Saito to make sure he <em>honors the agreement</em>.</p> <p>See a pattern here? In this case, there's more than one way to honor their agreement. Cobb eventually does find Saito, when Saito is much, much older. They speak of honoring their agreement, which is why Cobb came, and <em>Saito reaches for the gun</em>. This is the most notable piece of their entire level of interaction throughout the movie - if dying in a sublevel when you're that far under puts you in limbo, what happens when you die <em>in limbo</em>?</p> <p>Of course, we're never directly told. Tricky, tricky Nolan, but we'll pin this down yet.</p> <h2 id="on-heaven-hell-and-unicorns">On Heaven, Hell, and Unicorns</h2> <p>Mal's dead, no? What's funny about Mal inside Cobb's subconscious is that she's located in the basement of his mind, an <em>eternal hell</em> that Cobb can't seem to get rid of, as if he was condemned to that fate for his action against Mal (trying inception on her first). Eventually, when they make it down to limbo, Cobb finally kills the idea of Mal - this is his redemption.</p> <p>Cobb is essentially painted as a fallen angel throughout the movie, barred from heaven. By <em>atoning</em> for his sins, he's allowed back in and everything is supposedly great. That said...</p> <p>What was really Cobb's Heaven? Real life, where he got back to see his kids? His one goal was to go home, to be with his family, but it's made apparent to us, the viewers, that when you're in limbo it's difficult (or almost impossible) to get out.</p> <h2 id="bringing-it-all-together">Bringing it all together</h2> <p>As I said before, Cobb never actually woke up. Saito reaching for the gun is meant to imply that he kills Cobb, setting him free - not in the sense of waking him up, but allowing Cobb to dream that which he's wanted for ages, to be back with his family (hence why, when he goes back to his kids, they're <em>exactly the same as in every other shot</em>).</p> <p>Nolan again takes great care to make sure this is difficult to prove - specifically, the scene where Cobb and Mal allow themselves to be run over by a damn train. This is supposed to imply that simply dying in limbo allows you to truly wake up. That said, unless Saito kills himself and Cobb (which seems a little odd - Saito knew the rules of the game, why wouldn't he just kill himself prior to Cobb finding him?), the final scene really does seems like Cobb is dreaming forever.</p> <p>See, Saito supposedly woke up just fine, made the call, everything's great. However, what guarantee is there that either of them woke up normally from <em>limbo</em>, the level that seriously messed up Mal? None.</p> <p>This all brings us to the final scene with the spinning top: it was a great move on Nolan's part to end it right as the thing supposedly topples, essentially forcing the entire discussion this article was even written about. Sure, we could assume that it signifies Cobb is alive and well, not dreaming.</p> <p>However, a friend of mine offered this suggestion, which I think really ties it all together: she proposed that the top falling was Cobb giving in to his dreams, accepting that reality as reality itself. This is utterly genius, and makes an insane amount of sense.</p> <p>The other theory that comes to mind is that it was Mal's totem originally, and she's now, for all intents and purposes, finally gone, a demon purged from his mind, allowing him to dream peacefully forever.</p> <p>This movie was an incredible mental trip, and one I definitely intend to watch again. What do you think? Feel free to leave comments below; they'll be moderated to deal with spam and trolls, but healthy discussion on this film is highly encouraged.</p> Rendering emails with Django templates Tue, 13 Jul 2010 00:00:00 +0000 https://rymc.io/blog/2010/rendering-emails-from-django-templates/ https://rymc.io/blog/2010/rendering-emails-from-django-templates/ <p>I talk a lot about Javascript on this blog: client-side, server-side, wherever the language can be used. It's not the only language I enjoy working in, though; Python has always had a bit of a soft spot with me, and with that comes some inevitable <a href="http://djangoproject.org/">Django</a> love.</p> <p>I use Django for a lot of my server side tasks where I don't feel I can safely use something like <a href="http://nodejs.org/">Node.js</a>. On top of being battle tested beyond belief, it's a veritable bag of magic tricks just waiting to be used. Take this one case I ran into recently: I'm building a service (stealth at the moment, stay tuned) that has to do something as simple as send out an email when a new user signs up.</p> <p>Now, that email should ideally contain a few things. Let's assume that, for a basic example, we're gonna just include their username, a brand new password, and their full name (how we'll use each of these is shown in depth below). Django makes sending mail pretty easy, using the <em><code>send_mail</code></em> function:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;"># Make sure you import this, otherwise... well, you get the idea. </span><span class="lol"></span><span style="color:#ff79c6;">from </span><span style="color:#f8f8f2;">django</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">core</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">mail </span><span style="color:#ff79c6;">import </span><span style="color:#f8f8f2;">send_mail </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;"># send_mail is pretty straightforward, as you can see. </span><span class="lol"></span><span style="color:#50fa7b;">send_mail</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;Title&#39;</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&quot;Body&quot;</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&quot;from_addy&quot;</span><span style="color:#f8f8f2;">, [</span><span style="color:#f1fa8c;">&quot;emails&quot;</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&quot;sent&quot;</span><span style="color:#f8f8f2;">, </span><span style="color:#f1fa8c;">&quot;to&quot;</span><span style="color:#f8f8f2;">], </span><span style="font-style:italic;color:#ffb86c;">fail_silently </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">True</span><span style="color:#f8f8f2;">) </span><span class="lol"></span></code></pre> <p>That's all well and good, but what if we want to do more than <em>just</em> a simple string for our body message? Ideally, we should be able to treat this like any other Django template. In practice, this is actually incredibly easy (and fun).</p> <p>The code should be fairly well documented, but for those who'd like a little more verbose of a walkthrough, it's pretty simple: instead of passing in a string, load up a template and pass it a rendering context. See, what got me the first time around is that the <em><code>render</code></em> method of <em><code>get_template</code></em> needs a Template Context to do the dirty work, <em>not a standard dict</em>.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;"># We should all know what this is used for by now. </span><span class="lol"></span><span style="color:#ff79c6;">from </span><span style="color:#f8f8f2;">django</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">core</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">mail </span><span style="color:#ff79c6;">import </span><span style="color:#f8f8f2;">send_mail </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;"># get_template is what we need for loading up the template for parsing. </span><span class="lol"></span><span style="color:#ff79c6;">from </span><span style="color:#f8f8f2;">django</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">template</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">loader </span><span style="color:#ff79c6;">import </span><span style="color:#f8f8f2;">get_template </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;"># Templates in Django need a &quot;Context&quot; to parse with, so we&#39;ll borrow this. </span><span class="lol"></span><span style="color:#6272a4;"># &quot;Context&quot;&#39;s are really nothing more than a generic dict wrapped up in a </span><span class="lol"></span><span style="color:#6272a4;"># neat little function call. </span><span class="lol"></span><span style="color:#ff79c6;">from </span><span style="color:#f8f8f2;">django</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">template </span><span style="color:#ff79c6;">import </span><span style="color:#f8f8f2;">Context </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;"># Some generic stuff to parse with </span><span class="lol"></span><span style="color:#f8f8f2;">username </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&quot;fry&quot; </span><span class="lol"></span><span style="color:#f8f8f2;">password </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&quot;password_yo&quot; </span><span class="lol"></span><span style="color:#f8f8f2;">full_name </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&quot;Philip J Fry&quot; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;"># Our send_mail call revisited. This time, instead of passing </span><span class="lol"></span><span style="color:#6272a4;"># a string for the body, we load up a template with get_template() </span><span class="lol"></span><span style="color:#6272a4;"># and render it with a Context of the variables we want to make available </span><span class="lol"></span><span style="color:#6272a4;"># to that template. </span><span class="lol"></span><span style="color:#50fa7b;">send_mail</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;Thanks for signing up!&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">get_template</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;templates_path/email.html&#39;</span><span style="color:#f8f8f2;">)</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">render</span><span style="color:#f8f8f2;">( </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">Context</span><span style="color:#f8f8f2;">({ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;username&#39;</span><span style="color:#f8f8f2;">: username, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;password&#39;</span><span style="color:#f8f8f2;">: password, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;full_name&#39;</span><span style="color:#f8f8f2;">: full_name </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">}) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">), </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&#39;admin@moxlee.com&#39;</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">[</span><span style="color:#f1fa8c;">&#39;users_email@gmail.com&#39;</span><span style="color:#f8f8f2;">], </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#ffb86c;">fail_silently </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">True </span><span class="lol"></span><span style="color:#f8f8f2;">) </span><span class="lol"></span></code></pre> <p>With that, here's an example of an email template - it's literally just a Django template, but ready for all your email attributes.</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#f8f8f2;">Welcome {{ full_name }}! </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;">Your username </span><span style="color:#ff79c6;">is </span><span style="color:#f8f8f2;">{{ username }} </span><span style="color:#ff79c6;">and </span><span style="color:#f8f8f2;">your password </span><span style="color:#ff79c6;">is </span><span style="color:#f8f8f2;">{{ password }}</span><span style="color:#ff79c6;">. </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">- </span><span style="color:#f8f8f2;">Management </span><span class="lol"></span></code></pre> <p>Django's templating system is incredibly flexible, and can easily be used for more than just your generic views. If you have any questions or suggestions, feel free to throw them in the comments!</p> You got your Base64 in my CSS! Thu, 17 Jun 2010 00:00:00 +0000 https://rymc.io/blog/2010/you-got-your-base64-in-my-css/ https://rymc.io/blog/2010/you-got-your-base64-in-my-css/ <p>If you're a developer working on a website that's getting any traffic, you'll inevitably come up against the problem of making sure you're as performant as possible on the front-end. You'll probably compress your Javascript and CSS, maybe refine the slower portions of your site after profiling them, and maybe (if you're smart) sprite your images.</p> <p>You see, one of the chief goals in terms of a highly performant site is to get the amount of HTTP requests as low as possible. Most people turn to CSS sprites; they're a great technique for designers, and fairly easy to understand. That said, <a href="http://blog.vlad1.com/2009/06/22/to-sprite-or-not-to-sprite/">they're not without their downsides</a>. They can become quite an unmaintainable mess if you're not careful - change the location of a few images, and you have to change the corresponding CSS declaration to match it. On a large site, you'll end up repeatedly putting together a puzzle that's wasting your time.</p> <p>What if you're working on a team? How do you manage to keep <em>one image</em> in sync across 2 people? 5 people? At this point, you're probably wondering why I'm even bothering to rant on this, since these points are all fairly well known. Well, here's the thing - there's an alternate solution, depending on how open to the concept you are.</p> <h2 id="what-the-hell-is-base64">What the hell is Base64?</h2> <p>Just about every modern browser supports the concept of <a href="http://en.wikipedia.org/wiki/Data_URI_scheme">Data URI Schemes</a>. With this, you can do the following:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#ff79c6;">body </span><span style="color:#f8f8f2;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">background</span><span style="color:#f8f8f2;">: </span><span style="color:#6be5fd;">transparent </span><span style="color:#8be9fd;">url</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">data:image/png;base64, </span><span class="lol"></span><span style="color:#f1fa8c;">iVBORw0KGgoAAAANSUhEUgAAAAEAAABnCAIAAABO2r+ZAAAA </span><span class="lol"></span><span style="color:#f1fa8c;">GXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAA </span><span class="lol"></span><span style="color:#f1fa8c;">AG9JREFUeNp8T9sNgDAIvJCu4Ay6rEO5hXEBP9yB0xLa0Fr9 </span><span class="lol"></span><span style="color:#f1fa8c;">aLgHBxTrdgpIASB8HoiAXf/VaJmSbbj1df1jnpyn1qfViF+e </span><span class="lol"></span><span style="color:#f1fa8c;">31T3Rxw8yzSez4u4u89medWMtWiZB197jYP8t1dv878u85T2 </span><span class="lol"></span><span style="color:#f1fa8c;">47oFGAC4z6tdlRsLNAAAAABJRU5ErkJggg==</span><span style="color:#f8f8f2;">) </span><span style="color:#6be5fd;">repeat-x top left</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;">} </span><span class="lol"></span></code></pre> <p>A bit ugly? Yeah, you might say so. Effective? Definitely. The above code simply generates a blue gradient that tiles perfectly. The really long block of text you see there is a Base64 encoded string that essentially gets transformed into your image. There's various different ways to generate the Base64 representation you need; if you want a really simple tool, check out <a href="https://addons.mozilla.org/en-US/firefox/addon/5611">this Firefox extension</a>.</p> <p>The format of a Data URI is fairly simple to understand - in this case, we're saying that this is a PNG, getting passed via Base64, and then the Base64 string itself. If it helps, think of it as a glorified function call.</p> <p>As I noted above, most modern browsers support this technique. The one caveat worth mentioning is that Internet Explorer 8 is limited on the size of a Data URI to around 32kb. It's not really recommended to use this technique for anything above that size, but hey, your life, do what you want.</p> <p>Oh, and Internet Explorer 6 and 7 have absolute zero support for this method.</p> <h2 id="don-t-fret-we-can-make-base64-encoding-work-in-ie6-7">Don't fret, we can make Base64 encoding work in IE6/7!</h2> <p>Internet Explorer (Explorer in general, really) supports another file type that <em>does</em> understand Base64: <strong>mhtml</strong>. We can use this trick to make IE6/7 do some awesome magic:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;">/* </span><span class="lol"></span><span style="color:#6272a4;">Content-Type: multipart/related; boundary=&quot;_&quot; </span><span class="lol"></span><span style="color:#6272a4;">--_ </span><span class="lol"></span><span style="color:#6272a4;">Content-Location:1 </span><span class="lol"></span><span style="color:#6272a4;">Content-Type: image/png </span><span class="lol"></span><span style="color:#6272a4;">Content-Transfer-Encoding:base64 </span><span class="lol"></span><span style="color:#6272a4;">iVBORw0KGgoAAAANSUhEUgAAAAEAAABnCAIAAABO2r+ZAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAG9JREFUeNp8T9sNgDAIvJCu4Ay6rEO5hXEBP9yB0xLa0Fr9aLgHBxTrdgpIASB8HoiAXf/VaJmSbbj1df1jnpyn1qfViF+e31T3Rxw8yzSez4u4u89medWMtWiZB197jYP8t1dv878u85T247oFGAC4z6tdlRsLNAAAAABJRU5ErkJggg== </span><span class="lol"></span><span style="color:#6272a4;">--_-- </span><span class="lol"></span><span style="color:#6272a4;">*/ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#ff79c6;">body </span><span style="color:#f8f8f2;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#66d9ef;">background</span><span style="color:#f8f8f2;">: </span><span style="color:#6be5fd;">transparent </span><span style="color:#8be9fd;">url</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAABnCAIAAABO2r+ZAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAG9JREFUeNp8T9sNgDAIvJCu4Ay6rEO5hXEBP9yB0xLa0Fr9aLgHBxTrdgpIASB8HoiAXf/VaJmSbbj1df1jnpyn1qfViF+e31T3Rxw8yzSez4u4u89medWMtWiZB197jYP8t1dv878u85T247oFGAC4z6tdlRsLNAAAAABJRU5ErkJggg==</span><span style="color:#f8f8f2;">) </span><span style="color:#6be5fd;">repeat-x top left</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">*</span><span style="font-style:italic;color:#66d9ef;">background</span><span style="color:#f8f8f2;">: </span><span style="color:#6be5fd;">transparent </span><span style="color:#8be9fd;">url</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;mhtml:http://absolute_url.com/css_file.css!1&#39;</span><span style="color:#f8f8f2;">) </span><span style="color:#6be5fd;">repeat-x top left</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;">} </span><span class="lol"></span></code></pre> <p>What we're doing here is fairly simple, but hilariously hacky. Take a look at the top of the file - it's a damn MIME header, with a little &quot;pointer&quot; (<em>content-location</em>) to our Base64 representation. We then just do a second background declaration for IE only that points <em>to the same stylesheet</em>, but <em>parses it as mhtml</em> instead. Our <em>!1</em> is simply an identifier pointing to our first declaration - subsequent encodings could use 2, 3, &quot;jackalope&quot;, whatever you want. Each &quot;block&quot; is defined by a &quot;separator&quot; or &quot;boundary&quot; - in this case, we're using &quot;_&quot;.</p> <p>I stumbled across this little trick while working on performance for <a href="http://webs.com/">Webs.com</a>. However, it seems I can't take credit for it - <a href="http://www.phpied.com/mhtml-when-you-need-data-uris-in-ie7-and-under/">Stoyan Stefanov</a> posted on this awhile back. However, finding his work led me to one barrier that I've yet to see a solution to.</p> <h2 id="ie7-on-windows-vista-oh-god-why">IE7 on Windows Vista - oh god, why?</h2> <p>It would seem that, due to some of the security settings on Vista (and maybe Windows 7), IE7 <a href="http://www.phpied.com/data-uris-mhtml-ie7-win7-vista-blues/">refuses to load the necessary styles</a>. Stefanov advocates making the browser <em>always</em> request the mhtml as a new file. This is counter to the goal, though - we want that file cached.</p> <p>Well, I happened to stumble on an easier solution (a combination of reading the MHTML spec and rearranging formats out of madness). Just make sure to end your IE hackery on a boundary declaration - the IE code above already does this: notice the &quot;--_--&quot; right before the end of the comment containing all the Base64 encodings for IE. This'll have IE7 parsing correctly everywhere, and we can all sleep soundly at night.</p> <h2 id="whew-awesome-now-what">Whew! Awesome, now what?</h2> <p>Well, that's for you to decide. This technique can be quite useful - at <a href="http://webs.com/">Webs.com</a>, I was able to cut the size of our sprite down immensely by relying on this trick for all the background tiling gradients on the page. In my humble opinion, this trick is good, but it works best when you combine it with a CSS sprite. I've found the best method is to split the usage down the middle - constant, rarely changing pieces become encoded, and the other pieces (testing-related, generally) get sprited out.</p> <p>With any luck, this post can help someone else out. It seems like there's a lot of information regarding this technique, but it's been haphazardly organized over time. This isn't quite a &quot;definitive&quot; guide, but it aims to be a kickass outline!</p> Quit incorrectly passing functions in Javascript Wed, 09 Jun 2010 00:00:00 +0000 https://rymc.io/blog/2010/quit-incorrectly-passing-functions-in-javascript/ https://rymc.io/blog/2010/quit-incorrectly-passing-functions-in-javascript/ <p>In my line of work, I edit a lot of legacy code, and with that I see a lot of the following:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;">// Bad practice. Can you see why? </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#50fa7b;">foo </span><span style="color:#ff79c6;">= </span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">() </span><span style="color:#ffffff;">{ </span><span style="color:#50fa7b;">alert</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&quot;bar&quot;</span><span style="color:#f8f8f2;">); </span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#8be9fd;">setTimeout</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&quot;foo()&quot;</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">2000</span><span style="color:#f8f8f2;">); </span><span class="lol"></span></code></pre> <p>That little piece of code is actually a really bad way of doing things, and (to me) is illustrative of when someone is working in Javascript and doesn't actually know what they're doing.</p> <p>For those who don't know (and would like to learn), <code>setTimeout</code> (and its sister, <code>setInterval</code>) can accept a string of code to be executed as an argument. Know what other method takes a string of code and executes it? <code>eval()</code> does. Generally, methods like these should be avoided, as they leave ways for arbitrary scripts to run if you're not careful.</p> <p>So how could the above code be improved? Take the following:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#50fa7b;">foo </span><span style="color:#ff79c6;">= </span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">() </span><span style="color:#ffffff;">{ </span><span style="color:#50fa7b;">alert</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&quot;bar&quot;</span><span style="color:#f8f8f2;">); </span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">// Better practice. Note how the function is passed this time. </span><span class="lol"></span><span style="color:#8be9fd;">setTimeout</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">foo</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">2000</span><span style="color:#f8f8f2;">); </span><span class="lol"></span></code></pre> <p>What's important to remember here is that *everything* in Javascript is an object. This may seem like grade school material, but it's surprising how many people miss it - since our function (<code>foo</code>) is an object, we can point to it like any other object. What we're essentially doing now is, instead of telling <code>setTimeout</code> &quot;hey, evaluate this script after this timer expires&quot;, just &quot;run this function when this timer expires&quot;. There's no need to invoke <code>eval()</code> for such a simple operation.</p> <h2 id="but-what-about-my-arguments">But what about my arguments?</h2> <p>Yes, you can still pass arguments with this form. Whereas before you'd make up a whole string to be executed, you'd now just run it with an anonymous function. For those of you using jQuery, this should feel very familiar:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="color:#6272a4;">// Bad practice. Why concoct such an ugly string? </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#50fa7b;">delayedLol </span><span style="color:#ff79c6;">= </span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">laugh</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#8be9fd;">setTimeout</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&quot;alert(&#39;Laughing: &quot; </span><span style="color:#ff79c6;">+ </span><span style="color:#ffffff;">laugh </span><span style="color:#ff79c6;">+ </span><span style="color:#f1fa8c;">&quot;&#39;)&quot;</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">2000</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#50fa7b;">delayedLol</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&quot;bwahahaha&quot;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#6272a4;">// Instead, pass around your argument (laugh) with anonymous functions. </span><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#50fa7b;">delayedLol </span><span style="color:#ff79c6;">= </span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#ffb86c;">laugh</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#8be9fd;">setTimeout</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">() </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">alert</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&#39;Laughing: &#39; </span><span style="color:#ff79c6;">+ </span><span style="color:#ffffff;">laugh</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">, </span><span style="color:#bd93f9;">2000</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#50fa7b;">delayedLol</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&quot;bwahahaha&quot;</span><span style="color:#f8f8f2;">); </span><span class="lol"></span></code></pre> <p>Javascript is incredibly flexible. If you're going to work in it, take the time to actually understand what's going on. You (and your users) will be better off for it.</p> jQuery Introduction, Refresh Fred May 2010 Thu, 13 May 2010 00:00:00 +0000 https://rymc.io/blog/2010/jquery-introduction-refresh-fred-may-2010/ https://rymc.io/blog/2010/jquery-introduction-refresh-fred-may-2010/ <p>I had the awesome chance to give a presentation at the May 2010 meeting of <a href="http://refreshfred.com/">Refresh Fred</a>. The talk I gave was on an introduction to jQuery (as well as some basic Javascript).</p> <p>The great part is that, since I opted to build the presentation in HTML, CSS, and JS, it's easily viewable online. You can <a href="http://projects.venodesigns.net/refreshfredmay/">check out the presentation online</a>, or <a href="http://github.com/ryanmcgrath/refreshfred-may2010-jquery%3E">check out the source on Github</a>.</p> <p>The entire presentation is MIT licensed (of course, that goes as far as a presentation even so much as *can* be licensed). Feel free to take the code, remix it, extend it, or re-use it - go crazy and enjoy.</p> Time for a revival Mon, 10 May 2010 00:00:00 +0000 https://rymc.io/blog/2010/time-for-a-revival/ https://rymc.io/blog/2010/time-for-a-revival/ <p>Alright, so while it's taken me a nine hour long sprint of coding and design, I've finally revived Veno. This is pretty significant, at least on a personal level - the last time I had a personal site and portfolio online was around the beginning of 2007. Several people have asked me why this took so long, though, and so I figured I'd take a moment to jot down my thoughts on the matter.</p> <h2 id="full-time-jobs-take-well-time">Full time jobs take... well, time</h2> <p>See, the biggest draw on my time over the past <strong>3 years</strong> has been my day job with <a href="http://webs.com/">Webs.com</a>. I wanted to take the time to really dig in and better my skills in this craft, and there's really been no better opportunity presented to me since. It's been grueling at points (week long coding sessions without sleep to push a redesign out the door, anyone?), but well worth it.</p> <p>Now, after three years, I've found that I missed having a personal platform to do... well, anything I wanted to, in terms of the internet. This led to a six month period of trashed redesign attempts for Veno; after enough failed designs, I decided to just power through it in a day, and this is the result. It's heavily CSS3-based, but should fall back gracefully in just about every browser. I foresee myself refining it over time, though - there's certain things I'm unhappy with, such as the overall &quot;tightness&quot; of the layout. Expect to see it open up and breathe a bit more over the next few weeks, as well as become more personalized.</p> <h2 id="the-other-drain-wait-twitter">The other drain... wait, Twitter?</h2> <p>Ah, the micro-blogging phenomenon known as <a href="http://twitter.com/">Twitter</a>. For a pretty long while, Twitter dominated most of my &quot;soapbox&quot; needs. I could throw out a small note about whatever I'm feeling, and it would instantly hit an incredibly connected and thriving community. I've probably met and interfaced with more developers and designers over Twitter than anywhere else!</p> <p>Of course, Twitter is limited to 140 characters. What if I wanna throw up a Javascript or Python tutorial? That certainly can't be done over Twitter (at least, not without annoying the hell out of everyone). By re-igniting Veno, I can provide some much needed separation to my &quot;web 2.0 social aspects&quot;, so to speak. Expect this space to be occupied by deeper thoughts and more intricate material, whereas my Twitter feed will remain filled with smaller pieces (updates to my open source projects, etc).</p> <p>So there it is, all laid out. It's taken a few years, but I firmly believe that this is a case of &quot;good things come to those who wait&quot;. It's good to be back - expect to see some awesome things here in the next few months!</p> Interviewing for front-end development Mon, 25 Jan 2010 00:00:00 +0000 https://rymc.io/blog/2010/interviewing-for-front-end-development/ https://rymc.io/blog/2010/interviewing-for-front-end-development/ <div class="blog-edit-note"> <h4>This is Old!</h4> <p>I wrote this at a much younger age, when I was honestly a bit of a jackass (pardon my french). I'm leaving it up because this is my personal space, and I periodically review my old work to see how far I've come. You probably shouldn't take too much away from this piece, though. </p> </div> <p>Interviewing people can be one of the most aggravating things in this industry. Just about everybody who walks through your door could be some run of the mill engineer who's set in their ways.</p> <p>Now, I should be fair here - I probably see this more often, due to the position I'm in. As a front-end developer, I can't begin to count the number of times I've had somebody come in for an interview, expecting their database skills and server-setup capabilities to translate into web skills. It's maddening, at points, and a real problem in the industry - we're only now beginning to get out of the era where front-end development is treated as a necessary evil.</p> <p>Front-end development carries its own unique set of problems. Nobody who's been doing database optimizations for the past five years is going to be able to hit the ground running on UI work; the playing field for this stuff is constantly changing. To that end, I've started trying to come up with different interview questions that can help show how someone attacks the kind of work we routinely face.</p> <p>Seeing as how I've been in Javascript-land for the past four months, the most recent one I came up with is this:</p> <pre style="background-color:#282a36;"> <code><span class="lol"></span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#ffffff;">t </span><span style="color:#ff79c6;">= </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f8f8f2;">r: </span><span style="color:#ffffff;">{}</span><span style="color:#f8f8f2;">, </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#50fa7b;">b</span><span style="color:#f8f8f2;">: </span><span style="font-style:italic;color:#8be9fd;">function</span><span style="color:#f8f8f2;">() </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#ffffff;">m </span><span style="color:#ff79c6;">= </span><span style="color:#f1fa8c;">&quot;%45%6D%61%69%6C%20&quot;</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">for</span><span style="color:#f8f8f2;">(</span><span style="font-style:italic;color:#8be9fd;">var </span><span style="color:#ffffff;">i </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">0</span><span style="color:#f8f8f2;">; </span><span style="color:#ffffff;">i </span><span style="color:#ff79c6;">&lt; </span><span style="color:#bd93f9;">arguments</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">length; </span><span style="color:#ffffff;">i</span><span style="color:#ff79c6;">++</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(</span><span style="color:#bd93f9;">arguments</span><span style="color:#f8f8f2;">[</span><span style="color:#ffffff;">i</span><span style="color:#f8f8f2;">] </span><span style="color:#ff79c6;">instanceof </span><span style="font-style:italic;color:#66d9ef;">Array</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">t</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">r</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">a </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">arguments</span><span style="color:#f8f8f2;">[</span><span style="color:#ffffff;">i</span><span style="color:#f8f8f2;">]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">else </span><span style="color:#ffffff;">t</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">r</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">f </span><span style="color:#ff79c6;">= </span><span style="color:#bd93f9;">arguments</span><span style="color:#f8f8f2;">[</span><span style="color:#ffffff;">i</span><span style="color:#f8f8f2;">]; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">for</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">p </span><span style="color:#ff79c6;">in </span><span style="color:#ffffff;">t</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">r</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">a) </span><span style="color:#ffffff;">{ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">t</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">r</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">a[</span><span style="color:#ffffff;">p</span><span style="color:#f8f8f2;">] </span><span style="color:#ff79c6;">=== </span><span style="color:#8be9fd;">decodeURIComponent</span><span style="color:#f8f8f2;">(</span><span style="color:#f1fa8c;">&quot;%72%65%64%64%69%74&quot;</span><span style="color:#f8f8f2;">)) </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">m </span><span style="color:#ff79c6;">+= </span><span style="color:#f1fa8c;">&quot;%72%79%61%6E%40%76%65%6E%6F&quot; </span><span style="color:#ff79c6;">+ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;%64%65%73%69%67%6E%73%2E%6E&quot; </span><span style="color:#ff79c6;">+ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;%65%74%20%74%6F%20%74%61%6C&quot; </span><span style="color:#ff79c6;">+ </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#f1fa8c;">&quot;%6B%20%66%75%72%74%68%65%72&quot;</span><span style="color:#f8f8f2;">; </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ff79c6;">if</span><span style="color:#f8f8f2;">(</span><span style="color:#ff79c6;">typeof </span><span style="color:#ffffff;">t</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">r</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">f </span><span style="color:#ff79c6;">=== </span><span style="color:#f1fa8c;">&quot;function&quot;</span><span style="color:#f8f8f2;">) </span><span style="color:#ffffff;">t</span><span style="color:#ff79c6;">.</span><span style="color:#f8f8f2;">r</span><span style="color:#ff79c6;">.</span><span style="color:#50fa7b;">f</span><span style="color:#f8f8f2;">(</span><span style="color:#ffffff;">m</span><span style="color:#f8f8f2;">); </span><span class="lol"></span><span style="color:#f8f8f2;"> </span><span style="color:#ffffff;">} </span><span class="lol"></span><span style="color:#ffffff;">}</span><span style="color:#f8f8f2;">; </span><span class="lol"></span></code></pre> <p>The problem is simple: figure out what arguments need to be passed to <code>t.b()</code> for it to display the proper message.</p> <p>I feel like this is a great problem to have someone solve, as it illustrates a few things right off the bat. Are they scared away by encoded characters? Do they even know how to decode those characters? Can they tell, based off the arguments work being done, what types of objects need to be passed?</p> <p>It helps to draw a clear line between someone who just manages to get by with Javascript, and someone who can really dive in and tear things apart if need be. The code is just ugly enough to make some people roll their eyes back into their head, but it's deceivingly simple when you get down to it, and that's why it succeeds at the job.</p> Open source programming languages for kids Fri, 19 Dec 2008 00:00:00 +0000 https://rymc.io/blog/2008/open-source-programming-languages-for-kids/ https://rymc.io/blog/2008/open-source-programming-languages-for-kids/ <div class="blog-edit-note"> <h4>This Entry is Archived!</h4> <p>This article was originally published on December 19, 2008 (2:00:00 PM) over at Linux.com. A still-breathing version of the article (broken links intact) can be found at <a href="https://www.linux.com/news/open-source-programming-languages-kids/">Linux.com</a>.</p> </div> <h2 id="scratch">Scratch</h2> <p>The past couple of years have seen an explosion of open source programming languages and utilities that are geared toward children. Many of these efforts are based around the idea that, since the days of BASIC, programming environments have become far too complex for untrained minds to wrap themselves around. Some toolkits aim to create entirely new ways of envisioning and creating projects that appeal to younger minds, such as games and animations, while others aim to recreate the &quot;basic&quot;-ness of BASIC in a modern language and environment.</p> <p>Developed by the Lifelong Kindergarten group at MIT, Scratch is a graphical programming environment implemented in Squeak that works in a very Lego-like fashion. The basic premise is that you build programs by snapping together colorful blocks of code. Scratch's custom interface allows a programmer to bring in graphics and sounds and create basic animations. All the basic programming constructs, such as loops and if statements, are supported, and grouped into different block categories, such as Motion, Sensing, and Sound.</p> <p>Scratch has implementations available under Microsoft Windows and Mac OS X, but as of yet there's no (official) native Linux version to run. It is possible to run Scratch through Wine, though in my tests most audio-related Scratch programs ended up failing. There is a Linux-runnable version of Scratch, though it's not actively developed by the folks at MIT. The one problem with using this version is that presentation mode, where your Scratch program can take over the whole screen, doesn't work. This isn't really a show-stopper, as there are a few different ways to view a Scratch program, but it's easy to see how it could be a desired feature.</p> <p>One useful prospect that Scratch offers is the ability to upload your programs to the Scratch Web site, where you can create an account, get support, and browse programs that other Scratch users have uploaded. All uploaded programs are open source, in the sense that you can download and modify the source of any Scratch program that's been uploaded. Scratch programs are also viewable from within a Web browser, for the most part, through use of a Java applet called the Scratch Player. Scratch itself is released under its own Scratch License, and all uploaded programs exist under a Creative Commons Share Alike license.</p> <p>One issue I came across with Scratch was that the source code for a program could become quite large when the program involved many graphics or, more specifically, music. One program, a simple music player, reached a strikingly large 93MB in size. Typically Scratch would choke on loading any program greater than 60MB in size, usually erroring out. The large size of a file may have something to do with how old the source code is; repeated instances of saving and re-opening the same file seemed to grow the size exponentially.</p> <h2 id="alice">Alice</h2> <p>Scratch deals well with 2-D graphics, text, and other somewhat &quot;flat&quot; programming concepts. By contrast, Alice teaches programming fundamentals in the form of 3-D movies and games. Alice is developed in Java, and is somewhat like Scratch in that you build things in a drag and drop interface.</p> <p>Alice, developed by a group of researchers at Carnegie Mellon University, has releases for Linux, Mac OS X, and Windows, and is released under an aptly titled Alice License. The environment is open source in the sense that you can download and examine the source code, but the creators prefer to work exclusively in-team, and don't take outside contributions. Alice has been around since 1999, making it one of the oldest and most developed environments for teaching children how to program. It is because of this that it's used in schools all over the world.</p> <h2 id="shoes">Shoes</h2> <p>Originally created by a developer who goes by &quot;why the lucky stiff,&quot; now furthered by a large development community, and based on the already user-friendly Ruby programming language, Shoes is an open source toolkit that's a bit more in line with traditional programming methods. All that's required to make a program in Shoes, besides its runtime environment, is a basic text editor. On the project's Web site you can find a free PDF guidebook that contains tutorials and examples for Shoes. You can also order the guidebook in paperback form for $5.57. Shoes 2 comes with an extensive built-in manual that users can access via key commands.</p> <p>Shoes has similar syntax to Ruby, and has easy methods for creating graphics and buttons, as well as displaying colors and text. It is supported across multiple platforms, including Linux, Mac OS X, and Windows. The toolkit works well across platforms, in that windows, buttons, and dialogs look native to their environment, and do so regardless of which platform the application was initially created on. A benefit of Shoes being in Ruby is that it's given access to the many different RubyGems packages that exist. Shoes 2 even includes support for automatically installing a Gem on a user's system if it's not already present.</p> <p>Shoes has a fan-supported Web site that showcases a gallery of applications created with Shoes. As with Scratch, all the applications that are uploaded can be downloaded, modified, and remixed. Shoes itself is released under an MIT License, and is open to outside patching and development.</p> <p>A multitude of other programming languages and environments exist to teach children, such as Greenfoot, Phogram, and Microsoft's Small Basic, though many of them exist as proprietary implementations. Scratch, Alice, and Shoes are all open source, include support channels such as forums or chatrooms, and have large, thriving communities. These three environments are possibly the most open, mature, and easily accessible environments that are geared toward teaching programming concepts to young minds.</p> Supercharge Firebug Mon, 15 Sep 2008 00:00:00 +0000 https://rymc.io/blog/2008/supercharge-firebug/ https://rymc.io/blog/2008/supercharge-firebug/ <div class="blog-edit-note"> <h4>This Entry is Archived!</h4> <p>This article was originally published on September 15, 2008 (9:00:00 AM) over at Linux.com. A still-breathing version of the article can be found at <a href="https://www.linux.com/news/supercharge-firebug/">Linux.com</a>.</p> </div> <h2 id="yslow">Yslow</h2> <p>While you can use Firebug to monitor network activity, you can take that to the next level by adding YSlow, which checks your Web site against Yahoo's best practices for a high performance Web site. Besides checking your performance, YSlow can also show you graphs and charts that let you inspect things such as expires headers, ETags, and the total amount of HTTP requests -- all useful for optimization.</p> <h2 id="jiffy">Jiffy</h2> <p>Jiffy is a Firebug extension developed at Netflix that checks and captures time measurements for JavaScript that loads on the page (via jiffyweb, an open source Web site measurement suite), and provides a graphical representation of the data, which is ideal for comparing and reducing the effects on page load that are brought on by various JavaScript libraries.</p> <h2 id="firephp">FirePHP</h2> <p>If you're debugging HTML, CSS, or JavaScript, Firebug fits your needs, but what if you want to go further? FirePHP is a Firebug extension that lets you work with PHP5 and up as your server-side language of choice. FirePHP requires that a PHP library be installed in order to function, but once that's installed FirePHP can make use of special &quot;X-FirePHP-Data&quot; headers to bring debugging into the Firebug pane. The debugging data that's sent from the server to the client-side does not interfere with the content of the page you're debugging in any way. For more information, including a quick tutorial, check out the FirePHP Homepage.</p> <h2 id="firecookie">Firecookie</h2> <p>The Firecookie Firebug extension allows you to view all the cookies currently being used on a page or search for a specific cookie, monitor any existing cookies on the page, and add or delete cookies at will. With Firecookie you can check out information on any cookie, such as the name, value, domain, expire date, or path. You can also use the extension to change your settings for accepting or denying cookies straight from within Firebug.</p> <h2 id="pixel-perfect">Pixel Perfect</h2> <p>Firebug can provide more than just developer functionality. If you're a designer, check out Pixel Perfect, a Firebug extension that allows you to overlay a semi-transparent image, or mockup, of your page design. Tou can then use Firebug to line up every element on your page with the overlay. Pixel Perfect is still an experimental add-on, but in my tests I found it to be perfectly stable.</p> <p>If you like the features of Pixel Perfect and would like to use it in a cross-browser setting, check out OverlayComp, an open source script that accomplishes much of the same functionality, albeit with lesser convenience.</p> <h2 id="rainbow-for-firebug">Rainbow for Firebug</h2> <p>Firebug's default script panel can feel somewhat like working in a plain text file at points. To get around this, check out another experimental add-on. Rainbow for Firebug adds syntax highlighting to the script panel, displaying intuitive, color-coded text for variables, objects, functions, and more. There are alternatives to the default highlighting colors -- to see more coloring options, or possibly create your own, check out the Rainbow for Firebug homepage.</p> <p>Rainbow utilizes the parser from CodeMirror.</p> <p>All the aforementioned extensions work best with Firefox 3 and some form of Firebug 1.2. Rainbow in particular requires Firebug 1.2 -- if it's installed alongside an older version of Firebug, that version will cease to function.</p> <p>Firebug by itself is an immense asset to any Web developer, but if you extend it with any of these add-ons, you'll have a tool that can reduce the time it takes you to implement and debug your sites.</p> Easy blogging with Pivot Tue, 18 Sep 2007 00:00:00 +0000 https://rymc.io/blog/2007/easy-blogging-with-pivot/ https://rymc.io/blog/2007/easy-blogging-with-pivot/ <div class="blog-edit-note"> <h4>This Entry is Archived!</h4> <p>This article was originally published on September 18, 2007 (9:00:00 AM) over at Linux.com. A still-breathing version of the article can be found at <a href="https://www.linux.com/news/easy-blogging-pivot/">Linux.com</a>.</p> </div> <p>The GPL-licensed Pivot blogging software stands out among blogging applications because it requires no database, no extra libraries, and minimal installation effort. While it's still in an early stage of development, its flexibilty and the ease with which it can be set up make it ideal for those new to maintaining their own blogging Web sites.</p> <p>To use Pivot, you need a server that's running PHP 4.1.0 or higher. Unlike WordPress or Movable Type, Pivot requires no MySQL database; it stores most of its data within XML files. Pivot also strives to use no extra libraries so as to be available and usable by as many people as possible.</p> <p>The simplest way to install Pivot is by using Pivot Setup. Download the setup zip file, unzip it, and open the resulting pivot-setup.php script in a text editor. Set your password and save the file -- nothing else is needed. Upload the script to your blog directory, which must have permission (chmod) settings of 777. Open the direct link to the script in a browser. If your server is ill-equipped, or needs something changed, Pivot should alert you first thing, as it checks before anything in the script is run. If you're good to go, just follow the instructions for an easy and successful installation. Pivot should automatically pull the files it needs from SourceForge.net and set itself up for you.</p> <p>If for some reason you can't get the aforementioned method to work, you can set up Pivot manually by downloading the entire package (1.40.3 Dreadwind is the latest version as of this article), uploading it to a Web server, and configuring it to your needs. The project provides a pretty well-written guide to the manual installation process.</p> <p>Pivot has many of the features you'll find in other blog systems. Users can register and log in to an account, reply to a blog post, or reply to another commenter through use of a threaded comment system. Pinging and trackback capabilities are included, as well as a simple search that indexes all blog posts. Since it's still young, Pivot lacks the number of free templates that WordPress and Movable Type have, but there is a growing archive at PivotStyles.</p> <p>Pivot supplies RSS feeds without any extra work, though you can only get a feed for all posts, not just, say, one category. RSS feeds can be made available in either RSS or Atom format.</p> <p>You can find a few plugins and extensions for Pivot, but as with some of the documentation, they can be hard to locate. A good list of the basic ones can be found in the Pivot Documentation.</p> <h2 id="drawbacks">Drawbacks</h2> <p>Pivot is, of course, still young and in development, and with that there are bound to be a few drawbacks. There's currently no support for any form of a static page, which restricts users to just writing the blog -- any other pages must be created by hand. Some users within the community, however, seem to have found ways around the issue. Getting technical support can be difficult at times, as there's really no designated support portion of the project. There is an active community, as well as a recently started Pivot Documentation Project that helps to alleviate some of the problems.</p> <p>Another issue is that recently Pivot's servers seem to have gone through long bouts of downtime, which makes getting any support virtually impossible.</p> <p>If you're looking for a lightweight blog application, Pivot may just fit your bill. It has by far the easiest installation I have encountered for a piece of blogging software, and it offers much of the functionality and usability that most other blogging applications have. If you're running a high-traffic site, you probably want to look instead at a more traditional blog that uses a database back end, as using XML files to store data in that situation may put too much of a load on your server. For a smaller site, though, Pivot's definitely worth a look at.</p> Keep users informed with PHPList Tue, 31 Jul 2007 00:00:00 +0000 https://rymc.io/blog/2007/keep-users-informed-with-phplist/ https://rymc.io/blog/2007/keep-users-informed-with-phplist/ <div class="blog-edit-note"> <h4>This Entry is Archived!</h4> <p>This article was originally published on July 31, 2007 (9:00:00 AM) over at Linux.com. A still-breathing version of the article can be found at <a href="https://www.linux.com/news/keep-users-informed-phplist/">Linux.com</a>.</p> </div> <p>If you've ever considered throwing together a mailing list to keep the members of your group, project, or organization informed, you'd be hard-pressed to find a better application for that purpose than PHPList, a free and open source newsletter manager.</p> <p>PHPList is a Web-based application written in PHP, and relies on MySQL databases for storing information. If you're leery of setting up PHP, MySQL, and a Web server, you can opt to pay for a hosting package with a service that offers PHPList preinstalled and configured, such as Host Color or SiteGround.</p> <p>However, setting up PHPList isn't that difficult. You can download the software as either a ZIP or a tarball, depending on your needs, and basic documentation is included within the downloadable PHPList package. If that doesn't cover your needs, there are other places to turn, such as the more extensive online documentation or the PHPList forums. Make sure you have a Web server and PHP installed, and a MySQL database to throw all your data in. Depending on how your site is hosted, you may have a basic database already set up -- check with your provider to make sure.</p> <p>Once the prerequisites are in place, simply download and unpackage the contents of the archive. Edit the PHPList config.php file and make sure all the variables --username, password, database ID, and default language -- are set. The configuration file is pretty well documented with comments concerning what each option is. Save the file, then open the PHPList installation page (www.siteaddress.com/lists/admin/) in a browser. The installation procedure asks for some general configuration settings, such as whether to offer RSS feeds with your lists.</p> <p>Once the software is installed, you can tailor your signup page to your needs. PHPList offers many different options for obtaining information from users at signup, which can then be used in the mailing process. For instance, you can ask for users' country of origin, and then down the road, if you'd like to send a newsletter to all users from a particular country, it's a snap -- that data is already stored.</p> <p>Integrating PHPList signup and address management pages into a Web site's existing design is simple, since PHPList templates are fully customizable. You can even apply different templates to different messages and signup pages.</p> <p>Using the Free PDF Library, PHPList can automatically create and attach messages as PDF files to ensure that formatting and graphics are kept intact.</p> <p>PHPList lets you track the messages you send, so you can tell how many of your users actually opened a message. You can also specify a certain time or date for a newsletter to be sent, and PHPList will wait until that moment to send it.</p> <p>The software includes some email bouncing tools that try to prune unused and nonexistent email addresses. In my time using the program, it managed to kick out 95% of the fake addresses I tossed in there. There are options for some advanced bounce handling tools, which help distinguish between situations such as &quot;this email address has never existed&quot; and &quot;this email address may be experiencing some slight downtime.&quot;</p> <p>Users too can control PHPList's behavior. For example, at any time a user can choose to opt out of receiving a newsletter. Users choose to receive messages in full HTML or text-only mode. They can also change almost all of their information on record -- name, location, interests, even their email addresses.</p> <p>I've found very few disadvantages with PHPList. Some could make the argument that users should be able to reply to any of the newsletters, but then you're getting more into the territory of Mailman, which is an actual email discussion application, somewhat different from PHPList. I found the bounce handling a bit confusing at first, but looking at the different documentation resources proved to be a help. There's a slew of FAQs and documentation, as well as an entire community of people to ask for support. After exhausting those resources, you can resort to the paid support from tincan, the product's sponsor.</p> <p>All in all, PHPList is a versatile and easy-to-use application for managing newsletters and mailing lists.</p> Simple polling with LimeSurvey Tue, 05 Jun 2007 00:00:00 +0000 https://rymc.io/blog/2007/simple-polling-with-limesurvey/ https://rymc.io/blog/2007/simple-polling-with-limesurvey/ <div class="blog-edit-note"> <h4>This Entry is Archived!</h4> <p>This article was originally published on June 05, 2007 (8:00:00 AM) over at Linux.com. A still-breathing version of the article can be found at <a href="https://www.linux.com/news/simple-polling-limesurvey/">Linux.com</a>.</p> </div> <p>Setting up a survey on your Web site can be a simple task with LimeSurvey, a flexible and easy to set up tool for conducting a survey.</p> <p>The LimeSurvey project began in 2003 as PHPSurveyor. Last month the project was renamed LimeSurvey in order to make licensing easier by not including PHP in the title.</p> <p>You can try a demo of LimeSurvey or download the full application.</p> <p>A typical LimeSurvey installation requires a server with PHP installed, as well as a MySQL database to store the data in. If you're wary of going setting it up yourself on a server, many hosting companies offer LimeSurvey installations, such as Hummingbird Hosting or UCHosting. Both of these hosting services allow users to set up a survey script with minimal work.</p> <p>To manually set up LimeSurvey, you must have a server that has PHP 4.2 or later installed, along with the mbstring (Multibyte String Functions) library, and a MySQL (4.1 or later) database for LimeSurvey to store data in. To take advantage of LimeSurvey's token system, you need to have the LDAP Library installed in PHP. The script itself takes up about 10MB of disk space.</p> <p>Installing LimeSurvey just requires uploading the survey software to a directory of your choice, editing the project's config.php file and making sure all the necessary fields -- such as database name, location, and email settings -- are filled in. Once they're set, run a simple installation script by navigating to it with your Web browser.</p> <p>If you're familiar with HTML, you can easily create custom templates to give your surveys a similar look and feel to the rest of your site. If you're not, there is a template editor function built in to LimeSurvey that allows you to create a custom template for your surveys.</p> <p>LimeSurvey's flexibility is its main driving point. The software is offered in a total of 19 different languages. With LimeSurvey, you can have out an unlimited number of surveys at any time. You can set different surveys to be public or private, and allow users to take a survey multiple times or only once via a &quot;post once&quot; token. LimeSurvey supports many different question types, ranging from the simple Yes or No variety to choices such as 10-point scales, open-ended answers, or dropdown choices.</p> <p>With most of the question selections, you can opt to include a field for users to describe why they chose the answer they did. You can branch a survey out by setting certain choices and questions to appear in response to earlier decisions. There is no limit to the number of questions you can include in a survey, and the program also offers ways to automatically email, notify, or invite users to a survey, or to remind them to take it if they've not already done so.</p> <p>You can view your data and results from within LimeSurvey, or you can back up the data by exporting it to simple text, CSV, or Excel files. Exporting to an XML format is not yet available, though it is listed on the road map for LimeSurvey version 2.</p> <p>Users can choose to stop the survey and continue at a later time, keeping their data intact and ready upon their return.</p> <p>My testing didn't uncover any large faults in the software, but if you need it, you can get support in a number of ways. The project provides an online documentation wiki. You can visit the project's online forum to ask a question, where most likely a developer or knowledgeable user can help you out. You can also visit IRC channel #limesurvey on irc.freenode.net, which many users frequent, and many are willing to help out. If your need for support is more pressing, you can email a member of the project, but if you do this they ask that you donate a small sum to the project.</p> <p>If you're in the market for a survey tool, LimeSurvey is one of the best choices around. It has excellent usability and support, and when you take into consideration that some hosting companies offer packages with it pre-installed, as well as it's large list of features, it becomes apparent that LimeSurvey is quite possibly the most flexible, easy to use choice to make.</p> Review: Moneydance 2007 Wed, 02 May 2007 00:00:00 +0000 https://rymc.io/blog/2007/review-moneydance-2007/ https://rymc.io/blog/2007/review-moneydance-2007/ <div class="blog-edit-note"> <h4>This Entry is Archived!</h4> <p>This article was originally published on May 02, 2007 (8:00:00 AM) over at Linux.com. A still-breathing version of the article can be found at <a href="https://www.linux.com/news/review-moneydance-2007/">Linux.com</a>.</p> </div> <p>Linux users have a multitude of choices for personal finance applications, including GnuCash, KMyMoney, Kapital, and others, not to mention being able to run Quicken under WINE. One good alternative that runs on multiple operating systems and manages finances with ease is Moneydance.</p> <p>Moneydance isn't free, but it does run on Windows, Mac OS X, Linux, Unix, and Solaris, due to it being coded in Java. It offers all the basic features most users need, such as checkbook balancing, basic exchange rates, and easy importing from Quicken, while also offering some more interesting and useful tools to judge the state of your finances, such as graphs that can indicate your net worth over a period of time, and easy reminder setups to make sure you're almost always in control.</p> <p>You can download a free trial version of the software that's limited to 100 transactions, with some of the features disabled. The Linux downloads come in two versions: with or without Java prepackaged. If you want to unlock the software's full potential, you'll need to purchase it for $30.</p> <p>To install Moneydance in Linux, open a terminal window, navigate to the directory where you downloaded Moneydance, and run ./moneydance_linux_x86.sh if you already have Java installed, and ./moneydance_linux_x86wj.sh if you downloaded the version bundled with Java. The Java-less installation checks to make sure you have Java installed before going through with the installation, and if not will automatically download Java for you.</p> <p>The first time you run the program, you're presented with three options: you can create a new file to record all your transactions in, open an existing file, or import a file in QIF, OFX, or OFC format. When you create a new file, Moneydance uses its own format, with the extension of .md. If you import a QIF file from Quicken and save your data, it takes the Moneydance format. Upon my first attempt at importing a QIF file, I found that Moneydance duplicated some of my prior transactions from Quicken. A quick visit to their forum explained an easy workaround. I had to set up each of my accounts, checking and credit card, manually, and then import the information from the QIF. This wasn't too big of an issue, and the problem seems to stem from how much data is actually available from the imported files. In short, filetypes won't always play nice with each other, but it can be made to work.</p> <p>After you create a file, you choose what your primary currency is. Moneydance offers 44 different choices. Next, choose whether you want to run in a standard or minimal account set. A standard account set includes checking accounts, loan accounts, liability accounts, as well as general income and expense accounts. Minimal account sets include just checking and savings, and leave you to import your data from other financial management programs.</p> <p>Setting up accounts is pretty simple. You need to enter standard bank account information, such as the account and routing numbers, for Moneydance's check printing and online banking functions. If you're setting up a credit card account, you can enter a card's annual percentage rate, while liability accounts ask for your initial liability.</p> <p>Once your data is set up, you can begin entering transactions. If you're security-conscious, you can encrypt your data with a choice of either 56-bit DES or the somewhat slower Triple DES. If you ever need to get your information out of Moneydance, you can export it to .QIF or Moneydance XML files.</p> <p>If you have finances in multiple currencies, you can view the current exchange rates in other countries. This is updated via a list from Moneydance, as long as you have a current Internet connection. If you have multiple accounts set up, Moneydance will also automatically calculate your net worth. If you own stocks, you can grab an extension that will automatically synchronize with the latest Yahoo! stock lists. You can set reminders that will automatically alert you of an upcoming transaction or bill. The interface to the program is intuitive -- in just minutes I had all of my information entered.</p> <p>Moneydance supports online banking, but in order to get it set up you have to do some legwork. First and foremost, you need to make sure that your bank supports direct OFX connections. Setting up an online banking account or bill payment is easy -- for either, go into the transaction register of your account, and in the top left you should see a tab for online transactions. Drop the tab down and click on either &quot;Set up online bill payment&quot; or &quot;set up online banking,&quot; depending on your needs. Moneydance works with a large list of banks; if yours isn't on the list, you can manually set up an account after you get your bank's OFX ID and URL. After you've configured your bank, you need to input your user ID and PIN that your bank gives you.</p> <p>If you have some form of Web-based banking with your bank, Moneydance can import your settings and transactions from there, provided the bank supports OFX. I was not able to import the settings from the Web site successfully, but I was able to obtain a downloadable file (.OFX) from my bank, which Moneydance accepted with ease.</p> <p>Moneydance also offers advanced budgeting and monitoring tools for your finances. You can have up to 10 budgets going simultaneously, and Moneydance can even calculate an initial budget based on your previous transactions. To create a new budget, open the Budget manager from the dropdown menu located at the top of the program. If you'd like to review your budget, select View -&gt; Show Budget Status in the top menu, and you'll be presented with a graph in the top of the main toolbar. You can also create a budget report for a specific time period, and Moneydance will show you how over or under budget you were.</p> <p>Moneydance can manage loans easily. You can set up a wide variety of loan options, varying the APR, the amount of payments per year, and the total amount of payments you'll be making. The software automatically calculates your monthly payment, but you can set a specific payment as well.</p> <p>Moneydance will print on pre-printed checks, but will only print the date, amount, and the payee. It can't print your routing and account numbers, but you can opt to print your address. Printing checks itself is simple -- you go through the register and mark whatever transactions you intend to be checks as &quot;to be printed,&quot; then select the Actions dropdown in your transaction window, and set it to print your checks. Here Moneydance worked out well for me -- I don't write checks a lot, but for setting up mass bill payments it was easier to use the software than to write each individual check out.</p> <p>Moneydance also offers its core API so that users can develop their own extensions through Python and Jython. It includes sample code, necessary libraries, and an ANT build file to allow compiling and signing of extensions. The project's developer page also includes sample extensions, as well as links to a mailing list for Moneydance development and common issue resolving. When in Moneydance itself, you can obtain a list of available extensions via an Internet connection from the Extensions menu in the top bar, or by clicking the &quot;Check for new updates/extensions&quot; link below all your account information. An extension that I found particularly useful was one that imports Yahoo! stock quotes.</p> <p>If you need support for anything Moneydance-related, you have a couple of options. Read the User Guide first, as many times it can answer your question right off the bat. You can also try the online support forum, with topics ranging from installation and configuration to Moneydance development, and even general finance talk. There is also a mailing list, as well as a Yahoo! discussion group. For matters of a more private nature, the company offers email support.</p> <p>Of course, when you first find that you need help, you can always turn to the Help section of Moneydance itself -- 16 chapters of tips, tricks, and tutorials ranging from simple transaction input to a full-on guide to configuring your online banking experience.</p> <p>Moneydance isn't without its drawbacks, one chiefly being that it's not free software. As a personal finance application, though, Moneydance is worth checking out. Its intuitive interface and easy setup should be a welcome choice for any new Linux users looking for personal finance software, and the ability to develop extensions may attract seasoned users looking to get the most out of their experience.</p> Review: Dreamlinux 2.2 Tue, 27 Mar 2007 00:00:00 +0000 https://rymc.io/blog/2007/review-dreamlinux-2-2/ https://rymc.io/blog/2007/review-dreamlinux-2-2/ <div class="blog-edit-note"> <h4>This Entry is Archived!</h4> <p>This article was originally published on March 27, 2007 (8:00:00 AM) over at Linux.com. A still-breathing version of the article can be found at <a href="https://www.linux.com/news/review-dreamlinux-22-0/">Linux.com</a>. </p> </div> <p>When it comes to choosing a Linux distribution, people tend to stick with the major players, such as Ubuntu, SUSE, or Fedora. However, every once in a while a distro comes along that offers a look at Linux in a new and fun way. One such distribution is Dreamlinux, a Morphix-based implementation of Linux that can be run from a single CD or installed on a hard drive. Dreamlinux 2.2 aims to offer a full range of desktop applications while providing a wealth of multimedia tools for easy production of professional-grade media.</p> <p>Dreamlinux installs easily. For a basic Dreamlinux installation, you'll want to have at least 128MB of RAM, 3GB of free space, and a processor that runs at at least 500MHz. Depending on your hardware, you may have to edit your BIOS settings to force a boot from CD. Dreamlinux should automatically detect all the hardware, but if for some reason it cannot, it will present a list of more basic options to get started, and you can tune and set up the rest upon installation. During the installation, Dreamlinux prompts you to select English or Portuguese as your language of choice, but you can configure it to install with a different language.</p> <p>Once the system is up and running, you have two options: you can continue running with a live CD, or you can do a full installation. If you plan to just try out Dreamlinux, the live CD offers an excellent look at the system and can guide you through much of how it works, and lets you store data on a USB storage device.</p> <p>If you want the entire experience, a full installation is recommended, especially if you intend to work with the XGL 3-D interface, which takes advantage of newer graphics cards to produce some great graphical features for the X Window System. XGL works only when the distribution is installed on the hard drive. A function within the Control Panel, aptly titled HD Install, handles the hard drive installation.</p> <p>Dreamlinux runs with Xfce as the default window manager, and it is full of style cues from the Mac OS X environment, the most notable nod being the Application Panel, which is handled by an independent version of Enlightenment's Engage. The distro comes with a simple set of applications suited for the average user, including OpenOffice.org 2.0.4, Firefox 1.5, Icedove (unbranded Thunderbird) as an email client, and aMSN 0.97 for instant messaging.</p> <p>Dreamlinux has excellent support for multimedia, in terms of both creating and viewing. It comes pre-installed with XMMS for playing music, Grip for playing and reading CDs, Audacity for recording, Kino for editing video, Blender for modeling, and GimpShop for basic graphical work. In terms of multimedia support, it comes ready and set up with all the necessary codecs to play MP3s and DVDs. All of these applications make Dreamlinux a good choice for a multimedia system.</p> <p>While Dreamlinux shares many likenesses with other modern Linux distributions, it has a few tools of its own that stand out -- most notably Mkdistro, a collection of four shell scripts for building and remastering distribution ISOs. MKdistro was developed by Nelson Gomes da Silveira, one of the cofounders of the Dreamlinux project, with the intent of letting users with any level of technical knowledge edit, design, and create a Linux distro suited specifically for themselves. This is likely the reason why the developers chose to adopt a Morphix-like philosophy -- that is, aiming to make the system as modular and changeable for the end user as possible.</p> <h2 id="some-restlessness">Some restlessness</h2> <p>While much of the software works like a dream, not all is perfect. Dreamlinux is still relatively new, and as a result its community is still in its own developing stage. The distribution's developers are Brazilian, and the community seems a bit divided due to a language barrier.</p> <p>In using the software, I ran into some small hindrances. Upon first running the system, Internet connections are not automatically configured, which can throw some new users off. A graphical interface guides you through the connection process, but it would be more efficient to have Internet connectivity automatically configured, and keep the GUI around just to allow users to make modifications as necessary. aMSN also had difficulty starting up correctly at points, and one time refused to even start at all. I downloaded Gaim and used it as my instant messaging client instead with no problems.</p> <p>Nevertheless, the distribution itself looks good and functions well. The Mkdistro tool will be useful for users who want complete control of their systems, and the overall ease of installation and use Dreamlinux offers is good enough that the average user can download and install the distribution and jump right in.</p> Easy discussions with Simple Machines Forums Wed, 17 Jan 2007 00:00:00 +0000 https://rymc.io/blog/2007/easy-discussions-with-simple-machines-forums/ https://rymc.io/blog/2007/easy-discussions-with-simple-machines-forums/ <div class="blog-edit-note"> <h4>This Entry is Archived!</h4> <p>This article was originally published on January 17, 2007 (8:00:00 AM) over at Linux.com. A still-breathing version of the article can be found at <a href="https://www.linux.com/news/easy-discussions-simple-machines-forums/">Linux.com</a>. </p> </div> <p>Many Web sites host discussion boards to bring together people with common interests, to help diagnose problems, or to gain a following for a project. Popular discussion board software includes phpBB, Invision Power Board, and, on the low end, PunBB. One system that is growing in popularity is Simple Machines Forum (SMF), which offers extended features while keeping to a minimalistic approach.</p> <p>SMF forked from a project called YaBB SE to add advanced templating to the original software. It is free (as in beer) and distributed under its own license. It claims to have minimal server impact while providing the features and abilities that larger forum systems carry around. The software itself is written in PHP, and uses MySQL databases to store user profiles, post counts, and so on. Its use of server-side includes also allows the forum system to be easily integrated into Web sites.</p> <p>SMF is lightweight, easy to use, but still full of features. Besides basic community features, such as private messaging, user icons, and individual profiles, SMF has a unique package management system that allows board administrators to update or install modifications with a few clicks. With many other forum systems, the process involves manually editing key files and reuploading. Security issues are not prevalent, but when they pop up, the developers are quick to nip the issues in the bud.</p> <p>The software logs most major functions, such as an administrator changing a template piece or reordering a page, with the time and the IP address under which they were done. Administration may be &quot;time locked,&quot; wherein the action is restricted to only so many tries within a certain time period. Login attempts to regular user accounts from any one IP address can be time locked and limited too.</p> <p>Simple Machines offers administrators several different courses of action for dealing with troublesome users. The system allows for three different types of bans: a full ban, in which the user is kicked off the board; a &quot;no-post ban,&quot; which allows the user to view the board and read topics, but not to post replies; or a timed ban, where a user can be banned for anywhere from an hour to days, or longer.</p> <p>Simple Machines boards support Wireless Application Protocol (WAP) as well. When someone accesses a page using a WAP, WAP2, or I-mode protocol, the software detects that and displays a page that's reduced in size, without unnecessary elements such as borders, larger graphics, or other media. You can view replies, make replies, and browse; however, in my experience, posting new messages and the log in/out functions don't always function properly in a WAP setting. They work fine in WAP2/I-mode, though, which are both essentially equivalent to the WAP environment.</p> <p>All in all, Simple Machines Forums works well. The system can cater to a large or small community. It's versatile, secure, and easy to set up. If you need help, you can read a list of Frequently Asked Questions at the software's Web site, and communicate with an entire community of other Simple Machines users that can help and answer questions. An online manual gives an overview of almost every function you could need. With helpful developers, a secure system, and tons of possibilities, Simple Machines Forums is a good choice for a community setup.</p> KToon: Simple 2D animation Thu, 14 Sep 2006 00:00:00 +0000 https://rymc.io/blog/2006/ktoon-simple-2d-animation/ https://rymc.io/blog/2006/ktoon-simple-2d-animation/ <div class="blog-edit-note"> <h4>This Entry is Archived!</h4> <p>This article was originally published on September 14, 2006 (8:00:00 AM) over at Linux.com. A still-breathing version of the article (broken links intact) can be found at <a href="https://www.linux.com/news/ktoon-simple-2d-animation/">Linux.com</a>. </p> </div> <p>If you are running Mac OS X or Microsoft Windows, you have access to many different animation applications, ranging from Adobe Flash to Anime Studio. That is not so for Linux. While many think of animation in Linux as a lost cause, there are alternatives. The relatively new KToon calls itself &quot;the open source animation revolution.&quot; KToon has a small learning curve and an intuitive interface, making it an excellent choice for simple animation within Linux.</p> <p>KToon was designed for animators by the animators at Toonka. To use KToon, you need a CPU speed of 800MHz or more, at least 256MB of RAM, and 5MB of free space, as well as Qt 4.1.1 or higher. If you really want to create, those numbers should probably be higher. I have found that a processor that is faster than 1GHz with at least 512MB of RAM does well.</p> <p>With a recent release, you can now burn an ISO image of KToon to a CD for a portable live version of KToon that can be booted on any computer. This is a useful method for trying the program if you are not running Linux.</p> <p>Ktoon is still in its early stages, so it is not the most functional piece of animation software there is yet, but it is excellent for simple animations. It has the beginnings of great tools for animation, such as the ability to create multiple brushes for complex animations, as well as the ability to create keyframes. These serve as tools for not only organization, but also as a means to help out in the animation process. Ktoon has a feature called &quot;onion paper,&quot; in which the frame that is being modified can be transparent, allowing the animator to see the same general outline that the previous frame had, which in turn allows subtle changes between frames to be created with less time and effort. Most commands are easy to find and experiment with.</p> <h2 id="disadvantages">Disadvantages</h2> <p>KToon also has its share of bugs that need to be ironed out. For instance, the recommended way to start the program requires opening a terminal window, getting to the directory where KToon is installed, and manually initiating it there. If KToon is not started in this manner, there is a possibility that you will not be able to save any of your work.</p> <p>KToon comes with the ability to store to a .SWF file type, otherwise known as Flash. However, to do so, it really just exports the animation in bitmap format, frame by frame, which makes for a very large file. If this is changed in later versions, it might make the program more popular, since there are not many viable Flash alternatives in the Linux environment.</p> <p>I have yet to have KToon crash on me, but other users of the program have lost work due to program crashes. Save your work frequently.</p> <p>The advantages that KToon has to offer do outweigh its bugs, for the most part. It is an excellent tool for simple 2D animation, and it is easy enough to get into. The interface is intuitive enough that newcomers can pick it up and work it without too much trouble, and it is a pretty lightweight package.</p> <p>KToon also has an informative documentation portal, with information relating to both use and development of the program. The information is presented in a variety of different languages, and includes tutorials on making your first animation, creating different themes for KToon, general tips, and frequently asked questions.</p> <p>KToon is not without competition. The open source vector animation program Synfig is in an early stage too. It takes after the animation studio formerly known as Moho, now known as Anime Studio. There is also the program known as Plastic Animation Paper; however, it is not free. KToon may see a bit more of a faster development cycle, due to its backing by Colciencias, the Colombian institute for the Development of Science and Technology, as well as ParqueSoft, a technological organization that gives backing to startup technology groups.</p> <p>KToon is free software under the GPL. While today it runs only on Linux, the developers have stated that they will develop versions for Windows and Mac OS X in the future. The community behind KToon is helpful. You can find discussion boards as well as fairly helpful tutorials and support, such as frequently asked questions and mailing lists, on the project's home page.</p> Kino makes video editing simple Fri, 19 Aug 2005 00:00:00 +0000 https://rymc.io/blog/2005/kino-makes-video-editing-simple/ https://rymc.io/blog/2005/kino-makes-video-editing-simple/ <div class="blog-edit-note"> <h4>This Entry is Archived!</h4> <p>This article was originally published on August 19, 2005 over at the (now defunct) Newsforge.com. A still-breathing version of the article (broken links intact) can be found at <a href="https://www.linux.com/news/kino-makes-video-editing-simple/">Linux.com</a>. </p> </div> <p>The Linux enviroment offers two major packages for creating and editing digital media. Cinelerra is a media powerhouse, while Kino works well for beginners who need to create simple digital video. It's a speedy editor, lightweight, and it seldom crashes. Its simplicity, ease of use, and small learning curve make it an excellent alternative for creating and editing digital media in the Linux enviroment. Kino does not require as much powerful hardware as Cinelerra; you can get good results with about 128MB of memory and a 1GHz processor. You'll need a bit of disk space for digital video editing -- about 40GB should do just fine.</p> <p>Kino takes video to the disk in AVI and raw DV format. When you finish editing a video, Kino lets you export it in a number of formats, such as MPEG and MP3. Kino also features incredible support for IEEE-1394, otherwise known as FireWire, which allows it to communicate with different video hardware, and also supports most USB drive input. Kino has easy tools for filters, general effects, and video transition, ranging from kaleidescope to a general background generator. Kino also comes equipped with audio tools, such as filters and audio transitions, which include useful &quot;fade in/out&quot; and &quot;mix&quot; features.</p> <p>The program is organized well, with a storyboard style view that allows you to see each of your scenes in a mini pane. You can drag and drop to rearrange frame order and movie flow. You can undo and redo changes up to 99 times, so you can learn by making mistakes and correcting them.</p> <p>Kino's excellent &quot;dvgrab&quot; interface, the tool that allows you to capture digital video to the disk, makes it ideal for importing digital media, even if you're doing the majority of your editing with another digital video editor.</p> <p>Kino's user interface is available in English, Danish, Swedish, French, Spanish, and Czech. It provides online help for troubleshooting issues.</p> <h2 id="disadvantages">Disadvantages</h2> <p>Despite its many pluses, Kino is a low-end video editor, best suited for doing quick editing jobs or inputting digital feeds. Kino does not support multiple layers or tracks of audio and video, which means that it's not suitable for video work that requires complex audio and video effects.</p> <p>For those using a video camera to record their work, be aware that you will be able to connect via FireWire only if you're using a digital video camcorder. A digital camera, meaning digital stills, will not work, whether it has a FireWire interface or not.</p> <p>Fortunately, Kino's advantages outweigh its disadvantages. Even if your project is a task of amazing proportions, Kino can be useful in one way or another, especially for inputting digital media and outputting it to a desired format. If you're a beginner, it provides an easy entrance into the field of digital video on a Linux system.</p> <p>Kino is free software under the GPL. The community behind Kino is helpful, too. You can find discussion boards, as well as excellent tutorials and support, such as Frequently Asked Questions and User Guides, on the project's home page.</p> My Workstation OS: VidaLinux Fri, 29 Apr 2005 00:00:00 +0000 https://rymc.io/blog/2005/my-workstation-os-vidalinux/ https://rymc.io/blog/2005/my-workstation-os-vidalinux/ <div class="blog-edit-note"> <h4>This Entry is Archived!</h4> <p>This article was originally published on April 29, 2005 over at the (now defunct) Newsforge.com. A still-breathing version of the article (broken links intact) can be found at <a href="https://www.linux.com/news/my-workstation-os-vidalinux/">Linux.com</a>.</p> </div> <p>My computer is my life, but I'm fairly new to the world of Linux. I started with SUSE Linux 9.1 Professional. It's a fairly nice and easy system, but I wanted to try some other distributions, to see what I liked and disliked. I wanted something that felt not too advanced, but also not too limited. That's what I found in the VidaLinux operating system (VLOS), the perfect combination of what I wanted.</p> <p>Many call VidaLinux a &quot;simpler Gentoo.&quot; It uses many of Gentoo's features, such as the Portage software distribution system, but also manages to make it all seem less intimidating. For instance, it uses Red Hat's Anaconda installation system. Anaconda is a graphical interface, which many find easier than Gentoo's command-line installation. Vida's system components also come prebuilt and ready for installation, whereas Gentoo's installation requires everything to be built from the command line, which intimidates some people.</p> <p>Some people have reported issues with Vida's networking and sound card configuration. While the sound card wasn't an issue for me, networking was. Thankfully, I was able to fix my problem easily by referring back to the MadPenguin article that introduced me to Vida. After that little escapade, I moved on to configuring my system.</p> <p>I use my computer for Web site management, helping out at sites such as The Mega Man Network and Metroid HQ. In addition to sitting on IRC most of the time, I tend to use AIM to contact some people. Vida comes set with what I need to get the job done. Firefox, generally the ideal browser for any Web designer, comes pre-installed, with many plug-ins already set, like MPlayer and Java. The GIMP is a fine image manipulation program, and works well for Web design. For music playing purposes, the system offers Xine, a fine music application. It covers chat too, with an easy installation of X-Chat and an included version of GAIM. When I need to get a professional project done, I've got OpenOffice.org right there. Vida's main window system is GNOME 2.8.0, and the default icon set, with an uncanny similarity to Mac OS X's, brings the desktop to life.</p> <p>VidaLinux runs well, and the Portage system makes it even more fun. The Portage application itself is called Porthole, and it's pretty useful. It allows users to choose new applications to install on their system, as well as old ones to get rid of. Apparently, some people have had issues with it suddenly shutting down on them, but that hasn't been an issue for me. The Vida community itself is very helpful, and getting support has been no problem.</p> <p>All in all, I thoroughly enjoy VidaLinux. It's a stable operating system that caters to both advanced and beginning Linux users. What I need my computer to do, it does. I can work on Web pages, listen to music, and play my games with ease. The system itself is friendly, versatile, and workable. I know my way around it, and I'll be sticking with it for awhile.</p>