78 Commits
0.2 ... 0.6

Author SHA1 Message Date
Luc Didry
9e27e0df59 Bump version 2014-10-03 00:40:25 +02:00
Luc Didry
0544a1b346 Update info page on rights on uploaded images 2014-10-03 00:38:47 +02:00
Luc Didry
bb1b43e215 Replace twitter page by social page (icon, description, etc)
+ small changes in README
2014-10-03 00:27:22 +02:00
Luc Didry
ad04bdd571 Add opengraph tags on twitter page.
Since the twitter page can now be embedded in Facebook and other social
networks which relies on opengraph tag, this page is now called the
social page.
2014-10-02 23:31:59 +02:00
Luc Didry
23c130750e Improve Twitter animated card (gif) (#45) 2014-09-25 01:41:42 +02:00
Luc Didry
a0b507e48e Fixes #45 + Update README.md and Changelog 2014-09-24 01:41:36 +02:00
Luc Didry
9692504096 Fix broken icon class 2014-09-12 14:29:42 +02:00
Luc Didry
8386a2f4d2 Add license mention in manifest.webapp + manifest.webapp version 2014-09-12 14:28:56 +02:00
Luc Didry
bef9dfaca3 Update description in manifest.webapp 2014-08-05 23:42:08 +02:00
Luc Didry
b71c25bf54 Close #42 2014-08-05 23:00:50 +02:00
Luc Didry
b0e5f771f2 Add Kharec as contributor 2014-07-21 16:48:12 +02:00
Sandro CAZZANIGA
2b361fce26 Fix pod description 2014-07-21 16:47:31 +02:00
Luc Didry
64e0db48f2 Change URL of official instance to the secured URL 2014-07-19 11:19:53 +02:00
Luc Didry
241e8b8ea1 Remove IO::Socket::SSL dependancy and add EV dependancy 2014-07-12 22:15:16 +02:00
Luc Didry
e0f13489a4 Update modules (cpanfile.snapshot) 2014-07-12 22:11:00 +02:00
Luc Didry
1ca3137dd1 Fix same I18N keyword used two times 2014-07-12 22:09:46 +02:00
Luc Didry
b791c609c2 Update Changelog 2014-07-12 22:01:15 +02:00
Luc Didry
388feba7c0 Replace some js in external file 2014-07-12 21:50:56 +02:00
Luc Didry
542d3efdf5 Fix #12 2014-07-12 21:50:31 +02:00
Luc Didry
a57815a2ea Add navigation bar in the webapp. 2014-07-11 12:33:34 +02:00
Luc Didry
44c0d5a9c9 Update I18N 2014-07-11 12:25:04 +02:00
Luc Didry
ebb5282579 Add "share" button for Firefox OS 2014-07-11 12:00:58 +02:00
Luc Didry
d03ce97c8b Add more icons of different sizes in manifest.webapp 2014-07-11 12:00:12 +02:00
Luc Didry
a4ca866790 Add Dattaz as dev in about page 2014-07-11 11:59:05 +02:00
Luc Didry
73b09d28c6 Small change in an img url 2014-07-11 00:20:52 +02:00
Luc Didry
5c45be0099 Hide install-app button by default, reveal it when necessary
+ avoid mozSetMessageHandler error in console when not available
2014-07-11 00:19:18 +02:00
Luc Didry
150f27bf9b Add icons & meta tags to be able to install as webapp in Chrome & Safari 2014-07-11 00:05:30 +02:00
dattaz
d0b388834f hide app button already install
add js code to hide app install button when app already install
2014-07-10 22:05:23 +02:00
Luc Didry
e951a44324 Refactor message function to build_message
Relative to webapp message fail.
2014-07-10 13:16:32 +02:00
Luc Didry
9fcefcdbd4 Small fix for webapp detection 2014-07-10 12:09:43 +02:00
dattaz
05892366d8 hide install web app
add js to hide install web app, when your already on the webapp
2014-07-10 11:53:16 +02:00
Luc Didry
989412046b Add Dattaz as part of the main developers 2014-07-08 23:42:29 +02:00
Luc Didry
f2baf273ca Webapp development, trying to send the image
Image sending is ok, but for some unknown reason, we can't have status
message displayed once upload is complete.
2014-07-08 23:24:50 +02:00
dattaz
420a37ee75 new file: public/img/lutim128.png
modified:   templates/index.html.ep
	modified:   templates/layouts/default.html.ep
	new file:   templates/manifest.webapp.ep
Add icon 128px for openwebapp (firefox os)
Add accept="image/*" in the form for accept only images
Add JS for install openwebapp and share image to lutim app
Add manifest for openwebapp
2014-07-08 16:54:45 +02:00
Luc Didry
940faed363 Fix #35 2014-07-07 21:54:36 +02:00
Luc Didry
f86b0e71d1 Make uploader label responsive 2014-07-07 21:18:20 +02:00
Luc Didry
7f99d73691 Fix bootstrap error for upload_by_url message when small screen 2014-07-06 17:59:42 +02:00
Chocobozzz
4633fdacef More lisibility for options of the documentation 2014-06-14 15:40:14 +02:00
Luc Didry
bf0e2d47b9 Update Shutter plugin (delete link) 2014-06-06 10:51:53 +02:00
Luc Didry
f044c1e953 Use Lutim namespace for commands 2014-06-03 21:41:30 +02:00
Luc Didry
277053d2a8 Fix asset bundles 2014-06-03 21:32:20 +02:00
Luc Didry
b450ea7607 Small fix on short url generation 2014-06-03 21:30:58 +02:00
Luc Didry
510bfb3eed Small bugfixes
Production always gives surprises !
2014-06-01 18:59:34 +02:00
Luc Didry
673d80cec2 Official release of Lutim 0.3 2014-06-01 18:44:25 +02:00
Luc Didry
d8498b89fd Small fixes 2014-06-01 18:40:51 +02:00
Luc Didry
846d8a1bfe Fix #28 2014-06-01 18:40:16 +02:00
Luc Didry
75dde62989 Modify cache header 2014-06-01 16:25:43 +02:00
Luc Didry
d9c094ff16 No more modal 2014-06-01 16:24:56 +02:00
Luc Didry
37244e866c Small changes in README 2014-06-01 16:21:52 +02:00
Luc Didry
124ccd6ec2 Use Mojolicious::Plugin::AssetPack
Less requests, concatenated and minified css and js => faster !
2014-06-01 16:14:26 +02:00
Luc Didry
3fb3bac3ef Add Chocobozzz as contributor 2014-05-19 21:08:47 +02:00
Chocobozzz
00ccb56c8a correct small error (missing comma) 2014-05-19 14:54:53 +02:00
Luc Didry
8696cd43c4 Update documentation 2014-05-01 21:12:13 +02:00
Luc Didry
313bae584c Change LUTIm to Lutim (easier to write) 2014-05-01 21:05:22 +02:00
Luc Didry
63651bd277 Add Nginx configuration exemple and remove Varnish exemple 2014-05-01 20:54:37 +02:00
Luc Didry
04d38bfdf7 Update Changelog 2014-05-01 20:24:25 +02:00
Luc Didry
d05827102e Fix #27 2014-05-01 20:20:28 +02:00
Luc Didry
eb0dac2b4b Replace SimpleGraph by morris.js in README 2014-05-01 19:26:32 +02:00
Luc Didry
9f130e1761 Update README (markdown corrections) 2014-05-01 19:20:56 +02:00
Luc Didry
1b580a2e76 Add init script 2014-05-01 19:20:10 +02:00
Luc Didry
2a7cb615ba Change in Cache-Control header 2014-05-01 18:38:35 +02:00
Luc Didry
6e9a8a79c8 Add the sticker image
Yes, I know, I shouldn't store binary files like this here, but it's the
best place to me : I will not lost them :)
2014-05-01 18:06:12 +02:00
Luc Didry
a7704987a3 Rewrite the shutter plugin from the official documentation 2014-05-01 18:03:35 +02:00
Luc Didry
6d155a57ab Fix sentence 2014-05-01 17:16:34 +02:00
Luc Didry
cc99fba3fa Update README.md for Gitlab's markdown compliance 2014-05-01 17:15:55 +02:00
Luc Didry
cd699dd6e8 Improve default config values
With Mojolicious::Plugin::ConfigHashMerge, it's a lot simplier to have
default config values.

This simplification induces some simplification in other parts of Lutim.
2014-05-01 17:11:04 +02:00
Luc Didry
c84d5cf691 Update Changelog 2014-04-30 01:33:06 +02:00
Luc Didry
ac301e104c Use the X-Forwarded-Proto header to set the scheme to https if needed
The "https" option in configuration file is deprecated and will be removed in 0.4
2014-04-30 01:31:12 +02:00
Luc Didry
b0af4ccd75 Remote port detection can now use the X-Remote-Port header if set 2014-04-30 00:54:06 +02:00
Luc Didry
6b71bbaf35 Update .gitignore 2014-04-30 00:17:54 +02:00
Luc Didry
0f3b99e617 Improved stats page 2014-04-30 00:17:11 +02:00
Luc Didry
ad5032b720 Fix bugs
* IO::Socket::SSL dependancy for HTTPS downloads by URL
* error message if the downloaded by URL image is too big
* typo in internationalization call
2014-04-30 00:07:21 +02:00
Luc Didry
7e60f4876a Fix #29
Configurable antiflood protection for the "Download by URL" feature
2014-04-29 23:54:27 +02:00
Luc Didry
8264356176 Small bugfix for max_delay handling 2014-03-22 00:06:25 +01:00
Luc Didry
52b1005b69 Comments in configuration template 2014-03-21 23:47:16 +01:00
Luc Didry
c7c8a7d786 Use Henny Penny font for app title
https://www.google.com/fonts#UsePlace:use/Collection:Henny+Penny
2014-03-17 13:03:11 +01:00
Luc Didry
12e0bf093a Fix bug in stats date sorting 2014-03-17 00:15:16 +01:00
Luc Didry
2c4416985b Fix #25
Add explations for the hypnotoad part of the lutim.conf.template
2014-03-07 17:11:09 +01:00
70 changed files with 2561 additions and 1070 deletions

3
.gitignore vendored
View File

