version/0.8 #7
			
				
			
		
		
		
	
							
								
								
									
										10
									
								
								README.md
								
								
								
								
							
							
						
						
									
										10
									
								
								README.md
								
								
								
								
							| 
						 | 
				
			
			@ -6,9 +6,9 @@ Un motore di ricerca libri basato su protocollo IPFS.
 | 
			
		|||
 | 
			
		||||
Una versione funzionante si trova [qui](https://millelibri.copyriot.xyz).
 | 
			
		||||
 | 
			
		||||
Il progetto è partito come un fork di [Book Searcher](https://github.com/book-searcher-org/book-searcher).
 | 
			
		||||
Un tempo (fino a dicembre 2023) il progetto era sincronizzato con [Book Searcher](https://github.com/book-searcher-org/book-searcher). Poi venne la macchina editoriale e distrusse tutto:
 | 
			
		||||
 | 
			
		||||
L'obiettivo è trasformarlo in una piattaforma partecipativa: gli utenti avranno la possibilità di richiedere l'aggiunta di libri, oltre a fare ricerche.
 | 
			
		||||
https://github.com/book-searcher-org/deleted/issues/1
 | 
			
		||||
 | 
			
		||||
## Installazione rapida
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -32,8 +32,4 @@ Per modificare la parte frontend (React) del progetto, posizionarsi nella cartel
 | 
			
		|||
npm install
 | 
			
		||||
npm run build
 | 
			
		||||
npm run dev
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Licenza
 | 
			
		||||
 | 
			
		||||
Book Searcher è rilasciato sotto licenza [BSD-3-Clause](https://github.com/book-searcher-org/book-searcher/blob/master/LICENSE). Millelibri eredita la licenza da Book Searcher.
 | 
			
		||||
```
 | 
			
		||||
| 
						 | 
				
			
			@ -4,18 +4,10 @@ services:
 | 
			
		|||
  zlib:
 | 
			
		||||
    image: lamacchinadesiderante/millelibri:latest
 | 
			
		||||
 | 
			
		||||
    # image: millelibri:v0.4
 | 
			
		||||
 | 
			
		||||
    # image: millelibri
 | 
			
		||||
 | 
			
		||||
    # build:
 | 
			
		||||
    #   context: .
 | 
			
		||||
    #   dockerfile: ./Dockerfile
 | 
			
		||||
 | 
			
		||||
    restart: always
 | 
			
		||||
 | 
			
		||||
    ports:
 | 
			
		||||
      - "7070:7070"
 | 
			
		||||
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./index:/index
 | 
			
		||||
      - ./index:/index
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,2 @@
 | 
			
		|||
# .env.production
 | 
			
		||||
VITE_BACKEND_BASE_API = 'http://127.0.0.1:7070/'
 | 
			
		||||
VITE_STORJ_JSON_URL = 'https://link.storjshare.io/juowot6xaa2rz4vjqeal2uo44jka/millelibri%2Findex%2Fjson%2Fmillelibri.json?download=1'
 | 
			
		||||
VITE_BACKEND_BASE_API = 'http://127.0.0.1:7070/'
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +1,2 @@
 | 
			
		|||
# .env.production
 | 
			
		||||
VITE_BACKEND_BASE_API = ''
 | 
			
		||||
VITE_STORJ_JSON_URL = 'https://link.storjshare.io/juowot6xaa2rz4vjqeal2uo44jka/millelibri%2Findex%2Fjson%2Fmillelibri.json?download=1'
 | 
			
		||||
VITE_BACKEND_BASE_API = ''
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "frontend",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "version": "v0.7.0",
 | 
			
		||||
  "version": "v0.8.0",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "repository": "https://git.lamacchinadesiderante.org/lamacchinadesiderante/millelibri",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +13,7 @@
 | 
			
		|||
    "@chakra-ui/react": "^2.4.6",
 | 
			
		||||
    "@chakra-ui/skip-nav": "^2.0.13",
 | 
			
		||||
    "@chakra-ui/system": "^2.3.7",
 | 
			
		||||
    "@chakra-ui/icons": "^2.1.1",
 | 
			
		||||
    "@emotion/react": "^11.10.5",
 | 
			
		||||
    "@emotion/styled": "^11.10.5",
 | 
			
		||||
    "@tanstack/react-table": "^8.7.4",
 | 
			
		||||
| 
						 | 
				
			
			@ -24,6 +25,7 @@
 | 
			
		|||
    "framer-motion": "^7.10.3",
 | 
			
		||||
    "i18next": "^22.4.6",
 | 
			
		||||
    "i18next-browser-languagedetector": "^7.0.1",
 | 
			
		||||
    "js-file-download": "^0.4.12",
 | 
			
		||||
    "lodash": "^4.17.21",
 | 
			
		||||
    "react": "^18.2.0",
 | 
			
		||||
    "react-dom": "^18.2.0",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
import { Card, CardHeader, Heading, Divider, CardBody, CardFooter, GridItem, SimpleGrid, Text, Button, Flex, Icon } from '@chakra-ui/react';
 | 
			
		||||
import { Box, Card, CardHeader, Heading, Divider, CardBody, CardFooter, GridItem, SimpleGrid, Text, Button, Flex, Icon } from '@chakra-ui/react';
 | 
			
		||||
 | 
			
		||||
import React, { useContext } from 'react';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -12,6 +12,7 @@ import { filesize as formatFileSize } from 'filesize';
 | 
			
		|||
import { TbChevronUp } from 'react-icons/tb';
 | 
			
		||||
import ExternalLink from './ExternalLink';
 | 
			
		||||
import Description from './Description';
 | 
			
		||||
import IpfsDownloadButton from './IpfsDownloadButton';
 | 
			
		||||
 | 
			
		||||
interface IProps {
 | 
			
		||||
  row: Row
 | 
			
		||||
| 
						 | 
				
			
			@ -19,13 +20,6 @@ interface IProps {
 | 
			
		|||
 | 
			
		||||
const BookDetailsCard: React.FC<IProps> = (props) => {
 | 
			
		||||
 | 
			
		||||
  const downloadLinkFromIPFS = (gateway: string, book: Book) => {
 | 
			
		||||
    return (
 | 
			
		||||
      `https://${gateway}/ipfs/${book.ipfs_cid}?filename=` +
 | 
			
		||||
      encodeURIComponent(`${book.title}_${book.author}.${book.extension}`)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { row } = props
 | 
			
		||||
| 
						 | 
				
			
			@ -44,12 +38,32 @@ const BookDetailsCard: React.FC<IProps> = (props) => {
 | 
			
		|||
    ipfs_cid
 | 
			
		||||
  } = row.original;
 | 
			
		||||
 | 
			
		||||
  const searchOnAnnasArchive = () => {
 | 
			
		||||
    return (`https://annas-archive.org/search?q=` + encodeURIComponent(`${title} ${author}`));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Card mt={{ base: 1, md: 2 }} mb={{ base: 2, md: 4 }} mx={{ base: 4, md: 8 }}>
 | 
			
		||||
      <CardHeader>
 | 
			
		||||
        <Heading as="h3" fontSize="xl">
 | 
			
		||||
          {title}
 | 
			
		||||
        </Heading>
 | 
			
		||||
        <Flex
 | 
			
		||||
          align="center"
 | 
			
		||||
          flexWrap={{ base: 'wrap', lg: 'nowrap' }}
 | 
			
		||||
          justify="space-between"
 | 
			
		||||
          gap={{ base: '4', lg: '2' }}
 | 
			
		||||
        >
 | 
			
		||||
          <Heading as="h3" fontSize={['xl', '2xl', '2xl']} whiteSpace="break-spaces" minW="0">
 | 
			
		||||
            {title}
 | 
			
		||||
          </Heading>
 | 
			
		||||
          <Flex gap="2">
 | 
			
		||||
            <Button
 | 
			
		||||
              as={ExternalLink}
 | 
			
		||||
              minWidth="unset"
 | 
			
		||||
              href={searchOnAnnasArchive()}
 | 
			
		||||
            >
 | 
			
		||||
              {t('input.anna_archive')}
 | 
			
		||||
            </Button>
 | 
			
		||||
          </Flex>
 | 
			
		||||
        </Flex>
 | 
			
		||||
      </CardHeader>
 | 
			
		||||
      <Divider />
 | 
			
		||||
      <CardBody>
 | 
			
		||||
| 
						 | 
				
			
			@ -87,9 +101,12 @@ const BookDetailsCard: React.FC<IProps> = (props) => {
 | 
			
		|||
        </SimpleGrid>
 | 
			
		||||
      </CardBody>
 | 
			
		||||
      <CardFooter flexDirection="column">
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        <SimpleGrid columns={{ sm: 2, md: 3, lg: 4, xl: 5 }} spacing={{ base: 2, md: 4 }}>
 | 
			
		||||
          {ipfsGateways.map((gateway) => (
 | 
			
		||||
 | 
			
		||||
          <IpfsDownloadButton book={row.original as Book} onlyIcon={false}></IpfsDownloadButton>
 | 
			
		||||
 | 
			
		||||
          {/* {ipfsGateways.map((gateway) => (
 | 
			
		||||
            <Button
 | 
			
		||||
              as={ExternalLink}
 | 
			
		||||
              href={downloadLinkFromIPFS(gateway, row.original)}
 | 
			
		||||
| 
						 | 
				
			
			@ -98,11 +115,11 @@ const BookDetailsCard: React.FC<IProps> = (props) => {
 | 
			
		|||
            >
 | 
			
		||||
              {gateway}
 | 
			
		||||
            </Button>
 | 
			
		||||
          ))}
 | 
			
		||||
          ))} */}
 | 
			
		||||
 | 
			
		||||
        </SimpleGrid>
 | 
			
		||||
 | 
			
		||||
        {/* <Flex><Text fontWeight={'bold'}>{t('disclaimer.nolink_warning')}</Text></Flex> */}
 | 
			
		||||
        <Flex><Text mt={2} mb={-2} fontSize={'2xs'} fontStyle={'italic'} fontWeight={'light'}>{t('disclaimer.broken_link')}</Text></Flex>
 | 
			
		||||
 | 
			
		||||
        <Flex justify="flex-end">
 | 
			
		||||
          <Button
 | 
			
		||||
| 
						 | 
				
			
			@ -117,7 +134,7 @@ const BookDetailsCard: React.FC<IProps> = (props) => {
 | 
			
		|||
          </Button>
 | 
			
		||||
        </Flex>
 | 
			
		||||
      </CardFooter>
 | 
			
		||||
    </Card>
 | 
			
		||||
    </Card >
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
import React, { useState } from 'react';
 | 
			
		||||
import { Button, useToast } from '@chakra-ui/react';
 | 
			
		||||
import { DownloadIcon } from '@chakra-ui/icons';
 | 
			
		||||
import { Book } from '../scripts/searcher';
 | 
			
		||||
import autoDownload from '../scripts/download';
 | 
			
		||||
import { t } from 'i18next';
 | 
			
		||||
 | 
			
		||||
export interface IpfsDownloadButtonProps {
 | 
			
		||||
  book: Book;
 | 
			
		||||
  onlyIcon: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const IpfsDownloadButton: React.FC<IpfsDownloadButtonProps> = ({ book, onlyIcon }) => {
 | 
			
		||||
  const toast = useToast();
 | 
			
		||||
  const [downloadProgress, setDownloadProgress] = useState(-1);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Button
 | 
			
		||||
      key="input.download"
 | 
			
		||||
      w="100%"
 | 
			
		||||
      zIndex={111}
 | 
			
		||||
      variant="outline"
 | 
			
		||||
      colorScheme="blue"
 | 
			
		||||
      leftIcon={onlyIcon ? undefined : <DownloadIcon />}
 | 
			
		||||
      isLoading={downloadProgress > -1}
 | 
			
		||||
      loadingText={`${downloadProgress}%`}
 | 
			
		||||
      onClick={(e) => {
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        autoDownload(book, toast, setDownloadProgress);
 | 
			
		||||
      }}
 | 
			
		||||
      style={{ fontVariantNumeric: 'tabular-nums' }}
 | 
			
		||||
    >
 | 
			
		||||
      {onlyIcon ? <DownloadIcon /> : t('input.download')}
 | 
			
		||||
    </Button>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default IpfsDownloadButton;
 | 
			
		||||
| 
						 | 
				
			
			@ -19,7 +19,6 @@ import { useTranslation } from 'react-i18next';
 | 
			
		|||
import SearchLanguage from './SearchLanguage';
 | 
			
		||||
import { MEDIA_QUERY_DESKTOP_STARTS, MEDIA_QUERY_MOBILE_ENDS } from '../constants/mediaquery';
 | 
			
		||||
import CopyToClipboardButton from './CopyToClipboardButton';
 | 
			
		||||
import { getJsonArchive } from '../scripts/searcher-browser';
 | 
			
		||||
 | 
			
		||||
function constructQuery(parts: Record<string, string>): string {
 | 
			
		||||
  return Object.keys(parts)
 | 
			
		||||
| 
						 | 
				
			
			@ -71,13 +70,6 @@ const Search: React.FC<SearchProps> = ({ setBooks }) => {
 | 
			
		|||
    }
 | 
			
		||||
  }, [])
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    (async () => {
 | 
			
		||||
      const jsonArchive = await getJsonArchive() as Book[]
 | 
			
		||||
      setBooksFromJsonArchive(jsonArchive)
 | 
			
		||||
    })();
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const handleLanguageChange = (language: string) => {
 | 
			
		||||
    if (language == 'input') {
 | 
			
		||||
      setShowLanguageDropdown(false)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,9 @@
 | 
			
		|||
      },
 | 
			
		||||
      "input": {
 | 
			
		||||
        "clear": "Clear",
 | 
			
		||||
        "select_language": "(Select language...)"
 | 
			
		||||
        "select_language": "(Select language...)",
 | 
			
		||||
        "download": "Download",
 | 
			
		||||
        "anna_archive": "Anna's Archive"
 | 
			
		||||
      },
 | 
			
		||||
      "book": {
 | 
			
		||||
        "id": "zlib/libgen id",
 | 
			
		||||
| 
						 | 
				
			
			@ -63,107 +65,8 @@
 | 
			
		|||
        "input": "Input..."
 | 
			
		||||
      },
 | 
			
		||||
      "disclaimer": {
 | 
			
		||||
        "nolink_warning": "WARNING: This platform does not host any kind of link to copyrighted material."
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "zh-CN": {
 | 
			
		||||
    "translation": {
 | 
			
		||||
      "nav": {
 | 
			
		||||
        "repository": "GitHub 仓库",
 | 
			
		||||
        "toggle_dark": "切换到暗黑模式",
 | 
			
		||||
        "toggle_light": "切换到亮色模式",
 | 
			
		||||
        "toggle_language": "切换语言"
 | 
			
		||||
      },
 | 
			
		||||
      "input": {
 | 
			
		||||
        "clear": "清空"
 | 
			
		||||
      },
 | 
			
		||||
      "book": {
 | 
			
		||||
        "id": "zlib/libgen id",
 | 
			
		||||
        "title": "书名",
 | 
			
		||||
        "author": "作者",
 | 
			
		||||
        "publisher": "出版社",
 | 
			
		||||
        "extension": "扩展名",
 | 
			
		||||
        "filesize": "文件大小",
 | 
			
		||||
        "language": "语言",
 | 
			
		||||
        "year": "年份",
 | 
			
		||||
        "pages": "页数",
 | 
			
		||||
        "isbn": "ISBN",
 | 
			
		||||
        "ipfs_cid": "IPFS CID",
 | 
			
		||||
        "unknown": "未知"
 | 
			
		||||
      },
 | 
			
		||||
      "table": {
 | 
			
		||||
        "sort_asc": "升序排序",
 | 
			
		||||
        "sort_desc": "降序排序",
 | 
			
		||||
        "not_sorted": "未排序",
 | 
			
		||||
        "filter": "过滤",
 | 
			
		||||
        "no_data": "无数据",
 | 
			
		||||
        "first_page": "第一页",
 | 
			
		||||
        "last_page": "最后一页",
 | 
			
		||||
        "next_page": "下一页",
 | 
			
		||||
        "previous_page": "上一页",
 | 
			
		||||
        "page": "第 {{page}} 页",
 | 
			
		||||
        "collapse": "收起"
 | 
			
		||||
      },
 | 
			
		||||
      "search": {
 | 
			
		||||
        "complex": "复杂搜索"
 | 
			
		||||
      },
 | 
			
		||||
      "settings": {
 | 
			
		||||
        "title": "设置",
 | 
			
		||||
        "ipfs_gateways": "IPFS 网关",
 | 
			
		||||
        "ipfs_gateways_help": "IPFS 网关列表,一行一个",
 | 
			
		||||
        "cancel": "取消",
 | 
			
		||||
        "save": "保存"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "fr": {
 | 
			
		||||
    "translation": {
 | 
			
		||||
      "nav": {
 | 
			
		||||
        "repository": "Dépôt GitHub",
 | 
			
		||||
        "toggle_dark": "Basculer en mode sombre",
 | 
			
		||||
        "toggle_light": "Basculer en mode clair",
 | 
			
		||||
        "toggle_language": "Basculer la langue"
 | 
			
		||||
      },
 | 
			
		||||
      "input": {
 | 
			
		||||
        "clear": "Effacer"
 | 
			
		||||
      },
 | 
			
		||||
      "book": {
 | 
			
		||||
        "id": "ID zlib/libgen",
 | 
			
		||||
        "title": "Titre",
 | 
			
		||||
        "author": "Auteur",
 | 
			
		||||
        "publisher": "Éditeur",
 | 
			
		||||
        "extension": "Extension",
 | 
			
		||||
        "filesize": "Taille du fichier",
 | 
			
		||||
        "language": "Langue",
 | 
			
		||||
        "year": "Année",
 | 
			
		||||
        "pages": "Pages",
 | 
			
		||||
        "isbn": "ISBN",
 | 
			
		||||
        "ipfs_cid": "CID IPFS",
 | 
			
		||||
        "unknown": "Inconnu"
 | 
			
		||||
      },
 | 
			
		||||
      "table": {
 | 
			
		||||
        "sort_asc": "Trier par ordre croissant",
 | 
			
		||||
        "sort_desc": "Trier par ordre décroissant",
 | 
			
		||||
        "not_sorted": "Non trié",
 | 
			
		||||
        "filter": "Filtrer",
 | 
			
		||||
        "no_data": "Aucune donnée",
 | 
			
		||||
        "first_page": "Première page",
 | 
			
		||||
        "last_page": "Dernière page",
 | 
			
		||||
        "next_page": "Page suivante",
 | 
			
		||||
        "previous_page": "Page précédente",
 | 
			
		||||
        "page": "Page {{page}}",
 | 
			
		||||
        "collapse": "Replier"
 | 
			
		||||
      },
 | 
			
		||||
      "search": {
 | 
			
		||||
        "complex": "Recherche complexe"
 | 
			
		||||
      },
 | 
			
		||||
      "settings": {
 | 
			
		||||
        "title": "Paramètres",
 | 
			
		||||
        "ipfs_gateways": "Passerelles IPFS",
 | 
			
		||||
        "ipfs_gateways_help": "Liste des passerelles IPFS, saut de ligne",
 | 
			
		||||
        "cancel": "Annuler",
 | 
			
		||||
        "save": "Enregistrer"
 | 
			
		||||
        "nolink_warning": "WARNING: This platform does not host any kind of link to copyrighted material.",
 | 
			
		||||
        "broken_link": "WARNING: Links might be broken. If download doesn't start, try with Anna's Archive"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			@ -177,7 +80,9 @@
 | 
			
		|||
      },
 | 
			
		||||
      "input": {
 | 
			
		||||
        "clear": "pulisci",
 | 
			
		||||
        "select_language": "(Seleziona lingua...)"
 | 
			
		||||
        "select_language": "(Seleziona lingua...)",
 | 
			
		||||
        "download": "Download",
 | 
			
		||||
        "anna_archive": "Anna's Archive"
 | 
			
		||||
      },
 | 
			
		||||
      "book": {
 | 
			
		||||
        "id": "ID zlib/libgen",
 | 
			
		||||
| 
						 | 
				
			
			@ -231,7 +136,8 @@
 | 
			
		|||
        "input": "Scrivi..."
 | 
			
		||||
      },
 | 
			
		||||
      "disclaimer": {
 | 
			
		||||
        "nolink_warning": "IMPORTANTE: Questa piattaforma non ospita nessun tipo di link a materiale protetto da copyright."
 | 
			
		||||
        "nolink_warning": "IMPORTANTE: Questa piattaforma non ospita nessun tipo di link a materiale protetto da copyright.",
 | 
			
		||||
        "broken_link": "IMPORTANTE: I link potrebbero non funzionare. Se il download non parte, provare con Anna's Archive."
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,139 @@
 | 
			
		|||
import type { Book } from './searcher';
 | 
			
		||||
import getIpfsGateways, { getDownloadLinkFromIPFS, ipfsGateways } from './ipfs';
 | 
			
		||||
import axios, { AxiosProgressEvent, AxiosResponse } from 'axios';
 | 
			
		||||
import fileDownload from 'js-file-download';
 | 
			
		||||
import { t } from 'i18next';
 | 
			
		||||
 | 
			
		||||
export default async function autoDownload(book: Book, toast: any, setDownloadProgress: any) {
 | 
			
		||||
  const filename = `${book.title}_${book.author}.${book.extension}`;
 | 
			
		||||
  toast({
 | 
			
		||||
    title: `${filename} ${t('download_start')}!`,
 | 
			
		||||
    status: 'info',
 | 
			
		||||
    position: 'bottom-right',
 | 
			
		||||
    isClosable: true,
 | 
			
		||||
    duration: 3000
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  console.log('Download: ', book);
 | 
			
		||||
  var gateways = ipfsGateways;
 | 
			
		||||
  gateways = gateways.filter(function (item, pos) {
 | 
			
		||||
    return gateways.indexOf(item) == pos;
 | 
			
		||||
  });
 | 
			
		||||
  console.log('Try gateways:', gateways);
 | 
			
		||||
  const controllerMap = new Map();
 | 
			
		||||
  var fastedProgress = 0;
 | 
			
		||||
  setDownloadProgress(fastedProgress.toFixed(2));
 | 
			
		||||
  Promise.any(
 | 
			
		||||
    gateways.map((gateway) => {
 | 
			
		||||
      const controller = new AbortController();
 | 
			
		||||
      controllerMap.set(gateway, controller);
 | 
			
		||||
      return axios
 | 
			
		||||
        .get(getDownloadLinkFromIPFS(gateway, book), {
 | 
			
		||||
          signal: controller.signal,
 | 
			
		||||
          withCredentials: false,
 | 
			
		||||
          responseType: 'blob',
 | 
			
		||||
          onDownloadProgress: (e: AxiosProgressEvent) => {
 | 
			
		||||
            console.log('Download Progress: ', gateway, e);
 | 
			
		||||
            const myProgress = e.progress! * 100;
 | 
			
		||||
            const bar = 10;
 | 
			
		||||
            if (fastedProgress > bar && myProgress < bar) {
 | 
			
		||||
              controllerMap.get(gateway).abort();
 | 
			
		||||
            }
 | 
			
		||||
            if (myProgress > fastedProgress && myProgress != 100) {
 | 
			
		||||
              fastedProgress = myProgress;
 | 
			
		||||
              setDownloadProgress(fastedProgress.toFixed(2));
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
        .catch();
 | 
			
		||||
    })
 | 
			
		||||
  )
 | 
			
		||||
    .then((resp: AxiosResponse) => {
 | 
			
		||||
      controllerMap.forEach((c) => c.abort());
 | 
			
		||||
      fileDownload(resp.data, filename);
 | 
			
		||||
      toast({
 | 
			
		||||
        title: `${filename} ${t('download_success')}!`,
 | 
			
		||||
        status: 'success',
 | 
			
		||||
        position: 'bottom-right',
 | 
			
		||||
        isClosable: true,
 | 
			
		||||
        duration: 6000
 | 
			
		||||
      });
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {
 | 
			
		||||
      toast({
 | 
			
		||||
        title: `${filename} ${t('download_failed')}!`,
 | 
			
		||||
        status: 'error',
 | 
			
		||||
        position: 'bottom-right',
 | 
			
		||||
        isClosable: true,
 | 
			
		||||
        duration: 6000
 | 
			
		||||
      });
 | 
			
		||||
    })
 | 
			
		||||
    .finally(() => {
 | 
			
		||||
      setDownloadProgress(-1);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const downloadBookData = async function (book: Book, signal: AbortSignal) {
 | 
			
		||||
  var gateways = await getIpfsGateways();
 | 
			
		||||
  gateways = gateways.filter(function (item, pos) {
 | 
			
		||||
    return gateways.indexOf(item) == pos;
 | 
			
		||||
  });
 | 
			
		||||
  const controllerMap = new Map();
 | 
			
		||||
  var fastedProgress = 0;
 | 
			
		||||
  return Promise.any(
 | 
			
		||||
    gateways.map((gateway) => {
 | 
			
		||||
      const controller = new AbortController();
 | 
			
		||||
      controllerMap.set(gateway, controller);
 | 
			
		||||
      return axios
 | 
			
		||||
        .get(getDownloadLinkFromIPFS(gateway, book), {
 | 
			
		||||
          signal: anySignal([controller.signal, signal]),
 | 
			
		||||
          withCredentials: false,
 | 
			
		||||
          responseType: 'arraybuffer',
 | 
			
		||||
          onDownloadProgress: (e: AxiosProgressEvent) => {
 | 
			
		||||
            console.log('Download Progress: ', gateway, e);
 | 
			
		||||
            const myProgress = e.progress! * 100;
 | 
			
		||||
            const bar = 10;
 | 
			
		||||
            if (fastedProgress > bar && myProgress < bar) {
 | 
			
		||||
              controllerMap.get(gateway).abort();
 | 
			
		||||
            }
 | 
			
		||||
            if (myProgress > fastedProgress && myProgress != 100) {
 | 
			
		||||
              fastedProgress = myProgress;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
        .catch();
 | 
			
		||||
    })
 | 
			
		||||
  )
 | 
			
		||||
    .then((resp: AxiosResponse) => {
 | 
			
		||||
      return resp.data;
 | 
			
		||||
    })
 | 
			
		||||
    .catch()
 | 
			
		||||
    .finally(() => {
 | 
			
		||||
      controllerMap.forEach((c) => c.abort());
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export { downloadBookData };
 | 
			
		||||
 | 
			
		||||
function anySignal(signals: AbortSignal[]) {
 | 
			
		||||
  const controller = new AbortController();
 | 
			
		||||
 | 
			
		||||
  function onAbort() {
 | 
			
		||||
    controller.abort();
 | 
			
		||||
 | 
			
		||||
    // Cleanup
 | 
			
		||||
    for (const signal of signals) {
 | 
			
		||||
      signal.removeEventListener('abort', onAbort);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (const signal of signals) {
 | 
			
		||||
    if (signal.aborted) {
 | 
			
		||||
      onAbort();
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    signal.addEventListener('abort', onAbort);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return controller.signal;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,14 +1,18 @@
 | 
			
		|||
import { Book } from './searcher';
 | 
			
		||||
 | 
			
		||||
interface TauriConfig {
 | 
			
		||||
    index_dir: string;
 | 
			
		||||
    ipfs_gateways: string[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const ipfsGateways: string[] = [
 | 
			
		||||
    'cloudflare-ipfs.com',
 | 
			
		||||
    'ipfs.2read.net',
 | 
			
		||||
    'dweb.link',
 | 
			
		||||
    'ipfs.io',
 | 
			
		||||
    'gateway.pinata.cloud'
 | 
			
		||||
    // 'cloudflare-ipfs.com',
 | 
			
		||||
    // 'dweb.link',
 | 
			
		||||
    // 'ipfs.io',
 | 
			
		||||
    // 'gateway.pinata.cloud',
 | 
			
		||||
    // 'nftstorage.link'
 | 
			
		||||
    'ipfs.copyriot.xyz',
 | 
			
		||||
    'ipfs2.copyriot.xyz'
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
export default async function getIpfsGateways() {
 | 
			
		||||
| 
						 | 
				
			
			@ -28,4 +32,11 @@ export default async function getIpfsGateways() {
 | 
			
		|||
 | 
			
		||||
export function parseIpfsGateways(text: string) {
 | 
			
		||||
    return text.split('\n').filter(g => g.length);
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getDownloadLinkFromIPFS(gateway: string, book: Book) {
 | 
			
		||||
    return (
 | 
			
		||||
      `https://${gateway}/ipfs/${book.ipfs_cid}?filename=` +
 | 
			
		||||
      encodeURIComponent(`${book.title}_${book.author}.${book.extension}`)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -8,39 +8,6 @@ const http = axios.create({
 | 
			
		|||
  timeout: 5000
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const storj = axios.create({
 | 
			
		||||
  baseURL: import.meta.env.VITE_STORJ_JSON_URL,
 | 
			
		||||
  timeout: 5000
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const getJsonArchive = async () => {
 | 
			
		||||
  //@ts-ignore
 | 
			
		||||
  if (!window[JSON_ARCHIVE_WINDOW_KEY]) {
 | 
			
		||||
    
 | 
			
		||||
    const response = await storj.get(``);
 | 
			
		||||
 | 
			
		||||
    if (response.status == 200) {
 | 
			
		||||
      //@ts-ignore
 | 
			
		||||
      window[JSON_ARCHIVE_WINDOW_KEY] = response.data as Book[];
 | 
			
		||||
    } else {
 | 
			
		||||
 | 
			
		||||
      console.log(response);
 | 
			
		||||
 | 
			
		||||
      //@ts-ignore
 | 
			
		||||
      window[JSON_ARCHIVE_WINDOW_KEY] = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //@ts-ignore
 | 
			
		||||
    return window[JSON_ARCHIVE_WINDOW_KEY] as Book[];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  } else {
 | 
			
		||||
    //@ts-ignore
 | 
			
		||||
    return window[JSON_ARCHIVE_WINDOW_KEY] as Book[];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default async function search(query: string, limit: number) {
 | 
			
		||||
  const response = await http.get(`search?limit=${limit}&query=${query}`);
 | 
			
		||||
  return response.data.books as Book[];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue