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
Showing
20 changed files
with
593 additions
and
102 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 |
---|---|---|
@@ -1,31 +1,28 @@ | ||
# Safe My work | ||
SafeMyWork save all files in the given directory in another directory to keep your work safe and avoid losing data. | ||
SafeMyWork save all files in the given directory in another directory to keep your work safe and avoid loosing data. | ||
|
||
[![Documentation Status](https://readthedocs.org/projects/safemywork/badge/?version=master)](http://safemywork.readthedocs.org/en/master) | ||
[![Build Status](https://travis-ci.org/Thykof/SafeMyWork.svg?branch=master)](https://travis-ci.org/Thykof/SafeMyWork) | ||
[![Code Health](https://landscape.io/github/Thykof/SafeMyWork/master/landscape.svg?style=flat)](https://landscape.io/github/Thykof/SafeMyWork/master) | ||
|
||
## Version | ||
Current version is 0.2. | ||
Current version is 1.0 | ||
#### Current state | ||
- feature/outline | ||
- watch different directories | ||
- specify files and extensions to exclude | ||
- specify directories, files and extensions to exclude | ||
- interface (gtk) | ||
|
||
Work in progress... | ||
|
||
#### TODO | ||
- make an history of each files | ||
- compress files | ||
- interface (gtk) | ||
|
||
## What for ? | ||
SafeMyWork is intend for people who handle lot of files which can be texts, images, songs. | ||
|
||
Run SafeMyWork and give it a directory to watch. Then while you are working, every ten minutes all files in this directory will be copy in another directory. As the result of keeping them safe if you does not save your work or delete accidentally files. | ||
|
||
## How to use it ? | ||
Run `python3 main.py <path-to-your-dir>` to start. The given path is the directory watching. You don't need extra requirement. | ||
|
||
## License | ||
SafeMyWork is under the GNU GPL v3 license. |
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
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
File renamed without changes.
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,147 @@ | ||
# -*- coding: utf-8 -*- | ||
#!/usr/bin/python3 | ||
|
||
import gi | ||
gi.require_version('Gtk', '3.0') | ||
from gi.repository import Gtk | ||
|
||
from os import path | ||
|
||
def del_dir_dialog(parent, directory): | ||
dialog = Gtk.MessageDialog(parent, 0, Gtk.MessageType.QUESTION, | ||
Gtk.ButtonsType.YES_NO, "Ne plus surveiller " + directory + " ?") | ||
response = dialog.run() | ||
return response == Gtk.ResponseType.YES, dialog | ||
|
||
class Settings(Gtk.Dialog): | ||
"""docstring for Settings""" | ||
def __init__(self, parent): | ||
Gtk.Dialog.__init__(self, 'Préférences', parent, 0) | ||
self.parent = parent | ||
self.set_modal(True) | ||
self.set_resizable(False) | ||
self.set_border_width(10) | ||
self.connect('delete-event', self.close) | ||
box = self.get_content_area() | ||
box.set_spacing(6) | ||
|
||
box_time = self.create_box_time() | ||
box_ext = self.create_box_ext() | ||
box_files = self.create_box_files() | ||
box_dirs = self.create_box_dirs() | ||
box_archive_dir = self.create_box_archive_dir() | ||
button_close = Gtk.Button.new_with_label('Femrer') | ||
button_close.connect('clicked', self.close) | ||
|
||
# Pack boxes | ||
box.pack_start(box_time, False, False, 0) | ||
box.pack_start(box_ext, False, False, 0) | ||
box.pack_start(box_files, False, False, 0) | ||
box.pack_start(box_dirs, False, False, 0) | ||
box.pack_start(box_archive_dir, False, False, 0) | ||
box.pack_start(button_close, False, False, 0) | ||
self.show_all() | ||
|
||
def create_box_time(self): | ||
# Set timedelta: | ||
box_time = Gtk.Box(spacing=6) | ||
adjustment = Gtk.Adjustment(10, 1, 60, 10, 10, 0) | ||
self.spinbutton = Gtk.SpinButton(adjustment=adjustment) | ||
self.spinbutton.set_digits(0) | ||
self.spinbutton.set_value(self.parent.config['time_delta']) | ||
timedelta_button = Gtk.Button.new_with_label('Changer') | ||
timedelta_button.connect('clicked', self.change_timedelta) | ||
box_time.pack_start(Gtk.Label('Scan tous les :'), False, False, 0) | ||
box_time.pack_start(self.spinbutton, False, False, 0) | ||
box_time.pack_start(Gtk.Label('Minutes. '), False, False, 0) | ||
box_time.pack_start(timedelta_button, False, False, 0) | ||
return box_time | ||
|
||
def create_box_ext(self): | ||
# Exclude extenstions: | ||
box_ext = Gtk.Box(spacing=6) | ||
box_ext.pack_start(Gtk.Label('Extensions : '), False, False, 0) | ||
ext_list = Gtk.ComboBoxText.new_with_entry() | ||
button_add_ext = Gtk.Button.new_with_label('Ajouter') | ||
button_add_ext.connect('clicked', lambda arg: self.add('exclude_ext', ext_list)) | ||
button_del_ext = Gtk.Button.new_with_label('Supprimer') | ||
button_del_ext.connect('clicked', lambda arg: self.delete('exclude_ext', ext_list)) | ||
box_ext.pack_start(ext_list, False, False, 0) | ||
box_ext.pack_start(button_add_ext, False, False, 0) | ||
box_ext.pack_start(button_del_ext, False, False, 0) | ||
for ext in self.parent.config['exclude_ext']: | ||
ext_list.append_text(ext) | ||
return box_ext | ||
|
||
def create_box_files(self): | ||
# Exclude files: | ||
box_files = Gtk.Box(spacing=6) | ||
box_files.pack_start(Gtk.Label('Fichiers : '), False, False, 0) | ||
file_list = Gtk.ComboBoxText.new_with_entry() | ||
button_add_file = Gtk.Button.new_with_label('Ajouter') | ||
button_add_file.connect('clicked', lambda arg: self.add('exclude_files', file_list)) | ||
button_del_file = Gtk.Button.new_with_label('Supprimer') | ||
button_del_file.connect('clicked', lambda arg: self.delete('exclude_files', file_list)) | ||
box_files.pack_start(file_list, False, False, 0) | ||
box_files.pack_start(button_add_file, False, False, 0) | ||
box_files.pack_start(button_del_file, False, False, 0) | ||
for file in self.parent.config['exclude_files']: | ||
file_list.append_text(file) | ||
return box_files | ||
|
||
def create_box_dirs(self): | ||
# Exclude directories: | ||
box_dirs = Gtk.Box(spacing=6) | ||
box_dirs.pack_start(Gtk.Label('Dossiers : '), False, False, 0) | ||
dirs_list = Gtk.ComboBoxText.new_with_entry() | ||
button_add_dirs = Gtk.Button.new_with_label('Ajouter') | ||
button_add_dirs.connect('clicked', lambda arg: self.add('exclude_dirs', dirs_list)) | ||
button_del_dirs = Gtk.Button.new_with_label('Supprimer') | ||
button_del_dirs.connect('clicked', lambda arg: self.delete('exclude_dirs', dirs_list)) | ||
box_dirs.pack_start(dirs_list, False, False, 0) | ||
box_dirs.pack_start(button_add_dirs, False, False, 0) | ||
box_dirs.pack_start(button_del_dirs, False, False, 0) | ||
for dirs in self.parent.config['exclude_dirs']: | ||
dirs_list.append_text(dirs) | ||
return box_dirs | ||
|
||
def create_box_archive_dir(self): | ||
# Archive directory: | ||
box_archive_dir = Gtk.Box(spacing=6) | ||
box_archive_dir.pack_start(Gtk.Label('Dossier archive : '), False, False, 0) | ||
self.archive_entry = Gtk.Entry() | ||
self.archive_entry.set_text(self.parent.config['archive_dir']) | ||
archive_button = Gtk.Button.new_with_label('Changer') | ||
archive_button.connect('clicked', self.change_archive_dir) | ||
box_archive_dir.pack_start(self.archive_entry, False, False, 0) | ||
box_archive_dir.pack_start(archive_button, False, False, 0) | ||
return box_archive_dir | ||
|
||
|
||
def close(self, *args): | ||
self.destroy() | ||
|
||
def add(self, elt, combo_list): | ||
tree_iter = combo_list.get_active_iter() | ||
if tree_iter is None: | ||
new = combo_list.get_child().get_text() | ||
if new not in self.parent.config[elt]: | ||
combo_list.append_text(new) | ||
self.parent.config[elt].append(new) | ||
|
||
def delete(self, elt, combo_list): | ||
tree_iter = combo_list.get_active_iter() | ||
if tree_iter is not None: | ||
model = combo_list.get_model() | ||
new = model[tree_iter][0] | ||
combo_list.remove(int(combo_list.get_active())) | ||
self.parent.config[elt].remove(new) | ||
|
||
def change_timedelta(self, button): | ||
timedelta = int(self.spinbutton.get_value()) | ||
self.parent.config['time_delta'] = timedelta | ||
|
||
def change_archive_dir(self, button): | ||
new = self.archive_entry.get_text() | ||
if path.exists(path.dirname(new)) or path.dirname(new) == '': | ||
self.parent.config['archive_dir'] = new |
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,115 @@ | ||
# -*- coding: utf-8 -*- | ||
#!/usr/bin/python3 | ||
|
||
import gi | ||
gi.require_version('Gtk', '3.0') | ||
from gi.repository import Gtk | ||
import threading | ||
from os import path | ||
from platform import system | ||
|
||
from .menubar import create_menus | ||
from .tools import about, MainGrid | ||
from .dialog import del_dir_dialog, Settings | ||
from watcher.watcher import Watcher | ||
import watcher | ||
|
||
SYSTEM = system() | ||
if SYSTEM == 'Linux': | ||
from subprocess import Popen | ||
elif SYSTEM == 'Windows': | ||
from os import startfile | ||
else: | ||
watcher.mod.tell('Import Error') | ||
|
||
class MyWindow(Gtk.ApplicationWindow): | ||
def __init__(self, app, config): | ||
Gtk.Window.__init__(self, title='SafeMyWork 1.0', application=app) | ||
self.set_position(Gtk.WindowPosition.CENTER) | ||
self.set_border_width(5) | ||
|
||
self.grid = MainGrid(self) | ||
self.add(self.grid) | ||
self.grid.switch_start.do_grab_focus(self.grid.switch_start) | ||
|
||
create_menus(self) | ||
|
||
self.config = config | ||
self.watcher = Watcher(self.config) | ||
self.timer = None | ||
self.thread = None | ||
|
||
self.initialize_config() | ||
|
||
def initialize_config(self): | ||
for watched_dir in self.config['watched_dirs']: | ||
self.grid.watched_list.append_text(watched_dir) | ||
|
||
def save_config(self, config): | ||
watcher.mod.tell('Save config') | ||
watcher.conf.save_config(config) | ||
|
||
def watch(self, loop): | ||
"""Launch watch""" | ||
self.grid.spinner.start() | ||
self.watcher.watch() | ||
self.grid.spinner.stop() | ||
if loop: | ||
self.timer = threading.Timer(self.config['time_delta']*60, self.start_watching, args=(loop,)) | ||
self.timer.start() | ||
|
||
def start_watching(self, loop): | ||
"""Start watching : watch + timer""" | ||
can = True | ||
if loop: | ||
can = self.grid.switch_start.get_active() | ||
for thread in threading.enumerate(): | ||
if thread.name == 'watcher_loop' or thread.name == 'watcher_alone': | ||
if thread.is_alive(): | ||
can = False | ||
if can: | ||
self.thread = threading.Thread(target=self.watch, name='watcher_loop', args=(loop,)) | ||
self.thread.start() | ||
if loop: | ||
self.grid.text.set_text('Surveillance active') | ||
|
||
def stop_watching(self): | ||
"""Cancel timer""" | ||
if self.timer is not None: | ||
self.timer.cancel() | ||
self.grid.text.set_text('En attente...') | ||
|
||
def abort_watch(self): | ||
"""Abort current watch""" | ||
self.watcher.stop = True | ||
|
||
def show_saved(self, *args): | ||
if SYSTEM == 'Linux': | ||
Popen(['xdg-open', self.config['archive_dir']]) | ||
elif SYSTEM == 'Windows': | ||
startfile(self.config['archive_dir']) | ||
|
||
def settings(self, action, parameter): | ||
dialog_settings = Settings(self) | ||
dialog_settings.run() | ||
|
||
def add_watched_dir(self, button): | ||
tree_iter = self.grid.watched_list.get_active_iter() | ||
if tree_iter is None: | ||
new_dir = self.grid.watched_list.get_child().get_text() | ||
if new_dir != '' and new_dir not in self.config['watched_dirs'] and path.exists(new_dir): | ||
self.grid.watched_list.append_text(new_dir) | ||
self.config['watched_dirs'].append(new_dir) | ||
self.grid.text.set_text('Dossier ajouté') | ||
|
||
def del_watched_dir(self, button): | ||
tree_iter = self.grid.watched_list.get_active_iter() | ||
if tree_iter is not None: | ||
model = self.grid.watched_list.get_model() | ||
directory = model[tree_iter][0] | ||
must_del, dialog = del_dir_dialog(self, directory) | ||
dialog.destroy() | ||
if must_del: | ||
self.config['watched_dirs'].remove(directory) | ||
self.grid.watched_list.remove(int(self.grid.watched_list.get_active())) | ||
self.grid.text.set_text('Dossier supprimé') |
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,47 @@ | ||
# -*- coding: utf-8 -*- | ||
#!/usr/bin/python3 | ||
|
||
import gi | ||
gi.require_version('Gtk', '3.0') | ||
from gi.repository import Gio | ||
|
||
from .tools import about | ||
|
||
def create_menus(parent): | ||
show_saved_action = Gio.SimpleAction.new('show_saved') | ||
show_saved_action.connect('activate', parent.show_saved) | ||
parent.add_action(show_saved_action) | ||
|
||
settings_action = Gio.SimpleAction.new('settings') | ||
settings_action.connect('activate', parent.settings) | ||
parent.add_action(settings_action) | ||
|
||
start_watching_action = Gio.SimpleAction.new('start_watching') | ||
start_watching_action.connect('activate', start_watching, parent) | ||
parent.add_action(start_watching_action) | ||
|
||
stop_watching_action = Gio.SimpleAction.new('stop_watching') | ||
stop_watching_action.connect('activate', stop_watching, parent) | ||
parent.add_action(stop_watching_action) | ||
|
||
watch_now_action = Gio.SimpleAction.new('watch_now') | ||
watch_now_action.connect('activate', wtach_now, parent) | ||
parent.add_action(watch_now_action) | ||
|
||
about_action = Gio.SimpleAction.new('about') | ||
about_action.connect('activate', show_about, parent) | ||
parent.add_action(about_action) | ||
|
||
def start_watching(action, parameter, parent): | ||
if not parent.grid.switch_start.get_active(): | ||
parent.grid.switch_start.set_active(True) | ||
|
||
def stop_watching(action, parameter, parent): | ||
if parent.grid.switch_start.get_active(): | ||
parent.grid.switch_start.set_active(False) | ||
|
||
def show_about(action, parameter, parent): | ||
about(parent) | ||
|
||
def wtach_now(action, parameter, parent): | ||
parent.start_watching(False) |
Oops, something went wrong.