Skip to content

Commit

Permalink
Cleanup of static and library code.
Browse files Browse the repository at this point in the history
Incorporated library from other project for doing DB queries, content
extraction, etc.

Fixed facebox paths, since they now live under
  • Loading branch information
Benjamin Trott committed Nov 20, 2009
1 parent 0f75b0c commit b5d00f7
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 30 deletions.
10 changes: 5 additions & 5 deletions bin/app.psgi
@@ -1,11 +1,11 @@
#!/usr/bin/perl -w
use strict;

use lib '/Users/btrott/Documents/devel/faved-tp';
use Find::Lib '../lib';

use Dash::Util;
use DateTime;
use DateTime::Format::Mail;
use Faved::Util;
use JSON;
use List::Util qw( first );
use Plack::App::File;
Expand All @@ -16,7 +16,6 @@ use Template::Provider::Encoding;
use Template::Stash::ForceUTF8;
use WWW::TypePad;

my $dbh = Faved::Util->get_dbh;
my $tt = Template->new(
LOAD_TEMPLATES => [
Template::Provider::Encoding->new( INCLUDE_PATH => 'templates' )
Expand All @@ -37,6 +36,7 @@ my $error = sub {
sub load_assets_by {
my( $sql, @bind ) = @_;

my $dbh = Dash::Util->get_dbh;
my $sth = $dbh->prepare( <<SQL );
SELECT a.asset_id,
a.api_id,
Expand Down Expand Up @@ -74,7 +74,7 @@ SQL

# Calculate an excerpt, extract media, etc, and stuff it all
# into the "content" key.
$row->{content} = Faved::Util->get_content_data(
$row->{content} = Dash::Util->get_content_data(
$row->{type},
$row->{content},
decode_json( $row->{links_json} ),
Expand Down Expand Up @@ -168,7 +168,7 @@ SQL
};

builder {
mount '/stat' => builder {
mount '/static' => builder {
Plack::App::File->new( { root => './static' } );
};

Expand Down
269 changes: 269 additions & 0 deletions lib/Dash/Util.pm
@@ -0,0 +1,269 @@
package Dash::Util;
use strict;

use CGI::Deurl::XS ();
use DateTime;
use DateTime::Format::Human::Duration;
use DateTime::Format::ISO8601;
use DBI;
use Encode;
use Exporter::Lite;
use HTML::Entities ();
use HTML::Sanitizer;
use HTML::TokeParser;
use JSON;
use List::Util qw( first min reduce );

our @EXPORT_OK = qw( debug );

sub debug ($) {
print STDERR "@_\n";
}

sub get_dbh {
our $DBH ||= DBI->connect( 'dbi:mysql:database=favrd', 'btrott', '', { RaiseError => 1 } );
return $DBH;
}

sub find_or_create_person_from_api {
my $class = shift;
my( $obj ) = @_;

my $api_id = $obj->{urlId};

my $dbh = $class->get_dbh;
my $row = $dbh->selectrow_hashref( <<SQL, undef, $api_id );
SELECT person_id, api_id, display_name, avatar_uri FROM person WHERE api_id = ?
SQL
unless ( defined $row ) {
my $avatar_uri = $class->get_best_avatar_uri( $obj->{links} );
my $display_name = $obj->{displayName};

$dbh->do( <<SQL, undef, $api_id, $display_name, $avatar_uri );
INSERT INTO person (api_id, display_name, avatar_uri) VALUES (?, ?, ?)
SQL
$row = {
person_id => $dbh->{mysql_insertid},
api_id => $api_id,
display_name => $display_name,
avatar_uri => $avatar_uri,
};
}
return $row;
}

sub get_best_avatar_uri {
my $class = shift;
my( $links ) = @_;
return unless $links && @$links;

# Look first for a 50px width avatar...
my $link = first { $_->{rel} eq 'avatar' && $_->{width} == 50 } @$links;

# ... and if we can't find one, grab the largest available avatar,
# which will by definition be less than 50px width, I think?
unless ( $link ) {
$link = reduce { $a->{width} > $b->{width} ? $a : $b }
grep { $_->{rel} eq 'avatar' } @$links;
}

return $link ? $link->{href} : undef;
}

sub find_or_create_asset_from_api {
my $class = shift;
my( $obj ) = @_;

my $api_id = $obj->{urlId};

my $dbh = $class->get_dbh;
my $row = $dbh->selectrow_hashref( <<SQL, undef, $api_id );
SELECT asset_id, api_id, person_id, title, content, permalink, created, favorite_count, links_json, object_type FROM asset WHERE api_id = ?
SQL

unless ( defined $row ) {
my $person = $class->find_or_create_person_from_api( $obj->{author} );

my $link = first { $_->{rel} eq 'alternate' } @{ $obj->{links} };
my( $type ) = $obj->{objectTypes}[0] =~ /(\w+)$/;

$row = {
api_id => $api_id,
person_id => $person->{person_id},
title => $obj->{title},
content => $obj->{content},
permalink => $link->{href},
created => $obj->{published},
favorite_count => 0,
links_json => encode_json( $obj->{links} ),
object_type => $type,
};

$dbh->do( <<SQL, undef, $row->{api_id}, $row->{person_id}, $row->{title}, $row->{content}, $row->{permalink}, $row->{created}, $row->{favorite_count}, $row->{links_json}, $row->{object_type} );
INSERT INTO asset (api_id, person_id, title, content, permalink, created, favorite_count, links_json, object_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
SQL
$row->{asset_id} = $dbh->{mysql_insertid};
}
return $row;
}

sub strip_html {
my( $html ) = @_;

my $safe = HTML::Sanitizer->new(
script => undef,
style => undef,
small => undef, # remove "via" lines (hack!)
'*' => 0,
);
$safe->set_encoder( sub {
my( $str ) = @_;
$str =~ tr/\x{00a0}/\x{0020}/;
return $str;
} );

Encode::_utf8_on( $html );
$html = $safe->sanitize( \$html );
utf8::encode( $html );

return $html;
}

sub generate_excerpt {
my( $html, $words ) = @_;

my $text = strip_html( $html );
return '' if !defined $text || !defined $words || $words == 0;

my $char = Encode::is_utf8( $text ) ?
$text :
Encode::decode( 'utf-8', $text );

$text =~ s/^\s+//; $text =~ s/\s+$//;

if ( $char =~ m/[\p{Han}\p{Hiragana}\p{Katakana}]/ ) {
$text =~ s/\s+/ /g;
Encode::_utf8_on( $text );
no warnings 'substr';
$text = substr $text, 0, $words;
} else {
my @words = split /\s+/, $text, $words + 1;
$text = join ' ', @words[ 0 .. min( $words, scalar @words ) - 1 ];
if ( @words > $words ) {
$text .= '...';
}
}
Encode::_utf8_off( $text );
return $text;
}
sub explode_asset_uri {
my( $uri ) = @_;
my( $server, $xid ) = $uri =~ /
http:\/\/(.*?)\/(6a[0-9a-f]{32})
/x or return;
return( $server, $xid );
}
sub get_content_data {
my $class = shift;
my( $type, $content, $links ) = @_;
my %data = (
excerpt => generate_excerpt( $content, 50 ),
media => [],
rendered => $content,
);
if ( $type eq 'Photo' ) {
my $link = reduce { $a->{width} > $b->{width} ? $a : $b }
grep { $_->{rel} eq 'enclosure' } @$links;
if ( $link ) {
$data{rendered} = <<HTML;
<p><img src="$link->{href}" width="$link->{width}" height="$link->{height}" /></p>
<p>$content</p>
HTML
my( $server, $xid ) = explode_asset_uri( $link->{href} );
$data{media} = [ {
type => 'photo',
uri => $link->{href},
server => $server,
id => $xid,
width => $link->{width},
height => $link->{height},
} ];
}
} elsif ( $type eq 'Post' ) {
my $parser = HTML::TokeParser->new( \$content );
while ( my $t = $parser->get_token ) {
if ( $t->[0] eq 'S' && $t->[1] eq 'img' ) {
my( $server, $xid ) = explode_asset_uri( $t->[2]{src} );
push @{ $data{media} }, {
type => 'photo',
uri => $t->[2]{src},
server => $server,
id => $xid,
width => $t->[2]{width},
height => $t->[2]{height},
};
}
}
}
return \%data;
}
1;
__END__
DROP TABLE IF EXISTS asset;
CREATE TABLE asset (
asset_id INTEGER UNSIGNED auto_increment NOT NULL,
api_id VARCHAR(75) NOT NULL,
person_id INTEGER UNSIGNED NOT NULL,
title VARCHAR(255),
content MEDIUMBLOB,
permalink VARCHAR(255),
created DATETIME,
favorite_count INTEGER UNSIGNED,
links_json MEDIUMBLOB,
object_type VARCHAR(15),
PRIMARY KEY (asset_id),
INDEX (api_id),
INDEX (person_id, created),
INDEX (created),
INDEX (favorite_count)
);
DROP TABLE IF EXISTS person;
CREATE TABLE person (
person_id INTEGER UNSIGNED auto_increment NOT NULL,
api_id VARCHAR(18) NOT NULL,
display_name VARCHAR(100),
avatar_uri VARCHAR(255),
PRIMARY KEY (person_id),
INDEX (api_id)
);
DROP TABLE IF EXISTS favorited_by;
CREATE TABLE favorited_by (
asset_id INTEGER UNSIGNED NOT NULL,
person_id INTEGER UNSIGNED NOT NULL,
api_id VARCHAR(53) NOT NULL,
INDEX (asset_id),
UNIQUE (api_id)
);
DROP TABLE IF EXISTS last_event_id;
CREATE TABLE last_event_id (
api_id varchar(34) NOT NULL
);
DROP TABLE IF EXISTS stream;
CREATE TABLE stream (
person_id INTEGER UNSIGNED NOT NULL,
asset_id INTEGER UNSIGNED NOT NULL,
PRIMARY KEY (person_id, asset_id)
);
10 changes: 5 additions & 5 deletions static/facebox/facebox.css
@@ -1,21 +1,21 @@
#facebox .b {
background:url(/facebox/b.png);
background:url(/static/facebox/b.png);
}

#facebox .tl {
background:url(/facebox/tl.png);
background:url(/static/facebox/tl.png);
}

#facebox .tr {
background:url(/facebox/tr.png);
background:url(/static/facebox/tr.png);
}

#facebox .bl {
background:url(/facebox/bl.png);
background:url(/static/facebox/bl.png);
}

#facebox .br {
background:url(/facebox/br.png);
background:url(/static/facebox/br.png);
}

#facebox {
Expand Down
6 changes: 3 additions & 3 deletions static/facebox/facebox.js
Expand Up @@ -79,8 +79,8 @@
settings: {
opacity : 0,
overlay : true,
loadingImage : '/facebox/loading.gif',
closeImage : '/facebox/closelabel.gif',
loadingImage : '/static/facebox/loading.gif',
closeImage : '/static/facebox/closelabel.gif',
imageTypes : [ 'png', 'jpg', 'jpeg', 'gif' ],
faceboxHtml : '\
<div id="facebox" style="display:none;"> \
Expand All @@ -97,7 +97,7 @@
</div> \
<div class="footer"> \
<a href="#" class="close"> \
<img src="/facebox/closelabel.gif" title="close" class="close_image" /> \
<img src="/static/facebox/closelabel.gif" title="close" class="close_image" /> \
</a> \
</div> \
</td> \
Expand Down
12 changes: 6 additions & 6 deletions templates/assets.tt
Expand Up @@ -2,12 +2,12 @@
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.8.0r4/build/reset-fonts-grids/reset-fonts-grids.css">
<link rel="stylesheet" type="text/css" href="/stat/css/styles.css" />
<link rel="stylesheet" type="text/css" href="/stat/facebox/facebox.css" />
<script type="text/javascript" src="/stat/js/jquery.min.js"></script>
<script type="text/javascript" src="/stat/js/jquery.hotkeys.js"></script>
<script type="text/javascript" src="/stat/js/jquery.relatize_date.js"></script>
<script type="text/javascript" src="/stat/facebox/facebox.js"></script>
<link rel="stylesheet" type="text/css" href="/static/css/styles.css" />
<link rel="stylesheet" type="text/css" href="/static/facebox/facebox.css" />
<script type="text/javascript" src="/static/js/jquery.min.js"></script>
<script type="text/javascript" src="/static/js/jquery.hotkeys.js"></script>
<script type="text/javascript" src="/static/js/jquery.relatize_date.js"></script>
<script type="text/javascript" src="/static/facebox/facebox.js"></script>
<script type="text/javascript">
$( document ).ready( function() {
$( '.relatize' ).relatizeDate()
Expand Down

0 comments on commit b5d00f7

Please sign in to comment.