Showing posts with label 3shrink. Show all posts
Showing posts with label 3shrink. Show all posts

Tuesday, May 02, 2017

3Shrinking Files on Android: From Photo to 3 Digit Code in 2 Clicks

I recently added the ability 3shrink content that has no URL, such as a photo, PDF or sound clip. I was itching to get this working on Android, and here's why. Consider this sketch:

It seems reasonable that I'd want to associate a photo of the scene so I could compare them later. After sketching the scene I snapped a photo and used the new 3shrink capability to automatically turn it into a code. I then scribbled the code on the photo, QU3. At a future date, I can visit: http://benjisimon.3shrink.com/qu3 and see my the photo that inspired this sketch:

In other words, I now the ability to seamlessly go from a photo to a 3 digit code on my Android device.

To make this functional, I needed to enhance the AutoShare'ing 3shrink solution I previously created. The first step was to enhance the top level 3shrink Tasker action:

I've added a new leading 'If' condition that checks to see if %asfile1 is set. If it is, then I know that the AutoShare command was invoked with at least one file. %asfile1 contains a full path to the file, which is exactly what I need later on in the process.

I then updated the low level 3shrink action to check to see if the content being shared is a file that exists:

If it is, I do a Tasker HTTP POST of the content. One gotcha I found with the HTTP POST is that %HTTPD doesn't appear to be set. I overcame this by storing the result of the POST in a text file and then reading that text file into a variable. Incidentally, the content returned by the POST is the 3 letter code that corresponds to this file.

And that's pretty much all there is to it. I snap a photo, hit the share button and a few moments later, I see my magic code:

See the original photos over at http://blogbyben.3shrink.com/ck4.

Wednesday, April 26, 2017

No URL? No Problem. Adding local file support to 3shrink.com

3shrink does a great job of linking online resources with offline media. For example, shrinking a lengthy Google Mail URL into a 3 letter code that gets hand written on a TODO list. But what if the resource isn't yet online? Say, a photo I've just snapped with my cell phone, or a PDF I've downloaded to my local computer.

To support this use case, I'd need to add the ability to upload arbitrary files to 3shrink. So that's what I did.

The first step was detecting a file upload. Because I plan to use this functionality with Tasker, I based this functionality on its HTTP POST Action. Here's the code that powers the detection:

function is_upload_request() {
  return (g($_SERVER, 'REQUEST_METHOD') == 'POST' &&
          g($_SERVER, 'CONTENT_TYPE')   == 'application/octet-stream' && 
          geek_val()                    == GEEK_KEY);
}

Most of that's pretty standard, except for the geek related code. I setup 3shrink so that if the URL parameter geek is provided, then the system operates in a command line friendly way. In this latest version of 3shrink, I've extended this in two ways: first, geek can be provided as either a URL parameter or a cookie, the latter being more convenient for the HTTP POST Action. And second, I only allow uploads from requests that know the value of GEEK_KEY. So while anyone on the web can 3shrink a URL, only those who have requested the key from me (please do!), can perform file uploads.

Once I've detected and upload, storing the data in S3 and generating a URL to it, was surprisingly straightforward:

function do_upload() {
  $in_fd     = fopen("php://input", 'r');
  $buffer    = tempnam("/tmp", "upload");
  $buffer_fd = fopen($buffer, 'w');
  while($data = fgets($in_fd)) {
    fputs($buffer_fd, $data);
  }
  fclose($in_fd);
  fclose($buffer_fd);
  $info      = finfo_open();
  $mime_type = finfo_file($info, $buffer, FILEINFO_MIME_TYPE);
  $name      = md5_file($buffer);
  $s3 = upload_s3_client();
  if(!$s3->doesObjectExist(UPLOAD_BUCKET, $name)) {
    $s3->putObject(['Bucket'      => UPLOAD_BUCKET,
                    'Key'         => $name,
                    'SourceFile'  => $buffer,
                    'ContentType' => $mime_type]);
                    
  }
  return $s3->getObjectUrl(UPLOAD_BUCKET, $name);
}

I'm suing the finfo library offered by PHP to effortlessly determine the mime type, and the Amazon PHP library to push the data to S3. I'm basing the name of the file on its contents, which means that multiple attempts to shrink the same file always returns the same 3 letter code.

