<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Anime-E-Manga | AkitaOnRails.com</title><link>https://akitaonrails.github.io/tags/anime-e-manga/</link><description>Blog do Fabio Akita do Canal do YouTube 'Akitando' falando sobre tecnologia, carreira e coisas geek.</description><generator>Hugo -- gohugo.io</generator><language>pt-BR</language><lastBuildDate>Mon, 19 Oct 2026 23:20:48 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/tags/anime-e-manga/index.xml" rel="self" type="application/rss+xml"/><item><title>[Akitando] #30 - Minha Máquina do Tempo | Meus Sites de Anime</title><link>https://akitaonrails.github.io/2018/11/29/akitando-30-minha-maquina-do-tempo-meus-sites-de-anime/</link><guid isPermaLink="true">https://akitaonrails.github.io/2018/11/29/akitando-30-minha-maquina-do-tempo-meus-sites-de-anime/</guid><pubDate>Thu, 29 Nov 2018 17:00:00 -0200</pubDate><description>&lt;p&gt;Disclaimer: esta série de posts são transcripts diretos dos scripts usados em cada video do canal &lt;a href="https://www.youtube.com/channel/UCib793mnUOhWymCh2VJKplQ"target="_blank" rel="noopener"&gt;Akitando&lt;/a&gt;. O texto tem erros de português mas é porque estou apenas publicando exatamente como foi usado pra gravar o video, perdoem os errinhos.&lt;/p&gt;
&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/0KNGiOp7eoE"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h3&gt;Descrição no YouTube&lt;span class="hx:absolute hx:-mt-20" id="descrição-no-youtube"&gt;&lt;/span&gt;
&lt;a href="#descri%c3%a7%c3%a3o-no-youtube" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Uma coisa que recomendo que todo mundo faça: backups. Faça backup de todos os seus trabalhos. Daqui 20, 30 anos, você vai se agradecer por ter feito isso.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Disclaimer: esta série de posts são transcripts diretos dos scripts usados em cada video do canal <a href="https://www.youtube.com/channel/UCib793mnUOhWymCh2VJKplQ"target="_blank" rel="noopener">Akitando</a>. O texto tem erros de português mas é porque estou apenas publicando exatamente como foi usado pra gravar o video, perdoem os errinhos.</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/0KNGiOp7eoE"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<h3>Descrição no YouTube<span class="hx:absolute hx:-mt-20" id="descrição-no-youtube"></span>
    <a href="#descri%c3%a7%c3%a3o-no-youtube" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Uma coisa que recomendo que todo mundo faça: backups. Faça backup de todos os seus trabalhos. Daqui 20, 30 anos, você vai se agradecer por ter feito isso.</p>
<p>Aproveitando a dica, quero abrir uma pequena janela pra um hobby que eu tinha nos anos 90: os sites de anime que eu fazia.</p>
<p>E ainda terminando com uma confusão que deu até boletim de ocorrência :-D</p>
<p>Links:</p>
<ul>
<li>Meu Passado Otaku – A revista de anime que publicou… um Boletim de Ocorrência (!!!) (<a href="https://maisdeoitomil.wordpress.com/2016/03/07/meu-passado-otaku-a-revista-de-anime-que-publicou-um-boletim-de-ocorrencia/"target="_blank" rel="noopener">https://maisdeoitomil.wordpress.com/2016/03/07/meu-passado-otaku-a-revista-de-anime-que-publicou-um-boletim-de-ocorrencia/</a>)</li>
</ul>
<p>AkitaOnRails:</p>
<ul>
<li>[Off-Topic] Meu Backup Pessoal (Drobo/Time Capsule) (<a href="http://www.akitaonrails.com/2009/09/19/off-topic-meu-backup-pessoal-drobo-time-capsule"target="_blank" rel="noopener">http://www.akitaonrails.com/2009/09/19/off-topic-meu-backup-pessoal-drobo-time-capsule</a>)</li>
<li>[Off-Topic] Faça Backup do seu Ambiente (<a href="http://www.akitaonrails.com/2011/03/01/off-topic-faca-backup-do-seu-ambiente"target="_blank" rel="noopener">http://www.akitaonrails.com/2011/03/01/off-topic-faca-backup-do-seu-ambiente</a>)</li>
</ul>
<h2>Script<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Se vocês ainda não me seguem nas redes sociais procurem por AkitaOnRails no Facebook, Twitter e Instagram. Quem me segue no Instagram sabe que alguns dias atrás eu resolvi finalmente abrir minha cápsula do tempo. Os códigos que vocês viram nos episódios da série Sua Linguagem É especial? vieram daí.</p>
<p>Quando eu era criança eu não lembro se foi no Show de Beakman ou outro programa infantil desse tipo, onde eu vi o conceito de Cápsula do Tempo pela primeira vez. Não é só acumular velharias. É empacotar seu presente intencionalmente pra ser aberto bem no futuro.</p>
<p>Intuitivamente eu nunca tive vontade de fazer o mais óbvio: pegar uma caixa de sapato e colocar pertences meus da época como uma fita cassette ou brinquedo. Em vez disso eu queria de alguma forma preservar o que eu era. E eu me defino não pelo o que eu possuo, mas o que eu penso e produzo. Eu não sou apegado a nenhuma posse. Por sorte, tudo que eu produzo é digital.</p>
<p>Eu já tinha visto os disquetes de 8 polegadas e depois os de 5 1/4 polegadas ficarem obsoletos, eu estava vendo os disquetes de 3 1/2 ficarem obsoletos. Então eu fui gradativamente passando tudo de disquetes pra zip drives, de zip drives pra CD-ROMs (quando finalmente os gravadores ficaram acessíveis), de CD-ROMs pra DVD. E de DVD pra HDs. As coisas hoje em dia estão muito fáceis, antigamente eu tinha caixas e caixas de disquetes, CDs e DVDs, hoje cabe tudo num HDzão.</p>
<p>Em 2008 obviamente eu tinha noção que HDs tem vida útil e costumam falhar, são dispositivos mecânicos que não foram feitos pra durar pra sempre. Assistindo os primeiros video casts, programas tech que existiam desde antes do YouTube, que você via no iTunes, eu vi um programa da Kali Lewis sobre Drobos. Eu já sabia o que era uma configuração RAID de storage pra servidor mas eu achava complicado pra ter em casa. Mas o Drobo tinha a intenção de ser portátil e plug and play. Então eu resolvi pedir pra um amigo trazer pra mim.</p>
<p>E o Drobo realmente funcionou. Eu fiquei com esse Drobo por 10 anos. Nesses 10 anos eu troquei discos 2 vezes porque falharam, e isso justificou essa compra. Custou caro pra caramba, na época foi quase 800 dólares se somar com os HDs. Mas se considerar que foi 800 dólares que protegeram minha história por 10 anos, eu digo que foi extremamente barato. Eu troquei de Drobo ano passado. A porta USB 2 era muito lenta, troquei pelo mesmo modelo com USB 3, que ainda não chega a ser um Thunderbolt, mas já ajuda. Eu tirei todos os 4 HDs do antigo, coloquei no novo, e boom, plug and play, tudo funciona. Impressionante.</p>
<p>O objetivo não é fazer propaganda do Drobo, claro, mas eu não confiaria em nenhum HD externo que não seja redundante. Aliás, muito cuidado, existem muitas configurações de HD externo com 2 ou mais HDs em layout de RAID-0 com striping. É só a pior configuração imaginável porque se você tem 2 HDs de 500 GB em striping, você enxerga como se fosse um único HDzaão de 1 TB. Só que não tem proteção contra erro nem redundância, se um dos HDs falhar, você perde os dados DOS DOIS. Jamais use RAID-0. E RAID-1 é o pior custo benefício porque é basicamente um HD fazendo cópia do outro.</p>
<p>Temos outro problema hoje em dia. Tudo está no maldito cloud. O problema não é o cloud, o problema são serviços que dificultam você sincronizar uma cópia local. Você está tão acostumado que o Google vai cuidar dos seus e-mails, que o Dropbox ou Microsoft OneDrive ou Google GDrive vai cuidar dos seus arquivos, que o Slack vai cuidar dos seus chats, que a Apple vai cuidar das suas fotos e músicas, que você literalmente não pensa em fazer mais backup hoje em dia.</p>
<p>Eu prefiro partir do princípio que qualquer um deles pode ter uma falha amanhã, nem estou dizendo falir, uma falha mesmo, e podem perder meus dados. Eu não preciso guardar tudo, mas de meses em meses eu acho saudável exportar os arquivos do Twitter, eu gosto de abrir um Mozilla Thunderbird e conectar nos meus diversos e-mails pra sincronizar tudo local via IMAP, prefiro baixar tudo do Dropbox e manter um backup local no meu Drobo, e assim por diante.</p>
<p>Nos últimos anos eu confesso que tenho sido menos cuidadoso com meus backups por conta de todos esses serviços cloud, mas é bom não descuidar. 25 anos depois, eu me arrependo de não ter guardado os fontes dos meus primeiros programas em Basic. Existem coisas que quando você tiver conseguido guardar dinheiro vai ainda conseguir encontrar algum colecionador vendendo, um MSX, um TK-85, mas seu primeiro programa? Isso não tem mais como.</p>
<p>De qualquer forma, este mês eu finalmente resolvi abrir meus arquivos antigos. Meus e-mails de 1997 a 2006, quase 10 anos de e-mails foram preservados em arquivos PST do Outlook que, felizmente, são fáceis de abrir. De 2006 em diante está tudo no Gmail.
Viu meu vídeo sobre Sua Linguagem É Especial que eu mostro os sites que eu trabalhava pra PSN como o do Corinthians e Cruzeiro? Eu tenho as imagens porque eu ainda tenho o Photoshop original que eu usei pra recortar as imagens pro site. Estavam como anexo nesses e-mails.</p>
<p>Essa é uma daquelas coisas que de vez em quando dá trabalho, você gasta dinheiro e tempo, mas o prêmio é que eu tenho uma oportunidade única: de viajar no tempo, pra qualquer lugar de hoje até mais de 20 anos no passado. Eu ainda ia esperar mais uma década antes de abrir, mas eu sinto que agora é a hora de fazer isso. Vai que amanhã eu morro e perco a oportunidade de deixar registrado pra posteridade?</p>
<p>Eu não guardei todos os instaladores de todos os programas que eu usava. Mas por sorte achei esse site WinWorld que tem instaladores de tudo que é programa dos anos 90. Achei até o instalador de Clipper, FoxPro e tudo mais que eu usava. E graças ao VMWare Workstation Pro que eu uso todo dia, ele também suporta Windows 95 em diante. Eu comecei antes do Windows 95, no MS DOS 3 e usei tudo que a Microsoft lançou do fim dos anos 80 até 2004 que foi quando migrei pra Apple e Mac até voltar pro Windows 10 anos depois em 2015. O Windows 98 foi um dos que eu mais usei nos anos 90 por isso instalei ele.</p>
<p>Como eu já falei bastante, por hoje quero mostrar um dos primeiros sites pessoais que eu fiz. É tão velho que se não for Internet Explorer 4 ou Netscape Navigator 4, o site nem renderiza direito. Essa é uma época anterior a eu aprender ASP ou PHP, quando eu fazia sites estáticos com HTML puro, estamos falando de 1997.</p>
<p>Em 1994 o Brasil foi pego de surpresa quando a Manchete resolveu lançar Cavaleiros do Zodíaco. De repente eu comecei a ver mais gente falando de anime. Até então gostar de anime era um troço bem solitário. Eu cresci assistindo anime gravado em fita Betamax e VHS que vinha do Japão no meio dos anos 80. Mas depois de 1994 todo mundo queria mais e mais anime. Shurato, Sailor Moon, Yuyu Hakusho, e tantos mais. Em 1995 surgiu uma das primeiras revistas especializadas em anime e cultura japonesa, a revista Herói. Daí na sequência saiu um pasquim de pior qualidade chamado Japan Fury que depois se tornou Animax em 1996.</p>
<p>Por coincidência eu fazia parte de um grupo de pessoas que se auto-intitulava Orcade e participava dessa revista, então eu mesmo escrevi um ou dois artigos, em particular sobre Evangelion, que eu me considerava especialista na época - típico de amador se achar especialista mesmo . Como eu tinha acabado de aprender HTML e quando virei monitor na sala de computação da USP eu ganhei acesso ao servidor Unix da Politécnica e como em toda faculdade da época, toda conta tinha uma pasta exposta no Apache pra internet, então decidi fazer um site pra revista. Essa foi a Animax Plug-In.</p>
<p>Essa é a primeira versão, que eu fiz usando o bom e velho Microsoft FrontPage 3.0. Eu sei, eu sei, nem precisa dizer, esse design é tenebroso até pros padrões da época, se bem que em 1997 não se tinha tanto padrão assim pras coisas, em outro vídeo vou mostrar os sites profissionais que fazia com designers de verdade da época. Mas por agora veja que a gente usava framesets e frames e muito table. Não tinha CSS ainda. E o destaque é o menu lateral que abre e fecha, quer dizer, abre e fecha no Netscape mas não no Internet Explorer. Pois é, diferença de Javascript entre navegadores é antiga, a web nasceu quebrada e a gente vem tentando consertar desde então. Mas é legal ver funcionando de novo, em navegador novo nada funciona. Nessa época Internet Explorer era superior ao Netscape já. Renderizava bem mais rápido, Javascript era mais rápido. Eu escrevia pra caramba, vocês podem ver que minha mania de fazer textão não vem de hoje.</p>
<p>Lendo meus textos fica claro que eu escrevia bem mal. Eu ainda não tinha adquirido a noção de editar meus textos. É como programador júnior que ainda tem dó de apagar o código que escreveu. Hoje em dia eu às vezes escrevo páginas e páginas e no meio do caminho eu vejo que está ruim e jogo tudo fora e começo do zero, ou vou refinando o texto, apago seções inteiras. Não existe escrever em apagar. O melhor fluxo é deixar tudo crú sair da sua cabeça, e quando terminar esse dump, daí sim reler e começar a ajustar e apagar e reorganizar.</p>
<p>De qualquer maneira, esse design está um pouco mais engolível. Eu ficava tentando fazer jeitos bizarros de navegação. Dessa vez em vez de um menu lateral eu tinha um menu no fim da página que ia abrindo sub menus pra cima. E eu tinha conceito de uma página que abre a próxima. Naquela época não era muito claro qual a melhor abstração pra organizar conteúdo. Vocês vão notar que nessa época era comum a gente querer simular o estilo de layout editoração de revista. E eu tinha de tudo nesse site, até uns textos tentando ensinar japonês, como se isso fosse fazer alguma diferença. E como ainda não existia Google também era comum os sites terem páginas linkando outros sites, como uma bibliografia ou recomendação.</p>
<p>Na sequência essa é a terceira ou quarta versão. Isso já era 1999, 3 anos fazendo isso. Esta é um pouquinho mais aceitável, pelo menos um pouco mais limpo. De novo note o estilo de layout inpirado em revistas. Eu gastava meu parco salário comprando revistas importadas de anime nessa época, eu queria fazer juz ao investimento. E, de novo, note o menu lateral em Javascript só que dessa vez com inspiração em ícones do Mac OS 8 que eu tinha acesso na agência onde fazia estágio. E continuando no mesmo estilo das outras versões eu colocava mais e mais seções de mangás novos explicando um a um em detalhes, olhando agora eu fico pensando se teria paciência de fazer a mesma coisa hoje em dia.</p>
<p>Eu cheguei até a iniciar um projeto onde eu escaneei vários volumes de um manga que eu gostava e ficava traduzindo balão a balão no photoshop, puta trampo, mas antigamente não existia mangareader e mesmo no mangareader, alguém faz esse tipo de trampo. Eu sei exatamente como dá trabalho</p>
<p>Essa atividade durou uns 3 anos. Durante esse tempo o site ficava hospedado na minha conta no UNIX da faculdade, mas na realidade eu não podia usar a conta da faculdade pra um site de revista comercial, mesmo que eu não ganhasse nada por isso. Eu ganhei zero reais, a revista não dava dinheiro, o site não dava dinheiro, a gente fazia tudo isso porque gostava. Vocês podem ver que isso de eu gastar todo meu tempo livre fazendo coisas trabalhosas só pela diversão vem de longa data.</p>
<p>Mas o que eu ganhei com isso foi outra coisa: com esse exercício todo, eu fiquei rápido pra layoutar sites no estilo da época. Podia me dar qualquer photoshop que eu recortava e montava muito rápido na agência. Sabia otimizar as imagens, otimizar o HTML - pelo menos pros padrões da época, e eu exercitei muito o Javascript da época. Isso seria muito útil nos anos seguintes.</p>
<p>Além disso eu treinei muito minha escrita. Como eu disse antes, lendo agora, é fácil ver que meus textos não eram bons, mas também dá pra ver que mesmo nesse curto período eu escrevi o suficiente pra encher as páginas de um pequeno livro. Eu escrevia em detalhes sobre as séries que eu gostava, indo de personagem em personagem, isso deu tipo MUITO trabalho. Mas novamente, esse trabalho também me ajudou muito, os meus e-mails no trabalho sempre foram completos e concisos, porque eu tinha treinado muito escrever em grande quantidade em pouco tempo.</p>
<p>Ou seja, quando eu comecei meu blog em 2006 eu já tinha escrito muito texto anos antes. E agora escrevendo scripts aqui pro canal, é tudo uma sequência. Eu não sou escritor profissional, mas eu vim aprendendo as técnicas e tentando refinar com o passar do tempo. Mesmo em 1999 comparado com 1997 eu já tinha melhorado.</p>
<p>E como última curiosidade, uma das coisas que atraía as pessoas pra Animax Plug-in e minhas matérias na revista é porque eu era um troll. Como eu disse, a revista Herói era uma droga em termos de conteúdo. Ela vendia porque na época não tinha nada na internet, e todo mundo queria qualquer coisa Cavaleiros do Zodíaco e outros animes.
Então eu tinha vários artigos listando um a um todos os erros dos textos da Herói, e não era errinhos ortográficos - que tinha de monte aliás - eram erros conceituais, contava a história errada mesmo. E eu não era sutil, eu chamava pro pau mesmo, eu diz sr Fulano, você não manja porque esse ponto X tá errado, ah e esse ponto Y tá totalmente errado.
Ah, e eu nos meus 20 sou que nem vocês nos seus 20, só que em vez de ficar tweetando, eu fazia matéria de revista.
E eu sei porque todo mundo gosta de extravazar no twitter, é extremamente satisfatório enfiar o dedo na cara dos outros, cagar um monte de regra, pisar no calo dos outros e ainda ter gente aplaudindo você fazendo isso. Mais satisfatório ainda quando o outro lado reage e vira uma briga. Acho que faz parte da natureza humana.</p>
<p>Hoje eu não faço mais isso. Pelo menos não desse jeito e jamais em público. E não é só por questões de imagem ou qualquer coisa assim. Deixa eu dar uma perspectiva: minhas matérias e artigos circularam entre 1997 e 1999. Hoje em dia, não tem uma pessoa que se lembra mais disso. Os assuntos da época foram todos esquecidos. Pra todos os efeitos, é como se nunca tivessem existido. As brigas são divertidas na hora, mas no final ninguém vai se lembrar, com exceção de quem você pisou no calo. E depois daquela satisfação momentânea, você se sente vazio porque no final, não mudou nada. E você fica querendo ir atrás da próxima briga pra ter de novo a satisfação momentânea, é um vício.</p>
<p>Dê tempo suficiente e qualquer briga que você achou que valia a pena apostar tudo, depois de alguns poucos anos não tem valor nenhum. Não deixa de ser um pouco o: faça o que eu falo mas não faça o que eu fiz. Por outro lado tinha gente que me dizia isso na época, quem disse que eu dei atenção?</p>
<p>O cara da revista Herói ficou tão puto que foi atrás do editor da Animax e socou ele na rua. Foram parar na polícia. E quem escreveu o texto ainda fui eu :-) hahaha Coitado. Vou deixar linkado na descrição se alguém se interessa nesse tipo de história.</p>
<p>Minha recomendação de hoje é: guarde o que você produz, mesmo que você ache que é uma droga. Daqui 20 ou 30 anos vai ser interessante constatar que você realmente evoluiu nesse tempo. Não confie nos serviços de cloud, você provavelmente vai viver mais do que eles. Muitos serviços que eu usava nos anos 90 e começo dos anos 2000 já não existem mais. Lembram do ICQ? Hotmail? Orkut?</p>
<p>E como eu disse no vídeo anterior, eu venho praticando deliberadamente a atividade de escrever desde o meio dos anos 90, agora vocês sabem porque eu escrevo tanto. Estamos no episódio 30 deste canal. Cada episódio tem em média umas 6 páginas no Google Docs, então desde Agosto eu já escrevi 180 páginas de texto, mais de 10 páginas semanalmente, ininterruptamente por umas 15 semanas já. Vou dizer que eu estou bem cansado, vamos ver até onde meu corpo aguenta.</p>
<p>E por hoje é isso aí, faça backups! E não deixe de comentar abaixo! Se curtiram mandem um joinha, compartilhem o vídeo com seus amigos, assinem o canal e cliquem no sininho. A gente se vê, até mais.</p>
]]></content:encoded><category>retrocomputacao</category><category>anime-e-manga</category><category>pessoal</category><category>akitando</category></item><item><title>[Akitando] #28 - Sua Vida em Anime: KAIJI</title><link>https://akitaonrails.github.io/2018/11/20/akitando-28-sua-vida-em-anime-kaiji/</link><guid isPermaLink="true">https://akitaonrails.github.io/2018/11/20/akitando-28-sua-vida-em-anime-kaiji/</guid><pubDate>Tue, 20 Nov 2018 17:00:00 -0200</pubDate><description>&lt;p&gt;Disclaimer: esta série de posts são transcripts diretos dos scripts usados em cada video do canal &lt;a href="https://www.youtube.com/channel/UCib793mnUOhWymCh2VJKplQ"target="_blank" rel="noopener"&gt;Akitando&lt;/a&gt;. O texto tem erros de português mas é porque estou apenas publicando exatamente como foi usado pra gravar o video, perdoem os errinhos.&lt;/p&gt;
&lt;iframe id="lbry-iframe" width="560" height="315" src="https://odysee.com/$/embed/sua-vida-em-anime-kaiji/4eabeadd0635ab9e3f842adf17b2b01758066c25?r=HnRGFGED6552Wae8RfNnecssKwKUNvBh" allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;Obs: YouTube bloqueou este video por copyright do Anime então subi no Odysee&lt;/p&gt;
&lt;h3&gt;Descrição no Odysee&lt;span class="hx:absolute hx:-mt-20" id="descrição-no-odysee"&gt;&lt;/span&gt;
&lt;a href="#descri%c3%a7%c3%a3o-no-odysee" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Por coincidência, depois dos episódios sobre Talento e Matar Semi-Deuses da semana passada, comecei a assistir este anime de 2007: Kaiji.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Disclaimer: esta série de posts são transcripts diretos dos scripts usados em cada video do canal <a href="https://www.youtube.com/channel/UCib793mnUOhWymCh2VJKplQ"target="_blank" rel="noopener">Akitando</a>. O texto tem erros de português mas é porque estou apenas publicando exatamente como foi usado pra gravar o video, perdoem os errinhos.</p>
<iframe id="lbry-iframe" width="560" height="315" src="https://odysee.com/$/embed/sua-vida-em-anime-kaiji/4eabeadd0635ab9e3f842adf17b2b01758066c25?r=HnRGFGED6552Wae8RfNnecssKwKUNvBh" allowfullscreen></iframe>
<p>Obs: YouTube bloqueou este video por copyright do Anime então subi no Odysee</p>
<h3>Descrição no Odysee<span class="hx:absolute hx:-mt-20" id="descrição-no-odysee"></span>
    <a href="#descri%c3%a7%c3%a3o-no-odysee" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Por coincidência, depois dos episódios sobre Talento e Matar Semi-Deuses da semana passada, comecei a assistir este anime de 2007: Kaiji.</p>
