Skip to content

Commit

Permalink
Changing how thumbnails generate HTML. Now the first thumbnail *with …
Browse files Browse the repository at this point in the history
…a reference location* is chosen, instead of just the first.
  • Loading branch information
threeplanetssoftware committed Mar 18, 2024
1 parent 9633c41 commit a568518
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 48 deletions.
56 changes: 35 additions & 21 deletions lib/AppleNotesEmbeddedObject.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,27 @@ def initialize(primary_key, uuid, uti, note)
@uuid = uuid
@type = uti
@conforms_to = uti
@note = note
@version = @note.version
@is_password_protected = @note.is_password_protected
@backup = @note.backup
@database = @note.database
@logger = @backup.logger

# Set variables to defaults to be overridden later
@version = AppleNoteStore::IOS_VERSION_UNKNOWN
@is_password_protected = false
@backup = nil
@database = nil
@logger = Logger.new(STDOUT)

@user_title = ""
@filepath = ""
@filename = ""
@backup_location = nil
@user_title = get_media_zusertitle_for_row

# Variable to hold ZMERGEABLEDATA objects
@gzipped_data = nil

# Create an Array to hold Thumbnails
@thumbnails = Array.new

# Create an Array to hold child objects, such as for a gallery
@child_objects = Array.new

# Zero out cryptographic settings
@crypto_iv = nil
Expand All @@ -49,21 +59,13 @@ def initialize(primary_key, uuid, uti, note)
@crypto_iterations = nil
@crypto_password = nil

if @is_password_protected
add_cryptographic_settings
end

@logger.debug("Note #{@note.note_id}: Created a new Embedded Object of type #{@type}")
# Override the variables if we were given a note
self.note=(note) if note

# Variable to hold ZMERGEABLEDATA objects
@gzipped_data = nil

# Create an Array to hold Thumbnails and add them
@thumbnails = Array.new
search_and_add_thumbnails
log_string = "Created a new Embedded Object of type #{@type}"
log_string = "Note #{@note.note_id}: #{log_string}" if @note

# Create an Array to hold child objects, such as for a gallery
@child_objects = Array.new
@logger.debug(log_string)
end

##
Expand All @@ -75,6 +77,11 @@ def note=(note)
@backup = @note.backup
@logger = @backup.logger
@database = @note.database
@user_title = get_media_zusertitle_for_row
if @is_password_protected
add_cryptographic_settings
end
search_and_add_thumbnails
end

##
Expand Down Expand Up @@ -631,7 +638,13 @@ def generate_html(individual_files=false)
##
# This method generates the HTML to be embedded into an AppleNote's HTML for objects that use thumbnails.
def generate_html_with_images(individual_files=false)
return @thumbnails.first.generate_html(individual_files) if @thumbnails.length > 0

# If we have thumbnails, return the first one with a reference location
@thumbnails.each do |thumbnail|
return thumbnail.generate_html(individual_files) if thumbnail.reference_location
end

# If we don't have a thumbnail with a reference location, use ours
if @reference_location
root = @note.folder.to_relative_root(individual_files)
builder = Nokogiri::HTML::Builder.new(encoding: "utf-8") do |doc|
Expand All @@ -641,6 +654,7 @@ def generate_html_with_images(individual_files=false)
return builder.doc.root
end

# If we get to here, neither our thumbnails, nor we had a reference location
return "{#{type} missing due to not having a file reference location}"
end

Expand Down
96 changes: 69 additions & 27 deletions lib/AppleNotesEmbeddedThumbnail.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,20 @@ def initialize(primary_key, uuid, uti, note, backup, height, width, parent)
@parent = parent
@filename = get_media_filename
@filepath = get_media_filepath
@backup = backup
@backup = backup
@zgeneration = get_zgeneration_for_thumbnail

# Find where on this computer that file is stored
back_up_file if (@backup and @filepath.length > 0 and @filename.length > 0)
end

##
# This method handles setting the relevant +@backup_location+ variable
# and then backing up the file, if it exists.
def back_up_file
return if (!@backup or !@filepath or @filepath.length == 0)
@backup_location = @backup.get_real_file_path(@filepath)
@logger.debug("Embedded Thumbnail #{@uuid}: \n\tExpected Filepath: '#{@filepath}' (length: #{@filepath.length}), \n\tExpected location: '#{@backup_location}', \n\tOrphaned Parent: #{@parent}")

