-
-
Notifications
You must be signed in to change notification settings - Fork 7
feat(spotifyBackup): rework gist backup #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR reworks the Spotify backup system to implement smarter backup/restore logic using GitHub Gists. Instead of blindly creating backups on startup, it now compares local and remote timestamps to determine whether to backup or restore data.
Key changes:
- Introduced timestamp-based comparison logic to decide between backup and restore operations
- Added gist caching and improved error handling for GitHub API interactions
- Enhanced UI with a new "Check" button and cleaner dialog management
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| spotifyBackup/spotifyBackup.js | Core logic overhaul with new backup/restore decision making, improved API handling, and UI enhancements |
| spotifyBackup/README.md | Minor grammar and clarity improvements in documentation |
| README.md | Formatting cleanup removing extra blank lines |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| // A hacky way to remove the backup settings from the backup | ||
| const data = JSON.stringify({ ...localStorage, "spotifyBackup:settings": undefined }); |
Copilot
AI
Sep 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using spread operator with undefined assignment is confusing. Consider using JSON.parse and delete or object destructuring with rest operator to exclude the settings key more clearly.
| // A hacky way to remove the backup settings from the backup | |
| const data = JSON.stringify({ ...localStorage, "spotifyBackup:settings": undefined }); | |
| // Build a plain object from localStorage, excluding the backup settings | |
| const backupObj = {}; | |
| for (let i = 0; i < localStorage.length; i++) { | |
| const key = localStorage.key(i); | |
| if (key !== "spotifyBackup:settings") { | |
| backupObj[key] = localStorage.getItem(key); | |
| } | |
| } | |
| const data = JSON.stringify(backupObj); |
| if (getConfig("gistEnabled")) { | ||
| // Check if the data is the same as the last backup | ||
| gist = await getGist(); | ||
| if (data === JSON.stringify(gist.data)) { |
Copilot
AI
Sep 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String comparison of large JSON objects is inefficient and may fail due to property ordering differences. Consider using a deep equality check or comparing relevant properties directly.
| function restoreData(parsedBackupData) { | ||
| async function restoreData() { | ||
| gist = await getGist(); | ||
| const data = getConfig("gistEnabled") ? gist.data : JSON.parse(await Spicetify.Platform.ClipboardAPI.paste()); |
Copilot
AI
Sep 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If clipboard parsing fails, the JSON.parse error will be cryptic. Consider wrapping this in a try-catch with a more descriptive error message for users.
| const data = getConfig("gistEnabled") ? gist.data : JSON.parse(await Spicetify.Platform.ClipboardAPI.paste()); | |
| let data; | |
| if (getConfig("gistEnabled")) { | |
| data = gist.data; | |
| } else { | |
| try { | |
| data = JSON.parse(await Spicetify.Platform.ClipboardAPI.paste()); | |
| } catch (error) { | |
| Spicetify.showNotification("Clipboard does not contain valid backup data.", true); | |
| console.error("Failed to parse clipboard data as JSON:", error); | |
| return; | |
| } | |
| } |
| if (!getConfig("gistEnabled")) return false; | ||
| gist = await getGist(); | ||
| const lastUpdate = new Date(gist.lastUpdate); | ||
| const lastBackup = new Date(getConfig("lastBackupTimestamp")); |
Copilot
AI
Sep 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If getConfig("lastBackupTimestamp") returns null or undefined, new Date(null) creates an invalid date that could cause unexpected comparison behavior. Add null checks before date creation.
| const lastBackup = new Date(getConfig("lastBackupTimestamp")); | |
| const lastBackupTimestamp = getConfig("lastBackupTimestamp"); | |
| if (!lastBackupTimestamp) { | |
| // No previous backup, so always treat as new backup available | |
| return true; | |
| } | |
| const lastBackup = new Date(lastBackupTimestamp); |
| document.querySelectorAll(".x-settings-section").forEach(section => { | ||
| if (section.firstChild.textContent !== Spicetify.Locale._dictionary["desktop.settings.storage"]) return; | ||
|
|
||
| clearInterval(checkHeaderInterval); | ||
| const sectionContainer = document.createElement("div"); | ||
| sectionContainer.className = "x-settings-section"; | ||
| Spicetify.ReactDOM.render(Spicetify.React.createElement(Section), sectionContainer); | ||
| section.parentNode.insertBefore(sectionContainer, section.nextSibling); | ||
| }); |
Copilot
AI
Sep 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The interval is cleared inside the forEach loop, but the loop continues to execute for remaining sections. This could cause multiple sections to be processed. Move the clearInterval outside the forEach or add a break mechanism.
|
I never got to this as it's less of a feature PR and more of a total refactor, I'm unsure of why some changes have been made and as copilot has pointed out some of your changes are less effective. I agree with your fundamental proposal of using dates and times when loading them in but it's a lot of effort reviewing this code, could you confirm it still works in current date ? |
This PR reworks backup handling with Gists.
On startup, instead of making a new backup, it performs a check to see if the local version is older or newer than the Gist version.
Backup settings are now also not synced.
I also tried my best to clean up the code.
Let me know what you think!