finished: check update
This commit is contained in:
		
							parent
							
								
									b7d64cba7b
								
							
						
					
					
						commit
						e0dbd03397
					
				
							
								
								
									
										22
									
								
								app.go
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								app.go
									
									
									
									
									
								
							| @ -1,7 +1,12 @@ | |||||||
| package main | package main | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"MacFastLookup/service" | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 
 | ||||||
|  | 	"github.com/wailsapp/wails/v2/pkg/runtime" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // App struct | // App struct | ||||||
| @ -19,3 +24,20 @@ func NewApp() *App { | |||||||
| func (a *App) startup(ctx context.Context) { | func (a *App) startup(ctx context.Context) { | ||||||
| 	a.ctx = ctx | 	a.ctx = ctx | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func (a *App) StartDownload() { | ||||||
|  | 	dbFileDir := ".macfastlookup" | ||||||
|  | 	userHomeDir, _ := os.UserHomeDir() | ||||||
|  | 	dbFileName := "mac_vendors.sqlite3" | ||||||
|  | 	dbFilePath := filepath.Join(userHomeDir, dbFileDir, dbFileName) | ||||||
|  | 
 | ||||||
|  | 	// create the folder if not exist | ||||||
|  | 	path := filepath.Join(userHomeDir, dbFileDir) | ||||||
|  | 	if _, err := os.Stat(path); os.IsNotExist(err) { | ||||||
|  | 		os.Mkdir(path, 0755) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	service.DownloadFile("https://tools.taurusxin.com/macfastlookup/mac_vendors.sqlite3", dbFilePath, true, func(progress, total int64) { | ||||||
|  | 		runtime.EventsEmit(a.ctx, "download-progress", float32(progress / total)) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
| @ -11,6 +11,8 @@ | |||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@radix-ui/react-alert-dialog": "^1.1.1", |     "@radix-ui/react-alert-dialog": "^1.1.1", | ||||||
|     "@radix-ui/react-checkbox": "^1.1.1", |     "@radix-ui/react-checkbox": "^1.1.1", | ||||||
|  |     "@radix-ui/react-dialog": "^1.1.1", | ||||||
|  |     "@radix-ui/react-progress": "^1.1.0", | ||||||
|     "@radix-ui/react-slot": "^1.1.0", |     "@radix-ui/react-slot": "^1.1.0", | ||||||
|     "@radix-ui/react-toast": "^1.2.1", |     "@radix-ui/react-toast": "^1.2.1", | ||||||
|     "class-variance-authority": "^0.7.0", |     "class-variance-authority": "^0.7.0", | ||||||
|  | |||||||
| @ -1 +1 @@ | |||||||
| 6c7c0ac0f4deb7b7027a4a528f0c2aca | 50363c2ee2f3f6316fa41844894a6d9c | ||||||
							
								
								
									
										29
									
								
								frontend/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										29
									
								
								frontend/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @ -14,6 +14,12 @@ importers: | |||||||
|       '@radix-ui/react-checkbox': |       '@radix-ui/react-checkbox': | ||||||
|         specifier: ^1.1.1 |         specifier: ^1.1.1 | ||||||
|         version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) |         version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) | ||||||
|  |       '@radix-ui/react-dialog': | ||||||
|  |         specifier: ^1.1.1 | ||||||
|  |         version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) | ||||||
|  |       '@radix-ui/react-progress': | ||||||
|  |         specifier: ^1.1.0 | ||||||
|  |         version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) | ||||||
|       '@radix-ui/react-slot': |       '@radix-ui/react-slot': | ||||||
|         specifier: ^1.1.0 |         specifier: ^1.1.0 | ||||||
|         version: 1.1.0(@types/react@18.3.3)(react@18.3.1) |         version: 1.1.0(@types/react@18.3.3)(react@18.3.1) | ||||||
| @ -417,6 +423,19 @@ packages: | |||||||
|       '@types/react-dom': |       '@types/react-dom': | ||||||
|         optional: true |         optional: true | ||||||
| 
 | 
 | ||||||
|  |   '@radix-ui/react-progress@1.1.0': | ||||||
|  |     resolution: {integrity: sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg==} | ||||||
|  |     peerDependencies: | ||||||
|  |       '@types/react': '*' | ||||||
|  |       '@types/react-dom': '*' | ||||||
|  |       react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc | ||||||
|  |       react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc | ||||||
|  |     peerDependenciesMeta: | ||||||
|  |       '@types/react': | ||||||
|  |         optional: true | ||||||
|  |       '@types/react-dom': | ||||||
|  |         optional: true | ||||||
|  | 
 | ||||||
|   '@radix-ui/react-slot@1.1.0': |   '@radix-ui/react-slot@1.1.0': | ||||||
|     resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} |     resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} | ||||||
|     peerDependencies: |     peerDependencies: | ||||||
| @ -1685,6 +1704,16 @@ snapshots: | |||||||
|       '@types/react': 18.3.3 |       '@types/react': 18.3.3 | ||||||
|       '@types/react-dom': 18.3.0 |       '@types/react-dom': 18.3.0 | ||||||
| 
 | 
 | ||||||
|  |   '@radix-ui/react-progress@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': | ||||||
|  |     dependencies: | ||||||
|  |       '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.3.1) | ||||||
|  |       '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) | ||||||
|  |       react: 18.3.1 | ||||||
|  |       react-dom: 18.3.1(react@18.3.1) | ||||||
|  |     optionalDependencies: | ||||||
|  |       '@types/react': 18.3.3 | ||||||
|  |       '@types/react-dom': 18.3.0 | ||||||
|  | 
 | ||||||
|   '@radix-ui/react-slot@1.1.0(@types/react@18.3.3)(react@18.3.1)': |   '@radix-ui/react-slot@1.1.0(@types/react@18.3.3)(react@18.3.1)': | ||||||
|     dependencies: |     dependencies: | ||||||
|       '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) |       '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) | ||||||
|  | |||||||
							
								
								
									
										120
									
								
								frontend/src/components/ui/dialog.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								frontend/src/components/ui/dialog.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,120 @@ | |||||||
