mirror of
				https://github.com/dgtlmoon/changedetection.io.git
				synced 2025-11-04 00:27:48 +00:00 
			
		
		
		
	Compare commits
	
		
			9 Commits
		
	
	
		
			restock-ta
			...
			history-tx
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					cb7b12ab9c | ||
| 
						 | 
					197a3e6366 | ||
| 
						 | 
					9c1b18ec22 | ||
| 
						 | 
					02b460768c | ||
| 
						 | 
					50edc36cc4 | ||
| 
						 | 
					3cf2a798cf | ||
| 
						 | 
					13daa92c92 | ||
| 
						 | 
					53f4472fb8 | ||
| 
						 | 
					a1bdf7e43f | 
@@ -987,9 +987,6 @@ def changedetection_app(config=None, datastore_o=None):
 | 
			
		||||
 | 
			
		||||
        # create a ZipFile object
 | 
			
		||||
        backupname = "changedetection-backup-{}.zip".format(int(time.time()))
 | 
			
		||||
 | 
			
		||||
        # We only care about UUIDS from the current index file
 | 
			
		||||
        uuids = list(datastore.data['watching'].keys())
 | 
			
		||||
        backup_filepath = os.path.join(datastore_o.datastore_path, backupname)
 | 
			
		||||
 | 
			
		||||
        with zipfile.ZipFile(backup_filepath, "w",
 | 
			
		||||
@@ -1005,12 +1002,12 @@ def changedetection_app(config=None, datastore_o=None):
 | 
			
		||||
            # Add the flask app secret
 | 
			
		||||
            zipObj.write(os.path.join(datastore_o.datastore_path, "secret.txt"), arcname="secret.txt")
 | 
			
		||||
 | 
			
		||||
            # Add any snapshot data we find, use the full path to access the file, but make the file 'relative' in the Zip.
 | 
			
		||||
            for txt_file_path in Path(datastore_o.datastore_path).rglob('*.txt'):
 | 
			
		||||
                parent_p = txt_file_path.parent
 | 
			
		||||
                if parent_p.name in uuids:
 | 
			
		||||
                    zipObj.write(txt_file_path,
 | 
			
		||||
                                 arcname=str(txt_file_path).replace(datastore_o.datastore_path, ''),
 | 
			
		||||
            # Add any data in the watch data directory.
 | 
			
		||||
            for uuid, w in datastore.data['watching'].items():
 | 
			
		||||
                for f in Path(w.watch_data_dir).glob('*'):
 | 
			
		||||
                    zipObj.write(f,
 | 
			
		||||
                                 # Use the full path to access the file, but make the file 'relative' in the Zip.
 | 
			
		||||
                                 arcname=os.path.join(f.parts[-2], f.parts[-1]),
 | 
			
		||||
                                 compress_type=zipfile.ZIP_DEFLATED,
 | 
			
		||||
                                 compresslevel=8)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								changedetectionio/download.zip
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								changedetectionio/download.zip
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -1,6 +1,8 @@
 | 
			
		||||
import os
 | 
			
		||||
import uuid as uuid_builder
 | 
			
		||||
from distutils.util import strtobool
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
import time
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
minimum_seconds_recheck_time = int(os.getenv('MINIMUM_SECONDS_RECHECK_TIME', 60))
 | 
			
		||||
mtable = {'seconds': 1, 'minutes': 60, 'hours': 3600, 'days': 86400, 'weeks': 86400 * 7}
 | 
			
		||||
@@ -22,7 +24,7 @@ class model(dict):
 | 
			
		||||
            #'newest_history_key': 0,
 | 
			
		||||
            'title': None,
 | 
			
		||||
            'previous_md5': False,
 | 
			
		||||
            'uuid': str(uuid_builder.uuid4()),
 | 
			
		||||
            'uuid': str(uuid.uuid4()),
 | 
			
		||||
            'headers': {},  # Extra headers to send
 | 
			
		||||
            'body': None,
 | 
			
		||||
            'method': 'GET',
 | 
			
		||||
@@ -60,7 +62,7 @@ class model(dict):
 | 
			
		||||
        self.update(self.__base_config)
 | 
			
		||||
        self.__datastore_path = kw['datastore_path']
 | 
			
		||||
 | 
			
		||||
        self['uuid'] = str(uuid_builder.uuid4())
 | 
			
		||||
        self['uuid'] = str(uuid.uuid4())
 | 
			
		||||
 | 
			
		||||
        del kw['datastore_path']
 | 
			
		||||
 | 
			
		||||
@@ -82,10 +84,9 @@ class model(dict):
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def ensure_data_dir_exists(self):
 | 
			
		||||
        target_path = os.path.join(self.__datastore_path, self['uuid'])
 | 
			
		||||
        if not os.path.isdir(target_path):
 | 
			
		||||
            print ("> Creating data dir {}".format(target_path))
 | 
			
		||||
            os.mkdir(target_path)
 | 
			
		||||
        if not os.path.isdir(self.watch_data_dir):
 | 
			
		||||
            print ("> Creating data dir {}".format(self.watch_data_dir))
 | 
			
		||||
            os.mkdir(self.watch_data_dir)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def label(self):
 | 
			
		||||
@@ -109,18 +110,39 @@ class model(dict):
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def history(self):
 | 
			
		||||
        """History index is just a text file as a list
 | 
			
		||||
            {watch-uuid}/history.txt
 | 
			
		||||
 | 
			
		||||
            contains a list like
 | 
			
		||||
 | 
			
		||||
            {epoch-time},{filename}\n
 | 
			
		||||
 | 
			
		||||
            We read in this list as the history information
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        tmp_history = {}
 | 
			
		||||
        import logging
 | 
			
		||||
        import time
 | 
			
		||||
 | 
			
		||||
        # Read the history file as a dict
 | 
			
		||||
        fname = os.path.join(self.__datastore_path, self.get('uuid'), "history.txt")
 | 
			
		||||
        fname = os.path.join(self.watch_data_dir, "history.txt")
 | 
			
		||||
        if os.path.isfile(fname):
 | 
			
		||||
            logging.debug("Reading history index " + str(time.time()))
 | 
			
		||||
            with open(fname, "r") as f:
 | 
			
		||||
                for i in f.readlines():
 | 
			
		||||
                    if ',' in i:
 | 
			
		||||
                        k, v = i.strip().split(',', 2)
 | 
			
		||||
 | 
			
		||||
                        # The index history could contain a relative path, so we need to make the fullpath
 | 
			
		||||
                        # so that python can read it
 | 
			
		||||
                        if not '/' in v and not '\'' in v:
 | 
			
		||||
                            v = os.path.join(self.watch_data_dir, v)
 | 
			
		||||
                        else:
 | 
			
		||||
                            # It's possible that they moved the datadir on older versions
 | 
			
		||||
                            # So the snapshot exists but is in a different path
 | 
			
		||||
                            snapshot_fname = v.split('/')[-1]
 | 
			
		||||
                            proposed_new_path = os.path.join(self.watch_data_dir, snapshot_fname)
 | 
			
		||||
                            if not os.path.exists(v) and os.path.exists(proposed_new_path):
 | 
			
		||||
                                v = proposed_new_path
 | 
			
		||||
 | 
			
		||||
                        tmp_history[k] = v
 | 
			
		||||
 | 
			
		||||
        if len(tmp_history):
 | 
			
		||||
@@ -132,7 +154,7 @@ class model(dict):
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def has_history(self):
 | 
			
		||||
        fname = os.path.join(self.__datastore_path, self.get('uuid'), "history.txt")
 | 
			
		||||
        fname = os.path.join(self.watch_data_dir, "history.txt")
 | 
			
		||||
        return os.path.isfile(fname)
 | 
			
		||||
 | 
			
		||||
    # Returns the newest key, but if theres only 1 record, then it's counted as not being new, so return 0.
 | 
			
		||||
@@ -151,25 +173,19 @@ class model(dict):
 | 
			
		||||
    # Save some text file to the appropriate path and bump the history
 | 
			
		||||
    # result_obj from fetch_site_status.run()
 | 
			
		||||
    def save_history_text(self, contents, timestamp):
 | 
			
		||||
        import uuid
 | 
			
		||||
        import logging
 | 
			
		||||
 | 
			
		||||
        output_path = os.path.join(self.__datastore_path, self['uuid'])
 | 
			
		||||
 | 
			
		||||
        self.ensure_data_dir_exists()
 | 
			
		||||
        snapshot_fname = os.path.join(output_path, str(uuid.uuid4()))
 | 
			
		||||
 | 
			
		||||
        logging.debug("Saving history text {}".format(snapshot_fname))
 | 
			
		||||
        snapshot_fname = "{}.txt".format(str(uuid.uuid4()))
 | 
			
		||||
 | 
			
		||||
        # in /diff/ and /preview/ we are going to assume for now that it's UTF-8 when reading
 | 
			
		||||
        # most sites are utf-8 and some are even broken utf-8
 | 
			
		||||
        with open(snapshot_fname, 'wb') as f:
 | 
			
		||||
        with open(os.path.join(self.watch_data_dir, snapshot_fname), 'wb') as f:
 | 
			
		||||
            f.write(contents)
 | 
			
		||||
            f.close()
 | 
			
		||||
 | 
			
		||||
        # Append to index
 | 
			
		||||
        # @todo check last char was \n
 | 
			
		||||
        index_fname = os.path.join(output_path, "history.txt")
 | 
			
		||||
        index_fname = os.path.join(self.watch_data_dir, "history.txt")
 | 
			
		||||
        with open(index_fname, 'a') as f:
 | 
			
		||||
            f.write("{},{}\n".format(timestamp, snapshot_fname))
 | 
			
		||||
            f.close()
 | 
			
		||||
@@ -210,14 +226,14 @@ class model(dict):
 | 
			
		||||
        return not local_lines.issubset(existing_history)
 | 
			
		||||
 | 
			
		||||
    def get_screenshot(self):
 | 
			
		||||
        fname = os.path.join(self.__datastore_path, self['uuid'], "last-screenshot.png")
 | 
			
		||||
        fname = os.path.join(self.watch_data_dir, "last-screenshot.png")
 | 
			
		||||
        if os.path.isfile(fname):
 | 
			
		||||
            return fname
 | 
			
		||||
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def __get_file_ctime(self, filename):
 | 
			
		||||
        fname = os.path.join(self.__datastore_path, self['uuid'], filename)
 | 
			
		||||
        fname = os.path.join(self.watch_data_dir, filename)
 | 
			
		||||
        if os.path.isfile(fname):
 | 
			
		||||
            return int(os.path.getmtime(fname))
 | 
			
		||||
        return False
 | 
			
		||||
@@ -242,9 +258,14 @@ class model(dict):
 | 
			
		||||
    def snapshot_error_screenshot_ctime(self):
 | 
			
		||||
        return self.__get_file_ctime('last-error-screenshot.png')
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def watch_data_dir(self):
 | 
			
		||||
        # The base dir of the watch data
 | 
			
		||||
        return os.path.join(self.__datastore_path, self['uuid'])
 | 
			
		||||
    
 | 
			
		||||
    def get_error_text(self):
 | 
			
		||||
        """Return the text saved from a previous request that resulted in a non-200 error"""
 | 
			
		||||
        fname = os.path.join(self.__datastore_path, self['uuid'], "last-error.txt")
 | 
			
		||||
        fname = os.path.join(self.watch_data_dir, "last-error.txt")
 | 
			
		||||
        if os.path.isfile(fname):
 | 
			
		||||
            with open(fname, 'r') as f:
 | 
			
		||||
                return f.read()
 | 
			
		||||
@@ -252,7 +273,7 @@ class model(dict):
 | 
			
		||||
 | 
			
		||||
    def get_error_snapshot(self):
 | 
			
		||||
        """Return path to the screenshot that resulted in a non-200 error"""
 | 
			
		||||
        fname = os.path.join(self.__datastore_path, self['uuid'], "last-error-screenshot.png")
 | 
			
		||||
        fname = os.path.join(self.watch_data_dir, "last-error-screenshot.png")
 | 
			
		||||
        if os.path.isfile(fname):
 | 
			
		||||
            return fname
 | 
			
		||||
        return False
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,31 @@
 | 
			
		||||
#!/usr/bin/python3
 | 
			
		||||
 | 
			
		||||
import time
 | 
			
		||||
from .util import set_original_response, set_modified_response, live_server_setup
 | 
			
		||||
from flask import url_for
 | 
			
		||||
from urllib.request import urlopen
 | 
			
		||||
from . util import set_original_response, set_modified_response, live_server_setup
 | 
			
		||||
from zipfile import ZipFile
 | 
			
		||||
import re
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_backup(client, live_server):
 | 
			
		||||
 | 
			
		||||
    live_server_setup(live_server)
 | 
			
		||||
 | 
			
		||||
    set_original_response()
 | 
			
		||||
 | 
			
		||||
    # Give the endpoint time to spin up
 | 
			
		||||
    time.sleep(1)
 | 
			
		||||
 | 
			
		||||
    # Add our URL to the import page
 | 
			
		||||
    res = client.post(
 | 
			
		||||
        url_for("import_page"),
 | 
			
		||||
        data={"urls": url_for('test_endpoint', _external=True)},
 | 
			
		||||
        follow_redirects=True
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    assert b"1 Imported" in res.data
 | 
			
		||||
    time.sleep(3)
 | 
			
		||||
 | 
			
		||||
    res = client.get(
 | 
			
		||||
        url_for("get_backup"),
 | 
			
		||||
        follow_redirects=True
 | 
			
		||||
@@ -20,6 +33,19 @@ def test_backup(client, live_server):
 | 
			
		||||
 | 
			
		||||
    # Should get the right zip content type
 | 
			
		||||
    assert res.content_type == "application/zip"
 | 
			
		||||
 | 
			
		||||
    # Should be PK/ZIP stream
 | 
			
		||||
    assert res.data.count(b'PK') >= 2
 | 
			
		||||
 | 
			
		||||
    # ZipFile from buffer seems non-obvious, just save it instead
 | 
			
		||||
    with open("download.zip", 'wb') as f:
 | 
			
		||||
        f.write(res.data)
 | 
			
		||||
 | 
			
		||||
    zip = ZipFile('download.zip')
 | 
			
		||||
    l = zip.namelist()
 | 
			
		||||
    uuid4hex = re.compile('^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}.*txt', re.I)
 | 
			
		||||
    newlist = list(filter(uuid4hex.match, l))  # Read Note below
 | 
			
		||||
 | 
			
		||||
    # Should be two txt files in the archive (history and the snapshot)
 | 
			
		||||
    assert len(newlist) == 2
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user