I know that PHP isn't a particularly sexy language, but I'm impressed at how effortlessly I was able to implement offline storage, including mime type detection. Much of what makes this code especially cool isn't visible. For example, the PHP library code is managed by composer, and authentication to Amazon is powered by IAM roles, and is therefore automatic. Library management and authentication aren't typically hard, but they're do contribute to the friction in getting work done. And in the above solution, much of that friction has been eliminated.

To integrate uploading into the shrinking process was almost too easy:

if(is_upload_request()) {
  $content = do_upload();
} else {
  $content = g($_GET, 'i');
}

That's because the result of do_upload is a URL that the rest of the 3shrink process can work with.

With the code in place, here's how it can be invoked using curl:

curl -s -H 'Content-Type: application/octet-stream' \
     -b geek=xxxxxxxxxxxx \
     --data-binary @foo.jpg \
      'http://projx.3shrink.com/shrink'

With this foundation in place, the next step is to enhance the AutoSharing capability on a mobile device to allow uploading of files. When that's done, I should be able to effortlessly snap a picture and moments later, have a 3 letter code that represents that picture.

Thursday, April 20, 2017

Gotcha of the Day: Using Tasker's HTTP POST Action and PHP to upload a file

I plan to enhance 3shrink to support generating 3 letter codes in response to an upload. Before making this change, however, I wanted to understand how Tasker's HTTP POST Action works with respect to files. That way, I could be sure that any change I made to the core of 3shrink would play nicely with Tasker.

Tasker's HTTP POST Action is surprisingly finicky to use. The trickiest part: when you select a file to upload, Tasker injects a relative path to the file in the UI. Say, DCIM/Camera/20170330_005914.jpg. However, without the leading /, Tasker just assumes that this is an arbitrary string. And if you just add a leading slash to the path, you'll end up with a Tasker error about the file not being found. What's required is to have a valid absolute path in place.