|  | import * as React from "react" | ||||||
|  | import * as DialogPrimitive from "@radix-ui/react-dialog" | ||||||
|  | import { X } from "lucide-react" | ||||||
|  | 
 | ||||||
|  | import { cn } from "@/lib/utils" | ||||||
|  | 
 | ||||||
|  | const Dialog = DialogPrimitive.Root | ||||||
|  | 
 | ||||||
|  | const DialogTrigger = DialogPrimitive.Trigger | ||||||
|  | 
 | ||||||
|  | const DialogPortal = DialogPrimitive.Portal | ||||||
|  | 
 | ||||||
|  | const DialogClose = DialogPrimitive.Close | ||||||
|  | 
 | ||||||
|  | const DialogOverlay = React.forwardRef< | ||||||
|  |   React.ElementRef<typeof DialogPrimitive.Overlay>, | ||||||
|  |   React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> | ||||||
|  | >(({ className, ...props }, ref) => ( | ||||||
|  |   <DialogPrimitive.Overlay | ||||||
|  |     ref={ref} | ||||||
|  |     className={cn( | ||||||
|  |       "fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", | ||||||
|  |       className | ||||||
|  |     )} | ||||||
|  |     {...props} | ||||||
|  |   /> | ||||||
|  | )) | ||||||
|  | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName | ||||||
|  | 
 | ||||||
|  | const DialogContent = React.forwardRef< | ||||||
|  |   React.ElementRef<typeof DialogPrimitive.Content>, | ||||||
|  |   React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> | ||||||
|  | >(({ className, children, ...props }, ref) => ( | ||||||
|  |   <DialogPortal> | ||||||
|  |     <DialogOverlay /> | ||||||
|  |     <DialogPrimitive.Content | ||||||
|  |       ref={ref} | ||||||
|  |       className={cn( | ||||||
|  |         "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", | ||||||
|  |         className | ||||||
|  |       )} | ||||||
|  |       {...props} | ||||||
|  |     > | ||||||
|  |       {children} | ||||||
|  |       <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> | ||||||
|  |         <X className="h-4 w-4" /> | ||||||
|  |         <span className="sr-only">Close</span> | ||||||
|  |       </DialogPrimitive.Close> | ||||||
|  |     </DialogPrimitive.Content> | ||||||
|  |   </DialogPortal> | ||||||
|  | )) | ||||||
|  | DialogContent.displayName = DialogPrimitive.Content.displayName | ||||||
|  | 
 | ||||||
|  | const DialogHeader = ({ | ||||||
|  |   className, | ||||||
|  |   ...props | ||||||
|  | }: React.HTMLAttributes<HTMLDivElement>) => ( | ||||||
|  |   <div | ||||||
|  |     className={cn( | ||||||
|  |       "flex flex-col space-y-1.5 text-center sm:text-left", | ||||||
|  |       className | ||||||
|  |     )} | ||||||
|  |     {...props} | ||||||
|  |   /> | ||||||
|  | ) | ||||||
|  | DialogHeader.displayName = "DialogHeader" | ||||||
|  | 
 | ||||||
