terça-feira, 27 de janeiro de 2026

🗂️ Acessando Arquivos Usando Java com Samba JCIFS

1. Introdução

Capacidades de intercâmbio de arquivos entre plataformas são cruciais para a operação de redes de computadores. Podemos fornecê-las com o protocolo Server Message Block (SMB) e sua implementação de código aberto amplamente difundida, o Samba.

Neste tutorial, aprenderemos como acessar recursos Samba a partir do Java, sem a necessidade de montar ou mapear uma unidade de rede.


2. A biblioteca JCIFS

O Common Internet File System (CIFS) é um dialeto do SMB. Usaremos a implementação codelibs JCIFS, que suporta o mais recente protocolo SMB3.

Vamos adicioná-la com a dependência do Maven:

<dependency>
    <groupId>org.codelibs</groupId>
    <artifactId>jcifs</artifactId>
    <version>3.0.1</version>
</dependency>

3. Configurando um Servidor Samba

💻 Ao longo deste tutorial, usaremos o servidor Samba configurado em uma máquina convidada do VirtualBox. Como convidado, escolhemos o servidor Ubuntu.

Em seguida, vamos configurar o adaptador de rede somente hospedeiro do VirtualBox. Então, estreitamos o intervalo de endereços DHCP para um único endereço IP: 192.168.56.101. Este truque nos poupa uma configuração de IP estático mais sofisticada.

Durante a instalação do sistema convidado, vamos adicionar o usuário `jane`. Depois, precisamos instalar o Samba e adicionar `jane` aos usuários do Samba:

$ sudo smbpasswd -a jane

Precisamos de duas pastas para os compartilhamentos:

$ mkdir /srv/samba/public /srv/samba/sambashare

Para expor as pastas, definimos a permissão 777 nelas:

$ sudo chmod 777 /srv/samba/public /srv/samba/sambashare

Agora, vamos editar o arquivo `/etc/samba/smb.conf`:

$ sudo nano /etc/samba/smb.conf

Então adicionamos duas seções para compartilhamentos:

[publicshare]
   comment = Anonymous Samba share
   path = /srv/samba/public
   read only = no
   guest ok = yes
   guest only = yes

[sambashare]
   comment = Samba on Ubuntu
   path = /srv/samba/sambashare
   read only = no
   browsable = yes

Desta forma, criamos dois compartilhamentos Samba: um `publicshare` anônimo e um `sambashare` protegido por senha.


4. Exemplo Simples

🔍 Vamos executar um código simples para ver o básico de alcançar um compartilhamento Samba. Verificaremos um arquivo localizado no `publicshare`:

// Contexto padrão
CIFSContext context = SingletonContext.getInstance();

LOGGER.info("#  Verificando se o arquivo existe");
try (SmbFile file = new SmbFile("smb://192.168.56.101/publicshare/test.txt", context)) {
    if (file.exists()) {
        LOGGER.info("Arquivo " + file.getName() + " encontrado!");
    } else {
        LOGGER.info("Arquivo " + file.getName() + " não encontrado!");
    }
}

Podemos notar os elementos necessários para estabelecer a comunicação:

  • CIFSContext mantém a configuração do cliente, credenciais e outras informações relacionadas. Aqui, é uma instância de `SingletonContext`, que fornece credenciais adequadas para uma conta anônima.
  • SmbFile, que representa qualquer tipo de recurso Samba. No nosso caso, este é um arquivo. Podemos colocá-lo em um bloco try-with-resource.

Finalmente, usamos o método `exists()` no objeto de arquivo.


5. Autenticação

🔐 Podemos criar o objeto `CIFSContext` com credenciais. Vamos listar os elementos no compartilhamento protegido por senha `sambashare`:

NtlmPasswordAuthenticator credentials = new NtlmPasswordAuthenticator(
    "WORKGROUP",    // Nome do domínio
    "jane",         // Nome de usuário
    "Test@Password" // Senha
);

// Contexto com autenticação
CIFSContext authContext = context.withCredentials(credentials);

LOGGER.info("# Fazendo login com usuário e senha");
try (SmbFile res = new SmbFile("smb://192.168.56.101/sambashare/", authContext)) {
    for (String element : res.list()) {
        LOGGER.info("Elemento encontrado " + element);
    }
}

Primeiro, criamos o objeto `NtlmPasswordAuthenticator` para armazenar as credenciais. Em seguida, chamamos o método `withCredentials()` no objeto de contexto existente. Como resultado, obtivemos um `authContext` filho com nossas credenciais. Finalmente, a função `list()` mostrou todos os componentes do compartilhamento.


6. Trabalhando com Arquivos e Diretórios

📂 O JCIFS fornece um conjunto abrangente de funções para operar em arquivos e pastas. Vamos dar uma olhada em algumas delas.

6.1. Listando e Verificando Arquivos

Com `listFiles()`, podemos listar arquivos e pastas. Ele retorna um objeto `SmbFile`, que permite o uso de muitas funções de verificação:

LOGGER.info("# Listar arquivos e pastas no compartilhamento Samba");
try (SmbFile res = new SmbFile("smb://192.168.56.101/publicshare/", context)) {
    for (SmbFile element : res.listFiles()) {
        LOGGER.info("Elemento Samba encontrado de nome: " + element.getName());
        LOGGER.info("    O elemento é arquivo ou pasta: " + (element.isDirectory() ? "pasta" : "arquivo"));
        LOGGER.info("    Tamanho: " + element.length());
        LOGGER.info("    Última modificação: " + new Date(element.lastModified()));
    }
}

