migración de archivos estáticos

This commit is contained in:
f 2019-11-14 14:27:24 -03:00
parent 0a6b07a4f4
commit 11b347e4c0
No known key found for this signature in database
GPG key ID: 2AE5A13E321F953D
9 changed files with 186 additions and 8 deletions

View file

@ -56,6 +56,8 @@ class MetadataFile < MetadataTemplate
# #
# @return ActiveStorage::Attachment # @return ActiveStorage::Attachment
def static_file def static_file
return @static_file if @static_file
ActiveRecord::Base.connection_pool.with_connection do ActiveRecord::Base.connection_pool.with_connection do
if uploaded? if uploaded?
blob = ActiveStorage::Blob.find_by(key: key_from_path) blob = ActiveStorage::Blob.find_by(key: key_from_path)
@ -68,10 +70,12 @@ class MetadataFile < MetadataTemplate
private private
def path
@path ||= Pathname.new value['path']
end
def key_from_path def key_from_path
# XXX: No podemos usar self#extension porque en este punto todavía path.dirname.basename.to_s
# no sabemos el static_file
File.basename(value['path'], '.*')
end end
# Hacemos un link duro para colocar el archivo dentro del repositorio # Hacemos un link duro para colocar el archivo dentro del repositorio
@ -83,7 +87,7 @@ class MetadataFile < MetadataTemplate
end end
def extension def extension
static_file.blob.content_type.split('/').last @extension ||= static_file.filename.to_s.split('.').last
end end
# Obtener la ruta al archivo # Obtener la ruta al archivo
@ -97,7 +101,7 @@ class MetadataFile < MetadataTemplate
end end
def relative_destination_path def relative_destination_path
File.join('public', [static_file.key, extension].join('.')) File.join('public', static_file.key, static_file.filename.to_s)
end end
def destination_path def destination_path

View file

@ -156,8 +156,8 @@ class Post < OpenStruct
# Guarda los cambios # Guarda los cambios
# rubocop:disable Metrics/CyclomaticComplexity # rubocop:disable Metrics/CyclomaticComplexity
def save def save(validation = true)
return false unless valid? return false if validation && !valid?
# Salir si tenemos que cambiar el nombre del archivo y no pudimos # Salir si tenemos que cambiar el nombre del archivo y no pudimos
return false if !new? && path_changed? && !update_path! return false if !new? && path_changed? && !update_path!
return false unless save_attributes! return false unless save_attributes!

View file

@ -39,7 +39,7 @@ class Site < ApplicationRecord
# Carga el sitio Jekyll una vez que se inicializa el modelo o después # Carga el sitio Jekyll una vez que se inicializa el modelo o después
# de crearlo # de crearlo
after_initialize :load_jekyll after_initialize :load_jekyll
after_create :load_jekyll after_create :load_jekyll, :static_file_migration!
# Cambiar el nombre del directorio # Cambiar el nombre del directorio
before_update :update_name! before_update :update_name!
# Guardar la configuración si hubo cambios # Guardar la configuración si hubo cambios
@ -323,6 +323,11 @@ class Site < ApplicationRecord
config.url = url config.url = url
end end
# Migra los archivos a Sutty
def static_file_migration!
Site::StaticFileMigration.new(site: self).migrate!
end
# Valida si el sitio tiene al menos una forma de alojamiento asociada # Valida si el sitio tiene al menos una forma de alojamiento asociada
# y es la local # y es la local
# #

View file

