Organizador de arquivos em GO

ionnss - Oct 29 - - Dev Community

Olá, ions por aqui, novamente.

O medo de estar aprendendo algo que um dia uma IA irá realizar, me deixa completamente angustiado. Mas, se "resolver problemas" ainda for uma exigência impostas aos seres humanos do futuro, por que não persistir?

Desta vez trago outro tutorial. Menos inútil que o primeiro. Então vamos definir as estruturas dos "problemas", pois de uma coisa nós já sabemos: quem não tem problemas, é porque não procurou o suficiente. E quem ainda não os encontrou, é só uma questão de tempo para conseguir criá-los.

Estrutura do projeto

A estrutura mais simples do programa é:

  • Varrer uma pasta (por exemplo a pasta de downloads ou outro diretório)
  • Identificar o tipo de cada arquivo no diretório em questão
  • Mover o arquivo para uma subpasta correspondente ao seu tipo (imagens, videos, documentos...)

Iniciando o projeto

Crie um diretório e navegue até ele:

mkdir organizador
cd organizador
Enter fullscreen mode Exit fullscreen mode

Crie um arquivo organizador.go e inicie os módulos do mesmo:

touch organizador.go
go mod init organizador.go
Enter fullscreen mode Exit fullscreen mode

Você deve ter algo mais ou menos assim:

~/organizador
.
├── go.mod
└── organizador.go
Enter fullscreen mode Exit fullscreen mode

Parte 1: Verificando se um diretório existe

Vamos definir o diretório de origemdirOrigem no qual realizaremos a organização. Com ele definido, vamos verificar se ele existe de fato, caso contrário, retornaremos um erro:

package main

import (
    "fmt"
    "os"
)

// Defina o deretório o qual você quer organizar como variável global
var dirOrigem string := "/Users/User/Downloads" // Troque o diretório 

func main() {

    // Verificar se o diretório existe, caso contrário, retornar erro
    if _, err := os.Stat(dirOrigem); os.IsNotExist(err) {
        fmt.Println("BAD DIR :( \nDiretório não encontrado: ", dirOrigem)
        return
    } else {
        // Imprimir mensagem caso o diretório exista
        fmt.Println("GOOD DIR :) \nDiretório encontrado: ", dirOrigem)
    }
}

Enter fullscreen mode Exit fullscreen mode

Agora, vamo fazer algumas considerações a respeito do código acima:

  • A função os.Stat retorna dois valores do tipo os.FileInfo e erro err.
  1. FileInfo é uma interface que retorna informações detalhadas de arquivos, que neste caso, não precisamos. Portanto, para ignorarmos essa interface, utilizamos _,. Entretanto, não queremos ignorar o erro: if _, err := ...
  2. Passamos o erro err como parâmetro para a função os.IsNotExist(), pois se o diretório não existir, a função os.Stat() retornará um erro NÃO NULO, fazendo com que a função os.IsNotExist() retorne true, executando nossa mensagem: BAD DIR :(
  3. Se os.IsNotExist() retornar false, teremos a impressão da mensagem da condição else: GOOD DIR :)

Parte 2: Conceito de função de "callback" é uma doidera, bixo!

Você reparou que aqui estamos indo aos poucos e nos deliciando com os bits e bytes ao som do teclado mecânico. _Tchaka tchaka bum! _

E agora nos vamos criar uma função do tipo callback. Algo que eu nunca tinha de fato aprendido sobre, ou nunca tive curiosidade o suficiente para questionar se um dia eu utilizei esse conceito em algum código python da minha vida pré-golang.

Função callback é uma função passada com argumento para outra função.

Se você já está familiarizado com o conceito, congrats, caso constrário, congrats. Ou seja, congrats!

Agora vamos criar uma função callback listarArquivos que será passada como argumento para outra função filepath.Walk.

