--- /dev/null
+use ExtUtils::MakeMaker;
+WriteMakefile(
+ 'NAME' => 'Memories',
+ 'VERSION_FROM' => 'Memories.pm', # finds $VERSION
+ 'PREREQ_PM' => {
+ Maypole => 1.1,
+ HTML::TagCloud => 0,
+ URI::Escape => 0,
+ Calendar::Simple => 0,
+ XML::RSS => 0,
+ Time::Piece => 0,
+ Class::DBI::Plugin::Pager => 0,
+ Class::DBI::Plugin::AbstractCount => 0,
+ Cache::MemoryCache => 0,
+ Image::Info => 0,
+ Image::ExifTool => 0,
+ File::Path => 0,
+ Image::Imlib2 => 0,
+ Text::Balanced => 0,
+ }
+);
+++ /dev/null
-package Memories;
-use strict;
-our $VERSION = "1.2";
-use Maypole::Application qw(Upload Authentication::UserSessionCookie);
-use HTML::TagCloud;
-use URI;
-use Memories::Config;
-use Memories::DBI;
-use Memories::Photo;
-use Memories::Comment;
-use Memories::Tag;
-use Memories::SystemTag;
-use Memories::User;
-use Memories::Album;
-use URI::Escape;
-use Calendar::Simple;
-use XML::RSS;
-
-Memories->config->auth->{ user_field } = "name";
-Memories->config->model("Maypole::Model::CDBI::Plain");
-Memories->setup([qw/ Memories::Photo Memories::User Memories::Tag
-Memories::Album Memories::SystemTag/]);
-
-sub message {
- my ($self, $message) = @_;
- push @{$self->{template_args}{messages}}, $message;
-}
-
-sub do_rss {
- my $r = shift;
- $r->model_class->process($r);
- my $rss = XML::RSS->new(version => "2.0");
- $rss->channel(
- title => ($r->config->{application_name}. " : ".ucfirst($r->action)." ".ucfirst($r->table)." ".($r->objects||[])->[0]) ,
- link => $r->config->{uri_base}."/".$r->path
- );
- my $maybe_photos = $r->{objects}||[];
- my $photos =
- (@$maybe_photos && $maybe_photos->[0]->isa("Memories::Photo"))
- ? $maybe_photos :
- ($r->{template_args}->{photos} || []);
- for my $item (@$photos) {
- my $link = $r->config->{uri_base}."photo/view/".$item->id;
- $rss->add_item( title => $item->title, link => $link,
- description =>
- "<a href=\"$link\">
- <img src=\"". $item->thumb_url."\" alt=\"".$item->title."\"></a>",
- dc => { subject => join " ", $item->tags },
- pubDate => $item->uploaded->strftime("%a, %d %b %Y %H:%M:%S %z")
- )
- }
- $r->output($rss->as_string);
- $r->content_type("application/rss+xml");
- return
-}
-
-sub additional_data {
- my $r = shift;
- if ($r->params->{view_cal}) {
- $r->{template_args}{view_cal} = eval {
- Time::Piece->strptime($r->{params}{view_cal}, "%Y-%m-%d") };
- }
- $r->{template_args}{now} = Time::Piece->new;
- return $r->do_rss if ($r->params->{format} =~ /rss/)
-}
-
-use Maypole::Constants;
-sub authenticate {
- my ($self, $r) = @_;
- return DECLINED if $self->path =~/static|store/; # XXX
- $r->get_user;
- return OK;
-}
-
-
-use Cache::SharedMemoryCache;
-my %cache_options = ( 'namespace' => 'MemoriesStuff',
- 'default_expires_in' => 600 );
-my $cache =
- new Cache::SharedMemoryCache( \%cache_options ) or
- croak( "Couldn't instantiate SharedMemoryCache" );
-
-sub zap_cache { $cache->Clear }
-use Storable qw(freeze); use MIME::Base64;
-sub do_cached {
- my ($self, $codeblock,$arg) = @_;
- my $key = 0+$codeblock; if ($arg) { $key .=":".encode_base64(freeze(\$arg)); }
- my $c = $cache->get(0+$codeblock); return @$c if $c;
- my @stuff = $codeblock->($arg);
- $cache->set(0+$codeblock, [ @stuff ]);
- return @stuff;
-}
-
-sub _recent_uploads { Memories::Photo->search_recent() }
-
-sub recent_uploads { shift->do_cached(\&_recent_uploads) }
-sub tagcloud { shift->do_cached(\&_tagcloud) }
-
-sub _tagcloud {
- my $cloud = HTML::TagCloud->new();
- my $base = Memories->config->uri_base."tag/view/";
- for my $tagging (Memories::Tagging->search_summary) {
- my $name = $tagging->tag->name;
- $cloud->add($name,
- $base.uri_escape($name),
- $tagging->{count}
- )
- }
- $cloud
-}
-
-sub calendar {
- # shift->do_cached(\&_calendar, shift ) }
-#sub _calendar {
- my $self = shift;
- my $arg = shift;
- my ($y, $m) = split /-/, ($arg || Time::Piece->new->ymd);
- my @m = Calendar::Simple::calendar($m, $y);
- my @month;
- foreach my $week (@m) {
- my @weekdays;
- foreach my $day (@$week) {
- my $d = { day => $day };
- if ($day) {
- my $tag = "date:$y-$m-".sprintf("%02d", $day);
- my ($x) = Memories::SystemTag->search(name => $tag);
- if ($x) { $d->{tag} = "/system_tag/view/$tag" }
- }
- push(@weekdays, $d);
- }
- push(@month, \@weekdays);
- }
- return \@month;
-}
-
-# THIS IS A HACK
-
-use Time::Seconds;
-sub Time::Piece::next_month {
- my $tp = shift;
- my $month = $tp + ONE_MONTH;
- return if $month > Time::Piece->new;
- return $month
-}
-sub Time::Piece::prev_month {
- my $tp = shift;
- my $month = $tp - ONE_MONTH;
- return $month
-}
-
-
-sub tag_select {
- my ($r, $tags) = @_;
- my %counter;
- my @photos = Memories::Photo->sth_to_objects(Memories::Tag->multi_search(@$tags));
- for (map {$_->tags} @photos) {
- $counter{$_->name}++;
- }
- delete $counter{$_->name} for @$tags;
- my @super;
-
- my $cloud = HTML::TagCloud->new();
- my $base = $r->config->uri_base.$r->path."/";
- my $tags;
- for my $name (sort {$a cmp $b} keys %counter) {
- if ($counter{$name} == @photos) {
- push @super, $name;
- } else {
- $cloud->add($name, $base.uri_escape($name), $counter{$name});
- $tags++;
- }
- }
- my %res;
- if (@super) { $res{super} = \@super }
- if ($tags) { $res{cloud} = $cloud }
- \%res;
-}
-1;
LockDirectory => "/var/lib/memories/sessionlock",
};
+# This is where your Image::Seek library will be stored.
+Memories->config->{image_seek} = "/var/lib/memories/imageseek.db";
+
# DISPLAY PARAMETERS
#
# It's OK to leave these as they are.
use Carp qw(cluck confess);
use base qw(Memories::DBI Maypole::Model::CDBI::Plain);
use Time::Piece;
+use Image::Seek;
use constant PAGER_SYNTAX => "LimitXY";
__PACKAGE__->columns(Essential => qw(id title uploader uploaded x y));
__PACKAGE__->untaint_columns(printable => [qw/title/]);
$photo->make_thumb;
$photo->add_tags($r->{params}{tags});
+ $photo->add_to_imageseek_library;
Memories->zap_cache();
# Add system tags here
);
}
+sub view :Exported {
+ my ($self, $r) = @_;
+ if ($r->{session}{last_search}) {
+ my $photo = $r->{objects}[0];
+ # This is slightly inefficient
+ my @search = split/,/, $r->{session}{last_search};
+ my $found = -1;
+ for my $i (0..$#search) {
+ next unless $photo->id == $search[$i];
+ $found = $i;
+ }
+ return unless $found > -1;
+ $r->{template_args}{next} = $self->retrieve($search[$found+1])
+ if $found+1 <= $#search;
+ $r->{template_args}{prev} = $self->retrieve($search[$found-1])
+ if $found-1 >= 0;
+ }
+}
sub upload :Exported {}
use Class::DBI::Plugin::Pager;
);
$r->objects([$pager->retrieve_all ]);
$r->{template_args}{pager} = $pager;
+ $r->last_search;
}
sub add_comment :Exported {
use Image::ExifTool;
my $cache = new Cache::MemoryCache( { 'namespace' => 'MemoriesInfo' });
+sub add_to_imageseek_library {
+ my $self = shift;
+ Image::Seek::cleardb();
+ my $img = Image::Imlib2->load($self->path("file"));
+
+ Image::Seek::add_image($img, $self->id);
+ # Merge this new one into the main database; there is a bit of a
+ # race condition here. XXX
+ Image::Seek::loaddb(Memories->config->{image_seek});
+ Image::Seek::savedb(Memories->config->{image_seek});
+}
+
+sub recommended_tags {
+ my $self = shift;
+ my %tags = map { $_->name => $_ }
+ map { $_->tags }
+ $self->find_similar(3);
+ values %tags;
+}
+
+sub find_similar {
+ my ($self, $count) = @_;
+ Image::Seek::cleardb();
+ Image::Seek::loaddb(Memories->config->{image_seek});
+ my @res = map {$_->[0] } Image::Seek::query_id($self->id, $count);
+ shift @res; # $self
+ map { $self->retrieve($_) } @res;
+}
+
sub unrotate {
my $self = shift;
my $orient = $self->exif_info->{EXIF}->{Orientation};
$r->{template_args}{tags} = [$tag]; # For selector
$r->{template_args}{photos} =
[$pager->search_sorted_by_system_tag($tag->id)];
+ $r->last_search;
}
package Memories::SystemTagging;
$r->{template_args}{photos} =
[$pager->search_sorted_by_tag($tag->id)];
}
+ $r->last_search();
}
sub multi_search {
GROUP BY tag
ORDER BY count DESC
/);
+__PACKAGE__->set_sql(user_summary => qq/
+SELECT tagging.id id, tag, count(*) AS count
+FROM tagging, photo
+WHERE tagging.photo = photo.id AND photo.uploader = ?
+GROUP BY tag
+ORDER BY count DESC
+/);
+
Memories::Tagging->has_a("photo" => "Memories::Photo");
Memories::Tagging->has_a("tag" => "Memories::Tag");
$user->id,{order_by => "uploaded desc"}) ];
$r->{template_args}{pager} = $pager;
$r->{template_args}{albums} = [$user->albums(privacy => 0)];
+ $r->last_search;
}
# Album support!
}
+sub api_taglist :Exported {
+ my ($self, $r) = @_;
+ my $user = $r->objects->[0];
+ $r->{output} .= $_->{tag}.":".$_->{count}."\n"
+ for Memories::Tagging->search_user_summary($user->id);
+ $r->{output}.= "\n";
+ $r->{content_type} = "text/plain";
+}
+
1;
You will also need a MySQL database. Again, in theory other databases
can be used, but in practice, you're on your own again.
-Configure Maypole/Config.pm to your site, and follow the instructions in
+Configure /etc/memories/Config.pm to your site, and follow the instructions in
there - it will require you to set other things up as well.
Test that everything works:
--- /dev/null
+package Tagtools;
+use HTML::TagCloud;
+use Carp;
+use Cache::FileCache;
+use Storable qw(freeze); use MIME::Base64;
+use Calendar::Simple;
+sub import {
+ my $whence = caller;
+ my ($class) = @_;
+ my %cache_options = ( 'namespace' => $whence.'TagTools',
+ 'default_expires_in' => 600 );
+ my $cache =
+ new Cache::FileCache( \%cache_options ) or
+ croak( "Couldn't instantiate FileCache" );
+ *{$whence."::zap_cache"} = sub { $cache->Clear };
+ *{$whence."::do_cached"} = sub {
+ my ($self, $codeblock,$arg) = @_;
+ my $key = 0+$codeblock; if ($arg) { $key .=":".encode_base64(freeze(\$arg)); }
+ my $c = $cache->get(0+$codeblock); return @$c if $c;
+ my @stuff = $codeblock->($arg);
+ $cache->set(0+$codeblock, [ @stuff ]);
+ return @stuff;
+ };
+ *{$whence."::_tagcloud"} = sub {
+ my $cloud = HTML::TagCloud->new();
+ my $base = $whence->config->uri_base."tag/view/";
+ for my $tagging (($whence."::Tagging")->search_summary) {
+ my $name = $tagging->tag->name;
+ $cloud->add($name, $base.uri_escape($name), $tagging->{count})
+ }
+ $cloud
+ };
+ *{$whence."::_calendar"} = sub {
+ my $self = shift;
+ my $arg = shift;
+ my ($y, $m) = split /-/, ($arg || Time::Piece->new->ymd);
+ my @m = Calendar::Simple::calendar($m, $y);
+ my @month;
+ foreach my $week (@m) {
+ my @weekdays;
+ foreach my $day (@$week) {
+ my $d = { day => $day };
+ if ($day) {
+ my $tag = "date:$y-$m-".sprintf("%02d", $day);
+ my ($x) = ($whence."::SystemTag")->search(name => $tag);
+ if ($x) { $d->{tag} = "/system_tag/view/$tag" }
+ }
+ push(@weekdays, $d);
+ }
+ push(@month, \@weekdays);
+ }
+ return \@month;
+ };
+ for my $thing (qw(tagcloud calendar)) {
+ *{$whence."::$thing"} = sub { shift->do_cached($thing, @_) }
+ }
+
+}
+
+
+# THIS IS A HACK
+
+use Time::Seconds;
+sub Time::Piece::next_month {
+ my $tp = shift;
+ my $month = $tp + ONE_MONTH;
+ return if $month > Time::Piece->new;
+ return $month
+}
+sub Time::Piece::prev_month {
+ my $tp = shift;
+ my $month = $tp - ONE_MONTH;
+ return $month
+}
+
+1;
+++ /dev/null
-use Memories;
-my $it = Memories::Photo->retrieve_all;
-
-my $thing = $it->first;
-do {
- print $thing->title, " $tag\n";
- my $tag = "date:".$thing->shot->ymd;
- $thing->add_to_system_tags({tag => Memories::SystemTag->find_or_create({name
- =>$tag}) });
-} while $thing = $it->next;
<h1> Welcome to Memories </h1>
<p>
- Memories is a site where you can upload and share your photos of
- college life with your friends.
+ Memories is a site where you can upload and share your photos.
</p>
<p>
To view other people's photos, look at the <a
<html>
<head>
- <title> Memories - ANCC Photo Sharing </title>
+ <title> Memories - Photo Sharing [% IF photo %] - [% photo.title; END%]</title>
+ <meta name="robots" content="nofollow">
<link title="Maypole" href="[%base%]/static/memories.css" type="text/css" rel="stylesheet"/>
[% IF photos %]
<link rel="alternate" type="application/rdf+xml" title="RSS"
--- /dev/null
+<html>
+<head>
+ <title> Memories - ANCC Photo Sharing </title>
+ <link title="Maypole" href="[%base%]/static/memories.css" type="text/css" rel="stylesheet"/>
+ [% IF photos %]
+<link rel="alternate" type="application/rdf+xml" title="RSS"
+href="[%base%]/[%path%]?format=rss" />
+[% END %]
+[% IF request.params.active == "tagedit" %]
+ <script type="text/javascript" src="[%base%]/tag/list_js"></script>
+ <script type="text/javascript" src="[%base%]/static/upload.js"></script>
+</head>
+<body onload="init()">
+[% ELSE %]
+</head>
+<body>
+[% END %]
+[% INCLUDE nav %]
+<table width="100%">
+ <tr>
+ <td valign="top">
+ [% IF messages %]
+ <div class="messages">
+ <ul> [% FOR m = messages %] <li> [%m%] </li> [% END %]
+ </ul></div>
+ [% END %]
+ <div id="main">
--- /dev/null
+ [% INCLUDE header %]
+
+ <div id="login">
+ [% IF request.user %]
+ Welcome, [% request.user.name %]
+ [% ELSE %]
+
+ [% IF login_error %]
+ <div class="error"> [% login_error %] </div>
+ [% END %]
+ <form method="post" action="[% base %]/user/register">
+ <fieldset>
+ <legend>Login or register as a new user</legend>
+ <table> <tr>
+ <td class="loginfield">
+ Username:
+ </td>
+ <td>
+ <input name="name" type="text" />
+ </td>
+ </tr>
+ <tr> <td class="loginfield">
+ Password:
+ </td><td>
+ <input name="password" type="password" />
+ </td></tr></table>
+ <input type="submit" name="login" value="Login"/>
+ <input type="submit" name="login" value="Register"/>
+ </fieldset>
+ </form>
+ [% END %]
+ </div>
+
+ [% INCLUDE footer %]
-[% MACRO thumb(photo, album) BLOCK %]
-<table class="thumb">
- <tr><td>
+[% MACRO minithumb(photo) BLOCK %]
<a href="[%base%]/photo/view/[%photo.id%]">
<img src="[% photo.thumb_url |uri%]" alt="[%photo.title|html%]"/>
</a>
+[% END; MACRO thumb(photo, album) BLOCK %]
+<table class="thumb">
+ <tr><td>
+ [% minithumb(photo) %]
</td> </tr>
<tr><td>
<a href="[%base%]/photo/view/[%photo.id%]">
<b>[% photo.title |html%] </b>
</a>
+ <br>
+ <small>[% FOR tag = photo.tags %] <a
+ href="[%base%]/tag/view/[%tag%]">[%tag %]</a> [% END %]</small>
</td></tr>
<tr><td> Uploaded by
<a href="[%base%]/user/view/[%photo.uploader.id%]">
--- /dev/null
+<p align="center">
+[% PROCESS macros; FOR sim = photo.find_similar(4); minithumb(sim); END %]
+</p>
+
+<p>
+Suggested tags: [% FOR tag = photo.recommended_tags; %]
+<a href="[%base%]/tag/view/[%tag.name|uri%]">[%tag.name%] </a>
+[% END %]
+</p>
<html>
<head>
- <title> Memories - ANCC Photo Sharing </title>
+ <title> Memories - Photo Sharing </title>
<link title="Maypole" href="[%base%]/static/memories.css" type="text/css" rel="stylesheet"/>
<script type="text/javascript" src="[%base%]/tag/list_js"></script>
<script type="text/javascript" src="[%base%]/static/upload.js"></script>
<table width="100%">
<tr valign="top">
<td width="70%">
+ [% IF prev %]
+ <small><a href="[%base%]/photo/view/[%prev.id%]"><< [% prev %]</a></small>
+ [% END %]
<h1>[% photo.title %]</h1>
+ [% IF next %]
+ <small><a href="[%base%]/photo/view/[%next.id%]"> [% next %] >></a></small>
+ [% END %]
[% IF request.user == photo.uploader %]
<p><a href="[%base%]/photo/delete/[%photo.id%]">Delete this
photo</a></p>
[% IF photo.is_bigger(sizes.$i); %]
[% IF i == size %]
[% sizes.$i %]
+ [% ELSIF sizes.$i == "full" %]
+ <a href="[%photo.path("url")%]">full</a>
[% ELSE %]
<a href="[%url%]?scale=[% i %]&active=[%tab%]">[% sizes.$i %]</a>
[% END %]
[%do_tab("comment", "Comments") %]
[%do_tab("exif", "Photo info") %]
[%do_tab("tagedit", "Edit tags") %]
+ [%do_tab("similar", "Similar photos") %]
</ul>
<div id="content">
[%
IF request.params.active == "tagedit"; INCLUDE tagedit;
ELSIF request.params.active == "exif"; INCLUDE exif;
+ELSIF request.params.active == "similar"; INCLUDE similar;
ELSE; INCLUDE comment; END;
%]
</div>
--- /dev/null
+User-agent: NaverBot
+Disallow: /
+
+User-agent: *
+Disallow: /tag/view/
--- /dev/null
+[% INCLUDE header %]
+[% INCLUDE footer %]