After much playing, I learned that a valid absolute path begins with /sdcard, not /storage/emulated as some the file dialog box initially indicated. (At least that's how it works on my LG G6)

The other piece of the puzzle: you'll want to set the appropriate Content-Type for your file (using application/octet-stream is a fine catch-all).

Here are the correct settings:

On the PHP side of things, you can access this data by reading in the stream associated with php://input. Here's some sample code that slurps in the file from the web and writes it locally as capture.data. Notice that I'm doing a bit of checking here to insure that the Content-Type is set as I expect it.

<?
/*
 * A chunk of PHP code to catch an incoming file that's been posted
 * by Tasker's HTTP Post action
 */

if(isset($_SERVER['CONTENT_TYPE']) && $_SERVER['CONTENT_TYPE'] == 'application/octet-stream') {
  $out_fd = fopen(__DIR__ . "/capture.data", "w");
  $in_fd  = fopen("php://input", "r");
  while($data = fgets($in_fd)) {
    fputs($out_fd, $data);
  }
  echo "saved: " . filesize(__DIR__ . "/capture.data") . "\n";
} else {
  die("malformed request");
}
?>

If all goes as planned, Tasker should happily push the file, and your PHP script should happily pull it in. Enjoy!

Note: for uploading a queue of files, it's far smarter to use the FolderSync and the its Tasker Plugin.

Friday, February 03, 2017

3shrink'ing on a Mobile Device

3shrink is working well in a desktop context, so it was time to spread the love to mobile. Specifically, I wanted to be able to take a resource on my mobile phone and quickly generate a 3shrink code for it. This turned out to be straightforward to do in Tasker.

Much of the heavy lifting is done by AutoShare, a Tasker plugin that links Tasker profiles to your phone's share menu. I used geek mode to allow 3shrink to play nicely with Tasker's HTTP Get action. Finally, I created a Tasker Scene which displays the code, domain and content encoded as a window overlay.

Here's an example of 3shrink'ing a YouTube video:

And here's an example of 3shrink'ing arbitrary text:

You can see these codes work by visiting the relevant URLs: http://projx.3shrink.com/ALN and http://projx.3shrink.com/DN3.

By naming the AutoShrink command intelligently, I can programmatically derive the correct domain from %ascommand. This allows me to have a single 3shrink Tasker Profile which handles any number of domains.

The trickiest part of this project was dealing with the inherent messiness behind Android sharing. AutoShare exposes 3 different variables: %assubject, %astext and %asfiles, which correspond to the subject, text and files being shared. There's no set standard for what will appear in each of these variables. YouTube, for example, stores the video name %assubject and the video URL in %astext. Taptalk, seems like it does the same thing, storing post titles in %assubject. However, it chooses to set %astext to the URL to the post followed by a space, followed by the post title again.

My solution was to examine %assubject %astext as one large string, and if a URL is detected, extract it, otherwise fall back on returning the whole mess. Here's the JavaScriplet that implements this logic:

var text = 
  (local('par1') === 'undefined' ? '' : local('par1')) + " " +
  (local('par2') === 'undefined' ? '' : local('par2'));

var text = text.replace(/[\n\r ]+/g, " ").trim();

var matches = text.match(/(http[^ \n\r]+)/);
if(matches) {
  setLocal('result', matches[1]);
} else {
  setLocal('result', text);
}

I can't tell you how helpful it was to be able to write this code as a JavaScript rather than implementing this using clunky Tasker commands. Of course this solution isn't perfect, as it's biased towards URLs. But generally, if there's a URL in the sharing context that's what I want to capture within 3shrink.

For now I'm completely ignoring %asfiles, though eventually, I'll want to account for this variable as well. For example, I can imagine having a collection of photos I want to share with someone, and 3shrink would reduce all these files to a simple 3 character code.

Up next is to reverse the process: that is, adding support for typing in a 3shrink code and expanding that to the URL or content.

However, for now, this is certainly a step in the right direction. There's just something awesome about being able to share a video, album or article on a Post-It note containing 3 characters:

Thursday, January 19, 2017

Web Based Protocol Handlers: The fastest user experience in the shortest amount of dev time

Firefox's Web Based Protocol Handler support has to be one of its hidden gems. Once a handler is registered, you gain especially quick access to specific content on the web. For 3shrink, this means quick access to the URL behind the 3 letter codes the site produces.

For example, to visit the URL behind the code PQW I need only type the following in my browser bar:

  grow:pqw

The protocol handler serves as the complement to the 1 click bookmarket, insuring that both growing and shrinking a URL is as streamlined as can be.

Best of all, implementing a Web Based handler couldn't be simpler. You need only make a call to: navigator.registerProtocolHandler. Here's the code that powers the register protocol handler link on 3shrink:

<?php
/*
 * A PHP file for rendering the code needed to install a web
 * based protocol handler.
 */
$domain = item_domain();
$code = <<<EOF
(function() {
  var proto = prompt("Protocol? (ex: grow)");
  navigator.registerProtocolHandler(proto, 
                                    "http://$domain/grow?i=%s",
                                    "$domain Handler");
})()
EOF;

$code = esc_attr($code);
?>
<a href="javascript:<?= $code ?>">Install Firefox Protocol Handler</a>
(<a href="https://developer.mozilla.org/en-US/docs/Web-based_protocol_handlers">Huh?</a>)

Note how the code above prompts the user to enter the protocol name. That way, you could have personal and business and point them to two different 3shrink domains.

When you click to register a protocol, you'll a sort of pop-up bar message thingy:

The first time you use the protocol (that is, enter: grow:...) you'll be prompted to select the handler. While you're at it, select the checkbox so you're not nagged about this again:

One gotcha: attempting to access a protocol containing only numbers fails. For example:

  grow:347

Will take you to: http://grow.com:347/ - not what I wanted. The fix for 3shrink is to use any letter prefix. So you can type:

  grow:x347

I'm amazed I haven't found more uses for web based protocol handlers. For advanced users, you can offer a terrifically fast user experience at almost no cost in terms of development time.

Friday, January 13, 2017

3shrink: adventures in one click shrinking

While I'm pleased with the core functionality of 3shrink, it definitely needed a faster interface. That is, a way to effortlessly encode and decode 3shrink URLs, as well as bits of text. For now I've only solved half the problem, the shrinking side of things. The solution was straightforward: create a couple of bookmarklets. Here they are in action.

With these bookmarklets in place, I'm only one click away from a 3shrink code.

The implementation of the bookmarklets is nearly trivial. Here's the PHP snippet that powers them both:

<?php
/*
 * A PHP file for generating a URL shrinking bookmarklet
 */
$code = <<<EOF
(function(){
  window.location = 
    'http://{$_SERVER['SERVER_NAME']}/shrink?i=' + encodeURIComponent($src)
})()
EOF;
$code = esc_attr($code);
?>
<a href="javascript:<?= $code ?>">3shrink <?= $label ?></a>

And here's the calling code that renders the bookmarklets in the footer:

<div class="footer">
  <a href="/">Home</a> | <a href="http://blogbyben.com/">Built By Ben</a> |
  Bookmarklets: 
  [<?= snippet('bookmarklet',
       ['label' => "URL", 'src' => 'window.location']) ?>]
  [<?= snippet('bookmarklet', 
       ['label' => "TXT", 'src' => "prompt('Text to Shrink')"]) ?>]
</div>

Installing the bookmarklets is as simple as browsing to your preferred instance of 3shrink, say http://projx.3shrink.com/ and dragging the appropriate links from the footer into your bookmark toolbar.

You'll want to note that the bookmarklet's a domain specific. That is, the bookmarklets in the footer of the site http://projx.3shrink.com register URLs with a different context than http://personal.3shrink.com.

With the shrinking side of things streamlined, the next area to focus on is how to quickly expand 3shrink codes. Stay tuned for that solution.

Wednesday, January 11, 2017

3shrink.com: In Pursuit of Paper Friendly URLs

But Why?

Recall that my paper based TODO list had two challenges to overcome. First, I needed a backup/archiving mechanism, second I needed an easy bridge from the paper to the digital world.

The former issue, backups, was easy solved: every morning I splay out the various index cards I'm tracking tasks on (with each card representing a single project) and snap a photo of them. If I lose, or corrupt any of the cards (read: spill tea all over them), I've got a photo I can use to recover my lost work.

The latter issue I solved by use of a jerry-rigged URL shortner. Essentially, I can turn any entity associated with a URL (an e-mail message, bug report, Google Doc) into a 3 letter code. I've found that even with my crappy handwriting I can manage to neatly print 3 characters.

Here's a card for a hypothetical Project X:

Using the URL Shortner, I can jump to the resource associated with the first task using the code CLW.

This all works, and works surprisingly well.

However, it was nagging at me that my URL Shortner solution depended on a now defunct Google Apps Lab Project. While this works for me now, nothing stops Google from pulling the plug on this project tomorrow and I'd be left hanging. Also, I figured if I was getting benefit from these super short URL codes, maybe other folks would, too.

Introducing 3Shrink

I therefore give you: 3shrink.com. 3shrink is yet another URL Shortner, but unlike others out there, it focuses on making extra short, text friendly URLs. (OK, it does feel a bit like 7 minute abs, but bear with me here.) I quickly coded 3shrink, and you're welcome to grab the source code here to play with it. Or, just head over to 3shrink.com to give it a try.

To make 3shrink truly useful I need to implement a corresponding bookmarklet and protocol handler. Stay tuned for those bits of code, but for now 3shrink does have some value.

For one thing, you can control the namespace associated with your 3 digit codes by changing the URL. The above codes, CLW, DEY and L48 were created in the projx.3shrink.com instance. So visiting http://projx.3shrink.com/DEY takes you to a valid link, whereas visiting http://ziggy.3shrink.com/DEY will most likely give you a 404 page. You can even point (CNAME) your own domain, say 3shrink.myawesomecompany.com, to the 3shrink server, and it will use that as the corresponding name space. It's all very mailinator like.

And like mailinator, there's effectively no security. There's no obvious way to get a list of all URLs associated with an instance, but if you spent enough time poking at projx.3shrink.com you could figure out all the URLs associated with it. So be smart. You can also use a more difficult to guess submdomain if you want a bit more security.

Throw &geek=1 on any 3shrink URL, and you gain access to a trivial text based API that Tasker, curl and friends can easily plug into. See:

$ little=`curl -G -s http://projx.3shrink.com/shrink \
  --data-urlencode i=http://someurl.com -d geek=1 `
$ big=`curl -G -s http://projx.3shrink.com/grow -d i=$little -d geek=1 `
$ echo $big
http://someurl.com
$ echo $little
HPT

And finally, 3shrink isn't limited to encoding and decoding URLs. It's happy to shrink any text that's 1024 characters or smaller. This lets you turn an address, latitude and longitude or a Tweet into a handy 3 character code. Like the following:

  RU5
  BMZ
  JK7

So there you have it, 3shrink.com. Go and put it to use. Where will your 3 letter URLs take you today?