001/* 002 * SPDX-License-Identifier: Apache-2.0 003 * 004 * Copyright 2025-2026 The Enola <https://enola.dev> Authors 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package dev.enola.cas; 019 020import com.google.common.io.ByteSource; 021 022import io.ipfs.api.AddArgs; 023import io.ipfs.api.IPFS; 024import io.ipfs.api.NamedStreamable; 025import io.ipfs.cid.Cid; 026 027import org.slf4j.Logger; 028import org.slf4j.LoggerFactory; 029 030import java.io.IOException; 031 032// https://docs.ipfs.tech/reference/kubo/rpc/ 033/** <a href="https://ipfs.tech/">IPFS</a> Kubo RPC API client. */ 034public class IPFSBlobStore implements BlobStore { 035 private final Logger LOG = LoggerFactory.getLogger(getClass()); 036 037 // TODO: Support IPLD <=> Thing API bridge; see https://github.com/enola-dev/enola/issues/777. 038 039 private final IPFS ipfs; 040 041 public IPFSBlobStore(IPFS ipfs) { 042 this.ipfs = ipfs; 043 try { 044 LOG.info("{}", ipfs.version()); 045 LOG.info("IPFS {}", ipfs.version); 046 } catch (IOException e) { 047 LOG.error("Failed to retrieve IPFS version", e); 048 } 049 } 050 051 @Override 052 public ByteSource load(Cid cid) throws IOException { 053 return ByteSource.wrap(ipfs.cat(cid)); 054 } 055 056 public ByteSource load(Cid cid, String subPath) throws IOException { 057 return ByteSource.wrap(ipfs.cat(cid, subPath)); 058 } 059 060 @Override 061 public Cid store(ByteSource source) throws IOException { 062 try (var is = source.openStream()) { 063 var namedStreamable = new NamedStreamable.InputStreamWrapper(is); 064 // TODO .setHash("blake3") https://github.com/multiformats/java-multihash/issues/49 065 var args = AddArgs.Builder.newInstance().setCidVersion(1).build(); 066 var merkleNode = ipfs.add(namedStreamable, args); 067 return Cid.build(1, Cid.Codec.Raw, merkleNode.get(0).hash); 068 } 069 } 070 071 // TODO Cid store(Tree of relative directories & files...) 072}