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

View file

@ -156,8 +156,8 @@ class Post < OpenStruct
# Guarda los cambios
# rubocop:disable Metrics/CyclomaticComplexity
def save
return false unless valid?
def save(validation = true)
return false if validation && !valid?
# Salir si tenemos que cambiar el nombre del archivo y no pudimos
return false if !new? && path_changed? && !update_path!
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
# de crearlo
after_initialize :load_jekyll
after_create :load_jekyll
after_create :load_jekyll, :static_file_migration!
# Cambiar el nombre del directorio
before_update :update_name!
# Guardar la configuración si hubo cambios
@ -323,6 +323,11 @@ class Site < ApplicationRecord
config.url = url
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
# 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'
simple: 'Simple'
sites:
static_file_migration: 'File migration'
index: 'This is the list of sites you can edit.'
edit_translations: "You can edit texts from your site other than
posts', and you can also translate them to other languages."

View file

@ -149,6 +149,7 @@ es:
anexo: 'Anexo'
simple: 'Simple'
sites:
static_file_migration: 'Migración de archivos'
index: 'Este es el listado de sitios que puedes editar.'
edit_translations: 'Puedes editar los textos que salen en tu sitio
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?
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
title = SecureRandom.hex
@post.title.value = title