├── requirements.txt ├── nextcloud_integration ├── patches.txt ├── www │ ├── __init__.py │ └── __pycache__ │ │ └── __init__.py ├── config │ ├── __init__.py │ ├── desktop.py │ └── docs.py ├── templates │ ├── __init__.py │ └── pages │ │ ├── __init__.py │ │ └── __pycache__ │ │ └── __init__.py ├── modules.txt ├── nextcloud_integration │ ├── __init__.py │ └── doctype │ │ ├── __init__.py │ │ └── nextcloud_setting │ │ ├── __init__.py │ │ ├── test_nextcloud_setting.py │ │ ├── nextcloud_setting.js │ │ ├── nextcloud_setting.json │ │ └── nextcloud_setting.py ├── __init__.py └── hooks.py ├── .gitignore ├── .github ├── nextcloud_setting_screen.png ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── setup.py ├── MANIFEST.in ├── LICENSE └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | frappe -------------------------------------------------------------------------------- /nextcloud_integration/patches.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nextcloud_integration/www/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nextcloud_integration/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nextcloud_integration/templates/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nextcloud_integration/templates/pages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nextcloud_integration/www/__pycache__/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nextcloud_integration/modules.txt: -------------------------------------------------------------------------------- 1 | Nextcloud Integration -------------------------------------------------------------------------------- /nextcloud_integration/nextcloud_integration/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nextcloud_integration/nextcloud_integration/doctype/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nextcloud_integration/templates/pages/__pycache__/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nextcloud_integration/nextcloud_integration/doctype/nextcloud_setting/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.egg-info 4 | *.swp 5 | tags 6 | nextcloud_integration/docs/current -------------------------------------------------------------------------------- /.github/nextcloud_setting_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/nextcloud-integration/main/.github/nextcloud_setting_screen.png -------------------------------------------------------------------------------- /nextcloud_integration/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | __version__ = '0.0.1' 5 | 6 | -------------------------------------------------------------------------------- /nextcloud_integration/nextcloud_integration/doctype/nextcloud_setting/test_nextcloud_setting.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2020, Frappe and Contributors 3 | # See license.txt 4 | from __future__ import unicode_literals 5 | 6 | # import frappe 7 | import unittest 8 | 9 | class TestNextcloudSetting(unittest.TestCase): 10 | pass 11 | -------------------------------------------------------------------------------- /nextcloud_integration/config/desktop.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from frappe import _ 4 | 5 | def get_data(): 6 | return [ 7 | { 8 | "module_name": "Nextcloud Integration", 9 | "color": "grey", 10 | "icon": "octicon octicon-file-directory", 11 | "type": "module", 12 | "label": _("Nextcloud Integration") 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /nextcloud_integration/config/docs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Configuration for docs 3 | """ 4 | 5 | # source_link = "https://github.com/[org_name]/nextcloud_integration" 6 | # docs_base_url = "https://[org_name].github.io/nextcloud_integration" 7 | # headline = "App that does everything" 8 | # sub_heading = "Yes, you got that right the first time, everything" 9 | 10 | def get_context(context): 11 | context.brand_html = "Nextcloud Integration" 12 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from setuptools import setup, find_packages 3 | 4 | with open('requirements.txt') as f: 5 | install_requires = f.read().strip().split('\n') 6 | 7 | # get version from __version__ variable in nextcloud_integration/__init__.py 8 | from nextcloud_integration import __version__ as version 9 | 10 | setup( 11 | name='nextcloud_integration', 12 | version=version, 13 | description='Frappe App for NextCloud Backup', 14 | author='Frappe', 15 | author_email='developers@frappe.io', 16 | packages=find_packages(), 17 | zip_safe=False, 18 | include_package_data=True, 19 | install_requires=install_requires 20 | ) 21 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MANIFEST.in 2 | include requirements.txt 3 | include *.json 4 | include *.md 5 | include *.py 6 | include *.txt 7 | recursive-include nextcloud_integration *.css 8 | recursive-include nextcloud_integration *.csv 9 | recursive-include nextcloud_integration *.html 10 | recursive-include nextcloud_integration *.ico 11 | recursive-include nextcloud_integration *.js 12 | recursive-include nextcloud_integration *.json 13 | recursive-include nextcloud_integration *.md 14 | recursive-include nextcloud_integration *.png 15 | recursive-include nextcloud_integration *.py 16 | recursive-include nextcloud_integration *.svg 17 | recursive-include nextcloud_integration *.txt 18 | recursive-exclude nextcloud_integration *.pyc -------------------------------------------------------------------------------- /nextcloud_integration/nextcloud_integration/doctype/nextcloud_setting/nextcloud_setting.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Frappe and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on('Nextcloud Setting', { 5 | 6 | refresh: function(frm) { 7 | frm.clear_custom_buttons(); 8 | frm.events.add_backup_button(frm); 9 | }, 10 | 11 | enabled: function(frm) { 12 | frm.refresh(); 13 | }, 14 | 15 | add_backup_button: function(frm) { 16 | if (frm.doc.enabled && frm.doc.nextcloud_username && frm.doc.password) { 17 | frm.add_custom_button(__("Backup Now"), function() { 18 | frappe.call({ 19 | method: "nextcloud_integration.nextcloud_integration.doctype.nextcloud_setting.nextcloud_setting.take_backup", 20 | freeze: true 21 | }); 22 | }).addClass("btn-primary"); 23 | } 24 | } 25 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Frappe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 22 | 23 | > Please provide enough information so that others can review your pull request: 24 | 25 | 26 | 27 | > Explain the **details** for making this change. What existing problem does the pull request solve? 28 | 29 | 30 | 31 | > Screenshots/GIFs 32 | 33 | 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea to improve Frappe 4 | labels: feature-request 5 | --- 6 | 7 | 17 | 18 | **Is your feature request related to a problem? Please describe.** 19 | A clear and concise description of what the problem is. Ex. I'm always frustrated when (...) 20 | 21 | **Describe the solution you'd like** 22 | A clear and concise description of what you want to happen. 23 | 24 | **Describe alternatives you've considered** 25 | A clear and concise description of any alternative solutions or features you've considered. 26 | 27 | **Additional context** 28 | Add any other context or screenshots about the feature request here. 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug encountered while using the Frappe Framework 4 | labels: bug 5 | --- 6 | 7 | 19 | 20 | ## Description of the issue 21 | 22 | ## Context information (for bug reports) 23 | 24 | **Output of `bench version`** 25 | ``` 26 | (paste here) 27 | ``` 28 | 29 | ## Steps to reproduce the issue 30 | 31 | 1. 32 | 2. 33 | 3. 34 | 35 | ### Observed result 36 | 37 | ### Expected result 38 | 39 | ### Stacktrace / full error message 40 | 41 | ``` 42 | (paste here) 43 | ``` 44 | 45 | ## Additional information 46 | 47 | OS version / distribution, `Frappe` install method, etc. 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nextcloud Integration 2 | 3 | Custom Frappe App for Nextcloud Backup 4 | 5 | ### Features 6 | This app lets you take backup of your database, config and files to your Nextcloud instance. You can configure it to take daily or weekly backups. 7 | 8 | ### Installation 9 | On your site you can download and install *nextcloud-integration* app using 10 | 11 | ``` 12 | bench get-app https://github.com/frappe/nextcloud-integration.git 13 | bench --site {site_name} install-app nextcloud_integration 14 | ``` 15 | 16 | ### Configuration 17 | 18 | After successful installation of *nextcloud-integration* app You can search for **Nextcloud Settings** in the **Awesome Bar** which will direct you to the following **Nextcloud Settings** page 19 | 20 | Nextcloud Setting Screen 21 | 22 | * **Username**: Your *Nextcloud Account Username* 23 | * **Password**: Your *Nextcloud Account password* or *App Password* you might have created for this app. 24 | * **Nextcloud URL**: URL of site where Nextcloud Account exist. *For eg("https://example.com")*. 25 | Optionally you can also provide a port number after your URL as *("https://example.com:443")* 26 | * **WebDav URL**: You will find this in your Nextcloud Account. Example: */remote.php/dav/files/*{email_address}*/* 27 | * **Path to Upload Folder**: You can provide the *path* of folder where you would like your files to be uploaded. 28 | * **NOTE**: 29 | 1. The folder should have already been created. 30 | 2. If not provided, a folder *Frappe Backups* will be created. 31 | * **Backup Frequency**: One of either *Daily* or *Weekly* can be choosen. 32 | * **Backup Files**: Check this option to *Backup public and private files along with the database*. 33 | * **Send Notifications To**: Email on which the notification for Backups should be sent. 34 | * **Send Email for Successful Backup**: Check this option to receive email for successful backups, by default emails for failed backups are sent. 35 | 36 | After saving the configuration click on **Backup Now** button and verify if the files where uploaded in your *Nextcloud* instance. 37 | 38 | **NOTE**: This process generally takes from a few minutes to half an hour depending on the size of your backup. 39 | 40 | ### License 41 | This repository has been released under the [MIT License](LICENSE). 42 | -------------------------------------------------------------------------------- /nextcloud_integration/nextcloud_integration/doctype/nextcloud_setting/nextcloud_setting.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "creation": "2020-12-01 18:35:45.159102", 4 | "doctype": "DocType", 5 | "editable_grid": 1, 6 | "engine": "InnoDB", 7 | "field_order": [ 8 | "enabled", 9 | "nextcloud_details", 10 | "nextcloud_username", 11 | "password", 12 | "path_to_upload_folder", 13 | "column_break_6", 14 | "nextcloud_url", 15 | "webdav_url", 16 | "backup_details_section", 17 | "backup_frequency", 18 | "backup_files", 19 | "column_break_12", 20 | "send_notifications_to", 21 | "send_email_for_successful_backup" 22 | ], 23 | "fields": [ 24 | { 25 | "default": "0", 26 | "fieldname": "enabled", 27 | "fieldtype": "Check", 28 | "label": "Enable Nextcloud Backup" 29 | }, 30 | { 31 | "depends_on": "enabled", 32 | "fieldname": "nextcloud_details", 33 | "fieldtype": "Section Break", 34 | "label": "Nextcloud Details" 35 | }, 36 | { 37 | "fieldname": "nextcloud_username", 38 | "fieldtype": "Data", 39 | "in_list_view": 1, 40 | "label": "Nextcloud Username", 41 | "mandatory_depends_on": "enabled", 42 | "reqd": 1 43 | }, 44 | { 45 | "fieldname": "password", 46 | "fieldtype": "Password", 47 | "label": "Password", 48 | "mandatory_depends_on": "enabled", 49 | "reqd": 1 50 | }, 51 | { 52 | "depends_on": "enabled", 53 | "description": "Path where backups will be uploaded, if not provided default is Frappe Backups", 54 | "fieldname": "path_to_upload_folder", 55 | "fieldtype": "Data", 56 | "label": "Path to Upload Folder" 57 | }, 58 | { 59 | "fieldname": "column_break_6", 60 | "fieldtype": "Column Break" 61 | }, 62 | { 63 | "depends_on": "enabled", 64 | "description": "Optionally you can also provide port number after the URL as (https://example.com:443)", 65 | "fieldname": "nextcloud_url", 66 | "fieldtype": "Data", 67 | "in_list_view": 1, 68 | "label": "Nextcloud URL", 69 | "mandatory_depends_on": "enabled", 70 | "reqd": 1 71 | }, 72 | { 73 | "description": "Example: /remote.php/dav/files/{nextcloud_username}", 74 | "fieldname": "webdav_url", 75 | "fieldtype": "Data", 76 | "in_list_view": 1, 77 | "label": "WebDav URL", 78 | "mandatory_depends_on": "enabled", 79 | "reqd": 1 80 | }, 81 | { 82 | "depends_on": "enabled", 83 | "fieldname": "backup_details_section", 84 | "fieldtype": "Section Break", 85 | "label": "Backup Details" 86 | }, 87 | { 88 | "default": "Daily", 89 | "fieldname": "backup_frequency", 90 | "fieldtype": "Select", 91 | "in_list_view": 1, 92 | "label": "Backup Frequency", 93 | "mandatory_depends_on": "enabled", 94 | "options": "Daily\nWeekly", 95 | "reqd": 1 96 | }, 97 | { 98 | "default": "0", 99 | "description": "Backup public and private files along with the database and site config.", 100 | "fieldname": "backup_files", 101 | "fieldtype": "Check", 102 | "label": "Backup Files" 103 | }, 104 | { 105 | "fieldname": "column_break_12", 106 | "fieldtype": "Column Break" 107 | }, 108 | { 109 | "fieldname": "send_notifications_to", 110 | "fieldtype": "Data", 111 | "in_list_view": 1, 112 | "label": "Send Notifications To", 113 | "mandatory_depends_on": "enabled", 114 | "options": "Email", 115 | "reqd": 1 116 | }, 117 | { 118 | "default": "0", 119 | "description": "Note: By default emails for failed backups are sent.", 120 | "fieldname": "send_email_for_successful_backup", 121 | "fieldtype": "Check", 122 | "label": "Send Email for Successful Backup" 123 | } 124 | ], 125 | "hide_toolbar": 1, 126 | "index_web_pages_for_search": 1, 127 | "issingle": 1, 128 | "links": [], 129 | "modified": "2021-01-08 17:49:41.790409", 130 | "modified_by": "Administrator", 131 | "module": "Nextcloud Integration", 132 | "name": "Nextcloud Setting", 133 | "owner": "Administrator", 134 | "permissions": [ 135 | { 136 | "create": 1, 137 | "delete": 1, 138 | "email": 1, 139 | "print": 1, 140 | "read": 1, 141 | "role": "System Manager", 142 | "share": 1, 143 | "write": 1 144 | } 145 | ], 146 | "quick_entry": 1, 147 | "sort_field": "modified", 148 | "sort_order": "DESC", 149 | "track_changes": 1 150 | } -------------------------------------------------------------------------------- /nextcloud_integration/hooks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from . import __version__ as app_version 4 | 5 | app_name = "nextcloud_integration" 6 | app_title = "Nextcloud Integration" 7 | app_publisher = "Frappe" 8 | app_description = "Frappe App for NextCloud Backup" 9 | app_icon = "octicon octicon-file-directory" 10 | app_color = "grey" 11 | app_email = "developers@frappe.io" 12 | app_license = "MIT" 13 | 14 | # Includes in 15 | # ------------------ 16 | 17 | # include js, css files in header of desk.html 18 | # app_include_css = "/assets/nextcloud_integration/css/nextcloud_integration.css" 19 | # app_include_js = "/assets/nextcloud_integration/js/nextcloud_integration.js" 20 | 21 | # include js, css files in header of web template 22 | # web_include_css = "/assets/nextcloud_integration/css/nextcloud_integration.css" 23 | # web_include_js = "/assets/nextcloud_integration/js/nextcloud_integration.js" 24 | 25 | # include custom scss in every website theme (without file extension ".scss") 26 | # website_theme_scss = "nextcloud_integration/public/scss/website" 27 | 28 | # include js, css files in header of web form 29 | # webform_include_js = {"doctype": "public/js/doctype.js"} 30 | # webform_include_css = {"doctype": "public/css/doctype.css"} 31 | 32 | # include js in page 33 | # page_js = {"page" : "public/js/file.js"} 34 | 35 | # include js in doctype views 36 | # doctype_js = {"doctype" : "public/js/doctype.js"} 37 | # doctype_list_js = {"doctype" : "public/js/doctype_list.js"} 38 | # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} 39 | # doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} 40 | 41 | # Home Pages 42 | # ---------- 43 | 44 | # application home page (will override Website Settings) 45 | # home_page = "login" 46 | 47 | # website user home page (by Role) 48 | # role_home_page = { 49 | # "Role": "home_page" 50 | # } 51 | 52 | # Generators 53 | # ---------- 54 | 55 | # automatically create page for each record of this doctype 56 | # website_generators = ["Web Page"] 57 | 58 | # Installation 59 | # ------------ 60 | 61 | # before_install = "nextcloud_integration.install.before_install" 62 | # after_install = "nextcloud_integration.install.after_install" 63 | 64 | # Desk Notifications 65 | # ------------------ 66 | # See frappe.core.notifications.get_notification_config 67 | 68 | # notification_config = "nextcloud_integration.notifications.get_notification_config" 69 | 70 | # Permissions 71 | # ----------- 72 | # Permissions evaluated in scripted ways 73 | 74 | # permission_query_conditions = { 75 | # "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", 76 | # } 77 | # 78 | # has_permission = { 79 | # "Event": "frappe.desk.doctype.event.event.has_permission", 80 | # } 81 | 82 | # DocType Class 83 | # --------------- 84 | # Override standard doctype classes 85 | 86 | # override_doctype_class = { 87 | # "ToDo": "custom_app.overrides.CustomToDo" 88 | # } 89 | 90 | # Document Events 91 | # --------------- 92 | # Hook on document methods and events 93 | 94 | # doc_events = { 95 | # "*": { 96 | # "on_update": "method", 97 | # "on_cancel": "method", 98 | # "on_trash": "method" 99 | # } 100 | # } 101 | 102 | # Scheduled Tasks 103 | # --------------- 104 | 105 | scheduler_events = { 106 | # "all": [ 107 | # "nextcloud_integration.tasks.all" 108 | # ], 109 | "daily": [ 110 | "nextcloud_integration.nextcloud_integration.doctype.nextcloud_setting.nextcloud_setting.daily_backup" 111 | ], 112 | # "hourly": [ 113 | # "nextcloud_integration.tasks.hourly" 114 | # ], 115 | "weekly": [ 116 | "nextcloud_integration.nextcloud_integration.doctype.nextcloud_setting.nextcloud_setting.weekly_backup" 117 | ], 118 | # "monthly": [ 119 | # "nextcloud_integration.tasks.monthly" 120 | # ] 121 | } 122 | 123 | # Testing 124 | # ------- 125 | 126 | # before_tests = "nextcloud_integration.install.before_tests" 127 | 128 | # Overriding Methods 129 | # ------------------------------ 130 | # 131 | # override_whitelisted_methods = { 132 | # "frappe.desk.doctype.event.event.get_events": "nextcloud_integration.event.get_events" 133 | # } 134 | # 135 | # each overriding function accepts a `data` argument; 136 | # generated from the base implementation of the doctype dashboard, 137 | # along with any modifications made in other Frappe apps 138 | # override_doctype_dashboards = { 139 | # "Task": "nextcloud_integration.task.get_dashboard_data" 140 | # } 141 | 142 | # exempt linked doctypes from being automatically cancelled 143 | # 144 | # auto_cancel_exempted_doctypes = ["Auto Repeat"] 145 | 146 | -------------------------------------------------------------------------------- /nextcloud_integration/nextcloud_integration/doctype/nextcloud_setting/nextcloud_setting.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2020, Frappe and contributors 3 | # For license information, please see license.txt 4 | 5 | import frappe 6 | from frappe.model.document import Document 7 | from frappe import _ 8 | from frappe.utils.background_jobs import enqueue 9 | from frappe.utils.backups import new_backup 10 | from frappe.integrations.offsite_backup_utils import send_email, validate_file_size 11 | 12 | import requests, os, datetime 13 | from rq.timeouts import JobTimeoutException 14 | from urllib.parse import urlparse 15 | 16 | class NextcloudSetting(Document): 17 | upload_path = None 18 | session = None 19 | failed_uploads, error_log = [], [] 20 | 21 | def start_taking_backup(self, retry_count=0, upload_db_backup=True): 22 | try: 23 | if self.enabled: 24 | validate_file_size() 25 | self.backup_to_nextcloud(upload_db_backup) 26 | if self.error_log: 27 | raise Exception 28 | if self.send_email_for_successful_backup: 29 | send_email(True, "Nextcloud", "Nextcloud Setting", "send_notifications_to") 30 | except JobTimeoutException: 31 | if retry_count < 2: 32 | args = { 33 | "retry_count": retry_count + 1, 34 | "upload_db_backup": False #considering till worker timeout db backup is uploaded 35 | } 36 | enqueue(self.start_taking_backup, queue='long', timeout=1500, **args) 37 | except Exception: 38 | if isinstance(self.error_log, str): 39 | error_message = self.error_log + "\n" + frappe.get_traceback() 40 | else: 41 | file_and_error = [" - ".join(f) for f in zip(self.failed_uploads if self.failed_uploads else '', list(set(self.error_log)))] 42 | error_message = ("\n".join(file_and_error) + "\n" + frappe.get_traceback()) 43 | send_email(False, "Nextcloud", "Nextcloud Setting", "send_notifications_to", error_message) 44 | 45 | def backup_to_nextcloud(self, upload_db_backup=True,): 46 | if not frappe.db: 47 | frappe.connect() 48 | if upload_db_backup: 49 | base_url = self.make_baseurl() 50 | if not base_url: 51 | self.error_log.append(_('Nextcloud URL incorrect')) 52 | return 53 | self.make_upload_path(base_url) 54 | self.make_session() 55 | 56 | # check if folder exist 57 | self.check_for_upload_folder() 58 | self.process_uploading() 59 | 60 | 61 | def make_upload_path(self, base_url): 62 | '''This function checks if path is provided and depending on it makes an upload path''' 63 | if self.path_to_upload_folder: 64 | self.upload_path = '{0}{1}'.format(base_url, self.path_to_upload_folder) 65 | else: 66 | self.upload_path = '{0}{1}'.format(base_url, 'Frappe Backups') 67 | 68 | def process_uploading(self): 69 | db_backup, site_config, public_file_backup, private_file_backup = self.prepare_backup() 70 | 71 | db_response = self.upload_backup(db_backup) 72 | if db_response == 'Failed': 73 | self.failed_uploads.append(db_backup) 74 | self.error_log.append(_('Failed while uploading DB')) 75 | 76 | site_config_response = self.upload_backup(site_config) 77 | if site_config_response == 'Failed': 78 | self.failed_uploads.append(site_config) 79 | self.error_log.append(_('Failed while uploading Site Config')) 80 | 81 | # file backup 82 | if self.backup_files and db_response != 'Failed' and site_config_response != 'Failed': 83 | self.file_upload(public_file_backup, private_file_backup) 84 | 85 | def file_upload(self, public_file_backup, private_file_backup): 86 | if public_file_backup: 87 | response_public_file = self.upload_backup(public_file_backup) 88 | if response_public_file == 'Failed': 89 | self.failed_uploads.append(public_file_backup) 90 | self.error_log.append(_('Failed while uploading Public files')) 91 | if private_file_backup: 92 | response_private_file = self.upload_backup(private_file_backup) 93 | if response_private_file == 'Failed': 94 | self.failed_uploads.append(private_file_backup) 95 | self.error_log.append(_('Failed while uploading Private files')) 96 | 97 | def prepare_backup(self): 98 | odb = new_backup(ignore_files=False if self.backup_files else True, force=frappe.flags.create_new_backup) 99 | database, public, private, config = odb.get_recent_backup(older_than=24 * 30) 100 | return database, config, public, private 101 | 102 | def make_session(self): 103 | session = requests.session() 104 | session.verify = True 105 | session.stream = True 106 | session.auth = (self.nextcloud_username, 107 | self.get_password(fieldname='password',raise_exception=False)) 108 | session.headers.update({ 109 | "OCS-APIRequest": "true", 110 | }) 111 | self.session = session 112 | 113 | def make_baseurl(self): 114 | vurl = urlparse(self.nextcloud_url) 115 | if not vurl.scheme: 116 | return None 117 | if not vurl.netloc: 118 | return None 119 | if not vurl.port: 120 | port = 443 if vurl.scheme == 'https' else 80 121 | else: 122 | port_url = vurl.netloc 123 | nc_url = port_url.replace(":" + str(vurl.port),"") 124 | 125 | base_url = '{0}://{1}:{2}'.format(vurl.scheme, vurl.netloc if not vurl.port else nc_url, vurl.port if vurl.port else port) 126 | if self.webdav_url.startswith('/'): 127 | base_url = '{0}{1}'.format(base_url, self.webdav_url) 128 | else: 129 | base_url = '{0}/{1}'.format(base_url, self.webdav_url) 130 | if not base_url.endswith('/'): 131 | base_url = '{0}/'.format(base_url) 132 | return base_url 133 | 134 | def check_for_upload_folder(self): 135 | '''If a path is provide in Nextcloud Setting, this function checks if that path exist. 136 | If no path is provided, this function will create a folder called "Frappe Backups" for the user.''' 137 | response = self.session.request("PROPFIND", self.upload_path, headers={"Depth": "0"}, allow_redirects=False) 138 | if response.status_code == 404: 139 | if self.path_to_upload_folder: 140 | frappe.throw(_('Given "Path to upload folder" does not exist')) 141 | else: 142 | response = self.session.request("MKCOL", self.upload_path, allow_redirects=False) 143 | if not response.ok: 144 | frappe.throw(_('There was an error. Please try again')) 145 | 146 | def upload_backup(self, filebackup): 147 | if not os.path.exists(filebackup): 148 | return 149 | local_fileobj = filebackup 150 | fileobj = local_fileobj.split('/') 151 | dir_length = len(fileobj) - 1 152 | remote_fileobj=str(datetime.datetime.today().weekday()) + fileobj[dir_length].encode("ascii", "ignore").decode("ascii")[15:] 153 | if self.upload_path.endswith('/'): 154 | url = '{0}{1}'.format(self.upload_path, remote_fileobj) 155 | else: 156 | url = '{0}/{1}'.format(self.upload_path, remote_fileobj) 157 | if isinstance(filebackup, str): 158 | try: 159 | with open(filebackup, 'rb') as f: 160 | response = self.session.request("PUT", url, allow_redirects=False, data=f) 161 | except Exception as e: 162 | return "Failed" 163 | else: 164 | try: 165 | response = self.session.request("PUT", url, allow_redirects=False, data=filebackup) 166 | except Exception as e: 167 | return "Failed" 168 | if response.status_code not in (201, 204): 169 | return "Failed" 170 | else: 171 | return "Success" 172 | 173 | @frappe.whitelist() 174 | def take_backup(): 175 | """Enqueue longjob for taking backup to nextcloud""" 176 | enqueue("nextcloud_integration.nextcloud_integration.doctype.nextcloud_setting.nextcloud_setting.start_backup", queue='long', timeout=1500) 177 | frappe.msgprint(_("Queued for backup. It may take from a few minutes upto 30 minutes.")) 178 | 179 | def daily_backup(): 180 | take_backups_if("Daily") 181 | 182 | def weekly_backup(): 183 | take_backups_if("Weekly") 184 | 185 | def take_backups_if(freq): 186 | if frappe.db.get_single_value("Nextcloud Setting", "backup_frequency") == freq: 187 | start_backup() 188 | 189 | def start_backup(): 190 | backup = frappe.get_doc("Nextcloud Setting") 191 | backup.start_taking_backup() 192 | 193 | --------------------------------------------------------------------------------