gallerist 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/CONTRIBUTING.md +53 -0
- data/Gemfile +8 -0
- data/LICENSE +25 -0
- data/README.md +57 -0
- data/Rakefile +16 -0
- data/assets/stylesheets/main.scss +17 -0
- data/bin/gallerist +59 -0
- data/config.ru +21 -0
- data/gallerist.gemspec +32 -0
- data/lib/gallerist.rb +46 -0
- data/lib/gallerist/app.rb +230 -0
- data/lib/gallerist/errors.rb +12 -0
- data/lib/gallerist/helpers.rb +72 -0
- data/lib/gallerist/library.rb +100 -0
- data/lib/gallerist/middleware/raise_warmup_exceptions.rb +24 -0
- data/lib/gallerist/middleware/show_exceptions.rb +40 -0
- data/lib/gallerist/models/admin_data.rb +10 -0
- data/lib/gallerist/models/album.rb +30 -0
- data/lib/gallerist/models/album_photo.rb +20 -0
- data/lib/gallerist/models/base_model.rb +32 -0
- data/lib/gallerist/models/image_proxies_model.rb +11 -0
- data/lib/gallerist/models/image_proxy_state.rb +22 -0
- data/lib/gallerist/models/master.rb +25 -0
- data/lib/gallerist/models/model_resource.rb +22 -0
- data/lib/gallerist/models/person.rb +46 -0
- data/lib/gallerist/models/person_model.rb +10 -0
- data/lib/gallerist/models/person_photo.rb +39 -0
- data/lib/gallerist/models/photo.rb +100 -0
- data/lib/gallerist/models/tag.rb +31 -0
- data/lib/gallerist/models/tag_photo.rb +20 -0
- data/lib/rack/handler/unicorn.rb +34 -0
- data/views/500.erb +5 -0
- data/views/album.erb +9 -0
- data/views/index.erb +24 -0
- data/views/layout.erb +34 -0
- data/views/partials/thumbnail.erb +13 -0
- data/views/person.erb +9 -0
- data/views/photos.erb +9 -0
- data/views/tag.erb +9 -0
- metadata +183 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015, Sebastian Staudt
|
5
|
+
|
6
|
+
class Gallerist::LibraryInUseError < StandardError
|
7
|
+
|
8
|
+
def initialize(library_path)
|
9
|
+
super "The library '%s' is currently in use." % [ library_path ]
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015, Sebastian Staudt
|
5
|
+
|
6
|
+
module Gallerist::Helpers
|
7
|
+
|
8
|
+
def library
|
9
|
+
settings.library
|
10
|
+
end
|
11
|
+
|
12
|
+
def link_to(obj, classes = nil)
|
13
|
+
url = url_for obj
|
14
|
+
current = (url == request.path)
|
15
|
+
|
16
|
+
classes = [ classes ].compact
|
17
|
+
|
18
|
+
case obj
|
19
|
+
when Gallerist::Album
|
20
|
+
link = obj.name
|
21
|
+
when Gallerist::Person
|
22
|
+
classes << 'label' << 'tag'
|
23
|
+
classes << (current ? 'label-info' : 'label-primary')
|
24
|
+
link = obj.name
|
25
|
+
when Gallerist::Photo
|
26
|
+
classes << 'thumbnail'
|
27
|
+
link = '<img src="/thumbs/%s">' % [ obj.id ]
|
28
|
+
when Gallerist::Tag
|
29
|
+
classes << 'label' << 'tag'
|
30
|
+
classes << (current ? 'label-info' : 'label-primary')
|
31
|
+
link = obj.name
|
32
|
+
end
|
33
|
+
|
34
|
+
classes = classes.empty? ? '' : ' class="%s"' % [ classes.join(' ') ]
|
35
|
+
|
36
|
+
if current
|
37
|
+
'<span%s>%s</span>' % [ classes, link ]
|
38
|
+
else
|
39
|
+
'<a href="%s"%s>%s</a>' % [ url_for(obj), classes, link ]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def navbar
|
44
|
+
@navbar
|
45
|
+
end
|
46
|
+
|
47
|
+
def partial(partial, *options)
|
48
|
+
erb :"partials/#{partial}", *options
|
49
|
+
end
|
50
|
+
|
51
|
+
def route_exists(url)
|
52
|
+
settings.routes['GET'].map(&:first).any? { |route| route =~ url }
|
53
|
+
end
|
54
|
+
|
55
|
+
def title
|
56
|
+
'%s – Gallerist' % [ @title ]
|
57
|
+
end
|
58
|
+
|
59
|
+
def url_for(obj)
|
60
|
+
case obj
|
61
|
+
when Gallerist::Album
|
62
|
+
'/albums/%s' % [ obj.id ]
|
63
|
+
when Gallerist::Person
|
64
|
+
'/persons/%s' % [ obj.id ]
|
65
|
+
when Gallerist::Photo
|
66
|
+
'/photos/%s' % [ obj.id ]
|
67
|
+
when Gallerist::Tag
|
68
|
+
'/tags/%s' % [ URI.encode(obj.simple_name) ]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015, Sebastian Staudt
|
5
|
+
|
6
|
+
require 'sqlite3'
|
7
|
+
|
8
|
+
class Gallerist::Library
|
9
|
+
|
10
|
+
attr_reader :app
|
11
|
+
attr_reader :name
|
12
|
+
attr_reader :path
|
13
|
+
|
14
|
+
def initialize(library_path)
|
15
|
+
@name = File.basename(library_path).rpartition('.').first
|
16
|
+
@path = File.expand_path library_path
|
17
|
+
@db_path = File.dirname File.realpath(File.join @path, 'Database', 'Library.apdb')
|
18
|
+
end
|
19
|
+
|
20
|
+
def albums
|
21
|
+
Gallerist::Album.all
|
22
|
+
end
|
23
|
+
|
24
|
+
def app_id
|
25
|
+
@app_id ||= Gallerist::AdminData.
|
26
|
+
where(propertyArea: 'database', propertyName: 'applicationIdentifier').
|
27
|
+
pluck(:propertyValue).first
|
28
|
+
end
|
29
|
+
|
30
|
+
def db_path
|
31
|
+
@temp_path || @db_path
|
32
|
+
end
|
33
|
+
|
34
|
+
def copy_base_db
|
35
|
+
@temp_path = Dir.mktmpdir 'gallerist'
|
36
|
+
temp_path = @temp_path.dup
|
37
|
+
at_exit { FileUtils.rm_rf temp_path }
|
38
|
+
|
39
|
+
copy_tmp_db 'Library.apdb'
|
40
|
+
end
|
41
|
+
|
42
|
+
def copy_extra_dbs
|
43
|
+
copy_tmp_db 'ImageProxies.apdb'
|
44
|
+
|
45
|
+
if iphoto?
|
46
|
+
copy_tmp_db 'Faces.db'
|
47
|
+
else
|
48
|
+
copy_tmp_db 'Person.db'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def copy_tmp_db(db_name)
|
53
|
+
source_path = File.join @db_path, db_name
|
54
|
+
dest_path = File.join @temp_path, db_name
|
55
|
+
|
56
|
+
db = SQLite3::Database.new source_path
|
57
|
+
db.transaction :immediate do |_|
|
58
|
+
FileUtils.cp source_path, dest_path, preserve: true
|
59
|
+
end
|
60
|
+
ensure
|
61
|
+
db.close unless db.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
def iphoto?
|
65
|
+
app_id == 'com.apple.iPhoto'
|
66
|
+
end
|
67
|
+
|
68
|
+
def tags
|
69
|
+
Gallerist::Tag.all
|
70
|
+
end
|
71
|
+
|
72
|
+
def library_db
|
73
|
+
File.join db_path, 'Library.apdb'
|
74
|
+
end
|
75
|
+
|
76
|
+
def image_proxies_db
|
77
|
+
File.join db_path, 'ImageProxies.apdb'
|
78
|
+
end
|
79
|
+
|
80
|
+
def inspect
|
81
|
+
"#<%s path='%s'>" % [ self.class, path ]
|
82
|
+
end
|
83
|
+
|
84
|
+
def person_db
|
85
|
+
File.join db_path, iphoto? ? 'Faces.db' : 'Person.db'
|
86
|
+
end
|
87
|
+
|
88
|
+
def persons
|
89
|
+
Gallerist::Person.all
|
90
|
+
end
|
91
|
+
|
92
|
+
def photos
|
93
|
+
Gallerist::Photo.all
|
94
|
+
end
|
95
|
+
|
96
|
+
def type
|
97
|
+
iphoto? ? :iphoto : :photos
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015, Sebastian Staudt
|
5
|
+
|
6
|
+
class Gallerist::RaiseWarmupExceptions
|
7
|
+
|
8
|
+
def initialize(app)
|
9
|
+
@app = app
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
@app.call env
|
14
|
+
rescue Exception
|
15
|
+
if env['rack.warmup']
|
16
|
+
if $!.is_a? SQLite3::BusyException
|
17
|
+
raise Gallerist::LibraryInUseError, Gallerist::App.library_path
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
raise $!
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015, Sebastian Staudt
|
5
|
+
|
6
|
+
# Monkey-patch for Sinatra 1.4 and Rack 1.6
|
7
|
+
#
|
8
|
+
# See https://github.com/sinatra/sinatra/issues/951
|
9
|
+
class Gallerist::ShowExceptions < Sinatra::ShowExceptions
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
@app.call(env)
|
13
|
+
rescue Exception => e
|
14
|
+
raise if env['rack.warmup']
|
15
|
+
|
16
|
+
errors, env["rack.errors"] = env["rack.errors"], @@eats_errors
|
17
|
+
|
18
|
+
if prefers_plain_text?(env)
|
19
|
+
content_type = "text/plain"
|
20
|
+
exception_string = dump_exception(e)
|
21
|
+
else
|
22
|
+
content_type = "text/html"
|
23
|
+
exception_string = pretty(env, e)
|
24
|
+
end
|
25
|
+
|
26
|
+
env["rack.errors"] = errors
|
27
|
+
|
28
|
+
# Post 893a2c50 in rack/rack, the #pretty method above, implemented in
|
29
|
+
# Rack::ShowExceptions, returns a String instead of an array.
|
30
|
+
body = Array(exception_string)
|
31
|
+
|
32
|
+
[
|
33
|
+
500,
|
34
|
+
{"Content-Type" => content_type,
|
35
|
+
"Content-Length" => Rack::Utils.bytesize(body.join).to_s},
|
36
|
+
body
|
37
|
+
]
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015, Sebastian Staudt
|
5
|
+
|
6
|
+
class Gallerist::Album < Gallerist::BaseModel
|
7
|
+
|
8
|
+
self.table_name = 'RKAlbum'
|
9
|
+
|
10
|
+
has_many :album_photos, primary_key: 'modelId', foreign_key: 'albumId'
|
11
|
+
has_many :photos, through: :album_photos
|
12
|
+
|
13
|
+
alias_attribute :date, :createDate
|
14
|
+
alias_attribute :hidden, :isHidden
|
15
|
+
alias_attribute :trashed, :isInTrash
|
16
|
+
|
17
|
+
default_scope { select(:createDate, :isHidden, :isInTrash, :modelId, :name) }
|
18
|
+
scope :nonempty, -> {
|
19
|
+
joins(:album_photos).
|
20
|
+
select('count(RKAlbumVersion.albumId) as photos_count').
|
21
|
+
group('RKAlbumVersion.albumId').
|
22
|
+
having('photos_count > 0')
|
23
|
+
}
|
24
|
+
scope :visible, -> { where(trashed: false, hidden: false) }
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
"#<%s id=%d uuid=%s name='%s'>" % [ self.class, id, uuid, name ]
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015, Sebastian Staudt
|
5
|
+
|
6
|
+
class Gallerist::AlbumPhoto < Gallerist::BaseModel
|
7
|
+
|
8
|
+
self.table_name = 'RKAlbumVersion'
|
9
|
+
|
10
|
+
has_one :album, primary_key: 'albumId', foreign_key: 'modelId'
|
11
|
+
has_one :photo, primary_key: 'versionId', foreign_key: 'modelId'
|
12
|
+
|
13
|
+
alias_attribute :album_id, :albumId
|
14
|
+
alias_attribute :photo_id, :versionId
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
'#<%s album_id=%d photo_id=%d>' % [ self.class, album_id, photo_id ]
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015, Sebastian Staudt
|
5
|
+
|
6
|
+
class Gallerist::BaseModel < ActiveRecord::Base
|
7
|
+
|
8
|
+
self.abstract_class = true
|
9
|
+
self.inheritance_column = nil
|
10
|
+
self.primary_key = 'modelId'
|
11
|
+
|
12
|
+
def self.iphoto(&block)
|
13
|
+
store_setup :iphoto, &block
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.photos(&block)
|
17
|
+
store_setup :photos, &block
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.setup_for(app)
|
21
|
+
((@setups || {})[app] || []).each do |setup|
|
22
|
+
setup.call
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.store_setup(app, &block)
|
27
|
+
@setups ||= {}
|
28
|
+
@setups[app] ||= []
|
29
|
+
@setups[app] << block
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015, Sebastian Staudt
|
5
|
+
|
6
|
+
|
7
|
+
class Gallerist::ImageProxiesModel < Gallerist::BaseModel
|
8
|
+
|
9
|
+
self.abstract_class = true
|
10
|
+
|
11
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015, Sebastian Staudt
|
5
|
+
|
6
|
+
class Gallerist::ImageProxyState < Gallerist::ImageProxiesModel
|
7
|
+
|
8
|
+
self.table_name = 'RKImageProxyState'
|
9
|
+
|
10
|
+
belongs_to :photo
|
11
|
+
|
12
|
+
alias_attribute :photo_id, :versionId
|
13
|
+
alias_attribute :small_thumbnail_path, :miniThumbnailPath
|
14
|
+
alias_attribute :thumbnail_available, :thumbnailsCurrent
|
15
|
+
|
16
|
+
default_scope { select(:miniThumbnailPath, :modelId, :thumbnailsCurrent, :versionId) }
|
17
|
+
|
18
|
+
def inspect
|
19
|
+
'#<%s id=%d photo=%s>' % [ self.class, id, photo_id ]
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015, Sebastian Staudt
|
5
|
+
|
6
|
+
class Gallerist::Master < Gallerist::BaseModel
|
7
|
+
|
8
|
+
self.table_name = 'RKMaster'
|
9
|
+
|
10
|
+
alias_attribute :file_name, :fileName
|
11
|
+
alias_attribute :path, :imagePath
|
12
|
+
|
13
|
+
photos do
|
14
|
+
default_scope { select(:fileName, :imagePath, :modelId, :uuid) }
|
15
|
+
end
|
16
|
+
|
17
|
+
iphoto do
|
18
|
+
default_scope { select(:fileName, :imagePath, :modelId, :type, :uuid) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def inspect
|
22
|
+
"#<%s id=%d uuid=%s name='%s'>" % [ self.class, id, uuid, file_name ]
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015, Sebastian Staudt
|
5
|
+
|
6
|
+
class Gallerist::ModelResource < Gallerist::ImageProxiesModel
|
7
|
+
|
8
|
+
self.table_name = 'RKModelResource'
|
9
|
+
|
10
|
+
belongs_to :photo
|
11
|
+
|
12
|
+
alias_attribute :file_name, :filename
|
13
|
+
alias_attribute :model_type, :attachedModelType
|
14
|
+
alias_attribute :uuid, :resourceUuid
|
15
|
+
|
16
|
+
default_scope { select(:attachedModelType, :filename, :modelId, :resourceUuid) }
|
17
|
+
|
18
|
+
def inspect
|
19
|
+
'#<%s id=%d uuid=%s file_name=%s>' % [ self.class, id, uuid, file_name ]
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015, Sebastian Staudt
|
5
|
+
|
6
|
+
class Gallerist::Person < Gallerist::PersonModel
|
7
|
+
|
8
|
+
iphoto do
|
9
|
+
self.table_name = 'RKFaceName'
|
10
|
+
|
11
|
+
has_many :person_photos, primary_key: 'faceKey', foreign_key: 'faceKey'
|
12
|
+
|
13
|
+
default_scope { select(:name, :faceKey, :keyVersionUuid).order(:manual_order) }
|
14
|
+
end
|
15
|
+
|
16
|
+
photos do
|
17
|
+
self.table_name = 'RKPerson'
|
18
|
+
|
19
|
+
has_many :person_photos, primary_key: 'modelId', foreign_key: 'personId'
|
20
|
+
|
21
|
+
alias_attribute :person_type, :personType
|
22
|
+
alias_attribute :simple_name, :searchName
|
23
|
+
|
24
|
+
default_scope do
|
25
|
+
select(:modelId, :name, :representativeFaceId).
|
26
|
+
order(person_type: :desc, manual_order: :asc)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
alias_attribute :manual_order, :manualOrder
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
"#<%s id=%d name='%s'>" % [ self.class, id, name ]
|
34
|
+
end
|
35
|
+
|
36
|
+
# ActiveRecord does not support has_many-through associations across
|
37
|
+
# different databases, so we have to query the photos manually
|
38
|
+
def photos
|
39
|
+
Gallerist::Photo.where modelId: person_photos.map(&:photo_id)
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
name
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|