<p>Fiquei impressionado com a forma como eles montaram uma alegoria que descreve como é a vida real da maioria das pessoas.</p>
<p>Este episódio vai continuar explicando o meu conceito de &ldquo;prática deliberada&rdquo; usando a história deste anime como pano de fundo.</p>
<p>Assista no Crunchyroll: <a href="https://www.crunchyroll.com/kaiji"target="_blank" rel="noopener">https://www.crunchyroll.com/kaiji</a></p>
<h2>Script<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Esta semana vou fazer um vídeo só. O motivo? Este daqui vai ser super longo, mas espero que eu consiga passar minha mensagem. Tenham paciência comigo e vamos lá.</p>
<p>Não era minha intenção falar sobre animes, pelo menos não agora, mas o assunto cruzou com a série sobre Talento que falei semana passada. Se ainda não assistiu clique no link acima. Você já vai entender porque.</p>
<p>Gyakkyou Burai Kaiji: Ultimate Survivor foi ao ar em 2007
e 2008, produzido pelo lendário estúdio MadHouse, então sabemos que a qualidade de animação é excelente e, no melhor estilo MadHouse, é mais dark e adulto do que os animes que a maioria de vocês estão acostumados.</p>
<p>O autor original é Nobuyuki Fukumoto, que escreveu Kaiji em 1996. Antes desse anime também saiu outro que já entrou pra minha lista pra assistir, Touhai Densetsu Akagi que assim como Kaiji é um thriller psicológico, mas de Mahjong, tudo do mesmo autor e do mesmo estúdio.</p>
<p>Kaiji não é um battle manga como Naruto ou Dragonball, mas eu diria que é a mesma adrenalina, a tensão psicológica é definitivamente alta, nenhum capítulo que vi até agora (e eu estou no 18o no momento em que gravei este vídeo) é um filler ou enrolação, e é o tipo que você não se aguenta de esperar pra ver o próximo episódio.</p>
<p>Eu expliquei em detalhes sobre prática deliberada no vídeo anterior, mas uma coisa muito difícil de explicar é como de fato isso funciona. E um dos problemas é que você começa pensando em algo pequeno e isolado. Se começou a academia, você pensa, vou aplicar prática deliberada no cross-fit ou na maratona ou qualquer coisa assim.</p>
<p>Mas prática deliberada é nada mais, nada menos, que disciplina. A boa e velha. E disciplina não é uma coisa que você liga e desliga pra uma atividade. Esse é um dos maiores problemas: eu disse que o importante não é o resultado, é o processo. Processo é o caminho que leva você do ponto A ao ponto B..</p>
<p>E pra piorar a situação, a maioria das pessoas talvez só entenda isso quando tiver chegado perto da metade da vida. Aos 20 anos é impossível ter essa noção de forma clara. O máximo que você vai conseguir, é fazer o que eu fiz: dar um salto de fé.</p>
<p>(&hellip;)</p>
<p>No mundo moderno, todo mundo é imediatista, nós evoluímos ao ponto em que podemos nos dar ao luxo de sermos imediatistas, pense nisso. Todo mundo até se compromete a se matar por um curto período de tempo contanto que exista um resultado palpável logo na sequência. Por exemplo, se matar por 1 ano estudando como se não houvesse amanhã, mas sabendo que na sequência o prêmio é passar no vestibular da melhor universidade.</p>
<p>Mas a vida não funciona assim. E embora eu corra o risco de soar como um tiozão dando sermão, considere algumas verdades fundamentais:</p>
<ol>
<li>
<p>a vida não é justa, ela não foi feita pra ser justa. Não há absolutamente nada na fundação da vida que um dia vai garantir igualdade e justiça pra todos. Igualdade e justiça são abstrações da nossa civilização. Veja qualquer ecossistema natural e pergunte ao rato se ele acha justo ser caçado pelo gato, ou o cervo pelo leão. O ciclo da vida é injusto. A natureza é injusta, por isso ela entra em equilíbrio. Equilíbrio não é estático, é um sistema dinâmico, um dia da caça outro do caçador.</p>
</li>
<li>
<p>a vida é meritocrática, meritocracia não tem como premissa a igualdade. Mérito é pura e simplesmente fazer o melhor com o que você tem. Só isso. Se você tem menos que outro competidor e mesmo assim conseguiu superá-lo, isso é mérito. Não existe mérito automático. Mérito é uma conquista, é o cervo que conseguiu correr um pouco mais rápido que o leão e sobreviveu aquele dia. Ë seleção natural.</p>
</li>
<li>
<p>as pessoas não reagem como você gostaria que elas reagissem. enfie isso na sua cabeça e você vai ser mais feliz. Você se frustra com as pessoas não porque elas não te entendem, ou porque são burras ou mal caráter, é você que não entende as outras pessoas. Isso se chama &ldquo;falta de empatia&rdquo;.
Você pensa &ldquo;porque esse infeliz não faz o óbvio que é certo?&rdquo; &hellip; só que o &ldquo;óbvio&rdquo; e o &ldquo;certo&rdquo; são conceitos da sua cabeça. Pra outra pessoa &ldquo;o certo&rdquo; é outra coisa diferente e, nesse caso, ele está simplesmente agindo segundo o que ele acredita, não o que você acredita. E isso é lógico. Você se frustra porque assume que todo mundo deveria pensar igual. Bem vindo ao conceito de liberdade e livre arbítrio.</p>
</li>
</ol>
<p>Existem outros fatores importantes mas pro vídeo de hoje esses 3 são suficientes. Eu precisaria de vários vídeos explicando em detalhes o porque dessas afirmações serem verdadeiras acima de dúvida razoável e o que fazer em cada uma delas na prática.
Provavelmente eu vou fazer isso.</p>
<p>Mas, e aqui eu retorno ao tópico deste vídeo. Kaiji. Em 2 curtos episódios essa série explicou esses pontos de maneira mais simples e mais eficaz do que eu conseguiria em 10 vídeos. Claro, é um anime, é superficial, mas talvez justamente por isso seja tão efetivo.</p>
<p>Kaiji é um sandbox, é basicamente um simulador social. Um experimento social tenta colocar as pessoas num ambiente controlado, remover todas as variáveis externas e deixar as pessoas se comportarem em situações extremas. É quando o melhor e o pior das pessoas vem à tona.</p>
<p>Breaking bad é um dos temas do anime. Kaiji é um jovem de 20 e poucos anos, como muitos de vocês assistindo. Sem um tostão no bolso, gosta de procrastinar, não sabe o que fazer da vida, e fica esperando uma grande chance cair no colo. Todo mundo já fez isso.</p>
<p>Kaiji é uma alegoria ou uma parábola, não é pra levar ao pé da letra. Ela serve de perspectiva pra você. Vai rolar um pouquinho de spoilers a partir de agora mas não se preocupe é só de um trecho da série. Não vai estragar a série toda.</p>
<p>Nesse ponto você precisa saber que por diversos motivos Kaiji se endividou com um grupo tipo Yakuza. Das duas uma, ou ele vai ter que viver como escravo da Yakuza e dar praticamente todo o pouco salário que ganha só pra conseguir pagar os juros da dívida pelos próximos 16 anos ou ele participa de uma série de jogos desse grupo, jogos desenhados pra entreter caras ricos e entediados que querem se divertir.</p>
<p>(clip)</p>
<p>Só que esses jogos são tudo ou nada. Se você ganha, se livra de todas as dívidas e ainda pode sair com algum dinheiro. Se você perde, na pior das hipóteses sai com ainda mais dívida ou, na melhor das hipóteses, você morre.</p>
<p>Nesse jogo estão diversos outros caras endividados. No começo eles se simpatizam até porque estão todos no mesmo barco. Mas quando a coisa realmente aperta, no final cada um pensa por si só.</p>
<p>(clip)</p>
<p>Cada etapa desses jogos são decisões de moral, o que eu faço? Morro ou mato? Todo mundo em algum momento se sente numa situação dessas. Todo mundo se acha o bastião da moral e dos bons costumes, menos quando está ele mesmo numa situação ruim, daí o que as pessoas fazem? Acham justificativas. Todo discurso bonitinho de moral cai por terra na primeira dificuldade, por isso eu tenho zero paciência pra quem gosta de cagar regra de moralidade. Nenhum de nós jamais vai ser 100% moralmente perfeito. Julgar os outros pela moral é perda de tempo.</p>
<p>E nessa série, assim como na vida real, quando você acha que venceu a batalha, descobre que a guerra é muito maior. Você acabou de fazer o que achou que deveria, inclusive traindo sua moral. &ldquo;Foi por um bem maior&rdquo; você pensa, e vem o balde de água fria, com um desafio ainda pior.</p>
<p>(clip)</p>
<p>Eu comecei a ver essa série porque um dos tops da Crunchyroll agora é o Mr Tonegawa que é um spin off do Kaiji. E o tal Mr Tonegawa aparece aqui também e o cara é sensacional, agora entendi porque ele tem uma série só dele.
Eu gosto de pensar que eu tenho um Tonegawa na minha cabeça me dizendo mais ou menos as mesmas coisas. Durante muitos momentos da minha vida eu fui como um Kaiji e tendo um Tonegawa martelando dentro da minha cabeça sempre me ajudou. Vamos ver:</p>
<p>(clip tonegawa)</p>
<p>Eu não sei como funciona pra todo mundo porque eu só conheço dentro da minha cabeça. Mas eu me identifiquei muito. De qualquer forma, de volta ao jogo, nesse cenário de atravessar caminhando na barra entre os dois prédios, você poderia pensar: porque eles só não agarram na barra e vão se arrastando então? Ah esse autor pensou em tudo.</p>
<p>(clip eletrico)</p>
<p>Também me identifiquei com isso porque eu tenho a mania de colocar obstáculos que nem existem só pra ver se eu consigo superar. E são coisas pequenas, às vezes eu me coloco um prazo absurdo. Ou me forço a fazer coisas que nem precisaria, tipo agora, fazer 2 vídeos por semana. É meu Tonegawa eletrificando minha ponte antes de eu caminhar.</p>
<p>Todo mundo deveria ter um Tonegawa. Pra ocasiões onde ficamos empacados:</p>
<p>(clip esperando)</p>
<p>Se ainda não passaram por isso, vocês vão passar, aquele momento em que ou vai ou racha, que não tem mais volta. Você tenta de tudo pra ter pensamento positivo, se animar, &ldquo;acreditar&rdquo; que é possível.</p>
<p>(clip acreditar)</p>
<p>Mesmo frente à uma situação quase impossível, você vai encontrar alguém que vai te dizer que é possível. Interessante como um monte de gente que nunca atravessou a ponte de verdade consegue dizer pros outros que vai conseguir. Nunca entendi esses coaches modernos.</p>
<p>Eu dei muita risada ne sse clip porque pareceu um desses coaches, vendedor de metodologia, eu sempre vejo eles dessa forma:</p>
<p>(clip metodologia)</p>
<p>E da mesma forma como quando eu vejo um vendedor de metodologia, coach, guru, especialista, empreendedor serial &hellip; o Tonegawa na minha cabeça pensa a mesma coisa:</p>
<p>(clip tonegawa)</p>
<p>Eu falo com um certo desdém de todos esses títulos pomposos mas na realidade eu não acho que eles estão querendo de fato enganar ninguém, eles estão até sendo sinceros. Como o Kaiji. Mas são ingênuos, porque a realidade é muito pior do que se imagina, como disse Tonegawa, eles vão durar os primeiros 5 metros. E todo coach que se preza fica com as pessoas só durante os primeiros 5 metros da ponte. E vamos ver um empreendedor de primeira viagem querendo atravessar a ponte, agora que ele aprendeu a metodologia.</p>
<p>(clip entrando na ponte)</p>
<p>Todo workshop, evento, meetup, é igual o que acabamos de ver o Kaiji dizer, mas poucos chegam nessa conclusão a tempo e mesmo quando entendem isso, continuam como um bando de Lemmings. Mas dê um pouco de tempo e a realidade bate na sua porta, brutalmente e imediatamente como um demônio. De novo, Tonegawa já sabia disso:</p>
<p>(clip tonegawa)</p>
<p>Quando eu disse no vídeo anterior que é muito difícil só ir aprendendo enquanto você vai fazendo, tipo, acabei de sair da faculdade, não tenho idéia do que estou fazendo mas vou abrir uma empresa, uma startup, vou aprendendo enquanto vou fazendo. Você vai em eventos, vê grupos empolgados como o Kaiji acima, risca seu tênis pra alinhar na ponte, segue a metodologia, mas na hora H é assim que você vai se sentir:</p>
<p>(clip dando pra trás)</p>
<p>Finalmente, a maioria nunca realmente treinou pra isso. É na hora H na zona de pânico de Noel Tichy, que você vai arriscar tudo. É uma aposta, um jogo de cassino. Se você não tiver estrutura pra aguentar, você vai ceder.</p>
<p>(clip oota)</p>
<p>Pra cada uma história de sucesso precoce que você vê publicado num Techcrunch ou Buzzfeed existem dezenas de caras como Ootas que não geram notícia nenhuma, que no começo acharam que poderiam ir até o fim, mas foram derrotados e esquecidos pela realidade.</p>
<p>E aí, você vê outros iguais a você falhando, fracassando, caindo, e você entra em desespero. E é pior ainda se tentar voltar atrás.</p>
<p>(clip voltando atrás)</p>
<p>E agora?</p>
<p>No cenário desse jogo, existe o Tonegawa que representa um grupo de mafiosos Yakuza, e vários ricos que estão assistindo a isso enquanto jantam e bebem, apostando em quem vai ganhar e nem aí pra quem vai perder.</p>
<p>Eu sei que é uma metáfora extrema, mas pensem neles como seus investidores, venture capitalists, fundos de investimento. O que você acha que eles vão fazer se você falhar? Cair com você?</p>
<p>E como Tonegawa disso, 200 mil dólares é muito dinheiro. E você acha que soltando fogos de artifício de segunda categoria e clamando frases de efeito, vai fazer jus a esse investimento? Se cair, você cai sozinho.</p>
<p>E se você já se comprometeu até o pescoço, e no meio da travessia descobre que não tem peito suficiente pra continuar, que opções você tem?</p>
<p>(clip pede pra desistir)</p>
<p>Pior ainda, sabe o que começa a acontecer na maioria dos grupos de amadores que se juntam só com frases de efeito e palavras bonitas mas que tem pouca ação?</p>
<p>(clip todo mundo caindo)</p>
<p>Como eu disse, o caminho pra grandeza é solitário. Por mais que seria bacana se fosse possível que todos andássemos juntos e ritmados pro mesmo lugar, a realidade é que cada um trilha seu caminho.</p>
<p>(clip solidao)</p>
<p>Cada um trilha seu caminho. O caminho pra cima é solitário, o caminho do empreendedor - e quando eu digo empreendedor não quer dizer só quem abre empresa, é qualquer um que decide empreender qualquer coisa, incluindo você mesmo. Esse caminho é o mais solitário de todos.</p>
<p>Mas voltando pra realidade. Essa alegoria é terrivelmente cruel. É cruel mas é uma boa ilustração do que os espera no mundo real.</p>
<p>Se você conseguiram se engajar na história do Kaiji, talvez se sintam tensos agora, estejam torcendo pra que eles consigam atravessar, talvez estejam com raiva do Tonegawa por ser tão frio.</p>
<p>Mas não se esqueçam de uma realidade: o Tonegawa e esse grupo não forçou ninguém a estar nessa situação. Os jogadores se colocaram nessa situação sozinhos. Na verdade, esse jogo é uma benção pra eles. Lembram do que o Tonegawa disse? Todas as más decisões que eles tomaram na vida os levou até aquele ponto. Vítimas da sociedade? Não, isso é só uma desculpa.</p>
<p>Mesmo que não seja verdade, encare dessa forma: onde você está nesse exato momento é o resultado de todas as decisões que você tomou até agora. Só porque você não consegue imaginar outros caminhos alternativos, não quer dizer que eles não existem. Onde eu estou neste momento é o resultado de todas as boas e de todas as más decisões que eu tomei, sozinho. Se eu tenho problemas, não é culpa de ninguém a não ser de mim mesmo, e o mesmo vale pra todos vocês.</p>
<p>A vida real é injusta. Ela não foi feita pra ser justa. Alguns começaram com muito mais vantagens que nós; e muitos deles, mesmo com todas as vantagens também estão atravessando a ponte como nós. Não importa o que você tem se não consegue tomar as decisões que precisa, no momento que precisa.</p>
<p>Sua vida é o conjunto de todas as suas decisões. Livre arbítrio tem essa desvantagem: se tudo der errado, a culpa é sua, e só sua. Não importa quantos tweets mal criados você poste apontando dedos pra todo mundo.</p>
<p>Mas a vantagem do livre arbítrio é que nunca é tarde. O Tonegawa não é o vilão da história. E é essa uma das coisas que eu mais gostei dessa série até agora. O vilão da história são as más decisões dos protagonistas. O Tonegawa representa a consciência que você deveria tentar ouvir. Ele diz só a verdade. A verdade cruel que nós não queremos ouvir.</p>
<p>Eu também sinto dó de mim mesmo algumas vezes. Quando eu explico coisas como no vídeo anterior, fica parecendo que eu fiz o caminho certinho, passo a passo sem errar nada. É o contrário, eu só sei que aquele funciona porque eu tentei outros que não funcionam como o Kaiji. Eu também atravessei aquela ponte. Eu vi com meus próprios olhos as pessoas caindo dela. E eu me vi no mesmo desespero quase caindo também. E não quer dizer que eu não vou ter que subir nessa ponte de novo. E acredite, você nunca está preparado o suficiente.</p>
<p>No vídeo anterior eu disse o seguinte: &ldquo;Os melhores fixam objetivos que não são sobre o resultado mas sobre o processo de chegar no resultado.&rdquo; Pois bem, eu não quis dizer o processo só de uma atividade isolada como programar ou fazer academia. Sua vida inteira é um processo em andamento.</p>
<p>O objetivo? Chegar no dia da sua morte sabendo que você não deixou de fazer o que deveria ter feito, seja lá o que isso for.</p>
<p>Ufa, esse episódio ficou dark né? A realidade não é sempre colorida, mas é melhor entender isso antes do que depois. Você já teve que atravessar essa ponte? Como foi pra você? Compartilhe com a gente nos comentários abaixo, se curtiram mandem um joinha, assinem o canal e clique no sininho. A gente se vê, até mais!</p>
]]></content:encoded><category>anime-e-manga</category><category>aprendizado</category><category>psicologia</category><category>akitando</category></item><item><title>[Manga-Downloadr] Melhorando o Crystal/Ruby de rajadas para fluxo de pool</title><link>https://akitaonrails.github.io/2016/06/07/manga-downloadr-melhorando-o-crystal-ruby-de-rajadas-para-fluxo-de-pool/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/06/07/manga-downloadr-melhorando-o-crystal-ruby-de-rajadas-para-fluxo-de-pool/</guid><pubDate>Tue, 07 Jun 2016 17:13:00 -0300</pubDate><description>&lt;p&gt;Ontem &lt;a href="http://www.akitaonrails.com/2016/06/06/manga-downloadr-portando-de-crystal-para-ruby-e-um-pouco-de-jruby"target="_blank" rel="noopener"&gt;eu publiquei&lt;/a&gt; como construí uma nova implementação do Manga Reader Downloader em Crystal, portado para Ruby, testado em JRuby e comparado com Elixir. Só pra recapitular, esses foram os resultados pra buscar capítulos, páginas e links de imagens de um mangá de exemplo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MRI Ruby 2.3.1: 57 segundos&lt;/li&gt;
&lt;li&gt;JRuby 9.1.1.0: 45 segundos&lt;/li&gt;
&lt;li&gt;Crystal 0.17.0: 59 segundos&lt;/li&gt;
&lt;li&gt;Elixir: 14 segundos&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eu também disse que era uma comparação injusta, já que a versão Elixir usa um algoritmo diferente - e obviamente mais eficiente.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Ontem <a href="http://www.akitaonrails.com/2016/06/06/manga-downloadr-portando-de-crystal-para-ruby-e-um-pouco-de-jruby"target="_blank" rel="noopener">eu publiquei</a> como construí uma nova implementação do Manga Reader Downloader em Crystal, portado para Ruby, testado em JRuby e comparado com Elixir. Só pra recapitular, esses foram os resultados pra buscar capítulos, páginas e links de imagens de um mangá de exemplo:</p>
<ul>
<li>MRI Ruby 2.3.1: 57 segundos</li>
<li>JRuby 9.1.1.0: 45 segundos</li>
<li>Crystal 0.17.0: 59 segundos</li>
<li>Elixir: 14 segundos</li>
</ul>
<p>Eu também disse que era uma comparação injusta, já que a versão Elixir usa um algoritmo diferente - e obviamente mais eficiente.</p>
<p>Era a &ldquo;primeira-versão-que-funcionou&rdquo;, então resolvi ir adiante e melhorar as implementações. Na versão Ruby/JRuby adicionei a gem <a href="https://github.com/meh/ruby-thread"target="_blank" rel="noopener">thread</a> pra ter uma implementação razoável de Thread pool que funciona tanto em Ruby quanto em JRuby. Eu provavelmente deveria ter usado Concurrent-Ruby, mas estava tendo problemas pra fazer o FixedThreadPool funcionar.</p>
<p>Enfim, agora todas as versões vão ter um pool constante de requisições rodando e podemos fazer uma comparação melhor.</p>
<p>Outra coisa que pode ter prejudicado os resultados do Crystal é que ele parece ter uma <a href="https://github.com/crystal-lang/crystal/issues/2660"target="_blank" rel="noopener">implementação defeituosa de DNS resolver</a>, então por enquanto eu só adicionei o IP do Manga Reader direto no meu <code>/etc/hosts</code> pra evitar exceções de <code>getaddrinfo: Name or service not known</code>.</p>
<p>Pra começar, vamos testar a mesma implementação Elixir e, como esperado, o resultado continua o mesmo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>11,49s user 0,82s system 77% cpu 15,827 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora, o MRI Ruby com o algoritmo de &ldquo;rajada em lote&rdquo; estava levando 57 segundos e essa nova implementação usando ThreadPool roda muito melhor:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>12,67s user 0,92s system 50% cpu 27,149 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Em Crystal é um pouco mais complicado, porque não tem como implementar o equivalente a um &ldquo;Fiber Pool&rdquo;. O que temos que fazer é um loop infinito até o último processo sinalizar a quebra do loop. Dentro do loop criamos um número máximo de fibers, esperamos cada uma sinalizar que terminou através de um channel individual e voltamos ao loop pra criar uma nova fiber, e por aí vai. Comparado aos 59 segundos de ontem, isso é bem melhor:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>5,29s user 0,33s system 26% cpu 21,166 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A versão JRuby não está tão rápida. Ainda melhor que os 45 segundos de ontem, mas agora está perdendo até pro MRI:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>49,24s user 1,41s system 146% cpu 34,602 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Tentei usar a flag <code>--dev</code> pra <a href="https://github.com/jruby/jruby/wiki/Improving-startup-time"target="_blank" rel="noopener">tempo de inicialização mais rápido</a> e melhora um pouco, ficando mais perto do MRI:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>22,26s user 0,99s system 76% cpu 30,320 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Não sei se dá pra melhorar mais, mas qualquer dica é bem-vinda - não esqueçam de comentar abaixo.</p>
<p>Então, Elixir continua pelo menos duas vezes mais rápido que Crystal nesse momento. Mas isso também demonstra como um algoritmo diferente faz uma diferença enorme onde importa. Eu provavelmente consigo ajustar mais um pouco, mas isso já basta por enquanto.</p>
<h3>Mudando a implementação Ruby pra usar ThreadPool<span class="hx:absolute hx:-mt-20" id="mudando-a-implementação-ruby-pra-usar-threadpool"></span>
    <a href="#mudando-a-implementa%c3%a7%c3%a3o-ruby-pra-usar-threadpool" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;thread/pool&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">MangaDownloadr</span>
