gallerist 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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