gallerist 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/CONTRIBUTING.md +53 -0
  4. data/Gemfile +8 -0
  5. data/LICENSE +25 -0
  6. data/README.md +57 -0
  7. data/Rakefile +16 -0
  8. data/assets/stylesheets/main.scss +17 -0
  9. data/bin/gallerist +59 -0
  10. data/config.ru +21 -0
  11. data/gallerist.gemspec +32 -0
  12. data/lib/gallerist.rb +46 -0
  13. data/lib/gallerist/app.rb +230 -0
  14. data/lib/gallerist/errors.rb +12 -0
  15. data/lib/gallerist/helpers.rb +72 -0
  16. data/lib/gallerist/library.rb +100 -0
  17. data/lib/gallerist/middleware/raise_warmup_exceptions.rb +24 -0
  18. data/lib/gallerist/middleware/show_exceptions.rb +40 -0
  19. data/lib/gallerist/models/admin_data.rb +10 -0
  20. data/lib/gallerist/models/album.rb +30 -0
  21. data/lib/gallerist/models/album_photo.rb +20 -0
  22. data/lib/gallerist/models/base_model.rb +32 -0
  23. data/lib/gallerist/models/image_proxies_model.rb +11 -0
  24. data/lib/gallerist/models/image_proxy_state.rb +22 -0
  25. data/lib/gallerist/models/master.rb +25 -0
  26. data/lib/gallerist/models/model_resource.rb +22 -0
  27. data/lib/gallerist/models/person.rb +46 -0
  28. data/lib/gallerist/models/person_model.rb +10 -0
  29. data/lib/gallerist/models/person_photo.rb +39 -0
  30. data/lib/gallerist/models/photo.rb +100 -0
  31. data/lib/gallerist/models/tag.rb +31 -0
  32. data/lib/gallerist/models/tag_photo.rb +20 -0
  33. data/lib/rack/handler/unicorn.rb +34 -0
  34. data/views/500.erb +5 -0
  35. data/views/album.erb +9 -0
  36. data/views/index.erb +24 -0
  37. data/views/layout.erb +34 -0
  38. data/views/partials/thumbnail.erb +13 -0
  39. data/views/person.erb +9 -0
  40. data/views/photos.erb +9 -0
  41. data/views/tag.erb +9 -0
  42. 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,10 @@
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::AdminData < Gallerist::BaseModel
7
+
8
+ self.table_name = 'RKAdminData'
9
+
10
+ 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