Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ ENV/
# Rope project settings
.ropeproject

# Local development files
localconfig.env.py
config.py
data.db

Expand All @@ -97,3 +99,6 @@ data.db

# mypy
.mypy_cache

# vim swap files
*.swp
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,18 @@ an Openshift Origin cluster.
GALLERY_S3_BUCKET_ID = $s3BucketID
GALLERY_S3_SECRET_KEY = $s3SecretKey
```

## Local Development
Below are instructions for running gallery locally. It assumes that you have already forked and cloned this repository onto your local machine, and have Python3 installed.

1. Change the line in `__init__.py` that sets the config file from `config.env.py` to `localconfig.env.py`.

2. Copy `localconfig-sample.env.py` to `localconfig.env.py`, get gallery dev secrets from an RTP, and fill in.

3. Create a [virtual environment](https://docs.python.org/3/library/venv.html), `python3 -m venv venv`

4. `source venv/bin/activate` to enter the virtual environment

5. `pip install -r requirements.txt`

6. `python3 wsgi.py`
103 changes: 75 additions & 28 deletions gallery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from flask import send_from_directory
from flask import abort
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import or_
from sqlalchemy.sql import func as sql_func
from sqlalchemy.orm import load_only
from flask_pyoidc.flask_pyoidc import OIDCAuthentication
Expand Down Expand Up @@ -213,7 +214,7 @@ def upload_file(auth_dict: Optional[Dict[str, Any]] = None):
upload_status['error'] = errors
upload_status['success'] = success

refresh_thumbnail()
refresh_default_thumbnails()
# actually redirect to URL
# change from FORM post to AJAX maybe?
return jsonify(upload_status)
Expand Down Expand Up @@ -246,6 +247,9 @@ def view_mkdir(auth_dict: Optional[Dict[str, Any]] = None):
@auth.oidc_auth('default')
@gallery_auth
def view_jumpdir(auth_dict: Optional[Dict[str, Any]] = None):
gallery_lockdown = util.get_lockdown_status()
if gallery_lockdown and (not auth_dict['is_eboard'] and not auth_dict['is_rtp']):
abort(405)
return render_template("jumpdir.html",
auth_dict=auth_dict)

Expand Down Expand Up @@ -311,7 +315,7 @@ def api_mkdir(
@app.cli.command()
def refresh_thumbnails():
click.echo("Refreshing thumbnails")
refresh_thumbnail()
refresh_default_thumbnails()


@app.cli.command()
Expand Down Expand Up @@ -386,24 +390,25 @@ def add_file(file_name: str, path: str, dir_id: str, description: str, owner: st
return file_model


def refresh_thumbnail():
def refresh_thumbnail_helper(dir_model: Directory) -> str:
dir_children = [d for d in Directory.query.filter(Directory.parent == dir_model.id).all()]
file_children = [f for f in File.query.filter(File.parent == dir_model.id).all()]
for file in file_children:
if file.thumbnail_uuid != DEFAULT_THUMBNAIL_NAME:
return file.thumbnail_uuid
for d in dir_children:
if d.thumbnail_uuid != DEFAULT_THUMBNAIL_NAME:
return d.thumbnail_uuid
# WE HAVE TO GO DEEPER (inception noise)
for d in dir_children:
# TODO: Switch to iterative tree walk using a queue to avoid
# recursion issues with super large directory structures
return refresh_thumbnail_helper(d)
# No thumbnail found
return DEFAULT_THUMBNAIL_NAME

def refresh_directory_thumbnail(dir_model: Directory) -> str:
dir_children = [d for d in Directory.query.filter(Directory.parent == dir_model.id).all()]
file_children = [f for f in File.query.filter(File.parent == dir_model.id).all()]
for file in file_children:
if file.thumbnail_uuid != DEFAULT_THUMBNAIL_NAME and not file.hidden:
return file.thumbnail_uuid
for d in dir_children:
if d.thumbnail_uuid != DEFAULT_THUMBNAIL_NAME:
return d.thumbnail_uuid
# WE HAVE TO GO DEEPER (inception noise)
for d in dir_children:
Comment thread
mxmeinhold marked this conversation as resolved.
# TODO: Switch to iterative tree walk using a queue to avoid
# recursion issues with super large directory structures
return refresh_directory_thumbnail(d)
# No thumbnail found
return DEFAULT_THUMBNAIL_NAME


def refresh_default_thumbnails():
missing_thumbnails = File.query.filter(File.thumbnail_uuid == DEFAULT_THUMBNAIL_NAME).all()
for file_model in missing_thumbnails:
dir_path = get_full_dir_path(file_model.parent)
Expand All @@ -416,7 +421,7 @@ def refresh_thumbnail_helper(dir_model: Directory) -> str:

missing_thumbnails = Directory.query.filter(Directory.thumbnail_uuid == DEFAULT_THUMBNAIL_NAME).all()
for dir_model in missing_thumbnails:
dir_model.thumbnail_uuid = refresh_thumbnail_helper(dir_model)
dir_model.thumbnail_uuid = refresh_directory_thumbnail(dir_model)
db.session.flush()
db.session.commit()
db.session.refresh(dir_model)
Expand Down Expand Up @@ -476,6 +481,13 @@ def hide_file(file_id: int, auth_dict: Optional[Dict[str, Any]] = None):
return "Permission denied", 403

file_model.hidden = True

# Remove image from thumbnails
dirs = Directory.query.filter(or_(Directory.thumbnail_uuid == file_model.thumbnail_uuid, \
Directory.thumbnail_uuid == file_model.thumbnail_uuid[:-4]))
for d in dirs:
d.thumbnail_uuid = refresh_directory_thumbnail(d)

db.session.flush()
db.session.commit()

Expand All @@ -497,6 +509,12 @@ def show_file(file_id: int, auth_dict: Optional[Dict[str, Any]] = None):
return "Permission denied", 403

file_model.hidden = False

# Add image as directory thumbnail
parent_model = Directory.query.filter(Directory.id == file_model.parent).first()
if parent_model.thumbnail_uuid == DEFAULT_THUMBNAIL_NAME:
parent_model.thumbnail_uuid = refresh_directory_thumbnail(parent_model)

db.session.flush()
db.session.commit()

Expand Down Expand Up @@ -718,7 +736,12 @@ def tag_file(file_id: int):

@app.route("/api/file/get/<int:file_id>")
@auth.oidc_auth('default')
def display_file(file_id: int):
@gallery_auth
def display_file(file_id: int, auth_dict: Optional[Dict[str, Any]] = None):
gallery_lockdown = util.get_lockdown_status()
if gallery_lockdown and (not auth_dict['is_eboard'] and not auth_dict['is_rtp']):
abort(405)

file_model = File.query.filter(File.id == file_id).first()

if file_model is None:
Expand All @@ -730,7 +753,12 @@ def display_file(file_id: int):

@app.route("/api/thumbnail/get/<int:file_id>")
@auth.oidc_auth('default')
def display_thumbnail(file_id: int):
@gallery_auth
def display_thumbnail(file_id: int, auth_dict: Optional[Dict[str, Any]] = None):
gallery_lockdown = util.get_lockdown_status()
if gallery_lockdown and (not auth_dict['is_eboard'] and not auth_dict['is_rtp']):
abort(405)

file_model = File.query.filter(File.id == file_id).first()

link = storage_interface.get_link("thumbnails/{}".format(file_model.s3_id))
Expand All @@ -739,7 +767,12 @@ def display_thumbnail(file_id: int):

@app.route("/api/thumbnail/get/dir/<int:dir_id>")
@auth.oidc_auth('default')
def display_dir_thumbnail(dir_id: int):
@gallery_auth
def display_dir_thumbnail(dir_id: int, auth_dict: Optional[Dict[str, Any]] = None):
gallery_lockdown = util.get_lockdown_status()
if gallery_lockdown and (not auth_dict['is_eboard'] and not auth_dict['is_rtp']):
abort(405)

dir_model = Directory.query.filter(Directory.id == dir_id).first()

thumbnail_uuid = dir_model.thumbnail_uuid
Expand Down Expand Up @@ -795,7 +828,11 @@ def get_supported_mimetypes():

@app.route("/api/get_dir_tree")
@auth.oidc_auth('default')
def get_dir_tree(internal: bool = False):
@gallery_auth
def get_dir_tree(internal: bool = False, auth_dict: Optional[Dict[str, Any]] = None):
gallery_lockdown = util.get_lockdown_status()
if gallery_lockdown and (not auth_dict['is_eboard'] and not auth_dict['is_rtp']):
abort(405)

# TODO: Convert to iterative tree traversal using a queue to avoid
# recursion issues with large directory structures
Expand Down Expand Up @@ -828,7 +865,12 @@ def get_dir_children(dir_id: int) -> Any:

@app.route("/api/directory/get/<int:dir_id>")
@auth.oidc_auth('default')
def display_files(dir_id: int, internal: bool = False):
@gallery_auth
def display_files(dir_id: int, internal: bool = False, auth_dict: Optional[Dict[str, Any]] = None):
gallery_lockdown = util.get_lockdown_status()
if gallery_lockdown and (not auth_dict['is_eboard'] and not auth_dict['is_rtp']):
abort(405)

file_list = [("File", f) for f in File.query.filter(File.parent == dir_id).all()]
dir_list = [("Directory", d) for d in Directory.query.filter(Directory.parent == dir_id).all()]

Expand Down Expand Up @@ -980,7 +1022,12 @@ def view_filtered(auth_dict: Optional[Dict[str, Any]] = None):

@app.route("/api/memberlist")
@auth.oidc_auth('default')
def get_member_list():
@gallery_auth
def get_member_list(auth_dict: Optional[Dict[str, Any]] = None):
gallery_lockdown = util.get_lockdown_status()
if gallery_lockdown and (not auth_dict['is_eboard'] and not auth_dict['is_rtp']):
abort(405)

return jsonify(ldap.get_members())


Expand All @@ -999,7 +1046,7 @@ def route_errors(error: Any, auth_dict: Optional[Dict[str, Any]] = None):
if code == 404:
error_desc = "Page Not Found"
elif code == 405:
error_desc = "Page Not Available"
error_desc = "Gallery is currently unavailable"
else:
error_desc = type(error).__name__

Expand Down
2 changes: 1 addition & 1 deletion gallery/_version.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from os import environ as env

__version__ = "2.1.0"
__version__ = "2.1.1"

BUILD_REFERENCE = env.get("OPENSHIFT_BUILD_REFERENCE")
COMMIT_HASH = env.get("OPENSHIFT_BUILD_COMMIT")
Expand Down
6 changes: 5 additions & 1 deletion gallery/file_modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ def generate_thumbnail(self):
from gallery.file_modules.ogg import OggFile
from gallery.file_modules.pdf import PDFFile
from gallery.file_modules.txt import TXTFile
from gallery.file_modules.mp3 import MP3File
from gallery.file_modules.wav import WAVFile

file_mimetype_relation = {
"image/jpeg": JPEGFile,
Expand All @@ -77,7 +79,9 @@ def generate_thumbnail(self):
"video/webm": WebMFile,
"video/ogg": OggFile,
"application/pdf": PDFFile,
"text/plain": TXTFile
"text/plain": TXTFile,
"audio/mpeg": MP3File,
"audio/x-wav": WAVFile
}


Expand Down
18 changes: 18 additions & 0 deletions gallery/file_modules/mp3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import os
from wand.image import Image

from gallery.file_modules import FileModule
from gallery.util import hash_file

class MP3File(FileModule):
def __init__(self, file_path, dir_path):
FileModule.__init__(self, file_path, dir_path)
self.mime_type = "audio/mpeg"

self.generate_thumbnail()

def generate_thumbnail(self):
self.thumbnail_uuid = hash_file(self.file_path) + ".jpg"

with Image(filename="thumbnails/reedphoto.jpg") as bg:
bg.save(filename=os.path.join(self.dir_path, self.thumbnail_uuid))
18 changes: 18 additions & 0 deletions gallery/file_modules/wav.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import os
from wand.image import Image

from gallery.file_modules import FileModule
from gallery.util import hash_file

class WAVFile(FileModule):
def __init__(self, file_path, dir_path):
FileModule.__init__(self, file_path, dir_path)
self.mime_type = "audio/x-wav"

self.generate_thumbnail()

def generate_thumbnail(self):
self.thumbnail_uuid = hash_file(self.file_path) + ".jpg"

with Image(filename="thumbnails/reedphoto.jpg") as bg:
bg.save(filename=os.path.join(self.dir_path, self.thumbnail_uuid))
1 change: 1 addition & 0 deletions gallery/static/images/material_lock.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion gallery/static/js/gallery.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ function hideFile() {

function showFile() {
$('#show').modal('show');
$('#show button[id^="hide"]').click(function(e) {
$('#show button[id^="show"]').click(function(e) {
e.preventDefault();
var this_id = $('#show button[id^="show"]').attr('id').substr($('#show button[id^="show"]').attr('id').indexOf("-") + 1);
$.ajax({
Expand Down
13 changes: 9 additions & 4 deletions gallery/templates/errors.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
{% block body %}
<div class="container error-page align-center">
<div class="col-xs-12">
<img src="/static/images/material_attention.svg" alt="Attention!">
<h1>Oops!</h1>
<h2>Something has gone terribly wrong!</h2>
<h3>{{ error }}</h3>
{% if error_code == 405 %}
<img src="/static/images/material_lock.svg" alt="Locked" />
<h1>{{ error }}</h1>
{% else %}
<img src="/static/images/material_attention.svg" alt="Attention" />
<h1>Oops!</h1>
<h2>Something has gone terribly wrong!</h2>
<h3>{{ error }}</h3>
{% endif %}
</div>
</div>
{% endblock %}
6 changes: 6 additions & 0 deletions gallery/templates/view_file.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@

{% elif file.mimetype == "application/pdf" or file.mimetype == "text/plain" %}
<embed id="file-content" src="/api/file/get/{{file.id}}">

{% elif file.mimetype.split('/')[0] == "audio" %}
<audio controls>
<source src="/api/file/get/{{file.id}}">
</audio>

{% else %}
Text Data
{% endif %}
Expand Down
27 changes: 27 additions & 0 deletions localconfig-sample.env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import os

# Flask config
DEBUG=False
IP=os.environ.get('GALLERY_IP', 'localhost')
PORT=os.environ.get('GALLERY_PORT', '6969')
SERVER_NAME = os.environ.get('GALLERY_SERVER_NAME', 'localhost:6969')
SECRET_KEY = os.environ.get('GALLERY_SECRET_KEY', '')

# LDAP config
LDAP_BIND_DN=os.environ.get('GALLERY_LDAP_BIND_DN', '')
LDAP_BIND_PW=os.environ.get('GALLERY_LDAP_BIND_PW', '')

# OpenID Connect SSO config
OIDC_ISSUER = os.environ.get('GALLERY_OIDC_ISSUER', 'https://sso.csh.rit.edu/auth/realms/csh')
OIDC_CLIENT_ID = os.environ.get('GALLERY_OIDC_CLIENT_ID', 'gallery-dev')
OIDC_CLIENT_SECRET = os.environ.get('GALLERY_OIDC_CLIENT_SECRET', '')

SQLALCHEMY_DATABASE_URI = os.environ.get(
'GALLERY_DATABASE_URI',
'postgresql://DB_USERNAME:DB_PASSWORD@postgres.csh.rit.edu/gallery-dev')
SQLALCHEMY_TRACK_MODIFICATIONS = False

S3_URI = os.environ.get('GALLERY_S3_URI', 'https://s3.csh.rit.edu')
S3_ACCESS_ID = os.environ.get('GALLERY_S3_ACCESS_ID','')
S3_SECRET_KEY = os.environ.get('GALLERY_S3_SECRET_KEY','')
S3_BUCKET_ID = os.environ.get('GALLERY_S3_BUCKET_ID','gallery-dev')
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Flask==1.0.2
Flask-pyoidc==2.0.0
csh_ldap==2.1.1
csh_ldap~=2.2.0
addict==2.2.0
flask_sqlalchemy==2.3.2
flask_migrate==2.3.1
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ summary = Python Photo Gallery Written in Flask
url = "https://github.com/ComputerScienceHouse/gallery"
description-file = README.md
license = MIT
version = 2.0.5
version = 2.1.1
classifier =
Natural Language :: English
Operating System :: POSIX :: Linux