forked from go/golangs_learn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
725 lines
21 KiB
725 lines
21 KiB
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
// "github.com/wailsapp/wails/v2/pkg/options/mac"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
. "ytd/constants"
|
|
. "ytd/db"
|
|
. "ytd/models"
|
|
. "ytd/plugins"
|
|
|
|
"github.com/denisbrodbeck/machineid"
|
|
"github.com/leonelquinteros/gotext"
|
|
"github.com/mitchellh/mapstructure"
|
|
"github.com/wailsapp/wails/v2"
|
|
|
|
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
|
"github.com/xujiajun/nutsdb"
|
|
)
|
|
|
|
var i18nPath string
|
|
|
|
var wailsRuntime *wails.Runtime
|
|
|
|
type HostInfo struct {
|
|
ID string `json:"id"`
|
|
Username string `json:"username"`
|
|
}
|
|
|
|
type AppState struct {
|
|
runtime *wails.Runtime
|
|
db *nutsdb.DB
|
|
plugins []Plugin
|
|
Entries []GenericEntry `json:"entries"`
|
|
OfflinePlaylists []OfflinePlaylist `json:"offlinePlaylists"`
|
|
Config *AppConfig `json:"config"`
|
|
Stats *AppStats `json:"stats"`
|
|
AppVersion string `json:"appVersion"`
|
|
Host HostInfo `json:"host"`
|
|
PwaUrl string `json:"pwaUrl"`
|
|
context.Context `json:"-"`
|
|
|
|
isInForeground bool
|
|
canStartAtLogin bool
|
|
tray TrayMenu
|
|
updater *Updater
|
|
offlinePlaylistService *OfflinePlaylistService
|
|
ngrok *NgrokService
|
|
convertQueue chan GenericEntry
|
|
}
|
|
|
|
func (state *AppState) PreWailsInit(ctx context.Context) {
|
|
state.db = InitializeDb()
|
|
state.Entries = DbGetAllEntries()
|
|
state.OfflinePlaylists = DbGetAllOfflinePlaylists()
|
|
state.offlinePlaylistService = &OfflinePlaylistService{}
|
|
state.ngrok = &NgrokService{}
|
|
state.ngrok.Context = ctx
|
|
state.Config = state.Config.Init()
|
|
state.AppVersion = version
|
|
state.convertQueue = make(chan GenericEntry)
|
|
|
|
// configure i18n paths
|
|
gotext.Configure("/Users/oskarmarciniak/projects/golang/ytd/i18n", state.Config.Language, "default")
|
|
|
|
/*if _, err := mac.StartsAtLogin(); err == nil {
|
|
state.canStartAtLogin = true
|
|
}*/
|
|
|
|
if machineid, err := machineid.ProtectedID("ytd"); err == nil {
|
|
state.Host.ID = machineid
|
|
}
|
|
if user, err := user.Current(); err == nil {
|
|
state.Host.Username = user.Username
|
|
}
|
|
}
|
|
|
|
func (state *AppState) WailsInit(runtime *wails.Runtime) {
|
|
// Save runtime
|
|
state.runtime = runtime
|
|
state.offlinePlaylistService.runtime = runtime
|
|
state.ngrok.runtime = runtime
|
|
state.Config.SetRuntime(runtime)
|
|
// Do some other initialisation
|
|
state.Stats = &AppStats{}
|
|
appState = state
|
|
|
|
// this is sync so it blocks until finished and wails:loaded are not dispatched until this finishes
|
|
if runtime.System.AppType() == "default" { // wails serve & ng serve
|
|
runtime.Events.On("wails:loaded", func(...interface{}) {
|
|
time.Sleep(100 * time.Millisecond)
|
|
fmt.Println("EMIT YTD:ONLOAD")
|
|
runtime.Events.Emit("ytd:onload", state)
|
|
})
|
|
} else { // dekstop build
|
|
go func() {
|
|
runtime.Events.Emit("ytd:onload", state)
|
|
}()
|
|
}
|
|
|
|
// initialize plugins
|
|
for _, plugin := range plugins {
|
|
plugin.SetWailsRuntime(runtime)
|
|
plugin.SetContext(state.Context)
|
|
plugin.SetAppConfig(state.Config)
|
|
plugin.SetAppStats(state.Stats)
|
|
}
|
|
|
|
fmt.Println("APP STATE INITIALIZED")
|
|
state.InitializeListeners()
|
|
// create app sys tray menu
|
|
state.tray.runtime = runtime
|
|
state.tray.createTray()
|
|
state.runtime.Menu.SetTrayMenu(state.tray.defaultTrayMenu)
|
|
// event emitted from fe if tray should be updated
|
|
runtime.Events.On("ytd:app:tray:update", func(data ...interface{}) {
|
|
state.tray.reRenderTray(func() {})
|
|
})
|
|
|
|
state.checkStartsAtLogin()
|
|
|
|
/* go func() {
|
|
for {
|
|
time.Sleep(10 * time.Second)
|
|
state.checkForTracksToDownload()
|
|
}
|
|
}() */
|
|
|
|
go func() {
|
|
ticker := time.NewTicker(3 * time.Second)
|
|
pending := make(chan int, maxConvertJobs)
|
|
|
|
for {
|
|
select {
|
|
case entry := <-state.convertQueue:
|
|
go func() {
|
|
if len(pending) == cap(pending) {
|
|
state.runtime.Events.Emit("ytd:track:convert:queued", entry)
|
|
}
|
|
pending <- 1
|
|
state.convertToMp3(ticker, &entry, true)
|
|
<-pending
|
|
}()
|
|
case <-ticker.C:
|
|
// get entries that could be converted
|
|
for _, t := range DbGetAllEntries() {
|
|
entry := t
|
|
plugin := getPluginFor(entry.Source)
|
|
|
|
if entry.Type == "track" && entry.Track.Status == TrackStatusDownladed && !entry.Track.IsConvertedToMp3 && plugin.IsTrackFileExists(entry.Track, "webm") {
|
|
// skip tracks which has failed at least 3 times in a row
|
|
if entry.Track.ConvertingStatus.Attempts >= 3 {
|
|
fmt.Printf("Skipping audio extraction for %s(%s)...due to too many attempts\n", entry.Track.Name, entry.Track.ID)
|
|
continue
|
|
}
|
|
|
|
go func() {
|
|
// this will block until pending channel is full
|
|
pending <- 1
|
|
state.convertToMp3(ticker, &entry, false)
|
|
<-pending
|
|
}()
|
|
}
|
|
}
|
|
default:
|
|
fmt.Printf("Convert to mp3 nothing to do....max parralel %d | converting %d | pending %d entries\n\n", maxConvertJobs, state.Stats.ConvertingCount, len(pending))
|
|
time.Sleep(1 * time.Second)
|
|
}
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
time.Sleep(10 * time.Second)
|
|
for {
|
|
state.checkForUpdates()
|
|
// check again in twelve hours
|
|
time.Sleep(12 * time.Hour)
|
|
}
|
|
}()
|
|
|
|
if state.Config.PublicServer.Enabled {
|
|
ShowLoader(state.runtime, "Starting public server...")
|
|
result := state.ngrok.StartProcess(false)
|
|
if result.err != nil {
|
|
state.runtime.Events.Emit("ytd:ngrok", NgrokStateEventPayload{Status: NgrokStatusError, ErrCode: result.errCode})
|
|
HideLoader(state.runtime)
|
|
return
|
|
}
|
|
state.runtime.Events.Emit("ytd:ngrok", NgrokStateEventPayload{Status: result.status, Url: result.publicUrl})
|
|
HideLoader(state.runtime)
|
|
|
|
// monitor ngrok state
|
|
go state.ngrok.MonitorNgrokProcess()
|
|
}
|
|
}
|
|
|
|
func (state *AppState) WailsShutdown() {
|
|
err := state.ngrok.KillProcess()
|
|
if err != nil {
|
|
fmt.Println("WailsShutdown state.ngrok.KillProcess() failed", err)
|
|
}
|
|
err = state.db.Merge()
|
|
if err != nil {
|
|
fmt.Println("WailsShutdown db.Merge() failed", err)
|
|
}
|
|
CloseDb()
|
|
}
|
|
|
|
func (state *AppState) ReloadNewLanguage() {
|
|
gotext.Configure(i18nPath, state.Config.Language, "default")
|
|
// re render tray to take effect for new language translations
|
|
state.tray.reRenderTray(func() {})
|
|
// do other stuff if needed to reload translations from some ui native elements
|
|
}
|
|
|
|
func (state *AppState) InitializeListeners() {
|
|
state.runtime.Events.On("ytd:app:foreground", func(optionalData ...interface{}) {
|
|
var json map[string]interface{} = optionalData[0].(map[string]interface{})
|
|
if isInForeground, ok := json["isInForeground"]; ok {
|
|
state.isInForeground = isInForeground.(bool)
|
|
}
|
|
})
|
|
|
|
state.runtime.Events.On("ytd:offline:playlists:addedTrack", func(optionalData ...interface{}) {
|
|
state.OfflinePlaylists, _ = state.offlinePlaylistService.GetPlaylists(true)
|
|
})
|
|
|
|
state.runtime.Events.On("ytd:offline:playlists:removedTrack", func(optionalData ...interface{}) {
|
|
state.OfflinePlaylists, _ = state.offlinePlaylistService.GetPlaylists(true)
|
|
})
|
|
|
|
state.runtime.Events.On("ytd:offline:playlists:created", func(optionalData ...interface{}) {
|
|
state.OfflinePlaylists, _ = state.offlinePlaylistService.GetPlaylists(true)
|
|
})
|
|
|
|
state.runtime.Events.On("ytd:offline:playlists:removed", func(optionalData ...interface{}) {
|
|
state.OfflinePlaylists, _ = state.offlinePlaylistService.GetPlaylists(true)
|
|
})
|
|
|
|
state.runtime.Events.On("ngrok:configured", func(optionalData ...interface{}) {
|
|
if state.Config.PublicServer.Enabled {
|
|
ShowLoader(state.runtime, "Configuring public server...")
|
|
result := state.ngrok.StartProcess(true)
|
|
if result.err != nil {
|
|
state.runtime.Events.Emit("ytd:ngrok", NgrokStateEventPayload{Status: NgrokStatusError, ErrCode: result.errCode})
|
|
HideLoader(state.runtime)
|
|
return
|
|
}
|
|
state.runtime.Events.Emit("ytd:ngrok", NgrokStateEventPayload{Status: result.status, Url: result.publicUrl})
|
|
HideLoader(state.runtime)
|
|
|
|
// monitor ngrok process
|
|
go state.ngrok.MonitorNgrokProcess()
|
|
}
|
|
|
|
if !state.Config.PublicServer.Enabled {
|
|
err := state.ngrok.KillProcess()
|
|
if err != nil {
|
|
SendNotification(state.runtime, NotificationEventPayload{Type: "error", Label: "Cannot shutdown public server"}, state.isInForeground)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func (state *AppState) GetAppConfig() *AppConfig {
|
|
return state.Config
|
|
}
|
|
|
|
func (state *AppState) SelectDirectory() (string, error) {
|
|
selectedDirectory, err := state.runtime.Dialog.OpenDirectory(&dialog.OpenDialog{
|
|
AllowFiles: false,
|
|
CanCreateDirectories: true,
|
|
AllowDirectories: true,
|
|
Title: gotext.Get("Choose directory"),
|
|
})
|
|
return selectedDirectory, err
|
|
}
|
|
|
|
func (state *AppState) GetEntryById(entry GenericEntry) *GenericEntry {
|
|
for _, t := range state.Entries {
|
|
if t.Type == "track" && t.Track.ID == entry.Track.ID {
|
|
return &t
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (state *AppState) checkForTracksToDownload() error {
|
|
fmt.Printf("Check for tracks to start downloads...%d/%d\n", state.Stats.DownloadingCount, state.Config.MaxParrallelDownloads)
|
|
if state.Stats.DownloadingCount >= state.Config.MaxParrallelDownloads {
|
|
return nil
|
|
}
|
|
|
|
fmt.Printf("Checking...%d entries\n", len(state.Entries))
|
|
// range over DbGetAllEntries()
|
|
for _, t := range DbGetAllEntries() {
|
|
var freeSlots uint = state.Config.MaxParrallelDownloads - state.Stats.DownloadingCount
|
|
entry := t
|
|
// auto download only tracks with processing status
|
|
// if track has pending/failed status it means that something goes wrong so user have to download it manually from UI
|
|
if entry.Type == "track" && entry.Track.Status == TrackStatusProcessing {
|
|
if freeSlots == 0 {
|
|
return nil
|
|
}
|
|
|
|
// start download for track
|
|
fmt.Printf("Found %s to download\n", entry.Track.Name)
|
|
plugin := getPluginFor(entry.Source)
|
|
// make chan GenericEntry
|
|
// goriutine scrive li dentro
|
|
if plugin != nil {
|
|
freeSlots--
|
|
go func(entry GenericEntry) {
|
|
// storedEntry := state.GetEntryById(entry)
|
|
// fmt.Printf("Stored entry %v\n", storedEntry)
|
|
// fmt.Println(entry.Track.Url)
|
|
// fmt.Println(storedEntry.Track.Url)
|
|
plugin.StartDownload(&entry)
|
|
// storedEntry.Track = entry.Track
|
|
}(entry)
|
|
}
|
|
}
|
|
}
|
|
|
|
// qui un for che legge dal channel e ogni volta che riceve una entry downloadata
|
|
return nil
|
|
}
|
|
|
|
func (state *AppState) convertToMp3(restartTicker *time.Ticker, entry *GenericEntry, force bool) error {
|
|
if !state.Config.ConvertToMp3 {
|
|
// if option is not enabled restart check after 3s
|
|
restartTicker.Stop()
|
|
restartTicker.Reset(3 * time.Second)
|
|
return nil
|
|
}
|
|
|
|
fmt.Println("Converting....")
|
|
ffmpeg, _ := state.IsFFmpegInstalled()
|
|
plugin := getPluginFor(entry.Source)
|
|
|
|
if entry.Type == "track" && entry.Track.Status == TrackStatusDownladed && !entry.Track.IsConvertedToMp3 && plugin.IsTrackFileExists(entry.Track, "webm") {
|
|
// skip tracks which has failed at least 3 times in a row
|
|
if entry.Track.ConvertingStatus.Attempts >= 3 && !force {
|
|
fmt.Printf("Skipping audio extraction for %s - (%s)...due to too many attempts\n", entry.Track.Name, entry.Track.ID)
|
|
return nil
|
|
}
|
|
|
|
fmt.Printf("Extracting audio for %s - (%s)...\n", entry.Track.Name, entry.Track.ID)
|
|
entry.Track.ConvertingStatus.Status = TrakcConverting
|
|
DbWriteEntry(entry.Track.ID, entry)
|
|
state.Stats.IncConvertCount()
|
|
state.runtime.Events.Emit("ytd:track", entry)
|
|
|
|
// ffmpeg -i "41qC3w3UUkU.webm" -vn -ab 128k -ar 44100 -y "41qC3w3UUkU.mp3"
|
|
outputPath := fmt.Sprintf("%s/%s.mp3", plugin.GetDir(), entry.Track.ID)
|
|
cmd := exec.CommandContext(
|
|
state.Context,
|
|
ffmpeg,
|
|
"-loglevel", "quiet",
|
|
"-i", fmt.Sprintf("%s/%s.webm", plugin.GetDir(), entry.Track.ID),
|
|
"-vn",
|
|
"-ab", "128k",
|
|
"-ar", "44100",
|
|
"-y", outputPath,
|
|
)
|
|
cmd.Stdin = os.Stdin
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
fmt.Println("Failed to extract audio:", err)
|
|
entry.Track.ConvertingStatus.Status = TrakcConvertFailed
|
|
entry.Track.ConvertingStatus.Err = err.Error()
|
|
entry.Track.ConvertingStatus.Attempts += 1
|
|
DbWriteEntry(entry.Track.ID, entry)
|
|
state.Stats.DecConvertCount()
|
|
state.runtime.Events.Emit("ytd:track", entry)
|
|
} else {
|
|
entry.Track.ConvertingStatus.Status = TrakcConverted
|
|
entry.Track.IsConvertedToMp3 = true
|
|
|
|
// check new filesize and save it
|
|
fileInfo, err := os.Stat(outputPath)
|
|
if err == nil {
|
|
entry.Track.ConvertingStatus.Filesize = int(fileInfo.Size())
|
|
}
|
|
|
|
DbWriteEntry(entry.Track.ID, entry)
|
|
state.Stats.DecConvertCount()
|
|
state.runtime.Events.Emit("ytd:track", entry)
|
|
|
|
// remove webm if needed
|
|
if state.Config.CleanWebmFiles && plugin.IsTrackFileExists(entry.Track, "webm") {
|
|
err = os.Remove(fmt.Sprintf("%s/%s.webm", plugin.GetDir(), entry.Track.ID))
|
|
if err != nil && !os.IsNotExist(err) {
|
|
fmt.Printf("Cannot remove %s.webm file after successfull converting to mp3\n", entry.Track.ID)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (state *AppState) checkForUpdates() {
|
|
if !state.Config.CheckForUpdates {
|
|
return
|
|
}
|
|
|
|
state.updater = &Updater{
|
|
CurrentVersion: version,
|
|
LatestReleaseGitHubEndpoint: "https://api.github.com/repos/marcio199226/ytd/releases",
|
|
Client: &http.Client{Timeout: 10 * time.Minute},
|
|
SelectAsset: func(release Release, asset Asset) bool {
|
|
// look for the zip file
|
|
return strings.Contains(asset.Name, fmt.Sprintf("%s-%s", runtime.GOOS, runtime.GOARCH)) && filepath.Ext(asset.Name) == ".zip"
|
|
},
|
|
DownloadBytesLimit: 10_741_824, // 10MB
|
|
}
|
|
|
|
latest, hasUpdate, err := state.updater.HasUpdate()
|
|
|
|
if err != nil {
|
|
_, err := state.runtime.Dialog.Message(&dialog.MessageDialog{
|
|
Type: dialog.ErrorDialog,
|
|
Title: gotext.Get("Update check failed"),
|
|
Message: err.Error(),
|
|
Buttons: []string{"OK"},
|
|
CancelButton: "OK",
|
|
})
|
|
if err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
if !hasUpdate {
|
|
_, err := state.runtime.Dialog.Message(&dialog.MessageDialog{
|
|
Type: dialog.InfoDialog,
|
|
Title: gotext.Get("You're up to date"),
|
|
Message: fmt.Sprintf(gotext.Get("%s is the latest version.", latest.TagName)),
|
|
Buttons: []string{"OK"},
|
|
CancelButton: "OK",
|
|
})
|
|
if err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
clickedAction, _ := state.runtime.Dialog.Message(&dialog.MessageDialog{
|
|
Type: dialog.InfoDialog,
|
|
Title: fmt.Sprintf(gotext.Get("New version available: %s", latest.TagName)),
|
|
Message: gotext.Get("Would you like to update?"),
|
|
Buttons: []string{"OK", "Changelog", gotext.Get("Update")},
|
|
CancelButton: "OK",
|
|
})
|
|
|
|
state.tray.reRenderTray(func() {
|
|
state.tray.versionMenuItem.Label = fmt.Sprintf(gotext.Get("⚠️ ytd (%s) (new version %s)", version, latest.TagName))
|
|
})
|
|
|
|
switch clickedAction {
|
|
case "Update":
|
|
// continue
|
|
case "Changelog":
|
|
state.runtime.Window.Show()
|
|
state.runtime.Events.Emit("ytd:app:update:changelog", latest)
|
|
return
|
|
case "OK":
|
|
state.runtime.Events.Emit("ytd:app:update:available", latest)
|
|
return
|
|
}
|
|
|
|
state.Update(false)
|
|
}
|
|
|
|
func (state *AppState) Update(restart bool) {
|
|
_, err := state.updater.Update()
|
|
if err != nil {
|
|
_, err := state.runtime.Dialog.Message(&dialog.MessageDialog{
|
|
Type: dialog.WarningDialog,
|
|
Title: gotext.Get("Update not successful"),
|
|
Message: err.Error(),
|
|
Buttons: []string{"OK"},
|
|
CancelButton: "OK",
|
|
})
|
|
if err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
// restart for now does not work properly, it closes current ytd instance but not launch the new one
|
|
// so notify user that update has been done and could restart app
|
|
// err = u.Restart()
|
|
state.runtime.Dialog.Message(&dialog.MessageDialog{
|
|
Type: dialog.InfoDialog,
|
|
Title: gotext.Get("Update successful"),
|
|
Message: gotext.Get("Please restart ytd for the changes to take effect."),
|
|
Buttons: []string{"OK"},
|
|
CancelButton: "OK",
|
|
})
|
|
}
|
|
|
|
func (state *AppState) checkStartsAtLogin() {
|
|
/*startsAtLogin, err := mac.StartsAtLogin()
|
|
if err != nil {
|
|
state.tray.reRenderTray(func() {
|
|
state.tray.startAtLoginMenuItem.Label = gotext.Get("⚠ Start at Login unavailable")
|
|
state.tray.startAtLoginMenuItem.Disabled = true
|
|
})
|
|
} else if startsAtLogin {
|
|
mac.ShowNotification("Ytd", gotext.Get("App has been started in background"), "", "")
|
|
state.tray.reRenderTray(func() {
|
|
state.tray.startAtLoginMenuItem.Checked = true
|
|
state.tray.startAtLoginMenuItem.Disabled = false
|
|
})
|
|
}*/
|
|
}
|
|
|
|
func (state *AppState) SaveSettingBoolValue(name string, val bool) (err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
fmt.Println("Recovering from panic saveSettingValue:", r)
|
|
switch x := r.(type) {
|
|
case string:
|
|
err = errors.New(x)
|
|
case error:
|
|
err = x
|
|
default:
|
|
// Fallback err (per specs, error strings should be lowercase w/o punctuation
|
|
err = errors.New("unknown panic")
|
|
}
|
|
}
|
|
}()
|
|
|
|
error := DbSaveSettingBoolValue(name, val)
|
|
if err != nil {
|
|
return error
|
|
}
|
|
|
|
appState.Config.Set(name, val)
|
|
return nil
|
|
}
|
|
|
|
func (state *AppState) SaveSettingValue(name string, val string) (err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
fmt.Println("Recovering from panic saveSettingValue:", r)
|
|
switch x := r.(type) {
|
|
case string:
|
|
err = errors.New(x)
|
|
case error:
|
|
err = x
|
|
default:
|
|
// Fallback err (per specs, error strings should be lowercase w/o punctuation
|
|
err = errors.New("unknown panic")
|
|
}
|
|
}
|
|
}()
|
|
|
|
error := DbWriteSetting(name, val)
|
|
if err != nil {
|
|
return error
|
|
}
|
|
|
|
appState.Config.Set(name, val)
|
|
return nil
|
|
}
|
|
|
|
func (state *AppState) ReadSettingBoolValue(name string) (bool, error) {
|
|
return DbReadSettingBoolValue(name)
|
|
}
|
|
|
|
func (state *AppState) ReadSettingValue(name string) (string, error) {
|
|
return DbReadSetting(name)
|
|
}
|
|
|
|
func (state *AppState) RemoveEntry(record map[string]interface{}) error {
|
|
var err error
|
|
var entry GenericEntry
|
|
err = mapstructure.Decode(record, &entry)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if entry.Type == "track" {
|
|
if err = DbDeleteEntry(entry.Track.ID); err == nil {
|
|
plugin := getPluginFor(entry.Source)
|
|
if plugin.IsTrackFileExists(entry.Track, "webm") {
|
|
err = os.Remove(fmt.Sprintf("%s/%s.webm", plugin.GetDir(), entry.Track.ID))
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
}
|
|
// remove mp3 if file has been already converted
|
|
if plugin.IsTrackFileExists(entry.Track, "mp3") {
|
|
err = os.Remove(fmt.Sprintf("%s/%s.mp3", plugin.GetDir(), entry.Track.ID))
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// remove track from playlists
|
|
for _, p := range state.OfflinePlaylists {
|
|
for _, tid := range p.TracksIds {
|
|
if tid == entry.Track.ID {
|
|
state.offlinePlaylistService.RemoveTrackFromPlaylist(tid, p)
|
|
}
|
|
}
|
|
}
|
|
state.OfflinePlaylists, _ = state.offlinePlaylistService.GetPlaylists(true)
|
|
return nil
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (state *AppState) AddToDownload(url string, isFromClipboard bool) error {
|
|
for _, plugin := range plugins {
|
|
if support := plugin.Supports(url); support {
|
|
if appState.GetAppConfig().ConcurrentDownloads {
|
|
go func() {
|
|
newEntries <- plugin.Fetch(url, isFromClipboard)
|
|
}()
|
|
} else {
|
|
newEntries <- plugin.Fetch(url, isFromClipboard)
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (state *AppState) StartDownload(record map[string]interface{}) error {
|
|
var err error
|
|
var entry GenericEntry
|
|
err = mapstructure.Decode(record, &entry)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if appState.Stats.DownloadingCount >= appState.Config.MaxParrallelDownloads {
|
|
return errors.New("Max simultaneous downloads are reached please retry after some track finished downloading")
|
|
}
|
|
|
|
for _, plugin := range plugins {
|
|
if plugin.GetName() == entry.Source && entry.Type == "track" {
|
|
if appState.GetAppConfig().ConcurrentDownloads {
|
|
go func() {
|
|
plugin.StartDownload(&entry)
|
|
}()
|
|
} else {
|
|
plugin.StartDownload(&entry)
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (state *AppState) AddToConvertQueue(entry GenericEntry) error {
|
|
state.convertQueue <- entry
|
|
return nil
|
|
}
|
|
|
|
func (state *AppState) IsSupportedUrl(url string) bool {
|
|
for _, plugin := range plugins {
|
|
if support := plugin.Supports(url); support {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (state *AppState) IsFFmpegInstalled() (string, error) {
|
|
ffmpeg, err := exec.LookPath("ffmpeg")
|
|
if state.runtime.System.Platform() == "darwin" && err != nil {
|
|
// on darwin check if ffmpeg is maybe installed by homebrew
|
|
// (searching for ffmpeg only give wrong results if installed with homebrew)
|
|
ffmpeg, err := exec.LookPath("/opt/homebrew/bin/ffmpeg")
|
|
return ffmpeg, err
|
|
}
|
|
return ffmpeg, err
|
|
}
|
|
|
|
func (state *AppState) OpenUrl(url string) error {
|
|
return state.runtime.Browser.Open(url)
|
|
}
|
|
|
|
func (state *AppState) ForceQuit() {
|
|
state.runtime.Quit()
|
|
}
|
|
|
|
func (state *AppState) ShowWindow() {
|
|
state.runtime.Window.Show()
|
|
}
|
|
|
|
func getPluginFor(name string) Plugin {
|
|
for _, plugin := range plugins {
|
|
if plugin.GetName() == name {
|
|
return plugin
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//WailsRuntime .
|
|
type WailsRuntime struct {
|
|
runtime *wails.Runtime
|
|
}
|
|
|