Neste exemplo, iteramos por todos os itens na pasta pública. Determinamos se é um arquivo ou um diretório usando o método `isDirectory()`. Em seguida, recuperamos seu tamanho e tempo de modificação com os métodos `length()` e `getLastModified()`, respectivamente.

6.2. Criando e Excluindo Arquivos

A biblioteca JCFIS permite a criação e exclusão de arquivos e diretórios. Primeiro, vamos trabalhar com arquivos. Vamos criar e imediatamente excluir o arquivo `New_file.txt`:

LOGGER.info("# Criando e excluindo um arquivo");
String fileName = "New_file.txt";
try (SmbFile file = new SmbFile("smb://192.168.56.101/publicshare/" + fileName, context)) {
    LOGGER.info("Prestes a criar o arquivo " + file.getName() + "!");
    file.createNewFile();

    LOGGER.info("Prestes a excluir o arquivo " + file.getName() + "!");
    file.delete();
}

Criamos um objeto `SmbFile` para um arquivo ainda não existente. Em seguida, chamamos seu método `createNewFile()`. Finalmente, aplicamos a função `delete()` para remover o arquivo. Notavelmente, `createNewFile()` ignora arquivos existentes sem notificação.

6.3. Criando e Excluindo Pastas

Podemos lidar com pastas de maneira semelhante:

LOGGER.info("# Criando e excluindo uma pasta");
String newFolderName = "New_folder/";
try (SmbFile newFolder = new SmbFile("smb://192.168.56.101/publicshare/" + newFolderName, context)) {
    LOGGER.info("Prestes a criar a pasta " + newFolder.getName() + "!");
    newFolder.mkdir();

    LOGGER.info("Prestes a excluir a pasta " + newFolder.getName() + "!");
    newFolder.delete();
}

Usamos o método `mkdir()` para criar a pasta e o método `delete()` para removê-la. No caso da pasta, devemos garantir que a pasta não exista; caso contrário, `mkdir()` falhará.

👀 Vamos notar que devemos ter muito cuidado ao remover uma pasta com `delete()`. Este método percorre e exclui toda a árvore de pastas, com todos os arquivos. Além disso, ele é capaz de remover a permissão somente leitura em arquivos.

Além disso, podemos criar uma árvore de diretórios inteira com o método `mkdirs()`:

LOGGER.info("# Criando e excluindo uma subpasta com pai");
newFolderName = "New_folder/";
String subFolderName = "New_subfolder/";
try (SmbFile newSubFolder = new SmbFile("smb://192.168.56.101/publicshare/" + newFolderName + subFolderName, context)) {
    LOGGER.info("Prestes a criar a pasta " + newSubFolder.getName() + "!");
    newSubFolder.mkdirs();
}

Criamos um novo diretório, `New_subfolder`, juntamente com a pasta pai anteriormente inexistente, `New_folder`.

6.4. Copiando Arquivos

O método `copyTo()` facilita a cópia de arquivos e diretórios. Podemos usá-lo em um único arquivo ou em uma pasta. Vamos copiar todo o conteúdo do `sambashare` para o `publicshare`:

LOGGER.info("# Copiando arquivos com copyTo");
try (SmbFile source = new SmbFile("smb://192.168.56.101/sambashare/", authContext); //precisa de autenticação
    SmbFile dest = new SmbFile("smb://192.168.56.101/publicshare/", context)) { //compartilhamento público
    source.copyTo(dest);
}

Copiamos chamando `copyTo()` no recurso Samba de origem e passando o recurso de destino `dest` para este método. Notavelmente, esses recursos são compartilhamentos Samba diferentes.

Também podemos copiar arquivos entre servidores diferentes. No entanto, não podemos copiar do sistema de arquivos local, apenas entre recursos gerenciados pelo Samba.


7. Trabalhando com Fluxos (Streams)

🔄 A biblioteca fornece `SmbFileInputStream` e `SmbFileOutputStream`, que substituem as classes abstratas padrão `InputStream` e `OutputStream` do Java, respectivamente. Vamos copiar um arquivo local para o compartilhamento Samba:

LOGGER.info("# Copiando arquivos com fluxos");
try (InputStream is = new FileInputStream("/home/joe/test.txt"); //Arquivo local
     SmbFile dest = new SmbFile("smb://192.168.56.101/publicshare/test_copy.txt", context); //Recurso Samba
     OutputStream os = dest.getOutputStream()) {

    byte[] buffer = new byte[65536]; // usando buffer de 64KB
    int bytesRead;
    while ((bytesRead = is.read(buffer)) != -1) {
        os.write(buffer, 0, bytesRead);
    }
}

Lemos o arquivo local com `FileInputStream`. Em seguida, copiamos o conteúdo usando um buffer interno. Este método complementa o método `copyTo()` quando arquivos locais estão em jogo.


8. Conclusão

🎯 Neste artigo, aprendemos como acessar recursos Samba com a biblioteca JCIFS. Para testes, configuramos um servidor Samba simples. Em seguida, examinamos um recurso compartilhado e aprendemos brevemente sobre autenticação Samba.

Depois, focamos nas operações de arquivos. Primeiro, listamos arquivos e pastas e verificamos suas propriedades. Em seguida, realizamos operações de criação, cópia e exclusão em arquivos e diretórios. Finalmente, usamos a implementação JCIFS dos fluxos de E/S do Java para ler e escrever arquivos Samba.

Como sempre, o código para os exemplos está disponível no GitHub.