@@ -4,3 +4,6 @@ script/hypnotoad.pid
local/*
files/*
templates/data.html.ep
public/img/rezopole.png
public/img/rezopole.xcf
public/packed/*

31
Changes
View File

@@ -1,4 +1,33 @@
Revision history for LUTIm
Revision history for Lutim
0.6 2014-10-03
- Add OpenGraph tags in social page (ex-twitter page)
- Update README.md
- Update info page
0.5 2014-09-24
- Add support for animated gif in Twitter cards (#45)
- Update README.md with Twitter integration informations
- bugfixes
0.4 2014-07-12
- Webapp ! Downloadable directly from the Lutim instance
- Configure expiration delay after uploading (#12)
- Twitter share button in the "upload success" message (#35)
0.3 2014-06-01
- Add a delete link to images (#28)
- Concatenated css and js with Mojolicious::Plugin::AssetPack
- Antiflood protection for the "Download by URL" feature (#29)
- Stats page improved
- Self-documented configuration template
- Remote port detection can now use the X-Remote-Port header if set
- Lutim now uses the X-Forwarded-Proto header to set the scheme to https if needed
The "https" option in configuration file is deprecated and will be removed in 0.4
- Optionally delete images that are no longer viewed after a configurable delay in order to avoid saturation (#27)
- Provide init script
- Update Shutter plugin
- Small bugfixes
0.2 2014-03-07
- Server-side encryption available

241
README.md
View File

@@ -1,25 +1,26 @@
#LUTIm
# Lutim
##What LUTIm means?
## What Lutim means?
It means Let's Upload That Image.
##What does it do?
It stores images and allows you to see them or download them.
## What does it do?
It stores images and allows you to see them, download them or share them on social networks. From version 0.5, the gif images can be displayed as animated gifs in Twitter, but you need a HTTPS server (Twitter requires that. Lutim detects if you have a HTTPS server and displays an static image twitter card if you don't);
Images are indefinitly stored unless you request that they will be deleted at first view or after 24 hours / one week / one month / one year.
##License
LUTIm is licensed under the terms of the AGPL. See the LICENSE file.
## License
Lutim is licensed under the terms of the AGPL. See the LICENSE file.
##Official instance
You can see it working at http://lut.im.
## Official instance
You can see it working at https://lut.im.
##Logo
LUTIm's logo is an adaptation of [Lutin](http://commons.wikimedia.org/wiki/File:Lutin_by_godo.jpg) by [Godo](http://godoillustrateur.wordpress.com/), licensed under the terms of the CC-BY-SA 3.0 license.
## Logo
Lutim's logo is an adaptation of [Lutin](http://commons.wikimedia.org/wiki/File:Lutin_by_godo.jpg) by [Godo](http://godoillustrateur.wordpress.com/), licensed under the terms of the CC-BY-SA 3.0 license.
![LUTIm's logo](http://lut.im/img/LUTIm_small.png)
![Lutim's logo](https://lut.im/img/Lutim_small.png)
##Dependencies
* Carton : Perl dependencies manager, it will get what you need, so don't bother for dependencies (but you can read the file `cpanfile` if you want).
## Dependencies
* Carton : Perl dependencies manager, it will get what you need, so don't bother for Perl modules dependencies (but you can read the file `cpanfile` if you want).
```shell
sudo cpan Carton
@@ -31,50 +32,79 @@ or
sudo apt-get install carton
```
###Thumbnails dependancy
If you want to provide thumbnails of uploaded images, you have to install the *ImageMagick* image manipulation software (<http://www.imagemagick.org/>) and the Image::Magick CPAN module.
* But, on another hand, some modules that Carton will install need to be compiled. So you will need some tools:
```shell
sudo apt-get install build-essential
```
### Thumbnails and animated gifs in Twitter dependancy
If you want to provide thumbnails of uploaded images or have animated gifs in Twitter, you have to install the *ImageMagick* image manipulation software (<http://www.imagemagick.org/>) and the Image::Magick CPAN module.
On Debian, you can do:
```shell
sudo apt-get install perlmagick
```
##Installation
## Twitter integration
If you want to share images of your Lutim instance on Twitter, you have to register your site at <https://cards-dev.twitter.com/validator>.
## Other social networks integration
It seems that you only need to put the Lutim' social page link (the one with `?t`) in your post and it will be automatically embedded.
## Installation
After installing Carton :
```shell
git clone https://github.com/ldidry/lutim.git
git clone https://git.framasoft.org/luc/lutim.git
cd lutim
carton install
cp lutim.conf.template lutim.conf
vi lutim.conf
```
##Configuration
* hypnotoad: listen to listen to, user and group which runs hypnotoad ;
* contact: write something which make people able to contact you (contact form URL, email address, whatever) ;
* secrets: an array of random string. Used by Mojolicious for encrypting session cookies.
* piwik\_img: the Piwik image provides you records of visits without javascript (better privacy than js and cookies) ;
* length: length of the random string part of image's URL (default is 8) ;
* provis\_step: LUTIm provisions random strings for image's URL per pack of `provis_step` (default is 5) ;
* provisioning: number of random strings to provision (default is 100) ;
* hosted\_by: if someone hosts your LUTIm instance, you can add some HTML (a logo for example) to make it appear on index page ;
* tweet\_card\_via: a Twitter account which will appear on Twitter cards ;
* max\_file\_size: well, this is explicit (default is 10Mio = 10485760 octets) ;
* https: 1 if you want to provide secure images URLs (default is 0) ;
* stats\_day\_num: when you generate statistics with `script/lutim cron stats`, you will have stats for the last `stats_day_num` days (default is 365) ;
* keep\_ip\_during: when you delete IP addresses of image's senders with `script/lutim cron cleanbdd`, the IP addresses of images older than `keep_ip_during` days will be deleted (default is 365) ;
* broadcast\_message: put some string (not HTML) here and this message will be displayed on all LUTIm pages (not in JSON responses) ;
* allowed\_domains: array of authorized domains for API calls. Example: `['http://1.example.com', 'http://2.example.com']`. If you want to authorize everyone to use the API: `['*']`.
* default\_delay: what is the default time limit for files? Valid values are 0, 1, 7, 30 and 365;
* max\_delay: if defined, the images will be deleted after that delay (in days), even if they were uploaded with "no delay" (or value superior to max\_delay) option and a warning message will be displayed on homepage;
# always\_encrypt: if set to 1, all images will be encrypted.
## Configuration
The `lutim.conf.template` is self-documented but here is the options that you can set:
##Usage
* **hypnotoad :** address and port to listen to, user and group which runs hypnotoad (if you run Lutim with a different user from what is defined here, be sure that the user which launchs hypnotoad is able to setuid/setgid to the defined user/group, otherwise it will not work and you'll have 100% CPU consumption. Launch hypnotoad with the root user or with the user which is defined here);
* **contact :** write something which make people able to contact you (contact form URL, email address, whatever);
* **secrets :** an array of random string. Used by Mojolicious for encrypting session cookies.
* **piwik_img :** the Piwik image provides you records of visits without javascript (better privacy than js and cookies);
* **length :** length of the random string part of image's URL (default is 8);
* **provis_step :** Lutim provisions random strings for image's URL per pack of `provis_step` (default is 5);
* **provisioning :** number of random strings to provision (default is 100);
* **hosted_by :** if someone hosts your Lutim instance, you can add some HTML (a logo for example) to make it appear on index page;
* **tweet_card_via :** a Twitter account which will appear on Twitter cards;
* **max_file_size :** well, this is explicit (default is 10Mio = 10485760 octets);
* **https :** 1 if you want to provide secure images URLs (default is 0) DEPRECATED, PASS A `X-Forwarded-Proto` HEADER TO LUTIM FROM YOUR REVERSE PROXY INSTEAD;
* **token_length :** length of the secret token used to allow people to delete their images when they want;
* **stats_day_num :** when you generate statistics with `script/lutim cron stats`, you will have stats for the last `stats_day_num` days (default is 365);
* **keep_ip_during :** when you delete IP addresses of image's senders with `script/lutim cron cleanbdd`, the IP addresses of images older than `keep_ip_during` days will be deleted (default is 365);
* **broadcast_message :** put some string (not HTML) here and this message will be displayed on all Lutim pages (not in JSON responses);
* **allowed_domains :** array of authorized domains for API calls. Example: `['http://1.example.com', 'http://2.example.com']`. If you want to authorize everyone to use the API: `['\*']`.
* **default_delay :** what is the default time limit for files? Valid values are 0, 1, 7, 30 and 365;
* **max_delay :** if defined, the images will be deleted after that delay (in days), even if they were uploaded with "no delay" (or value superior to max_delay) option and a warning message will be displayed on homepage;
* **always_encrypt :** if set to 1, all images will be encrypted.
* **delete_no_longer_viewed_files :** if set, the images which have not been viewed since `delete_no_longer_viewed_files` days will be deleted by the `script/lutim cron cleanfiles` command
## Usage
### Starting Lutim from Command line
```
carton exec hypnotoad script/lutim
```
##Update
### Starting Lutim with the init script
```
cp utilities/lutim.init /etc/init.d/lutim
cp utilities/lutim.default /etc/default/lutim
chmod +x /etc/init.d/lutim
chown root:root /etc/init.d/lutim /etc/default/lutim
vim /etc/default/lutim
/etc/init.d/lutim start
```
## Update
```
git pull
carton install
@@ -85,56 +115,114 @@ Yup, that's all (Mojolicious magic), it will listen at "http://127.0.0.1:8080".
For more options (interfaces, user, etc.), change the configuration in `lutim.conf` (have a look at http://mojolicio.us/perldoc/Mojo/Server/Hypnotoad#SETTINGS for the available options).
##Reverse proxy
You can use a reverse proxy like Nginx or Varnish (or Apache with the mod\_proxy module). The web is full of tutos.
***Warning!!!***
If you want to update to Lutim **0.3**, from a previous version, you'll have to modify the database.
Here's a valid *Varnish* configuration:
```
backend lutim {
.host = "127.0.0.1";
.port = "8080";
}
sub vcl_recv {
if (req.restarts == 0) {
set req.http.X-Forwarded-For = client.ip;
sqlite3 lutim.db
PRAGMA writable_schema = 1;
UPDATE SQLITE_MASTER SET SQL = 'CREATE TABLE lutim ( short TEXT PRIMARY KEY, path TEXT, footprint TEXT, enabled INTEGER, mediatype TEXT, filename TEXT, counter INTEGER, delete_at_first_view INTEGER, delete_at_day INTEGER, created_at INTEGER, created_by TEXT, last_access_at INTEGER, mod_token TEXT)' WHERE NAME = 'lutim';
PRAGMA writable_schema = 0;
```
***Warning!!!***
If you want to update to Lutim **0.5**, from a previous version, you'll have to modify the database.
```
sqlite3 lutim.db
PRAGMA writable_schema = 1;
UPDATE SQLITE_MASTER SET SQL = 'CREATE TABLE lutim ( short TEXT PRIMARY KEY, path TEXT, footprint TEXT, enabled INTEGER, mediatype TEXT, filename TEXT, counter INTEGER, delete_at_first_view INTEGER, delete_at_day INTEGER, created_at INTEGER, created_by TEXT, last_access_at INTEGER, mod_token TEXT, width INTEGER, height INTEGER)' WHERE NAME = 'lutim';
PRAGMA writable_schema = 0;
```
## Reverse proxy
You can use a reverse proxy like Nginx or Varnish (or Apache with the mod_proxy module). The web is full of tutos.
Here's a valid *Nginx* configuration:
```
server {
listen 80;
root /path/to/lutim/public;
# This is important for user's privacy !
access_log off;
error_log /var/log/nginx/lutim.error.log;
# This is important ! Make it OK with your Lutim configuration
client_max_body_size 40M;
location ~* ^/(img|css|font|js)/ {
try_files $uri @lutim;
add_header Expires "Thu, 31 Dec 2037 23:55:55 GMT";
add_header Cache-Control "public, max-age=315360000";
# HTTPS only header, improves security
#add_header Strict-Transport-Security "max-age=15768000";
}
if (req.http.host == "lut.im") {
set req.backend = lutim;
location / {
try_files $uri @lutim;
# HTTPS only header, improves security
#add_header Strict-Transport-Security "max-age=15768000";
}
location @lutim {
# Adapt this to your configuration
# My advice: put a varnish between nginx and Lutim, it's really useful when images are widely viewed
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# If you want to log the remote port of the image senders, you'll need that
proxy_set_header X-Remote-Port $remote_port;
# Lutim reads this header and understands that the current session is actually HTTPS.
# Enable it if you run a HTTPS server (in this case, don't forgot to change the listen port above)
#proxy_set_header X-Forwarded-Proto https;
# We expect the downsteam servers to redirect to the right hostname, so don't do any rewrites here.
proxy_redirect off;
}
}
```
##Cron jobs
LUTIm have commands which can be used in cron jobs.
## Cron jobs
Lutim have commands which can be used in cron jobs.
To see what commands are available:
```shell
carton exec script/lutim cron
```
###Statistics
### Statistics
To generate statistics which can be viewed at the address `/stats` (we need to reload hypnotoad after the stats generation):
```shell
carton exec script/lutim cron stats && carton exec hypnotoad script/lutim
```
###Delete IP adresses from database
### Delete IP adresses from database
To automatically delete the IP addresses of image's senders after a configurable delay:
```shell
carton exec script/lutim cron cleanbdd
```
###Delete expired files
### Delete expired files
To automatically delete files which availability delay is over (when you choose that your image will be deleted after 24h / one week / etc.)
If `delete_no_longer_viewed_files`, the files not viewed since `delete_no_longer_viewed_files` days will be deleted too.
```shell
carton exec script/lutim cron cleanfiles
```
###Watch the size of the files directory
### Watch the size of the files directory
To execute an action when the files directory is heavier than `max_total_size`.
The available actions are `warn` and `stop-upload`:
* `warn` prints a message on the standard out (which is normally mailed to you by `cron`) ;
* `stop-upload` prints a message on the standard out and creates the `stop-upload` file which prevents uploading and put a warn on LUTIm interface ;
* `stop-upload` prints a message on the standard out and creates the `stop-upload` file which prevents uploading and put a warn on Lutim interface ;
* **DANGEROUS OPTION!!!** `delete` prints a message on the standard out and delete older images until the files directory goes under quota.
If the files directory go under quota, the `stop-upload` file is deleted. If you want to manually prevents uploading, create a file named `stop-upload.manual`.
@@ -143,7 +231,7 @@ If the files directory go under quota, the `stop-upload` file is deleted. If you
carton exec script/lutim cron watch
```
##Broadcast message
## Broadcast message
Set a string in the `broadcast_message` option of `lutim.conf` and reload the server with:
```shell
carton exec hypnotoad script/lutim
@@ -151,12 +239,12 @@ carton exec hypnotoad script/lutim
It may take a few reloads of page before the message is displayed.
##Encryption
LUTIm do encryption on the server if asked to, but does not store the key.
## Encryption
Lutim does encryption on the server if asked to, but does not store the key.
The encryption is made on the server since LUTIm is made to be usable even without javascript. If you want to add client-side encryption for javascript-enabled browsers, patches are welcome.
The encryption is made on the server since Lutim is made to be usable even without javascript. If you want to add client-side encryption for javascript-enabled browsers, patches are welcome.
##API
## API
You can add images by using the API. Here's the parameters of the `POST` request to `/` adress:.
* format: json
MANDATORY if you want to get a json response, otherwise it will send a web page
@@ -173,10 +261,10 @@ Exemple with curl:
curl -F "format=json" -F "file=@/tmp/snap0001.jpg" http://lut.im
```
You can allow people to use your instance of LUTIm from other domains.
Add the allowed domains as an array in the `allowed_domains` conf option. Put '`[*]`' if you want to allow all domains.
You can allow people to use your instance of Lutim from other domains.
Add the allowed domains as an array in the `allowed_domains` conf option. Put '`[\*]`' if you want to allow all domains.
##Shutter integration
## Shutter integration
See where Shutter (<http://en.wikipedia.org/wiki/Shutter_%28software%29>) keeps its plugins on your computer.
On my computer, it's in `/usr/share/shutter/resources/system/upload_plugins/upload`.
@@ -187,24 +275,31 @@ sudo cp utilities/Shutter.pm /usr/share/shutter/resources/system/upload_plugins/
And restart Shutter if it was running.
Of course, this plugin is configured for the official instance of LUTIm (<http://lut.im>), feel free to edit it for your own instance.
Of course, this plugin is configured for the official instance of Lutim (<http://lut.im>), feel free to edit it for your own instance.
##Internationalization
LUTIm comes with English and French languages. It will choose the language to display from the browser's settings.
## Internationalization
Lutim comes with English and French languages. It will choose the language to display from the browser's settings.
If you want to add more languages, for example German:
```shell
cd lib/I18N
cd lib/Lutim/I18N
cp en.pm de.pm
vim de.pm
```
There's just a few sentences, so it will be quick to translate. Please consider to send me you language file in order to help the other users :smile:.
##Others projects dependancies
LUTIm is written in Perl with the [Mojolicious](http://mojolicio.us) framework, uses the [Twitter bootstrap](http://getbootstrap.com) framework to look not too ugly, [JQuery](http://jquery.com) and [JQuery File Uploader](https://github.com/danielm/uploader/) (slightly modified) to add some modernity, [Raphaël](http://raphaeljs.com/) and [SimpleGraph](http://benaskins.github.io/simplegraph/) for stats graphs.
## Others projects dependancies
Lutim is written in Perl with the [Mojolicious](http://mojolicio.us) framework, uses the [Twitter bootstrap](http://getbootstrap.com) framework to look not too ugly, [JQuery](http://jquery.com) and [JQuery File Uploader](https://github.com/danielm/uploader/) (slightly modified) to add some modernity, [Raphaël](http://raphaeljs.com/) and [morris.js](http://www.oesmith.co.uk/morris.js/) for stats graphs and [freezeframe.js](http://freezeframe.chrisantonellis.com/) (slightly modified) to be able to freeze animated gifs in twitter card.
##Contributors
* Luc Didry, aka Sky (<http://www.fiat-tux.fr>), main developer
Licenses for the icons fonts are in `public/font/LICENSE.txt`.
## Main developers
* Luc Didry, aka Sky (<http://www.fiat-tux.fr>), core developer, @framasky on [Twitter](https://twitter.com/framasky) and on [Diaspora*](https://framasphere.org/public/framasky)
* Dattaz (<http://dattaz.fr>), webapp developer, [@dat_taz](https://twitter.com/dat_taz)
## Contributors
* Jean-Bernard Marcon, aka Goofy (<https://github.com/goofy-bz>)
* Jean-Christophe Bach (<https://github.com/jcb>)
* Florian Bigard, aka Chocobozzz (<https://github.com/Chocobozzz>)
* Sandro CAZZANIGA, aka Kharec (<http://sandrocazzaniga.fr>), [@Kharec](https://twitter.com/Kharec)

View File

@@ -371,13 +371,11 @@
"progress-bars.less",
"close.less",
"component-animations.less",
"modals.less",
"utilities.less",
"responsive-utilities.less"
],
"js": [
"alert.js",
"modal.js",
"transition.js"
]
}

View File

@@ -1,6 +1,9 @@
requires 'Mojolicious';
requires 'EV';
requires 'Data::Validate::URI';
requires 'Mojolicious::Plugin::I18N';
requires 'Mojolicious::Plugin::ConfigHashMerge';
requires 'Mojolicious::Plugin::AssetPack';
requires 'ORLite';
requires 'File::Type';
requires 'Text::Unidecode';

View File

@@ -1,5 +1,41 @@
# carton snapshot format: version 1.0
DISTRIBUTIONS
Algorithm-Diff-1.1902
pathname: T/TY/TYEMQ/Algorithm-Diff-1.1902.tar.gz
provides:
Algorithm::Diff 1.1902
Algorithm::Diff::_impl 1.1902
Algorithm::DiffOld 1.1
requirements:
ExtUtils::MakeMaker 0
CSS-Minifier-XS-0.09
pathname: G/GT/GTERMARS/CSS-Minifier-XS-0.09.tar.gz
provides:
CSS::Minifier::XS 0.09
requirements:
ExtUtils::CBuilder 0
Test::More 0
Carp-1.3301
pathname: Z/ZE/ZEFRAM/Carp-1.3301.tar.gz
provides:
Carp 1.3301
Carp::Heavy 1.3301
requirements:
Config 0
Exporter 0
ExtUtils::MakeMaker 0
IPC::Open3 1.0103
Test::More 0
overload 0
parent 0
strict 0
warnings 0
Class-Data-Inheritable-0.08
pathname: T/TM/TMTM/Class-Data-Inheritable-0.08.tar.gz
provides:
Class::Data::Inheritable 0.08
requirements:
ExtUtils::MakeMaker 0
Class-Load-0.21
pathname: E/ET/ETHER/Class-Load-0.21.tar.gz
provides:
@@ -26,6 +62,13 @@ DISTRIBUTIONS
Class::Singleton 1.4
requirements:
ExtUtils::MakeMaker 0
Clone-0.36
pathname: G/GA/GARU/Clone-0.36.tar.gz
provides:
Clone 0.36
requirements:
ExtUtils::MakeMaker 0
Test::More 0
Crypt-Blowfish-2.14
pathname: D/DP/DPARIS/Crypt-Blowfish-2.14.tar.gz
provides:
@@ -188,16 +231,16 @@ DISTRIBUTIONS
Data::Validate::Domain 0
Data::Validate::IP 0
ExtUtils::MakeMaker 0
DateTime-1.07
pathname: D/DR/DROLSKY/DateTime-1.07.tar.gz
DateTime-1.10
pathname: D/DR/DROLSKY/DateTime-1.10.tar.gz
provides:
DateTime 1.07
DateTime::Duration 1.07
DateTime::Helpers 1.07
DateTime::Infinite 1.07
DateTime::Infinite::Future 1.07
DateTime::Infinite::Past 1.07
DateTime::LeapSecond 1.07
DateTime 1.10
DateTime::Duration 1.10
DateTime::Helpers 1.10
DateTime::Infinite 1.10
DateTime::Infinite::Future 1.10
DateTime::Infinite::Past 1.10
DateTime::LeapSecond 1.10
inc::MyModuleBuild undef
requirements:
Carp 0
@@ -218,6 +261,7 @@ DISTRIBUTIONS
strict 0
vars 0
warnings 0
warnings::register 0
DateTime-Locale-0.45
pathname: D/DR/DROLSKY/DateTime-Locale-0.45.tar.gz
provides:
@@ -692,7 +736,7 @@ DISTRIBUTIONS
DateTime::Locale::zu_ZA undef
requirements:
List::MoreUtils 0
Module::Build 0
Module::Build 0.36
Params::Validate 0.91
perl 5.006
DateTime-TimeZone-1.64
@@ -1122,6 +1166,18 @@ DISTRIBUTIONS
strict 0
vars 0
warnings 0
Devel-StackTrace-1.31
pathname: D/DR/DROLSKY/Devel-StackTrace-1.31.tar.gz
provides:
Devel::StackTrace 1.31
Devel::StackTrace::Frame 1.31
requirements:
ExtUtils::MakeMaker 6.30
File::Spec 0
Scalar::Util 0
overload 0
strict 0
warnings 0
Dist-CheckConflicts-0.10
pathname: D/DO/DOY/Dist-CheckConflicts-0.10.tar.gz
provides:
@@ -1135,6 +1191,31 @@ DISTRIBUTIONS
base 0
strict 0
warnings 0
EV-4.17
pathname: M/ML/MLEHMANN/EV-4.17.tar.gz
provides:
EV 4.17
EV::MakeMaker undef
requirements:
ExtUtils::MakeMaker 0
common::sense 0
Exception-Class-1.37
pathname: D/DR/DROLSKY/Exception-Class-1.37.tar.gz
provides:
Exception::Class 1.37
Exception::Class::Base 1.37
requirements:
Class::Data::Inheritable 0.02
Devel::StackTrace 1.20
ExtUtils::MakeMaker 6.30
File::Spec 0
Scalar::Util 0
Test::More 0.88
base 0
overload 0
strict 0
vars 0
warnings 0
ExtUtils-Config-0.007
pathname: L/LE/LEONT/ExtUtils-Config-0.007.tar.gz
provides:
@@ -1178,40 +1259,40 @@ DISTRIBUTIONS
File::Spec 0
strict 0
warnings 0
ExtUtils-MakeMaker-6.88
pathname: B/BI/BINGOS/ExtUtils-MakeMaker-6.88.tar.gz
ExtUtils-MakeMaker-6.96
pathname: B/BI/BINGOS/ExtUtils-MakeMaker-6.96.tar.gz
provides:
DynaLoader 6.88
ExtUtils::Command::MM 6.88
ExtUtils::Liblist 6.88
ExtUtils::Liblist::Kid 6.88
ExtUtils::MM 6.88
ExtUtils::MM_AIX 6.88
ExtUtils::MM_Any 6.88
ExtUtils::MM_BeOS 6.88
ExtUtils::MM_Cygwin 6.88
ExtUtils::MM_DOS 6.88
ExtUtils::MM_Darwin 6.88
ExtUtils::MM_MacOS 6.88
ExtUtils::MM_NW5 6.88
ExtUtils::MM_OS2 6.88
ExtUtils::MM_QNX 6.88
ExtUtils::MM_UWIN 6.88
ExtUtils::MM_Unix 6.88
ExtUtils::MM_VMS 6.88
ExtUtils::MM_VOS 6.88
ExtUtils::MM_Win32 6.88
ExtUtils::MM_Win95 6.88
ExtUtils::MY 6.88
ExtUtils::MakeMaker 6.88
ExtUtils::MakeMaker::Config 6.88
ExtUtils::MakeMaker::_version 6.88
ExtUtils::Mkbootstrap 6.88
ExtUtils::Mksymlists 6.88
ExtUtils::testlib 6.88
MM 6.88
MY 6.88
in 6.88
DynaLoader 6.96
ExtUtils::Command::MM 6.96
ExtUtils::Liblist 6.96
ExtUtils::Liblist::Kid 6.96
ExtUtils::MM 6.96
ExtUtils::MM_AIX 6.96
ExtUtils::MM_Any 6.96
ExtUtils::MM_BeOS 6.96
ExtUtils::MM_Cygwin 6.96
ExtUtils::MM_DOS 6.96
ExtUtils::MM_Darwin 6.96
ExtUtils::MM_MacOS 6.96
ExtUtils::MM_NW5 6.96
ExtUtils::MM_OS2 6.96
ExtUtils::MM_QNX 6.96
ExtUtils::MM_UWIN 6.96
ExtUtils::MM_Unix 6.96
ExtUtils::MM_VMS 6.96
ExtUtils::MM_VOS 6.96
ExtUtils::MM_Win32 6.96
ExtUtils::MM_Win95 6.96
ExtUtils::MY 6.96
ExtUtils::MakeMaker 6.96
ExtUtils::MakeMaker::Config 6.96
ExtUtils::MakeMaker::_version 6.96
ExtUtils::Mkbootstrap 6.96
ExtUtils::Mksymlists 6.96
ExtUtils::testlib 6.96
MM 6.96
MY 6.96
in 6.96
requirements:
Data::Dumper 0
DirHandle 0
@@ -1237,6 +1318,17 @@ DISTRIBUTIONS
requirements:
IO::File 0.01
Test::More 0.01
File-Which-1.09
pathname: A/AD/ADAMK/File-Which-1.09.tar.gz
provides:
File::Which 1.09
requirements:
Exporter 0
ExtUtils::MakeMaker 0
File::Spec 0.60
Getopt::Std 0
Test::More 0.80
Test::Script 1.05
Filesys-DiskUsage-0.05
pathname: C/CO/COG/Filesys-DiskUsage-0.05.tar.gz
provides:
@@ -1245,6 +1337,31 @@ DISTRIBUTIONS
ExtUtils::MakeMaker 0
File::Basename 0
Test::More 0
Hash-Merge-Simple-0.051
pathname: R/RO/ROKR/Hash-Merge-Simple-0.051.tar.gz
provides:
Hash::Merge::Simple 0.051
requirements:
Clone 0
ExtUtils::MakeMaker 6.31
Storable 0
Test::Most 0
IO-Socket-SSL-1.997
pathname: S/SU/SULLR/IO-Socket-SSL-1.997.tar.gz
provides:
IO::Socket::SSL 1.997
IO::Socket::SSL::Intercept 1.93
IO::Socket::SSL::OCSP_Cache 1.997
IO::Socket::SSL::OCSP_Resolver 1.997
IO::Socket::SSL::PublicSuffix undef
IO::Socket::SSL::SSL_Context 1.997
IO::Socket::SSL::SSL_HANDLE 1.997
IO::Socket::SSL::Session_Cache 1.997
IO::Socket::SSL::Utils 0.03
requirements:
ExtUtils::MakeMaker 0
Net::SSLeay 1.46
Scalar::Util 0
IPC-Run3-0.046
pathname: R/RJ/RJBS/IPC-Run3-0.046.tar.gz
provides:
@@ -1253,6 +1370,14 @@ DISTRIBUTIONS
ExtUtils::MakeMaker 0
Test::More 0.31
Time::HiRes 0
JavaScript-Minifier-XS-0.09
pathname: G/GT/GTERMARS/JavaScript-Minifier-XS-0.09.tar.gz
provides:
JavaScript::Minifier::XS 0.09
requirements:
ExtUtils::CBuilder 0
Test::More 0
perl v5.8.8
List-MoreUtils-0.33
pathname: A/AD/ADAMK/List-MoreUtils-0.33.tar.gz
provides:
@@ -1262,16 +1387,6 @@ DISTRIBUTIONS
ExtUtils::MakeMaker 6.52
Test::More 0.82
perl 5.00503
MIME-Types-2.04
pathname: M/MA/MARKOV/MIME-Types-2.04.tar.gz
provides:
MIME::Type 2.04
MIME::Types 2.04
requirements:
ExtUtils::MakeMaker 0
File::Basename 0
File::Spec 0
Test::More 0.47
Module-Build-0.4205
pathname: L/LE/LEONT/Module-Build-0.4205.tar.gz
provides:
@@ -1377,8 +1492,8 @@ DISTRIBUTIONS
perl 5.006
strict 0
warnings 0
Mojolicious-4.87
pathname: S/SR/SRI/Mojolicious-4.87.tar.gz
Mojolicious-5.12
pathname: S/SR/SRI/Mojolicious-5.12.tar.gz
provides:
Mojo undef
Mojo::Asset undef
@@ -1441,7 +1556,7 @@ DISTRIBUTIONS
Mojo::UserAgent::Server undef
Mojo::UserAgent::Transactor undef
Mojo::Util undef
Mojolicious 4.87
Mojolicious 5.12
Mojolicious::Command undef
Mojolicious::Command::cgi undef
Mojolicious::Command::cpanify undef
@@ -1490,14 +1605,35 @@ DISTRIBUTIONS
requirements:
ExtUtils::MakeMaker 0
perl 5.010001
Mojolicious-Plugin-I18N-1.21
pathname: S/SH/SHARIFULN/Mojolicious-Plugin-I18N-1.21.tar.gz
Mojolicious-Plugin-AssetPack-0.16
pathname: J/JH/JHTHORSEN/Mojolicious-Plugin-AssetPack-0.16.tar.gz
provides:
Mojolicious::Plugin::I18N 1.21
Mojolicious::Plugin::AssetPack 0.16
Mojolicious::Plugin::AssetPack::Preprocessors 0.01
requirements:
CSS::Minifier::XS 0.01
ExtUtils::MakeMaker 0
File::Which 1.00
IPC::Run3 0.04
JavaScript::Minifier::XS 0.01
Mojolicious 4.30
Test::More 0.88
Mojolicious-Plugin-ConfigHashMerge-0.01
pathname: D/DO/DOTAN/Mojolicious-Plugin-ConfigHashMerge-0.01.tar.gz
provides:
Mojolicious::Plugin::ConfigHashMerge 0.01
requirements:
ExtUtils::MakeMaker 0
Hash::Merge::Simple 0.051
Mojolicious 4.85
Mojolicious-Plugin-I18N-1.3
pathname: S/SH/SHARIFULN/Mojolicious-Plugin-I18N-1.3.tar.gz
provides:
Mojolicious::Plugin::I18N 1.3
requirements:
I18N::LangTags 0.35
Module::Build 0.42
Mojolicious 4
Mojolicious 5
Test::More 0
perl 5.010001
Net-Domain-TLD-1.70
@@ -1508,6 +1644,16 @@ DISTRIBUTIONS
Carp 0
ExtUtils::MakeMaker 0
Storable 0
Net-SSLeay-1.58
pathname: M/MI/MIKEM/Net-SSLeay-1.58.tar.gz
provides:
Net::SSLeay 1.58
Net::SSLeay::Handle 0.61
requirements:
ExtUtils::MakeMaker 6.36
MIME::Base64 0
Test::More 0.60_01
perl 5.005
NetAddr-IP-4.072
pathname: M/MI/MIKER/NetAddr-IP-4.072.tar.gz
provides:
@@ -1610,13 +1756,22 @@ DISTRIBUTIONS
requirements:
Attribute::Handlers 0.79
Carp 0
Devel::Peek 0
Exporter 0
ExtUtils::CBuilder 0
File::Spec 0
File::Temp 0
Module::Build 0.3601
Module::Implementation 0
Scalar::Util 1.10
Test::Fatal 0
Test::More 0.88
Tie::Array 0
Tie::Hash 0
XSLoader 0
attributes 0
base 0
overload 0
perl 5.008001
strict 0
vars 0
@@ -1641,14 +1796,110 @@ DISTRIBUTIONS
Scalar::Util 0
strict 0
warnings 0
Switch-2.16
pathname: R/RG/RGARCIA/Switch-2.16.tar.gz
Sub-Uplevel-0.24
pathname: D/DA/DAGOLDEN/Sub-Uplevel-0.24.tar.gz
provides:
Switch 2.16
Sub::Uplevel 0.24
requirements:
Carp 0
Exporter 0
ExtUtils::MakeMaker 6.30
File::Find 0
File::Temp 0
Test::More 0
constant 0
strict 0
warnings 0
Switch-2.17
pathname: C/CH/CHORNY/Switch-2.17.tar.gz
provides:
Switch 2.17
requirements:
ExtUtils::MakeMaker 0
Filter::Util::Call 0
Text::Balanced 0
Text::Balanced 2
if 0
perl 5.005
Test-Deep-0.112
pathname: R/RJ/RJBS/Test-Deep-0.112.tar.gz
provides:
Test::Deep 0.112
Test::Deep::All undef
Test::Deep::Any undef
Test::Deep::Array undef
Test::Deep::ArrayEach undef
Test::Deep::ArrayElementsOnly undef
Test::Deep::ArrayLength undef
Test::Deep::ArrayLengthOnly undef
Test::Deep::Blessed undef
Test::Deep::Boolean undef
Test::Deep::Cache undef
Test::Deep::Cache::Simple undef
Test::Deep::Class undef
Test::Deep::Cmp undef
Test::Deep::Code undef
Test::Deep::Hash undef
Test::Deep::HashEach undef
Test::Deep::HashElements undef
Test::Deep::HashKeys undef
Test::Deep::HashKeysOnly undef
Test::Deep::Ignore undef
Test::Deep::Isa undef
Test::Deep::ListMethods undef
Test::Deep::MM undef
Test::Deep::Methods undef
Test::Deep::NoTest undef
Test::Deep::Number undef
Test::Deep::Obj undef
Test::Deep::Ref undef
Test::Deep::RefType undef
Test::Deep::Regexp undef
Test::Deep::RegexpMatches undef
Test::Deep::RegexpRef undef
Test::Deep::RegexpRefOnly undef
Test::Deep::RegexpVersion undef
Test::Deep::ScalarRef undef
Test::Deep::ScalarRefOnly undef
Test::Deep::Set undef
Test::Deep::Shallow undef
Test::Deep::Stack undef
Test::Deep::String undef
Test::Deep::SubHash undef
Test::Deep::SubHashElements undef
Test::Deep::SubHashKeys undef
Test::Deep::SubHashKeysOnly undef
Test::Deep::SuperHash undef
Test::Deep::SuperHashElements undef
Test::Deep::SuperHashKeys undef
Test::Deep::SuperHashKeysOnly undef
requirements:
ExtUtils::MakeMaker 0
List::Util 1.09
Scalar::Util 1.09
Test::More 0
Test::NoWarnings 0.02
Test::Tester 0.04
Test-Differences-0.61
pathname: O/OV/OVID/Test-Differences-0.61.tar.gz
provides:
Test::Differences 0.61
requirements:
Data::Dumper 2.126
Module::Build 0.36
Test::More 0
Text::Diff 0.35
Test-Exception-0.32
pathname: A/AD/ADIE/Test-Exception-0.32.tar.gz
provides:
Test::Exception 0.32
requirements:
Module::Build 0.38
Sub::Uplevel 0.18
Test::Builder 0.7
Test::Builder::Tester 1.07
Test::Harness 2.03
Test::More 0.7
Test::Simple 0.7
Test-Fatal-0.013
pathname: R/RJ/RJBS/Test-Fatal-0.013.tar.gz
provides:
@@ -1714,6 +1965,31 @@ DISTRIBUTIONS
Test::Harness 3.30
requirements:
ExtUtils::MakeMaker 0
Test-Most-0.33
pathname: O/OV/OVID/Test-Most-0.33.tar.gz
provides:
Test::Most 0.33
Test::Most::Exception 0.33
requirements:
Exception::Class 1.14
ExtUtils::MakeMaker 0
Test::Deep 0.106
Test::Differences 0.61
Test::Exception 0.31
Test::Harness 3.21
Test::More 0.88
Test::Warn 0.23
Test-NoWarnings-1.04
pathname: A/AD/ADAMK/Test-NoWarnings-1.04.tar.gz
provides:
Test::NoWarnings 1.04
Test::NoWarnings::Warning 1.04
requirements:
ExtUtils::MakeMaker 0
Test::Builder 0.86
Test::More 0.47
Test::Tester 0.107
perl 5.006
Test-Requires-0.07
pathname: T/TO/TOKUHIROM/Test-Requires-0.07.tar.gz
provides:
@@ -1739,12 +2015,48 @@ DISTRIBUTIONS
Test::Builder::Tester 1.02
Test::More 0.62
blib 0
Text-Unidecode-0.04
pathname: S/SB/SBURKE/Text-Unidecode-0.04.tar.gz
Test-Tester-0.109
pathname: F/FD/FDALY/Test-Tester-0.109.tar.gz
provides:
Text::Unidecode 0.04
Test::Tester 0.109
Test::Tester::Capture undef
Test::Tester::CaptureRunner undef
Test::Tester::Delegate undef
requirements:
ExtUtils::MakeMaker 0
Test::Builder 0
Test-Warn-0.30
pathname: C/CH/CHORNY/Test-Warn-0.30.tar.gz
provides:
Test::Warn 0.30
Test::Warn::Categorization 0.30
requirements:
Carp 1.22
ExtUtils::MakeMaker 0
File::Spec 0
Sub::Uplevel 0.12
Test::Builder 0.13
Test::Builder::Tester 1.02
Test::More 0
perl 5.006
Text-Diff-1.41
pathname: O/OV/OVID/Text-Diff-1.41.tar.gz
provides:
Text::Diff 1.41
Text::Diff::Base 1.41
Text::Diff::Config 1.41
Text::Diff::Table 1.41
requirements:
Algorithm::Diff 1.19
Exporter 0
ExtUtils::MakeMaker 0
Text-Unidecode-1.01
pathname: S/SB/SBURKE/Text-Unidecode-1.01.tar.gz
provides:
Text::Unidecode 1.01
requirements:
ExtUtils::MakeMaker 0
perl 5.008
Try-Tiny-0.19
pathname: D/DO/DOY/Try-Tiny-0.19.tar.gz
provides:
@@ -1756,3 +2068,9 @@ DISTRIBUTIONS
constant 0
strict 0
warnings 0
common-sense-3.73
pathname: M/ML/MLEHMANN/common-sense-3.73.tar.gz
provides:
common::sense 3.73
requirements:
ExtUtils::MakeMaker 0

64
fontello-config.json Normal file
View File

@@ -0,0 +1,64 @@
{
"name": "fontello",
"css_prefix_text": "icon-",
"css_use_suffix": false,
"hinting": true,
"units_per_em": 1000,
"ascent": 850,
"glyphs": [
{
"uid": "c5fd349cbd3d23e4ade333789c29c729",
"css": "eye",
"code": 59399,
"src": "fontawesome"
},
{
"uid": "9a76bc135eac17d2c8b8ad4a5774fc87",
"css": "download",
"code": 59397,
"src": "fontawesome"
},
{
"uid": "4aad6bb50b02c18508aae9cbe14e784e",
"css": "share",
"code": 59400,
"src": "fontawesome"
},
{
"uid": "f48ae54adfb27d8ada53d0fd9e34ee10",
"css": "trash",
"code": 59396,
"src": "fontawesome"
},
{
"uid": "2cb15eb2b295ee3c33fab1530e18a924",
"css": "bitcoin",
"code": 59395,
"src": "fontawesome"
},
{
"uid": "0f6a2573a7b6df911ed199bb63717e27",
"css": "github-circled",
"code": 59393,
"src": "fontawesome"
},
{
"uid": "627abcdb627cb1789e009c08e2678ef9",
"css": "twitter",
"code": 59392,
"src": "fontawesome"
},
{
"uid": "fccd3ea0efb711b849045bee686b1ceb",
"css": "spinner",
"code": 59398,
"src": "mfglabs"
},
{
"uid": "c71d7db10ede1349b3a8ae0293b1dbf8",
"css": "flattr",
"code": 59394,
"src": "zocial"
}
]
}

View File

@@ -10,20 +10,35 @@ mkdir($ENV{MOJO_TMPDIR}, 0700) unless (-d $ENV{MOJO_TMPDIR});
sub startup {
my $self = shift;
$self->plugin('I18N');
push @{$self->commands->namespaces}, 'Lutim::Command';
my $config = $self->plugin('Config');
$self->{wait_for_it} = {};
$self->plugin('I18N');
$self->plugin('AssetPack');
my $config = $self->plugin('ConfigHashMerge', {
default => {
provisioning => 100,
provis_step => 5,
length => 8,
always_encrypt => 0,
anti_flood_delay => 5,
tweet_card_via => '@framasky',
max_file_size => 10*1024*1024,
https => 0,
default_delay => 0,
max_delay => 0,
token_length => 24,
}
});
# Default values
$config->{provisioning} = 100 unless (defined($config->{provisionning}));
$config->{provisioning} = 100 unless (defined($config->{provisioning}));
$config->{provis_step} = 5 unless (defined($config->{provis_step}));
$config->{length} = 8 unless (defined($config->{length}));
$config->{always_encrypt} = 0 unless (defined($config->{always_encrypt}));
$config->{provisioning} = $config->{provisionning} if (defined($config->{provisionning}));
die "You need to provide a contact information in lutim.conf !" unless (defined($config->{contact}));
$ENV{MOJO_MAX_MESSAGE_SIZE} = $config->{max_file_size} if (defined($config->{max_file_size}));
$ENV{MOJO_MAX_MESSAGE_SIZE} = $config->{max_file_size};
$self->secrets($config->{secrets});
@@ -47,7 +62,7 @@ sub startup {
my $headers = Mojo::Headers->new();
if ($nocache) {
$headers->add('Cache-Control' => 'no-cache');
$headers->add('Cache-Control' => 'no-cache, no-store, max-age=0, must-revalidate');
} else {
$headers->add('Expires' => $expires);
}
@@ -71,14 +86,15 @@ sub startup {
$self->helper(
ip => sub {
my $c = shift;
my $ip_only = shift || 0;
my $proxy = $c->req->headers->header('X-Forwarded-For');
my $proxy = '';
my @x_forward = $c->req->headers->header('X-Forwarded-For');
for my $x (@x_forward) {
$proxy .= join(', ', @$x);
}
my $ip = ($proxy) ? $proxy : $c->tx->remote_address;
return $ip.' Remote port: '.$c->tx->remote_port;
my $remote_port = (defined($c->req->headers->header('X-Remote-Port'))) ? $c->req->headers->header('X-Remote-Port') : $c->tx->remote_port;
return ($ip_only) ? $ip : "$ip remote port:$remote_port";
}
);
@@ -93,14 +109,15 @@ sub startup {
my $short;
do {
$short= $c->shortener($c->config->{length});
} while (LutimModel::Lutim->count('WHERE short = ?', $short) || $short eq 'about' || $short eq 'stats');
} while (LutimModel::Lutim->count('WHERE short = ?', $short) || $short eq 'about' || $short eq 'stats' || $short eq 'd' || $short eq 'm');
LutimModel::Lutim->create(
short => $short,
counter => 0,
enabled => 1,
delete_at_first_view => 0,
delete_at_day => 0
delete_at_day => 0,
mod_token => $c->shortener($c->config->{token_length})
);
LutimModel->commit;
}
@@ -141,14 +158,9 @@ sub startup {
max_delay => sub {
my $c = shift;
if (defined($c->config->{max_delay})) {
my $delay = $c->config->{max_delay};
if ($delay >= 0) {
return $delay;
} else {
warn "max_delay set to a negative value. Default to 0."
}
}
return $c->config->{max_delay} if ($c->config->{max_delay} >= 0);
warn "max_delay set to a negative value. Default to 0.";
return 0;
}
);
@@ -157,14 +169,9 @@ sub startup {
default_delay => sub {
my $c = shift;
if (defined($c->config->{default_delay})) {
my $delay = $c->config->{default_delay};
if ($delay >= 0) {
return $delay;
} else {
warn "default_delay set to a negative value. Default to 0."
}
}
return $c->config->{default_delay} if ($c->config->{default_delay} >= 0);
warn "default_delay set to a negative value. Default to 0.";
return 0;
}
);
@@ -236,10 +243,21 @@ sub startup {
}
);
$self->helper(
delete_image => sub {
my $c = shift;
my $image = shift;
unlink $image->path();
$image->update(enabled => 0);
}
);
$self->hook(
before_dispatch => sub {
my $c = shift;
$c->stop_upload();
# API allowed domains
if (defined($c->config->{allowed_domains})) {
if ($c->config->{allowed_domains}->[0] eq '*') {
$c->res->headers->header('Access-Control-Allow-Origin' => '*');
@@ -252,15 +270,32 @@ sub startup {
}
}
}
# Scheme detection
if ((defined($c->req->headers->header('X-Forwarded-Proto')) && $c->req->headers->header('X-Forwarded-Proto') eq 'https') || $c->config->{https}) {
$c->req->url->base->scheme('https');
}
}
);
$self->hook(
after_dispatch => sub {
shift->provisioning();
my $c = shift;
$c->provisioning();
# Purge expired anti-flood protection
my $wait_for_it = $c->app->{wait_for_it};
delete @{$wait_for_it}{grep { time - $wait_for_it->{$_} > $c->config->{anti_flood_delay} } keys %{$wait_for_it}} if (defined($wait_for_it));
}
);
$self->asset('index.css' => 'css/bootstrap.min.css', 'css/fontello-embedded.css', 'css/animation.css', 'css/uploader.css', 'css/hennypenny.css', 'css/lutim.css');
$self->asset('stats.css' => 'css/bootstrap.min.css', 'css/fontello-embedded.css', 'css/morris-0.4.3.min.css', 'css/hennypenny.css', 'css/lutim.css');
$self->asset('about.css' => 'css/bootstrap.min.css', 'css/fontello-embedded.css', 'css/hennypenny.css', 'css/lutim.css');
$self->asset('index.js' => 'js/jquery-2.1.0.min.js', 'js/bootstrap.min.js', 'js/lutim.js', 'js/dmuploader.min.js');
$self->asset('stats.js' => 'js/jquery-2.1.0.min.js', 'js/bootstrap.min.js', 'js/lutim.js', 'js/raphael-min.js', 'js/morris-0.4.3.min.js', 'js/stats.js');
$self->asset('freeze.js' => 'js/jquery-2.1.0.min.js', 'js/freezeframe.min.js');
$self->defaults(layout => 'default');
@@ -287,10 +322,22 @@ sub startup {
to('Controller#stats')->
name('stats');
$r->get('/manifest.webapp')->
to('Controller#webapp')->
name('manifest.webapp');
$r->post('/')->
to('Controller#add')->
name('add');
$r->get('/d/:short/:token')->
to('Controller#delete')->
name('delete');
$r->post('/m/:short/:token')->
to('Controller#modify')->
name('modify');
$r->get('/:short')->
to('Controller#short')->
name('short');

View File

@@ -1,4 +1,4 @@
package Mojolicious::Command::cron;
package Lutim::Command::cron;
use Mojo::Base 'Mojolicious::Commands';
has description => 'Execute tasks.';
@@ -7,7 +7,7 @@ has hint => <<EOF;
See 'script/lutim cron help TASK' for more information on a specific task.
EOF
has message => sub { shift->extract_usage . "\nCron tasks:\n" };
has namespaces => sub { ['Mojolicious::Command::cron'] };
has namespaces => sub { ['Lutim::Command::cron'] };
sub help { shift->run(@_) }
@@ -17,7 +17,7 @@ sub help { shift->run(@_) }
=head1 NAME
Mojolicious::Command::cron - Cron commands
Lutim::Command::cron - Cron commands
=head1 SYNOPSIS

View File

@@ -1,8 +1,7 @@
package Mojolicious::Command::cron::cleanbdd;
package Lutim::Command::cron::cleanbdd;
use Mojo::Base 'Mojolicious::Command';
use LutimModel;
use Mojo::Util qw(slurp decode);
use Mojolicious::Plugin::Config;
has description => 'Delete IP addresses from database after configured delay.';
has usage => sub { shift->extract_usage };
@@ -10,9 +9,11 @@ has usage => sub { shift->extract_usage };
sub run {
my $c = shift;
my $config = Mojolicious::Plugin::Config->parse(decode('UTF-8', slurp 'lutim.conf'), 'lutim.conf');
$config->{keep_ip_during} = (defined($config->{keep_ip_during})) ? $config->{keep_ip_during} : 365;
my $config = $c->app->plugin('ConfigHashMerge', {
default => {
keep_ip_during => 365,
}
});
my $separation = time() - $config->{keep_ip_during} * 86400;
@@ -27,7 +28,7 @@ sub run {
=head1 NAME
Mojolicious::Command::cron::cleanbdd - Delete IP addresses from database after configured delay
Lutim::Command::cron::cleanbdd - Delete IP addresses from database after configured delay
=head1 SYNOPSIS

View File

@@ -0,0 +1,43 @@
package Lutim::Command::cron::cleanfiles;
use Mojo::Base 'Mojolicious::Command';
use LutimModel;
use Mojo::Util qw(slurp decode);
has description => 'Delete expired files.';
has usage => sub { shift->extract_usage };
sub run {
my $c = shift;
my $time = time();
my @images = LutimModel::Lutim->select('WHERE enabled = 1 AND (delete_at_day * 86400) < (? - created_at) AND delete_at_day != 0', $time);
for my $image (@images) {
$c->app->delete_image($image);
}
my $config = $c->app->plugin('Config');
if (defined($config->{delete_no_longer_viewed_files}) && $config->{delete_no_longer_viewed_files} > 0) {
$time = time() - $config->{delete_no_longer_viewed_files} * 86400;
@images = LutimModel::Lutim->select('WHERE enabled = 1 AND last_access_at < ?', $time);
for my $image (@images) {
$c->app->delete_image($image);
}
}
}
=encoding utf8
=head1 NAME
Lutim::Command::cron::cleanfiles - Delete expired files
=head1 SYNOPSIS
Usage: script/lutim cron cleanfiles
=cut
1;

View File

@@ -1,19 +1,21 @@
package Mojolicious::Command::cron::stats;
package Lutim::Command::cron::stats;
use Mojo::Base 'Mojolicious::Command';
use LutimModel;
use Mojo::DOM;
use Mojo::Util qw(slurp spurt decode);
use DateTime;
use Mojolicious::Plugin::Config;
has description => 'Generate statistics about LUTIm.';
has description => 'Generate statistics about Lutim.';
has usage => sub { shift->extract_usage };
sub run {
my $c = shift;
my $config = Mojolicious::Plugin::Config->parse(decode('UTF-8', slurp 'lutim.conf'), 'lutim.conf');
$config->{stats_day_num} = (defined($config->{stats_day_num})) ? $config->{stats_day_num} : 365;
my $config = $c->app->plugin('ConfigHashMerge', {
default => {
stats_day_num => 365
}
});
my $text = slurp('templates/data.html.ep.template');
my $dom = Mojo::DOM->new($text);
@@ -36,9 +38,9 @@ sub run {
}
my $total = LutimModel::Lutim->count('WHERE path IS NOT NULL AND created_at < ?', $separation);
for my $year (sort keys %data) {
for my $month (sort keys %{$data{$year}}) {
for my $day (sort keys %{$data{$year}->{$month}}) {
for my $year (sort {$a <=> $b} keys %data) {
for my $month (sort {$a <=> $b} keys %{$data{$year}}) {
for my $day (sort {$a <=> $b} keys %{$data{$year}->{$month}}) {
$thead_tr->append_content('<th>'."$day/$month/$year".'</th>'."\n");
$tbody_tr->append_content('<td>'.$data{$year}->{$month}->{$day}.'</td>'."\n");
$total += $data{$year}->{$month}->{$day};
@@ -53,7 +55,7 @@ sub run {
=head1 NAME
Mojolicious::Command::cron::stats - Stats generator
Lutim::Command::cron::stats - Stats generator
=head1 SYNOPSIS

View File

@@ -0,0 +1,67 @@
package Lutim::Command::cron::watch;
use Mojo::Base 'Mojolicious::Command';
use Mojo::Util qw(slurp decode);
use Filesys::DiskUsage qw/du/;
use LutimModel;
use Switch;
has description => 'Watch the files directory and take action when over quota';
has usage => sub { shift->extract_usage };
sub run {
my $c = shift;
my $config = $c->app->plugin('ConfigHashMerge', {
default => {
policy_when_full => 'warn'
}
});
if (defined($config->{max_total_size})) {
my $total = du(qw/files/);
if ($total > $config->{max_total_size}) {
say "[Lutim cron job watch] Files directory is over quota ($total > ".$config->{max_total_size}.")";
switch ($config->{policy_when_full}) {
case 'warn' {
say "[Lutim cron job watch] Please, delete some files or increase quota (".$config->{max_total_size}.")";
}
case 'stop-upload' {
open (my $fh, '>', 'stop-upload') or die ("Couldn't open stop-upload: $!");
close($fh);
say '[Lutim cron job watch] Uploads are stopped. Delete some images and the stop-upload file to reallow uploads.';
}
case 'delete' {
say '[Lutim cron job watch] Older files are being deleted';
do {
for my $img (LutimModel::Lutim->select('WHERE path IS NOT NULL AND enabled = 1 ORDER BY created_at ASC LIMIT 50')) {
unlink $img->path() or warn "Could not unlink ".$img->path.": $!";
$img->update(enabled => 0);
}
} while (du(qw/files/) > $config->{max_total_size});
}
else {
say '[Lutim cron job watch] Unrecognized policy_when_full option: '.$config->{policy_when_full}.'. Aborting.';
}
}
} else {
unlink 'stop-upload' if (-f 'stop-upload');
}
} else {
say "[Lutim cron job watch] No max_total_size found in the configuration file. Aborting.";
}
}
=encoding utf8
=head1 NAME
Lutim::Command::cron::watch - Watch the files directory and take action when over quota
=head1 SYNOPSIS
Usage: script/lutim cron watch
=cut
1;

View File

@@ -46,6 +46,124 @@ sub stats {
);
}
sub webapp {
my $c = shift;
my $headers = Mojo::Headers->new();
$headers->add('Content-Type' => 'application/x-web-app-manifest+json');
$c->res->content->headers($headers);
$c->render(
template => 'manifest',
format => 'webapp'
);
}
sub modify {
my $c = shift;
my $short = $c->param('short');
my $token = $c->param('token');
my $url = $c->param('url');
my @images = LutimModel::Lutim->select('WHERE short = ? AND path IS NOT NULL', $short);
if (scalar(@images)) {
my $image = $images[0];
my $msg;
if ($image->mod_token() ne $token || $token eq '') {
$msg = $c->l('invalid_token');
} else {
$c->app->log->info('[MODIFICATION] someone modify '.$image->filename.' with token method (path: '.$image->path.')');
$image->update(
delete_at_day => ($c->param('delete-day') && $c->param('delete-day') <= $c->max_delay) ? $c->param('delete-day') : $c->max_delay,
delete_at_first_view => ($c->param('first-view')) ? 1 : 0,
);
$msg = $c->l('image_delay_modified');
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->true,
msg => $msg
}
);
} else {
$msg .= ' (<a href="'.$url.'">'.$url.'</a>)' unless (!defined($url));
$c->flash(
success => $msg
);
return $c->redirect_to('/');
}
}
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->false,
msg => $msg
}
);
} else {
$c->flash(
msg => $msg
);
return $c->redirect_to('/');
}
} else {
$c->app->log->info('[UNSUCCESSFUL] someone tried to modify '.$short.' but it does\'nt exist.');
# Image never existed
my $msg = $c->l('image_mod_not_found', $short);
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->false,
msg => $msg
}
);
} else {
$c->flash(
msg => $msg
);
return $c->redirect_to('/');
}
}
}
sub delete {
my $c = shift;
my $short = $c->param('short');
my $token = $c->param('token');
my @images = LutimModel::Lutim->select('WHERE short = ? AND path IS NOT NULL', $short);
if (scalar(@images)) {
my $image = $images[0];
my $msg;
if ($image->mod_token() ne $token || $token eq '') {
$msg = $c->l('invalid_token');
} elsif ($image->enabled() == 0) {
$msg = $c->l('already_deleted', $image->filename);
} else {
$c->app->log->info('[DELETION] someone made '.$image->filename.' removed with token method (path: '.$image->path.')');
$c->delete_image($image);
$c->flash(
success => $c->l('image_deleted', $image->filename)
);
return $c->redirect_to('/');
}
$c->flash(
msg => $msg
);
return $c->redirect_to('/');
} else {
$c->app->log->info('[UNSUCCESSFUL] someone tried to delete '.$short.' but it does\'nt exist.');
# Image never existed
$c->render_not_found;
}
}
sub add {
my $c = shift;
my $upload = $c->param('file');
@@ -54,6 +172,11 @@ sub add {
if(!defined($c->stash('stop_upload'))) {
if (defined($file_url) && $file_url) {
if (is_http_uri($file_url) || is_https_uri($file_url)) {
# Anti-flood protection
my $ip = $c->ip(1);
while (defined($c->app->{wait_for_it}->{$ip}) && (time - $c->app->{wait_for_it}->{$ip}) <= $c->config->{anti_flood_delay} ) {
sleep($c->config->{anti_flood_delay});
}
my $ua = Mojo::UserAgent->new;
my $tx = $ua->get($file_url => {DNT => 1});
if (my $res = $tx->success) {
@@ -63,11 +186,30 @@ sub add {
$filename = 'uploaded.image' unless (defined($filename));
$filename .= '.image' if (index($filename, '.') == -1);
$upload = Mojo::Upload->new(
asset => $tx->res->content->asset,
asset => $res->content->asset,
filename => $filename
);
$c->app->{wait_for_it}->{$ip} = time;
} elsif ($tx->res->is_limit_exceeded) {
my $msg = $c->l('file_too_big', $tx->res->max_message_size);
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->false,
msg => {
filename => $file_url,
msg => $msg
}
}
);
} else {
$c->flash(msg => $msg);
$c->flash(filename => $upload->filename);
return $c->redirect_to('/');
}
} else {
my $msg = $c->l('download_error');
$c->app->log->warn('[DOWNLOAD ERROR]'.$c->dumper($tx->error));
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
@@ -109,14 +251,14 @@ sub add {
my $ip = $c->ip;
my ($msg, $short, $thumb);
my ($msg, $short, $real_short, $token, $thumb);
# Check file type
if (index($mediatype, 'image/') >= 0) {
# Create directory if needed
mkdir('files', 0700) unless (-d 'files');
if ($c->req->is_limit_exceeded) {
$msg = l('file_too_big', $c->req->max_message_size);
$msg = $c->l('file_too_big', $c->req->max_message_size);
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
@@ -137,9 +279,13 @@ sub add {
my $filename = unidecode($upload->filename);
my $ext = ($filename =~ m/([^.]+)$/)[0];
my $path = 'files/'.$records[0]->short.'.'.$ext;
my ($width, $height);
if ($im_loaded) {
my $im = Image::Magick->new;
my $im = Image::Magick->new;
$im->BlobToImage($upload->slurp);
$width = $im->Get('width');
$height = $im->Get('height');
$im->Resize(geometry=>'x85');
$thumb = 'data:'.$mediatype.';base64,';
@@ -156,18 +302,27 @@ sub add {
mediatype => $mediatype,
footprint => digest_file_hex($path, 'SHA-512'),
enabled => 1,
delete_at_day => ($c->param('delete-day')) ? $c->param('delete-day') : $c->max_delay,
delete_at_day => ($c->param('delete-day') && $c->param('delete-day') <= $c->max_delay) ? $c->param('delete-day') : $c->max_delay,
delete_at_first_view => ($c->param('first-view')) ? 1 : 0,
created_at => time(),
created_by => $ip
created_by => $ip,
width => $width,
height => $height
);
# Log image creation
$c->app->log->info('[CREATION] '.$c->ip.' pushed '.$filename.' (path: '.$path.')');
$c->app->log->info('[CREATION] '.$ip.' pushed '.$filename.' (path: '.$path.')');
# Give url to user
$short = $records[0]->short;
$short .= '/'.$key if (defined($key));
$short = $records[0]->short;
$real_short = $short;
if (!defined($records[0]->mod_token)) {
$records[0]->update(
mod_token => $c->shortener($c->config->{token_length})
);
}
$token = $records[0]->mod_token;
$short .= '/'.$key if (defined($key));
} else {
# Houston, we have a problem
$msg = $c->l('no_more_short', $c->config->{contact});
@@ -181,9 +336,11 @@ sub add {
if (defined($c->param('format')) && $c->param('format') eq 'json') {
if (defined($short)) {
$msg = {
filename => $upload->filename,
short => $short,
thumb => $thumb
filename => $upload->filename,
short => $short,
real_short => $real_short,
token => $token,
thumb => $thumb
};
} else {
$msg = {
@@ -203,9 +360,11 @@ sub add {
$c->flash(filename => $upload->filename);
return $c->redirect_to('/');
} else {
$c->stash(short => $short) if (defined($short));
$c->stash(thumb => $thumb);
$c->stash(filename => $upload->filename);
$c->stash(short => $short) if (defined($short));
$c->stash(real_short => $real_short);
$c->stash(token => $token);
$c->stash(thumb => $thumb);
$c->stash(filename => $upload->filename);
return $c->render(
template => 'index',
max_file_size => $c->req->max_message_size
@@ -246,8 +405,7 @@ sub short {
$c->app->log->info('[DELETION] someone tried to view '.$images[0]->filename.' but it has been removed by expiration (path: '.$images[0]->path.')');
# Delete image
unlink $images[0]->path();
$images[0]->update(enabled => 0);
$c->delete_image($images[0]);
# Warn user
$c->flash(
@@ -261,19 +419,53 @@ sub short {
$test = 1;
my $short = $images[0]->short;
$short .= '/'.$key if (defined($key));
my ($width, $height) = (340,340);
if ($images[0]->mediatype eq 'image/gif') {
if (defined($images[0]->width) && defined($images[0]->height)) {
($width, $height) = ($images[0]->width, $images[0]->height);
} elsif ($im_loaded) {
my $upload = $c->decrypt($key, $images[0]->path);
my $im = Image::Magick->new;
$im->BlobToImage($upload->slurp);
$width = $im->Get('width');
$height = $im->Get('height');
$images[0]->update(
width => $width,
height => $height
);
}
}
return $c->render(
template => 'twitter',
layout => undef,
short => $short,
filename => $images[0]->filename
filename => $images[0]->filename,
mimetype => ($c->req->url->to_abs()->scheme eq 'https') ? $images[0]->mediatype : '',
width => $width,
height => $height
);
} else {
my $expires = ($images[0]->delete_at_day) ? $images[0]->delete_at_day : 360;
my $dt = DateTime->from_epoch( epoch => $expires * 86400 + $images[0]->created_at);
$dt->set_time_zone('GMT');
$expires = $dt->strftime("%a, %d %b %Y %H:%M:%S GMT");
# Delete image if needed
if ($images[0]->delete_at_first_view && $images[0]->counter >= 1) {
# Log deletion
$c->app->log->info('[DELETION] someone made '.$images[0]->filename.' removed (path: '.$images[0]->path.')');
$test = $c->render_file($images[0]->filename, $images[0]->path, $images[0]->mediatype, $dl, $expires, $images[0]->delete_at_first_view, $key);
# Delete image
$c->delete_image($images[0]);
$c->flash(
msg => $c->l('image_not_found')
);
return $c->redirect_to('/');
} else {
my $expires = ($images[0]->delete_at_day) ? $images[0]->delete_at_day : 360;
my $dt = DateTime->from_epoch( epoch => $expires * 86400 + $images[0]->created_at);
$dt->set_time_zone('GMT');
$expires = $dt->strftime("%a, %d %b %Y %H:%M:%S GMT");
$test = $c->render_file($images[0]->filename, $images[0]->path, $images[0]->mediatype, $dl, $expires, $images[0]->delete_at_first_view, $key);
}
}
if ($test != 500) {
@@ -294,8 +486,7 @@ sub short {
$c->app->log->info('[DELETION] someone made '.$images[0]->filename.' removed (path: '.$images[0]->path.')');
# Delete image
unlink $images[0]->path();
$images[0]->update(enabled => 0);
$c->delete_image($images[0]);
}
});
}
@@ -304,7 +495,7 @@ sub short {
if (scalar(@images)) {
# Log access try
$c->app->log->info('[NOT FOUND] someone tried to view '.$short.' but it does\'nt exist.');
$c->app->log->info('[NOT FOUND] someone tried to view '.$short.' but it does\'nt exist anymore.');
# Warn user
$c->flash(

View File

@@ -2,34 +2,44 @@ package Lutim::I18N::en;
use Mojo::Base 'Lutim::I18N';
my $inf_body = <<EOF;
<h4>What is LUTIm?</h4>
<p>LUTIm is a free (as in free beer) and anonymous image hosting service. It's also the name of the free (as in free speech) software which provides this service.</p>
<p>The images you post on LUTIm can be stored indefinitely or be deleted at first view or after 24 hours.</p>
<h4>What is Lutim?</h4>
<p>Lutim is a free (as in free beer) and anonymous image hosting service. It's also the name of the free (as in free speech) software which provides this service.</p>
<p>The images you post on Lutim can be stored indefinitely or be deleted at first view or after a delay selected from those proposed.</p>
<h4>How does it work?</h4>
<p>Drag and drop an image in the appropriate area or use the traditional way to send files and LUTIm will provide you two URLs. One to view the image, the other to directly download it.</p>
<p>You can, optionally, request that the image(s) posted on LUTIm to be deleted at first view (or download) or after 24 hours.</p>
<p>Drag and drop an image in the appropriate area or use the traditional way to send files and Lutim will provide you three URLs. One to view the image, an other to directly download it, one you can use on social networks and a last to delete the image when you want.</p>
<p>You can, optionally, request that the image(s) posted on Lutim to be deleted at first view (or download) or after the delay selected from those proposed.</p>
<h4>Is it really free (as in free beer)?</h4>
<p>Yes, it is! On the other side, if you want to support the developer, you can do it via <a href="https://flattr.com/submit/auto?user_id=_SKy_&amp;url=[_1]&amp;title=LUTIm&amp;category=software">Flattr</a> or with <a href="bitcoin:1K3n4MXNRSMHk28oTfXEvDunWFthePvd8v?label=lutim">BitCoin</a>.</p>
<p>Yes, it is! On the other side, if you want to support the developer, you can do it via <a href="https://flattr.com/submit/auto?user_id=_SKy_&amp;url=[_1]&amp;title=Lutim&amp;category=software">Flattr</a> or with <a href="bitcoin:1K3n4MXNRSMHk28oTfXEvDunWFthePvd8v?label=lutim">BitCoin</a>.</p>
<h4>Is it really anonymous?</h4>
<p>Yes, it is! On the other side, for legal reasons, your IP address will be stored when you send an image. Don't panic, it is normally the case of all sites on which you send files!</p>
<p>The IP address of the image's sender is permanently retained.</p>
<p>The IP address of the image's sender is retained for a delay which depends of the administrator's choice (for the official instance, which is located in France, it's one year).</p>
<p>If the files are deleted if you ask it while posting it, their SHA512 footprint are retained.</p>
<h4>Who owns rights on images uploaded on Lutim?</h4>
<p>Only the uploader! (well, only if he's the only owner of the images' rights before the upload)</p>
<p>Unlike many image sharing services, you don't give rights on uploaded images.</p>
<h4>How to report an image?</h4>
<p>Please contact the administrator: [_2]</p>
<h4>How do you pronounce LUTIm?</h4>
<h4>How do you pronounce Lutim?</h4>
<p>Juste like you pronounce the French word <a href="https://fr.wikipedia.org/wiki/Lutin">lutin</a> (/ly.tɛ̃/).</p>
<h4>What about the software which provides the service?</h4>
<p>The LUTIm software is a <a href="http://en.wikipedia.org/wiki/Free_software">free software</a>, which allows you to download and install it on you own server. Have a look at the <a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL</a> to see what you can do.</p>
<p>The Lutim software is a <a href="http://en.wikipedia.org/wiki/Free_software">free software</a>, which allows you to download and install it on you own server. Have a look at the <a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL</a> to see what you can do.</p>
<p>For more details, see the <a href="https://github.com/ldidry/lutim">Github</a> page of the project.</p>
<h4>Main developers</h4>
<ul>
<li>Luc Didry, aka Sky (<a href="http://www.fiat-tux.fr">http://www.fiat-tux.fr</a>), core developer, \@framasky on <a href="https://twitter.com/framasky">Twitter</a>, or on <a href="https://framasphere.org/public/framasky">Diaspora*</a></li>
<li>Dattaz (<a href="http://dattaz.fr">http://dattaz.fr</a>), webapp developer, <a href="https://twitter.com/dat_taz">\@dat_taz</a></li>
</ul>
<h4>Contributors</h4>
<ul>
<li>Luc Didry, aka Sky (<a href="http://www.fiat-tux.fr">http://www.fiat-tux.fr</a>), main developer</li>
<li>Jean-Bernard Marcon, aka Goofy (<a href="https://github.com/goofy-bz">https://github.com/goofy-bz</a>)</li>
<li>Jean-Christophe Bach (<a href="https://github.com/jcb">https://github.com/jcb</a>)</li>
<li>Florian Bigard, aka Chocobozzz (<a href="https://github.com/Chocobozzz">https://github.com/Chocobozzz</a>)</li>
<li>Sandro CAZZANIGA, aka Kharec (<a href="http://sandrocazzaniga.fr">http://sandrocazzaniga.fr</a>), <a href="https://twitter.com/Kharec">\@Kharec</a></li>
</ul>
EOF
our %Lexicon = (
'homepage' => 'Homepage',
'license' => 'License:',
'fork-me' => 'Fork me on Github !',
'share-twitter' => 'Share on Twitter',
@@ -37,7 +47,10 @@ our %Lexicon = (
'informations-body' => $inf_body,
'view-link' => 'View link',
'download-link' => 'Download link',
'twitter-link' => 'Link for put in a tweet',
'share-link' => 'Link for share on social networks',
'tweet_it' => 'Tweet it!',
'share_it' => 'Share it!',
'delete-link' => 'Deletion link',
'some-bad' => 'Something bad happened',
'delete-first' => 'Delete at first view?',
'delete-day' => 'Delete after 24 hours?',
@@ -47,7 +60,7 @@ our %Lexicon = (
'drag-n-drop' => 'Drag & drop images here',
'or' => '-or-',
'file-browser' => 'Click to open the file browser',
'image_not_found' => 'Unable to find the image',
'image_not_found' => 'Unable to find the image: it has been deleted.',
'no_more_short' => 'There is no more available URL. Retry or contact the administrator. [_1]',
'no_valid_file' => 'The file [_1] is not an image.',
'file_too_big' => 'The file exceed the size limit ([_1])',
@@ -58,7 +71,7 @@ our %Lexicon = (
'1_year' => 'One year',
'pushed-images' => ' sent images on this instance from beginning.',
'graph-data-once-a-day' => 'The graph\'s datas are not updated in real-time.',
'lutim-stats' => 'LUTIm\'s statistics',
'lutim-stats' => 'Lutim\'s statistics',
'back-to-index' => 'Back to homepage',
'stop_upload' => 'Uploading is currently disabled, please try later or contact the administrator ([_1]).',
'download_error' => 'An error occured while downloading the image.',
@@ -70,8 +83,15 @@ our %Lexicon = (
'delay_days' => '[_1] days',
'delay_365' => '1 year',
'max_delay' => 'Warning! The maximum time limit for an image is [_1] day(s), even if you choose "no time limit".',
'crypt_image' => 'Encrypt the image (LUTIm does not keep the key).',
'always_encrypt' => 'The images are encrypted on the server (LUTIm does not keep the key).',
'crypt_image' => 'Encrypt the image (Lutim does not keep the key).',
'always_encrypt' => 'The images are encrypted on the server (Lutim does not keep the key).',
'image_deleted' => 'The image [_1] has been successfully deleted',
'invalid_token' => 'The delete token is invalid.',
'already_deleted' => 'The image [_1] has already been deleted.',
'install_as_webapp' => 'Install webapp',
'image_delay_modified' => 'The image\'s delay has been successfully modified',
'image_mod_not_found' => 'Unable to find the image [_1].',
'modify_image_error' => 'Error while trying to modify the image.',
);
1;

View File

@@ -2,34 +2,44 @@ package Lutim::I18N::fr;
use Mojo::Base 'Lutim::I18N';
my $inf_body = <<EOF;
<h4>Quest-ce que LUTIm ?</h4>
<p>LUTIm est un service gratuit et anonyme dhébergement dimages. Il sagit aussi du nom du logiciel (libre) qui fournit ce service.</p>
<p>Les images déposées sur LUTIm peuvent être stockées indéfiniment, ou seffacer dès le premier affichage ou au bout de 24h.</p>
<h4>Quest-ce que Lutim ?</h4>
<p>Lutim est un service gratuit et anonyme dhébergement dimages. Il sagit aussi du nom du logiciel (libre) qui fournit ce service.</p>
<p>Les images déposées sur Lutim peuvent être stockées indéfiniment, ou seffacer dès le premier affichage ou au bout du délai choisi parmi ceux proposés.</p>
<h4>Comment ça marche ?</h4>
<p>Faites glisser des images dans la zone prévue à cet effet ou sélectionnez un fichier de façon classique et LUTIm vous fournira deux URLs en retour. Une pour afficher limage, lautre pour la télécharger directement.</p>
<p>Vous pouvez, de façon facultative, demander à ce que la ou les images déposées sur LUTIm soient supprimées après leur premier affichage (ou téléchargement) ou au bout de 24 heures.</p>
<p>Faites glisser des images dans la zone prévue à cet effet ou sélectionnez un fichier de façon classique et Lutim vous fournira troie URLs en retour. Une pour afficher limage, une autre pour la télécharger directement, une pour l'utiliser sur les réseaux sociaux et une dernière pour supprimer votre image quand vous le souhaitez.</p>
<p>Vous pouvez, de façon facultative, demander à ce que la ou les images déposées sur Lutim soient supprimées après leur premier affichage (ou téléchargement) ou au bout d'un délai choisi parmi ceux proposés.</p>
<h4>Cest vraiment gratuit ?</h4>
<p>Oui, ça lest ! Par contre, si vous avez envie de soutenir le développeur, vous pouvez faire un microdon avec <a href="https://flattr.com/submit/auto?user_id=_SKy_&amp;url=[_1]&amp;title=LUTIm&amp;category=software">Flattr</a> ou en <a href="bitcoin:1K3n4MXNRSMHk28oTfXEvDunWFthePvd8v?label=lutim">BitCoin</a>.</p>
<p>Oui, ça lest ! Par contre, si vous avez envie de soutenir le développeur, vous pouvez faire un microdon avec <a href="https://flattr.com/submit/auto?user_id=_SKy_&amp;url=[_1]&amp;title=Lutim&amp;category=software">Flattr</a> ou en <a href="bitcoin:1K3n4MXNRSMHk28oTfXEvDunWFthePvd8v?label=lutim">BitCoin</a>.</p>
<h4>Cest vraiment anonyme ?</h4>
<p>Oui, ça lest ! Par contre, pour des raisons légales, votre adresse IP sera enregistrée lorsque vous enverrez une image. Ne vous affolez pas, cest de toute façon normalement le cas de tous les sites sur lesquels vous envoyez des fichiers !</p>
<p>LIP de la personne ayant déposé limage est stockée de manière définitive.</p>
<p>LIP de la personne ayant déposé limage est stockée pendant un délai dépendant de l'administrateur de l'instance (pour l'instance officielle, dont le serveur est en France, c'est un délai d'un an).</p>
<p>Si les fichiers sont bien supprimés si vous en avez exprimé le choix, leur empreinte SHA512 est toutefois conservée.</p>
<h4>Qui possède des droits sur les images envoyées sur Lutim ?</h4>
<p>Seulement l'envoyeur ! (enfin, seulement s'il possède des droits exclusifs sur les images avant de les envoyer)</p>
<p>Au contraire de la majorité des services de partages d'image, vous ne cédez aucun droit sur les images envoyées.</p>
<h4>Comment peut-on faire pour signaler une image ?</h4>
<p>Veuillez contacter ladministrateur : [_2]</p>
<h4>Comment doit-on prononcer LUTIm ?</h4>
<h4>Comment doit-on prononcer Lutim ?</h4>
<p>Comme on prononce <a href="https://fr.wikipedia.org/wiki/Lutin">lutin</a> !</p>
<h4>Et à propos du logiciel qui fournit le service ?</h4>
<p>Le logiciel LUTIm est un <a href="https://fr.wikipedia.org/wiki/Logiciel_libre">logiciel libre</a>, ce qui vous permet de le télécharger et de linstaller sur votre propre serveur. Jetez un coup dœil à l<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL</a> pour voir quels sont vos droits.</p>
<p>Le logiciel Lutim est un <a href="https://fr.wikipedia.org/wiki/Logiciel_libre">logiciel libre</a>, ce qui vous permet de le télécharger et de linstaller sur votre propre serveur. Jetez un coup dœil à l<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL</a> pour voir quels sont vos droits.</p>
<p>Pour plus de détails, consultez la page <a href="https://github.com/ldidry/lutim">Github</a> du projet.</p>
<h4>Développeurs de l'application</h4>
<ul>
<li>Luc Didry, aka Sky (<a href="http://www.fiat-tux.fr">http://www.fiat-tux.fr</a>), développeur principal, \@framasky sur <a href="https://twitter.com/framasky">Twitter</a> ou sur <a href="https://framasphere.org/public/framasky">Diaspora*</a></li>
<li>Dattaz (<a href="http://dattaz.fr">http://dattaz.fr</a>), développeur de la webapp, <a href="https://twitter.com/dat_taz">\@dat_taz</a></li>
</ul>
<h4>Contributeurs</h4>
<ul>
<li>Luc Didry, aka Sky (<a href="http://www.fiat-tux.fr">http://www.fiat-tux.fr</a>), développeur principal</li>
<li>Jean-Bernard Marcon, aka Goofy (<a href="https://github.com/goofy-bz">https://github.com/goofy-bz</a>)</li>
<li>Jean-Christophe Bach (<a href="https://github.com/jcb">https://github.com/jcb</a>)</li>
<li>Florian Bigard, aka Chocobozzz (<a href="https://github.com/Chocobozzz">https://github.com/Chocobozzz</a>)</li>
<li>Sandro CAZZANIGA, aka Kharec (<a href="http://sandrocazzaniga.fr">http://sandrocazzaniga.fr</a>), <a href="https://twitter.com/Kharec">\@Kharec</a></li>
</ul>
EOF
our %Lexicon = (
'homepage' => 'Accueil',
'license' => 'Licence :',
'fork-me' => 'Fork me on Github',
'share-twitter' => 'Partager sur Twitter',
@@ -37,7 +47,10 @@ our %Lexicon = (
'informations-body' => $inf_body,
'view-link' => 'Lien d\'affichage',
'download-link' => 'Lien de téléchargement',
'twitter-link' => 'Lien pour mettre dans un tweet',
'share-link' => 'Lien pour partager sur les réseaux sociaux',
'tweet_it' => 'Tweetez !',
'share_it' => 'Partagez !',
'delete-link' => 'Lien de suppression',
'some-bad' => 'Un problème est survenu',
'delete-first' => 'Supprimer au premier accès ?',
'delete-day' => 'Supprimer après 24 heures ?',
@@ -47,7 +60,7 @@ our %Lexicon = (
'drag-n-drop' => 'Déposez vos images ici',
'or' => '-ou-',
'file-browser' => 'Cliquez pour utiliser le navigateur de fichier',
'image_not_found' => 'Impossible de trouver l\'image',
'image_not_found' => 'Impossible de trouver l\'image : elle a été supprimée.',
'no_more_short' => 'Il n\'y a plus d\'URL disponible. Veuillez réessayer ou contactez l\'administrateur. [_1].',
'no_valid_file' => 'Le fichier [_1] n\'est pas une image.',
'file_too_big' => 'Le fichier dépasse la limite de taille ([_1])',
@@ -58,9 +71,9 @@ our %Lexicon = (
'1_year' => 'Un an',
'pushed-images' => ' images envoyées sur cette instance depuis le début.',
'graph-data-once-a-day' => 'Les données du graphique ne sont pas mises à jour en temps réél.',
'lutim-stats' => 'Statistiques de LUTIm',
'lutim-stats' => 'Statistiques de Lutim',
'back-to-index' => 'Retour à la page d\'accueil',
'stop_upload' => 'L\'envoi d\'images est actuellement désactivé, veuillez réessayer plus ou contacter l\'administrateur ([_1]).',
'stop_upload' => 'L\'envoi d\'images est actuellement désactivé, veuillez réessayer plus tard ou contacter l\'administrateur ([_1]).',
'download_error' => 'Une erreur est survenue lors du téléchargement de l\'image.',
'no_valid_url' => 'l\'URL n\'est pas valide.',
'image_url' => 'URL de l\'image',
@@ -70,8 +83,15 @@ our %Lexicon = (
'delay_days' => '[_1] jours',
'delay_365' => '1 an',
'max_delay' => 'Attention ! Le délai maximal de rétention d\'une image est de [_1] jour(s), même si vous choisissez « pas de limitation de durée ».',
'crypt_image' => 'Chiffrer l\'image (LUTIm ne stocke pas la clé).',
'always_encrypt' => 'Les images sont chiffrées sur le serveur (LUTIm ne stocke pas la clé).',
'crypt_image' => 'Chiffrer l\'image (Lutim ne stocke pas la clé).',
'always_encrypt' => 'Les images sont chiffrées sur le serveur (Lutim ne stocke pas la clé).',
'image_deleted' => 'L\'image [_1] a été supprimée avec succès.',
'invalid_token' => 'Le jeton de suppression est invalide.',
'already_deleted' => 'L\'image [_1] a déjà été supprimée.',
'install_as_webapp' => 'Installer la webapp',
'image_delay_modified' => 'Le délai de l\'image a été modifié avec succès.',
'image_mod_not_found' => 'Impossible de trouver l\'image [_1].',
'modify_image_error' => 'Une erreur est survenue lors de la tentative de modification de l\'image.',
);
1;

View File

@@ -20,7 +20,9 @@ use ORLite {
created_at INTEGER,
created_by TEXT,
last_access_at INTEGER,
last_access_by INTEGER)'
mod_token TEXT,
width INTEGER,
height INTEGER)'
);
return 1;
}

View File

@@ -1,36 +0,0 @@
package Mojolicious::Command::cron::cleanfiles;
use Mojo::Base 'Mojolicious::Command';
use LutimModel;
use Mojo::Util qw(slurp decode);
use Mojolicious::Plugin::Config;
has description => 'Delete expired files.';
has usage => sub { shift->extract_usage };
sub run {
my $c = shift;
my $config = Mojolicious::Plugin::Config->parse(decode('UTF-8', slurp 'lutim.conf'), 'lutim.conf');
my $time = time();
my @images = LutimModel::Lutim->select('WHERE enabled = 1 AND (delete_at_day * 86400) < (? - created_at) AND delete_at_day != 0', $time);
for my $image (@images) {
$image->update(enabled => 0);
unlink $image->path();
}
}
=encoding utf8
=head1 NAME
Mojolicious::Command::cron::cleanfiles - Delete expired files
=head1 SYNOPSIS
Usage: script/lutim cron cleanfiles
=cut
1;

View File

@@ -1,68 +0,0 @@
package Mojolicious::Command::cron::watch;
use Mojo::Base 'Mojolicious::Command';
use Mojo::Util qw(slurp decode);
use Mojolicious::Plugin::Config;
use Filesys::DiskUsage qw/du/;
use LutimModel;
use Switch;
has description => 'Watch the files directory and take action when over quota';
has usage => sub { shift->extract_usage };
sub run {
my $c = shift;
my $config = Mojolicious::Plugin::Config->parse(decode('UTF-8', slurp 'lutim.conf'), 'lutim.conf');
if (defined($config->{max_total_size})) {
my $total = du(qw/files/);
if ($total > $config->{max_total_size}) {
if (defined($config->{policy_when_full})) {
say "[LUTIm cron job watch] Files directory is over quota ($total > ".$config->{max_total_size}.")";
switch ($config->{policy_when_full}) {
case 'warn' {
say "[LUTIm cron job watch] Please, delete some files or increase quota (".$config->{max_total_size}.")";
}
case 'stop-upload' {
open (my $fh, '>', 'stop-upload') or die ("Couldn't open stop-upload: $!");
close($fh);
say '[LUTIm cron job watch] Uploads are stopped. Delete some images and the stop-upload file to reallow uploads.';
}
case 'delete' {
say '[LUTIm cron job watch] Older files are being deleted';
do {
for my $img (LutimModel::Lutim->select('WHERE path IS NOT NULL AND enabled = 1 ORDER BY created_at ASC LIMIT 50')) {
unlink $img->path() or warn "Could not unlink ".$img->path.": $!";
$img->update(enabled => 0);
}
} while (du(qw/files/) > $config->{max_total_size});
}
else {
say '[LUTIm cron job watch] Unrecognized policy_when_full option: '.$config->{policy_when_full}.'. Aborting.';
}
}
} else {
say "[LUTIm cron job watch] Files directory over quota ($total > ".$config->{max_total_size}.") but no configured policy_when_full option!" ;
}
} else {
unlink 'stop-upload' if (-f 'stop-upload');
}
} else {
say "[LUTIm cron job watch] No max_total_size found in the configuration file. Aborting.";
}
}
=encoding utf8
=head1 NAME
Mojolicious::Command::cron::watch - Delete IP addresses from database after configured delay
=head1 SYNOPSIS
Usage: script/lutim cron watch
=cut
1;

View File

@@ -1,28 +1,131 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
# See README.md for more explanations
{
####################
# Hypnotoad settings
####################
# see http://mojolicio.us/perldoc/Mojo/Server/Hypnotoad for a full list of settings
hypnotoad => {
# array of IP addresses and ports you want to listen to
listen => ['http://127.0.0.1:8080'],
# user and group you want for Lutim to run with
# be sure that this user/group have rights on the lutim directory
# if you launch lutim from a different user, be sure that this user have the right to su this user/group
# => if current_user is not the user that you sets here and is not root, there's chances that it will fail (see https://github.com/ldidry/lutim/issues/25)
user => 'www-data',
group => 'www-data'
},
################
# Lutim settings
################
# put a way to contact you here and uncomment it
# mandatory
#contact => 'John Doe, admin[at]example.com',
secrets => ['fdjsofjoihrei'], # please provide a random string
length => 8, # optional
provis_step => 5, # optional
provisioning => 100, # optional
tweet_card_via => '@framasky', # optional
max_file_size => 10485760, # optional, size in octets, you can write it 10*1024*1024
#piwik_img => 'https://piwik.example.org/piwik.php?idsite=1&amp;rec=1', # optional, only the piwik image tracker is allowed, no javascript
#hosted_by => 'My super hoster <img src="http://hoster.example.com" alt="Hoster logo">', # optional
#https => 0, # optional, set to 1 if you use Lutim behind a secure web server
#stats_day_num => 365, # optional, number of days shown in /stats page (used with script/lutim cron stats)
#keep_ip_during => 365, # optional, number of days, after that delay, the IP addresses of image creators will be deleted (used with script/lutim cron cleanbdd)
#max_total_size => 10*1024*1024*1024, # optional, maximum total size of the files directory (used with script/lutim cron watch)
#policy_when_full => 'warn', # optional, policy when files directory is over max_total_size. Valid values are 'warn', 'stop-upload' and 'delete'. See README.
#broadcast_message => 'Maintenance', #optional, the broadcast_message is displayed on all pages of LUTIm (but no in json response)
#allowed_domains => ['http://1.example.com', 'http://2.example.com'], #optional, array of authorized domains for API calls. If you want to authorize everyone to use the API: ['*']
#default_delay => 0, #optional: what is the default time limit for files? Valid values are 0, 1, 7, 30 and 365.
#max_delay => 0, #optional, if defined, the images will be deleted after that delay (in days), even if they were uploaded with "no delay" (or value superior to max\_delay) option and a warning message will be displayed on homepage.
#always_encrypt => 0, #optional, if set to 1, all the images will be encrypted
# random string used to encrypt cookies
# mandatory
secrets => ['fdjsofjoihrei'],
# length of the images random URL
# optional, default is 8
#length => 8,
# how many URLs will be provisioned in a batch ?
# optional, default is 5
#provis_step => 5,
# max number of URLs to be provisioned
# WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
# in the first version, this option was provisionning with two 'n'. While the option with the typo is still valid, it is deprecated.
# in the next version (0.4), only provisioning with ine 'n' will be accepted
# optional, default is 100
#provisioning => 100,
# anti-flood protection delay, in seconds
# users won't be able to ask Lutim to download images more than one per anti_flood_delay seconds
# optional, default is 5
#anti_flood_delay => 5,
# twitter account which will appear on twitter cards
# see https://dev.twitter.com/docs/cards/validation/validator to register your Lutim instance on twitter
# optional, default is @framasky
#tweet_card_via => '@framasky',
# max image size, in octets
# you can write it 10*1024*1024
# optional, default is 10485760
#max_file_size => 10485760,
# if you want to have piwik statistics, provide a piwik image tracker
# only the image tracker is allowed, no javascript
# optional, no default
#piwik_img => 'https://piwik.example.org/piwik.php?idsite=1&amp;rec=1',
# if you want to include something in the right of the screen, put it here
# here's an exemple to put the logo of your hoster
# optional, no default
#hosted_by => 'My super hoster <img src="http://hoster.example.com" alt="Hoster logo">',
# DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED
# Lutim now checks if the X-Forwarded-Proto header is present and equal to https.
# set to 1 if you use Lutim behind a secure web server
# optional, default is 0
#https => 0,
# broadcast_message which will displayed on all pages of Lutim (but no in json response)
# optional, no default
#broadcast_message => 'Maintenance',
# array of authorized domains for API calls.
# if you want to authorize everyone to use the API: ['*']
# optional, no domains allowed by default
#allowed_domains => ['http://1.example.com', 'http://2.example.com'],
# default time limit for files
# valid values are 0, 1, 7, 30 and 365
# optional, default is 0 (no limit)
#default_delay => 0,
# number of days after which the images will be deleted, even if they were uploaded with "no delay" (or value superior to max_delay)
# a warning message will be displayed on homepage
# optional, default is 0 (no limit)
#max_delay => 0,
# if set to 1, all the images will be encrypted and the encryption option will no be displayed
# optional, default is 0
#always_encrypt => 0,
# length of the image's delete token
# optional, default is 24
#token_length => 24,
##########################
# Lutim cron jobs settings
##########################
# number of days shown in /stats page (used with script/lutim cron stats)
# optional, default is 365
#stats_day_num => 365,
# number of days senders' IP addresses are kept in database
# after that delay, they will be deleted from database (used with script/lutim cron cleanbdd)
# optional, default is 365
#keep_ip_during => 365,
# max size of the files directory, in octets
# used by script/lutim cron watch to trigger an action
# optional, no default
#max_total_size => 10*1024*1024*1024,
# default action when files directory is over max_total_size (used with script/lutim cron watch)
# valid values are 'warn', 'stop-upload' and 'delete'
# please, see readme
# optional, default is 'warn'
#policy_when_full => 'warn',
# images which are not viewed since delete_no_longer_viewed_files days will be deleted by the cron cleanfiles task
# if delete_no_longer_viewed_files is not set, the no longer viewed files will NOT be deleted
# optional, no default
#delete_no_longer_viewed_files => 90
};

View File

@@ -2645,119 +2645,6 @@ button.close {
border: 0;
-webkit-appearance: none;
}
.modal-open {
overflow: hidden;
}
.modal {
display: none;
overflow: auto;
overflow-y: scroll;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1050;
-webkit-overflow-scrolling: touch;
outline: 0;
}
.modal.fade .modal-dialog {
-webkit-transform: translate(0, -25%);
-ms-transform: translate(0, -25%);
transform: translate(0, -25%);
-webkit-transition: -webkit-transform 0.3s ease-out;
-moz-transition: -moz-transform 0.3s ease-out;
-o-transition: -o-transform 0.3s ease-out;
transition: transform 0.3s ease-out;
}
.modal.in .modal-dialog {
-webkit-transform: translate(0, 0);
-ms-transform: translate(0, 0);
transform: translate(0, 0);
}
.modal-dialog {
position: relative;
width: auto;
margin: 10px;
}
.modal-content {
position: relative;
background-color: #ffffff;
border: 1px solid #999999;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 6px;
-webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
background-clip: padding-box;
outline: none;
}
.modal-backdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1040;
background-color: #000000;
}
.modal-backdrop.fade {
opacity: 0;
filter: alpha(opacity=0);
}
.modal-backdrop.in {
opacity: 0.5;
filter: alpha(opacity=50);
}
.modal-header {
padding: 15px;
border-bottom: 1px solid #e5e5e5;
min-height: 16.42857143px;
}
.modal-header .close {
margin-top: -2px;
}
.modal-title {
margin: 0;
line-height: 1.42857143;
}
.modal-body {
position: relative;
padding: 20px;
}
.modal-footer {
margin-top: 15px;
padding: 19px 20px 20px;
text-align: right;
border-top: 1px solid #e5e5e5;
}
.modal-footer .btn + .btn {
margin-left: 5px;
margin-bottom: 0;
}
.modal-footer .btn-group .btn + .btn {
margin-left: -1px;
}
.modal-footer .btn-block + .btn-block {
margin-left: 0;
}
@media (min-width: 768px) {
.modal-dialog {
width: 600px;
margin: 30px auto;
}
.modal-content {
-webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
}
.modal-sm {
width: 300px;
}
}
@media (min-width: 992px) {
.modal-lg {
width: 900px;
}
}
.clearfix:before,
.clearfix:after,
.container:before,
@@ -2767,9 +2654,7 @@ button.close {
.row:before,
.row:after,
.form-horizontal .form-group:before,
.form-horizontal .form-group:after,
.modal-footer:before,
.modal-footer:after {
.form-horizontal .form-group:after {
content: " ";
display: table;
}
@@ -2777,8 +2662,7 @@ button.close {
.container:after,
.container-fluid:after,
.row:after,
.form-horizontal .form-group:after,
.modal-footer:after {
.form-horizontal .form-group:after {
clear: both;
}
.center-block {

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +1,10 @@
.icon-eye:before { content: '\e806'; } /* '' */
.icon-download:before { content: '\e804'; } /* '' */
.icon-bitcoin:before { content: '\e802'; } /* '' */
.icon-spinner:before { content: '\e805'; } /* '' */
.icon-github-circled:before { content: '\e800'; } /* '' */
.icon-touiteur:before { content: '\e801'; } /* '' */
.icon-flattr:before { content: '\e803'; } /* '' */
.icon-twitter:before { content: '\e800'; } /* '' */
.icon-github-circled:before { content: '\e801'; } /* '' */
.icon-flattr:before { content: '\e802'; } /* '' */
.icon-bitcoin:before { content: '\e803'; } /* '' */
.icon-trash:before { content: '\e804'; } /* '' */
.icon-download:before { content: '\e805'; } /* '' */
.icon-spinner:before { content: '\e806'; } /* '' */
.icon-eye:before { content: '\e807'; } /* '' */
.icon-share:before { content: '\e808'; } /* '' */

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +1,10 @@
.icon-eye { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
.icon-download { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
.icon-bitcoin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
.icon-spinner { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
.icon-github-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
.icon-touiteur { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
.icon-flattr { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
.icon-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
.icon-github-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
.icon-flattr { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
.icon-bitcoin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
.icon-trash { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
.icon-download { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
.icon-spinner { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
.icon-eye { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }
.icon-share { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); }

View File

@@ -10,10 +10,12 @@
/* font-size: 120%; */
}
.icon-eye { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
.icon-download { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
.icon-bitcoin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
.icon-spinner { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
.icon-github-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
.icon-touiteur { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
.icon-flattr { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
.icon-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
.icon-github-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
.icon-flattr { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
.icon-bitcoin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
.icon-trash { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
.icon-download { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
.icon-spinner { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
.icon-eye { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }
.icon-share { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); }

View File

@@ -1,10 +1,10 @@
@font-face {
font-family: 'fontello';
src: url('../font/fontello.eot?69050751');
src: url('../font/fontello.eot?69050751#iefix') format('embedded-opentype'),
url('../font/fontello.woff?69050751') format('woff'),
url('../font/fontello.ttf?69050751') format('truetype'),
url('../font/fontello.svg?69050751#fontello') format('svg');
src: url('../font/fontello.eot?96982888');
src: url('../font/fontello.eot?96982888#iefix') format('embedded-opentype'),
url('../font/fontello.woff?96982888') format('woff'),
url('../font/fontello.ttf?96982888') format('truetype'),
url('../font/fontello.svg?96982888#fontello') format('svg');
font-weight: normal;
font-style: normal;
}
@@ -14,7 +14,7 @@
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'fontello';
src: url('../font/fontello.svg?69050751#fontello') format('svg');
src: url('../font/fontello.svg?96982888#fontello') format('svg');
}
}
*/
@@ -50,10 +50,12 @@
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
.icon-eye:before { content: '\e806'; } /* '' */
.icon-download:before { content: '\e804'; } /* '' */
.icon-bitcoin:before { content: '\e802'; } /* '' */
.icon-spinner:before { content: '\e805'; } /* '' */
.icon-github-circled:before { content: '\e800'; } /* '' */
.icon-touiteur:before { content: '\e801'; } /* '' */
.icon-flattr:before { content: '\e803'; } /* '' */
.icon-twitter:before { content: '\e800'; } /* '' */
.icon-github-circled:before { content: '\e801'; } /* '' */
.icon-flattr:before { content: '\e802'; } /* '' */
.icon-bitcoin:before { content: '\e803'; } /* '' */
.icon-trash:before { content: '\e804'; } /* '' */
.icon-download:before { content: '\e805'; } /* '' */
.icon-spinner:before { content: '\e806'; } /* '' */
.icon-eye:before { content: '\e807'; } /* '' */
.icon-share:before { content: '\e808'; } /* '' */

View File

@@ -0,0 +1,6 @@
@font-face {
font-family: 'Henny_Penny';
font-style: normal;
font-weight: 400;
src: local('Henny Penny'), local('HennyPenny-Regular'), url(../font/hennypenny.ttf) format('truetype');
}

50
public/css/lutim.css Normal file
View File

@@ -0,0 +1,50 @@
@media (max-width: 767px) {
body {
padding-top: 5px;
padding-bottom: 5px;
}
}
@media (min-width: 768px) {
body {
padding-top: 40px;
padding-bottom: 40px;
}
}
.container {
padding: 15px;
margin: 0 auto;
}
.jsonly {
display: none;
}
.thumbnail {
margin-right: 8px;
}
.hennypenny {
font-family: 'Henny_Penny', cursive;
font-size: 42px;
}
.logo {
margin-right: 10px;
}
label.always-encrypt {
display: none;
}
.link_nocol,
.link_nocol:hover{
color: #000000;
text-decoration: none;
}
#install-app img {
height: 22px;
}
#install-app {
display: none;
}

2
public/css/morris-0.4.3.min.css vendored Normal file
View File

@@ -0,0 +1,2 @@
.morris-hover{position:absolute;z-index:1000;}.morris-hover.morris-default-style{border-radius:10px;padding:6px;color:#666;background:rgba(255, 255, 255, 0.8);border:solid 2px rgba(230, 230, 230, 0.8);font-family:sans-serif;font-size:12px;text-align:center;}.morris-hover.morris-default-style .morris-hover-row-label{font-weight:bold;margin:0.25em 0;}
.morris-hover.morris-default-style .morris-hover-point{white-space:nowrap;margin:0.1em 0;}

View File

@@ -26,6 +26,18 @@
padding: 10px;
}
@media (max-width:768px){
.uploader div.browser label {
max-width:95%;
}
}
@media (min-width:768px){
.uploader div.browser label {
width:300px;
}
}
.uploader div.browser label {
background-color: #5a7bc2;
padding: 5px 15px;
@@ -38,7 +50,6 @@
position: relative;
overflow: hidden;
display: block;
width: 300px;
margin: 20px auto 0px auto;
box-shadow: 2px 2px 2px #888888;

30
public/font/LICENSE.txt Normal file
View File

@@ -0,0 +1,30 @@
Font license info
## Font Awesome
Copyright (C) 2012 by Dave Gandy
Author: Dave Gandy
License: SIL ()
Homepage: http://fortawesome.github.com/Font-Awesome/
## Zocial
Copyright (C) 2012 by Sam Collins
Author: Sam Collins
License: MIT (http://opensource.org/licenses/mit-license.php)
Homepage: http://zocial.smcllns.com/
## MFG Labs
Copyright (C) 2012 by Daniel Bruce
Author: MFG Labs
License: SIL (http://scripts.sil.org/OFL)
Homepage: http://www.mfglabs.com/

Binary file not shown.

View File

@@ -6,13 +6,15 @@
<font id="fontello" horiz-adv-x="1000" >
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="eye" unicode="&#xe806;" d="m929 314q-85 132-213 197q34-58 34-125q0-104-73-177t-177-73t-177 73t-73 177q0 67 34 125q-128-65-213-197q75-114 187-182t242-68t242 68t187 182z m-402 215q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-12 8-19t19-8t19 8t8 19q0 48 34 82t82 34q11 0 19 8t8 19z m473-215q0-19-11-38q-78-129-210-206t-279-77t-279 77t-210 206q-11 19-11 38t11 39q78 128 210 205t279 78t279-78t210-205q11-20 11-39z" horiz-adv-x="1000" />
<glyph glyph-name="download" unicode="&#xe804;" d="m714 100q0 15-10 25t-25 11t-26-11t-10-25t10-25t26-11t25 11t10 25z m143 0q0 15-10 25t-26 11t-25-11t-10-25t10-25t25-11t26 11t10 25z m72 125v-179q0-22-16-37t-38-16h-821q-23 0-38 16t-16 37v179q0 22 16 38t38 16h259l75-76q33-32 76-32t76 32l76 76h259q22 0 38-16t16-38z m-182 318q10-23-8-40l-250-250q-10-10-25-10t-25 10l-250 250q-17 17-8 40q10 21 33 21h143v250q0 15 11 25t25 11h143q14 0 25-11t10-25v-250h143q24 0 33-21z" horiz-adv-x="928.6" />
<glyph glyph-name="bitcoin" unicode="&#xe802;" d="m651 493q10-102-73-144q65-16 98-58t25-119q-4-40-18-70t-36-49t-54-33t-68-19t-81-9v-142h-86v140q-45 0-68 1v-141h-86v142q-10 0-30 1t-31 0h-112l18 102h62q27 0 32 28v225h9q-4 0-9 0v160q-7 38-50 38h-62v92l119-1q35 0 54 1v141h86v-138q45 1 68 1v137h86v-141q44-4 78-13t63-25t46-43t20-64z m-120-304q0 20-8 35t-21 26t-32 17t-36 10t-42 5t-38 2t-36 0t-27-1v-189q5 0 21 0t27 0t29 1t33 2t32 5t31 8t26 11t22 17t14 22t5 29z m-39 265q0 19-7 33t-17 23t-27 16t-31 9t-34 5t-33 1t-30 0t-22-1v-171q3 0 20 0t26 0t27 1t31 3t29 6t27 10t21 15t15 22t5 28z" horiz-adv-x="714.3" />
<glyph glyph-name="spinner" unicode="&#xe805;" d="m277 100q0-33-24-57t-57-23q-33 0-56 23t-24 57t24 57t56 23q33 0 57-23t24-57z m241-107q0-30-21-51t-51-21t-50 21t-21 51t21 50t50 21t51-21t21-50z m-339 357q0-37-27-63t-63-26t-63 26t-26 63t26 63t63 26t63-26t27-63z m580-250q0-26-18-44t-45-18t-44 18t-18 44t18 44t44 19t45-19t18-44z m-464 500q0-41-29-69t-70-29t-69 29t-29 69t29 69t69 29t70-29t29-69z m259 107q0-45-32-76t-76-31t-75 31t-32 76t32 76t75 31t76-31t32-76z m303-357q0-22-15-38t-38-16t-38 16t-16 38t16 38t38 16t38-16t15-38z m-116 250q0-18-13-32t-32-13t-31 13t-13 32t13 31t31 14t32-14t13-31z" horiz-adv-x="875" />
<glyph glyph-name="github-circled" unicode="&#xe800;" d="m857 350q0-140-82-252t-211-155q-15-3-22 4t-7 17v118q0 54-29 79q32 3 57 10t53 22t45 37t30 58t11 84q0 68-44 115q21 51-5 114q-15 5-45-6t-51-25l-21-13q-52 15-107 15t-108-15q-8 6-23 15t-47 22t-48 7q-24-63-4-114q-44-47-44-115q0-47 12-83t29-59t45-37t52-22t57-10q-22-20-27-58q-12-5-25-8t-32-3t-36 12t-31 35q-11 18-27 29t-28 14l-11 1q-12 0-16-2t-3-7t5-8t7-6l4-3q12-6 24-21t18-29l5-13q8-21 25-34t37-17t39-4t31 2l13 3q0-22 0-50t1-30q0-10-8-17t-22-4q-129 43-211 155t-82 252q0 117 58 215t155 156t216 58t215-58t156-156t57-215z" horiz-adv-x="857.1" />
<glyph glyph-name="touiteur" unicode="&#xe801;" d="m904 622q-37-54-90-93q0-8 0-23q0-73-21-145t-64-139t-103-117t-144-82t-181-30q-151 0-276 81q19-3 43-3q126 0 224 77q-59 2-105 36t-64 89q19-2 34-2q24 0 48 6q-63 13-104 62t-41 115v2q38-21 82-23q-37 25-59 64t-22 86q0 49 25 91q68-83 164-133t208-55q-5 21-5 41q0 75 53 127t127 53q79 0 132-57q61 12 114 44q-20-64-79-100q52 6 104 28z" horiz-adv-x="928.6" />
<glyph glyph-name="flattr" unicode="&#xe803;" d="m180 424l0-210l-180-180l0 414q0 302 278 302l442 0l-312-310q-6-6-10-6q-8 0-12 10l0 128q-98 0-112-2q-94-16-94-146z m440 64l180 180l0-414q0-304-278-304l-440 0l312 312q2 6 8 6q8 0 12-10l0-128q98 0 112 2q94 16 94 146l0 210z" horiz-adv-x="800" />
<glyph glyph-name="twitter" unicode="&#xe800;" d="m25 74q19-2 43-2 126 0 224 77-59 1-105 36t-64 89q19-3 34-3 24 0 48 6-63 13-104 62t-41 115v2q38-21 82-23-37 25-59 64t-22 86q0 49 25 91 67-83 164-133t208-55q-5 21-5 41 0 75 53 127t127 53q78 0 132-57 61 12 114 44-20-64-79-100 52 6 104 28-37-54-90-93 0-8 0-23 0-73-21-145t-64-139-103-117-144-82-181-30q-151 0-276 81z" horiz-adv-x="928.6" />
<glyph glyph-name="github-circled" unicode="&#xe801;" d="m0 350q0 117 58 215t155 156 216 58 215-58 156-156 57-215q0-140-82-252t-211-155q-15-3-22 4t-7 17v118q0 54-29 79 32 3 57 10t53 22 45 37 30 58 11 84q0 68-44 115 21 51-5 114-15 5-45-6t-51-25l-21-13q-52 15-107 15t-108-15q-8 6-23 15t-47 22-48 7q-24-63-4-114-44-47-44-115 0-47 12-83t29-59 45-37 52-22 57-10q-22-20-27-58-12-5-25-8t-32-3-36 12-31 35q-11 18-27 29t-28 14l-11 1q-12 0-16-2t-3-7 5-8 7-6l4-3q12-6 24-21t18-29l5-13q8-21 25-34t37-17 39-4 31 2l13 3q0-22 0-50t1-30q0-10-8-17t-22-4q-129 43-211 155t-82 252z" horiz-adv-x="857.1" />
<glyph glyph-name="flattr" unicode="&#xe802;" d="m0-37l0 514q0 179 85 278t259 99l548 0q-5-5-52-53t-100-101-109-109-95-93-42-37q-15 0-15 16l0 156-48 0q-59 0-94-6t-63-26-39-57-12-96l0-262z m67-117q5 5 53 53t100 101 109 110 95 93 41 36q15 0 15-16l0-156 48 0q116 0 162 36t45 149l0 262 224 223 0-514q0-179-84-278t-260-99l-548 0z" horiz-adv-x="959" />
<glyph glyph-name="bitcoin" unicode="&#xe803;" d="m31-7l18 102h62q27 0 32 28v225h9q-4 0-9 0v160q-7 38-50 38h-62v92l119-1q35 0 54 1v141h86v-138q45 1 68 1v137h86v-141q44-4 78-13t63-25 46-43 20-64q10-102-73-144 65-16 98-58t25-119q-4-40-18-70t-36-49-54-33-68-19-81-9v-142h-86v140q-45 0-68 1v-141h-86v142q-10 0-30 1t-31 0h-112z m260 101q5 0 21 0t27 0 29 1 33 2 32 5 31 8 26 11 22 17 14 22 5 29q0 20-8 35t-21 26-32 17-36 10-42 6-38 1-36 0-27-1v-189z m0 275q3 0 20 0t26 0 27 1 31 3 29 6 27 10 21 15 15 22 5 28q0 19-7 33t-17 23-27 16-31 9-34 5-33 1-30 0-22-1v-171z" horiz-adv-x="714.3" />
<glyph glyph-name="trash" unicode="&#xe804;" d="m0 582v36q0 8 5 13t13 5h172l39 93q9 21 31 35t44 15h178q22 0 44-15t30-35l39-93h173q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-54v-529q0-46-26-80t-63-34h-464q-37 0-63 33t-27 79v531h-53q-8 0-13 5t-5 13z m143-547q0-12 4-22t8-15 6-5h464q2 0 6 5t8 15 4 22v529h-500v-529z m71 83v321q0 8 5 13t13 5h36q8 0 13-5t5-13v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13z m54 518h250l-27 65q-4 5-9 6h-177q-6-1-10-6z m89-518v321q0 8 5 13t13 5h36q8 0 13-5t5-13v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13z m143 0v321q0 8 5 13t13 5h36q7 0 12-5t5-13v-321q0-8-5-13t-12-5h-36q-8 0-13 5t-5 13z" horiz-adv-x="785.7" />
<glyph glyph-name="download" unicode="&#xe805;" d="m0 46v179q0 22 16 38t38 16h259l75-76q33-32 76-32t76 32l76 76h259q22 0 38-16t16-38v-179q0-22-16-37t-38-16h-821q-23 0-38 16t-16 37z m181 497q10 21 33 21h143v250q0 15 11 25t25 11h143q14 0 25-11t10-25v-250h143q24 0 33-21 10-23-8-40l-250-250q-10-10-25-10t-25 10l-250 250q-17 17-8 40z m462-443q0-14 10-25t26-11 25 11 10 25-10 25-25 11-26-11-10-25z m143 0q0-14 10-25t25-11 26 11 10 25-10 25-26 11-25-11-10-25z" horiz-adv-x="928.6" />
<glyph glyph-name="spinner" unicode="&#xe806;" d="m469 614v204q129 0 237-61t169-170 62-237h-204q0 72-36 133t-95 96-133 35z" horiz-adv-x="937.5" />
<glyph glyph-name="eye" unicode="&#xe807;" d="m0 314q0 19 11 39 78 128 210 205t279 78 279-78 210-205q11-20 11-39t-11-38q-78-129-210-206t-279-77-279 77-210 206q-11 19-11 38z m71 0q75-114 187-182t242-68 242 68 187 182q-85 132-213 197 34-58 34-125 0-104-73-177t-177-73-177 73-73 177q0 67 34 125-128-65-213-197z m259 72q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19-8 19-19 7q-70 0-120-50t-50-119z" horiz-adv-x="1000" />
<glyph glyph-name="share" unicode="&#xe808;" d="m0 350q0 74 52 126t127 53q70 0 121-48l201 100q-1 12-1 19 0 74 52 126t127 53 126-53 52-126-52-126-126-53q-71 0-122 48l-201-100q1-12 1-19t-1-19l201-100q51 48 122 48 74 0 126-53t52-126-52-126-126-53-127 53-52 126q0 7 1 19l-201 100q-51-48-121-48-75 0-127 53t-52 126z" horiz-adv-x="857.1" />
</font>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Binary file not shown.

BIN
public/font/hennypenny.ttf Normal file

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 234 KiB

After

Width:  |  Height:  |  Size: 234 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

BIN
public/img/lutim120.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
public/img/lutim128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/img/lutim152.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
public/img/lutim196.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
public/img/lutim256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
public/img/lutim32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
public/img/lutim60.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
public/img/lutim76.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
public/img/lutim90.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
public/img/rocket.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

244
public/js/bootstrap.js vendored
View File

@@ -87,250 +87,6 @@
}(jQuery);
/* ========================================================================
* Bootstrap: modal.js v3.1.1
* http://getbootstrap.com/javascript/#modals
* ========================================================================
* Copyright 2011-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// MODAL CLASS DEFINITION
// ======================
var Modal = function (element, options) {
this.options = options
this.$element = $(element)
this.$backdrop =
this.isShown = null
if (this.options.remote) {
this.$element
.find('.modal-content')
.load(this.options.remote, $.proxy(function () {
this.$element.trigger('loaded.bs.modal')
}, this))
}
}
Modal.DEFAULTS = {
backdrop: true,
keyboard: true,
show: true
}
Modal.prototype.toggle = function (_relatedTarget) {
return this[!this.isShown ? 'show' : 'hide'](_relatedTarget)
}
Modal.prototype.show = function (_relatedTarget) {
var that = this
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
this.$element.trigger(e)
if (this.isShown || e.isDefaultPrevented()) return
this.isShown = true
this.escape()
this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
this.backdrop(function () {
var transition = $.support.transition && that.$element.hasClass('fade')
if (!that.$element.parent().length) {
that.$element.appendTo(document.body) // don't move modals dom position
}
that.$element
.show()
.scrollTop(0)
if (transition) {
that.$element[0].offsetWidth // force reflow
}
that.$element
.addClass('in')
.attr('aria-hidden', false)
that.enforceFocus()
var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
transition ?
that.$element.find('.modal-dialog') // wait for modal to slide in
.one($.support.transition.end, function () {
that.$element.focus().trigger(e)
})
.emulateTransitionEnd(300) :
that.$element.focus().trigger(e)
})
}
Modal.prototype.hide = function (e) {
if (e) e.preventDefault()
e = $.Event('hide.bs.modal')
this.$element.trigger(e)
if (!this.isShown || e.isDefaultPrevented()) return
this.isShown = false
this.escape()
$(document).off('focusin.bs.modal')
this.$element
.removeClass('in')
.attr('aria-hidden', true)
.off('click.dismiss.bs.modal')
$.support.transition && this.$element.hasClass('fade') ?
this.$element
.one($.support.transition.end, $.proxy(this.hideModal, this))
.emulateTransitionEnd(300) :
this.hideModal()
}
Modal.prototype.enforceFocus = function () {
$(document)
.off('focusin.bs.modal') // guard against infinite focus loop
.on('focusin.bs.modal', $.proxy(function (e) {
if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
this.$element.focus()
}
}, this))
}
Modal.prototype.escape = function () {
if (this.isShown && this.options.keyboard) {
this.$element.on('keyup.dismiss.bs.modal', $.proxy(function (e) {
e.which == 27 && this.hide()
}, this))
} else if (!this.isShown) {
this.$element.off('keyup.dismiss.bs.modal')
}
}
Modal.prototype.hideModal = function () {
var that = this
this.$element.hide()
this.backdrop(function () {
that.removeBackdrop()
that.$element.trigger('hidden.bs.modal')
})
}
Modal.prototype.removeBackdrop = function () {
this.$backdrop && this.$backdrop.remove()
this.$backdrop = null
}
Modal.prototype.backdrop = function (callback) {
var animate = this.$element.hasClass('fade') ? 'fade' : ''
if (this.isShown && this.options.backdrop) {
var doAnimate = $.support.transition && animate
this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
.appendTo(document.body)
this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
if (e.target !== e.currentTarget) return
this.options.backdrop == 'static'
? this.$element[0].focus.call(this.$element[0])
: this.hide.call(this)
}, this))
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
this.$backdrop.addClass('in')
if (!callback) return
doAnimate ?
this.$backdrop
.one($.support.transition.end, callback)
.emulateTransitionEnd(150) :
callback()
} else if (!this.isShown && this.$backdrop) {
this.$backdrop.removeClass('in')
$.support.transition && this.$element.hasClass('fade') ?
this.$backdrop
.one($.support.transition.end, callback)
.emulateTransitionEnd(150) :
callback()
} else if (callback) {
callback()
}
}
// MODAL PLUGIN DEFINITION
// =======================
var old = $.fn.modal
$.fn.modal = function (option, _relatedTarget) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.modal')
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
if (typeof option == 'string') data[option](_relatedTarget)
else if (options.show) data.show(_relatedTarget)
})
}
$.fn.modal.Constructor = Modal
// MODAL NO CONFLICT
// =================
$.fn.modal.noConflict = function () {
$.fn.modal = old
return this
}
// MODAL DATA-API
// ==============
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
var $this = $(this)
var href = $this.attr('href')
var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
if ($this.is('a')) e.preventDefault()
$target
.modal(option, this)
.one('hide', function () {
$this.is(':visible') && $this.focus()
})
})
$(document)
.on('show.bs.modal', '.modal', function () { $(document.body).addClass('modal-open') })
.on('hidden.bs.modal', '.modal', function () { $(document.body).removeClass('modal-open') })
}(jQuery);
/* ========================================================================
* Bootstrap: transition.js v3.1.1
* http://getbootstrap.com/javascript/#transitions

File diff suppressed because one or more lines are too long

390
public/js/freezeframe.js Normal file
View File

@@ -0,0 +1,390 @@
/* -----------------------------------------------------------------------------
* Freezeframe
* freezeframe.js v2.0.2
* 2014 Chris Antonellis
*
* Freezeframe.js is a script that automatically pauses animated GIFs and
* enables them to start animating on mouse hover.
*
* Website: http://freezeframe.chrisantonellis.com/
* Documentation: http://freezeframe.chrisantonellis.com/documentation/
*
* Licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License
* http://creativecommons.org/licenses/by-sa/3.0/deed.en_US
* -------------------------------------------------------------------------- */
FreezeFrame = (function($) {
var _ff;
var canvas;
var context;
var class_name;
var trigger_event;
var support_touch_devices;
var animation_play_duration;
var loading_background_color;
var loading_background_image;
var loading_background_position;
var loading_fade_in_speed;
var animation_icon_image;
var animation_icon_position;
var animation_fade_out_speed;
var is_touch_device;
var freezeframe_count;
function FreezeFrame( _options ) {
_ff = this;
_options.class_name == null ?
this.class_name = "freezeframe" :
this.class_name = _options.class_name;
_options.trigger_event == null ?
this.trigger_event = "hover" :
this.trigger_event = _options.trigger_event.toLowerCase();
_options.support_touch_devices == null ?
this.support_touch_devices = true :
this.support_touch_devuces = _options.support_touch_devices.toLowerCase();
_options.animation_play_duration == null ?
this.animation_play_duration = 10000 :
this.animation_play_duration = parseInt(_options.animation_play_duration);
_options.loading_background_color == null ?
this.loading_background_color = "#666" :
this.loading_background_color = _options.loading_background_color.toLowerCase();
_options.loading_background_image == null ?
this.loading_background_image = "data:image/gif;base64,R0lGODlhEAAQAPIAAGZmZv///4mJidbW1v///8PDw7CwsKampiH+GkNyZWF0ZWQgd2l0aCBhamF4bG9hZC5pbmZvACH5BAAKAAAAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQACgABACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkEAAoAAgAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkEAAoAAwAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkEAAoABAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQACgAFACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQACgAGACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAAKAAcALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" :
this.loading_background_image = _options.loading_background_image;
_options.loading_background_position == null ?
this.loading_background_position = "center center" :
this.loading_background_position = _options.loading_background_position.toLowerCase();
_options.animation_icon_image == null ?
this.animation_icon_image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAl9JREFUeNrs2s2K2lAUAGATb3TE2Cr+oaJQNFOh1MVsxK0OuJbSdX2V9gG67GqW3RbaB7B9gy7qgKJF8G/EarHVWGMyJj230EXLLITm6q05F44/IVw5X3JPjiGCZVkuJw/R5fCBAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAjwjyMej79MpVKP/hcAwe6boul0+p3P5/vmdruv4evbVqvVdRRALpd7HQqFpn6/XxUEwdI07UZV1ffNZvMzjwDE7gk9Hs8mGAwuYCl8hTNB32630nw+f1IqlRY8QtgOQAjRZVleJxKJ77AcVLptOBwup9PpPR4hbAcQRdGUJOk2HA5riqKokUjEgKK4GY/HKo8QhMGcv4oKIJg0+Xw+r8H7bSaT0XiEICwnhyLoCgQCptfrNSgCjxDkED8ChdGiwSMEOaQ2jxDkGOtuH4jJZHJ/Nps9LRaLi+Vy2WDVUB0FYB+IXq+36ff7P0ajURj2eQbF9EO73W6cFMBdENA8mXQbNFDier0mhULhQaVSeZzNZj+d3Bnwe+i6LkDCAqx9QmMwGJwlk8nzarV6Cf3EDHZ5DvHx5ADuSjwWiz0sl8uX8H9izjLxowLskfgL1okfBYCnxA8KwGPiBwGg9xpWq5XIY+IsAQT6stvt6BGX4Nrt4jFxZgCmaYqGYRDo4nzdbncny3KGx8SZAcCRl6CH9yuKcl6r1S6i0egXHhP/Y53aGfV6/U2n02nA5yuIC7vntzsEBo/KXkG84vaI/12w8Flhhw8EQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEcNr4KcAABsB8S2vxAV8AAAAASUVORK5CYII=" :
this.animation_icon_image = _options.animation_icon_image;
_options.animation_icon_position == null ?
this.animation_icon_position = "top left" :
this.animation_icon_position = _options.animation_icon_position.toLowerCase();
_options.loading_fade_in_speed == null ?
this.loading_fade_in_speed = 500 :
this.loading_fade_in_speed = parseInt(_options.loading_fade_in_speed);
_options.animation_fade_out_speed == null ?
this.animation_fade_out_speed = 250 :
this.animation_fade_out_speed = parseInt(_options.animation_fade_out_speed);
this.is_touch_device = isMouseEventSupported("ontouchstart");
this.freezeframe_count = 0;
};
/** --------------------------------------------------------------------------
* @function .setup()
* Sets up the freezeframe instance by adding a canvas element to the
* document and capturing references to the canvas and its 2D context
* ------------------------------------------------------------------------ */
FreezeFrame.prototype.setup = function() {
$("<canvas>",{id:"freezeframe-canvas"})
.css("display", "none")
.prependTo($("body"));
this.canvas = $("canvas#freezeframe-canvas");
this.context = this.canvas[0].getContext('2d');
};
/** --------------------------------------------------------------------------
* .run()
* Starts freezeframe processing for each image. Copies the image to
* the freezeframe canvas and converts it to a data url
* ------------------------------------------------------------------------ */
FreezeFrame.prototype.run = function() {
var images = [];
var ext;
var figure_background = _ff.loading_background_color;
if( _ff.loading_background_image !== false ) {
figure_background += " url('" + _ff.loading_background_image + "') " +
_ff.loading_background_position + " no-repeat";
}
// Select all images with a class matching the option class_name
images = $('img[class*="' + _ff.class_name + '"]')
.not('[class*="' + _ff.class_name + '_done"]');
// Process each image by resetting the animation sequence, copying to the
// canvas, converting to a data url, and attaching that data url to the
// image itself as an attribute
$(images).each(function(index) {
// Change image class so it won't be reprocessed if .run() is run again
$(this).removeClass(_ff.class_name).addClass(_ff.class_name + "_done");
// Set cross-origin to anon to load images from remote services that send
// the correct header. Not working correctly, needs more testing
$(this).crossOrigin = "anonymous";
// Determine file extension
ext = $(this)[0].src.split(".");
ext = ext[ext.length - 1].toLowerCase();
// Remove non GIF files
if(ext !== "gif") {
images.splice(index, 1);
} else {
var freezeframe_figure = $("<figure />")
.attr("class", "freezeframe-container " + _ff.freezeframe_count)
.css({"display": "inline-block",
"overflow": "hidden",
"margin": 0,
"background-size": "100% auto",
"width": "100%",
"max-height": "100%",
"background": figure_background});
$(this).css({"opacity": 0,
"display": "block"})
.wrap(freezeframe_figure);
freezeframe_figure = $(this).parent();
// If an animation icon image is available, attach it
if(_ff.animation_icon_image !== false) {
var animation_icon = $("<div />")
.attr("class", "freezeframe-animation-icon")
.css({"display": "block",
"position": "absolute",
"background": "transparent " + "url('" + _ff.animation_icon_image + "') " +
_ff.animation_icon_position + " no-repeat",
"pointer-events": "none",
"z-index": 100,
"opacity": 0});
$(freezeframe_figure).prepend(animation_icon);
animation_icon = $(this).siblings($(".freezeframe-animation-icon"));
}
// Increment counter so each image gets a unique number
_ff.freezeframe_count++;
// Create a temporary refernce to the image as non-object to pass to .drawImage
var _self = this;
// Using imagesLoaded by Desandro because .load doesn't work on cached images
$(this).imagesLoaded(function() {
$(this).off("imagesLoaded");
_ff.canvas[0].width = $(this)[0].clientWidth;
_ff.canvas[0].height = $(this)[0].clientHeight;
$(this).attr("animated", $(this).attr("src"))
.attr("src", $(this).attr("src"));
_ff.context.drawImage(_self, 0, 0, $(this)[0].clientWidth, $(this)[0].clientHeight);
$(this).attr("src", _ff.canvas[0].toDataURL());
// If an animation icon image is available, show it
if(_ff.animation_icon_image !== false) {
$(animation_icon).css({
"width": parseInt($(this)[0].width),
"height": parseInt($(this)[0].height)})
.animate({"opacity": 1}, _ff.loading_fade_in_speed);
}
// When fade in sequence is complete, enable interaction
$(this).animate({"opacity": 1}, _ff.loading_fade_in_speed, function() {
$(freezeframe_figure).css("background", "url('" + $(this).attr("src") + "')");
$(this).css("opacity", 0)
.attr("src", $(this).attr("animated"));
// Touch Device or Click Event
if((_ff.support_touch_devices && _ff.is_touch_device) || _ff.trigger_event.toLowerCase() == "click") {
var stop_animation;
var animating = false;
$(this).click(function() {
// If not currently animating, start animating
if(!animating) {
$(this).attr("src", $(this).attr("src"))
.css("opacity", 1);
if(_ff.animation_icon_image !== false) {
$(animation_icon).css("opacity", 0);
}
stop_animation = setInterval(function() {
clearInterval(stop_animation);
this.animate({"opacity": 0}, _ff.animation_fade_out_speed);
if(_ff.animation_icon_image !== false) {
$(animation_icon).animate({"opacity": 1}, _ff.animation_fade_out_speed);
}
animating = false;
}, _ff.animation_play_duration);
animating = true;
// If currently animating, stop animating
} else {
clearInterval(stop_animation);
$(this).animate({"opacity": 0}, _ff.animation_fade_out_speed);
if(_ff.animation_icon_image !== false) {
$(animation_icon).animate({"opacity": 1}, _ff.animation_fade_out_speed);
}
animating = false;
}
});
} else {
// Hover Event
$(this).mouseenter(function() {
$(this).attr("src", $(this).attr("src"))
.css("opacity", 1);
if(_ff.animation_icon_image !== false) {
$(animation_icon).css("opacity", 0);
}
});
$(this).mouseleave(function() {
$(this).animate({"opacity": 0}, _ff.animation_fade_out_speed);
if(_ff.animation_icon_image !== false) {
$(animation_icon).animate({"opacity": 1}, _ff.animation_fade_out_speed);
}
});
}
});
});
}
});
};
return FreezeFrame;
})(jQuery);
/** ----------------------------------------------------------------------------
* Setup & Run Freezeframe
* -------------------------------------------------------------------------- */
jQuery(document).ready(function($) {
typeof(freezeframe_options) == 'undefined' ? freezeframe_options = {} : null;
freezeframe = new FreezeFrame(freezeframe_options);
freezeframe.setup();
freezeframe.run();
});
/** ----------------------------------------------------------------------------
* jQuery imagesLoaded plugin v2.1.1
* http://github.com/desandro/imagesloaded
*
* MIT License. by Paul Irish et al.
* -------------------------------------------------------------------------- */
;(function($, undefined) {
'use strict';
var BLANK = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==';
$.fn.imagesLoaded = function( callback ) {
var $this = this,
deferred = $.isFunction($.Deferred) ? $.Deferred() : 0,
hasNotify = $.isFunction(deferred.notify),
$images = $this.find('img').add( $this.filter('img') ),
loaded = [],
proper = [],
broken = [];
if ($.isPlainObject(callback)) {
$.each(callback, function (key, value) {
if (key === 'callback') {
callback = value;
} else if (deferred) {
deferred[key](value);
}
});
}
function doneLoading() {
var $proper = $(proper),
$broken = $(broken);
if ( deferred ) {
if ( broken.length ) {
deferred.reject( $images, $proper, $broken );
} else {
deferred.resolve( $images );
}
}
if ( $.isFunction( callback ) ) {
callback.call( $this, $images, $proper, $broken );
}
}
function imgLoadedHandler( event ) {
imgLoaded( event.target, event.type === 'error' );
}
function imgLoaded( img, isBroken ) {
if ( img.src === BLANK || $.inArray( img, loaded ) !== -1 ) {
return;
}
loaded.push( img );
if ( isBroken ) {
broken.push( img );
} else {
proper.push( img );
}
$.data( img, 'imagesLoaded', { isBroken: isBroken, src: img.src } );
if ( hasNotify ) {
deferred.notifyWith( $(img), [ isBroken, $images, $(proper), $(broken) ] );
}
if ( $images.length === loaded.length ) {
setTimeout( doneLoading );
$images.unbind( '.imagesLoaded', imgLoadedHandler );
}
}
if ( !$images.length ) {
doneLoading();
} else {
$images.bind( 'load.imagesLoaded error.imagesLoaded', imgLoadedHandler )
.each( function( i, el ) {
var src = el.src;
var cached = $.data( el, 'imagesLoaded' );
if ( cached && cached.src === src ) {
imgLoaded( el, cached.isBroken );
return;
}
if ( el.complete && el.naturalWidth !== undefined ) {
imgLoaded( el, el.naturalWidth === 0 || el.naturalHeight === 0 );
return;
}
if ( el.readyState || el.complete ) {
el.src = BLANK;
el.src = src;
}
});
}
return deferred ? deferred.promise( $this ) : $this;
};
})(jQuery);
/** ----------------------------------------------------------------------------
* isMouseEventSupported by kangax
* http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
* -------------------------------------------------------------------------- */
function isMouseEventSupported(eventName) {
var el = document.createElement('div');
eventName = 'on' + eventName;
var isSupported = (eventName in el);
if (!isSupported) {
el.setAttribute(eventName, 'return;');
isSupported = typeof el[eventName] == 'function';
}
el = null;
return isSupported;
}

1
public/js/freezeframe.min.js vendored Normal file

File diff suppressed because one or more lines are too long

26
public/js/lutim.js Normal file
View File

@@ -0,0 +1,26 @@
$('document').ready(function() {
$('.jsonly').show();
// Are we in a mozilla navigator? (well, are we in a navigator which can handle webapps?)
if (navigator.mozApps !== undefined) {
var installCheck = navigator.mozApps.checkInstalled(manifestUrl);
installCheck.onsuccess = function() {
if(installCheck.result === null) {
var button = $('#install-app');
// Show app install button when app is not installed
button.css('display','inline-block');
button.click(function() {
var request = window.navigator.mozApps.install(manifestUrl);
request.onsuccess = function () {
// Save the App object that is returned
var appRecord = this.result;
button.css('display','none');
};
request.onerror = function () {
// Display the error information from the DOMError object
alert('Install failed, error: ' + this.error.name);
};
});
}
}
}
});

1
public/js/morris-0.4.3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

10
public/js/raphael-min.js vendored Normal file

File diff suppressed because one or more lines are too long

67
public/js/stats.js Normal file
View File

@@ -0,0 +1,67 @@
function graph(stats_labels, stats_data, stats_total) {
Morris.Line({
// ID of the element in which to draw the chart.
element: 'evol-holder',
// Chart data records -- each entry in this array corresponds to a point on
// the chart.
data: stats_data,
// The name of the data record attribute that contains x-values.
xkey: 'day',
// A list of names of data record attributes that contain y-values.
ykeys: ['value'],
// Labels for the ykeys -- will be displayed when you hover over the
// chart.
labels: ['Uploaded files'],
xLabels: 'day',
dateFormat: function(x) { return new Date(x).toLocaleDateString(); },
xLabelFormat: function(x) { return x.toLocaleDateString(); }
});
Morris.Line({
// ID of the element in which to draw the chart.
element: 'total-holder',
// Chart data records -- each entry in this array corresponds to a point on
// the chart.
data: stats_total,
// The name of the data record attribute that contains x-values.
xkey: 'day',
// A list of names of data record attributes that contain y-values.
ykeys: ['value'],
// Labels for the ykeys -- will be displayed when you hover over the
// chart.
labels: ['Uploaded files'],
xLabels: 'day',
lineColors: ['red'],
dateFormat: function(x) { return new Date(x).toLocaleDateString(); },
xLabelFormat: function(x) { return x.toLocaleDateString(); }
});
}
$(document).ready(function() {
// Get the data
var stats_labels = [], stats_data = [], stats_total = [];
$("#stats-data thead th").each(function () {
stats_labels.push($(this).html());
});
var i = 0;
$("#stats-data tbody tr:first-child td").each(function () {
var s = stats_labels[i++];
s = s.replace(/([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{4})/, "$3-$2-$1");
stats_data.push({ day: s, value: $(this).html()});
});
i = 0;
$("#stats-data tbody tr:nth-child(2) td").each(function () {
var s = stats_labels[i++];
s = s.replace(/([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{4})/, "$3-$2-$1");
stats_total.push({ day: s, value: $(this).html()});
});
// Hide the data
$("#stats-data").hide();
graph(stats_labels, stats_data, stats_total);
$(window).resize(function() {
$("#evol-holder").empty();
$("#total-holder").empty();
graph(stats_labels, stats_data, stats_total);
});
});

View File

@@ -1,6 +1,5 @@
% # vim:set sw=4 ts=4 sts=4 ft=html.epl expandtab:
% my $scheme = (defined(config('https')) && config('https')) ? 'https' : 'http';
<div class="modal-body">
<%==l 'informations-body', url_for('/')->base->scheme($scheme)->to_abs().'/', config('contact') %>
<div>
<%==l 'informations-body', url_for('/')->to_abs(), config('contact') %>
<%= link_to url_for('index') => ( class => "btn btn-primary btn-lg" ) => begin %><%=l 'back-to-index' %><% end%>
</div>

View File

@@ -1,5 +1,4 @@
% # vim:set sw=4 ts=4 sts=4 ft=html.epl expandtab:
% my $scheme = (defined(config('https')) && config('https')) ? 'https' : 'http';
<div class="messages">
% if (config('always_encrypt')) {
<p><%=l 'always_encrypt' %></p>
@@ -10,17 +9,66 @@
<img class="pull-left thumbnail" alt="<%= stash('filename') %> thumbnail" src="<%= stash('thumb') %>">
% }
<div>
% # Display image informations
% my $url = url_for('/')->to_abs().stash('short');
<strong><%= stash('filename') %></strong>
&nbsp;&nbsp;&nbsp;<a target="_blank" class="btn btn-default btn-primary btn-xs" href="https://twitter.com/share?url=<%= $url %>?t"><%=l 'tweet_it' %></a>
<ul class="list-unstyled">
<li><i class="icon icon-eye" title="<%=l 'view-link' %>"></i> <%= link_to url_for('/')->base->scheme($scheme)->to_abs.'/'.stash('short') => begin %><%= url_for('/')->base->scheme($scheme)->to_abs.'/'.stash('short') %><%= end %></li>
<li><i class="icon icon-download" title="<%=l 'download-link' %>"></i> <%= link_to url_for('/')->base->scheme($scheme)->to_abs.'/'.stash('short').'?dl' => begin %><%= url_for('/')->base->scheme($scheme)->to_abs.'/'.stash('short').'?dl' %><%= end %></li>
<li><i class="icon icon-touiteur" title="<%=l 'twitter-link' %>"></i> <%= link_to url_for('/')->base->scheme($scheme)->to_abs.'/'.stash('short').'?t' => begin %><%= url_for('/')->base->scheme($scheme)->to_abs.'/'.stash('short').'?t' %><%= end %></li>
% my $delete_url = url_for('delete', {short => stash('real_short'), token => stash('token')})->to_abs();
<li><i class="icon icon-eye" title =" <%= l 'view-link' %>"></i> <%= link_to $url => begin %> <%= $url %> <%= end %></li>
<li><i class="icon icon-download" title =" <%= l 'download-link' %>"></i> <%= link_to $url.'?dl' => begin %> <%= $url.'?dl' %> <%= end %></li>
<li><i class="icon icon-share" title =" <%= l 'share-link' %>"></i> <%= link_to $url.'?t' => begin %> <%= $url.'?t' %> <%= end %></li>
<li><i class="icon icon-trash" title =" <%= l 'delete-link' %>"></i> <%= link_to $delete_url => begin %> <%= $delete_url %> <%= end %></li>
</ul>
</div>
% # Delay modification form
% my $modify_url = url_for('modify', {short => stash('real_short'), token => stash('token')})->to_abs();
<form class="form" role="form" method="POST" action="<%== $modify_url %>">
<div class="form-group form-inline">
<input name="image_url" type="hidden" value="<%= $url %>">
<select name="delete-day" class="form-control">
% for my $delay (qw/0 1 7 30 365/) {
% my $text = ($delay == 7 || $delay == 30) ? l('delay_days', $delay) : l("delay_$delay");
% if (config('max_delay')) {
% if ($delay) {
% if ($delay < config('max_delay')) {
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
% } elsif ($delay == config('max_delay')) {
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
% last;
% } else {
% my $text = ($delay == 1) ? l('delay_1') : l('delay_days', $delay);
<option value="<%= config('max_delay') %>" <%== is_selected(config('max_delay')) %>><%=l('delay_days', config('max_delay')) %></option>
% last;
% }
% }
% } else {
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
% }
% }
</select>
<div class="checkbox">
<label>
<input type="checkbox" name="first-view"> <%=l 'delete-first' %>
</label>
<label <%== (config('always_encrypt')) ? 'class="always-encrypt"' : '' %>>
<input type="checkbox" name="crypt"> <%=l 'crypt_image' %>
</label>
</div>
<%= submit_button l('go'), class => 'btn btn-sm btn-default btn-primary', id => 'submitbutton' %>
</div>
</form>
</div>
% }
% if (defined(flash('success'))) {
<div class="alert alert-success">
<button type="button" class="close jsonly" data-dismiss="alert" aria-hidden="true">&times;</button>
<p><%== flash('success') %></p>
</div>
% }
% if (defined(flash('msg'))) {
<div class="alert alert-danger">
<button type="button" class="close jsonly" data-dismiss="alert" aria-hidden="true">&times;</button>
<strong><%=l 'some-bad' %></strong><br>
<%= flash('filename') %> <%= flash('msg') %>
</div>
@@ -29,48 +77,48 @@
<noscript>
<form class="form" role="form" method="POST" action="<%== url_for('add') %>" enctype="multipart/form-data">
<div class="form-group form-inline">
<select name="delete-day" class="form-control">
% for my $delay (qw/0 1 7 30 365/) {
% my $text = ($delay == 7 || $delay == 30) ? l('delay_days', $delay) : l("delay_$delay");
% if (config('max_delay')) {
% if ($delay) {
% if ($delay < config('max_delay')) {
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
% } elsif ($delay == config('max_delay')) {
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
% last;
% } else {
% my $text = ($delay == 1) ? l('delay_1') : l('delay_days', $delay);
<option value="<%= config('max_delay') %>" <%== is_selected(config('max_delay')) %>><%=l('delay_days', config('max_delay')) %></option>
% last;
% }
% }
% } else {
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
% }
% }
</select>
<div class="checkbox">
<label>
<input type="checkbox" name="first-view"> <%=l 'delete-first' %>
</label>
<label class="always-encrypt">
<input type="checkbox" name="crypt"> <%=l 'crypt_image' %>
</label>
<div class="form-group form-inline">
<select name="delete-day" class="form-control">
% for my $delay (qw/0 1 7 30 365/) {
% my $text = ($delay == 7 || $delay == 30) ? l('delay_days', $delay) : l("delay_$delay");
% if (config('max_delay')) {
% if ($delay) {
% if ($delay < config('max_delay')) {
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
% } elsif ($delay == config('max_delay')) {
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
% last;
% } else {
% my $text = ($delay == 1) ? l('delay_1') : l('delay_days', $delay);
<option value="<%= config('max_delay') %>" <%== is_selected(config('max_delay')) %>><%=l('delay_days', config('max_delay')) %></option>
% last;
% }
% }
% } else {
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
% }
% }
</select>
<div class="checkbox">
<label>
<input type="checkbox" name="first-view"> <%=l 'delete-first' %>
</label>
<label <%== (config('always_encrypt')) ? 'class="always-encrypt"' : '' %>>
<input type="checkbox" name="crypt"> <%=l 'crypt_image' %>
</label>
</div>
</div>
<div class="form-group">
<label for="lutim-file"><%=l 'upload_image' %></label>
<input type="file" name="file" id="lutim-file" accept="image/*">
</div>
<div class="form-group">
<label for="lutim-file-url"><%=l 'upload_image_url' %></label>
<input type="url" name="lutim-file-url" placeholder="<%=l 'image_url' %>">
</div>
</div>
<div class="form-group">
<label for="lutim-file"><%=l 'upload_image' %></label>
<input type="file" name="file" id="lutim-file">
</div>
<div class="form-group">
<label for="lutim-file-url"><%=l 'upload_image_url' %></label>
<input type="url" name="lutim-file-url" placeholder="<%=l 'image_url' %>">
<p class="help-block"><%=l 'image-only' %></p>
</div>
<%= submit_button l('go'), class => 'btn btn-default btn-primary', id => 'submitbutton' %>
</form>
<%= submit_button l('go'), class => 'btn btn-default btn-primary', id => 'submitbutton' %>
</form>
</noscript>
<!-- D&D Zone-->
@@ -101,7 +149,7 @@
<label>
<input type="checkbox" id="first-view"> <%=l 'delete-first' %>
</label>
<label class="always-encrypt">
<label <%== (config('always_encrypt')) ? 'class="always-encrypt"' : '' %>>
<input type="checkbox" id="crypt"> <%=l 'crypt_image' %>
</label>
</div>
@@ -112,16 +160,16 @@
<div class="browser">
<label>
<span><%=l 'file-browser' %></span>
<input type="file" name="files[]" multiple="multiple" title='<%=l 'file-browser' %>'>
<input type="file" name="files[]" multiple="multiple" title='<%=l 'file-browser' %>' accept="image/*">
</label>
</div>
</div>
<p class="help-block"><%=l 'image-only' %></p>
<form class="form-horizontal" role="form" method="POST" action="<%== url_for('add') %>">
<div class="form-group">
<span class="col-sm-3"><span class="hidden-spin" style="font-size:200%; display:none;" > <i class="icon-spinner animate-spin pull-right"></i></span><label for="lutim-file-url" class="control-label pull-right"><%=l 'upload_image_url' %></label></span>
<div class="col-sm-9">
<input type="url" name="file-url" class="form-control" id="lutim-file-url" placeholder="<%=l 'image_url' %>">
<span class="col-sm-3 col-xs-12"><span class="hidden-spin" style="font-size:200%; display:none;" > <i class="icon-spinner animate-spin pull-right"></i></span><label for="lutim-file-url" class="control-label pull-right"><%=l 'upload_image_url' %></label></span>
<div class="col-sm-9 col-xs-12">
<input type="url" name="lutim-file-url" class="form-control" id="lutim-file-url" placeholder="<%=l 'image_url' %>">
</div>
</div>
<a href="#" class="btn btn-default btn-primary pull-right" id="file-url-button"><%=l 'go' %></a>
@@ -129,15 +177,56 @@
</div>
<!-- /D&D Zone -->
%= javascript 'js/dmuploader.min.js'
%= javascript begin
function link(url, dl) {
if (dl !== '') {
function link(url, dl, token, modify) {
if (token !== undefined) {
if (modify !== undefined && modify === true) {
return '<%== url_for('index')->to_abs() %>m/'+url+'/'+token;
} else {
url = 'd/'+url+'/'+token;
}
} else if (dl !== '') {
url = url+'?'+dl;
}
return '<a href="<%== url_for('index')->base->scheme($scheme)->to_abs() %>/'+url+'"><%== url_for('index')->base->scheme($scheme)->to_abs() %>/'+url+'</a>';
return '<a href="<%== url_for('index')->to_abs() %>'+url+'"><%== url_for('index')->to_abs() %>'+url+'</a>';
}
function message(success, msg) {
function share(url) {
console.log(url);
new MozActivity({
name: "share",
data: {
type: "url",
number: 1,
url: url
}
});
}
function tw_url(url) {
var btn = '&nbsp;&nbsp;&nbsp;<a target="_blank" class="btn btn-default btn-primary btn-xs" href="https://twitter.com/share?url=<%== url_for('index')->to_abs() %>'+url+'?t"><%=l 'tweet_it' %></a>';
if (navigator.mozSetMessageHandler !== undefined) {
btn = btn+'&nbsp;&nbsp;&nbsp;<a target="_blank" class="btn btn-default btn-primary btn-xs" href="" onclick="share(\'<%== url_for('index')->to_abs() %>'+url+'?t\');return false;"><%=l 'share_it' %></a>';
}
return btn
}
function modify(url, short) {
$.ajax({
url : url,
type : "POST",
data : {
'image_url' : '<%== url_for('index')->to_abs() %>'+short,
'format' : 'json',
'first-view' : ($("#first-view-"+short).prop('checked')) ? 1 : 0,
'delete-day' : $("#day-"+short).val()
},
success: function(data) {
alert(data.msg);
},
error: function() {
alert('<%=l 'modify_image_error' %>');
}
});
}
function build_message(success, msg) {
if(success) {
var thumb = (msg.thumb !== null) ? '<img class="pull-left thumbnail" alt="'+msg.filename+' thumbnail" src="'+msg.thumb+'">' : ''
return '<div class="alert alert-success"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>'
@@ -145,13 +234,41 @@
+'<div><strong>'
+msg.filename
+'</strong>'
+tw_url(msg.short)
+'<ul class="list-unstyled"><li><i class="icon icon-eye" title="<%=l 'view-link' %>"></i>&nbsp;'
+link(msg.short, '')
+'</a></li><li><i class="icon icon-download" title="<%=l 'download-link' %>"></i>&nbsp;'
+'</li><li><i class="icon icon-download" title="<%=l 'download-link' %>"></i>&nbsp;'
+link(msg.short, 'dl')
+'</a></li><li><i class="icon icon-touiteur" title="<%=l 'twitter-link' %>"></i>&nbsp;'
+'</li><li><i class="icon icon-share" title="<%=l 'share-link' %>"></i>&nbsp;'
+link(msg.short, 't')
+'</li></ul></div>';
+'</li><li><i class="icon icon-trash" title="<%=l 'delete-link' %>"></i>&nbsp;'
+link(msg.real_short, '', msg.token)
+'</li></ul><form class="form" role="form" method="POST" action="'
+link(msg.real_short, '', msg.token, true)
+'"><div class="form-group form-inline"><select id="day-'+msg.real_short+'" name="delete-day" class="form-control">'
% for my $delay (qw/0 1 7 30 365/) {
% my $text = ($delay == 7 || $delay == 30) ? l('delay_days', $delay) : l("delay_$delay");
% if (config('max_delay')) {
% if ($delay) {
% if ($delay < config('max_delay')) {
+'<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>'
% } elsif ($delay == config('max_delay')) {
+'<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>'
% last;
% } else {
% my $text = ($delay == 1) ? l('delay_1') : l('delay_days', $delay);
+'<option value="<%= config('max_delay') %>" <%== is_selected(config('max_delay')) %>><%=l('delay_days', config('max_delay')) %></option>'
% last;
% }
% }
% } else {
+'<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>'
% }
% }
+'</select>&nbsp;<div class="checkbox"><label><input id="first-view-'+msg.real_short+'" type="checkbox" name="first-view"> <%=l 'delete-first' %></label>'
+'</div>&nbsp;'
+'<a href="#" onclick="modify(\''+link(msg.real_short, '', msg.token, true)+'\', \''+msg.real_short+'\');return false;" class="btn btn-sm btn-default btn-primary"><%=l 'go' %></a></div></form>'
+'</div>';
} else {
return '<div class="alert alert-danger"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button><strong><%=l 'some-bad' %></strong><br>'
+msg.filename
@@ -174,17 +291,16 @@
$('#'+id).prop('aria-valuenow', percent);
$('#'+id).prop('style', 'width: '+percent+'%;');
$('#'+id+'-text').html(percentStr);
},
onUploadSuccess: function(id, data){
$('#'+id+'-div').remove();
$(".messages").append(message(data.success, data.msg));
$(".messages").append(build_message(data.success, data.msg));
},
onUploadError: function(id, message){
$(".messages").append(message(false, ''));
$(".messages").append(build_message(false, ''));
},
onFileSizeError: function(file){
$(".messages").append(message(false, { filename: file.name, msg: '<%= l('file_too_big', $max_file_size) %>'}));
$(".messages").append(build_message(false, { filename: file.name, msg: '<%= l('file_too_big', $max_file_size) %>'}));
}
});
}
@@ -206,13 +322,13 @@
'delete-day' : $("#delete-day").val()
},
success: function(data) {
$(".messages").append(message(data.success, data.msg));
$(".messages").append(build_message(data.success, data.msg));
if (data.success) {
$("#lutim-file-url").val('');
}
},
error: function() {
$(".messages").append(message(false, ''));
$(".messages").append(build_message(false, ''));
},
complete: function() {
$("#lutim-file-url").prop('disabled', '');
@@ -224,6 +340,67 @@
}
}
function fileUpload(file) {
var fd = new FormData();
fd.append('file', file);
fd.append('format', 'json');
fd.append('first-view', ($("#first-view").prop('checked')) ? 1 : 0);
fd.append('crypt', ($("#crypt").prop('checked')) ? 1 : 0);
fd.append('delete-day', ($("#delete-day").val()));
$(".messages").append('<div id="1-div">'+file.name+'<br><div class="progress"><div id="1"class="progress-bar progress-striped active" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"><span id="1-text" class="pull-left" style="padding-left: 10px;"> 0%</span></div></div></div>');
// Ajax Submit
$.ajax({
url: '<%== url_for('add') %>',
type: 'POST',
dataType: 'json',
data: fd,
cache: false,
contentType: false,
processData: false,
forceSync: false,
xhr: function(){
var xhrobj = $.ajaxSettings.xhr();
if(xhrobj.upload){
xhrobj.upload.addEventListener('progress', function(event) {
var percent = 0;
var position = event.loaded || event.position;
var total = event.total || e.totalSize;
if(event.lengthComputable){
percent = Math.ceil(position / total * 100);
}
var percentStr = ' '+percent+'%';
$('#1').prop('aria-valuenow', percent);
$('#1').prop('style', 'width: '+percent+'%;');
$('#1-text').html(percentStr);
}, false);
}
return xhrobj;
},
success: function (data, message, xhr){
$('#1-div').remove();
$(".messages").append(build_message(data.success, data.msg));
},
error: function (xhr, status, errMsg){
$(".messages").append(build_message(false, ''));
},
});
}
window.onload = function() {
if (navigator.mozSetMessageHandler !== undefined) {
navigator.mozSetMessageHandler('activity', function handler(activityRequest) {
var activityName = activityRequest.source.name;
if (activityName == 'share') {
activity = activityRequest;
blob = activity.source.data.blobs[0];
fileUpload(blob);
}
});
}
};
$('document').ready(function() {
var firstview = ($("#first-view").prop('checked')) ? 1 : 0;
var deleteday = ($("#delete-day").prop('checked')) ? 1 : 0;
@@ -231,5 +408,12 @@
bindddz(firstview, deleteday);
$("#file-url-button").on("click", upload_url);
$('#lutim-file-url').keydown( function(e) {
var key = e.charCode ? e.charCode : e.keyCode ? e.keyCode : 0;
if(key == 13) {
e.preventDefault();
upload_url();
}
});
});
% end

View File

@@ -1,54 +1,34 @@
% # vim:set sw=4 ts=4 sts=4 ft=html.epl expandtab:
% use Mojo::Util qw(url_escape);
% my $twitter_url = 'https://twitter.com/share';
% my $scheme = (defined(config('https')) && config('https')) ? 'https' : 'http';
% my $url = url_for('/')->base->scheme($scheme)->to_abs().'/';
% my $url = url_for('/')->to_abs();
% $twitter_url .= '?url='.url_escape("$url")
% .'&via=framasky'
% .'&text=Check out this %23LUTIm instance! ';
% .'&text=Check out this %23Lutim instance! ';
<!DOCTYPE html>
<html>
<head>
<title>LUTIm</title>
<title>Lutim</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="icon" type="image/png" href="<%= url_for('/') %>img/favicon.png">
%= stylesheet 'css/bootstrap.min.css', media => 'screen'
%= stylesheet 'css/fontello.css'
%= stylesheet 'css/animation.css'
%= stylesheet 'css/uploader.css'
%= stylesheet begin
body {
padding-top: 40px;
padding-bottom: 40px;
}
.container {
padding: 15px;
margin: 0 auto;
}
.jsonly {
display: none;
}
.thumbnail {
margin-right: 8px;
}
% if (config('always_encrypt')) {
label.always-encrypt {
display: none;
}
<link rel="icon" sizes="128x128" href="<%= url_for('/') %>img/lutim128.png">
<link rel="icon" sizes="196x196" href="<%= url_for('/') %>img/lutim196.png">
<link rel="apple-touch-icon" href="<%= url_for('/') %>img/lutim60.png">
<link rel="apple-touch-icon" sizes="76x76" href="<%= url_for('/') %>img/lutim76.png">
<link rel="apple-touch-icon" sizes="120x120" href="<%= url_for('/') %>img/lutim120.png">
<link rel="apple-touch-icon" sizes="152x152" href="<%= url_for('/') %>img/lutim152.png">
<link rel="apple-touch-icon-precomposed" sizes="128x128" href="<%= url_for('/') %>img/lutim128.png">
% if (current_route 'stats') {
%= asset 'stats.css'
% } elsif (current_route 'about') {
%= asset 'about.css'
% } else {
%= asset 'index.css'
% }
% end
%= javascript 'js/jquery-2.1.0.min.js'
%= javascript 'js/bootstrap.min.js'
%= javascript begin
$('document').ready(function() {
$('.jsonly').show();
});
% end
</head>
<body>
<div class="container-fluid">
@@ -59,19 +39,19 @@
</div>
% }
<div>
<div class="pull-left hidden-xs">
<img src="<%= url_for('/') %>img/LUTIm_small.png" alt="LUTIm logo">
<div class="pull-left hidden-xs logo">
<img src="<%= url_for('/') %>img/Lutim_small.png" alt="Lutim logo">
</div>
<h1>Let's Upload That Image!</h1>
<a class="link_nocol" href="<%= url_for('/') %>" title="<%=l 'homepage' %>"><h1 class="hennypenny">Let's Upload That Image!</h1></a>
<p>
&copy; 2014 <%= link_to 'http://www.fiat-tux.fr' => begin %>Luc Didry<% end %> — 
<%=l 'license' %> <%= link_to 'https://www.gnu.org/licenses/agpl-3.0.html' => begin %>AGPL<% end %> — 
<span class="jsonly"><a data-toggle="modal" href="#myModal"><%=l 'informations' %></a> — </span>
<noscript><%= link_to url_for('about') => begin %><%=l 'informations' %><% end %> — </noscript>
<%= link_to url_for('about') => begin %><%=l 'informations' %><% end %> — 
<%= link_to 'https://github.com/ldidry/lutim' => (title => l 'fork-me') => begin %><i class="lead icon icon-github-circled"></i><% end %> 
<%= link_to $twitter_url => (title => l 'share-twitter') => begin %><i class="lead icon icon-touiteur"></i><% end %> 
<%= link_to 'https://flattr.com/submit/auto?user_id=_SKy_&url='.$url.'&title=LUTIm&category=software' => (title => 'Flattr this') => begin %><i class="lead icon icon-flattr"></i><% end %> 
<%= link_to 'bitcoin:1K3n4MXNRSMHk28oTfXEvDunWFthePvd8v?label=lutim' => (title => 'Give Bitcoins') => begin %><i class="lead icon icon-bitcoin"></i><% end %>
<%= link_to $twitter_url => (title => l 'share-twitter') => begin %><i class="lead icon icon-twitter"></i><% end %> 
<%= link_to 'https://flattr.com/submit/auto?user_id=_SKy_&url='.$url.'&title=Lutim&category=software' => (title => 'Flattr this') => begin %><i class="lead icon icon-flattr"></i><% end %> 
<%= link_to 'bitcoin:1K3n4MXNRSMHk28oTfXEvDunWFthePvd8v?label=lutim' => (title => 'Give Bitcoins') => begin %><i class="lead icon icon-bitcoin"></i><% end %> 
<a class="btn btn-default btn-xs" href="#" id="install-app"><img src="<%= url_for('/') %>img/rocket.png" alt="mozilla rocket logo"> <%=l 'install_as_webapp' %></a>
</p>
</div>
</div>
@@ -84,24 +64,16 @@
<div class="alert alert-danger">
<strong><%= stash('stop_upload') %></strong>
</div>
% }
%= javascript begin
var manifestUrl = '<%== url_for('manifest.webapp')->to_abs() %>';
% end
% if (current_route 'stats') {
%= asset 'stats.js'
% } elsif (!(current_route 'about')) {
%= asset 'index.js'
% }
<%= content %>
<div class="modal fade bs-modal-lg" id="myModal" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3 class="modal-title">LUTIm</h3>
</div>
<div class="modal-body">
<%==l 'informations-body', url_for('/')->base->scheme($scheme)->to_abs().'/', config('contact') %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</div>
% if (defined(config('piwik_img'))) {
<img src="<%== config('piwik_img') %>" style="border:0" alt="" />

View File

@@ -0,0 +1,34 @@
{
"name": "Lutim",
"description": "Lets Upload That Image!\n\nThis is a simple image sharing app which use <%= url_for('/')->to_abs %> for storing the images. Once you have uploaded an image, you'll be provided differents links:\n a view link, which points directly to the image\n a download link, which force the download of the image\n a twitter link, which you can share on twitter : the image will natively appeared in twitter\n a delete link, which allows you to delete the image anytime you want\n\nThe image is stored on <%= url_for('/')->to_abs %> for a delay which you can define.\n\nThe particularity of Lutim is that the image is encrypted and its usage is as most anonymous as possible. See the information page (<%= url_for('about')->to_abs %>) for more details.\nLicense: AGPLv3 (https://www.gnu.org/licenses/agpl-3.0.html)",
"launch_path": "<%= url_for('/') %>",
"icons": {
"32": "<%= url_for('/img/lutim32.png') %>",
"60": "<%= url_for('/img/lutim60.png') %>",
"90": "<%= url_for('/img/lutim90.png') %>",
"120": "<%= url_for('/img/lutim120.png') %>",
"128": "<%= url_for('/img/lutim128.png') %>",
"256": "<%= url_for('/img/lutim256.png') %>"
},
"developer": {
"name": "Lutim team !",
"url": "https://github.com/ldidry/lutim"
},
"default_locale": "en",
"locales": {
"fr": {
"description": "Envoyons cette image !\n\nCeci est une application de partage simple d'images qui utilise <%= url_for('/')->to_abs %> pour enregistrer les images. Une fois que vous avez envoyé une image, vous obtiendrez différents liens :\n un lien de visualisation, qui pointe directement sur l'image\n un lien de téléchargement, qui force le téléchargement de l'image\n un lien twitter, que vous pouvez partager sur twitter : l'image apparaîtra nativement dans twitter\n un lien de suppression, qui vous permet de supprimer l'image quand vous le souhaitez\n\nL'image est conservée sur <%= url_for('/')->to_abs %> pour un délai que vous pouvez définir.\n\nLa particularité de Lutim est que l'image est chiffrée et que son usage est aussi anonyme que possible. Voir la page d'information (<%= url_for('about')->to_abs %>) pour plus de détails.\nLicence : AGPLv3 (https://www.gnu.org/licenses/agpl-3.0.html)"
}
},
"activities": {
"share": {
"filters": {
"type": [ "image/*"]
},
"href": "<%= url_for('/') %>",
"disposition": "window"
}
},
"version": "0.1",
"chrome": { "navigation": true }
}

View File

@@ -1,64 +1,13 @@
% # vim:set sts=4 sw=4 ts=4 ft=html.epl expandtab:
%= javascript 'js/raphael.js'
%= javascript 'js/raphael.path.methods.js'
%= javascript 'js/jquery.simplegraph.js'
<h4><%= $total %><%=l 'pushed-images' %></h4>
<hr>
%= include 'data'
<h4>Uploaded files by days</h4>
<div id="evol-holder"></div>
<hr>
<h4>Evolution of total files</h4>
<div id="total-holder"></div>
<p><small><%=l 'graph-data-once-a-day' %></small></p>
<h4><%= $total %><%=l 'pushed-images' %></h4>
<%= link_to url_for('index') => ( class => "btn btn-primary btn-lg" ) => begin %><%=l 'back-to-index' %><% end%>
%= javascript begin
function graph(stats_data, stats_labels, stats_total) {
// Plot the data
// - Temperature Graph - adds colour, fill, and a minimum value for the y axis
$("#evol-holder").simplegraph(
stats_data,
stats_labels,
{
addHover: true,
penColor: "#f00",
fillUnderLine: true,
drawPoints: true,
width: document.body.offsetWidth - 50
}
)
$("#total-holder").simplegraph(
stats_total,
stats_labels,
{
addHover: true,
penColor: "#00f",
fillUnderLine: true,
drawPoints: true,
width: document.body.offsetWidth - 50
}
);
}
$(document).ready(function() {
// Get the data
var stats_labels = [], stats_data = [], stats_total = [];
$("#stats-data thead th").each(function () {
stats_labels.push($(this).html());
});
$("#stats-data tbody tr:first-child td").each(function () {
stats_data.push($(this).html());
});
$("#stats-data tbody tr:nth-child(2) td").each(function () {
stats_total.push($(this).html());
});
// Hide the data
$("#stats-data").hide();
graph(stats_data, stats_labels, stats_total);
$(window).resize(function() {
$("#evol-holder").empty();
$("#total-holder").empty();
graph(stats_data, stats_labels, stats_total);
});
});
% end

View File

@@ -1,20 +1,40 @@
% # vim:set sw=4 ts=4 sts=4 ft=html.epl expandtab:
% my $scheme = (defined(config('https')) && config('https')) ? 'https' : 'http';
% my $g = ($mimetype eq 'image/gif') ? 1 : 0;
<!DOCTYPE html>
<html style="height:100%;">
<html style="max-height:100%;">
<head>
<title>LUTIm</title>
<title>Lutim</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<link rel="icon" type="image/png" href="<%= url_for('/')->base->scheme($scheme) %>/img/favicon.png">
<meta name="twitter:card" content="photo">
% if (defined(config('tweet_card_via'))) {
<link rel="icon" type="image/png" href="<%= url_for('index')->to_abs() %>img/favicon.png">
<meta property="og:title" content="Lutim" />
<meta property="og:type" content="website" />
<meta property="og:url" content="<%= url_for('index')->to_abs().$short %>?t" />
<meta property="og:image" content="<%= url_for('index')->to_abs().$short %>" />
<meta property="og:image:url" content="<%= url_for('index')->to_abs().$short %>" />
<meta property="og:image:type" content="<%= $mimetype %>" />
<meta name="twitter:site" content="<%= config('tweet_card_via') %>">
<meta name="twitter:image:src" content="<%= url_for('index')->to_abs().$short %>">
% if ($g) {
<meta name="twitter:card" content="player">
<meta name="twitter:image" content="<%= url_for('index')->to_abs().$short %>">
<meta name="twitter:player" content="<%= url_for('index')->to_abs().$short.'?t' %>">
<meta name="twitter:title" content="<%= $filename %>">
<meta name="twitter:player:width" content="<%= $width %>">
<meta name="twitter:player:height" content="<%= $height %>">
%= asset 'freeze.js'
%= javascript begin
freezeframe_options = {
trigger_event: "click",
animation_play_duration: 60000
}
% end
% } else {
<meta name="twitter:card" content="photo">
% }
<meta name="twitter:image:src" content="<%= url_for('/')->base->scheme($scheme)->to_abs().'/'.$short %>">
</head>
<body style="height: 97%;">
<img style="max-width:100%; max-height:100%;" src="<%= url_for('/').$short %>" alt="<%= $filename %>">
<body<%= ($g) ? '' : ' style="height: 97%;"' %>>
<img<%= ' class="freezeframe"' if ($g) %> style="<%= 'max-' unless ($g) %>width:100%; max-height:100%;" src="<%= url_for('index')->to_abs().$short %><%= '.gif' if ($g) %>" alt="<%= $filename %>">
</body>
</html>

View File

@@ -38,117 +38,108 @@ my $d = Locale::gettext->domain("shutter-upload-plugins");
$d->dir( $ENV{'SHUTTER_INTL'} );
my %upload_plugin_info = (
'module' => "Lutim",
'url' => "http://lut.im/",
'registration' => "-",
'description' => $d->get( "Upload screenshots to lut.im" ),
'supports_anonymous_upload' => TRUE,
'supports_authorized_upload' => FALSE,
'module' => "Lutim",
'url' => "https://lut.im/",
'registration' => "-",
'name' => "Lutim",
'description' => "Upload screenshots to Lutim",
'supports_anonymous_upload' => TRUE,
'supports_authorized_upload' => FALSE,
'supports_oauth_upload' => FALSE,
);
binmode( STDOUT, ":utf8" );
if ( exists $upload_plugin_info{$ARGV[ 0 ]} ) {
print $upload_plugin_info{$ARGV[ 0 ]};
exit;
print $upload_plugin_info{$ARGV[ 0 ]};
exit;
}
###################################################
#don't touch this
sub new {
my $class = shift;
my $class = shift;
#call constructor of super class (host, debug_cparam, shutter_root, gettext_object, main_gtk_window, ua)
my $self = $class->SUPER::new( shift, shift, shift, shift, shift, shift );
#call constructor of super class (host, debug_cparam, shutter_root, gettext_object, main_gtk_window, ua)
my $self = $class->SUPER::new( shift, shift, shift, shift, shift, shift );
bless $self, $class;
return $self;
bless $self, $class;
return $self;
}
#load some custom modules here (or do other custom stuff)
sub init {
my $self = shift;
my $self = shift;
#do custom stuff here
use WWW::Mechanize;
use HTTP::Status;
use HTTP::Request::Common 'POST';
use JSON;
use LWP::UserAgent;
use HTTP::Request::Common;
$self->{_mech} = WWW::Mechanize->new( agent => "$self->{_ua}", timeout => 20 );
$self->{_http_status} = undef;
return TRUE;
return TRUE;
}
#handle
sub upload {
my ( $self, $upload_filename, $username, $password ) = @_;
my ( $self, $upload_filename, $username, $password ) = @_;
#store as object vars
$self->{_filename} = $upload_filename;
$self->{_username} = $username;
$self->{_password} = $password;
#store as object vars
$self->{_filename} = $upload_filename;
$self->{_username} = $username;
$self->{_password} = $password;
my $filesize = -s $upload_filename;
my $max_filesize = 15360000;
if ( $filesize > $max_filesize ) {
$self->{_links}{'status'} = 998;
$self->{_links}{'max_filesize'} = sprintf( "%.2f", $max_filesize / 1024 ) . " KB";
return %{ $self->{_links} };
}
utf8::encode $upload_filename;
utf8::encode $password;
utf8::encode $username;
utf8::encode $upload_filename;
utf8::encode $password;
utf8::encode $username;
my $json = JSON->new();
eval{
my $browser = LWP::UserAgent->new(
'timeout' => 20,
'keep_alive' => 10,
'env_proxy' => 1,
);
my $url = "http://lut.im";
$self->{_mech}->get($url);
$self->{_http_status} = $self->{_mech}->status();
if ( is_success( $self->{_http_status} ) ) {
#upload the file
eval{
$self->{_mech}->request(POST $url,
Content_Type => 'form-data',
Content => [
file => [$upload_filename],
format => 'json'
],
);
my $url = 'https://lut.im/';
my $request = HTTP::Request::Common::POST(
$url,
Content_Type => 'multipart/form-data',
Content => [
file => [$upload_filename],
format => 'json'
]
);
$self->{_http_status} = $self->{_mech}->status();
my $response = $browser->request($request);
if ( is_success( $self->{_http_status} ) ) {
my $html_file = $self->{_mech}->content;
if ($response->is_success) {
my $hash = $json->decode($response->decoded_content);
my @links = $html_file =~ m/"short":"([^"]+)"/;
#my @links = $html_file =~ m{ <textarea>(.*)</textarea> }gx;
if ($hash->{success}) {
my $link = $url.$hash->{msg}->{short};
$self->{_links}->{'view_image'} = $link;
$self->{_links}->{'download_link'} = $link.'?dl';
$self->{_links}->{'twitter_link'} = $link.'?t';
$self->{_links}->{'delete_link'} = $url.'d/'.$hash->{msg}->{real_short}.'/'.$hash->{msg}->{token};
$self->{_links}{'view_image'} = $url.'/'.$links[0];
$self->{_links}{'download_link'} = $url.'/'.$links[0].'?dl';
$self->{_links}{'twitter_link'} = $url.'/'.$links[0].'?t';
#set success code (200)
$self->{_links}{'status'} = 200;
} else {
$self->{_links}{'status'} = $hash->{msg}->{msg};
if ( $self->{_debug} ) {
print "The following links were returned by http://lut.im:\n";
print $self->{_links}{'view_image'},"\n";
print $self->{_links}{'download_link'},"\n";
print $self->{_links}{'twitter_link'},"\n";
}
$self->{_links}{'status'} = $self->{_http_status};
} else {
$self->{_links}{'status'} = $self->{_http_status};
}
}else{
$self->{_links}{'status'} = $self->{_http_status};
}
};
if($@){
$self->{_links}{'status'} = $@;
}
return %{ $self->{_links} };
}
} else {
$self->{_links}{'status'} = $response->status_line;
}
};
if($@){
$self->{_links}{'status'} = $@;
}
#and return links
return %{ $self->{_links} };
}
1;

3
utilities/lutim.default Normal file
View File

@@ -0,0 +1,3 @@
# LDIR is the path where you installed Lutim
# It has to end with a final /
LDIR=/var/lutim/

191
utilities/lutim.init Executable file
View File

@@ -0,0 +1,191 @@
#!/bin/sh
# vim: set ts=4 sw=4 sts=4 tw=0:
# vim: set expandtab:
### BEGIN INIT INFO
# Provides: lutim
# Required-Start: $local_fs $remote_fs $network $syslog
# Required-Stop: $local_fs $remote_fs $network $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts lutim with hypnotoad
# Description: starts lutim with hypnotoad
### END INIT INFO
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=script/lutim
NAME=lutim
DESC=lutim
if [ -f "/etc/default/lutim" ]
then
. /etc/default/lutim
if [ -z $LDIR ]
then
echo "LDIR variable is empty, please fill it in /etc/default/lutim"
exit 0
fi
else
echo "Missing /etc/default/lutim file"
exit 0
fi
if [ ! -f "$LDIR$DAEMON" ]
then
echo "Missing $LDIR$DAEMON file"
exit 0
fi
set -e
. /lib/lsb/init-functions
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
cd $LDIR
carton exec hypnotoad $DAEMON >/dev/null 2>&1
return "$?"
}
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
cd $LDIR
carton exec hypnotoad -s $DAEMON >/dev/null 2>&1
return "$?"
}
do_status()
{
cd $LDIR
if [ -f "script/hypnotoad.pid" ]
then
pgrep -lf $DAEMON >/dev/null 2>&1
if [ "$?" = "0" ]; then
log_progress_msg "$NAME is running"
else
log_progress_msg "$NAME is NOT running"
fi
else
log_progress_msg "$NAME is NOT running"
fi
}
case "$1" in
start)
log_daemon_msg "Starting $NAME"
cd $LDIR
if [ -f "script/hypnotoad.pid" ]
then
pgrep -lf $DAEMON >/dev/null 2>&1
if [ "$?" = "0" ]
then
log_progress_msg "$NAME is already running. Unable to start."
log_end_msg 1;
else
do_start
case "$?" in
0|1)
log_progress_msg "done"
log_end_msg 0
;;
2)
log_progress_msg "failed"
log_end_msg 1
;;
esac
fi
else
do_start
case "$?" in
0|1)
log_progress_msg "done"
log_end_msg 0
;;
2)
log_progress_msg "failed"
log_end_msg 1
;;
esac
fi
;;
stop)
log_daemon_msg "Stopping $NAME"
cd $LDIR
if [ -f "script/hypnotoad.pid" ]
then
pgrep -lf $DAEMON >/dev/null 2>&1
if [ "$?" = "0" ]; then
do_stop
case "$?" in
0|1)
log_progress_msg "done"
log_end_msg 0
;;
*)
log_progress_msg "failed"
log_end_msg 1
;;
esac
else
log_progress_msg "$NAME is NOT running. Unable to stop"
log_end_msg 1
fi
else
log_progress_msg "$NAME is NOT running. Unable to stop"
log_end_msg 1
fi
;;
status)
log_daemon_msg "Checking $NAME status"
do_status
log_end_msg 0
;;
reload)
log_daemon_msg "Reloading $NAME"
do_start
case "$?" in
0|1)
log_progress_msg "done"
log_end_msg 0
;;
2)
log_progress_msg "failed"
log_end_msg 1
;;
esac
;;
restart)
log_daemon_msg "Restarting $NAME"
do_stop
sleep 1
do_start
case "$?" in
0|1)
log_progress_msg "done"
log_end_msg 0
;;
2)
log_progress_msg "failed";
log_end_msg 1
;;
esac
;;
*)
echo "Usage: $0 {start|stop|status|reload|restart}" >&2
exit 3
;;
esac
exit 0