Skip to content

Commit

Permalink
Option for skipping duplicate file names or renaming the destination …
Browse files Browse the repository at this point in the history
…file.
  • Loading branch information
amnuts committed Jul 16, 2023
1 parent 526e1e4 commit 3e448b3
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 15 deletions.
6 changes: 6 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type App struct {
minHeight int
namingConvention string
moveOrCopy string
skipOrRename string
verifyResults bool
substitutions []Substitution
cache *CacheDatabase
Expand Down Expand Up @@ -81,6 +82,7 @@ func (a *App) ResetEverything() {
a.minHeight = 0
a.namingConvention = ""
a.moveOrCopy = ""
a.skipOrRename = ""
a.verifyResults = false
a.substitutions = []Substitution{}
}
Expand Down Expand Up @@ -149,6 +151,10 @@ func (a *App) BackendSetMoveOrCopy(moveOrCopy string) {
a.moveOrCopy = moveOrCopy
}

func (a *App) BackendSetSkipOrRename(skipOrRename string) {
a.skipOrRename = skipOrRename
}

func (a *App) BackendSetVerifyResults(verifyResults bool) {
a.verifyResults = verifyResults
}
Expand Down
9 changes: 7 additions & 2 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
BackendSetMinHeight,
BackendSetNamingConvention,
BackendSetMoveOrCopy,
BackendSetSkipOrRename,
BackendSetVerifyResults,
ProcessImages,
VerifyRelocation,
Expand All @@ -30,6 +31,7 @@ function App() {
const [minHeight, setMinHeight] = useState('');
const [namingConvention, setNamingConvention] = useState('');
const [moveOrCopy, setMoveOrCopy] = useState('copy');
const [skipOrRename, setSkipOrRename] = useState('rename');
const [verifyResults, setVerifyResults] = useState(true);
const [nextDisabled, setNextDisabled] = useState(true);

Expand Down Expand Up @@ -126,10 +128,12 @@ function App() {
BackendSetNamingConvention(namingConvention);
};

const updateOrganiseData = ({ moveOrCopy, verifyResults }) => {
const updateOrganiseData = ({ moveOrCopy, skipOrRename, verifyResults }) => {
setMoveOrCopy(moveOrCopy);
setSkipOrRename(skipOrRename);
setVerifyResults(verifyResults);
BackendSetMoveOrCopy(moveOrCopy);
BackendSetSkipOrRename(skipOrRename);
BackendSetVerifyResults(verifyResults);
};

Expand All @@ -143,6 +147,7 @@ function App() {
setMinHeight('');
setNamingConvention('');
setMoveOrCopy('copy');
setSkipOrRename('rename');
setVerifyResults(true);
setNextDisabled(true);
setFoundFiles([]);
Expand All @@ -163,7 +168,7 @@ function App() {
{currentPanel === 1 && <DirectoriesPanel {...{updateData: updateDirectoriesData, canProceed, startDirectories, destinationDirectory}} />}
{currentPanel === 2 && <FiltersPanel {...{updateData: updateFiltersData, canProceed, minSize, minWidth, minHeight}} />}
{currentPanel === 3 && <NamingPanel {...{updateData: updateNamingData, canProceed, namingConvention, destinationDirectory}} />}
{currentPanel === 4 && <OrganisePanel {...{updateData: updateOrganiseData, canProceed, moveOrCopy, verifyResults}} />}
{currentPanel === 4 && <OrganisePanel {...{updateData: updateOrganiseData, canProceed, moveOrCopy, skipOrRename, verifyResults}} />}
{currentPanel === 5 && <ProcessingPanel {...{reset, moveOrCopy, verifyResults, isProcessingComplete, foundFiles, isRelocating, isRelocationComplete,totalFiles, totalFilesRelocated, destinationDirectory }} />}
</main>
<footer className="bg-slate-700 text-white py-4 px-6 flex justify-between">
Expand Down
44 changes: 41 additions & 3 deletions frontend/src/OrganisePanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,26 @@ export const OrganisePanelMeta = {
description: 'Start the move or copy',
};

export default function OrganisePanel({ updateData, canProceed, moveOrCopy, verifyResults })
export default function OrganisePanel({ updateData, canProceed, moveOrCopy, skipOrRename, verifyResults })
{
const [willVerifyResults, setWillVerifyResults] = useState(!!verifyResults);
const [willMoveOrCopy, setWillMoveOrCopy] = useState(moveOrCopy || "copy");
const [willSkipOrRename, setWillSkipOrRename] = useState(skipOrRename || "rename");

useEffect(() => {
updateData({ moveOrCopy: willMoveOrCopy, verifyResults: willVerifyResults });
updateData({ moveOrCopy: willMoveOrCopy, skipOrRename: willSkipOrRename, verifyResults: willVerifyResults });
canProceed(true);
})

const handleMoveOrCopyChange = (event) => {
setWillMoveOrCopy(event.target.value);
};


const handleSkipOrRenameChange = (event) => {
setWillSkipOrRename(event.target.value);
};

const handleVerifyResultsChange = (event) => {
setWillVerifyResults(!willVerifyResults);
};
Expand All @@ -46,7 +52,6 @@ export default function OrganisePanel({ updateData, canProceed, moveOrCopy, veri
<p className="text-gray-500 text-xs">Organise by copying your files from the original locations and duplicating them in the destination location, so they will be in both folders.</p>
</label>
</div>

<div className="flex items-center align-middle mt-4 mb-2">
<input type="radio" id="move" name="transfer" value="move" className="opacity-0 absolute h-8 w-8" checked={willMoveOrCopy === "move"} onChange={handleMoveOrCopyChange}/>
<div className="bg-white border-2 rounded-full border-gray-500 w-8 h-8 flex flex-shrink-0 justify-center items-center mr-2 focus-within:text-orange-700">
Expand All @@ -64,6 +69,39 @@ export default function OrganisePanel({ updateData, canProceed, moveOrCopy, veri
</label>
</div>

<div className="flex items-center align-middle mt-8 mb-2">
<input type="radio" id="rename" name="skipping" value="rename" className="opacity-0 absolute h-8 w-8" checked={willSkipOrRename === "rename"} onChange={handleSkipOrRenameChange}/>
<div className="bg-white border-2 rounded-full border-gray-500 w-8 h-8 flex flex-shrink-0 justify-center items-center mr-2 focus-within:text-orange-700">
<svg className="fill-current hidden w-3 h-3 text-orange-700 pointer-events-none" version="1.1" viewBox="0 0 17 12" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fillRule="evenodd">
<g transform="translate(-9 -11)" fill="rgb(194, 65, 12)" fillRule="nonzero">
<path d="m25.576 11.414c0.56558 0.55188 0.56558 1.4439 0 1.9961l-9.404 9.176c-0.28213 0.27529-0.65247 0.41385-1.0228 0.41385-0.37034 0-0.74068-0.13855-1.0228-0.41385l-4.7019-4.588c-0.56584-0.55188-0.56584-1.4442 0-1.9961 0.56558-0.55214 1.4798-0.55214 2.0456 0l3.679 3.5899 8.3812-8.1779c0.56558-0.55214 1.4798-0.55214 2.0456 0z"/>
</g>
</g>
</svg>
</div>
<label htmlFor="rename" className="select-none leading-tight">
<p className="font-bold text-gray-400">Rename duplicate file names</p>
<p className="text-gray-500 text-xs">Copy files with the same filename, keeping both, but rename any duplicates.</p>
</label>
</div>
<div className="flex items-center align-middle mt-4 mb-2">
<input type="radio" id="skip" name="skipping" value="skip" className="opacity-0 absolute h-8 w-8" checked={willSkipOrRename === "skip"} onChange={handleSkipOrRenameChange}/>
<div className="bg-white border-2 rounded-full border-gray-500 w-8 h-8 flex flex-shrink-0 justify-center items-center mr-2 focus-within:border-orange-700">
<svg className="fill-current hidden w-3 h-3 text-orange-700 pointer-events-none" version="1.1" viewBox="0 0 17 12" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fillRule="evenodd">
<g transform="translate(-9 -11)" fill="rgb(194, 65, 12)" fillRule="nonzero">
<path d="m25.576 11.414c0.56558 0.55188 0.56558 1.4439 0 1.9961l-9.404 9.176c-0.28213 0.27529-0.65247 0.41385-1.0228 0.41385-0.37034 0-0.74068-0.13855-1.0228-0.41385l-4.7019-4.588c-0.56584-0.55188-0.56584-1.4442 0-1.9961 0.56558-0.55214 1.4798-0.55214 2.0456 0l3.679 3.5899 8.3812-8.1779c0.56558-0.55214 1.4798-0.55214 2.0456 0z"/>
</g>
</g>
</svg>
</div>
<label htmlFor="skip" className="select-none leading-tight">
<p className="font-bold text-gray-400">Skip files with duplicate names</p>
<p className="text-gray-500 text-xs">When organising into the new folder, don't copy any files with the same filename as one already in place.</p>
</label>
</div>

<div className="flex items-center align-middle mt-12 mb-2">
<input type="checkbox" id="validate" name="validate" value="1" className="opacity-0 absolute h-8 w-8" checked={willVerifyResults} onChange={handleVerifyResultsChange}/>
<div className="bg-white border-2 rounded-md border-gray-500 w-8 h-8 flex flex-shrink-0 justify-center items-center mr-2 focus-within:text-orange-700">
Expand Down
2 changes: 2 additions & 0 deletions frontend/wailsjs/go/main/App.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export function BackendSetMoveOrCopy(arg1:string):Promise<void>;

export function BackendSetNamingConvention(arg1:string):Promise<void>;

export function BackendSetSkipOrRename(arg1:string):Promise<void>;

export function BackendSetVerifyResults(arg1:boolean):Promise<void>;

export function ClearStartDirectories():Promise<Array<string>>;
Expand Down
4 changes: 4 additions & 0 deletions frontend/wailsjs/go/main/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export function BackendSetNamingConvention(arg1) {
return window['go']['main']['App']['BackendSetNamingConvention'](arg1);
}

export function BackendSetSkipOrRename(arg1) {
return window['go']['main']['App']['BackendSetSkipOrRename'](arg1);
}

export function BackendSetVerifyResults(arg1) {
return window['go']['main']['App']['BackendSetVerifyResults'](arg1);
}
Expand Down
22 changes: 12 additions & 10 deletions path_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ func RelocateFiles(a *App) {
runtime.EventsEmit(a.ctx, "relocating-start")
totalFiles := len(a.substitutions)
totalRelocated := 0

for i := range a.substitutions {
destinationPath := ToPathWithSuffix(a.substitutions[i].To, a.substitutions[i].Suffix)
destinationFolder := filepath.Dir(destinationPath)
Expand All @@ -220,6 +221,17 @@ func RelocateFiles(a *App) {
_ = os.MkdirAll(destinationFolder, os.ModePerm)
}

uniqueDestinationPath := UniqueFileName(destinationPath)
if uniqueDestinationPath != destinationPath {
fmt.Printf("The destination file '%s' already exists", destinationPath)
if a.skipOrRename == "skip" {
fmt.Println(" - skipping")
continue
}
fmt.Printf(" - renaming to '%s'\n", uniqueDestinationPath)
destinationPath = uniqueDestinationPath
}

fmt.Println("Copying", a.substitutions[i].From, "to", destinationPath)

sourceFile, err := os.Open(a.substitutions[i].From)
Expand All @@ -236,16 +248,6 @@ func RelocateFiles(a *App) {
}
defer destinationFile.Close()

if _, err := os.Stat(destinationPath); err == nil {
fmt.Println("Destination file already exists. Skipping...")
sourceFile.Close()
continue
} else if !os.IsNotExist(err) {
fmt.Println("An error occurred while checking the destination file", err)
sourceFile.Close()
continue
}

if _, err := io.Copy(destinationFile, sourceFile); err != nil {
fmt.Println("Failed to copy", err)
sourceFile.Close()
Expand Down
15 changes: 15 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
Expand Down Expand Up @@ -90,3 +91,17 @@ func HasLocationPlaceholders(str string) bool {
match := re.FindAllString(str, -1)
return len(match) > 0
}

func UniqueFileName(filePath string) string {
dir := filepath.Dir(filePath)
ext := filepath.Ext(filePath)
base := strings.TrimSuffix(filepath.Base(filePath), ext)

for {
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return filePath
}
base = base + " (duplicate)"
filePath = filepath.Join(dir, base+ext)
}
}

0 comments on commit 3e448b3

Please sign in to comment.