/* * CINELERRA * Copyright (C) 2008 Adam Williams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "bcsignals.h" #include "bctimer.h" #include "clip.h" #include "cstrdup.h" #include "undostack.h" #include UndoStack::UndoStack() : List() { current = 0; } UndoStack::~UndoStack() { } UndoStackItem* UndoStack::push() { // current is only 0 if before first undo if(current) current = insert_after(current); else current = insert_before(first); // delete future undos if necessary if(current && current->next) { while(current->next) remove(last); } // delete oldest 2 undos if necessary if(total() > UNDOLEVELS) { for(int i = 0; i < 2; i++) { UndoStackItem *second = first->next; char *temp_data = 0; if(!second->is_key()) { temp_data = second->get_data(); } remove(first); // Convert new first to key buffer. if(!second->is_key()) { second->set_data(temp_data); } delete [] temp_data; } } return current; } void UndoStack::pull() { if(current) current = PREVIOUS; } UndoStackItem* UndoStack::pull_next() { // use first entry if none if(!current) current = first; else // use next entry if there is a next entry if(current->next) current = NEXT; // don't change current if there is no next entry else return 0; return current; } void UndoStack::dump(FILE *fp) { fprintf(fp,"UndoStack::dump\n"); UndoStackItem *current = last; int i = 0; // Dump most recent while(current && i < 10) { fprintf(fp," %d %p %s %c\n", i++, current, current->get_description(), current == this->current ? '*' : ' '); current = PREVIOUS; } } // These difference routines are straight out of the Heroinediff/Heroinepatch // utilities. // We didn't want to use the diff program because that isn't efficient enough // to compress EDL's. This program also handles binary data. // Algorithm 1: // Search from beginning for first difference. // Count everything up to first difference as equal. // Determine length of different block by searching for transposition with most // similar characters. // For every byte in different block, search for occurrence of // transposed copy longer than a record header in remaining old buffer. // If no match is found, add 1 to the different block length and try the next // byte. Once the most similar match is found, store the changed data leading up to it in the // difference record. // This can be real slow if there is a large transposition early in the file. // Algorithm 2: // Search backwards from end of both files for last difference. // Count all data from last same bytes as a transposition. // Search forwards from start of both files until last same bytes // for differences. // Minimum size of transposed block. // Smaller transpositions should be included in the same difference record. #define MIN_TRANS 16 // Format for difference records. // The first 4 bytes are the offset of the change. // The next 4 bytes are the number of bytes of new data. // The next 4 bytes are the number of bytes of old data replaced. // The new data follows. static void append_record(unsigned char **result, int *result_size, unsigned char *new_data, int new_offset, int new_size, int old_size) { if(new_size || old_size) { int record_size = new_size + 12; unsigned char *new_result = new unsigned char[(*result_size) + record_size]; memcpy(new_result, (*result), (*result_size)); delete [] (*result); unsigned char *new_result_ptr = new_result + (*result_size); *(int32_t*)new_result_ptr = new_offset; new_result_ptr += 4; *(int32_t*)new_result_ptr = new_size; new_result_ptr += 4; *(int32_t*)new_result_ptr = old_size; new_result_ptr += 4; memcpy(new_result_ptr, new_data, new_size); (*result_size) += record_size; (*result) = new_result; } } static unsigned char* get_difference_fast(unsigned char *before, int before_len, unsigned char *after, int after_len, int *result_len, int verbose) { unsigned char *result = new unsigned char[4]; *result_len = 4; // Store size of new buffer *(int32_t*)result = after_len; // Get last different bytes unsigned char *last_difference_after = after + after_len - 1; unsigned char *last_difference_before = before + before_len - 1; while(1) { if(last_difference_after < after || last_difference_before < before) break; if(*last_difference_after != *last_difference_before) { break; } last_difference_after--; last_difference_before--; } last_difference_after++; last_difference_before++; int done = 0; unsigned char *before_ptr = before; unsigned char *after_ptr = after; unsigned char *before_end = before + before_len; unsigned char *after_end = after + after_len; // Scan forward for first difference while(!done) { if(before_ptr < before_end && after_ptr < after_end) { // Both characters equal if(*before_ptr == *after_ptr && before_ptr < last_difference_before && after_ptr < last_difference_after) { before_ptr++; after_ptr++; } else // Characters differ { // Get length of difference. unsigned char *before_difference_start = before_ptr; unsigned char *after_difference_start = after_ptr; while(*before_ptr != *after_ptr && before_ptr < last_difference_before && after_ptr < last_difference_after) { before_ptr++; after_ptr++; } // Finished comparing if either pointer hits its last difference if(before_ptr >= last_difference_before || after_ptr >= last_difference_after) { done = 1; before_ptr = last_difference_before; after_ptr = last_difference_after; } int after_start = after_difference_start - after; int before_start = before_difference_start - before; int after_len = after_ptr - after_difference_start; int before_len = before_ptr - before_difference_start; if(verbose) { char string[1024]; memcpy(string, after_difference_start, MIN(after_len, 40)); string[MIN(after_len, 40)] = 0; printf("after_offset=0x%x before_offset=0x%x after_size=%d before_size=%d \"%s\"\n", after_start, before_start, after_len, before_len, string); } // Create difference record append_record(&result, result_len, after_difference_start, after_start, after_len, before_len); } } else done = 1; } return result; } static void get_record(unsigned char **patch_ptr, unsigned char *patch_end, unsigned char **after_data, int *after_offset, int *after_size, int *before_size) { (*after_data) = 0; if((*patch_ptr) < patch_end) { (*after_offset) = *(int32_t*)(*patch_ptr); (*patch_ptr) += 4; (*after_size) = *(int32_t*)(*patch_ptr); (*patch_ptr) += 4; (*before_size) = *(int32_t*)(*patch_ptr); (*patch_ptr) += 4; (*after_data) = (*patch_ptr); (*patch_ptr) += (*after_size); } } static unsigned char* apply_difference(unsigned char *before, int before_len, unsigned char *patch, int patch_len, int *result_len) { unsigned char *patch_ptr = patch; *result_len = *(int32_t*)patch_ptr; patch_ptr += 4; unsigned char *patch_end = patch + patch_len; //unsigned char *before_end = before + before_len; unsigned char *result = new unsigned char[*result_len]; unsigned char *result_ptr = result; unsigned char *result_end = result + *result_len; unsigned char *before_ptr = before; int done = 0; while(!done) { unsigned char *after_data; int after_offset; int after_size; int before_size; get_record(&patch_ptr, patch_end, &after_data, &after_offset, &after_size, &before_size); if(after_data) { int result_offset = result_ptr - result; if(after_offset > result_offset) { int skip_size = after_offset - result_offset; memcpy(result_ptr, before_ptr, skip_size); result_ptr += skip_size; before_ptr += skip_size; } memcpy(result_ptr, after_data, after_size); result_ptr += after_size; before_ptr += before_size; } else { // All data from before_ptr to end of result buffer is identical if(result_end - result_ptr > 0) memcpy(result_ptr, before_ptr, result_end - result_ptr); done = 1; } } return result; } UndoStackItem::UndoStackItem() : ListItem() { description = data = 0; data_size = 0; key = 0; creator = 0; session_filename = 0; } UndoStackItem::~UndoStackItem() { delete [] description; delete [] data; delete [] session_filename; } void UndoStackItem::set_description(char *description) { delete [] this->description; this->description= 0; this->description = new char[strlen(description) + 1]; strcpy(this->description, description); } void UndoStackItem::set_filename(char *filename) { delete [] session_filename; this->session_filename = cstrdup(filename); } char* UndoStackItem::get_filename() { return session_filename; } const char* UndoStackItem::get_description() { if(description) return description; else return ""; } int UndoStackItem::has_data() { return data_size ? 1 : 0; } void UndoStackItem::set_data(char *data) { delete [] this->data; this->data = 0; this->data_size = 0; // Search for key buffer within interval int need_key = 1; UndoStackItem *current = this; this->key = 0; for(int i = 0; i < UNDO_KEY_INTERVAL && current; i++) { if(current->key && current->has_data()) { need_key = 0; break; } else current = PREVIOUS; } int new_size = strlen(data) + 1; if(!need_key) { // Reconstruct previous data for difference char *prev_buffer = previous->get_data(); int prev_size = prev_buffer ? strlen(prev_buffer) + 1 : 0; // Timer timer; // timer.update(); // printf("UndoStackItem::set_data 1\n"); this->data = (char*)get_difference_fast((unsigned char*)prev_buffer, prev_size, (unsigned char*)data, new_size, &this->data_size, 0); //printf("UndoStackItem::set_data 2 " _LD "\n", timer.get_difference()); // Testing // FILE *test1 = fopen("/tmp/undo1", "w"); // fwrite(prev_buffer, prev_size, 1, test1); // fclose(test1); // FILE *test2 = fopen("/tmp/undo2", "w"); // fwrite(data, new_size, 1, test2); // fclose(test2); // FILE *test3 = fopen("/tmp/undo2.diff", "w"); // fwrite(this->data, this->data_size, 1, test3); // fclose(test3); // //printf("UndoStackItem::set_data 3 %d %d\n", new_size, this->data_size); // Diff was bigger than original. // Happens if a lot of tiny changes happened and the record headers // took more space than the changes. if(this->data_size > new_size) { delete [] this->data; this->data_size = 0; need_key = 1; } else { // Reconstruct current data from difference int test_size; char *test_buffer = (char*)apply_difference((unsigned char*)prev_buffer, prev_size, (unsigned char*)this->data, this->data_size, &test_size); if(test_size != new_size || memcmp(test_buffer, data, test_size)) { // FILE *test1 = fopen("/tmp/undo1", "w"); // fwrite(prev_buffer, prev_size, 1, test1); // fclose(test1); // FILE *test2 = fopen("/tmp/undo2", "w"); // fwrite(data, new_size, 1, test2); // fclose(test2); // FILE *test3 = fopen("/tmp/undo2.diff", "w"); // fwrite(this->data, this->data_size, 1, test3); // fclose(test3); // FILE *test4 = fopen("/tmp/undo3", "w"); // fwrite(test_buffer, test_size, 1, test4); // fclose(test4); printf("UndoStackItem::set_data: incremental undo failed!\n"); need_key = 1; delete [] this->data; this->data_size = 0; this->data = 0; } delete [] test_buffer; } delete [] prev_buffer; } if(need_key) { this->key = 1; this->data_size = new_size; this->data = new char[this->data_size]; memcpy(this->data, data, this->data_size); } return; } char* UndoStackItem::get_incremental_data() { return data; } int UndoStackItem::get_incremental_size() { return data_size; } int UndoStackItem::get_size() { return data_size; } char* UndoStackItem::get_data() { // Find latest key buffer UndoStackItem *current = this; while(current && !current->key) current = PREVIOUS; if(!current) { printf("UndoStackItem::get_data: no key buffer found!\n"); return 0; } // This is the key buffer if(current == this) { char *result = new char[data_size]; memcpy(result, data, data_size); return result; } // Get key buffer char *current_data = current->get_data(); int current_size = current->get_size(); current = NEXT; while(current) { // Do incremental updates int new_size; char *new_data = (char*)apply_difference((unsigned char*)current_data, current_size, (unsigned char*)current->get_incremental_data(), current->get_incremental_size(), &new_size); delete [] current_data; if(current == this) return new_data; else { current_data = new_data; current_size = new_size; current = NEXT; } } // Never hit this object. delete [] current_data; printf("UndoStackItem::get_data: lost starting object!\n"); return 0; } int UndoStackItem::is_key() { return key; } void UndoStackItem::set_flags(uint64_t flags) { this->load_flags = flags; } uint64_t UndoStackItem::get_flags() { return load_flags; } void UndoStackItem::set_creator(void *creator) { this->creator = creator; } void* UndoStackItem::get_creator() { return creator; }