</span></span><span class="line"><span class="cl">  <span class="k">class</span> <span class="nc">Concurrency</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">collection</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">pool</span>    <span class="o">=</span> <span class="no">Thread</span><span class="o">.</span><span class="n">pool</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">download_batch_size</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">mutex</span>   <span class="o">=</span> <span class="no">Mutex</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl">      <span class="n">results</span> <span class="o">=</span> <span class="o">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">collection</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">        <span class="n">pool</span><span class="o">.</span><span class="n">process</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">engine</span>  <span class="o">=</span> <span class="vi">@turn_on_engine</span> <span class="p">?</span> <span class="vi">@engine_klass</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">domain</span><span class="p">)</span> <span class="p">:</span> <span class="kp">nil</span>
</span></span><span class="line"><span class="cl">          <span class="n">reply</span> <span class="o">=</span> <span class="n">block</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="n">engine</span><span class="p">)</span><span class="o">&amp;.</span><span class="n">flatten</span>
</span></span><span class="line"><span class="cl">          <span class="n">mutex</span><span class="o">.</span><span class="n">synchronize</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">            <span class="n">results</span> <span class="o">+=</span> <span class="p">(</span> <span class="n">reply</span> <span class="o">||</span> <span class="o">[]</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="k">end</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">      <span class="n">pool</span><span class="o">.</span><span class="n">shutdown</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">results</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A ideia é que vamos ter um número fixo de threads nativas spawnadas (nesse caso, determinado por <code>@config.download_batch_size</code>). Conforme uma thread termina, ela puxa um novo link do Array <code>collection</code>, funcionando essencialmente como uma &ldquo;fila&rdquo; a ser esvaziada.</p>
<p>Os resultados são acumulados no Array <code>results</code>. Como várias threads podem querer modificá-lo ao mesmo tempo, temos que sincronizar o acesso através de um Mutex.</p>
<p>Dessa forma sempre temos uma quantidade fixa de workers fazendo requisições constantemente em vez de fatiar o Array <code>collection</code> e fazer rajadas como na versão anterior.</p>
<h3>Mudando a implementação Crystal pra simular um Pool de Fibers<span class="hx:absolute hx:-mt-20" id="mudando-a-implementação-crystal-pra-simular-um-pool-de-fibers"></span>
    <a href="#mudando-a-implementa%c3%a7%c3%a3o-crystal-pra-simular-um-pool-de-fibers" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A versão Crystal ficou um pouco mais complicada porque não achei nenhuma biblioteca de pool pra usar. Encontrei uma implementação rústica de pool <a href="http://stackoverflow.com/a/30854065/1529907"target="_blank" rel="noopener">neste post do stackoverflow</a> e consegui implementar uma versão melhorada num shard novo, pra que vocês possam aproveitar nos seus projetos. Confiram o código fonte em <a href="https://github.com/akitaonrails/fiberpool"target="_blank" rel="noopener">akitaonrails/fiberpool</a>. Foi assim que adicionei no meu projeto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">dependencies</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">fiberpool</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">github</span><span class="p">:</span><span class="w"> </span><span class="l">akitaonrails/fiberpool</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E foi assim que usei. Repare que a lógica em si é bem próxima da versão MRI Ruby, mas usando um &ldquo;Fiber Pool&rdquo; em vez de Thread Pool.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;fiberpool&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">CrMangaDownloadr</span>
</span></span><span class="line"><span class="cl">  <span class="n">struct</span> <span class="no">Concurrency</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="vi">@config</span> <span class="p">:</span> <span class="no">Config</span><span class="p">,</span> <span class="vi">@turn_on_engine</span> <span class="o">=</span> <span class="kp">true</span><span class="p">);</span> <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">collection</span> <span class="p">:</span> <span class="nb">Array</span><span class="p">(</span><span class="n">A</span><span class="p">)</span><span class="sc">?,</span> <span class="n">engine_class</span> <span class="p">:</span> <span class="n">E</span><span class="o">.</span><span class="n">class</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span> <span class="p">:</span> <span class="n">A</span><span class="p">,</span> <span class="n">E?</span> <span class="o">-&gt;</span> <span class="nb">Array</span><span class="p">(</span><span class="n">B</span><span class="p">)</span><span class="sc">?)</span> <span class="p">:</span> <span class="nb">Array</span><span class="p">(</span><span class="n">B</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">results</span> <span class="o">=</span> <span class="o">[]</span> <span class="n">of</span> <span class="n">B</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="n">collection</span>
</span></span><span class="line"><span class="cl">        <span class="n">pool</span> <span class="o">=</span> <span class="no">Fiberpool</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">collection</span><span class="p">,</span> <span class="vi">@config</span><span class="o">.</span><span class="n">download_batch_size</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">pool</span><span class="o">.</span><span class="n">run</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="n">engine</span> <span class="o">=</span> <span class="k">if</span> <span class="vi">@turn_on_engine</span>
</span></span><span class="line"><span class="cl">                     <span class="n">engine_class</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">domain</span><span class="p">,</span> <span class="vi">@config</span><span class="o">.</span><span class="n">cache_http</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                   <span class="k">end</span>
</span></span><span class="line"><span class="cl">          <span class="k">if</span> <span class="n">reply</span> <span class="o">=</span> <span class="n">block</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="n">engine</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">concat</span><span class="p">(</span><span class="n">reply</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="k">end</span>
</span></span><span class="line"><span class="cl">        <span class="k">end</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">      <span class="n">results</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mas, de novo, isso é muito intensivo em I/O e tanto a versão Ruby quanto a Crystal aproveitam o fato de poderem fazer mais trabalho enquanto esperam uma requisição HTTP terminar.</p>
<p><a name="reproducing-tests"> </a></p>
<h3>Reproduzindo os Testes<span class="hx:absolute hx:-mt-20" id="reproduzindo-os-testes"></span>
    <a href="#reproduzindo-os-testes" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Implementei um &ldquo;Modo de Teste&rdquo; nas 3 implementações. Vocês podem clonar dos meus repositórios:</p>
<ul>
<li><a href="https://github.com/akitaonrails/cr_manga_downloadr"target="_blank" rel="noopener">Versão Crystal</a></li>
<li><a href="https://github.com/akitaonrails/manga-downloadr"target="_blank" rel="noopener">Versão Ruby/JRuby</a></li>
<li><a href="https://github.com/akitaonrails/ex_manga_downloadr"target="_blank" rel="noopener">Versão Elixir</a></li>
</ul>
<p>E vocês podem rodar o modo de teste assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># crystal:
time ./cr_manga_downloadr --test

# MRI:
time bin/manga-downloadr --test

# JRuby (você precisa editar o Gemfile pra descomentar a engine JRuby):
time jruby --dev -S bin/manga-downloadr --test

# Elixir:
time ./ex_manga_downloadr --test</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso vai rodar só o fetch de capítulos, páginas e links de imagens, pulando o download real das imagens, otimização via mogrify e compilação do PDF. Essas partes puladas demoram demais e não dizem nada sobre as linguagens testadas.</p>
<p>E se quiserem testar só as partes intensivas em CPU e evitar qualquer interferência de rede, vocês podem ligar o modo HTTP Cache e rodar os testes duas vezes pra que a primeira execução cacheie tudo, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># crystal:
time ./cr_manga_downloadr --test --cache

# MRI:
time bin/manga-downloadr --test --cache

# JRuby (você precisa editar o Gemfile pra descomentar a engine JRuby):
time jruby --dev -S bin/manga-downloadr --test --cache