|  | const DialogFooter = ({ | ||||||
|  |   className, | ||||||
|  |   ...props | ||||||
|  | }: React.HTMLAttributes<HTMLDivElement>) => ( | ||||||
|  |   <div | ||||||
|  |     className={cn( | ||||||
|  |       "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", | ||||||
|  |       className | ||||||
|  |     )} | ||||||
|  |     {...props} | ||||||
|  |   /> | ||||||
|  | ) | ||||||
|  | DialogFooter.displayName = "DialogFooter" | ||||||
|  | 
 | ||||||
|  | const DialogTitle = React.forwardRef< | ||||||
|  |   React.ElementRef<typeof DialogPrimitive.Title>, | ||||||
|  |   React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> | ||||||
|  | >(({ className, ...props }, ref) => ( | ||||||
|  |   <DialogPrimitive.Title | ||||||
|  |     ref={ref} | ||||||
|  |     className={cn( | ||||||
|  |       "text-lg font-semibold leading-none tracking-tight", | ||||||
|  |       className | ||||||
|  |     )} | ||||||
|  |     {...props} | ||||||
|  |   /> | ||||||
|  | )) | ||||||
|  | DialogTitle.displayName = DialogPrimitive.Title.displayName | ||||||
|  | 
 | ||||||
|  | const DialogDescription = React.forwardRef< | ||||||
|  |   React.ElementRef<typeof DialogPrimitive.Description>, | ||||||
|  |   React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> | ||||||
|  | >(({ className, ...props }, ref) => ( | ||||||
|  |   <DialogPrimitive.Description | ||||||
|  |     ref={ref} | ||||||
|  |     className={cn("text-sm text-muted-foreground", className)} | ||||||
|  |     {...props} | ||||||
|  |   /> | ||||||
|  | )) | ||||||
|  | DialogDescription.displayName = DialogPrimitive.Description.displayName | ||||||
|  | 
 | ||||||