func main() { 
// Restante do código
.
.
.

// Percorrer e listar os arquivos no diretório dirOrigem
    err := filepath.Walk(dirOrigem, listarArquivos)
    if err != nil {
        fmt.Println("Erro ao percorrer o diretório: ", err)
    }
}
Enter fullscreen mode Exit fullscreen mode
// Função que lista os arquivos do diretório
func listarArquivos(caminho string, info os.FileInfo, err error) error {
    if err != nil {
        return err
    }

    // Ignorar diretórios e exibir apenas arquivos
    if !info.IsDir() && !strings.HasPrefix(info.Name(), ".") {
        fmt.Println("Arquivo encontrado: ", info.Name())
    }
    return nil
}
Enter fullscreen mode Exit fullscreen mode

Mas calma, como que filepath.Walk chama a Função de Callback?

Quando você chama filepath.Walk(dirOrigem, listarArquivos), a função filepath.Walk faz o trabalho pesado de percorrer todos os arquivos e subdiretórios dentro de dirOrigem.

Para cada arquivo ou diretório encontrado, ela chama a função listarArquivos com três argumentos:

  • caminho: o caminho completo para o arquivo ou diretório atual.
  • info: um objeto os.FileInfo que contém informações sobre o arquivo/diretório (como nome, se é um diretório, tamanho, etc.).
  • err: um erro, caso algo dê errado ao acessar esse arquivo ou diretório.

O Go entende automaticamente que listarArquivos deve receber esses três parâmetros porque filepath.Walk espera uma função que siga exatamente essa assinatura:

// Função Walk()
func Walk(root string, walkFn WalkFunc) error

// Tipo WalkFunc
type WalkFunc func(path string, info os.FileInfo, err error) error
Enter fullscreen mode Exit fullscreen mode

Repare que a função Walk retorna um error! Isso é relevante!

Por isso, que igualamos a nossa função filepath.Walk(dirOrigem, listarArquivos) a um err:

err := filepath.Walk(dirOrgiem, listarArquivos)
Enter fullscreen mode Exit fullscreen mode

Pois no final das contas, por retornar um erro, ela é um erro XD

Exemplo em Ação

Aqui está uma visão mais detalhada do que acontece em cada passo:

//Percorrer e listar os arquivos no diretório
err := filepath.Walk(dirOrigem, listarArquivos)
Enter fullscreen mode Exit fullscreen mode

Para cada arquivo ou diretório em dirOrigem, filepath.Walk vai chamar listarArquivos como se fosse algo assim:

listarArquivos("/Users/User/Downloads/file1.txt", infoSobreFile1, nil)
listarArquivos("/Users/User/Downloads/file2.txt", infoSobreFile2, nil)
listarArquivos("/Users/User/Downloads/file3.mp3", infoSobreFile3, nil)
Enter fullscreen mode Exit fullscreen mode

Nesse exemplo, para cada chamada:

  • caminho: recebe o caminho do arquivo ou diretório.
  • info: contém informações sobre esse item (como o nome e o tipo).
  • err: é usado para capturar qualquer erro específico ao acessar o arquivo/diretório.

Função de Callback

A listarArquivos é uma função de callback que filepath.Walk chama automaticamente com esses valores. Assim, não precisamos nos preocupar em definir os valores de caminho, info e err; o filepath.Walk já faz isso por nós.

UFA!

Agora faça aquele teste maroto em seu terminal:

go run organizador.go
Enter fullscreen mode Exit fullscreen mode

Você pode ter com resultado:

GOOD DIR :)
Diretório encontrado:  /Users/User/Downloads
Arquivo encontrado:  Suic_ SOS · 4.32pm · 10-25.jpeg
Arquivo encontrado:  Suic_ SOS.jpeg
Arquivo encontrado:  quantum-distortion-cyberpunk-music-234058.mp3
Arquivo encontrado:  slow-mode-enabled-cyberpunk-music-230623.mp3
Arquivo encontrado:  teaching-lp-20152-seminario-go.pdf
Enter fullscreen mode Exit fullscreen mode