# Copy the file to our output directory if we can
@reference_location = @backup.back_up_file(@filepath,
Expand All @@ -42,6 +52,16 @@ def initialize(primary_key, uuid, uti, note, backup, height, width, parent)
@crypto_tag)
end

##
# This method sets the thumbnail's Note object. It expects an AppleNote +note+
# and immediately calls AppleNotesEmbeddedObjects note= function before firing
# the thumbnail's get_zgeneration_for_thumbnail.
def note=(note)
super(note)
@zgeneration = get_zgeneration_for_thumbnail
back_up_file
end

##
# This method just returns a readable String for the object.
# Adds to the AppleNotesEmbeddedObject.to_s by pointing to where the media is.
Expand All @@ -53,37 +73,47 @@ def to_s
# This method returns the +filepath+ of this object.
# This is computed based on the assumed default storage location.
def get_media_filepath
return get_media_filepath_ios16_and_earlier if @note.notestore.version < AppleNoteStore::IOS_VERSION_17
return get_media_filepath_ios16_and_earlier if @version < AppleNoteStore::IOS_VERSION_17
return get_media_filepath_ios17
end

##
# This method returns the +filepath+ of this object.
# This is computed based on the assumed default storage location.
# Examples of valid iOS 16 paths:
# Accounts/{account_uuid}/Previews/{parent_uuid}-1-192x144-0.png
# Accounts/{account_uuid}/Previews/{parent_uuid}-1-768x768-0.png.encrypted
# Accounts/{account_uuid}/Previews/{parent_uuid}-1-216x384-0-oriented.png
# Accounts/{account_uuid}/Previews/{parent_uuid}-1-144x192-0.jpg
# Accounts/{account_uuid}/Previews/{parent_uuid}-1-288x384-0.jpg.encrypted
def get_media_filepath_ios16_and_earlier
return "#{@note.account.account_folder}Previews/#{@uuid}.#{get_thumbnail_extension}.encrypted" if @is_password_protected
return "#{@note.account.account_folder}Previews/#{@uuid}.#{get_thumbnail_extension}"
return "[Unknown Account]/Previews/#{@filename}" if !@note
return "#{@note.account.account_folder}Previews/#{@filename}"
end

##
# This method returns the +filepath+ of this object.
# This is computed based on the assumed default storage location.
# Examples of valid iOS 17 paths:
# Accounts/{account_uuid}/Previews/{parent_uuid}-1-272x384-0.png
# Accounts/{account_uuid}/Previews/{parent_uuid}-1-768x768-0.png.encrypted
# Accounts/{account_uuid}/Previews/{parent_uuid}-1-192x144-0/{zgeneration}/Preview.png
# Accounts/{account_uuid}/Previews/{parent_uuid}-1-2384x3360-0/{zgeneration}/OrientedPreview.jpeg
def get_media_filepath_ios17
zgeneration = get_zgeneration_for_thumbnail
zgeneration = "#{@uuid}/#{zgeneration}/" if (zgeneration and zgeneration.length > 0)
zgeneration_string = ""
zgeneration_string = "#{@uuid}/#{@zgeneration}/" if (@zgeneration and @zgeneration.length > 0)

return "#{@note.account.account_folder}Previews/#{@uuid}.#{get_thumbnail_extension}.encrypted" if @is_password_protected
return "#{@note.account.account_folder}Previews/#{@uuid}.#{get_thumbnail_extension}" if !zgeneration
return "#{@note.account.account_folder}Previews/#{zgeneration}#{@filename}"
return "[Unknown Account]/Previews/#{@filename}" if !@note
return "#{@note.account.account_folder}Previews/#{zgeneration_string}#{@filename}"
end

##
# As these are created by Notes, it is just the UUID. These are either
# .png (apparently created by com.apple.notes.gallery) or .jpg (rest)
# .png (apparently created by com.apple.notes.gallery) or .jpeg/.jpg (rest)
# Encrypted thumbnails just have .encrypted added to the end.
def get_media_filename
return get_media_filename_ios17 if @note.notestore.version >= AppleNoteStore::IOS_VERSION_17
return get_media_filename_ios16_and_earlier
return get_media_filename_ios16_and_earlier if @version < AppleNoteStore::IOS_VERSION_17
return get_media_filename_ios17
end

##
Expand All @@ -96,20 +126,26 @@ def get_media_filename_ios16_and_earlier
end

