Skip to content

Commit

Permalink
remove choice of IPFS gateway, just try all of them instead
Browse files Browse the repository at this point in the history
  • Loading branch information
bjesus committed Oct 6, 2024
1 parent f8b5124 commit 03a3ca8
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 141 deletions.
14 changes: 0 additions & 14 deletions app.config.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,4 @@
export default defineAppConfig({
title: "TeaTime",
icon: "🫖",
ipfsGateways: {
"ipfs.io": {
name: "IPFS.io",
description: "IPFS official gateway",
info: "https://docs.ipfs.tech/concepts/ipfs-gateway/",
url: "https://ipfs.io/ipfs/${cid}?filename=${filename}",
},
localhost: {
name: "Local IPFS Gateway",
description: "Local IPFS gateway",
info: "https://docs.ipfs.tech/install/ipfs-desktop/",
url: "http://${cid}.ipfs.localhost:8080/?filename=${filename}",
},
},
});
158 changes: 103 additions & 55 deletions app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ footer {
}
</style>

<script setup>
<script setup lang="ts">
import sanitize from "sanitize-filename";
const appConfig = useAppConfig();
import { createDbWorker } from "sql.js-httpvfs";
Expand All @@ -136,7 +136,7 @@ const ipfsGateway = useLocalStorage("ipfsGateway", "ipfs.io");
const searchQuery = useState("searchQuery", () => "");
const isLoading = useState("isLoading", () => false);
const directLink = useState("directLink", () => "");
const directLink = useState("directLink", () => false);
const view = useState("view", () => "welcome");
const darkMode = useState("darkMode", () => false);
const bookURL = useState("bookURL", () => "");
Expand Down Expand Up @@ -281,10 +281,81 @@ const fetchResults = async (query) => {
}
};
const getBookURL = (result, filename) =>
appConfig.ipfsGateways[ipfsGateway.value].url
.replaceAll("${cid}", result.ipfs_cid)
.replaceAll("${filename}", filename);
const downloadUrls = (urls: string[], contentLength: number): Promise<Blob> => {
const controller = new AbortController();
const signal = controller.signal;
const downloads = urls.map((url) =>
fetch(url, { signal }).then((response) => {
if (!response.ok)
throw new Error(`HTTP error! status: ${response.status}`);
if (!response.body) throw new Error("Response body is null");
const reader = response.body.getReader();
let receivedLength = 0;
const chunks: Uint8Array[] = [];
return {
read: async function () {
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
chunks.push(value);
receivedLength += value.length;
const progress = (receivedLength / contentLength) * 100;
if (progress > downloadProgress.value) {
downloadProgress.value = progress;
}
if (downloadProgress.value >= 100) {
controller.abort();
break;
}
}
return new Blob(chunks, {
type:
response.headers.get("content-type") ||
"application/octet-stream",
});
},
contentType:
response.headers.get("content-type") || "application/octet-stream",
};
}),
);
return new Promise((resolve, reject) => {
let completedDownloads = 0;
let failedDownloads = 0;
downloads.forEach(async (download) => {
try {
const { read, contentType } = await download;
const blob = await read();
resolve(new Blob([blob], { type: contentType }));
controller.abort(); // Cancel other ongoing downloads
} catch (error) {
failedDownloads++;
if (failedDownloads === urls.length) {
reject(new Error("All downloads failed"));
}
} finally {
completedDownloads++;
if (
completedDownloads === urls.length &&
failedDownloads === urls.length
) {
reject(new Error("All downloads failed"));
}
}
});
});
};
const handleClick = async (result) => {
lastResult.value = result;
Expand All @@ -300,66 +371,43 @@ const handleClick = async (result) => {
bookFile.value = null;
downloadProgress.value = 0;
const filename = sanitize(`${result.author} - ${result.title}.${result.ext}`);
directLink.value = false;
bookURL.value = getBookURL(result, filename);
const filename = sanitize(
`${result.author} - ${result.title}.${result.ext}`.trim(),
);
try {
const response = await fetch(bookURL.value);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const reader = response.body.getReader();
const contentLength = result.Filesize;
let receivedLength = 0;
let chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
chunks.push(value);
isLoading.value = false;
receivedLength += value.length;
downloadProgress.value = (receivedLength / contentLength) * 100;
}
let chunksAll = new Uint8Array(receivedLength);
let position = 0;
for (let chunk of chunks) {
chunksAll.set(chunk, position);
position += chunk.length;
}
const urls = [
"https://ipfs.io",
"https://flk-ipfs.xyz",
"https://ipfs.cyou",
"https://storry.tv",
"https://dweb.link",
"https://gateway.pinata.cloud",
"https://ipfs.runfission.com",
"https://nftstorage.link",
"https://4everland.io",
"https://w3s.link",
"http://localhost:8080",
].map((u) => `${u}/ipfs/${result.ipfs_cid}`);
bookURL.value = urls[0];
const blob = await downloadUrls(urls, result.size);
bookFile.value = new File([blob], filename, {
type: blob.type,
lastModified: new Date().getTime(),
});
console.log("Download completed");
const { fraction } = updatedHistory.find((b) =>
bookURL.value.includes(result.ipfs_cid),
);
bookProgress.value = fraction;
// Create a Blob from the Uint8Array
const blob = new Blob([chunksAll], {
type: response.headers.get("content-type"),
});
// Create a File object from the Blob
// bookFile.value = new File([blob], filename, {
bookFile.value = new File([blob], "blabla.epub", {
type: response.headers.get("content-type"),
lastModified: new Date().getTime(),
});
console.log("Download completed");
} catch (error) {
console.error("Download failed:", error);
view.value = "error";
error.value = error;
return;
} finally {
isLoading.value = false;
}
Expand Down
40 changes: 0 additions & 40 deletions components/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,41 +55,6 @@
>
Show all
</button>
<h2>IPFS Gateway</h2>
<p>Please choose a TeaTime compatible database to search in.</p>
<table>
<thead>
<tr>
<th class="center">Active</th>
<th>Name</th>
<th>Description</th>
<td></td>
</tr>
</thead>
<tbody>
<tr
v-for="(gateway, value) of appConfig.ipfsGateways"
@click="setGateway(value)"
:class="{ active: ipfsGateway === value }"
>
<td class="center">
<input
type="radio"
name="ipfsGateway"
:checked="ipfsGateway === value"
@click="setGateway(value)"
/>
</td>
<td>{{ gateway.name }}</td>
<td>{{ gateway.description }}</td>
<td class="right">
<a :href="gateway.info" target="_blank">
<LucideExternalLink />
</a>
</td>
</tr>
</tbody>
</table>
</div>
</template>

Expand Down Expand Up @@ -166,7 +131,6 @@ const showAllRemotes = ref(false);
const remotes = ref([]);
const remote = useLocalStorage("remote");
const remoteConfig = useLocalStorage("remoteConfig");
const ipfsGateway = useLocalStorage("ipfsGateway");
const remotesList = computed(() => {
return showAllRemotes.value ? remotes.value : remotes.value.slice(0, 5);
Expand Down Expand Up @@ -194,8 +158,4 @@ const setRemote = async (selection) => {
remoteConfig.value = JSON.stringify(config);
remote.value = selection.full_name;
};
const setGateway = async (gateway) => {
ipfsGateway.value = gateway;
};
</script>
32 changes: 0 additions & 32 deletions teatime.config.ts

This file was deleted.

0 comments on commit 03a3ca8

Please sign in to comment.