Ou:

go run organizador.go
BAD DIR :(
Diretório não encontrado:  /Users/User/Downloadss
Enter fullscreen mode Exit fullscreen mode

Nesse caso eu só coloquei um "s" extra em "Downloads" para que o dirOrigem ficasse incorreto.

Agora apague a função listarArquivos, pois não vamos usá-la.

Brincadeira, só vamos mudar ela de nome e adicionar outras lógicas.

PARTE 3: ORGANIZAR != ORGANIZADO

Organizado é bom, organizar é fod@.

Depois desta constatação genial da minha parte, vamos partir para a parte que nos interessa de fato: organizar a p*rr@ toda.

Por ironia da vida, antes de organizar os arquivos, temos que organizar as idéias dos próximos passos.

Nossa próxima função precisa, basicamente:

  • Criar subpastas com base nas extensões de cada arquivo do nosso diretório dirOrigem, caso ela não exista.
  • Mover os arquivos para suas respectivas pastas de acordo com suas extensões.
  • Mas se os arquivos já estiverem nas subpastas organizadoras, ele não deve criá-las novamente.

Vamos entender o que cada parte desse código faz:

// Função que organiza os arquivos do diretório
func organizarArquivos(caminho string, info os.FileInfo, err error) error {
    if err != nil {
        return err
    }

    // Ignorar diretórios e exibir apenas arquivos
    if !info.IsDir() && !strings.HasPrefix(info.Name(), ".") {
        fmt.Println("Arquivo encontrado: ", info.Name())

        // Obter extensões em letras minúsculas
        extensao := strings.ToLower(filepath.Ext(info.Name()))

        // Criar nome das subpastas diretamente em dirOrigem
        subpasta := filepath.Join(dirOrigem, extensao[1:])

        // Verificar se a subpasta já existe; caso contrário, criar a pasta
        if _, err := os.Stat(subpasta); os.IsNotExist(err) {
            err := os.Mkdir(subpasta, os.ModePerm)
            if err != nil {
                fmt.Println("Erro ao criar subpasta: ", err)
                return err
            }
        }

        // Caminho de destino para mover arquivos
        caminhoDestino := filepath.Join(subpasta, info.Name())

        // Verificar se o arquivo já está na subpasta
        if caminhoDestino == caminho {
            fmt.Printf("O arquivo %s já está na subpasta %s. Ignorando...\n", info.Name(), subpasta)
        } else {
            // Mover o arquivo para a subpasta
            err := os.Rename(caminho, caminhoDestino)
            if err != nil {
                fmt.Println("Erro ao mover arquivo: ", err)
                return err
            }
            fmt.Printf("Arquivo %s movido para %s\n", info.Name(), subpasta)
        }
    }
    return nil
}

Enter fullscreen mode Exit fullscreen mode

Estrutura da função organizarArquivos

A função organizarArquivos é chamada para cada arquivo ou pasta encontrada na estrutura de diretórios. Ela verifica as condições para organizar cada arquivo com base em sua extensão, criando subpastas e movendo arquivos, se necessário.

func organizarArquivos(caminho string, info os.FileInfo, err error) error {
    if err != nil {
        return err
    }
Enter fullscreen mode Exit fullscreen mode

Aqui, a função organizarArquivos recebe três parâmetros:

  • caminho: o caminho completo para o arquivo ou diretório atual.
  • info: informações do arquivo ou diretório, obtidas do tipo os.FileInfo.
  • err: um possível erro que pode ocorrer ao tentar acessar o item.

A primeira verificação é se existe algum erro ao acessar o arquivo/diretório. Caso positivo, ele é retornado imediatamente.

Filtrando arquivos e ignorando diretórios ocultos

if !info.IsDir() && !strings.HasPrefix(info.Name(), ".") {
        fmt.Println("Arquivo encontrado: ", info.Name())
Enter fullscreen mode Exit fullscreen mode

Este trecho faz duas verificações:

  • !info.IsDir(): verifica se o item não é um diretório (ou seja, é um arquivo).
  • !strings.HasPrefix(info.Name(), "."): verifica se o nome do arquivo não começa com ".", ignorando arquivos ocultos em sistemas baseados em Unix.

Se as duas condições forem atendidas, o arquivo é exibido com fmt.Println.

Identificando a extensão do arquivo e criando o nome da subpasta

extensao := strings.ToLower(filepath.Ext(info.Name()))
subpasta := filepath.Join(dirOrigem, extensao[1:])

Enter fullscreen mode Exit fullscreen mode

Aqui:

  • strings.ToLower(filepath.Ext(info.Name())): extrai a extensão do arquivo (por exemplo, .txt) e a transforma em minúsculas para garantir consistência.
  • subpasta := filepath.Join(dirOrigem, extensao[1:]): cria o caminho completo da subpasta onde o arquivo será movido. O extensao[1:] remove o ponto inicial (.) da extensão, formando o nome da subpasta, como txt.

Criando a subpasta, caso ainda não exista

if _, err := os.Stat(subpasta); os.IsNotExist(err) {
            err := os.Mkdir(subpasta, os.ModePerm)
            if err != nil {
                fmt.Println("Erro ao criar subpasta: ", err)
                return err
            }
        }

Enter fullscreen mode Exit fullscreen mode

Aqui, a função:

  • Verifica se a subpasta já existe usando os.Stat.
  • Se a subpasta não existe (os.IsNotExist(err)), ela é criada com os.Mkdir(subpasta, os.ModePerm).
  • os.ModePerm define as permissões padrão para a nova pasta. Caso haja um erro ao criar a pasta, ele é exibido e retornado.

Definindo o caminho de destino do arquivo

caminhoDestino := filepath.Join(subpasta, info.Name())
Enter fullscreen mode Exit fullscreen mode

Neste ponto, caminhoDestino representa o caminho final para onde o arquivo será movido. Ele é construído usando filepath.Join, para unir o caminho da subpasta ao nome do arquivo.

Verificando se o arquivo já está na pasta de destino

if caminhoDestino == caminho {
            fmt.Printf("O arquivo %s já está na subpasta %s. Ignorando...\n", info.Name(), subpasta)
        } else {
            // Mover o arquivo para a subpasta
            err := os.Rename(caminho, caminhoDestino)
            if err != nil {
                fmt.Println("Erro ao mover arquivo: ", err)
                return err
            }
            fmt.Printf("Arquivo %s movido para %s\n", info.Name(), subpasta)
        }
    }
    return nil
}
Enter fullscreen mode Exit fullscreen mode
  • Este trecho compara o caminhoDestino com o caminho atual do arquivo. Se forem iguais, significa que o arquivo já está na subpasta correta, então ele é ignorado com uma mensagem (fmt.Printf).
  • Caso contrário, os.Rename(caminho, caminhoDestino) move o arquivo para a subpasta. Se houver erro durante a movimentação, ele é retornado.

Resumo Final

A função:

  1. Percorre um diretório, verificando cada item.
  2. Ignora diretórios e arquivos ocultos.
  3. Determina a extensão do arquivo e, com isso, a subpasta de destino.
  4. Cria a subpasta (se ainda não existir).
  5. Move o arquivo para a subpasta, a menos que ele já esteja lá.

O uso de filepath.Walk(dirOrigem, organizarArquivos) passa essa função para cada arquivo dentro do diretório, fazendo com que todos sejam organizados automaticamente.

Esse código se encaixa bem como uma função de organização de arquivos porque ele lida com a lógica de criação e movimentação em uma única função – uma forma eficiente e organizada de estrutura.

REPO: https://github.com/ionnss/organizador


***Mais um dia na terra,
ions

. .
Terabox Video Player