@ -0,0 +1,124 @@
# frozen_string_literal: true
class Site
MigrationAuthor = Struct.new :email, :name, keyword_init: true
# Obtiene todos los archivos relacionados en artículos del sitio y los
# sube a Sutty de forma que los podamos seguir utilizando normalmente
# sin casos especiales (ej. soportar archivos locales al repositorio y
# remotos, alojados en Sutty)
#
# TODO: Convertir en reutilizable, por ejemplo correr en cada pull, no
# asumir que la migración se hizo una sola vez...
class StaticFileMigration
# Tipos de metadatos que contienen archivos
STATIC_TYPES = %w[file image].freeze
attr_reader :site
def initialize(site:)
@site = site
end
# Recorre todos los artículos cuyos layouts contengan campos con
# archivos estáticos
def migrate!
log = File.open(File.join(site.path, 'migration.log'), 'w')
modified = []
Dir.chdir site.path do
site.locales.each do |locale|
# Recorrer todos los documentos de todas las colecciones
site.posts(lang: locale).each do |doc|
# Ignoramos los documentos cuyo layout no contiene archivos
next unless layouts.include? doc.layout.name
remove = true
# Buscamos todos los campos con archivos
fields.each do |field|
next unless doc.attribute? field
next unless doc.document.data.key? field.to_s
# Traemos los metadatos, en este punto, Sutty cree que el
# archivo está subido, porque es una string apuntando a un
# archivo.
metadata = doc.public_send(field)
next if metadata.value['path'].blank?
path = Pathname.new(metadata.value['path'])
# Si no existe, agregamos una imagen faltante para no
# romper el sitio en Sutty
unless path.exist?
log.write "#{path} no existe\n"
path = Pathname.new(Rails.root.join('app/assets/images/logo.png'))
remove = false
end
# Agregamos el archivo al sitio y se lo asignamos al campo
metadata.value['path'] = {
io: path.open,
filename: path.basename
}
# Copiar y analizar el archivo sincrónicamente
metadata.static_file.blob.upload path.open
metadata.static_file.blob.analyze
next unless remove
dest = Pathname.new(metadata.send(:relative_destination_path))
# Eliminamos el archivo original y lo vinculamos al subido
# para mantener la ruta y no romper el sitio
FileUtils.rm_f path
# XXX: Link simbólico o duro?
FileUtils.ln_s dest.relative_path_from(path.dirname), path
end
# Guardamos los cambios
unless doc.save(false)
log.write "#{doc.path.relative} no se pudo guardar\n"
end
modified << doc.path.absolute
end
end
end
log.close
# TODO: Hacer la migración desde el servicio de creación de sitios?
site.repository.commit(file: modified,
message: I18n.t('sites.static_file_migration'),
usuarie: author)
end
private
def author
@author = MigrationAuthor.new email: "sutty@#{Site.domain}",
name: 'Sutty'
end
# Encuentra todos los layouts con campos estáticos
def layouts
@layouts ||= site.layouts.reject do |_, layout|
layout.metadata.select do |_, desc|
STATIC_TYPES.include? desc['type']
end.empty?
end.keys
end
# Encuentra todos los campos con archivos estáticos
def fields
@fields ||= layouts.map do |layout|
site.layouts[layout].metadata.select do |_, desc|
STATIC_TYPES.include? desc['type']
end.keys
end.flatten.uniq.map(&:to_sym)
end
end
end

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
# TODO: Estamos procesando el análisis de los archivos en el momento
# porque queremos obtener la ruta del archivo en el momento y no
# después. Necesitaríamos poder generar el vínculo en el
# repositorio a destiempo, modificando el Job de ActiveStorage
ActiveStorage::AnalyzeJob.queue_adapter = :inline

View file

@ -148,6 +148,7 @@ en:
anexo: 'Appendix' anexo: 'Appendix'
simple: 'Simple' simple: 'Simple'
sites: sites:
static_file_migration: 'File migration'
index: 'This is the list of sites you can edit.' index: 'This is the list of sites you can edit.'
edit_translations: "You can edit texts from your site other than edit_translations: "You can edit texts from your site other than
posts', and you can also translate them to other languages." posts', and you can also translate them to other languages."

View file

@ -149,6 +149,7 @@ es:
anexo: 'Anexo' anexo: 'Anexo'
simple: 'Simple' simple: 'Simple'
sites: sites:
static_file_migration: 'Migración de archivos'
index: 'Este es el listado de sitios que puedes editar.' index: 'Este es el listado de sitios que puedes editar.'
edit_translations: 'Puedes editar los textos que salen en tu sitio edit_translations: 'Puedes editar los textos que salen en tu sitio
que no corresponden a artículos aquí, además de traducirlos a que no corresponden a artículos aquí, además de traducirlos a

30
doc/static_files.md Normal file
View file

@ -0,0 +1,30 @@
# Archivos estáticos
El objetivo es encontrar una forma de importar los archivos estáticos de
un sitio que se ha migrado a la estructura de Sutty, es decir, subirlos
como archivos de Rails y luego vincularlos a los artículos.
Actualmente, los archivos primero se suben a la interfaz via
ActiveStorage y luego se vinculan internamente al repositorio[^git].
Lo que deberíamos lograr es reconocer los archivos vinculados desde los
artículos y hacer el proceso de cargarlos internamente, modificando los
artículos para que apunten a las nuevas versiones.
Esto podría hacerse por cada artículo individual o durante el proceso de
creación del sitio, después de clonarlo.
El algoritmo sería:
* Clonar el sitio
* Obtener todos los artículos de todas las colecciones
* Encontrar todos los layouts con archivos adjuntos
* Buscar todos los artículos que tengan esos layouts
* Tomar el archivo físico desde los metadatos y asociarlo al sitio via Active Storage
* Asociar el archivo al MetadataFile/Image correspondiente
* ???
* Migración cumplida!
[^git]: esto puede hacer que los repositorios git sean gigantes,
podríamos usar algo como git-annex quizás?

View file

@ -63,6 +63,12 @@ class PostTest < ActiveSupport::TestCase
assert_not @post.valid? assert_not @post.valid?
end end
test 'se pueden guardar sin validar' do
assert @post.valid?
@post.title.value = ''
assert @post.save(false)
end
test 'se pueden guardar los cambios' do test 'se pueden guardar los cambios' do
title = SecureRandom.hex title = SecureRandom.hex
@post.title.value = title @post.title.value = title