##
# As of iOS 17, these appear to be called Preview.png if there is a zgeneration.
# As of iOS 17, these appear to be called Preview.png if there is a zgeneration.
# Examples of valid paths:
# Accounts/{account_uuid}/Previews/{parent_uuid}-1-768x768-0.png.encrypted
# Accounts/{account_uuid}/Previews/{parent_uuid}-1-2384x3360-0/{zgeneration}/OrientedPreview.jpeg
# Accounts/{account_uuid}/Previews/{parent_uuid}-1-192x144-0/{zgeneration}/Preview.png
# Accounts/{account_uuid}/Previews/{parent_uuid}-1-272x384-0.png
def get_media_filename_ios17
zgeneration = get_zgeneration_for_thumbnail
#zgeneration = get_zgeneration_for_thumbnail

return "#{@uuid}.png.encrypted" if @is_password_protected
return "Preview.#{get_thumbnail_extension_ios17}" if zgeneration
#return "#{@uuid}.png.encrypted" if @is_password_protected
return "#{@uuid}.#{get_thumbnail_extension_ios17}.encrypted" if @is_password_protected
return "Preview.#{get_thumbnail_extension_ios17}" if @zgeneration
return "#{@uuid}.#{get_thumbnail_extension_ios17}"
end

##
# This method fetches the appropriate ZFALLBACKGENERATION string to compute
# media location for iOS 17 and later.
def get_zgeneration_for_thumbnail
return nil if @note.notestore.version < AppleNoteStore::IOS_VERSION_17
return nil if @version < AppleNoteStore::IOS_VERSION_17 or !@database
@database.execute("SELECT ZICCLOUDSYNCINGOBJECT.ZGENERATION " +
"FROM ZICCLOUDSYNCINGOBJECT " +
"WHERE ZICCLOUDSYNCINGOBJECT.ZIDENTIFIER=?",
Expand All @@ -122,42 +158,48 @@ def get_zgeneration_for_thumbnail
# This method returns the thumbnail's extension. These are either
# .jpg (apparently created by com.apple.notes.gallery) or .png (rest).
def get_thumbnail_extension
return get_thumbnail_extension_ios17 if @note.notestore.version >= AppleNoteStore::IOS_VERSION_17
return get_thumbnail_extension_ios16_and_earlier
return get_thumbnail_extension_ios16_and_earlier if @version < AppleNoteStore::IOS_VERSION_17
return get_thumbnail_extension_ios17
end

##
# This method returns the thumbnail's extension. This is apparently png for iOS
# 17 and later.
# 17 and later and jpeg for Galleries.
def get_thumbnail_extension_ios17
return "jpeg" if (@parent.type == "com.apple.notes.gallery")
return "jpeg" if (@parent.parent and @parent.parent.type == "com.apple.notes.gallery")
return "jpeg" if (@parent and @parent.type == "com.apple.notes.gallery")
return "jpeg" if (@parent and @parent.parent and @parent.parent.type == "com.apple.notes.gallery")
return "png"
end

##
# This method returns the thumbnail's extension. These are either
# .jpg (apparently created by com.apple.notes.gallery) or .png (rest) for iOS 16 and earlier.
def get_thumbnail_extension_ios16_and_earlier
return "jpg" if (@parent.type == "com.apple.notes.gallery")
return "jpg" if (@parent.parent and @parent.parent.type == "com.apple.notes.gallery")
return "jpg" if (@parent and @parent.type == "com.apple.notes.gallery")
return "jpg" if (@parent and @parent.parent and @parent.parent.type == "com.apple.notes.gallery")
return "png"
end

##
# This method generates the HTML necessary to display the image inline.
def generate_html(individual_files)
if (@parent.reference_location and @reference_location)
if (@parent and @reference_location)

# We default to the thumbnail's location to link to...
href_target = @reference_location
# ...but if possible, we use the parent's location to get the real file
href_target = @parent.reference_location if @parent.reference_location

root = @note.folder.to_relative_root(individual_files)
builder = Nokogiri::HTML::Builder.new(encoding: "utf-8") do |doc|
doc.a(href: "#{root}#{@parent.reference_location}") {
doc.a(href: "#{root}#{href_target}") {
doc.img(src: "#{root}#{@reference_location}")
}.attr("data-apple-notes-zidentifier" => "#{@parent.uuid}")
end
return builder.doc.root
end

return "{Image missing due to not having file reference point}"
return "{Thumbnail missing due to not having file reference point}"
end

##
Expand Down

0 comments on commit a568518

Please sign in to comment.