-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 3db39f6
Showing
9 changed files
with
440 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/lock | ||
/output | ||
/skype-media-saver.exe | ||
/skype-media-saver.log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# Skype Media Saver | ||
|
||
## What is this? | ||
|
||
There is a Skype setting called "When I receive a file..." where you can select a folder and Skype used to put all | ||
received files into that directory. | ||
|
||
At some point Skype stopped doing that for images and videos. Those are now **only in the cloud** (and I don't know | ||
for how long) unless you explicitly save each picture manually. | ||
|
||
So this little tool will watch the Skype media cache folder and whenever a new file is received, it will copy this file | ||
to its output folder. | ||
|
||
## How to use? | ||
|
||
Download the [latest release](). Put it in a folder and make sure it can write to this folder. Put a link in the Start > | ||
All Programs > Startup folder. Run it. | ||
|
||
It will write every received picture to that folder. | ||
|
||
Currently it only works on Windows because I don't know where Linux Skype puts its files. PRs welcome. | ||
|
||
## How to exit? | ||
|
||
Run it again, it will ask if you want it to exit. | ||
|
||
## How to build? | ||
|
||
``` | ||
go get | ||
go build -ldflags "-s -w -H=windowsgui" | ||
``` | ||
|
||
(`-s -w` for smaller executables, `-H=windowsgui` for no console window) | ||
|
||
### What is `rsrc.syso`? | ||
|
||
It will be linked by the go compiler to [tell windows to use modern windows buttons](https://msdn.microsoft.com/en-us/library/windows/desktop/bb773175(v=vs.85).aspx) | ||
in the dialogs. It can be generated from `beautiful_buttons.manifest.xml` with | ||
`rsrc -manifest=beautiful_dialogs.manifest.xml` (by [rsrc]()). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> | ||
<assemblyIdentity | ||
version="1.0.0.0" | ||
processorArchitecture="*" | ||
name="skype-media-saver" | ||
type="win32" | ||
/> | ||
<description>skype-media-saver</description> | ||
<dependency> | ||
<dependentAssembly> | ||
<assemblyIdentity | ||
type="win32" | ||
name="Microsoft.Windows.Common-Controls" | ||
version="6.0.0.0" | ||
processorArchitecture="*" | ||
publicKeyToken="6595b64144ccf1df" | ||
language="*" | ||
/> | ||
</dependentAssembly> | ||
</dependency> | ||
</assembly> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package main | ||
|
||
import ( | ||
"syscall" | ||
"unsafe" | ||
) | ||
|
||
const ( | ||
MB_OK = 0x00000000 | ||
MB_OKCANCEL = 0x00000001 | ||
MB_ABORTRETRYIGNORE = 0x00000002 | ||
MB_YESNOCANCEL = 0x00000003 | ||
MB_YESNO = 0x00000004 | ||
MB_RETRYCANCEL = 0x00000005 | ||
MB_CANCELTRYCONTINUE = 0x00000006 | ||
MB_ICONHAND = 0x00000010 | ||
MB_ICONQUESTION = 0x00000020 | ||
MB_ICONEXCLAMATION = 0x00000030 | ||
MB_ICONASTERISK = 0x00000040 | ||
MB_USERICON = 0x00000080 | ||
MB_ICONWARNING = MB_ICONEXCLAMATION | ||
MB_ICONERROR = MB_ICONHAND | ||
MB_ICONINFORMATION = MB_ICONASTERISK | ||
MB_ICONSTOP = MB_ICONHAND | ||
|
||
MB_DEFBUTTON1 = 0x00000000 | ||
MB_DEFBUTTON2 = 0x00000100 | ||
MB_DEFBUTTON3 = 0x00000200 | ||
MB_DEFBUTTON4 = 0x00000300 | ||
) | ||
|
||
const ( | ||
IDOK = 1 | ||
IDCANCEL = 2 | ||
IDABORT = 3 | ||
IDRETRY = 4 | ||
IDIGNORE = 5 | ||
IDYES = 6 | ||
IDNO = 7 | ||
IDCLOSE = 8 | ||
IDHELP = 9 | ||
IDTRYAGAIN = 10 | ||
IDCONTINUE = 11 | ||
IDTIMEOUT = 32000 | ||
) | ||
|
||
func showMessage(title string, text string, error bool) int { | ||
|
||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms645505(v=vs.85).aspx | ||
|
||
user32 := syscall.MustLoadDLL("user32.dll") | ||
msgboxf := user32.MustFindProc("MessageBoxW") | ||
|
||
icon := MB_ICONINFORMATION | ||
if error { | ||
icon = MB_ICONERROR | ||
} | ||
|
||
//noinspection GoDeprecation | ||
ret, _, _ := msgboxf.Call( | ||
uintptr(0), | ||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text))), | ||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))), | ||
uintptr(icon)) | ||
|
||
return int(ret) | ||
|
||
} | ||
|
||
func askMessage(title string, text string) (yes bool, res int) { | ||
|
||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms645505(v=vs.85).aspx | ||
|
||
user32 := syscall.MustLoadDLL("user32.dll") | ||
msgboxf := user32.MustFindProc("MessageBoxW") | ||
|
||
//noinspection GoDeprecation | ||
ret, _, _ := msgboxf.Call( | ||
uintptr(0), | ||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text))), | ||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))), | ||
uintptr(MB_ICONQUESTION|MB_YESNO)) | ||
|
||
return ret == IDYES, int(ret) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package main | ||
|
||
import ( | ||
"io" | ||
"os" | ||
) | ||
|
||
func copyFile(dst, src string) error { | ||
s, err := os.Open(src) | ||
if err != nil { | ||
return err | ||
} | ||
defer s.Close() | ||
d, err := os.Create(dst) | ||
if err != nil { | ||
return err | ||
} | ||
if _, err := io.Copy(d, s); err != nil { | ||
d.Close() | ||
return err | ||
} | ||
return d.Close() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
package main | ||
|
||
import ( | ||
"github.com/fsnotify/fsnotify" | ||
"github.com/sirupsen/logrus" | ||
"github.com/theckman/go-flock" | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
var exeDir = getExeDir() | ||
|
||
var log = makeLogger() | ||
|
||
func main() { | ||
|
||
log.Infoln("Start") | ||
|
||
fl := flock.NewFlock(filepath.Join(exeDir, "lock")) | ||
locked, _ := fl.TryLock() // ignore errors because a failed lock is also an error | ||
if !locked { | ||
// Another instance is already running | ||
|
||
pl := findMyOtherProcesses() | ||
if len(pl) != 1 { | ||
log.Fatalln("You are running two instances of Skype Media Saver from two different folders. In this case I can't help you to kill those instances, use the Task Manager.") | ||
os.Exit(1) | ||
} | ||
|
||
yes, _ := askMessage("Skype Media Saver", "Another instance is already running (PID: "+strconv.Itoa(pl[0])+"). Do you want to kill it?") | ||
|
||
if yes { | ||
p, err := os.FindProcess(pl[0]) | ||
if err != nil { | ||
log.Fatalln("Error getting the other process: " + err.Error()) | ||
} | ||
err = p.Kill() | ||
if err != nil { | ||
log.Fatalln("Error killing the other process: " + err.Error()) | ||
} | ||
} | ||
|
||
os.Exit(0) | ||
} | ||
|
||
log.Infoln("Finding Skype media cache directories") | ||
|
||
mcs := findSkypeMediaCaches() | ||
|
||
if len(mcs) == 0 { | ||
log.Warnln("No media cache directory found") | ||
os.Exit(1) | ||
} | ||
|
||
for _, d := range mcs { | ||
go watch(d) | ||
} | ||
|
||
select {} // block main goroutine | ||
|
||
} | ||
|
||
func watch(dir SkypeProfileDirectory) { | ||
|
||
var err error | ||
|
||
log := log.WithField("profile", dir.Name) | ||
log.Infoln("Watching: " + dir.MediaCache) | ||
|
||
output := filepath.Join(exeDir, "output", dir.Name) | ||
err = os.MkdirAll(output, 0777) | ||
if err != nil { | ||
log.Errorln("Can't create output directory: " + err.Error()) | ||
} | ||
log.Infoln("Output directory: " + output) | ||
|
||
watcher, err := fsnotify.NewWatcher() | ||
if err != nil { | ||
log.Errorln("Watching error: " + err.Error()) | ||
} | ||
|
||
err = watcher.Add(dir.MediaCache) | ||
if err != nil { | ||
log.Errorln("Watching error: " + err.Error()) | ||
} | ||
|
||
sync(dir.MediaCache, output) | ||
|
||
go func() { | ||
for err := range watcher.Errors { | ||
log.Warnln("Error from fsnotify: " + err.Error()) | ||
} | ||
}() | ||
|
||
for e := range watcher.Events { | ||
|
||
// When Skype downloads an image, the following events happen: | ||
// Create: ^foo..._fullsize_distr | ||
// Write: ^foo..._fullsize_distr | ||
// Write: ^foo..._fullsize_distr | ||
// ... | ||
// Create: ^foo..._fullsize_distr.jpg | ||
// Write: ^foo..._fullsize_distr.jpg | ||
|
||
// So when we get a write event with a file extension, we assume the file to be complete | ||
|
||
if e.Op&fsnotify.Write == fsnotify.Write { | ||
if strings.HasSuffix(e.Name, "_fullsize_distr.jpg") || | ||
strings.HasSuffix(e.Name, "_fullsize_distr.png") || | ||
strings.HasSuffix(e.Name, "_video_distr.mp4") { | ||
|
||
name := filepath.Base(e.Name) | ||
err = copyFile(filepath.Join(output, name), e.Name) | ||
if err != nil { | ||
log.Errorln("Error copying file: " + err.Error()) | ||
} | ||
} | ||
} | ||
|
||
} | ||
} | ||
|
||
func sync(from string, to string) { | ||
|
||
files, err := ioutil.ReadDir(from) | ||
if err != nil { | ||
log.Fatalln("Error doing initial sync: " + err.Error()) | ||
} | ||
for _, f := range files { | ||
|
||
if f.IsDir() { | ||
continue | ||
} | ||
if !(strings.HasSuffix(f.Name(), "_fullsize_distr.jpg") || | ||
strings.HasSuffix(f.Name(), "_fullsize_distr.png") || | ||
strings.HasSuffix(f.Name(), "_video_distr.mp4")) { | ||
continue | ||
} | ||
|
||
dst, err := os.Stat(filepath.Join(to, f.Name())) | ||
if err != nil || dst.ModTime().Before(f.ModTime()) { | ||
err = copyFile(filepath.Join(to, f.Name()), filepath.Join(from, f.Name())) | ||
if err != nil { | ||
log.Errorln("Error copying file: " + err.Error()) | ||
} | ||
} | ||
} | ||
|
||
} | ||
|
||
func getExeDir() string { | ||
if os.Getenv("EXEDIR") != "" { | ||
return os.Getenv("EXEDIR") | ||
} else { | ||
return filepath.Dir(os.Args[0]) | ||
} | ||
} | ||
|
||
func makeLogger() *logrus.Logger { | ||
log := logrus.New() | ||
|
||
log.SetLevel(logrus.DebugLevel) | ||
|
||
log.Hooks.Add(DialogHook{}) | ||
|
||
f, err := os.OpenFile(exeDir+"/skype-media-saver.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600) | ||
if err != nil { | ||
log.Fatalln("Can't open log file: " + err.Error()) | ||
} | ||
|
||
log.Out = f | ||
|
||
return log | ||
} | ||
|
||
type DialogHook struct{} | ||
|
||
func (DialogHook) Levels() []logrus.Level { | ||
return []logrus.Level{ | ||
logrus.WarnLevel, | ||
logrus.ErrorLevel, | ||
logrus.FatalLevel, | ||
logrus.PanicLevel, | ||
} | ||
} | ||
|
||
func (DialogHook) Fire(e *logrus.Entry) error { | ||
showMessage("Skype Media Saver", e.Message, true) | ||
return nil | ||
} |
Oops, something went wrong.