# Elixir:
time CACHE_HTTP=true ./ex_manga_downloadr --test</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Então, <strong>com todas as requisições já cacheadas</strong> esses são os resultados:</p>
<p>Elixir:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>7,13s user 0,24s system 331% cpu 2,227 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>MRI Ruby:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>5.590000   0.180000   5.770000 (  5.678714)
5,87s user 0,21s system 101% cpu 5,996 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>JRuby:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>10.580000   0.180000  10.760000 (  3.184472)
14,54s user 0,44s system 262% cpu 5,716 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Crystal:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>1.610000   0.050000   1.660000 (  1.344123)
1,62s user 0,06s system 124% cpu 1,350 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>As versões Ruby/JRuby/Crystal têm benchmarks internos pra remover o tempo de startup (por isso elas têm 2 linhas de tempos).</p>
<p>Então, Elixir é muito rápido. Leva mais ou menos 2 segundos pra parsear todos os 1.900 arquivos HTML procurando os links.</p>
<p>Ruby é o mais lento, obviamente. Leva quase 6 segundos.</p>
<p>A versão JRuby também leva quase 6 segundos, mas internamente processa em 3 segundos; o resto é tempo de startup e warm up da JVM por baixo.</p>
<p>E Crystal é o mais rápido, como vocês esperariam, porque é um binário super otimizado fazendo operações CPU bound, marcando pouco mais de 1 segundo.</p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Mesmo com algoritmos mais ou menos parecidos, Elixir continua ganhando por uma margem muito grande no processo total (com as requisições HTTP externas).</p>
<p>Tem mais coisa em jogo além de velocidade de interpretador/compilador, além de infraestrutura de single-thread, multi-thread e fibers.</p>
<p>Também temos a maturidade das respectivas bibliotecas padrão (incluindo stack TCP, bibliotecas HTTP client, operações de String/Array/Regex, etc.) e bibliotecas de terceiros (libXML, Nokogiri, etc.). Então tem muita coisa que pode interferir nos testes. Eu chutaria que a stdlib do Crystal, especialmente as partes de rede, ainda não está bem testada em batalha nesse momento (pré 1.0!).</p>
<p>Então, o resumo com os novos resultados é esse:</p>
<ul>
<li>MRI Ruby 2.3.1: 27 segundos</li>
<li>JRuby 9.1.1.0: 30 segundos</li>
<li>Crystal 0.17.0: 21 segundos</li>
<li>Elixir: 15 segundos</li>
</ul>
<p>Me avisem se tiverem ideias pra deixar tudo ainda mais rápido!</p>
]]></content:encoded><category>anime-e-manga</category><category>performance</category><category>sistemas-distribuidos</category></item><item><title>[Manga-Downloadr] Portando de Crystal para Ruby (e um pouco de JRuby)</title><link>https://akitaonrails.github.io/2016/06/06/manga-downloadr-portando-de-crystal-para-ruby-e-um-pouco-de-jruby/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/06/06/manga-downloadr-portando-de-crystal-para-ruby-e-um-pouco-de-jruby/</guid><pubDate>Mon, 06 Jun 2016 18:06:00 -0300</pubDate><description>&lt;p&gt;Eu tenho um projetinho pessoal antigo chamado &lt;a href="http://www.akitaonrails.com/2014/12/17/small-bites-downloader-para-mangareader-kindle-edition"target="_blank" rel="noopener"&gt;Manga Downloadr&lt;/a&gt; que publiquei em 2014. Era uma versão bem tosca. Eu estava experimentando com requisições assíncronas usando Typhoeus e no final o código acabou ficando uma bagunça absurda.&lt;/p&gt;
&lt;p&gt;A natureza do Manga Downloader original é a de um web crawler: ele baixa páginas HTML, faz parse para encontrar coleções de links e segue baixando até que um conjunto de imagens seja salvo. Depois eu organizo tudo em volumes, otimizo para caber na resolução de tela do Kindle e compilo em arquivos PDF. Isso faz desse projeto um exercício interessante para tentar fazer requisições HTTP concorrentes e processar os resultados.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Eu tenho um projetinho pessoal antigo chamado <a href="http://www.akitaonrails.com/2014/12/17/small-bites-downloader-para-mangareader-kindle-edition"target="_blank" rel="noopener">Manga Downloadr</a> que publiquei em 2014. Era uma versão bem tosca. Eu estava experimentando com requisições assíncronas usando Typhoeus e no final o código acabou ficando uma bagunça absurda.</p>
<p>A natureza do Manga Downloader original é a de um web crawler: ele baixa páginas HTML, faz parse para encontrar coleções de links e segue baixando até que um conjunto de imagens seja salvo. Depois eu organizo tudo em volumes, otimizo para caber na resolução de tela do Kindle e compilo em arquivos PDF. Isso faz desse projeto um exercício interessante para tentar fazer requisições HTTP concorrentes e processar os resultados.</p>
<p>Um ano depois eu estava aprendendo Elixir. O Manga Downloadr era um bom candidato para eu descobrir como implementar a mesma coisa em outra linguagem. Você pode acompanhar o meu processo de aprendizado <a href="http://www.akitaonrails.com/exmangadownloadr"target="_blank" rel="noopener">nessa série de posts</a>.</p>
<p>Finalmente, venho aprendendo mais sobre Crystal, uma plataforma inspirada em Ruby que compila código fonte parecido com Ruby em binários nativos otimizados via LLVM. E de bônus oferece canais e fibras no estilo CSP, parecido com Go, para permitir código concorrente.</p>
<p>Então adaptei minha versão Elixir para Crystal e o resultado é esse código que você encontra no Github como <a href="https://github.com/akitaonrails/cr_manga_downloadr"target="_blank" rel="noopener">akitaonrails/cr_manga_downloadr</a>.</p>
<p>Funciona muito bem e roda bem rápido, limitado principalmente por quantas requisições o MangaReader consegue responder concorrentemente e pela velocidade/confiabilidade da conexão de Internet. E como minha versão Ruby original era um código terrível, era uma boa hora para reescrevê-la. E como Crystal é surpreendentemente próximo de Ruby, decidi portar.</p>
<blockquote>
  <p>A portagem foi quase trivial demais</p>

</blockquote>
<p>Foi praticamente copiar e colar o código Crystal sem as anotações de tipo. E tive que substituir a implementação leve de Fibers e Channel para concorrência pelas tradicionais Threads de Ruby.</p>
<p>A nova versão 2.0 da ferramenta em Ruby pode ser encontrada nesse repositório no Github: <a href="https://github.com/akitaonrails/manga-downloadr"target="_blank" rel="noopener">akitaonrails/manga-downloadr</a>.</p>
<h3>Ruby, Ruby por todo lado!<span class="hx:absolute hx:-mt-20" id="ruby-ruby-por-todo-lado"></span>
    <a href="#ruby-ruby-por-todo-lado" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Transitar entre Ruby e Crystal não é tão difícil. O time do Crystal fez um trabalho fantástico implementando uma Standard Library (stdlib) muito sólida, que se assemelha bastante à do MRI Ruby.</p>