|  | export { | ||||||
|  |   Dialog, | ||||||
|  |   DialogPortal, | ||||||
|  |   DialogOverlay, | ||||||
|  |   DialogClose, | ||||||
|  |   DialogTrigger, | ||||||
|  |   DialogContent, | ||||||
|  |   DialogHeader, | ||||||
|  |   DialogFooter, | ||||||
|  |   DialogTitle, | ||||||
|  |   DialogDescription, | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								frontend/src/components/ui/progress.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								frontend/src/components/ui/progress.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | |||||||
|  | import * as React from "react" | ||||||
|  | import * as ProgressPrimitive from "@radix-ui/react-progress" | ||||||
|  | 
 | ||||||
|  | import { cn } from "@/lib/utils" | ||||||
|  | 
 | ||||||
|  | const Progress = React.forwardRef< | ||||||
|  |   React.ElementRef<typeof ProgressPrimitive.Root>, | ||||||
|  |   React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> | ||||||
|  | >(({ className, value, ...props }, ref) => ( | ||||||
|  |   <ProgressPrimitive.Root | ||||||
|  |     ref={ref} | ||||||
|  |     className={cn( | ||||||
|  |       "relative h-4 w-full overflow-hidden rounded-full bg-secondary", | ||||||
|  |       className | ||||||
|  |     )} | ||||||
|  |     {...props} | ||||||
|  |   > | ||||||
|  |     <ProgressPrimitive.Indicator | ||||||
|  |       className="h-full w-full flex-1 bg-primary transition-all" | ||||||
|  |       style={{ transform: `translateX(-${100 - (value || 0)}%)` }} | ||||||
|  |     /> | ||||||
|  |   </ProgressPrimitive.Root> | ||||||
|  | )) | ||||||
|  | Progress.displayName = ProgressPrimitive.Root.displayName | ||||||
|  | 
 | ||||||
|  | export { Progress } | ||||||
| @ -1,4 +1,3 @@ | |||||||
| import React from 'react' |  | ||||||
| import {createRoot} from 'react-dom/client' | import {createRoot} from 'react-dom/client' | ||||||
| import './style.css' | import './style.css' | ||||||
| import App from './App' | import App from './App' | ||||||
| @ -8,7 +7,5 @@ const container = document.getElementById('root') | |||||||
| const root = createRoot(container!) | const root = createRoot(container!) | ||||||
| 
 | 
 | ||||||
| root.render( | root.render( | ||||||
|     <React.StrictMode> |   <App/> | ||||||
|         <App/> |  | ||||||
|     </React.StrictMode> |  | ||||||
| ) | ) | ||||||
|  | |||||||
							
								
								
									
										59
									
								
								frontend/src/pages/Download.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								frontend/src/pages/Download.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | import { useEffect, useState } from 'react' | ||||||
|  | import { EventsOn } from '../../wailsjs/runtime/runtime' | ||||||
|  | import { StartDownload } from '../../wailsjs/go/main/App' | ||||||
|  | import { Close } from '../../wailsjs/go/service/SQLiteHelper' | ||||||
|  | import { | ||||||
|  |   Dialog, | ||||||
|  |   DialogContent, | ||||||
|  |   DialogDescription, | ||||||
|  |   DialogFooter, | ||||||
|  |   DialogHeader, | ||||||
|  |   DialogTitle, | ||||||
|  | } from '@/components/ui/dialog' | ||||||
|  | import { Button } from '@/components/ui/button' | ||||||
|  | import { Progress } from '@/components/ui/progress' | ||||||
|  | 
 | ||||||
|  | const Download = (props: { onFinish: () => void }) => { | ||||||
|  |   const [isOpen, setIsOpen] = useState(true) | ||||||
|  | 
 | ||||||
|  |   const [downloadProgress, setDownloadProgress] = useState<number>(0.0) | ||||||
|  |   const [isDisabled, setIsDisabled] = useState(true) | ||||||
|  | 
 | ||||||
|  |   useEffect(() => { | ||||||
|  |     Close().then(res => { | ||||||
|  |       StartDownload().then(res => { | ||||||
|  |         console.log(res) | ||||||
|  |       }) | ||||||
|  |       EventsOn('download-progress', (progress: number) => { | ||||||
|  |         setDownloadProgress(progress * 100) | ||||||
|  |         if (progress === 1) { | ||||||
|  |           props.onFinish() | ||||||
|  |           setIsDisabled(false) | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |     }) | ||||||
|  |   }, []) | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <Dialog open={isOpen}> | ||||||
|  |       <DialogContent> | ||||||
|  |         <DialogHeader> | ||||||
|  |           <DialogTitle>Downloading</DialogTitle> | ||||||
|  |         </DialogHeader> | ||||||
|  |         <Progress value={downloadProgress} /> | ||||||
|  |         <DialogFooter> | ||||||
|  |           <Button | ||||||
|  |             onClick={() => { | ||||||
|  |               setIsOpen(false) | ||||||
|  |             }} | ||||||
|  |             disabled={isDisabled} | ||||||
|  |           > | ||||||
|  |             Close | ||||||
|  |           </Button> | ||||||
|  |         </DialogFooter> | ||||||
|  |       </DialogContent> | ||||||
|  |     </Dialog> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default Download | ||||||
| @ -3,13 +3,33 @@ import React, { useEffect, useState } from 'react' | |||||||
| import { Button } from '@/components/ui/button' | import { Button } from '@/components/ui/button' | ||||||
| import { Input } from '@/components/ui/input' | import { Input } from '@/components/ui/input' | ||||||
| import { Checkbox } from '@/components/ui/checkbox' | import { Checkbox } from '@/components/ui/checkbox' | ||||||
| 
 |  | ||||||
| import { toast } from 'sonner' | import { toast } from 'sonner' | ||||||
| 
 | 
 | ||||||
| import { QueryMacAddressByPrefix, QueryVersion } from '../../wailsjs/go/service/SQLiteHelper' | import { | ||||||
|  |   CheckBeforeInit, | ||||||
|  |   InitSQLiteHelper, | ||||||
|  |   QueryMacAddressByPrefix, | ||||||
|  |   QueryVersion, | ||||||
|  | } from '../../wailsjs/go/service/SQLiteHelper' | ||||||
| import { GetLatestVersion } from '../../wailsjs/go/service/UpdateService' | import { GetLatestVersion } from '../../wailsjs/go/service/UpdateService' | ||||||
|  | import { ReloadDatabase } from '../../wailsjs/go/service/SQLiteHelper' | ||||||
|  | import { | ||||||
|  |   AlertDialog, | ||||||
|  |   AlertDialogAction, | ||||||
|  |   AlertDialogCancel, | ||||||
|  |   AlertDialogContent, | ||||||
|  |   AlertDialogDescription, | ||||||
|  |   AlertDialogFooter, | ||||||
|  |   AlertDialogHeader, | ||||||
|  |   AlertDialogTitle, | ||||||
|  |   AlertDialogTrigger, | ||||||
|  | } from '@/components/ui/alert-dialog' | ||||||
|  | 
 | ||||||
|  | import Download from './Download' | ||||||
| 
 | 
 | ||||||
| const Panel: React.FC = () => { | const Panel: React.FC = () => { | ||||||
|  |   const [macInputDisabled, setMacInputDisabled] = useState(false) | ||||||
|  | 
 | ||||||
|   const [macAddress, setMacAddress] = useState('') |   const [macAddress, setMacAddress] = useState('') | ||||||
|   const [prefix, setPrefix] = useState('') |   const [prefix, setPrefix] = useState('') | ||||||
|   const [vendorName, setVendorName] = useState('') |   const [vendorName, setVendorName] = useState('') | ||||||
| @ -20,14 +40,46 @@ const Panel: React.FC = () => { | |||||||
| 
 | 
 | ||||||
|   const [checkDisabled, setCheckDisabled] = useState(false) |   const [checkDisabled, setCheckDisabled] = useState(false) | ||||||
| 
 | 
 | ||||||
|  |   const [newVersion, setNewVersion] = useState('') | ||||||
|  |   const [isOpen, setIsOpen] = useState(false) | ||||||
|  |   const [isDownloading, setIsDownloading] = useState(false) | ||||||
|  | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     QueryVersion().then(res => { |     CheckBeforeInit().then(res => { | ||||||
|       setDatabaseDate(res) |       if (res) { | ||||||
|  |         InitSQLiteHelper().then(res => { | ||||||
|  |           if (res) { | ||||||
|  |             QueryVersion().then(res => { | ||||||
|  |               setDatabaseDate(res) | ||||||
|  |             }) | ||||||
|  |           } else { | ||||||
|  |             toast('Init dababase failed.', { | ||||||
|  |               description: '⛓️💥 Please check your internet connection.', | ||||||
|  |               action: { | ||||||
|  |                 label: 'Close', | ||||||
|  |                 onClick: () => {}, | ||||||
|  |               }, | ||||||
|  |             }) | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |       } else { | ||||||
|  |         setCheckDisabled(true) | ||||||
|  |         setMacInputDisabled(true) | ||||||
|  |         // download
 | ||||||
|  |       } | ||||||
|     }) |     }) | ||||||
|   }, []) |   }, []) | ||||||
| 
 | 
 | ||||||
|   const handleTextChange = (e: React.ChangeEvent<HTMLInputElement>) => { |   const handleTextChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||||||
|     setMacAddress(e.target.value) |     setMacAddress(e.target.value) | ||||||
|  |     if (e.target.value === '') { | ||||||
|  |       setPrefix('') | ||||||
|  |       setVendorName('') | ||||||
|  |       setIsPrivate(false) | ||||||
|  |       setBlockType('') | ||||||
|  |       setLastUpdate('') | ||||||
|  |       return | ||||||
|  |     } | ||||||
|     let address = e.target.value |     let address = e.target.value | ||||||
|       .trim() |       .trim() | ||||||
|       .toUpperCase() |       .toUpperCase() | ||||||
| @ -60,7 +112,7 @@ const Panel: React.FC = () => { | |||||||
|       if (res.Version === null) { |       if (res.Version === null) { | ||||||
|         setCheckDisabled(false) |         setCheckDisabled(false) | ||||||
|         toast('Check update failed.', { |         toast('Check update failed.', { | ||||||
|           description: 'Please check your internet connection.', |           description: '⛓️💥 Please check your internet connection.', | ||||||
|           action: { |           action: { | ||||||
|             label: 'Close', |             label: 'Close', | ||||||
|             onClick: () => {}, |             onClick: () => {}, | ||||||
| @ -77,6 +129,9 @@ const Panel: React.FC = () => { | |||||||
|             }, |             }, | ||||||
|           }) |           }) | ||||||
|         } else { |         } else { | ||||||
|  |           // alert
 | ||||||
|  |           setNewVersion(res) | ||||||
|  |           setIsOpen(true) | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }) |     }) | ||||||
| @ -90,6 +145,7 @@ const Panel: React.FC = () => { | |||||||
|           value={macAddress} |           value={macAddress} | ||||||
|           onChange={handleTextChange} |           onChange={handleTextChange} | ||||||
|           placeholder="Enter MAC Address" |           placeholder="Enter MAC Address" | ||||||
|  |           disabled={macInputDisabled} | ||||||
|         /> |         /> | ||||||
|       </div> |       </div> | ||||||
|       <div className="mb-2 flex items-center"> |       <div className="mb-2 flex items-center"> | ||||||
| @ -113,7 +169,7 @@ const Panel: React.FC = () => { | |||||||
|         <Input className="w-2/3 p-1 border h-8" value={lastUpdate} readOnly /> |         <Input className="w-2/3 p-1 border h-8" value={lastUpdate} readOnly /> | ||||||
|       </div> |       </div> | ||||||
|       <div className="flex items-center"> |       <div className="flex items-center"> | ||||||
|         <label className="w-1/3 select-none">Database</label> |         <label className="w-1/3 select-none">Mac Database</label> | ||||||
|         <Input className="w-5/12 p-1 border h-8" value={databaseDate} readOnly /> |         <Input className="w-5/12 p-1 border h-8" value={databaseDate} readOnly /> | ||||||
|         <Button |         <Button | ||||||
|           className="ml-2 p-1 h-8 w-1/4 select-none" |           className="ml-2 p-1 h-8 w-1/4 select-none" | ||||||
| @ -123,6 +179,46 @@ const Panel: React.FC = () => { | |||||||
|           Check |           Check | ||||||
|         </Button> |         </Button> | ||||||
|       </div> |       </div> | ||||||
|  |       <AlertDialog open={isOpen}> | ||||||
|  |         <AlertDialogContent> | ||||||
|  |           <AlertDialogHeader> | ||||||
|  |             <AlertDialogTitle>Update Available</AlertDialogTitle> | ||||||
|  |             <AlertDialogDescription> | ||||||
|  |               Version {newVersion} is available. Do you want to download it now? | ||||||
|  |             </AlertDialogDescription> | ||||||
|  |           </AlertDialogHeader> | ||||||
|  |           <AlertDialogFooter> | ||||||
|  |             <AlertDialogCancel | ||||||
|  |               onClick={() => { | ||||||
|  |                 setIsOpen(false) | ||||||
|  |               }} | ||||||
|  |             > | ||||||
|  |               No | ||||||
|  |             </AlertDialogCancel> | ||||||
|  |             <AlertDialogAction | ||||||
|  |               onClick={() => { | ||||||
|  |                 setIsOpen(false) | ||||||
|  |                 setIsDownloading(true) | ||||||
|  |               }} | ||||||
|  |             > | ||||||
|  |               Yes | ||||||
|  |             </AlertDialogAction> | ||||||
|  |           </AlertDialogFooter> | ||||||
|  |         </AlertDialogContent> | ||||||
|  |       </AlertDialog> | ||||||
|  |       {isDownloading && ( | ||||||
|  |         <Download | ||||||
|  |           onFinish={() => { | ||||||
|  |             ReloadDatabase().then(res => { | ||||||
|  |               if (res) { | ||||||
|  |                 QueryVersion().then(res => { | ||||||
|  |                   setDatabaseDate(res) | ||||||
|  |                 }) | ||||||
|  |               } | ||||||
|  |             }) | ||||||
|  |           }} | ||||||
|  |         /> | ||||||
|  |       )} | ||||||
|     </div> |     </div> | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								frontend/wailsjs/go/main/App.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								frontend/wailsjs/go/main/App.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
 | ||||||
|  | // This file is automatically generated. DO NOT EDIT
 | ||||||
|  | 
 | ||||||
|  | export function StartDownload():Promise<void>; | ||||||
							
								
								
									
										7
									
								
								frontend/wailsjs/go/main/App.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								frontend/wailsjs/go/main/App.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | // @ts-check
 | ||||||
|  | // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
 | ||||||
|  | // This file is automatically generated. DO NOT EDIT
 | ||||||
|  | 
 | ||||||
|  | export function StartDownload() { | ||||||
|  |   return window['go']['main']['App']['StartDownload'](); | ||||||
|  | } | ||||||
| @ -2,8 +2,12 @@ | |||||||
| // This file is automatically generated. DO NOT EDIT
 | // This file is automatically generated. DO NOT EDIT
 | ||||||
| import {service} from '../models'; | import {service} from '../models'; | ||||||
| 
 | 
 | ||||||
|  | export function CheckBeforeInit():Promise<boolean>; | ||||||
|  | 
 | ||||||
| export function Close():Promise<void>; | export function Close():Promise<void>; | ||||||
| 
 | 
 | ||||||
|  | export function InitSQLiteHelper():Promise<boolean>; | ||||||
|  | 
 | ||||||
| export function QueryMacAddressByPrefix(arg1:string):Promise<service.MacAddress>; | export function QueryMacAddressByPrefix(arg1:string):Promise<service.MacAddress>; | ||||||
| 
 | 
 | ||||||
| export function QueryVersion():Promise<string>; | export function QueryVersion():Promise<string>; | ||||||
|  | |||||||
| @ -2,10 +2,18 @@ | |||||||
| // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
 | // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
 | ||||||
| // This file is automatically generated. DO NOT EDIT
 | // This file is automatically generated. DO NOT EDIT
 | ||||||
| 
 | 
 | ||||||
|  | export function CheckBeforeInit() { | ||||||
|  |   return window['go']['service']['SQLiteHelper']['CheckBeforeInit'](); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export function Close() { | export function Close() { | ||||||
|   return window['go']['service']['SQLiteHelper']['Close'](); |   return window['go']['service']['SQLiteHelper']['Close'](); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export function InitSQLiteHelper() { | ||||||
|  |   return window['go']['service']['SQLiteHelper']['InitSQLiteHelper'](); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export function QueryMacAddressByPrefix(arg1) { | export function QueryMacAddressByPrefix(arg1) { | ||||||
|   return window['go']['service']['SQLiteHelper']['QueryMacAddressByPrefix'](arg1); |   return window['go']['service']['SQLiteHelper']['QueryMacAddressByPrefix'](arg1); | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								main.go
									
									
									
									
									
								
							| @ -3,8 +3,6 @@ package main | |||||||
| import ( | import ( | ||||||
| 	"MacFastLookup/service" | 	"MacFastLookup/service" | ||||||
| 	"embed" | 	"embed" | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
| 
 | 
 | ||||||
| 	"github.com/wailsapp/wails/v2" | 	"github.com/wailsapp/wails/v2" | ||||||
| 	"github.com/wailsapp/wails/v2/pkg/options" | 	"github.com/wailsapp/wails/v2/pkg/options" | ||||||
| @ -19,11 +17,7 @@ func main() { | |||||||
| 	app := NewApp() | 	app := NewApp() | ||||||
| 
 | 
 | ||||||
| 	// Create service | 	// Create service | ||||||
| 	dbFileDir := "MacFastLookup" | 	dbService := service.NewSQLiteHelper() | ||||||
| 	userHomeDir, _ := os.UserHomeDir() |  | ||||||
| 	dbFileName := "mac_vendors.sqlite3" |  | ||||||
| 	dbFilePath := filepath.Join(userHomeDir, dbFileDir, dbFileName) |  | ||||||
| 	dbService := service.NewSQLiteHelper(dbFilePath) |  | ||||||
| 
 | 
 | ||||||
| 	// create update service | 	// create update service | ||||||
| 	updateService := service.NewUpdateService("https://tools.taurusxin.com/macfastlookup/latest_version.txt") | 	updateService := service.NewUpdateService("https://tools.taurusxin.com/macfastlookup/latest_version.txt") | ||||||
|  | |||||||
							
								
								
									
										83
									
								
								service/download.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								service/download.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | |||||||
|  | package service | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  |     "fmt" | ||||||
|  |     "io" | ||||||
|  |     "net/http" | ||||||
|  |     "os" | ||||||
|  |     "strconv" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ProgressWriter is a custom writer to track progress and execute a callback | ||||||
|  | type ProgressWriter struct { | ||||||
|  |     io.Writer | ||||||
|  |     Total     int64 | ||||||
|  |     Progress  int64 | ||||||
|  |     Callback  func(progress int64, total int64) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (pw *ProgressWriter) Write(p []byte) (int, error) { | ||||||
|  |     n, err := pw.Writer.Write(p) | ||||||
|  |     if err != nil { | ||||||
|  |         return n, err | ||||||
|  |     } | ||||||
|  |     pw.Progress += int64(n) | ||||||
|  |     pw.Callback(pw.Progress, pw.Total) | ||||||
|  |     return n, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func DownloadFile(url string, filePath string, overwrite bool, callback func(progress int64, total int64)) error { | ||||||
|  |     // Check if the file exists | ||||||
|  |     if _, err := os.Stat(filePath); err == nil { | ||||||
|  |         if overwrite { | ||||||
|  |             // Overwrite the file if it exists | ||||||
|  |             if err := os.Remove(filePath); err != nil { | ||||||
|  |                 return err | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             return fmt.Errorf("file %s already exists", filePath) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Create the file | ||||||
|  |     out, err := os.Create(filePath) | ||||||
|  |     if err != nil { | ||||||
|  |         return err | ||||||
|  |     } | ||||||
|  |     defer out.Close() | ||||||
|  | 
 | ||||||
|  |     // Get the data | ||||||
|  |     resp, err := http.Get(url) | ||||||
|  |     if err != nil { | ||||||
|  |         return err | ||||||
|  |     } | ||||||
|  |     defer resp.Body.Close() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     // Check server response | ||||||
|  |     if resp.StatusCode != http.StatusOK { | ||||||
|  |         return fmt.Errorf("bad status: %s", resp.Status) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Get the size | ||||||
|  |     size, err := strconv.Atoi(resp.Header.Get("Content-Length")) | ||||||
|  |     if err != nil { | ||||||
|  |         return err | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Create progress writer | ||||||
|  |     pw := &ProgressWriter{ | ||||||
|  |         Writer:   out, | ||||||
|  |         Total:    int64(size), | ||||||
|  |         Callback: callback, | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Write the body to file with progress | ||||||
|  |     _, err = io.Copy(pw, resp.Body) | ||||||
|  |     if err != nil { | ||||||
|  |         return err | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fmt.Println("\nDownload complete") | ||||||
|  |     return nil | ||||||
|  | } | ||||||
| @ -3,7 +3,8 @@ package service | |||||||
| import ( | import ( | ||||||
| 	"database/sql" | 	"database/sql" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"log" | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
| 
 | 
 | ||||||
| 	_ "github.com/mattn/go-sqlite3" | 	_ "github.com/mattn/go-sqlite3" | ||||||
| ) | ) | ||||||
| @ -18,21 +19,50 @@ type MacAddress struct { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type SQLiteHelper struct { | type SQLiteHelper struct { | ||||||
| 	connectionString string | 	db *sql.DB | ||||||
| 	db               *sql.DB |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewSQLiteHelper(dbFilePath string) *SQLiteHelper { | // create the instance of SQLiteHelper but not initialize the database connection | ||||||
| 	connectionString := fmt.Sprintf("file:%s?cache=shared&mode=rwc", dbFilePath) | func NewSQLiteHelper() *SQLiteHelper { | ||||||
| 	db, err := sql.Open("sqlite3", connectionString) | 	return &SQLiteHelper{ | ||||||
| 	if err != nil { | 		db: nil, | ||||||
| 		log.Fatal(err) | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // check if the directory and file for the database exist | ||||||
|  | func (helper *SQLiteHelper) CheckBeforeInit() bool { | ||||||
|  | 	userHomeDir, _ := os.UserHomeDir() | ||||||
|  | 	dbFileDir := filepath.Join(userHomeDir, ".macfastlookup") | ||||||
|  | 	dbFileName := "mac_vendors.sqlite3" | ||||||
|  | 	dbFilePath := filepath.Join(dbFileDir, dbFileName) | ||||||
|  | 
 | ||||||
|  | 	if _, err := os.Stat(dbFileDir); os.IsNotExist(err) { | ||||||
|  | 		os.Mkdir(dbFileDir, 0755) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return &SQLiteHelper{ | 	if _, err := os.Stat(dbFilePath); os.IsNotExist(err) { | ||||||
| 		connectionString: connectionString, | 		return false | ||||||
| 		db:               db, |  | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // initialize the database connection | ||||||
|  | func (helper *SQLiteHelper) InitSQLiteHelper() bool { | ||||||
|  | 	dbFileDir := ".macfastlookup" | ||||||
|  | 	userHomeDir, _ := os.UserHomeDir() | ||||||
|  | 	dbFileName := "mac_vendors.sqlite3" | ||||||
|  | 	dbFilePath := filepath.Join(userHomeDir, dbFileDir, dbFileName) | ||||||
|  | 
 | ||||||
|  | 	connectionString := fmt.Sprintf("file:%s?cache=shared&mode=rwc", dbFilePath) | ||||||
|  | 	db, err := sql.Open("sqlite3", connectionString) | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	helper.db = db | ||||||
|  | 	return true | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (helper *SQLiteHelper) Close() { | func (helper *SQLiteHelper) Close() { | ||||||
| @ -40,14 +70,7 @@ func (helper *SQLiteHelper) Close() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (helper *SQLiteHelper) ReloadDatabase() bool { | func (helper *SQLiteHelper) ReloadDatabase() bool { | ||||||
| 	helper.db.Close() | 	return helper.InitSQLiteHelper() | ||||||
| 	db, err := sql.Open("sqlite3", helper.connectionString) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Fatal(err) |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	helper.db = db |  | ||||||
| 	return true |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (helper *SQLiteHelper) QueryMacAddressByPrefix(prefix string) (*MacAddress, error) { | func (helper *SQLiteHelper) QueryMacAddressByPrefix(prefix string) (*MacAddress, error) { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user