dav/sync.c

changeset 503
f84e64afee61
parent 502
a23fedac340c
child 504
bf3695fee719
equal deleted inserted replaced
502:a23fedac340c 503:f84e64afee61
523 523
524 int sync_success = 0; 524 int sync_success = 0;
525 int sync_delete = 0; 525 int sync_delete = 0;
526 int sync_error = 0; 526 int sync_error = 0;
527 527
528 UcxList *ls_get = NULL; 528 UcxList *res_modified = NULL;
529 UcxList *ls_remove = NULL; 529 UcxList *res_new = NULL;
530 UcxList *ls_conflict = NULL; 530 UcxList *res_conflict = NULL;
531 UcxList *ls_broken = NULL; 531 UcxList *res_mkdir = NULL;
532 UcxList *res_metadata = NULL;
533 UcxList *res_broken = NULL;
534 UcxList *lres_removed = NULL; // list of LocalResource*
532 535
533 UcxMap *svrres = ucx_map_new(db->resources->count); 536 UcxMap *svrres = ucx_map_new(db->resources->count);
537 UcxMap *dbres = ucx_map_clone(db->resources, NULL, NULL);
534 538
535 UcxList *statls = NULL; 539 UcxList *statls = NULL;
536 540
537 UcxList *stack = ucx_list_prepend(NULL, ls->children); 541 UcxList *stack = ucx_list_prepend(NULL, ls->children);
538 while(stack) { 542 while(stack) {
569 573
570 char *status = dav_get_string_property(res, "idav:status"); 574 char *status = dav_get_string_property(res, "idav:status");
571 if(status && !strcmp(status, "broken")) { 575 if(status && !strcmp(status, "broken")) {
572 res = res->next; 576 res = res->next;
573 localres_keep(db, res->path); 577 localres_keep(db, res->path);
574 ls_broken = ucx_list_append(ls_broken, res); 578 res_broken = ucx_list_append(res_broken, res);
575 continue; 579 continue;
576 } 580 }
577 581
578 // download the resource 582 // check if a resource has changed on the server
579 if(!sync_shutdown && sync_get_resource(a, dir, res, db, FALSE, &sync_success)) { 583 int change = resource_get_remote_change(a, res, dir, db);
580 fprintf(stderr, "sync_get_resource failed for resource: %s\n", res->path); 584 switch(change) {
581 sync_error++; 585 case REMOTE_NO_CHANGE: break;
586 case REMOTE_CHANGE_MODIFIED: {
587 res_modified = ucx_list_append(res_modified, res);
588 break;
589 }
590 case REMOTE_CHANGE_NEW: {
591 res_new = ucx_list_append(res_new, res);
592 break;
593 }
594 case REMOTE_CHANGE_DELETED: break; // never happens
595 case REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED: {
596 res_conflict = ucx_list_append(res_conflict, res);
597 break;
598 }
599 case REMOTE_CHANGE_METADATA: {
600 res_metadata = ucx_list_append(res_metadata, res);
601 break;
602 }
603 case REMOTE_CHANGE_MKDIR: {
604 res_mkdir = ucx_list_append(res_mkdir, res);
605 break;
606 }
582 } 607 }
583 608
584 // add every resource from the server to svrres 609 // remove every server resource from dbres
585 // then db-resources contains only resources which are not on the 610 // all remaining elements are the resources that are removed
586 // server 611 // on the server
587 LocalResource *local = ucx_map_cstr_remove(db->resources, res->path); 612 ucx_map_cstr_remove(dbres, res->path);
588 if(local) {
589 ucx_map_cstr_put(svrres, res->path, local);
590
591 if(local->last_modified == 0) {
592 // stat this file later (directory)
593 statls = ucx_list_prepend(statls, local);
594 }
595 } // else: sync_shutdown is TRUE
596 613
597 if(res->children) { 614 if(res->children) {
598 stack = ucx_list_prepend(stack, res->children); 615 stack = ucx_list_prepend(stack, res->children);
599 } 616 }
600 res = res->next; 617 res = res->next;
601 } 618 }
602 } 619 }
603 620
604 // stat all files with unknown lastmodified date 621 // find deleted resources
605 UCX_FOREACH(elm, statls) { 622 // svrres currently contains all resources from the server
606 LocalResource *l = elm->data; 623 // and will replace the current db->resources map later
607 char *local_path = util_concat_path(dir->path, l->path); 624 UcxMapIterator i = ucx_map_iterator(dbres);
608 SYS_STAT s;
609 if(!sys_stat(local_path, &s)) {
610 l->last_modified = s.st_mtime;
611 }
612 free(local_path);
613 }
614 ucx_list_free(statls);
615
616 // delete every remotely removed resource
617 UcxMapIterator i = ucx_map_iterator(db->resources);
618 LocalResource *local; 625 LocalResource *local;
619 UcxList *rmdirs = NULL;
620 UCX_MAP_FOREACH(key, local, i) { 626 UCX_MAP_FOREACH(key, local, i) {
621 if (res_matches_filter(dir, local->path)) { 627 if (res_matches_filter(dir, local->path)) {
622 continue; 628 continue;
623 } 629 }
624 630 if(!local->keep) {
625 if(sync_shutdown || local->keep) { 631 lres_removed = ucx_list_prepend(lres_removed, local);
626 ucx_map_cstr_put(svrres, local->path, local_resource_copy(local)); 632 }
633 }
634
635 // the first thing we need are all directories to put the files in
636 UCX_FOREACH(elm, res_mkdir) {
637 DavResource *res = elm->data;
638 char *local_path = util_concat_path(dir->path, res->path);
639 if(sys_mkdir(local_path) && errno != EEXIST) {
640 fprintf(stderr,
641 "Cannot create directory %s: %s",
642 local_path, strerror(errno));
643 }
644 free(local_path);
645 }
646
647 // we need a map for all conflicts for fast lookups
648 UcxMap *conflicts = ucx_map_new(ucx_list_size(res_conflict)+16);
649 UCX_FOREACH(elm, res_conflict) {
650 DavResource *res = elm->data;
651 ucx_map_cstr_put(conflicts, res->path, res);
652 }
653
654 // download all new, modified and conflict files
655 UcxList *download = ucx_list_concat(res_modified, res_conflict);
656 download = ucx_list_concat(res_new, download);
657 UCX_FOREACH(elm, download) {
658 DavResource *res = elm->data;
659 if(sync_shutdown) {
660 break;
661 }
662
663 if(ucx_map_cstr_get(conflicts, res->path)) {
664 rename_conflict_file(dir, db, res->path);
665 }
666
667 // download the resource
668 if(sync_get_resource(a, dir, res, db, &sync_success)) {
669 fprintf(stderr, "resource download failed: %s\n", res->path);
670 sync_error++;
671 }
672 }
673
674 UCX_FOREACH(elm, res_metadata) {
675 DavResource *res = elm->data;
676 if(sync_shutdown) {
677 break;
678 }
679
680 LocalResource *local = ucx_map_cstr_get(db->resources, res->path);
681 if(local) {
682 char *local_path = util_concat_path(dir->path, res->path);
683 if(sync_store_tags(dir, local_path, local, res)) {
684 fprintf(stderr, "Tag update failed: %s\n", res->path);
685 }
686 free(local_path);
627 } else { 687 } else {
628 // sync_remove_resource does all necessary tests 688 // this should never happen but who knows
629 int ret = sync_remove_local_resource(dir, local); 689 fprintf(stderr,
630 if(ret == -1) { 690 "Cannot update metadata of file %s: not in database\n",
631 rmdirs = ucx_list_append(rmdirs, local); 691 res->path);
632 } else if(ret == 0) { 692 }
633 sync_delete++; 693 }
634 } 694
635 } 695 UcxList *rmdirs = NULL;
636 } 696 UCX_FOREACH(elm, lres_removed) {
697 LocalResource *res = elm->data;
698 if(sync_shutdown) {
699 break;
700 }
701
702 int ret = sync_remove_local_resource(dir, res);
703 if(ret == -1) {
704 rmdirs = ucx_list_append(rmdirs, res);
705 } else if(ret == 0) {
706 LocalResource *local = ucx_map_cstr_remove(db->resources, res->path);
707 if(local) {
708 local_resource_free(local);
709 }
710 sync_delete++;
711 }
712 }
713
637 UCX_FOREACH(elm, rmdirs) { 714 UCX_FOREACH(elm, rmdirs) {
638 LocalResource *local_dir = elm->data; 715 LocalResource *local_dir = elm->data;
639 sync_remove_local_directory(dir, local_dir); 716 if(!sync_remove_local_directory(dir, local_dir)) {
640 } 717 LocalResource *local = ucx_map_cstr_remove(db->resources, local_dir->path);
641 ucx_map_free_content(db->resources, (ucx_destructor)local_resource_free); 718 if(local) {
642 ucx_map_free(db->resources); 719 local_resource_free(local);
643 db->resources = svrres; 720 }
721 sync_delete++;
722 }
723 }
644 724
645 // unlock repository 725 // unlock repository
646 if(locked) { 726 if(locked) {
647 if(dav_unlock(root)) { 727 if(dav_unlock(root)) {
648 print_resource_error(sn, "/"); 728 print_resource_error(sn, "/");
677 } 757 }
678 758
679 return ret; 759 return ret;
680 } 760 }
681 761
682 int sync_remote_is_changed( 762
763 RemoteChangeType resource_get_remote_change(
683 CmdArgs *a, 764 CmdArgs *a,
765 DavResource *res,
684 SyncDirectory *dir, 766 SyncDirectory *dir,
685 DavResource *res, 767 SyncDatabase *db)
686 SyncDatabase *db,
687 DavBool force,
688 DavBool *conflict,
689 DavBool *metadataupdate)
690 { 768 {
691 int cdt = cmd_getoption(a, "conflict") ? 0 : 1; // conflict detection 769 char *etag = dav_get_string_property(res, "D:getetag");
692 *conflict = 0; 770 if(!etag) {
693 *metadataupdate = 0; 771 fprintf(stderr, "Error: resource %s has no etag\n", res->path);
772 return REMOTE_NO_CHANGE;
773 }
774
775 RemoteChangeType type = cmd_getoption(a, "conflict") ?
776 REMOTE_CHANGE_MODIFIED : REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED;
694 777
695 LocalResource *local = ucx_map_cstr_get(db->resources, res->path); 778 LocalResource *local = ucx_map_cstr_get(db->resources, res->path);
696 char *local_path = util_concat_path(dir->path, res->path); 779 char *local_path = util_concat_path(dir->path, res->path);
697 780
698 char *etag = dav_get_string_property(res, "D:getetag");
699 SYS_STAT s; 781 SYS_STAT s;
700 int ret = 0; 782 DavBool exists = 1;
701 783 if(sys_stat(local_path, &s)) {
784 if(errno != ENOENT) {
785 fprintf(stderr, "Cannot stat file: %s\n", local_path);
786 free(local_path);
787 return REMOTE_NO_CHANGE;
788 }
789 exists = 0;
790 }
791
792 RemoteChangeType ret = REMOTE_NO_CHANGE;
793 if(res->iscollection) {
794 if(!exists) {
795 ret = REMOTE_CHANGE_MKDIR;
796 }
797 } else if(local) {
798 DavBool nochange = FALSE;
799 if(local->etag) {
800 sstr_t e = sstr(etag);
801 if(sstrprefix(e, S("W/"))) {
802 e = sstrsubs(e, 2);
803 }
804 if(!strcmp(e.ptr, local->etag)) {
805 // resource is already up-to-date on the client
806
807 // TODO: detect metadata update
808 //sync_store_tags(dir, local_path, local, res);
809 nochange = TRUE;
810 }
811 }
812
813 if(!nochange) {
814 if(!(exists && s.st_mtime != local->last_modified)) {
815 type = REMOTE_CHANGE_MODIFIED;
816 }
817 ret = type;
818 }
819 } else if(exists) {
820 ret = type;
821 } else {
822 ret = REMOTE_CHANGE_NEW;
823 }
702 824
703 free(local_path); 825 free(local_path);
704 return ret; 826 return ret;
705 } 827 }
706 828
707 int sync_get_resource( 829 int sync_get_resource(
708 CmdArgs *a, 830 CmdArgs *a,
709 SyncDirectory *dir, 831 SyncDirectory *dir,
710 DavResource *res, 832 DavResource *res,
711 SyncDatabase *db, 833 SyncDatabase *db,
712 DavBool force,
713 int *counter) 834 int *counter)
714 { 835 {
715 int cdt = cmd_getoption(a, "conflict") ? 0 : 1; // conflict detection
716
717 LocalResource *local = ucx_map_cstr_get(db->resources, res->path); 836 LocalResource *local = ucx_map_cstr_get(db->resources, res->path);
718 char *local_path = util_concat_path(dir->path, res->path); 837 char *local_path = util_concat_path(dir->path, res->path);
719 838
720 char *etag = dav_get_string_property(res, "D:getetag"); 839 char *etag = dav_get_string_property(res, "D:getetag");
721 SYS_STAT s; 840 SYS_STAT s;
722 memset(&s, 0, sizeof(SYS_STAT)); 841 memset(&s, 0, sizeof(SYS_STAT));
723 if(!force) {
724 if(local && !res->iscollection) {
725 int exists = 1;
726 if(sys_stat(local_path, &s)) {
727 // Ignore the fact, that the file is locally removed. If the
728 // server has an updated version, we read the file or the
729 // next push will delete it on the server.
730 if(errno != ENOENT) {
731 fprintf(stderr, "Cannot stat file: %s\n", local_path);
732 free(local_path);
733 return -1;
734 } else {
735 exists = 0;
736 }
737 }
738
739 if(local->etag) {
740 sstr_t e = sstr(etag);
741 if(sstrprefix(e, S("W/"))) {
742 e = sstrsubs(e, 2);
743 }
744 if(!strcmp(e.ptr, local->etag)) {
745 // resource is already up-to-date on the client
746 sync_store_tags(dir, local_path, local, res);
747 free(local_path);
748 return 0;
749 }
750 }
751
752 if(cdt && exists && s.st_mtime != local->last_modified) {
753 // file modified on the server and on the client
754 rename_conflict_file(dir, db, local->path);
755 }
756 } else {
757 if(sys_stat(local_path, &s)) {
758 if(errno != ENOENT) {
759 fprintf(stderr, "Cannot stat file: %s\n", local_path);
760 }
761 } else if(S_ISDIR(s.st_mode)) {
762 //fprintf(stderr, "Error: file %s is a directory\n", local_path);
763 } else if(cdt) {
764 // rename file on conflict
765 rename_conflict_file(dir, db, res->path);
766 }
767 }
768 }
769 842
770 int ret = 0; 843 int ret = 0;
771 char *tmp_path = create_tmp_download_path(local_path); 844 char *tmp_path = create_tmp_download_path(local_path);
772 if(res->iscollection) { 845
773 if(sys_mkdir(local_path) && errno != EEXIST) { 846 if(!tmp_path) {
774 ret = -1; 847 fprintf(stderr, "Cannot create tmp path for %s\n", local_path);
775 } 848 free(local_path);
776 849 return -1;
777 if(ret == 0) { 850 }
778 if(!local) { 851 FILE *out = sys_fopen(tmp_path, "wb");
779 // new local resource 852 if(!out) {
780 local = calloc(1, sizeof(LocalResource)); 853 fprintf(stderr, "Cannot open output file: %s\n", local_path);
781 local->path = util_concat_path(res->path, "/"); 854 free(local_path);
782 local->last_modified = 0; 855 free(tmp_path);
783 if(local->etag) { 856 return -1;
784 free(local->etag); 857 }
785 } 858 printf("get: %s\n", res->path);
786 local->etag = strdup(etag); 859 if(dav_get_content(res, out, (dav_write_func)fwrite)) {
787 ucx_map_cstr_put(db->resources, local->path, local); 860 ret = -1;
788 } 861 }
789 862 fclose(out);
790 sync_store_tags(dir, local_path, local, res); 863
791 } 864 if(ret == 0) {
792 } else { 865 (*counter)++;
793 if(!tmp_path) { 866
794 fprintf(stderr, "Cannot create tmp path for %s\n", local_path); 867 if(dir->trash && dir->backuppull) {
868 move_to_trash(dir, local_path);
869 }
870 if(sys_rename(tmp_path, local_path)) {
871 fprintf(
872 stderr,
873 "Cannot rename file %s to %s\n",
874 tmp_path,
875 local_path);
876 perror("");
877 free(tmp_path);
795 free(local_path); 878 free(local_path);
796 return -1; 879 return -1;
797 } 880 }
798 FILE *out = sys_fopen(tmp_path, "wb"); 881
799 if(!out) { 882 if(sys_stat(local_path, &s)) {
800 fprintf(stderr, "Cannot open output file: %s\n", local_path); 883 fprintf(stderr,
801 free(local_path); 884 "Cannot stat file %s: %s\n", local_path, strerror(errno));
802 free(tmp_path); 885 }
803 return -1; 886
804 } 887 if(!local) {
805 printf("get: %s\n", res->path); 888 // new local resource
806 if(dav_get_content(res, out, (dav_write_func)fwrite)) { 889 local = calloc(1, sizeof(LocalResource));
807 ret = -1; 890 local->path = strdup(res->path);
808 } 891 ucx_map_cstr_put(db->resources, local->path, local);
809 fclose(out); 892 }
893
894 if(local->etag) {
895 free(local->etag);
896 }
897 // set metadata from stat
898 local->etag = strdup(etag);
899 local->last_modified = s.st_mtime;
900 local->size = s.st_size;
901 local->skipped = FALSE;
810 902
811 sync_store_tags(dir, tmp_path, local, res); 903 sync_store_tags(dir, tmp_path, local, res);
812 904 } else {
813 if(ret == 0) { 905 if(sys_unlink(tmp_path)) {
814 (*counter)++; 906 fprintf(stderr, "Cannot remove tmp file: %s\n", tmp_path);
815
816 if(dir->trash && dir->backuppull) {
817 move_to_trash(dir, local_path);
818 }
819 if(sys_rename(tmp_path, local_path)) {
820 fprintf(
821 stderr,
822 "Cannot rename file %s to %s\n",
823 tmp_path,
824 local_path);
825 perror("");
826 free(tmp_path);
827 free(local_path);
828 return -1;
829 }
830
831 if(sys_stat(local_path, &s)) {
832 fprintf(stderr, "Cannot stat file: %s\n", local_path);
833 perror("");
834 }
835
836 if(!local) {
837 // new local resource
838 local = calloc(1, sizeof(LocalResource));
839 local->path = strdup(res->path);
840 ucx_map_cstr_put(db->resources, local->path, local);
841 }
842
843 if(local->etag) {
844 free(local->etag);
845 }
846 // set metadata from stat
847 local->etag = strdup(etag);
848 local->last_modified = s.st_mtime;
849 local->size = s.st_size;
850 local->skipped = FALSE;
851 } else {
852 if(sys_unlink(tmp_path)) {
853 fprintf(stderr, "Cannot remove tmp file: %s\n", tmp_path);
854 }
855 } 907 }
856 } 908 }
857 909
858 free(tmp_path); 910 free(tmp_path);
859 free(local_path); 911 free(local_path);
889 free(local_path); 941 free(local_path);
890 942
891 return ret; 943 return ret;
892 } 944 }
893 945
894 void sync_remove_local_directory(SyncDirectory *dir, LocalResource *res) { 946 int sync_remove_local_directory(SyncDirectory *dir, LocalResource *res) {
947 int ret = 0;
895 char *local_path = util_concat_path(dir->path, res->path); 948 char *local_path = util_concat_path(dir->path, res->path);
896 949
897 printf("delete: %s\n", res->path); 950 printf("delete: %s\n", res->path);
898 if(rmdir(local_path)) { 951 if(rmdir(local_path)) {
899 fprintf(stderr, "rmdir: %s : ", local_path); 952 fprintf(stderr, "rmdir: %s : %s", local_path, strerror(errno));
900 perror(NULL); 953 ret = 1;
901 } 954 }
902 955
903 free(local_path); 956 free(local_path);
957 return ret;
904 } 958 }
905 959
906 void rename_conflict_file(SyncDirectory *dir, SyncDatabase *db, char *path) { 960 void rename_conflict_file(SyncDirectory *dir, SyncDatabase *db, char *path) {
907 char *local_path = util_concat_path(dir->path, path); 961 char *local_path = util_concat_path(dir->path, path);
908 char *parent = util_parent_path(local_path); 962 char *parent = util_parent_path(local_path);
1472 if(status && !strcmp(status, "broken")) { 1526 if(status && !strcmp(status, "broken")) {
1473 continue; 1527 continue;
1474 } 1528 }
1475 1529
1476 // download the resource 1530 // download the resource
1477 if(!sync_shutdown && sync_get_resource(a, dir, res, db, TRUE, &sync_success)) { 1531 if(!sync_shutdown && sync_get_resource(a, dir, res, db, &sync_success)) {
1478 fprintf(stderr, "sync_get_resource failed for resource: %s\n", res->path); 1532 fprintf(stderr, "sync_get_resource failed for resource: %s\n", res->path);
1479 sync_error++; 1533 sync_error++;
1480 } 1534 }
1481 } 1535 }
1482 1536

mercurial