<p>Por exemplo, vamos comparar primeiro um trecho da minha versão Crystal:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">page_link</span> <span class="p">:</span> <span class="nb">String</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">get</span> <span class="n">page_link</span> <span class="k">do</span> <span class="o">|</span><span class="n">html</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="n">images</span> <span class="o">=</span> <span class="n">html</span><span class="o">.</span><span class="n">xpath</span><span class="p">(</span><span class="s2">&#34;//img[contains(@id, &#39;img&#39;)]&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">as</span><span class="p">(</span><span class="no">XML</span><span class="o">::</span><span class="no">NodeSet</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">image_alt</span> <span class="o">=</span> <span class="n">images</span><span class="o">[</span><span class="mi">0</span><span class="o">][</span><span class="s2">&#34;alt&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">image_src</span> <span class="o">=</span> <span class="n">images</span><span class="o">[</span><span class="mi">0</span><span class="o">][</span><span class="s2">&#34;src&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">image_alt</span> <span class="o">&amp;&amp;</span> <span class="n">image_src</span>
</span></span><span class="line"><span class="cl">      <span class="n">extension</span>      <span class="o">=</span> <span class="n">image_src</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;.&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">last</span>
</span></span><span class="line"><span class="cl">      <span class="n">list</span>           <span class="o">=</span> <span class="n">image_alt</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34; &#34;</span><span class="p">)</span><span class="o">.</span><span class="n">reverse</span>
</span></span><span class="line"><span class="cl">      <span class="n">title_name</span>     <span class="o">=</span> <span class="n">list</span><span class="o">[</span><span class="mi">4</span><span class="o">..-</span><span class="mi">1</span><span class="o">].</span><span class="n">join</span><span class="p">(</span><span class="s2">&#34; &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">chapter_number</span> <span class="o">=</span> <span class="n">list</span><span class="o">[</span><span class="mi">3</span><span class="o">].</span><span class="n">rjust</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s1">&#39;0&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">page_number</span>    <span class="o">=</span> <span class="n">list</span><span class="o">[</span><span class="mi">0</span><span class="o">].</span><span class="n">rjust</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s1">&#39;0&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">uri</span> <span class="o">=</span> <span class="no">URI</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">image_src</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="no">CrMangaDownloadr</span><span class="o">::</span><span class="no">Image</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">uri</span><span class="o">.</span><span class="n">host</span> <span class="n">as</span> <span class="nb">String</span><span class="p">,</span> <span class="n">uri</span><span class="o">.</span><span class="n">path</span> <span class="n">as</span> <span class="nb">String</span><span class="p">,</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">title_name</span><span class="si">}</span><span class="s2">-Chap-</span><span class="si">#{</span><span class="n">chapter_number</span><span class="si">}</span><span class="s2">-Pg-</span><span class="si">#{</span><span class="n">page_number</span><span class="si">}</span><span class="s2">.</span><span class="si">#{</span><span class="n">extension</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="k">raise</span> <span class="no">Exception</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&#34;Couldn&#39;t find proper metadata alt in the image tag&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora vamos olhar a versão portada para Ruby:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">page_link</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">get</span> <span class="n">page_link</span> <span class="k">do</span> <span class="o">|</span><span class="n">html</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="n">images</span> <span class="o">=</span> <span class="n">html</span><span class="o">.</span><span class="n">css</span><span class="p">(</span><span class="s1">&#39;#img&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">image_alt</span> <span class="o">=</span> <span class="n">images</span><span class="o">[</span><span class="mi">0</span><span class="o">][</span><span class="s2">&#34;alt&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">image_src</span> <span class="o">=</span> <span class="n">images</span><span class="o">[</span><span class="mi">0</span><span class="o">][</span><span class="s2">&#34;src&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">image_alt</span> <span class="o">&amp;&amp;</span> <span class="n">image_src</span>
</span></span><span class="line"><span class="cl">      <span class="n">extension</span>      <span class="o">=</span> <span class="n">image_src</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;.&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">last</span>
</span></span><span class="line"><span class="cl">      <span class="n">list</span>           <span class="o">=</span> <span class="n">image_alt</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34; &#34;</span><span class="p">)</span><span class="o">.</span><span class="n">reverse</span>
</span></span><span class="line"><span class="cl">      <span class="n">title_name</span>     <span class="o">=</span> <span class="n">list</span><span class="o">[</span><span class="mi">4</span><span class="o">..-</span><span class="mi">1</span><span class="o">].</span><span class="n">join</span><span class="p">(</span><span class="s2">&#34; &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">chapter_number</span> <span class="o">=</span> <span class="n">list</span><span class="o">[</span><span class="mi">3</span><span class="o">].</span><span class="n">rjust</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s1">&#39;0&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">page_number</span>    <span class="o">=</span> <span class="n">list</span><span class="o">[</span><span class="mi">0</span><span class="o">].</span><span class="n">rjust</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s1">&#39;0&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">uri</span> <span class="o">=</span> <span class="no">URI</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">image_src</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="no">Image</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">uri</span><span class="o">.</span><span class="n">host</span><span class="p">,</span> <span class="n">uri</span><span class="o">.</span><span class="n">path</span><span class="p">,</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">title_name</span><span class="si">}</span><span class="s2">-Chap-</span><span class="si">#{</span><span class="n">chapter_number</span><span class="si">}</span><span class="s2">-Pg-</span><span class="si">#{</span><span class="n">page_number</span><span class="si">}</span><span class="s2">.</span><span class="si">#{</span><span class="n">extension</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="k">raise</span> <span class="no">Exception</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&#34;Couldn&#39;t find proper metadata alt in the image tag&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É impressionante como são parecidos, até mesmo nas chamadas de stdlib como <code>URI.parse</code> ou métodos de Array como <code>split</code>.</p>
<blockquote>
  <p>Quando você remove as anotações de tipo da versão Crystal, é 99% Ruby.</p>

</blockquote>
<p>Ruby pouco se importa se você está chamando um método em um objeto Nil - em tempo de compilação. Crystal faz checagens em tempo de compilação e, se ele acha que a chamada de método pode acontecer sobre Nil, simplesmente não compila. Isso é uma grande vitória para evitar bugs sutis.</p>
<p>Em Rails estamos acostumados com o famigerado método <code>#try</code>. O Ruby 2.3 introduziu o <a href="http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/"target="_blank" rel="noopener">safe navigation operator</a>.</p>
<p>Então, em Ruby 2.3 com Rails, ambas as linhas a seguir são válidas:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">obj</span><span class="o">.</span><span class="n">try</span><span class="p">(</span><span class="ss">:something</span><span class="p">)</span><span class="o">.</span><span class="n">try</span><span class="p">(</span><span class="ss">:something2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">obj</span><span class="o">&amp;.</span><span class="n">something</span><span class="o">&amp;.</span><span class="n">something2</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Em Crystal podemos fazer assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">obj</span><span class="o">.</span><span class="n">try</span><span class="p">(</span><span class="o">&amp;.</span><span class="n">something</span><span class="p">)</span><span class="o">.</span><span class="n">try</span><span class="p">(</span><span class="o">&amp;.</span><span class="n">something2</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Então, é parecido. Use com cuidado.</p>
<p>Como mencionei antes, Crystal é próximo de Ruby, mas a intenção dele é ser uma linguagem própria, então não dá para simplesmente carregar Rubygems sem portar. Nesse exemplo eu não tenho Nokogiri para fazer parse de HTML. E é justamente aí que a stdlib brilha: Crystal vem com parsers de XML/HTML e JSON suficientemente bons. Então podemos fazer parse do HTML como XML e usar XPath puro.</p>
<p>No lugar do <code>Net::HTTP</code> do Ruby temos <code>HTTP::Client</code> (mas seus métodos e semântica são surpreendentemente similares).</p>
<p>Existem outras diferenças, por exemplo, esse é o arquivo principal que faz <code>require</code> de todos os outros em Ruby:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="vg">$LOAD_PATH</span><span class="o">.</span><span class="n">unshift</span> <span class="no">File</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="bp">__FILE__</span><span class="p">),</span> <span class="s2">&#34;lib&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;manga-downloadr/records.rb&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;manga-downloadr/downloadr_client.rb&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;manga-downloadr/concurrency.rb&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;manga-downloadr/chapters.rb&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;manga-downloadr/pages.rb&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;manga-downloadr/page_image.rb&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;manga-downloadr/image_downloader.rb&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;manga-downloadr/workflow.rb&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E essa é a versão Crystal do mesmo manifesto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;./cr_manga_downloadr/*&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Por outro lado, precisamos ser um pouco mais explícitos em cada arquivo de código fonte Crystal e declarar as dependências específicas onde forem necessárias. Por exemplo, no arquivo <code>pages.cr</code> ele começa assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;./downloadr_client&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;xml&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">CrMangaDownloadr</span>
</span></span><span class="line"><span class="cl">  <span class="k">class</span> <span class="nc">Pages</span> <span class="o">&lt;</span> <span class="no">DownloadrClient</span><span class="p">(</span><span class="nb">Array</span><span class="p">(</span><span class="nb">String</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Crystal tem menos espaço para &ldquo;mágica&rdquo;, mas ainda assim consegue manter um alto nível de abstração.</p>
<h3>Uma palavra sobre Tipos<span class="hx:absolute hx:-mt-20" id="uma-palavra-sobre-tipos"></span>
    <a href="#uma-palavra-sobre-tipos" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p>Podemos passar a próxima década masturbando sobre tudo que existe sobre Tipos, e vai ser extremamente chato.</p>

</blockquote>
<p>A única coisa que você precisa entender: o compilador precisa conhecer a assinatura dos métodos das classes antes de poder usá-las. Não existe um componente em Runtime que possa introspectar objetos em tempo real, como em Ruby e em outras linguagens dinâmicas (até Objective-C/Swift consegue fazer mais coisa dinâmica do que Crystal).</p>
<p>Na maior parte do tempo o compilador do Crystal é inteligente o bastante para inferir os tipos para você, então você não precisa ser absolutamente explícito. Você deve seguir o que o compilador indica para saber quando usar Type Annotations.</p>
<p>O que pode te assustar de início é a necessidade de Type Annotations, entender Generics e por aí vai. O compilador vai cuspir cada dump de erro assustador, e você vai precisar se acostumar. A maioria dos erros assustadores costuma ser ou uma Type Annotation faltando, ou você tentando chamar um método em um possível objeto Nil.</p>
<p>Por exemplo, se eu mudar a linha a seguir no arquivo de teste <code>page_image_spec.cr</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># linha 8:</span>
</span></span><span class="line"><span class="cl"><span class="n">image</span> <span class="o">=</span> <span class="no">CrMangaDownloadr</span><span class="o">::</span><span class="no">PageImage</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&#34;www.mangareader.net&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="s2">&#34;/naruto/662/2&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># linha 10:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># image.try(&amp;.host).should eq(&#34;i8.mangareader.net&#34;)</span>
</span></span><span class="line"><span class="cl"><span class="n">image</span><span class="o">.</span><span class="n">host</span><span class="o">.</span><span class="n">should</span> <span class="n">eq</span><span class="p">(</span><span class="s2">&#34;i8.mangareader.net&#34;</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A linha comentada reconhece que a instância <code>image</code> pode vir como Nil, então adicionamos uma chamada explícita de <code>#try</code> no spec.</p>
<p>Se tentarmos compilar sem esse reconhecimento, esse é o erro que o compilador vai despejar em você:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ crystal spec                                                        [
Error in ./spec/cr_manga_downloadr/page_image_spec.cr:10: undefined method &#39;host&#39; for Nil (compile-time type is CrMangaDownloadr::Image?)

    image.host.should eq(&#34;i8.mangareader.net&#34;)
          ^~~~

=============================================================================

Nil trace:

  ./spec/cr_manga_downloadr/page_image_spec.cr:8

        image = CrMangaDownloadr::PageImage.new(&#34;www.mangareader.net&#34;).fetch(&#34;/naruto/662/2&#34;)
...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Tem um stacktrace gigante depois desse trecho, mas você só precisa prestar atenção nas primeiras linhas, que já dizem o que está errado: &ldquo;undefined method &lsquo;host&rsquo; for Nil (compile-time type is CrMangaDownloadr::Image?)&rdquo;. Se você sabe ler, na maior parte do tempo não vai ter problema nenhum.</p>
<p>Agora, <code>Chapters</code>, <code>Pages</code>, <code>PageImage</code> (todos subclasses de <code>DownloadrClient</code>) são basicamente a mesma coisa: fazem requisições com <code>HTTP::Client</code>.</p>
<p>É assim que a classe <code>Pages</code> é implementada:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">CrMangaDownloadr</span>
</span></span><span class="line"><span class="cl">  <span class="k">class</span> <span class="nc">Pages</span> <span class="o">&lt;</span> <span class="no">DownloadrClient</span><span class="p">(</span><span class="nb">Array</span><span class="p">(</span><span class="nb">String</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">chapter_link</span> <span class="p">:</span> <span class="nb">String</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">get</span> <span class="n">chapter_link</span> <span class="k">do</span> <span class="o">|</span><span class="n">html</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">        <span class="n">nodes</span> <span class="o">=</span> <span class="n">html</span><span class="o">.</span><span class="n">xpath_nodes</span><span class="p">(</span><span class="s2">&#34;//div[@id=&#39;selectpage&#39;]//select[@id=&#39;pageMenu&#39;]//option&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">nodes</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">node</span><span class="o">|</span> <span class="o">[</span><span class="n">chapter_link</span><span class="p">,</span> <span class="n">node</span><span class="o">.</span><span class="n">text</span> <span class="n">as</span> <span class="nb">String</span><span class="o">].</span><span class="n">join</span><span class="p">(</span><span class="s2">&#34;/&#34;</span><span class="p">)</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><code>#get</code> é um método da superclasse <code>DownloadrClient</code> que recebe uma String <code>chapter_link</code> e um bloco. O bloco recebe uma coleção de nós <code>html</code> já parseada e podemos brincar com ela, retornando um Array de Strings.</p>
<p>É por isso que temos o <code>(Array(String))</code> ao herdar de <code>DownloadrClient</code>. Vejamos como a superclasse <code>DownloadrClient</code> é implementada.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">CrMangaDownloadr</span>
</span></span><span class="line"><span class="cl">  <span class="k">class</span> <span class="nc">DownloadrClient</span><span class="p">(</span><span class="n">T</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="n">uri</span> <span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span> <span class="p">:</span> <span class="no">XML</span><span class="o">::</span><span class="no">Node</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">response</span> <span class="o">=</span> <span class="vi">@http_client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">uri</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="k">case</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span>
</span></span><span class="line"><span class="cl">      <span class="k">when</span> <span class="mi">301</span>
</span></span><span class="line"><span class="cl">        <span class="n">get</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="o">[</span><span class="s2">&#34;Location&#34;</span><span class="o">]</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span>
</span></span><span class="line"><span class="cl">      <span class="k">when</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">        <span class="n">parsed</span> <span class="o">=</span> <span class="no">XML</span><span class="o">.</span><span class="n">parse_html</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">body</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">block</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">parsed</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">      <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Você pode ver que essa classe recebe um Generic Type e usa ele como o tipo de retorno do bloco yieldado no método <code>#get</code>. O <code>XML::Node -&gt; T</code> é a declaração da assinatura do bloco, enviando <code>XML::Node</code> e recebendo aquilo que <code>T</code> for. Em tempo de compilação, imagine esse <code>T</code> sendo substituído por <code>Array(String)</code>. É assim que dá para criar classes capazes de lidar com qualquer número de Tipos diferentes sem precisar fazer overload para polimorfismo.</p>
<p>Se você vem de Java, C#, Go ou qualquer outra linguagem moderna estaticamente tipada, provavelmente já sabe o que é um Generic.</p>
<p>Você pode ir bem longe com Generics, veja como começa o nosso <code>Concurrency.cr</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Concurrency</span><span class="p">(</span><span class="n">A</span><span class="p">,</span> <span class="n">B</span><span class="p">,</span> <span class="n">C</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">collection</span> <span class="p">:</span> <span class="nb">Array</span><span class="p">(</span><span class="n">A</span><span class="p">)</span><span class="sc">?,</span> <span class="o">&amp;</span><span class="n">block</span> <span class="p">:</span> <span class="n">A</span><span class="p">,</span> <span class="n">C?</span> <span class="o">-&gt;</span> <span class="nb">Array</span><span class="p">(</span><span class="n">B</span><span class="p">)</span><span class="sc">?)</span> <span class="p">:</span> <span class="nb">Array</span><span class="p">(</span><span class="n">B</span><span class="p">)?</span>
</span></span><span class="line"><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="o">[]</span> <span class="n">of</span> <span class="n">B</span>
</span></span><span class="line"><span class="cl">    <span class="n">collection</span><span class="o">.</span><span class="n">try</span> <span class="o">&amp;.</span><span class="n">each_slice</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">download_batch_size</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">batch</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="n">engine</span>  <span class="o">=</span> <span class="k">if</span> <span class="vi">@turn_on_engine</span>
</span></span><span class="line"><span class="cl">                  <span class="n">C</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">domain</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">end</span>
</span></span><span class="line"><span class="cl">      <span class="n">channel</span> <span class="o">=</span> <span class="no">Channel</span><span class="p">(</span><span class="nb">Array</span><span class="p">(</span><span class="n">B</span><span class="p">)</span><span class="sc">?)</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl">      <span class="n">batch</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E é assim que usamos no <code>workflow.cr</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="kp">private</span> <span class="k">def</span> <span class="nf">fetch_pages</span><span class="p">(</span><span class="n">chapters</span> <span class="p">:</span> <span class="nb">Array</span><span class="p">(</span><span class="nb">String</span><span class="p">)</span><span class="sc">?)</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="s2">&#34;Fetching pages from all chapters ...&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="n">reactor</span> <span class="o">=</span> <span class="no">Concurrency</span><span class="p">(</span><span class="nb">String</span><span class="p">,</span> <span class="nb">String</span><span class="p">,</span> <span class="no">Pages</span><span class="p">)</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="vi">@config</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">reactor</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="n">chapters</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">link</span><span class="p">,</span> <span class="n">engine</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="n">engine</span><span class="o">.</span><span class="n">try</span><span class="p">(</span> <span class="o">&amp;.</span><span class="n">fetch</span><span class="p">(</span><span class="n">link</span><span class="p">)</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Nesse exemplo, imagine <code>A</code> sendo substituído por <code>String</code>, <code>B</code> também sendo substituído por <code>String</code> e <code>C</code> sendo substituído por <code>Pages</code> na classe <code>Concurrency</code>.</p>
<p>Essa é a &ldquo;primeira-versão-que-funcionou&rdquo;, então provavelmente está pouco idiomática. Talvez dê para resolver com menos exercício de Generics ou, quem sabe, eu pudesse simplificar com o uso de Macros. Mas está funcionando bem assim.</p>
<p>A versão pura em Ruby acaba ficando assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Concurrency</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">engine_klass</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">,</span> <span class="n">config</span> <span class="o">=</span> <span class="no">Config</span><span class="o">.</span><span class="n">new</span><span class="p">,</span> <span class="n">turn_on_engine</span> <span class="o">=</span> <span class="kp">true</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">collection</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="o">[]</span>
</span></span><span class="line"><span class="cl">    <span class="n">collection</span><span class="o">&amp;.</span><span class="n">each_slice</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">download_batch_size</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">batch</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="n">mutex</span>   <span class="o">=</span> <span class="no">Mutex</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl">      <span class="n">threads</span> <span class="o">=</span> <span class="n">batch</span><span class="o">.</span><span class="n">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Essa versão é muito &ldquo;mais simples&rdquo; em termos de densidade de código fonte. Em compensação, teríamos que testar essa versão Ruby muito mais, porque ela tem várias permutações diferentes (a gente até injeta classes via <code>engine_klass</code>) e precisamos garantir que tudo responda corretamente. Na prática, deveríamos adicionar testes para todas as combinações dos argumentos do inicializador.</p>
<p>Na versão Crystal, como todos os tipos foram checados em tempo de compilação, foi mais exigente em termos de anotações; mas podemos ter bastante certeza de que se compilou, vai rodar como esperado.</p>
<blockquote>
  <p>Não estou dizendo que Crystal dispensa specs.</p>

</blockquote>
<p>Compiladores só vão até certo ponto. Mas até onde é &ldquo;certo ponto&rdquo;? Sempre que você é &ldquo;forçado&rdquo; a adicionar Type Annotations, eu afirmo que essas partes ou estão tentando ser espertas demais, ou são intrinsecamente complexas. Essas são as partes que exigiriam camadas extras de testes em Ruby e, se conseguirmos adicionar as anotações direito, podemos ter menos testes (não precisamos cobrir a maior parte das permutações) na versão Crystal (uma complexidade exponencial de permutações poderia cair para uma complexidade linear, eu acho).</p>
<h3>Uma palavra sobre Threads em Ruby<span class="hx:absolute hx:-mt-20" id="uma-palavra-sobre-threads-em-ruby"></span>
    <a href="#uma-palavra-sobre-threads-em-ruby" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Os principais conceitos que você precisa entender sobre concorrência em Ruby são esses:</p>
<ul>
<li>MRI Ruby tem um GIL, um Global Interpreter Lock, que proíbe código de rodar concorrentemente.</li>
<li>MRI Ruby tem acesso e expõe Threads nativas desde a versão 1.9. Mas mesmo se você disparar várias Threads, elas vão rodar sequencialmente, porque só uma thread pode segurar o Lock por vez.</li>
<li>Operações de I/O são a exceção: elas liberam o Lock para que outras threads rodem enquanto a operação está esperando completar. O SO sinaliza o programa via poll de nível de sistema.</li>
</ul>
<p>Isso significa que se sua aplicação é <strong>I/O intensiva</strong> (requisições HTTP, leituras ou escritas em arquivos, operações de socket etc), você terá <em>alguma</em> concorrência. Um servidor web, como Puma por exemplo, pode tirar algum proveito de Threads, porque uma boa parte das operações envolve receber requisições HTTP e mandar respostas HTTP pela rede, o que deixa o processo Ruby ocioso enquanto espera.</p>
<p>Se sua aplicação é CPU intensiva (algoritmos pesados, processamento de dados, coisas que realmente esquentam o CPU), aí Threads nativas não ajudam, só uma roda por vez. Se você tem múltiplos cores no seu CPU, pode dar Fork no seu processo para o número de cores disponíveis.</p>
<p>Dê uma olhada em <a href="https://github.com/grosser/parallel"target="_blank" rel="noopener">grosser/parallel</a> para facilitar.</p>
<p>É por isso que Puma também tem um modo &ldquo;worker&rdquo;. &ldquo;Worker&rdquo; é o nome que normalmente damos para processos filhos forkados.</p>
<p>No caso desse processo de download, ele vai fazer milhares de requisições HTTP para raspar os metadados necessários das páginas do MangaReader. Então é muito mais I/O intensivo do que CPU intensivo (as partes CPU intensivas são o parsing do HTML e, mais tarde, o redimensionamento das imagens e a compilação do PDF).</p>
<p>Uma versão sequencial do que precisa ser feito, em Ruby, fica assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">fetch_sequential</span><span class="p">(</span><span class="n">collection</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">results</span> <span class="o">=</span> <span class="o">[]</span>
</span></span><span class="line"><span class="cl">  <span class="n">engine</span>  <span class="o">=</span> <span class="vi">@turn_on_engine</span> <span class="p">?</span> <span class="vi">@engine_klass</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">domain</span><span class="p">)</span> <span class="p">:</span> <span class="kp">nil</span>
</span></span><span class="line"><span class="cl">  <span class="n">collection</span><span class="o">&amp;.</span><span class="n">each_slice</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">download_batch_size</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">batch</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="n">batch</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="n">batch_results</span> <span class="o">=</span> <span class="n">block</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="n">engine</span><span class="p">)</span><span class="o">&amp;.</span><span class="n">flatten</span>
</span></span><span class="line"><span class="cl">      <span class="n">results</span> <span class="o">+=</span> <span class="p">(</span> <span class="n">batch_results</span> <span class="o">||</span> <span class="o">[]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="n">results</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se temos 10.000 links na <code>collection</code>, primeiro fatiamos no tamanho que <code>@config.download_batch_size</code> define e iteramos sobre essas fatias menores, chamando algum bloco e acumulando os resultados. Esse é um algoritmo ingênuo, como você vai descobrir na próxima seção, mas vamos seguir.</p>
<p>Em Elixir você pode disparar micro-processos para fazer as requisições HTTP em paralelo. Em Crystal você pode disparar Fibers e esperar as requisições HTTP completarem e sinalizar os resultados via Channels.</p>
<p>Os dois são leves e você pode ter centenas ou até milhares rodando em paralelo. O Manga Reader provavelmente vai reclamar se você mandar tudo isso de uma vez, então o limite não está no código, e sim no serviço externo.</p>
<p>Para transformar a versão sequencial em uma concorrente, é isso que podemos fazer em Crystal:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">collection</span> <span class="p">:</span> <span class="nb">Array</span><span class="p">(</span><span class="n">A</span><span class="p">)</span><span class="sc">?,</span> <span class="o">&amp;</span><span class="n">block</span> <span class="p">:</span> <span class="n">A</span><span class="p">,</span> <span class="n">C?</span> <span class="o">-&gt;</span> <span class="nb">Array</span><span class="p">(</span><span class="n">B</span><span class="p">)</span><span class="sc">?)</span> <span class="p">:</span> <span class="nb">Array</span><span class="p">(</span><span class="n">B</span><span class="p">)?</span>
</span></span><span class="line"><span class="cl">  <span class="n">results</span> <span class="o">=</span> <span class="o">[]</span> <span class="n">of</span> <span class="n">B</span>
</span></span><span class="line"><span class="cl">  <span class="n">collection</span><span class="o">.</span><span class="n">try</span> <span class="o">&amp;.</span><span class="n">each_slice</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">download_batch_size</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">batch</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="n">channel</span> <span class="o">=</span> <span class="no">Channel</span><span class="p">(</span><span class="nb">Array</span><span class="p">(</span><span class="n">B</span><span class="p">)</span><span class="sc">?)</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl">    <span class="n">batch</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="n">spawn</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">engine</span>  <span class="o">=</span> <span class="k">if</span> <span class="vi">@turn_on_engine</span>
</span></span><span class="line"><span class="cl">                    <span class="n">C</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">domain</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                  <span class="k">end</span>
</span></span><span class="line"><span class="cl">        <span class="n">reply</span> <span class="o">=</span> <span class="n">block</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="n">engine</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">channel</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">reply</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">engine</span><span class="o">.</span><span class="n">try</span> <span class="o">&amp;.</span><span class="n">close</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="n">batch</span><span class="o">.</span><span class="n">size</span><span class="o">.</span><span class="n">times</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">reply</span> <span class="o">=</span> <span class="n">channel</span><span class="o">.</span><span class="n">receive</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="n">reply</span>
</span></span><span class="line"><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">concat</span><span class="p">(</span><span class="n">reply</span><span class="o">.</span><span class="n">flatten</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="n">channel</span><span class="o">.</span><span class="n">close</span>
</span></span><span class="line"><span class="cl">    <span class="nb">puts</span> <span class="s2">&#34;Processed so far: </span><span class="si">#{</span><span class="n">results</span><span class="o">.</span><span class="n">try</span> <span class="o">&amp;.</span><span class="n">size</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="n">results</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pegar uma coleção enorme e fatiar em &lsquo;batches&rsquo; menores é fácil. Agora temos uma coleção <code>batch</code> menor. Para cada item (geralmente uma URI) damos <code>spawn</code> em uma Fiber e chamamos um bloco que vai requisitar e processar os resultados. Quando termina o processamento, envia os resultados por um <code>channel</code>.</p>
<p>Quando terminamos de iterar sobre o batch e disparar todas essas Fibers, podemos &ldquo;esperar&rdquo; por elas chamando <code>channel.receive</code>, que vai começar a receber os resultados conforme as Fibers vão terminando de requisitar/processar cada URI.</p>
<p>Acumulamos os resultados e seguimos para o próximo batch da coleção até terminar. A quantidade de concorrência é determinada pelo tamanho do batch (é parecido com <a href="http://www.akitaonrails.com/en/2015/11/19/ex-manga-downloadr-parte-2-poolboy-ao-resgate/"target="_blank" rel="noopener">o que fiz com &lsquo;poolboy&rsquo; em Elixir</a> onde iniciamos um número fixo de processos rodando em paralelo para evitar fazer um Denial of Service no Manga Reader).</p>
<p>A propósito, essa implementação em Crystal é parecida com o que você faria em Go usando Channels.</p>
<p>Na versão Ruby você pode disparar Threads nativas - que têm bastante overhead para spawnar! - e assumir que as requisições HTTP vão rodar quase todas em paralelo. Como é I/O intensivo, dá para ter todas em paralelo. Fica assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">collection</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">results</span> <span class="o">=</span> <span class="o">[]</span>
</span></span><span class="line"><span class="cl">  <span class="n">collection</span><span class="o">&amp;.</span><span class="n">each_slice</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">download_batch_size</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">batch</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="n">mutex</span>   <span class="o">=</span> <span class="no">Mutex</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl">    <span class="n">threads</span> <span class="o">=</span> <span class="n">batch</span><span class="o">.</span><span class="n">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="no">Thread</span><span class="o">.</span><span class="n">new</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">engine</span>  <span class="o">=</span> <span class="vi">@turn_on_engine</span> <span class="p">?</span> <span class="vi">@engine_klass</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">domain</span><span class="p">)</span> <span class="p">:</span> <span class="kp">nil</span>
</span></span><span class="line"><span class="cl">        <span class="no">Thread</span><span class="o">.</span><span class="n">current</span><span class="o">[</span><span class="s2">&#34;results&#34;</span><span class="o">]</span> <span class="o">=</span> <span class="n">block</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="n">engine</span><span class="p">)</span><span class="o">&amp;.</span><span class="n">flatten</span>
</span></span><span class="line"><span class="cl">        <span class="n">mutex</span><span class="o">.</span><span class="n">synchronize</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">          <span class="n">results</span> <span class="o">+=</span> <span class="p">(</span> <span class="no">Thread</span><span class="o">.</span><span class="n">current</span><span class="o">[</span><span class="s2">&#34;results&#34;</span><span class="o">]</span> <span class="o">||</span> <span class="o">[]</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">end</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="n">threads</span><span class="o">.</span><span class="n">each</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:join</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">puts</span> <span class="s2">&#34;Processed so far: </span><span class="si">#{</span><span class="n">results</span><span class="o">&amp;.</span><span class="n">size</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="n">results</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>As Threads são todas inicializadas em estado &ldquo;pausado&rdquo;. Depois de instanciar todas essas Threads, podemos chamar <code>#join</code> em cada uma e esperar todas terminarem.</p>
<p>Quando cada Thread termina o mesmo processo de requisição/processamento de URI, os resultados precisam ser acumulados em um armazenamento global, nesse caso um simples array chamado <code>results</code>. Mas como existe a chance de duas ou mais threads tentarem atualizar o mesmo array, a gente sincroniza o acesso (não tenho certeza se o acesso a Array em Ruby já é sincronizado, mas acho que não). Para sincronizar o acesso usamos um Mutex, que é um lock fino, garantindo que só 1 thread possa modificar o array global por vez.</p>
<p>Para provar que Ruby suporta operações de I/O concorrentes, adicionei 2 métodos na classe <code>Concurrent</code>, o primeiro é só <code>#fetch</code> e é a implementação com Threads acima. O segundo se chama <code>#fetch_sequential</code> e é a versão sequencial mostrada no início dessa seção. E adicionei o seguinte spec:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">it</span> <span class="s2">&#34;should check that the fetch implementation runs in less time than the sequential version&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">reactor</span> <span class="o">=</span> <span class="no">MangaDownloadr</span><span class="o">::</span><span class="no">Concurrency</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="no">MangaDownloadr</span><span class="o">::</span><span class="no">Pages</span><span class="p">,</span> <span class="n">config</span><span class="p">,</span> <span class="kp">true</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">collection</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;/onepunch-man/96&#34;</span><span class="o">]</span> <span class="o">*</span> <span class="mi">10</span>
</span></span><span class="line"><span class="cl">  <span class="no">WebMock</span><span class="o">.</span><span class="n">allow_net_connect!</span>
</span></span><span class="line"><span class="cl">  <span class="k">begin</span>
</span></span><span class="line"><span class="cl">    <span class="n">concurrent_measurement</span> <span class="o">=</span> <span class="no">Benchmark</span><span class="o">.</span><span class="n">measure</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">results</span> <span class="o">=</span> <span class="n">reactor</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="n">collection</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">link</span><span class="p">,</span> <span class="n">engine</span><span class="o">|</span> <span class="n">engine</span><span class="o">&amp;.</span><span class="n">fetch</span><span class="p">(</span><span class="n">link</span><span class="p">)</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">sequential_measurement</span> <span class="o">=</span> <span class="no">Benchmark</span><span class="o">.</span><span class="n">measure</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">results</span> <span class="o">=</span> <span class="n">reactor</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="ss">:fetch_sequential</span><span class="p">,</span> <span class="n">collection</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">link</span><span class="p">,</span> <span class="n">engine</span><span class="o">|</span> <span class="n">engine</span><span class="o">&amp;.</span><span class="n">fetch</span><span class="p">(</span><span class="n">link</span><span class="p">)</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="sr">/\((.*?)\)$/</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="n">concurrent_measurement</span><span class="o">.</span><span class="n">to_s</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">cm</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="sr">/\((.*?)\)/</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="n">sequential_measurement</span><span class="o">.</span><span class="n">to_s</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">sm</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># esperado que a versao concorrente seja proxima de 10 vezes mais rapida que a sequencial</span>
</span></span><span class="line"><span class="cl">        <span class="n">expect</span><span class="p">(</span><span class="n">sm</span><span class="o">[</span><span class="mi">1</span><span class="o">].</span><span class="n">to_f</span><span class="p">)</span><span class="o">.</span><span class="n">to</span> <span class="n">be</span> <span class="o">&gt;</span> <span class="p">(</span> <span class="n">cm</span><span class="o">[</span><span class="mi">1</span><span class="o">].</span><span class="n">to_f</span> <span class="o">*</span> <span class="mi">9</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">ensure</span>
</span></span><span class="line"><span class="cl">    <span class="no">WebMock</span><span class="o">.</span><span class="n">disable_net_connect!</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como ele usa WebMock, eu primeiro desabilito durante esse spec. Crio uma coleção falsa de 10 links reais para o MangaReader. E aí faço benchmark da versão concorrente baseada em Threads e da versão sequencial pura. Como temos 10 links e são todos iguais, dá para assumir que a versão sequencial vai ser quase <strong>10 vezes mais lenta</strong> que a versão baseada em Threads. E é exatamente isso que esse spec compara e prova (o spec falha se a versão concorrente não for pelo menos 9x mais rápida).</p>
<p>Para comparar todas as versões dos Manga Downloadrs, deixei baixar e compilar uma coleção inteira de mangá, nesse caso &ldquo;One-Punch Man&rdquo;, que tem quase <strong>1.900 páginas/imagens</strong>. Estou medindo só os processos de fetching e scraping, pulando o download das imagens em si, redimensionamento e geração do PDF, porque eles tomam a maior parte do tempo e o redimensionamento e a parte do PDF são feitos pelo <code>mogrify</code> e <code>convert</code> do ImageMagick.</p>
<p>Esse é o tempo que essa nova versão Ruby leva para baixar e raspar quase 1.900 páginas (usando MRI Ruby 2.3.1):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>12,42s user 1,33s system 23% cpu 57,675 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse é o tempo que a versão Crystal leva:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>4,03s user 0,40s system 7% cpu 59,207 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Só por diversão tentei rodar a versão Ruby sob JRuby 9.1.1.0. Para rodar com JRuby basta adicionar a linha a seguir no <code>Gemfile</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">ruby</span> <span class="s2">&#34;2.3.0&#34;</span><span class="p">,</span> <span class="ss">:engine</span> <span class="o">=&gt;</span> <span class="s1">&#39;jruby&#39;</span><span class="p">,</span> <span class="ss">:engine_version</span> <span class="o">=&gt;</span> <span class="s1">&#39;9.1.1.0&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Bundle install, roda normalmente, e esse é o resultado:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>47,80s user 1,99s system 108% cpu 45,967 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E esse é o tempo que a versão Elixir leva:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>11,38s user 1,04s system 85% cpu 14,590 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>Reality Check!<span class="hx:absolute hx:-mt-20" id="reality-check"></span>
    <a href="#reality-check" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se você só olhar os tempos acima, pode chegar a conclusões erradas.</p>
<p>Antes de mais nada, é uma <strong>comparação injusta</strong>. A versão Elixir usa um algoritmo bem diferente das versões Ruby e Crystal.</p>
<p>Em Elixir eu subo um pool de processos, mais ou menos 50 deles. Aí disparo 50 requisições HTTP de uma vez. Quando cada processo termina, ele se libera de volta para o pool e eu posso disparar outra requisição HTTP da fila de links. Então é um <strong>fluxo constante</strong> de no máximo 50 requisições HTTP, constantemente.</p>
<p>As versões Crystal e Ruby/JRuby fatiam os 1.900 links em batches de 40 links e aí eu disparo 40 requisições de uma vez. Essa implementação espera todas as 40 terminarem para disparar mais 40. Ou seja, nunca é um fluxo constante, são rajadas de 40 requisições. Cada batch é freado pela requisição mais lenta do batch, sem dar chance para as outras requisições passarem.</p>
<p>É uma diferença de arquitetura. Elixir torna muito mais fácil fazer streams e Crystal, com seu estilo CSP, torna mais fácil fazer rajadas. Uma abordagem melhor seria enfileirar os 1.900 links e usar algo como Sidekiq.cr para ir consumindo um link por vez (spawnando 40 fibers para servir como um &ldquo;pool&rdquo;, por exemplo).</p>
<p>A versão Elixir tem uma arquitetura mais eficiente, e é por isso que ela leva no máximo 15 segundos para baixar todos os links de imagens, enquanto a versão Crystal leva quase um minuto inteiro para terminar (a soma das requisições mais lentas em cada batch).</p>
<p>Agora, você vai se surpreender ao ver que a versão Crystal é, na verdade, um pouco mais lenta que a versão Ruby! E você não vai ficar tão surpreso ao ver o JRuby mais rápido aos 45 segundos!</p>
<p>Essa é mais uma evidência de que você não deve descartar Ruby (e que deveria experimentar JRuby com mais frequência). Como expliquei antes, ele suporta concorrência em operações de I/O e as aplicações que testei são todas pesadas em I/O.</p>
<p>A diferença provavelmente está na maturidade da biblioteca <code>Net::HTTP</code> do Ruby contra a <code>HTTP::Client</code> do Crystal. Tentei vários ajustes na versão Crystal, mas não consegui ficar muito mais rápido. Tentei batches maiores, mas por algum motivo a aplicação simplesmente trava, pausa, e nunca libera. Tenho que dar Ctrl-C e tentar de novo até finalmente passar. Se alguém souber o que estou fazendo de errado, não se esqueça de escrever na seção de comentários abaixo.</p>
<p>Parte disso provavelmente é por conta dos servidores instáveis do MangaReader, eles devem ter algum tipo de prevenção contra DoS, throttling de conexões ou algo do tipo.</p>
<p>De qualquer forma, quando passam, como o algoritmo do Ruby e do Crystal são essencialmente o mesmo, eles levam aproximadamente o mesmo tempo para completar. O que falta é eu evoluir esse algoritmo para usar algo como Sidekiq ou implementar um esquema interno de fila/pool de workers.</p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O objetivo desse experimento era aprender mais sobre as capacidades do Crystal e o quão fácil seria ir e voltar com Ruby.</p>
<p>Como você pode ver, existem muitas diferenças, mas não é tão difícil. Posso estar deixando algo passar, mas esbarrei em algumas dificuldades quando empurrei <code>HTTP::Client</code> + Fibers para o limite, como expliquei acima. Se você sabe o que estou fazendo de errado, me avise.</p>
<p>A diferença entre os algoritmos do Elixir e do Ruby/Crystal mostra mais do que diferenças de performance entre linguagens, mostra também a importância da arquitetura e dos algoritmos na performance geral. Esse teste foi pouco conclusivo, apenas um indício de que minha implementação ingênua de Fibers precisa de mais trabalho, e que a forma natural do Elixir lidar com processos paralelos facilita atingir níveis mais altos de paralelismo.</p>
<p>Isso serve como um aperitivo do que Crystal pode fazer, e também de que Ruby continua no jogo. E também de que Elixir certamente é algo para se acompanhar de perto.</p>
]]></content:encoded><category>anime-e-manga</category><category>linguagens-de-programacao</category><category>ruby</category></item><item><title>Ex Manga Downloadr - Parte 3: Suporte ao Mangafox!</title><link>https://akitaonrails.github.io/2015/12/02/ex-manga-downloadr-parte-3-suporte-ao-mangafox/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/12/02/ex-manga-downloadr-parte-3-suporte-ao-mangafox/</guid><pubDate>Wed, 02 Dec 2015 16:27:00 -0200</pubDate><description>&lt;p&gt;Eu achava que a &lt;a href="http://www.akitaonrails.com/2015/11/19/ex-manga-downloadr-parte-2-poolboy-ao-resgate"target="_blank" rel="noopener"&gt;Parte 2&lt;/a&gt; seria meu último artigo sobre essa ferramentinha, mas no fim das contas é divertido demais para largar tão rápido. Como sempre, todo o código fonte está no meu &lt;a href="https://github.com/akitaonrails/ex_manga_downloadr"target="_blank" rel="noopener"&gt;repositório do Github&lt;/a&gt;. E o resumo do post é que agora dá pra fazer isso:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;git pull
mix escript.build
./ex_manga_downloadr -n onepunch -u http://mangafox.me/manga/onepunch_man/ -d /tmp/onepunch -s mangafox&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;E pronto: download direto do Mangafox embutido! \o/&lt;/p&gt;</description><content:encoded><![CDATA[<p>Eu achava que a <a href="http://www.akitaonrails.com/2015/11/19/ex-manga-downloadr-parte-2-poolboy-ao-resgate"target="_blank" rel="noopener">Parte 2</a> seria meu último artigo sobre essa ferramentinha, mas no fim das contas é divertido demais para largar tão rápido. Como sempre, todo o código fonte está no meu <a href="https://github.com/akitaonrails/ex_manga_downloadr"target="_blank" rel="noopener">repositório do Github</a>. E o resumo do post é que agora dá pra fazer isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>git pull
mix escript.build
./ex_manga_downloadr -n onepunch -u http://mangafox.me/manga/onepunch_man/ -d /tmp/onepunch -s mangafox</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E pronto: download direto do Mangafox embutido! \o/</p>
<p>Tudo começou quando eu quis baixar um mangá que não existe no MangaReader mas está disponível no Mangafox.</p>
<p>Então a empreitada inicial foi copiar os módulos do parser do MangaReader (IndexPage, ChapterPage e Page) e colar numa pasta específica &ldquo;lib/ex_manga_downloadr/mangafox&rdquo;. A mesma coisa na pasta de testes unitários. Só copiar e colar os arquivos e trocar o nome do módulo &ldquo;MangaReader&rdquo; por &ldquo;Mangafox&rdquo;.</p>
<p>Claro que os formatos de URL são diferentes, os seletores CSS do Floki são um pouquinho diferentes, então é isso que precisa mudar no parser. Por exemplo, é assim que eu faço o parsing dos links de capítulo da página principal no MangaReader:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">fetch_chapters</span><span class="p">(</span><span class="n">html</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">html</span><span class="p">,</span> <span class="s2">&#34;#listing a&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span> <span class="n">fn</span> <span class="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">,</span> <span class="o">[</span><span class="p">{</span><span class="s2">&#34;href&#34;</span><span class="p">,</span> <span class="n">url</span><span class="p">}</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">url</span> <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E essa é a mesma coisa, só que para o Mangafox:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">fetch_chapters</span><span class="p">(</span><span class="n">html</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">html</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">&#34;.chlist a[class=&#39;tips&#39;]&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span> <span class="n">fn</span> <span class="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">,</span> <span class="o">[</span><span class="p">{</span><span class="s2">&#34;href&#34;</span><span class="p">,</span> <span class="n">url</span><span class="p">},</span> <span class="p">{</span><span class="s2">&#34;title&#34;</span><span class="p">,</span> <span class="n">_</span><span class="p">},</span> <span class="p">{</span><span class="s2">&#34;class&#34;</span><span class="p">,</span> <span class="s2">&#34;tips&#34;</span><span class="p">}</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">url</span> <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Exatamente a mesma lógica, mas a estrutura de pattern matching muda porque os nodes do DOM HTML retornados são diferentes.</p>
<p>Outra diferença é que o MangaReader devolve tudo em texto puro por padrão, enquanto o Mangafox devolve tudo Gzipado independente de eu mandar o header HTTP &ldquo;Accept-Encoding&rdquo; (curiosamente, se eu tento várias vezes ele muda de comportamento e às vezes manda texto puro).</p>
<p>O que eu fiz de diferente foi checar se a estrutura <tt>%HTTPotion.Response{}</tt> retornada tinha o header &ldquo;Content-Encoding&rdquo; setado como &ldquo;gzip&rdquo; e, se sim, descomprimir usando o pacote &ldquo;zlib&rdquo; embutido do Erlang (sem precisar importar nada!):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">gunzip</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="n">headers</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="n">headers</span><span class="o">[</span><span class="ss">:&#34;Content-Encoding&#34;</span><span class="o">]</span> <span class="o">==</span> <span class="s2">&#34;gzip&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="ss">:zlib</span><span class="o">.</span><span class="n">gunzip</span><span class="p">(</span><span class="n">body</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="n">body</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Eu teria preferido que o HTTPotion já fizesse isso pronto pra mim (#OpportunityToContribute!), mas foi fácil o suficiente.</p>
<p>Depois que os testes unitários começaram a passar corretamente, com o scrapper (requisições HTTPotion) e o parser (seletores Floki) ajustados, chegou a hora de fazer meu Worker reconhecer a existência desse novo conjunto de módulos.</p>
<p>O módulo Workflow só chama o Worker, que por sua vez faz o trabalho pesado de buscar páginas e baixar imagens. O Worker chamava o módulo MangaReader diretamente, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">PoolManagement</span><span class="o">.</span><span class="n">Worker</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">GenServer</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span>
</span></span><span class="line"><span class="cl">  <span class="nb">require</span> <span class="no">Logger</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">chapter_page</span><span class="p">(</span><span class="n">chapter_link</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">Task</span><span class="o">.</span><span class="n">async</span> <span class="n">fn</span> <span class="o">-&gt;</span> 
</span></span><span class="line"><span class="cl">      <span class="ss">:poolboy</span><span class="o">.</span><span class="n">transaction</span> <span class="ss">:worker_pool</span><span class="p">,</span> <span class="n">fn</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="no">GenServer</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="p">{</span><span class="ss">:chapter_page</span><span class="p">,</span> <span class="n">chapter_link</span><span class="p">},</span> <span class="vi">@timeout_ms</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span><span class="p">,</span> <span class="vi">@transaction_timeout_ms</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_call</span><span class="p">({</span><span class="ss">:chapter_page</span><span class="p">,</span> <span class="n">chapter_link</span><span class="p">},</span> <span class="n">_from</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:reply</span><span class="p">,</span> <span class="no">ChapterPages</span><span class="o">.</span><span class="n">pages</span><span class="p">(</span><span class="n">chapter_link</span><span class="p">),</span> <span class="n">state</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Aquele &ldquo;<tt>use ExMangaDownloadr.MangaReader</tt>&rdquo; lá em cima é só uma macro que cria os aliases para os módulos correspondentes:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">defmacro</span> <span class="n">__using__</span><span class="p">(</span><span class="n">_opts</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">quote</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">IndexPage</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">ChapterPage</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">Page</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Então quando eu chamo &ldquo;<tt>ChapterPages.pages(chapter_link)</tt>&rdquo; é um atalho pra usar o nome qualificado completo do módulo, tipo: &ldquo;<tt>ExMangaDownloadr.MangaReader.ChapterPages.pages(chapter_link)</tt>&rdquo;.</p>
<p>Um namespace de módulo Elixir é só um Atom. Nomes de módulos aninhados têm o nome completo separado por pontos, prefixado com seu pai. Por exemplo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Foo</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">defmodule</span> <span class="no">Bar</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">defmodule</span> <span class="no">Xyz</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">       <span class="k">def</span> <span class="nf">teste</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">       <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Você pode chamar &ldquo;<tt>Foo.Bar.Xyz.teste()</tt>&rdquo; e pronto. Mas tem uma pequena pegadinha. O Elixir também prefixa transparentemente o nome completo do módulo com &ldquo;Elixir&rdquo;. Então na real, o nome completo do módulo é &ldquo;Elixir.Foo.Bar.Xyz&rdquo;, pra garantir que nenhum módulo Elixir conflite com algum módulo Erlang existente.</p>
<p>Isso é importante por causa dessa nova função que eu adicionei primeiro no módulo Worker:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">manga_source</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">module</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">case</span> <span class="n">source</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;mangareader&#34;</span> <span class="o">-&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">to_atom</span><span class="p">(</span><span class="s2">&#34;Elixir.ExMangaDownloadr.MangaReader.</span><span class="si">#{</span><span class="n">module</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;mangafox&#34;</span>    <span class="o">-&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">to_atom</span><span class="p">(</span><span class="s2">&#34;Elixir.ExMangaDownloadr.Mangafox.</span><span class="si">#{</span><span class="n">module</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É assim que eu mapeio de &ldquo;mangafox&rdquo; pro novo namespace &ldquo;ExMangaDownloadr.Mangafox.&rdquo;. E por causa da natureza dinâmica e baseada em troca de mensagens do Elixir, eu consigo trocar esse código:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">handle_call</span><span class="p">({</span><span class="ss">:chapter_page</span><span class="p">,</span> <span class="n">chapter_link</span><span class="p">},</span> <span class="n">_from</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="ss">:reply</span><span class="p">,</span> <span class="no">ChapterPages</span><span class="o">.</span><span class="n">pages</span><span class="p">(</span><span class="n">chapter_link</span><span class="p">),</span> <span class="n">state</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Por esse:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">handle_call</span><span class="p">({</span><span class="ss">:chapter_page</span><span class="p">,</span> <span class="n">chapter_link</span><span class="p">,</span> <span class="n">source</span><span class="p">},</span> <span class="n">_from</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">links</span> <span class="o">=</span> <span class="n">source</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">manga_source</span><span class="p">(</span><span class="s2">&#34;ChapterPage&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">apply</span><span class="p">(</span><span class="ss">:pages</span><span class="p">,</span> <span class="o">[</span><span class="n">chapter_link</span><span class="o">]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="ss">:reply</span><span class="p">,</span> <span class="n">links</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora eu posso escolher entre os módulos &ldquo;Elixir.ExMangaDownloadr.Mangafox.ChapterPage&rdquo; ou &ldquo;Elixir.ExMangaDownloadr.MangaReader.ChapterPage&rdquo;, chamar a função <tt>pages/1</tt> e mandar o mesmo argumento de antes. Só preciso garantir que consigo receber a string &ldquo;source&rdquo; pela linha de comando agora, então mudo o módulo CLI assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">parse_args</span><span class="p">(</span><span class="n">args</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">parse</span> <span class="o">=</span> <span class="no">OptionParser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">switches</span><span class="p">:</span> <span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">source</span><span class="p">:</span> <span class="ss">:string</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">aliases</span><span class="p">:</span> <span class="o">[</span><span class="ss">n</span><span class="p">:</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">u</span><span class="p">:</span> <span class="ss">:url</span><span class="p">,</span> <span class="ss">d</span><span class="p">:</span> <span class="ss">:directory</span><span class="p">,</span> <span class="ss">s</span><span class="p">:</span> <span class="ss">:source</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">case</span> <span class="n">parse</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="n">manga_name</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="n">directory</span><span class="p">,</span> <span class="ss">source</span><span class="p">:</span> <span class="n">source</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">directory</span><span class="p">,</span> <span class="n">source</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="n">manga_name</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="n">directory</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span> <span class="p">}</span> <span class="o">-&gt;</span> <span class="n">process</span><span class="p">(</span><span class="ss">:help</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Comparado com a versão anterior eu só adicionei o argumento string &ldquo;<tt>:source</tt>&rdquo; no OptionParser e passei o valor capturado para <tt>process/4</tt>. Eu deveria adicionar alguma validação aqui para evitar strings diferentes de &ldquo;mangareader&rdquo; ou &ldquo;mangafox&rdquo;, mas vou deixar isso pra outro momento.</p>
<p>E no módulo Workflow, em vez de começar só com a URL do mangá, agora preciso começar com a URL e a fonte do mangá:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">[</span><span class="n">url</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">chapters</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">pages</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">images_sources</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O que significa que cada uma dessas funções precisa não só retornar a nova lista de URLs como também passar a source adiante:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">chapters</span><span class="p">(</span><span class="o">[</span><span class="n">url</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">_manga_title</span><span class="p">,</span> <span class="n">chapter_list</span><span class="p">}</span> <span class="o">=</span> <span class="n">source</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Worker</span><span class="o">.</span><span class="n">manga_source</span><span class="p">(</span><span class="s2">&#34;IndexPage&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">apply</span><span class="p">(</span><span class="ss">:chapters</span><span class="p">,</span> <span class="o">[</span><span class="n">url</span><span class="o">]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">[</span><span class="n">chapter_list</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Essa era a única função do módulo Workflow hardcoded pro MangaReader, então eu também a deixei dinâmica usando a mesma função <tt>manga_source/2</tt> do Worker, e repare que o valor de retorno é &ldquo;<tt>[chapter_list, source]</tt>&rdquo; em vez de só &ldquo;<tt>chapter_list</tt>&rdquo;.</p>
<p>E agora eu finalmente posso testar com &ldquo;<tt>mix test</tt>&rdquo; e criar o novo binário executável de linha de comando com &ldquo;<tt>mix escript.build</tt>&rdquo; e rodar a nova versão assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>./ex_manga_downloadr -n onepunch -u http://mangafox.me/manga/onepunch_man/ -d /tmp/onepunch -s mangafox</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O site do Mangafox é bem instável com várias conexões concorrentes e dá timeout rapidinho às vezes, cuspindo erros feios assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>15:58:46.637 [error] Task #PID&lt;0.2367.0&gt; started from #PID&lt;0.124.0&gt; terminating
** (stop) exited in: GenServer.call(#PID&lt;0.90.0&gt;, {:page_download_image, {&#34;http://z.mfcdn.net/store/manga/11362/TBD-053.2/compressed/h006.jpg&#34;, &#34;Onepunch-Man 53.2: 53rd Punch [Fighting Spirit] (2) at MangaFox.me-h006.jpg&#34;}, &#34;/tmp/onepunch&#34;}, 1000000)
    ** (EXIT) an exception was raised:
        ** (HTTPotion.HTTPError) connection_closing
            (httpotion) lib/httpotion.ex:209: HTTPotion.handle_response/1</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ainda não descobri como fazer retry de requisições HTTPotion direito. Mas uma coisinha que eu fiz foi adicionar um check de disponibilidade no módulo Worker. Assim você pode simplesmente rodar o mesmo comando de novo e ele vai retomar baixando só os arquivos que faltam:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">download_image</span><span class="p">({</span><span class="n">image_src</span><span class="p">,</span> <span class="n">image_filename</span><span class="p">},</span> <span class="n">directory</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">filename</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">directory</span><span class="si">}</span><span class="s2">/</span><span class="si">#{</span><span class="n">image_filename</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="no">File</span><span class="o">.</span><span class="n">exists?</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">Logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&#34;Image </span><span class="si">#{</span><span class="n">filename</span><span class="si">}</span><span class="s2"> already downloaded, skipping.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">image_src</span><span class="p">,</span> <span class="n">filename</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="no">Logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&#34;Downloading image </span><span class="si">#{</span><span class="n">image_src</span><span class="si">}</span><span class="s2"> to </span><span class="si">#{</span><span class="n">filename</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="no">HTTPotion</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">image_src</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="o">[</span><span class="ss">headers</span><span class="p">:</span> <span class="o">[</span><span class="s2">&#34;User-Agent&#34;</span><span class="p">:</span> <span class="vi">@user_agent</span><span class="o">]</span><span class="p">,</span> <span class="ss">timeout</span><span class="p">:</span> <span class="vi">@http_timeout</span><span class="o">]</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="o">%</span><span class="no">HTTPotion</span><span class="o">.</span><span class="n">Response</span><span class="p">{</span> <span class="ss">body</span><span class="p">:</span> <span class="n">body</span><span class="p">,</span> <span class="ss">headers</span><span class="p">:</span> <span class="n">_headers</span><span class="p">,</span> <span class="ss">status_code</span><span class="p">:</span> <span class="mi">200</span> <span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="no">File</span><span class="o">.</span><span class="n">write!</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">body</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">image_src</span><span class="p">,</span> <span class="n">filename</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="n">_</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="ss">:err</span><span class="p">,</span> <span class="n">image_src</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso pelo menos reduz retrabalho. Outra coisa que ainda estou trabalhando é nesse outro pedaço da função principal &ldquo;CLI.process&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">directory</span><span class="p">,</span> <span class="n">source</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">File</span><span class="o">.</span><span class="n">mkdir_p!</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">dump_file</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">directory</span><span class="si">}</span><span class="s2">/images_list.dump&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="n">images_list</span> <span class="o">=</span> <span class="k">if</span> <span class="no">File</span><span class="o">.</span><span class="n">exists?</span><span class="p">(</span><span class="n">dump_file</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">                  <span class="ss">:erlang</span><span class="o">.</span><span class="n">binary_to_term</span><span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">dump_file</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span>
</span></span><span class="line"><span class="cl">                  <span class="n">list</span> <span class="o">=</span> <span class="o">[</span><span class="n">url</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">                    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">chapters</span>
</span></span><span class="line"><span class="cl">                    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">pages</span>
</span></span><span class="line"><span class="cl">                    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">images_sources</span>
</span></span><span class="line"><span class="cl">                  <span class="no">File</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">dump_file</span><span class="p">,</span> <span class="ss">:erlang</span><span class="o">.</span><span class="n">term_to_binary</span><span class="p">(</span><span class="n">list</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                  <span class="n">list</span>
</span></span><span class="line"><span class="cl">                <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="n">images_list</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">process_downloads</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">optimize_images</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">compile_pdfs</span><span class="p">(</span><span class="n">manga_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">finish_process</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como dá pra ver, a ideia é serializar os links finais das URLs das imagens num arquivo usando o serializador embutido &ldquo;<tt>:erlang.binary_to_term/1</tt>&rdquo; e checar se esse arquivo de dump existe, e desserializar com &ldquo;<tt>:erlang.term_to_binary/1</tt>&rdquo; antes de buscar todas as páginas de novo. Agora o processo pode retomar direto da função <tt>process_downloads/2</tt>.</p>
<p>O Mangafox é terrivelmente instável e eu vou precisar descobrir uma forma melhor de fazer retry de conexões que deram timeout, sem precisar quebrar e reiniciar manualmente da linha de comando. Ou é um site ruim ou um site esperto que derruba scrappers como o meu, embora eu chute que seja só infraestrutura ruim do lado deles.</p>
<p>Se eu reduzo de 50 processos para 5 no pool, parece que ele consegue lidar melhor (mas o processo fica mais lento, claro):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">    <span class="n">pool_options</span> <span class="o">=</span> <span class="o">[</span>
</span></span><span class="line"><span class="cl">      <span class="nb">name</span><span class="p">:</span> <span class="p">{</span><span class="ss">:local</span><span class="p">,</span> <span class="ss">:worker_pool</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="ss">worker_module</span><span class="p">:</span> <span class="no">PoolManagement</span><span class="o">.</span><span class="n">Worker</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">size</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">max_overflow</span><span class="p">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="o">]</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se você ver erros de timeout, mude esse parâmetro. O MangaReader ainda aguenta 50 ou mais de concorrência.</p>
<p>E agora você sabe como adicionar suporte pra mais fontes de mangá. Fique à vontade pra mandar um Pull Request! :-)</p>
]]></content:encoded><category>elixir-e-erlang</category><category>anime-e-manga</category><category>automacao</category></item><item><title>Ex Manga Downloadr, um exercício com Elixir</title><link>https://akitaonrails.github.io/2015/11/18/ex-manga-downloadr-um-exercicio-com-elixir/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/11/18/ex-manga-downloadr-um-exercicio-com-elixir/</guid><pubDate>Wed, 18 Nov 2015 17:26:00 -0200</pubDate><description>&lt;p&gt;&lt;strong&gt;Update 19/11/15:&lt;/strong&gt; Neste artigo eu menciono algumas dúvidas que tive, então leia este e depois acompanhe na &lt;a href="http://www.akitaonrails.com/2015/11/19/ex-manga-downloadr-parte-2-poolboy-ao-resgate"target="_blank" rel="noopener"&gt;Parte 2&lt;/a&gt; para ver como resolvi.&lt;/p&gt;
&lt;p&gt;Como exercício (e também porque, obviamente, sou um &lt;a href="https://en.wikipedia.org/wiki/Otaku"target="_blank" rel="noopener"&gt;Otaku&lt;/a&gt;) eu &lt;a href="https://github.com/akitaonrails/ex_manga_downloadr/blob/master/lib/ex_manga_downloadr/mangareader/index_page.ex"target="_blank" rel="noopener"&gt;implementei&lt;/a&gt; um scrapper simples em Elixir para o ótimo site &lt;a href="http://www.mangareader.net/"target="_blank" rel="noopener"&gt;MangaReader&lt;/a&gt;. Dá pra discutir se é certo fazer scrap do site deles, e dá pra discutir também se eles oferecerem esses mangás é certo em primeiro lugar, então vamos deixar essa discussão de lado.&lt;/p&gt;</description><content:encoded><![CDATA[<p><strong>Update 19/11/15:</strong> Neste artigo eu menciono algumas dúvidas que tive, então leia este e depois acompanhe na <a href="http://www.akitaonrails.com/2015/11/19/ex-manga-downloadr-parte-2-poolboy-ao-resgate"target="_blank" rel="noopener">Parte 2</a> para ver como resolvi.</p>
<p>Como exercício (e também porque, obviamente, sou um <a href="https://en.wikipedia.org/wiki/Otaku"target="_blank" rel="noopener">Otaku</a>) eu <a href="https://github.com/akitaonrails/ex_manga_downloadr/blob/master/lib/ex_manga_downloadr/mangareader/index_page.ex"target="_blank" rel="noopener">implementei</a> um scrapper simples em Elixir para o ótimo site <a href="http://www.mangareader.net/"target="_blank" rel="noopener">MangaReader</a>. Dá pra discutir se é certo fazer scrap do site deles, e dá pra discutir também se eles oferecerem esses mangás é certo em primeiro lugar, então vamos deixar essa discussão de lado.</p>
<p>Eu tinha uma versão mais antiga <a href="https://github.com/akitaonrails/manga-downloadr"target="_blank" rel="noopener">escrita em Ruby</a>. Ela ainda funciona, mas precisa muito de uma boa refatoração (desculpem por isso). O propósito daquela versão era ver se eu conseguia fazer fetch paralelo e retry de conexões usando Typhoeus.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/525/big_ex_manga_downloadr.png" alt="OnePunch Man baixado"  loading="lazy" /></p>
<p>Conforme fui evoluindo nos meus estudos de Elixir, essa ferramenta pareceu uma ótima candidata para testar meu conhecimento atual da plataforma. Ela me obrigaria a testar:</p>
<ol>
<li>Fetch e parse de conteúdo ad hoc via HTTP (<a href="https://github.com/myfreeweb/httpotion"target="_blank" rel="noopener">HTTPotion</a> e <a href="https://github.com/philss/floki"target="_blank" rel="noopener">Floki</a>).</li>
<li>Testar downloads paralelos/assíncronos (módulo <a href="http://elixir-lang.org/docs/stable/elixir/Task.html"target="_blank" rel="noopener">Task</a> embutido no Elixir).</li>
<li>O suporte embutido para linha de comando e <a href="http://elixir-lang.org/docs/stable/elixir/OptionParser.html"target="_blank" rel="noopener">option parser</a>.</li>
<li>Testes básicos via ExUnit e mock do workflow (<a href="https://github.com/jjh42/mock"target="_blank" rel="noopener">Mock</a>).</li>
</ol>
<p>O exercício foi muito interessante, e um scrapper também é candidato ideal para TDD. Os passos iniciais tinham que ser assim:</p>
<ol>
<li>Fazer parse da página principal do mangá para pegar todos os links dos capítulos.</li>
<li>Fazer parse de cada página de capítulo para pegar todas as páginas individuais.</li>
<li>Fazer parse de cada página para extrair a imagem principal embutida (a página real do mangá).</li>
</ol>
<p>Para cada um desses passos iniciais eu fiz um teste unitário simples e os módulos <tt>IndexPage</tt>, <tt>ChapterPage</tt> e <tt>Page</tt>. Eles têm mais ou menos a mesma estrutura, este é um exemplo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">IndexPage</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">chapters</span><span class="p">(</span><span class="n">manga_root_url</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="no">HTTPotion</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">manga_root_url</span><span class="p">,</span> <span class="o">[</span><span class="ss">timeout</span><span class="p">:</span> <span class="mi">30_000</span><span class="o">]</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="o">%</span><span class="no">HTTPotion</span><span class="o">.</span><span class="n">Response</span><span class="p">{</span> <span class="ss">body</span><span class="p">:</span> <span class="n">body</span><span class="p">,</span> <span class="ss">headers</span><span class="p">:</span> <span class="n">_headers</span><span class="p">,</span> <span class="ss">status_code</span><span class="p">:</span> <span class="mi">200</span> <span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">fetch_manga_title</span><span class="p">(</span><span class="n">body</span><span class="p">),</span> <span class="n">fetch_chapters</span><span class="p">(</span><span class="n">body</span><span class="p">)</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="n">_</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="ss">:err</span><span class="p">,</span> <span class="s2">&#34;not found&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">defp</span> <span class="n">fetch_manga_title</span><span class="p">(</span><span class="n">html</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">html</span><span class="p">,</span> <span class="s2">&#34;#mangaproperties h1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fn</span> <span class="p">{</span><span class="s2">&#34;h1&#34;</span><span class="p">,</span> <span class="o">[]</span><span class="p">,</span> <span class="o">[</span><span class="n">title</span><span class="o">]</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">title</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">at</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">defp</span> <span class="n">fetch_chapters</span><span class="p">(</span><span class="n">html</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">html</span><span class="p">,</span> <span class="s2">&#34;#listing a&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span> <span class="n">fn</span> <span class="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">,</span> <span class="o">[</span><span class="p">{</span><span class="s2">&#34;href&#34;</span><span class="p">,</span> <span class="n">url</span><span class="p">}</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">url</span> <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Aqui eu já estou exercitando algumas das features mais legais do Elixir, como pattern matching no resultado da função <tt>HTTPotion.get/2</tt> para extrair o body do record que retorna.</p>
<p>Depois eu passo o body do HTML para 2 funções diferentes: <tt>fetch_manga_title</tt> e <tt>fetch_chapters</tt>. As duas usam o pacote Floki, que aceita seletores CSS para retornar uma List. Aí eu preciso percorrer essa lista (usando <tt>Enum.map/2</tt> por exemplo) e fazer pattern match nela para extrair os valores que preciso.</p>
<p><a href="http://elixir-lang.org/getting-started/pattern-matching.html"target="_blank" rel="noopener">Pattern Matching</a> é um dos conceitos mais importantes para aprender em Elixir/Erlang. É diferente de simplesmente atribuir um valor a uma variável: dá pra usar para desmontar uma estrutura nos seus componentes e pegar as partes individuais.</p>
<p>Daí eu fui montar o esqueleto da interface de linha de comando. Isso já é explicado em outros tutoriais como <a href="http://asquera.de/blog/2015-04-10/writing-a-commandline-app-in-elixir/"target="_blank" rel="noopener">este</a> e <a href="https://speakerdeck.com/st23am/writing-command-line-applications-in-elixir"target="_blank" rel="noopener">este</a>, então não vou perder tempo explicando de novo. No núcleo eu precisava do seguinte workflow:</p>
<ol>
<li>Partindo da URL principal do mangá no MangaReader, extrair os capítulos</li>
<li>Depois loopar pelos capítulos e pegar todas as páginas</li>
<li>Depois loopar pelas páginas e pegar todos os sources de imagem</li>
<li>Depois loopar pelas imagens e baixar todas para um diretório temporário</li>
<li>Depois ordenar os arquivos no diretório e movê-los para subdiretórios de 250 imagens cada (acho um bom tamanho para cada volume)</li>
<li>Por fim, redimensionar e converter todas as imagens de cada subdiretório em um arquivo PDF para o meu Kindle consumir.</li>
</ol>
<p>Esse workflow é definido assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">File</span><span class="o">.</span><span class="n">mkdir_p!</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">url</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">chapters</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">pages</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">images_sources</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">process_downloads</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">optimize_images</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">compile_pdfs</span><span class="p">(</span><span class="n">manga_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">finish_process</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse é um lugar onde a notação de pipeline do Elixir realmente brilha. Fica muito melhor do que ter que escrever este equivalente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Workflow</span><span class="o">.</span><span class="n">compile_pdfs</span><span class="p">(</span><span class="no">Workflow</span><span class="o">.</span><span class="n">optimize_images</span><span class="p">(</span><span class="n">directory</span><span class="p">))</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Essa notação é só açúcar sintático onde o valor de retorno do statement anterior é usado como primeiro argumento da função seguinte. Combine isso com outros açúcares sintáticos como parênteses opcionais (assim como o amado Ruby) e você tem uma exposição clara de &ldquo;transformar uma URL em PDFs compilados&rdquo;.</p>
<p>Separei o Workflow no seu próprio módulo e cada passo é bem parecido, cada um pegando uma lista e percorrendo ela. Este é o mais simples deles:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pages</span><span class="p">(</span><span class="n">chapter_list</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">chapter_list</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">async</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span> <span class="no">ChapterPage</span><span class="o">.</span><span class="n">pages</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">)</span> <span class="k">end</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="vi">@http_timeout</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">[]</span><span class="p">,</span> <span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">list</span><span class="p">},</span> <span class="n">acc</span> <span class="o">-&gt;</span> <span class="n">acc</span> <span class="o">++</span> <span class="n">list</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se você é novo em Elixir, aqui vai encontrar outra esquisitice, esse <tt>&quot;&amp;(x(&amp;1))&quot;</tt>, que é só um macro de atalho para este outro statement equivalente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fn</span> <span class="p">(</span><span class="n">list</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="no">Task</span><span class="o">.</span><span class="n">async</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="no">ChapterPage</span><span class="o">.</span><span class="n">pages</span><span class="p">(</span><span class="n">list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><a href="http://elixir-lang.org/docs/stable/elixir/Enum.html"target="_blank" rel="noopener">Enum</a> é um dos módulos mais úteis que você precisa dominar. Se você vem de Ruby vai se sentir em casa, e tem que aprender todas as funções dele. Você normalmente vai estar transformando uma coleção em outra, então é importante estudar a fundo.</p>
<h3>Alguns problemas para entender o processamento HTTP paralelo (W.I.P.)<span class="hx:absolute hx:-mt-20" id="alguns-problemas-para-entender-o-processamento-http-paralelo-wip"></span>
    <a href="#alguns-problemas-para-entender-o-processamento-http-paralelo-wip" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Aí tem esse lance de <tt>Task.async/await</tt>. Se você vem de uma linguagem que tem Threads, é bem parecido: você inicia várias Threads diferentes e espera todas retornarem antes de continuar. Mas uma Task em Elixir não é uma thread real, é uma <a href="https://en.wikipedia.org/wiki/Green_threads#Green_threads_in_other_languages"target="_blank" rel="noopener">&ldquo;green thread&rdquo;</a> ou, no jargão de Erlang, um &ldquo;process&rdquo; bem leve. Erlang usa processes para tudo, e Elixir também. Por baixo dos panos, o módulo &ldquo;Task&rdquo; encapsula todo o framework OTP de supervisors/workers. Mas em vez de ter que lidar agora com <a href="http://elixir-lang.org/getting-started/mix-otp/genserver.html"target="_blank" rel="noopener">OTP GenServer</a> decidi ir pelo caminho mais simples por enquanto, e o módulo &ldquo;Task&rdquo; cumpre essa função.</p>
<p>Daí, acabei caindo num problema. Se eu continuasse assim, disparando centenas de chamadas HTTP assíncronas, rapidamente caía nessa exception:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>17:10:55.882 [error] Task #PID&lt;0.2217.0&gt; started from #PID&lt;0.69.0&gt; terminating
** (HTTPotion.HTTPError) req_timedout
    (httpotion) lib/httpotion.ex:209: HTTPotion.handle_response/1
    (ex_manga_downloadr) lib/ex_manga_downloadr/mangareader/page.ex:6: ExMangaDownloadr.MangaReader.Page.image/1
    (elixir) lib/task/supervised.ex:74: Task.Supervised.do_apply/2
    (elixir) lib/task/supervised.ex:19: Task.Supervised.async/3
    (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Function: #Function&lt;12.106612505/0 in ExMangaDownloadr.Workflow.images_sources/1&gt;
    Args: []</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Por isso existe o <tt>@maximum_fetches 80</tt> no topo do módulo <tt>Workflow</tt>, junto com essa outra construção esquisita:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">images_sources</span><span class="p">(</span><span class="n">pages_list</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">pages_list</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">chunk</span><span class="p">(</span><span class="vi">@maximum_fetches</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">[]</span><span class="p">,</span> <span class="n">fn</span> <span class="n">pages_chunk</span><span class="p">,</span> <span class="n">acc</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="n">result</span> <span class="o">=</span> <span class="n">pages_chunk</span>
</span></span><span class="line"><span class="cl">        <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">async</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span> <span class="no">Page</span><span class="o">.</span><span class="n">image</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">)</span> <span class="k">end</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">        <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="vi">@http_timeout</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">        <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">image</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">image</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">acc</span> <span class="o">++</span> <span class="n">result</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso pega uma lista enorme (como todas as páginas de um mangá bem longo tipo Naruto), quebra em listas menores de 80 elementos e aí dispara as Tasks assíncronas, reduzindo os resultados de volta para uma List simples. A função privada <tt>chunk/2</tt> só pega o menor tamanho entre o comprimento da lista e o valor máximo de fetches.</p>
<p>Às vezes quebra se o máximo é maior, às vezes não, então meu chute é que meu código não está lidando com instabilidades de rede (com alguma lógica de retry) ou então o site MangaReader está enfileirando acima do meu timeout designado (que setei para 30 segundos). De qualquer forma, manter o valor máximo abaixo de 100 parece ser um bom equilíbrio sem derrubar o workflow.</p>
<p>Essa é uma parte que ainda não tenho certeza do que fazer para lidar com incertezas no site externo não respondendo ou a rede caindo por um tempinho. HTTPotion tem suporte para chamadas assíncronas, mas eu não sei qual a diferença entre usar isso ou só fazer chamadas síncronas dentro de processes paralelos com Task, do jeito que estou fazendo. E em qualquer dos casos, eles são supervised workers, como lidar com as exceptions, como implementar lógica para tentar de novo quando falha? Se alguém tem mais conhecimento sobre isso, um comentário aí embaixo seria bem apreciado.</p>
<p>Por fim, tem um truque sujo por trás do motivo pelo qual eu gosto de usar o MangaReader: ele é bem amigável a scrappers porque em cada página do mangá a imagem é anotada com um atributo &ldquo;alt&rdquo; no formato &ldquo;[nome do mangá] [número do capítulo] - [número da página]&rdquo;. Então só tive que reformatar um pouco, adicionando um pad de zeros antes do número do capítulo e da página para que um sort simples dos arquivos baixados me dê a ordem correta. MangaFox não é tão amigável. Assim que reformato:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">normalize_metadata</span><span class="p">(</span><span class="n">image_src</span><span class="p">,</span> <span class="n">image_alt</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">extension</span>      <span class="o">=</span> <span class="nb">String</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">image_src</span><span class="p">,</span> <span class="s2">&#34;.&#34;</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">at</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">list</span>           <span class="o">=</span> <span class="nb">String</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">image_alt</span><span class="p">)</span>      <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reverse</span>
</span></span><span class="line"><span class="cl">  <span class="n">title_name</span>     <span class="o">=</span> <span class="no">Enum</span><span class="o">.</span><span class="n">slice</span><span class="p">(</span><span class="n">list</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="no">Enum</span><span class="o">.</span><span class="n">count</span><span class="p">(</span><span class="n">list</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">&#34; &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">chapter_number</span> <span class="o">=</span> <span class="no">Enum</span><span class="o">.</span><span class="n">at</span><span class="p">(</span><span class="n">list</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">rjust</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="sc">?0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">page_number</span>    <span class="o">=</span> <span class="no">Enum</span><span class="o">.</span><span class="n">at</span><span class="p">(</span><span class="n">list</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">rjust</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="sc">?0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="n">image_src</span><span class="p">,</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">title_name</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">chapter_number</span><span class="si">}</span><span class="s2"> - Page </span><span class="si">#{</span><span class="n">page_number</span><span class="si">}</span><span class="s2">.</span><span class="si">#{</span><span class="n">extension</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Uma vez que tenho todas as imagens, disparo outro processo externo usando <a href="https://github.com/alco/porcelain"target="_blank" rel="noopener">Porcelain</a> para fazer shell out e rodar as ferramentas Mogrify e Convert do ImageMagick para redimensionar todas as imagens para 600x800 pixels (resolução do Kindle Voyage) e empacotá-las num arquivo PDF. O resultado são arquivos PDF com 250 páginas e cerca de 20Mb. Agora é só copiar os arquivos para o meu Kindle via USB.</p>
<p>O código do ImageMagick é bem chato, eu só gero os comandos no seguinte formato para o Mogrify:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&#34;mogrify -resize #{@image_dimensions} #{directory}/*.jpg&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E compilo os PDFs com este outro comando:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&#34;convert #{volume_directory}/*.jpg #{volume_file}&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>(Aliás, repare na interpolação de strings ao estilo Ruby a que estamos acostumados.)</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/526/big_11249911_10153742264949837_7817568172948440418_n.jpg" alt="OnePunch Man no Kindle"  loading="lazy" /></p>
<p>Tecnicamente eu poderia copiar os arquivos do módulo MangaReader para um novo módulo MangaFox e reaproveitar a mesma lógica de Workflow uma vez que tweaksse os parsers para lidar com o formato de página do MangaFox. Mas deixo isso como exercício para o leitor.</p>
<p>Os testes do módulo MangaReader fazem chamadas reais ao site deles. Deixei assim de propósito porque se o teste falhar significa que mudaram o formato do site e o parser precisa de ajuste para se adaptar. Mas depois de alguns anos eu nunca vi eles mudarem o suficiente para quebrar o meu velho parser em Ruby.</p>
<p>Só como exercício final eu importei o pacote Mock, para controlar como algumas peças internas da implementação do Workflow retornam. Chama-se Mock mas é mais como stub de funções específicas de um módulo. Posso declarar um bloco onde sobrescrevo o <tt>File.rename/1</tt> para que ele realmente não tente mover um arquivo que não existe no ambiente de testes. Isso deixa o teste mais frágil porque depende de uma implementação específica, o que nunca é bom, mas quando estamos lidando com I/O externo, pode ser a única opção para isolar. Foi assim que o teste do Workflow foi feito. De novo, se houver um jeito melhor estou ansioso para aprender, por favor comente aí embaixo.</p>
<p>Assim fica um teste unitário com Mock, fazendo stub dos módulos HTTPotion e File:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">test</span> <span class="s2">&#34;workflow tries to download the images&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">with_mock</span> <span class="no">HTTPotion</span><span class="p">,</span> <span class="o">[</span><span class="ss">get</span><span class="p">:</span> <span class="n">fn</span><span class="p">(</span><span class="n">_url</span><span class="p">,</span> <span class="n">_options</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="o">%</span><span class="no">HTTPotion</span><span class="o">.</span><span class="n">Response</span><span class="p">{</span> <span class="ss">body</span><span class="p">:</span> <span class="kp">nil</span><span class="p">,</span> <span class="ss">headers</span><span class="p">:</span> <span class="kp">nil</span><span class="p">,</span> <span class="ss">status_code</span><span class="p">:</span> <span class="mi">200</span> <span class="p">}</span> <span class="k">end</span><span class="o">]</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">with_mock</span> <span class="no">File</span><span class="p">,</span> <span class="o">[</span><span class="n">write!</span><span class="p">:</span> <span class="n">fn</span><span class="p">(</span><span class="n">_filename</span><span class="p">,</span> <span class="n">_body</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kp">nil</span> <span class="k">end</span><span class="o">]</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">assert</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">process_downloads</span><span class="p">(</span><span class="o">[</span><span class="p">{</span><span class="s2">&#34;http://src_foo&#34;</span><span class="p">,</span> <span class="s2">&#34;filename_foo&#34;</span><span class="p">}</span><span class="o">]</span><span class="p">,</span> <span class="s2">&#34;/tmp&#34;</span><span class="p">)</span> <span class="o">==</span> <span class="o">[</span><span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="s2">&#34;http://src_foo&#34;</span><span class="p">,</span> <span class="s2">&#34;/tmp/filename_foo&#34;</span><span class="p">}</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">      <span class="n">assert</span> <span class="n">called</span> <span class="no">HTTPotion</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;http://src_foo&#34;</span><span class="p">,</span> <span class="o">[</span><span class="ss">timeout</span><span class="p">:</span> <span class="mi">30_000</span><span class="o">]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">assert</span> <span class="n">called</span> <span class="no">File</span><span class="o">.</span><span class="n">write!</span><span class="p">(</span><span class="s2">&#34;/tmp/filename_foo&#34;</span><span class="p">,</span> <span class="kp">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Tem sido uma experiência muito divertida, ainda que muito curta, e boa o suficiente para colocar em prática o que aprendi até agora. Código como este me faz sorrir:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">[</span><span class="n">destination_file</span><span class="o">|</span><span class="n">_rest</span><span class="o">]</span> <span class="o">=</span> <span class="nb">String</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">file</span><span class="p">,</span> <span class="s2">&#34;/&#34;</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reverse</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A forma como posso fazer pattern match para extrair o head de uma lista é uma maneira diferente de pensar. Daí tem a outra forma de pensar mais importante: tudo é uma cadeia de transformação, uma aplicação é uma forma de partir de algum argumento de entrada (como uma URL) e ir passo a passo &ldquo;transformando&rdquo; ele em uma coleção de arquivos PDF, por exemplo.</p>
<p>Em vez de pensar em como arquitetar classes e objetos, começamos a pensar em quais são os argumentos iniciais e qual o resultado que quero atingir, e vamos daí, uma pequena função de transformação por vez.</p>
<p>O módulo Workflow é um exemplo. Eu na verdade comecei escrevendo tudo numa única função grande no módulo CLI. Aí refatorei em funções menores e encadeei elas para criar o Workflow. Por fim, só movi todas as funções para o módulo Workflow e chamei a partir do módulo CLI.</p>
<p>Por causa da ausência de estado global, pensar em funções pequenas e isoladas, tanto refatorar quanto fazer test-driven development ficam muito mais suaves do que em linguagens OOP. Esse jeito de pensar é, admito, lento de pegar o jeito, mas depois começa a parecer muito natural e rapidamente direciona a sua forma de programar para um código mais enxuto.</p>
<p>E os aspectos dinâmicos tanto de Erlang quanto de Elixir me fazem sentir em casa, como se fosse um &ldquo;Ruby melhorado&rdquo;.</p>
<p>O código do downloader está todo no <a href="https://github.com/akitaonrails/ex_manga_downloadr/blob/master/lib/ex_manga_downloadr/mangareader/index_page.ex"target="_blank" rel="noopener">Github, podem fazer fork</a>.</p>
<p>Estou ansioso para exercitar mais. Espero que isso motive você a aprender Elixir. E se você já é um programador avançado em Elixir ou Erlang, não esqueça de comentar aí embaixo e até mandar um Pull Request para melhorar este pequeno exercício. Ainda sou iniciante e tem muito espaço para aprender mais. Todas as contribuições são muito apreciadas.</p>
]]></content:encoded><category>elixir-e-erlang</category><category>anime-e-manga</category><category>automacao</category></item><item><title>[Small Bites] Downloader para MangaReader (Kindle Edition)</title><link>https://akitaonrails.github.io/2014/12/17/small-bites-downloader-para-mangareader-kindle-edition/</link><guid isPermaLink="true">https://akitaonrails.github.io/2014/12/17/small-bites-downloader-para-mangareader-kindle-edition/</guid><pubDate>Wed, 17 Dec 2014 21:24:00 -0200</pubDate><description>&lt;p&gt;Acho que esta vai ser realmente a primeira small bite versão miniatura mesmo! :-)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/476/IMG_20141216_162841251.jpg" alt="Kindle Paperwhite &amp;#43; Manga = Diversão" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Quem acompanha o blog faz tempo sabe que curto muito mangas/animes (me perguntem qualquer coisa, especialmente se for das décadas de 60 a 90). 4 anos atrás eu fiz um &lt;a href="http://www.akitaonrails.com/2010/07/31/downloader-para-onemanga-com#.VJIOdcZHnxg"target="_blank" rel="noopener"&gt;downloader&lt;/a&gt; para o antigo site OneManga (que fechou).&lt;/p&gt;
&lt;p&gt;Recentemente resolvi adquirir um Kindle Paperwhite (que aliás, é excepcional, principalmente se comparado ao último Kindle 2 que eu tive). E além de tudo é o formato perfeito para mangas (que são preto-e-branco por natureza).&lt;/p&gt;</description><content:encoded><![CDATA[<p>Acho que esta vai ser realmente a primeira small bite versão miniatura mesmo! :-)</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/476/IMG_20141216_162841251.jpg" alt="Kindle Paperwhite &#43; Manga = Diversão"  loading="lazy" /></p>
<p>Quem acompanha o blog faz tempo sabe que curto muito mangas/animes (me perguntem qualquer coisa, especialmente se for das décadas de 60 a 90). 4 anos atrás eu fiz um <a href="http://www.akitaonrails.com/2010/07/31/downloader-para-onemanga-com#.VJIOdcZHnxg"target="_blank" rel="noopener">downloader</a> para o antigo site OneManga (que fechou).</p>
<p>Recentemente resolvi adquirir um Kindle Paperwhite (que aliás, é excepcional, principalmente se comparado ao último Kindle 2 que eu tive). E além de tudo é o formato perfeito para mangas (que são preto-e-branco por natureza).</p>
<p>Procurei ferramentas e achei vários horríveis, pra Windows. Então, por que não desenferrujar um pouco e fazer um pacote completo que baixa sua coleção completa de mangas e já gera um PDF otimizado pro tamanho do Kindle?</p>
<p>Aproveitei pra abusar um pouco do <a href="https://github.com/typhoeus/typhoeus"target="_blank" rel="noopener">Typhoeus</a> que usa a libcurl por baixo e permite fazer downloads paralelos. A única coisa que vai limitar o processo é a velocidade de banda do seu provedor de internet.</p>
<p>Sem enrolar mais, o código-fonte <a href="https://github.com/akitaonrails/manga-downloadr"target="_blank" rel="noopener">está no Github</a> ou instale diretamente a <a href="https://rubygems.org/gems/manga-downloadr"target="_blank" rel="noopener">gem</a>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>gem install manga-downloadr</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E para usar, navegue no site do <a href="http://www.mangareader.net"target="_blank" rel="noopener">MangaReader.net</a> escolha o manga que quiser e faça assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>manga-downloadr -n monster -u http://www.mangareader.net/99/monster.html -d /Users/akitaonrails/Documents/MangaReader</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E pronto! Se acontecer alguma interrupção (a internet cair ou coisa parecida) só execute novamente o mesmo comando e ele vai continuar de onde parou. Boa diversão!</p>
<p>PS: Não preciso dizer que não funciona em Windows ;-)</p>
]]></content:encoded><category>anime-e-manga</category><category>ruby</category><category>automacao</category></item></channel></rss>