[
  {
    "path": ".gitignore",
    "content": "*~\n*.bak\n*.o\n*.exe\ncscope*\n*#\nMakefile.in\nltmain.sh\nlibtool\n*.lo\n*.la\ninstall-sh\ndepcomp\nconfig.guess\nconfig.h\nconfig.log\nconfig.status\nconfig.sub\nconfig.cache\nconfigure\n*/.deps\nautom4te*\npo/POTFILES\npo/Makefile*\npo/stamp-it\npo/*.gmo\npo/*.pot\nmissing\nmkinstalldirs\nstamp-h1\n*.libs/\nMakefile\naclocal.m4\n*core\nm4/intltool.m4\nm4/libtool.m4\nm4/ltoptions.m4\nm4/ltsugar.m4\nm4/ltversion.m4\nm4/lt~obsolete.m4\nccnet-*.tar.gz\nconfig.h.in\npy-compile\nintltool-extract.in\nintltool-merge.in\nintltool-update.in\n*.stamp\n*.pyc\n*.tmp.ui\n*.defs\n*.log\n.deps\n*.db\n*.dll\n*.aps\n*.so\nbuild-stamp\ndebian/files\ndebian/seafile\ndebian/*.substvars\nlib/*.tmp\nlib/dir.c\nlib/dirent.c\nlib/webaccess.c\nlib/branch.c\nlib/commit.c\nlib/crypt.c\nlib/copy-task.c\napp/seafile\napp/seafserv-tool\ndaemon/seaf-daemon\ngui/gtk/seafile-applet\nserver/seaf-server\nserver/gc/seafserv-gc\nmonitor/seaf-mon\ncontroller/seafile-controller\nfileserver/fileserver\ntests/common-conf.sh\ntools/seaf-server-init\n*.mo\nseafile-web\ntests/basic/conf1/c882e263e9d02c63ca6b61c68508761cbc74c358.peer\ntests/basic/conf1/c882e263e9d02c63ca6b61c68508761cbc74c358.user\ntests/basic/conf1/group-db/\ntests/basic/conf1/peer-db/\ntests/basic/conf1/user-db/\ntests/basic/conf2/376cf9b6ef33a6839cf1fc096131893b5ecc673f.peer\ntests/basic/conf2/376cf9b6ef33a6839cf1fc096131893b5ecc673f.user\ntests/basic/conf2/group-db/\ntests/basic/conf2/peer-db/\ntests/basic/conf2/user-db/\ntests/basic/conf3/1e5b5e0f49010b94aa6c2995a6e7b7cba462d388.peer\ntests/basic/conf3/1e5b5e0f49010b94aa6c2995a6e7b7cba462d388.user\ntests/basic/conf3/group-db/\ntests/basic/conf3/peer-db/\ntests/basic/conf3/user-db/\ntests/basic/conf4/93ae3e01eea6667cbdd03c4afde413ccd9f1eb43.peer\ntests/basic/conf4/93ae3e01eea6667cbdd03c4afde413ccd9f1eb43.user\ntests/basic/conf4/peer-db/\ntests/basic/conf4/user-db/\nweb/local_settings.py\ngui/win/applet-po-gbk.h\n*.dylib\n.DS_Store\ngui/mac/seafile/build/\ngui/mac/seafile/ccnet\ngui/mac/seafile/seaf-daemon\ngui/mac/seafile/seafileweb.app\nweb/build/\nweb/dist/\ntests/basic/conf2/misc/\ntests/basic/conf2/seafile-data/\ntests/test-cdc\ntests/test-index\ntests/test-seafile-fmt\n*.pc\nseaf-fsck\n*.tar.gz\nfuse/seaf-fuse\nserver/gc/seaf-migrate\n/compile\n/test-driver\n*.dmp\n/symbols\n\n# visual studio\nDebug\nmsi/custom/x64\nmsi/ext.wxi\nmsi/shell.wxs\nseafile.vcxproj.user\nx64\nmsi/custom/seafile_custom.vcxproj.user\nmsi/custom/.vs\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: false\nlanguage: c\ndist: Xenial\ncompiler: gcc\nos: linux\ninstall:\n- ./integration-tests/install-deps.sh\nscript:\n - ./autogen.sh\n - ./configure\n - make\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "This program is released under GPLv2, with the following addition\npermission to link with OpenSSL library.\n\nIf you modify this program, or any covered work, by linking or\ncombining it with the OpenSSL project's OpenSSL library (or a\nmodified version of that library), containing parts covered by the\nterms of the OpenSSL or SSLeay licenses, Seafile Ltd.\ngrants you additional permission to convey the resulting work.\nCorresponding Source for a non-source form of such a combination\nshall include the source code for the parts of OpenSSL used as well\nas that of the covered work.\n\n\n                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "Makefile.am",
    "content": "SUBDIRS = include lib common daemon app doc python\n\nDIST_SUBDIRS = include lib common app daemon doc python\n\nEXTRA_DIST = install-sh $(INTLTOOL) README.markdown scripts debian msi LICENSE.txt dmg\n\nACLOCAL_AMFLAGS = -I m4\n\ndist-hook:\n\tgit log --format='%H' -1 > $(distdir)/latest_commit\n"
  },
  {
    "path": "README.markdown",
    "content": "## Introduction [![Build Status](https://secure.travis-ci.org/haiwen/seafile.svg?branch=master)](http://travis-ci.org/haiwen/seafile)\n\nSeafile is an open source cloud storage system with privacy protection and teamwork features. Collections of files are called libraries. Each library can be synced separately. A library can also be encrypted with a user chosen password. Seafile also allows users to create groups and easily sharing files into groups.\n\n\n## Feature Summary\n\nSeafile has the following features:\n\n### File syncing\n\n1. Selective sync for any folder.\n2. Correctly handles file conflicts based on history instead of timestamp.\n3. Only transfer content delta to the server. Interrupted transfers can be resumed.\n4. Sync with two or more servers.\n5. Sync with existing folders.\n\n\n### File sharing and collaboration\n\n1. Sharing folders between users or into groups.\n3. Download links with password protection\n4. Upload links\n5. Version control\n\n### Drive client\n\n* Access all files in the cloud via virtual drive.\n* Files are synced on demand.\n\n### Privacy protection\n\n1. Library encryption with a user chosen password.\n2. Client side encryption when using the desktop syncing.\n\n### Online documents and knowledge management (New)\n\n* Online Markdown editing in WYSIWYG way\n* A draft review workflow for online documents\n* Metadata management, including\n  * File labels\n  * Related documents\n* Wiki mode\n* Realtime notifications\n\n\n## Source repositories for Seafile components\n\n\nEach component of Seafile has its own source code repository on Github.\n\n* Sync client daemon (this repository): https://github.com/haiwen/seafile\n* Sync client GUI: https://github.com/haiwen/seafile-client\n* Server core: https://github.com/haiwen/seafile-server\n* Server web UI: https://github.com/haiwen/seahub\n* iOS app: https://github.com/haiwen/seafile-iOS\n* Android app: https://github.com/haiwen/seadroid\n* WebDAV: https://github.com/haiwen/seafdav\n\nBefore version 6.0, the source code of \"sync client daemon\" and \"server core\" was mixed together in https://github.com/haiwen/seafile.\nBut after 6.0 version, the server core is separated into its own repository.\nFor this reason, the sync client daemon repository is still the \"front page\" for Seafile project on Github.\n\nBuild and Run\n=============\n\nSee <https://manual.seafile.com/build_seafile/server>\n\nBug and Feature Request Reports\n===============================\n\nPlease only submit bugs in GitHub issues (Pro customers should contact us via Email):\n\n* Server, Web interface (Seahub) and desktop clients: https://github.com/haiwen/seafile/issues\n* Android client: https://github.com/haiwen/seadroid/issues\n* iOS client: https://github.com/haiwen/seafile-iOS/issues\n\nFeature requests can be made and installation/usage problems can be discussed in the forum https://forum.seafile.com/.\n\nInternationalization (I18n)\n===========================\n\n* [Translate Seafile web ui](https://github.com/haiwen/seahub/?tab=readme-ov-file#internationalization-i18n)\n* [Translate Seafile desktop client](https://github.com/haiwen/seafile-client/#internationalization)\n* [Translate Seafile Android app](https://github.com/haiwen/seadroid#internationalization)\n* [Translate Seafile iOS app](https://github.com/haiwen/seafile-ios#internationalization-i18n)\n\nChange Logs\n===========\n\nSee <https://manual.seafile.com/changelog/server-changelog/>\n\n\nWhy Open Source\n===============\n\nOur primary goal is to build a first-class product. We think this goal can only be achieved by collaborating with the whole world.\n\n\nContributing\n===========\n\nFor more information read [Contribution](https://manual.seafile.com/contribution/).\n\n\nLicense\n=======\n\n- Seafile iOS client: Apache License v2\n- Seafile Android client: GPLv3\n- Desktop syncing client (this repository): GPLv2\n- Seafile Server core: AGPLv3\n- Seahub (Seafile server Web UI): Apache License v2\n\nContact\n=======\n\nTwitter: @seafile <https://twitter.com/seafile>\n\nForum: <https://forum.seafile.com>\n"
  },
  {
    "path": "app/Makefile.am",
    "content": "bin_SCRIPTS = seaf-cli\n\nEXTRA_DIST = seaf-cli\n"
  },
  {
    "path": "app/seaf-cli",
    "content": "#!/usr/bin/env python3\n#-*- coding:utf-8 -*-\n# pylint: disable=E1121\n\n'''\n\nseaf-cli is command line interface for seafile client.\n\nSubcommands:\n\n    init:             create config files for seafile client\n    start:            start and run seafile client as daemon\n    stop:             stop seafile client\n    list:             list local libraries\n    status:           show syncing status\n    download:         download a library from seafile server\n                          (using libary id)\n    download-by-name: download a library from seafile server\n                          (using library name)\n    sync:             synchronize an existing folder with a library in\n                          seafile server\n    desync:           desynchronize a library with seafile server\n    create:           create a new library\n\n\nDetail\n======\n\nSeafile client stores all its configure information in a config dir. The default location is `~/.ccnet`. All the commands below accept an option `-c <config-dir>`.\n\ninit\n----\nInitialize seafile client. This command initializes the config dir. It also creates sub-directories `seafile-data` and `seafile` under `parent-dir`. `seafile-data` is used to store internal data, while `seafile` is used as the default location put downloaded libraries.\n\n    seaf-cli init [-c <config-dir>] -d <parent-dir>\n\nstart\n-----\nStart seafile client. This command starts `seaf-daemon`, which manages all the files.\n\n    seaf-cli start [-c <config-dir>]\n\nstop\n----\nStop seafile client.\n\n    seaf-cli stop [-c <config-dir>]\n\n\nDownload by id\n--------\nDownload a library from seafile server (using library id)\n\n    seaf-cli download -l <library-id> -s <seahub-server-url> -d <parent-directory> -u <username> -p <password> [-a <2fa-code>]\n\n\nDownload by name\n--------\nDownload a library from seafile server (using library name)\n\n    seaf-cli download -L <library-name> -s <seahub-server-url> -d <parent-directory> -u <username> -p <password> [-a <2fa-code>]\n\n\nsync\n----\nSynchronize a library with an existing folder.\n\n    seaf-cli sync -l <library-id> -s <seahub-server-url> -d <existing-folder> -u <username> -p <password> [-a <2fa-code>]\n\ndesync\n------\nDesynchronize a library from seafile server\n\n    seaf-cli desync -d <existing-folder>\n\ncreate\n------\nCreate a new library\n\n    seaf-cli create -s <seahub-server-url> -n <library-name> -u <username> -p <password> [-a <2fa-code>] -t <description> [-e <library-password>]\n\n'''\nimport argparse\nimport os\nimport json\nimport subprocess\nimport re\nimport sys\nimport time\nimport getpass\nimport random\nimport urllib.request, urllib.parse, urllib.error\nimport urllib.request, urllib.error, urllib.parse\nfrom urllib.parse import urlparse\n\nfrom os.path import abspath, dirname, exists, isdir, join\n\nimport seafile\n\nif 'HOME' in os.environ:\n    DEFAULT_CONF_DIR = \"%s/.ccnet\" % os.environ['HOME']\n    DEFAULT_USER_CONF_DIR = \"%s/.seafile.conf\" % os.environ['HOME']    \nelse:\n    DEFAULT_CONF_DIR = None\n    DEFAULT_USER_CONF_DIR = None\n\nseafile_datadir = None\nseafile_worktree = None\n\n\ndef _check_seafile():\n    ''' Check seafile daemon have been installed '''\n\n    dirs = os.environ['PATH'].split(':')\n    def exist_in_path(prog):\n        ''' Check whether 'prog' exists in system path '''\n        for d in dirs:\n            if d == '':\n                continue\n            path = join(d, prog)\n            if exists(path):\n                return True\n\n    progs = ['seaf-daemon']\n\n    for prog in progs:\n        if not exist_in_path(prog):\n            print(\"%s not found in PATH. Have you installed seafile?\" % prog)\n            sys.exit(1)\n\ndef _check_daemon_running():\n    import fcntl\n    pidfile = os.path.join(seafile_datadir, 'seaf-daemon.pid')\n    with open(pidfile, 'w') as f:\n        try:\n            fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)\n            fcntl.flock(f, fcntl.LOCK_UN)\n        except:\n            print(\"The seafile data directory %s is already used by another Seafile client instance. Please use another seafile data directory.\"%seafile_datadir)\n            sys.exit(1)\n\ndef get_rpc_client(confdir):\n    return seafile.RpcClient(join(seafile_datadir, 'seafile.sock'))\n\ndef _config_valid(conf):\n    ''' Check config directory valid '''\n\n    if not exists(conf) or not isdir(conf):\n        print(\"%s not exists\" % conf)\n        return False\n\n    seafile_ini = conf + \"/seafile.ini\"\n    if not exists(seafile_ini):\n        print(\"Could not load %s\" % seafile_ini)\n        return False\n\n    with open(seafile_ini) as f:\n        for line in f:\n            global seafile_datadir, seafile_worktree\n            seafile_datadir = line.strip()\n            seafile_worktree = join(\n                dirname(seafile_datadir), \"seafile\")\n            break\n\n    if not seafile_datadir or not seafile_worktree:\n        print(\"Could not load seafile_datadir and seafile_worktree\")\n        return False\n    return True\n\n\ndef _conf_dir(args):\n    ''' Determine and return the value of conf_dir '''\n    conf_dir = DEFAULT_CONF_DIR\n    if args.confdir:\n        conf_dir = args.confdir\n    conf_dir = abspath(conf_dir)\n\n    if not _config_valid(conf_dir):\n        print(\"Invalid config directory\")\n        sys.exit(1)\n    else:\n        get_device_id(conf_dir)\n        return conf_dir\n\ndef _user_config_valid(conf):\n    if exists(conf):\n        return True\n    return False\n\ndef _parse_user_config(conf):\n    try:\n        from configparser import ConfigParser\n        from configparser import NoOptionError\n    except ImportError:\n        from ConfigParser import ConfigParser\n        from ConfigParser import NoOptionError\n        \n    cfg = ConfigParser()\n    cfg.read(conf)\n    if len(cfg.sections()) < 1 or cfg.sections()[0] != 'account':\n        return None, None\n    try:\n        server = cfg.get('account', 'server')\n        user = cfg.get('account', 'user')\n        return server,user\n    except NoOptionError:\n        return None, None\n\ndef run_argv(argv, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Run a program and wait it to finish, and return its exit code. The\n    standard output of this program is supressed.\n\n    '''\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(argv,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env)\n        return proc.wait()\n\ndef get_env():\n    env = dict(os.environ)\n    ld_library_path = os.environ.get('SEAFILE_LD_LIBRARY_PATH', '')\n    if ld_library_path:\n        env['LD_LIBRARY_PATH'] = ld_library_path\n\n    return env\n\ndef urlopen(url, data=None, headers=None):\n    if data:\n        data = urllib.parse.urlencode(data).encode('utf-8')\n    headers = headers or {}\n    req = urllib.request.Request(url, data=data, headers=headers)\n    resp = urllib.request.urlopen(req)\n\n    return resp.read()\n\nSEAF_CLI_VERSION = \"\"\n\ndef randstring(size):\n    random.seed(time.time())\n    s = ''\n    while len(s) < size:\n        s += '%x' % random.randint(0, 255)\n    return s[:size]\n\ndevice_id = None\ndef get_device_id(conf_dir):\n    global device_id\n    if device_id:\n        return device_id\n\n    idfile = join(seafile_datadir, 'id')\n    ccnet_conf = join(conf_dir, 'ccnet.conf')\n    if exists(idfile):\n        with open(idfile, 'r') as fp:\n            device_id = fp.read().strip()\n            return device_id\n\n    # Id file doesn't exist. We either migrate it from ccnet.conf ID\n    # (for existing data), or create it.\n\n    if exists(ccnet_conf):\n        # migrate from existing ccnet.conf ID\n        with open(ccnet_conf, 'r') as fp:\n            for line in fp:\n                m = re.search('ID = (.*)', line)\n                if m:\n                    device_id = m.group(1)\n                    print('Migrating device id from ccnet conf')\n                    break\n    if not device_id:\n        # create a new id\n        print('New device id created')\n        device_id = randstring(40)\n    with open(idfile, 'w') as fp:\n        fp.write(device_id)\n    return device_id\n\ndef get_token(url, username, password, tfa, conf_dir):\n    platform = 'linux'\n    device_id = get_device_id(conf_dir)\n    device_name = 'terminal-' + os.uname()[1][:25]\n    client_version = SEAF_CLI_VERSION\n    platform_version = ''\n    data = {\n        'username': username,\n        'password': password,\n        'platform': platform,\n        'device_id': device_id,\n        'device_name': device_name,\n        'client_version': client_version,\n        'platform_version': platform_version,\n    }\n    if tfa:\n        headers = {\n            'X-SEAFILE-OTP': tfa,\n        }\n    else:\n        headers = None\n    token_json = urlopen(\"%s/api2/auth-token/\" % url, data=data, headers=headers)\n    tmp = json.loads(token_json.decode('utf8'))\n    token = tmp['token']\n    return token\n\ndef get_repo_download_info(url, token):\n    headers = { 'Authorization': 'Token %s' % token }\n    repo_info = urlopen(url, headers=headers)\n    return json.loads(repo_info.decode('utf8'))\n\ndef get_value_from_user_config(args, section, key):\n    user_config = args.C\n    if not user_config:\n        user_config = DEFAULT_USER_CONF_DIR\n    else:\n        user_config = abspath(user_config)\n    if _user_config_valid(user_config):\n        try:\n            from configparser import ConfigParser\n            from configparser import NoOptionError\n        except ImportError:\n            from ConfigParser import ConfigParser\n            from ConfigParser import NoOptionError\n\n        cfg = ConfigParser()\n        cfg.read(user_config)\n        if len(cfg.sections()) < 1 or cfg.sections()[0] != 'account':\n            return None\n        try:\n            token = cfg.get(section, key)\n            return token\n        except NoOptionError:\n            return None\n    else:\n        return None\n\ndef get_token_from_args_or_config(args):\n    if args.token:\n        return args.token\n    else:\n        return get_value_from_user_config(args, 'account', 'token')\n\ndef seaf_init(args):\n    ''' Initialize config directories'''\n\n    ccnet_conf_dir = DEFAULT_CONF_DIR\n    if args.confdir:\n        ccnet_conf_dir = args.confdir\n    if args.dir:\n        seafile_path = args.dir\n    else:\n        print(\"Must specify the parent path for put seafile-data\")\n        sys.exit(0)\n    seafile_path = abspath(seafile_path)\n\n    if exists(ccnet_conf_dir):\n        print(\"%s already exists\" % ccnet_conf_dir)\n        sys.exit(0)\n\n    os.mkdir(ccnet_conf_dir)\n    logsdir = join(ccnet_conf_dir, 'logs')\n    if not exists(logsdir):\n        os.mkdir(logsdir)\n\n    if not exists(seafile_path):\n        print(\"%s not exists\" % seafile_path)\n        sys.exit(0)\n    seafile_ini = ccnet_conf_dir + \"/seafile.ini\"\n    seafile_data = seafile_path + \"/seafile-data\"\n    with open(seafile_ini, 'w') as fp:\n        fp.write(seafile_data)\n    if not exists(seafile_data):\n        os.mkdir(seafile_data)\n    print(\"Writen seafile data directory %s to %s\" % (seafile_data, seafile_ini))\n\n\ndef seaf_start_all(args):\n    ''' Start seafile daemon '''\n    seaf_start_seafile(args)\n\ndef seaf_start_seafile(args):\n    ''' start seafile daemon '''\n\n    conf_dir = _conf_dir(args)\n\n    _check_daemon_running()\n\n    print(\"Starting seafile daemon ...\")\n\n    cmd = [ \"seaf-daemon\", \"--daemon\", \"-c\", conf_dir, \"-d\", seafile_datadir,\n            \"-w\", seafile_worktree ]\n    if run_argv(cmd, env=get_env()) != 0:\n        print('Failed to start seafile daemon')\n        sys.exit(1)\n\n    seafile_rpc = get_rpc_client(conf_dir)\n    i = 0\n    while True:\n        try:\n            seafile_rpc.seafile_set_config_int(\"delete_confirm_threshold\", 1000000)\n        except Exception as e:\n            if i > 3:\n                break;\n            i += 1\n            time.sleep(1)\n            continue\n        else:\n            break\n\n    print(\"Started: seafile daemon ...\")\n\ndef seaf_stop(args):\n    '''Stop seafile daemon '''\n\n    conf_dir = _conf_dir(args)\n\n    seafile_rpc = get_rpc_client(conf_dir)\n    try:\n        # TODO: add shutdown rpc in seaf-daemon\n        seafile_rpc.shutdown()\n    except:\n        # ignore NetworkError(\"Failed to read from socket\")\n        pass\n\n\ndef seaf_list(args):\n    '''List local libraries'''\n\n    conf_dir = _conf_dir(args)\n\n    seafile_rpc = get_rpc_client(conf_dir)\n    repos = seafile_rpc.get_repo_list(-1, -1)\n\n    if args.json:\n        repo_list = []\n        for repo in repos:\n            repo_dict = {'name':repo.name, 'id':repo.id, 'path':repo.worktree}\n            repo_list.append(repo_dict)\n        json_str = json.dumps(repo_list, ensure_ascii=False)\n        print(json_str)\n    else:\n        print(\"Name\\tID\\tPath\")\n        for repo in repos:\n            print(repo.name, repo.id, repo.worktree)\n\n\ndef seaf_list_remote(args):\n    '''List remote libraries'''\n\n    conf_dir = _conf_dir(args)\n\n    server_from_config = get_value_from_user_config(args, 'account', 'server')\n    user_from_config = get_value_from_user_config(args, 'account', 'user')\n\n    url = args.server        \n    if not url and server_from_config:\n        url = server_from_config\n    if not url:\n        print(\"Seafile server url need to be presented\")\n        sys.exit(1)\n\n    seafile_rpc = get_rpc_client(conf_dir)\n\n    username = args.username\n    if not username and user_from_config:\n        username = user_from_config\n    if not username:\n        username = input(\"Enter username: \")\n\n    token = get_token_from_args_or_config(args)\n    password = None\n    tfa = args.tfa\n    if not token:\n        password = args.password\n        if not password:\n            password = getpass.getpass(\"Enter password for user %s : \" % username)\n        # curl -d 'username=<USERNAME>&password=<PASSWORD>' http://127.0.0.1:8000/api2/auth-token\n        token = get_token(url, username, password, tfa, conf_dir)\n\n    repos = get_repo_download_info(\"%s/api2/repos/\" % (url), token)\n\n    printed = {}\n    if args.json:\n        repo_list = []\n        for repo in repos:\n            if repo['id'] in printed:\n                continue\n            printed[repo['id']] = repo['id']\n            repo_dict = {'name': repo['name'], 'id':repo['id']}\n            repo_list.append(repo_dict)\n        json_str = json.dumps(repo_list, ensure_ascii=False)\n        print(json_str)\n    else:\n        print(\"Name\\tID\")\n        for repo in repos:\n            if repo['id'] in printed:\n                continue\n\n            printed[repo['id']] = repo['id']\n            print(repo['name'], repo['id'])\n\n\ndef get_base_url(url):\n    parse_result = urlparse(url)\n    scheme = parse_result.scheme\n    netloc = parse_result.netloc\n\n    if scheme and netloc:\n        return '%s://%s' % (scheme, netloc)\n\n    return None\n\ndef seaf_download(args):\n    '''Download a library from seafile server '''\n\n    conf_dir = _conf_dir(args)\n\n    repo = args.library\n    if not repo:\n        print(\"Library id is required\")\n        sys.exit(1)\n\n    server_from_config = get_value_from_user_config(args, 'account', 'server')\n    user_from_config = get_value_from_user_config(args, 'account', 'user')\n\n    url = args.server\n    if not url and server_from_config:\n        url = server_from_config\n    if not url:\n        print(\"Seafile server url need to be presented\")\n        sys.exit(1)\n\n    download_dir = seafile_worktree\n    if args.dir:\n        download_dir = abspath(args.dir)\n\n\n    seafile_rpc = get_rpc_client(conf_dir)\n\n    username = args.username\n    if not username and user_from_config:\n        username = user_from_config\n    if not username:\n        username = input(\"Enter username: \")\n    token = get_token_from_args_or_config(args)\n    password = None\n    tfa = args.tfa\n    if not token:\n        password = args.password\n        if not password:\n            password = getpass.getpass(\"Enter password for user %s : \" % username)\n        # curl -d 'username=<USERNAME>&password=<PASSWORD>' http://127.0.0.1:8000/api2/auth-token\n        token = get_token(url, username, password, tfa, conf_dir)\n\n    tmp = get_repo_download_info(\"%s/api2/repos/%s/download-info/\" % (url, repo), token)\n\n    encrypted = tmp['encrypted']\n    magic = tmp.get('magic', None)\n    enc_version = tmp.get('enc_version', None)\n    random_key = tmp.get('random_key', None)\n\n    clone_token = tmp['token']\n    email = tmp['email']\n    repo_name = tmp['repo_name']\n    version = tmp.get('repo_version', 0)\n    repo_salt = tmp.get('salt', None)\n    permission = tmp.get('permission', None)\n\n    is_readonly = 0\n    if permission == 'r':\n        is_readonly = 1\n    \n    more_info = None\n    more_info_dict = {}\n    base_url = get_base_url(url)\n    if base_url:\n        more_info_dict.update({'server_url': base_url})\n    if repo_salt:\n        more_info_dict.update({'repo_salt': repo_salt})\n    more_info_dict.update({'is_readonly': is_readonly})\n    more_info = json.dumps(more_info_dict)\n\n    print(\"Starting to download ...\")\n    print(\"Library %s will be downloaded to %s\" % (repo, download_dir))\n    if encrypted == 1:\n        repo_passwd = args.libpasswd if args.libpasswd else getpass.getpass(\"Enter password for the library: \")\n    else:\n        repo_passwd = None\n\n    seafile_rpc.download(repo,\n                         version,\n                         repo_name,\n                         download_dir,\n                         clone_token,\n                         repo_passwd, magic,\n                         email, random_key, enc_version, more_info)\n\n\ndef seaf_download_by_name(args):\n    '''Download a library defined by name from seafile server'''\n    id = None\n\n    conf_dir = _conf_dir(args)\n\n    libraryname = args.libraryname\n    if not libraryname:\n        print(\"Library name is required\")\n        sys.exit(1)\n\n    server_from_config = get_value_from_user_config(args, 'account', 'server')\n    user_from_config = get_value_from_user_config(args, 'account', 'user')\n\n    url = args.server        \n    if not url and server_from_config:\n        url = server_from_config\n    if not url:\n        print(\"Seafile server url need to be presented\")\n        sys.exit(1)\n\n    seafile_rpc = get_rpc_client(conf_dir)\n\n    username = args.username\n    if not username and user_from_config:\n        username = user_from_config;\n    if not username:\n        username = input(\"Enter username: \")\n        args.username = username\n\n    token = get_token_from_args_or_config(args)\n    password = None\n    tfa = args.tfa\n    if not token:\n        password = args.password\n        if not password:\n            password = getpass.getpass(\"Enter password for user %s : \" % username)\n            args.password = password\n        # curl -d 'username=<USERNAME>&password=<PASSWORD>' http://127.0.0.1:8000/api2/auth-token\n        token = get_token(url, username, password, tfa, conf_dir)\n\n    tmp = get_repo_download_info(\"%s/api2/repos/\" % (url), token)\n\n    for i in tmp:\n        if libraryname == i['name']:\n             id = i['id']\n\n    if not id:\n        print(\"Defined library name not found\")\n        sys.exit(1)\n\n    args.library = id\n    seaf_download(args)\n\n\ndef seaf_sync(args):\n    ''' synchronize a library from seafile server '''\n\n    conf_dir = _conf_dir(args)\n\n    repo = args.library\n    if not repo:\n        print(\"Library id is required\")\n        sys.exit(1)\n\n    server_from_config = get_value_from_user_config(args, 'account', 'server')\n    user_from_config = get_value_from_user_config(args, 'account', 'user')\n\n    url = args.server\n    if not url and server_from_config:\n        url = server_from_config\n    if not url:\n        print(\"Seafile server url is required\")\n        sys.exit(1)\n\n    folder = args.folder\n    if not folder:\n        print(\"The local directory is required\")\n        sys.exit(1)\n\n    folder = abspath(folder)\n    if not exists(folder):\n        print(\"The local directory does not exists\")\n        sys.exit(1)\n\n    seafile_rpc = get_rpc_client(conf_dir)\n\n    username = args.username\n    if not username and user_from_config:\n        username = user_from_config;\n    if not username:\n        username = input(\"Enter username: \")\n\n    password = None\n    token = get_token_from_args_or_config(args)\n    tfa = args.tfa\n    if not token:\n        password = args.password\n        if not password:\n            password = getpass.getpass(\"Enter password for user %s : \" % username)\n        token = get_token(url, username, password, tfa, conf_dir)\n\n    tmp = get_repo_download_info(\"%s/api2/repos/%s/download-info/\" % (url, repo), token)\n\n    encrypted = tmp['encrypted']\n    magic = tmp.get('magic', None)\n    enc_version = tmp.get('enc_version', None)\n    random_key = tmp.get('random_key', None)\n\n    clone_token = tmp['token']\n    email = tmp['email']\n    repo_name = tmp['repo_name']\n    version = tmp.get('repo_version', 0)\n    repo_salt =  tmp.get('salt', None)\n    permission = tmp.get('permission', None)\n\n    is_readonly = 0\n    if permission == 'r':\n        is_readonly = 1\n    \n    more_info = None\n    more_info_dict = {}\n    base_url = get_base_url(url)\n    if base_url:\n        more_info_dict.update({'server_url': base_url})\n    if repo_salt:\n        more_info_dict.update({'repo_salt': repo_salt})\n    more_info_dict.update({'is_readonly': is_readonly})\n    more_info = json.dumps(more_info_dict)\n\n    print(\"Starting to download ...\")\n    if encrypted == 1:\n        repo_passwd = args.libpasswd if args.libpasswd else getpass.getpass(\"Enter password for the library: \")\n    else:\n        repo_passwd = None\n\n    seafile_rpc.clone(repo,\n                      version,\n                      repo_name,\n                      folder,\n                      clone_token,\n                      repo_passwd, magic,\n                      email, random_key, enc_version, more_info)\n\n\ndef seaf_desync(args):\n    '''Desynchronize a library from seafile server'''\n\n    conf_dir = _conf_dir(args)\n\n    repo_path = args.folder\n    if not repo_path:\n        print(\"Must specify the local path of the library\")\n        sys.exit(1)\n    repo_path = abspath(repo_path)\n\n    seafile_rpc = get_rpc_client(conf_dir)\n\n    repos = seafile_rpc.get_repo_list(-1, -1)\n    repo = None\n    for r in repos:\n        if r.worktree == repo_path:\n            repo = r\n            break\n\n    if repo is not None:\n        if sys.version_info[0] == 2:\n            repo.name = repo.name.encode('utf8')\n        print(\"Desynchronize %s\" % repo.name)\n        seafile_rpc.remove_repo(repo.id)\n    else:\n        print(\"%s is not a library\" % args.folder)\n\n\ndef seaf_config(args):\n    '''Configure the seafile client'''\n\n    conf_dir = _conf_dir(args)\n\n    config_key = args.key\n    if not config_key:\n        print(\"Must specify configuration key\")\n        sys.exit(1)\n\n    config_value = args.value\n\n    seafile_rpc = get_rpc_client(conf_dir)\n\n    if config_value:\n        # set configuration key\n        seafile_rpc.seafile_set_config(config_key, config_value)\n    else:\n        # print configuration key\n        val = seafile_rpc.seafile_get_config(config_key)\n        print(\"%s = %s\" % (config_key, val))\n\n\ndef seaf_status(args):\n    '''Show status'''\n\n    conf_dir = _conf_dir(args)\n\n    seafile_rpc = get_rpc_client(conf_dir)\n\n    tasks = seafile_rpc.get_clone_tasks()\n    print('# {:<50s}\\t{:<20s}\\t{:<20s}'.format('Name', 'Status', 'Progress'))\n    for task in tasks:\n        if sys.version_info[0] == 2:\n            task.repo_name = task.repo_name.encode('utf8')\n        if task.state == \"fetch\":\n            tx_task = seafile_rpc.find_transfer_task(task.repo_id)\n            try:\n                print('{:<50s}\\t{:<20s}\\t{:<.1f}%, {:<.1f}KB/s'.format(task.repo_name, 'downloading',\n                                                                       tx_task.block_done / tx_task.block_total * 100,\n                                                                       tx_task.rate / 1024.0))\n            except ZeroDivisionError: pass\n        elif task.state == \"error\":\n            err = seafile_rpc.sync_error_id_to_str(task.error)\n            print('{:<50s}\\t{:<20s}\\t{:<20s}'.format(task.repo_name, 'error', err))            \n        elif task.state == 'done':\n            # will be shown in repo status\n            pass\n        else:\n            print('{:<50s}\\t{:<20s}'.format(task.repo_name, task.state))\n\n    repos = seafile_rpc.get_repo_list(-1, -1)\n    for repo in repos:\n        auto_sync_enabled = seafile_rpc.is_auto_sync_enabled()\n        if not auto_sync_enabled or not repo.auto_sync:\n            print('{:<50s}\\t{:<20s}'.format(repo.name, 'auto sync disabled'))\n            continue\n\n        task = seafile_rpc.get_repo_sync_task(repo.id)\n        if task is None:\n            print('{:<50s}\\t{:<20s}'.format(repo.name, 'waiting for sync'))\n        elif task.state == 'uploading':\n            tx_task = seafile_rpc.find_transfer_task(repo.id)\n            try:\n                print('{:<50s}\\t{:<20s}\\t{:<.1f}%, {:<.1f}KB/s'.format(repo.name, 'uploading',\n                                                                       tx_task.block_done / tx_task.block_total * 100,\n                                                                       tx_task.rate / 1024.0))\n            except ZeroDivisionError: pass\n        elif task.state == 'downloading':\n            tx_task = seafile_rpc.find_transfer_task(repo.id)\n            try:\n                if tx_task.rt_state == 'data':\n                    print('{:<50s}\\t{:<20s}\\t{:<.1f}%, {:<.1f}KB/s'.format(repo.name, 'downloading files',\n                                                                           tx_task.block_done / tx_task.block_total * 100,\n                                                                           tx_task.rate / 1024.0))\n                if tx_task.rt_state == 'fs':\n                    print('{:<50s}\\t{:<20s}\\t{:<.1f}%'.format(repo.name, 'downloading file list',\n                                                              tx_task.fs_objects_done / tx_task.fs_objects_total * 100))\n            except ZeroDivisionError: pass\n        elif task.state == 'error':\n            err = seafile_rpc.sync_error_id_to_str(task.error)\n            print('{:<50s}\\t{:<20s}\\t{:<20s}'.format(repo.name, 'error', err))\n        else:\n            print('{:<50s}\\t{:<20s}'.format(repo.name, task.state))\n\ndef create_repo(url, token, args):\n    headers = { 'Authorization': 'Token %s' % token }\n    data = {\n        'name': args.name,\n        'desc': args.desc,\n    }\n    if args.libpasswd:\n        data['passwd'] = args.libpasswd\n    repo_info_json =  urlopen(url, data=data, headers=headers)\n    repo_info = json.loads(repo_info_json.decode('utf8'))\n    return repo_info['repo_id']\n\ndef seaf_create(args):\n    '''Create a library'''\n    conf_dir = _conf_dir(args)\n\n    server_from_config = get_value_from_user_config(args, 'account', 'server')\n    user_from_config = get_value_from_user_config(args, 'account', 'user')\n\n    # check username and password\n    username = args.username\n    if not username and user_from_config:\n        username = user_from_config;\n    if not username:\n        username = input(\"Enter username: \")\n\n    url = args.server    \n    if not url and server_from_config:\n        url = server_from_config\n    if not url:\n        print(\"Seafile server url need to be presented\")\n        sys.exit(1)\n\n    token = get_token_from_args_or_config(args)\n    password = None\n    tfa = args.tfa\n    if not token:\n        password = args.password\n        if not password:\n            password = getpass.getpass(\"Enter password for user %s \" % username)\n        # curl -d 'username=<USERNAME>&password=<PASSWORD>' http://127.0.0.1:8000/api2/auth-token\n        token = get_token(url, username, password, tfa, conf_dir)\n\n    repo_id = create_repo(\"%s/api2/repos/\" % (url), token, args)\n    print(repo_id)\n\n\ndef main():\n    ''' Main entry '''\n\n    _check_seafile()\n\n    parser = argparse.ArgumentParser()\n    subparsers = parser.add_subparsers(title='subcommands', description='')\n\n    confdir_required = DEFAULT_CONF_DIR is None\n\n    # init\n    parser_init = subparsers.add_parser('init', help='Initialize config directory')\n    parser_init.set_defaults(func=seaf_init)\n    parser_init.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)\n    parser_init.add_argument('-d', '--dir', help='the parent directory to put seafile-data', type=str)\n\n    # start\n    parser_start = subparsers.add_parser('start',\n                                         help='Start seafile daemon')\n    parser_start.set_defaults(func=seaf_start_all)\n    parser_start.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)\n\n    # stop\n    parser_stop = subparsers.add_parser('stop',\n                                         help='Stop seafile daemon')\n    parser_stop.set_defaults(func=seaf_stop)\n    parser_stop.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)\n\n    # list\n    parser_list = subparsers.add_parser('list', help='List local libraries')\n    parser_list.set_defaults(func=seaf_list)\n    parser_list.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)\n    parser_list.add_argument('--json', help='output json format result', action='store_true')\n\n    # list-remote\n    parser_download = subparsers.add_parser('list-remote', help='List remote libraries')\n    parser_download.set_defaults(func=seaf_list_remote)\n    parser_download.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)\n    parser_download.add_argument('-C', help='the user config directory', type=str)    \n    parser_download.add_argument('-s', '--server', help='URL for seafile server', type=str)\n    parser_download.add_argument('-u', '--username', help='username', type=str)\n    parser_download.add_argument('-p', '--password', help='password', type=str)\n    parser_download.add_argument('-T', '--token', help='token', type=str)\n    parser_download.add_argument('-a', '--tfa', help='two-factor authentication', type=str)\n    parser_download.add_argument('--json', help='output json format result', action='store_true')\n\n    # status\n    parser_status = subparsers.add_parser('status', help='Show syncing status')\n    parser_status.set_defaults(func=seaf_status)\n    parser_status.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)\n\n    # download\n    parser_download = subparsers.add_parser('download',\n                                         help='Download a library from seafile server')\n    parser_download.set_defaults(func=seaf_download)\n    parser_download.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)\n    parser_download.add_argument('-C', help='the user config directory', type=str)\n    parser_download.add_argument('-l', '--library', help='library id', type=str)\n    parser_download.add_argument('-s', '--server', help='URL for seafile server', type=str)\n    parser_download.add_argument('-d', '--dir', help='the directory to put the library', type=str)\n    parser_download.add_argument('-u', '--username', help='username', type=str)\n    parser_download.add_argument('-p', '--password', help='password', type=str)\n    parser_download.add_argument('-T', '--token', help='token', type=str)\n    parser_download.add_argument('-a', '--tfa', help='two-factor authentication', type=str)\n    parser_download.add_argument('-e', '--libpasswd', help='library password', type=str)\n\n    # download-by-name\n    parser_download = subparsers.add_parser('download-by-name',\n                                         help='Download a library defined by name from seafile server')\n    parser_download.set_defaults(func=seaf_download_by_name)\n    parser_download.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)\n    parser_download.add_argument('-C', help='the user config directory', type=str)    \n    parser_download.add_argument('-L', '--libraryname', help='library name', type=str)\n    parser_download.add_argument('-s', '--server', help='URL for seafile server', type=str)\n    parser_download.add_argument('-d', '--dir', help='the directory to put the library', type=str)\n    parser_download.add_argument('-u', '--username', help='username', type=str)\n    parser_download.add_argument('-p', '--password', help='password', type=str)\n    parser_download.add_argument('-T', '--token', help='token', type=str)\n    parser_download.add_argument('-a', '--tfa', help='two-factor authentication', type=str)\n    parser_download.add_argument('-e', '--libpasswd', help='library password', type=str)\n\n\n    # sync\n    parser_sync = subparsers.add_parser('sync',\n                                        help='Sync a library with an existing foler')\n    parser_sync.set_defaults(func=seaf_sync)\n    parser_sync.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)\n    parser_sync.add_argument('-C', help='the user config directory', type=str)\n    parser_sync.add_argument('-l', '--library', help='library id', type=str)\n    parser_sync.add_argument('-s', '--server', help='URL for seafile server', type=str)\n    parser_sync.add_argument('-u', '--username', help='username', type=str)\n    parser_sync.add_argument('-p', '--password', help='password', type=str)\n    parser_sync.add_argument('-T', '--token', help='token', type=str)\n    parser_sync.add_argument('-a', '--tfa', help='two-factor authentication', type=str)\n    parser_sync.add_argument('-d', '--folder', help='the existing local folder', type=str)\n    parser_sync.add_argument('-e', '--libpasswd', help='library password', type=str)\n\n    # desync\n    parser_desync = subparsers.add_parser('desync',\n                                          help='Desync a library with seafile server')\n    parser_desync.set_defaults(func=seaf_desync)\n    parser_desync.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)\n    parser_desync.add_argument('-d', '--folder', help='the local folder', type=str)\n\n    # create\n    parser_create = subparsers.add_parser('create',\n                                          help='Create a library')\n    parser_create.set_defaults(func=seaf_create)\n    parser_create.add_argument('-n', '--name', help='library name', type=str)\n    parser_create.add_argument('-t', '--desc', help='library description', type=str)\n    parser_create.add_argument('-e', '--libpasswd', help='library password', type=str)\n    parser_create.add_argument('-s', '--server', help='URL for seafile server', type=str)\n    parser_create.add_argument('-u', '--username', help='username', type=str)\n    parser_create.add_argument('-p', '--password', help='password', type=str)\n    parser_create.add_argument('-T', '--token', help='token', type=str)\n    parser_create.add_argument('-a', '--tfa', help='two-factor authentication', type=str)\n    parser_create.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)\n    parser_create.add_argument('-C', help='the user config directory', type=str)    \n\n    # config\n    parser_config = subparsers.add_parser('config',\n                                          help='Configure seafile client')\n    parser_config.set_defaults(func=seaf_config)\n    parser_config.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)\n    parser_config.add_argument('-k', '--key', help='configuration key', type=str)\n    parser_config.add_argument('-v', '--value', help='configuration value (if provided, key is set to this value)', type=str, required=False)\n\n    if len(sys.argv) == 1:\n        print(parser.format_help())\n        return\n\n    args = parser.parse_args()\n    args.func(args)\n\n\nif __name__ == '__main__':\n    main()\n    # print('device id is {}'.format(get_device_id(DEFAULT_CONF_DIR)))\n"
  },
  {
    "path": "autogen.sh",
    "content": "#!/bin/bash\n# Run this to generate all the initial makefiles, etc.\n\n: ${AUTOCONF=autoconf}\n: ${AUTOHEADER=autoheader}\n: ${AUTOMAKE=automake}\n: ${ACLOCAL=aclocal}\nif test \"$(uname)\" != \"Darwin\"; then\n    : ${LIBTOOLIZE=libtoolize}\nelse\n    : ${LIBTOOLIZE=glibtoolize}\nfi\n: ${INTLTOOLIZE=intltoolize}\n: ${LIBTOOL=libtool}\n\nsrcdir=`dirname $0`\ntest -z \"$srcdir\" && srcdir=.\n\nORIGDIR=`pwd`\ncd $srcdir\nPROJECT=ccnet\nTEST_TYPE=-f\nFILE=net/main.c\nCONFIGURE=configure.ac\n\nDIE=0\n\n($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {\n\techo\n\techo \"You must have autoconf installed to compile $PROJECT.\"\n\techo \"Download the appropriate package for your distribution,\"\n\techo \"or get the source tarball at ftp://ftp.gnu.org/pub/gnu/\"\n\tDIE=1\n}\n\n(grep \"^AC_PROG_INTLTOOL\" $srcdir/$CONFIGURE >/dev/null) && {\n  ($INTLTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {\n    echo\n    echo \"You must have \\`intltoolize' installed to compile $PROJECT.\"\n    echo \"Get ftp://ftp.gnome.org/pub/GNOME/stable/sources/intltool/intltool-0.22.tar.gz\"\n    echo \"(or a newer version if it is available)\"\n    DIE=1\n  }\n}\n\n($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {\n\techo\n\techo \"You must have automake installed to compile $PROJECT.\"\n\techo \"Get ftp://sourceware.cygnus.com/pub/automake/automake-1.7.tar.gz\"\n\techo \"(or a newer version if it is available)\"\n\tDIE=1\n}\n\nif test \"$(uname)\" != \"Darwin\"; then\n(grep \"^AC_PROG_LIBTOOL\" $CONFIGURE >/dev/null) && {\n  ($LIBTOOL --version) < /dev/null > /dev/null 2>&1 || {\n    echo\n    echo \"**Error**: You must have \\`libtool' installed to compile $PROJECT.\"\n    echo \"Get ftp://ftp.gnu.org/pub/gnu/libtool-1.4.tar.gz\"\n    echo \"(or a newer version if it is available)\"\n    DIE=1\n  }\n}\nfi\n\n\nif grep \"^AM_[A-Z0-9_]\\{1,\\}_GETTEXT\" \"$CONFIGURE\" >/dev/null; then\n  if grep \"sed.*POTFILES\" \"$CONFIGURE\" >/dev/null; then\n    GETTEXTIZE=\"\"\n  else\n    if grep \"^AM_GLIB_GNU_GETTEXT\" \"$CONFIGURE\" >/dev/null; then\n      GETTEXTIZE=\"glib-gettextize\"\n      GETTEXTIZE_URL=\"ftp://ftp.gtk.org/pub/gtk/v2.0/glib-2.0.0.tar.gz\"\n    else\n      GETTEXTIZE=\"gettextize\"\n      GETTEXTIZE_URL=\"ftp://alpha.gnu.org/gnu/gettext-0.10.35.tar.gz\"\n    fi\n\n    $GETTEXTIZE --version < /dev/null > /dev/null 2>&1\n    if test $? -ne 0; then\n      echo\n      echo \"**Error**: You must have \\`$GETTEXTIZE' installed to compile $PKG_NAME.\"\n      echo \"Get $GETTEXTIZE_URL\"\n      echo \"(or a newer version if it is available)\"\n      DIE=1\n    fi\n  fi\nfi\n\nif test \"$DIE\" -eq 1; then\n\texit 1\nfi\n\ndr=`dirname .`\necho processing $dr\naclocalinclude=\"$aclocalinclude -I m4\"\n\nif test x\"$MSYSTEM\" = x\"MINGW32\"; then\n    aclocalinclude=\"$aclocalinclude -I /mingw32/share/aclocal\"\nelif test \"$(uname)\" = \"Darwin\"; then\n    aclocalinclude=\"$aclocalinclude -I /opt/local/share/aclocal\"\nfi\n\n\necho \"Creating $dr/aclocal.m4 ...\"\ntest -r $dr/aclocal.m4 || touch $dr/aclocal.m4\necho \"Running glib-gettextize...  Ignore non-fatal messages.\"\necho \"no\" | glib-gettextize --force --copy\n\necho \"Making $dr/aclocal.m4 writable ...\"\ntest -r $dr/aclocal.m4 && chmod u+w $dr/aclocal.m4\n\necho \"Running intltoolize...\"\nintltoolize --copy --force --automake\n\necho \"Running $LIBTOOLIZE...\"\n$LIBTOOLIZE --force --copy\n\necho \"Running $ACLOCAL $aclocalinclude ...\"\n$ACLOCAL $aclocalinclude\n\necho \"Running $AUTOHEADER...\"\n$AUTOHEADER\n\necho \"Running $AUTOMAKE --gnu $am_opt ...\"\n$AUTOMAKE --add-missing --gnu $am_opt\n\necho \"Running $AUTOCONF ...\"\n$AUTOCONF\n"
  },
  {
    "path": "common/Makefile.am",
    "content": "SUBDIRS = cdc index\n\nnoinst_HEADERS = \\\n\tdiff-simple.h \\\n\tseafile-crypt.h \\\n\tcommon.h \\\n\tbranch-mgr.h \\\n\tfs-mgr.h \\\n\tblock-mgr.h \\\n\tcommit-mgr.h \\\n\tlog.h \\\n\tvc-common.h \\\n\tobj-store.h \\\n\tobj-backend.h \\\n\tblock-backend.h \\\n\tblock.h \\\n\tmq-mgr.h \\\n\tcurl-init.h\n"
  },
  {
    "path": "common/block-backend-fs.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef _WIN32_WINNT\n#define _WIN32_WINNT 0x500\n#endif\n\n#include \"common.h\"\n\n#include \"utils.h\"\n\n#include \"log.h\"\n\n#include <sys/stat.h>\n#include <fcntl.h>\n#ifndef WIN32\n#include <dirent.h>\n#endif\n\n#include \"block-backend.h\"\n#include \"obj-store.h\"\n\n\nstruct _BHandle {\n    char    *store_id;\n    int     version;\n    char    block_id[41];\n    int     fd;\n    int     rw_type;\n    char    *tmp_file;\n};\n\ntypedef struct {\n    char          *v0_block_dir;\n    int            v0_block_dir_len;\n    char          *block_dir;\n    int            block_dir_len;\n    char          *tmp_dir;\n    int            tmp_dir_len;\n} FsPriv;\n\nstatic char *\nget_block_path (BlockBackend *bend,\n                const char *block_sha1,\n                char path[],\n                const char *store_id,\n                int version);\n\nstatic int\nopen_tmp_file (BlockBackend *bend,\n               const char *basename,\n               char **path);\n\nstatic BHandle *\nblock_backend_fs_open_block (BlockBackend *bend,\n                             const char *store_id,\n                             int version,\n                             const char *block_id,\n                             int rw_type)\n{\n    BHandle *handle;\n    int fd = -1;\n    char *tmp_file;\n\n    g_return_val_if_fail (block_id != NULL, NULL);\n    g_return_val_if_fail (strlen(block_id) == 40, NULL);\n    g_return_val_if_fail (rw_type == BLOCK_READ || rw_type == BLOCK_WRITE, NULL);\n\n    if (rw_type == BLOCK_READ) {\n        char path[SEAF_PATH_MAX];\n        get_block_path (bend, block_id, path, store_id, version);\n        fd = g_open (path, O_RDONLY | O_BINARY, 0);\n        if (fd < 0) {\n            ccnet_warning (\"[block bend] failed to open block %s for read: %s\\n\",\n                           block_id, strerror(errno));\n            return NULL;\n        }\n    } else {\n        fd = open_tmp_file (bend, block_id, &tmp_file);\n        if (fd < 0) {\n            ccnet_warning (\"[block bend] failed to open block %s for write: %s\\n\",\n                           block_id, strerror(errno));\n            return NULL;\n        }\n    }\n\n    handle = g_new0(BHandle, 1);\n    handle->fd = fd;\n    memcpy (handle->block_id, block_id, 41);\n    handle->rw_type = rw_type;\n    if (rw_type == BLOCK_WRITE)\n        handle->tmp_file = tmp_file;\n    if (store_id)\n        handle->store_id = g_strdup(store_id);\n    handle->version = version;\n\n    return handle;\n}\n\nstatic int\nblock_backend_fs_read_block (BlockBackend *bend,\n                             BHandle *handle,\n                             void *buf, int len)\n{\n    return (readn (handle->fd, buf, len));\n}\n\nstatic int\nblock_backend_fs_write_block (BlockBackend *bend,\n                                BHandle *handle,\n                                const void *buf, int len)\n{\n    return (writen (handle->fd, buf, len));\n}\n\nstatic int\nblock_backend_fs_close_block (BlockBackend *bend,\n                                BHandle *handle)\n{\n    int ret;\n\n    ret = close (handle->fd);\n\n    return ret;\n}\n\nstatic void\nblock_backend_fs_block_handle_free (BlockBackend *bend,\n                                    BHandle *handle)\n{\n    if (handle->rw_type == BLOCK_WRITE) {\n        /* make sure the tmp file is removed even on failure. */\n        g_unlink (handle->tmp_file);\n        g_free (handle->tmp_file);\n    }\n    g_free (handle->store_id);\n    g_free (handle);\n}\n\nstatic int\ncreate_parent_path (const char *path)\n{\n    char *dir = g_path_get_dirname (path);\n    if (!dir)\n        return -1;\n\n    if (g_file_test (dir, G_FILE_TEST_EXISTS)) {\n        g_free (dir);\n        return 0;\n    }\n\n    if (checkdir_with_mkdir (dir) < 0) {\n        seaf_warning (\"Failed to create object parent path: %s.\\n\", dir);\n        g_free (dir);\n        return -1;\n    }\n\n    g_free (dir);\n    return 0;\n}\n\nstatic int\nblock_backend_fs_commit_block (BlockBackend *bend,\n                               BHandle *handle)\n{\n    char path[SEAF_PATH_MAX];\n\n    g_return_val_if_fail (handle->rw_type == BLOCK_WRITE, -1);\n\n    get_block_path (bend, handle->block_id, path, handle->store_id, handle->version);\n\n    if (create_parent_path (path) < 0) {\n        seaf_warning (\"Failed to create path for block %s:%s.\\n\",\n                      handle->store_id, handle->block_id);\n        return -1;\n    }\n\n    if (g_rename (handle->tmp_file, path) < 0) {\n        seaf_warning (\"[block bend] failed to commit block %s:%s: %s\\n\",\n                      handle->store_id, handle->block_id, strerror(errno));\n        return -1;\n    }\n\n    return 0;\n}\n    \nstatic gboolean\nblock_backend_fs_block_exists (BlockBackend *bend,\n                               const char *store_id,\n                               int version,\n                               const char *block_sha1)\n{\n    char block_path[SEAF_PATH_MAX];\n\n    get_block_path (bend, block_sha1, block_path, store_id, version);\n\n    if (g_access (block_path, F_OK) == 0)\n        return TRUE;\n    else\n        return FALSE;\n}\n\nstatic int\nblock_backend_fs_remove_block (BlockBackend *bend,\n                               const char *store_id,\n                               int version,\n                               const char *block_id)\n{\n    char path[SEAF_PATH_MAX];\n\n    get_block_path (bend, block_id, path, store_id, version);\n\n    return g_unlink (path);\n}\n\nstatic BMetadata *\nblock_backend_fs_stat_block (BlockBackend *bend,\n                             const char *store_id,\n                             int version,\n                             const char *block_id)\n{\n    char path[SEAF_PATH_MAX];\n    SeafStat st;\n    BMetadata *block_md;\n\n    get_block_path (bend, block_id, path, store_id, version);\n    if (seaf_stat (path, &st) < 0) {\n        seaf_warning (\"[block bend] Failed to stat block %s:%s at %s: %s.\\n\",\n                      store_id, block_id, path, strerror(errno));\n        return NULL;\n    }\n    block_md = g_new0(BMetadata, 1);\n    memcpy (block_md->id, block_id, 40);\n    block_md->size = (uint32_t) st.st_size;\n\n    return block_md;\n}\n\nstatic BMetadata *\nblock_backend_fs_stat_block_by_handle (BlockBackend *bend,\n                                       BHandle *handle)\n{\n    SeafStat st;\n    BMetadata *block_md;\n\n    if (seaf_fstat (handle->fd, &st) < 0) {\n        seaf_warning (\"[block bend] Failed to stat block %s:%s.\\n\",\n                      handle->store_id, handle->block_id);\n        return NULL;\n    }\n    block_md = g_new0(BMetadata, 1);\n    memcpy (block_md->id, handle->block_id, 40);\n    block_md->size = (uint32_t) st.st_size;\n\n    return block_md;\n}\n\nstatic int\nblock_backend_fs_foreach_block (BlockBackend *bend,\n                                const char *store_id,\n                                int version,\n                                SeafBlockFunc process,\n                                void *user_data)\n{\n    FsPriv *priv = bend->be_priv;\n    char *block_dir = NULL;\n    int dir_len;\n    GDir *dir1 = NULL, *dir2;\n    const char *dname1, *dname2;\n    char block_id[128];\n    char path[SEAF_PATH_MAX], *pos;\n    int ret = 0;\n\n#if defined MIGRATION\n    if (version > 0)\n        block_dir = g_build_filename (priv->block_dir, store_id, NULL);\n    else\n        block_dir = g_strdup(priv->v0_block_dir);\n#else\n    block_dir = g_build_filename (priv->block_dir, store_id, NULL);\n#endif\n    dir_len = strlen (block_dir);\n\n    dir1 = g_dir_open (block_dir, 0, NULL);\n    if (!dir1) {\n        goto out;\n    }\n\n    memcpy (path, block_dir, dir_len);\n    pos = path + dir_len;\n\n    while ((dname1 = g_dir_read_name(dir1)) != NULL) {\n        snprintf (pos, sizeof(path) - dir_len, \"/%s\", dname1);\n\n        dir2 = g_dir_open (path, 0, NULL);\n        if (!dir2) {\n            seaf_warning (\"Failed to open block dir %s.\\n\", path);\n            continue;\n        }\n\n        while ((dname2 = g_dir_read_name(dir2)) != NULL) {\n            snprintf (block_id, sizeof(block_id), \"%s%s\", dname1, dname2);\n            if (!process (store_id, version, block_id, user_data)) {\n                g_dir_close (dir2);\n                goto out;\n            }\n        }\n        g_dir_close (dir2);\n    }\n\nout:\n    if (dir1)\n        g_dir_close (dir1);\n    g_free (block_dir);\n\n    return ret;\n}\n\nstatic int\nblock_backend_fs_copy (BlockBackend *bend,\n                       const char *src_store_id,\n                       int src_version,\n                       const char *dst_store_id,\n                       int dst_version,\n                       const char *block_id)\n{\n    char src_path[SEAF_PATH_MAX];\n    char dst_path[SEAF_PATH_MAX];\n\n    get_block_path (bend, block_id, src_path, src_store_id, src_version);\n    get_block_path (bend, block_id, dst_path, dst_store_id, dst_version);\n\n    if (g_file_test (dst_path, G_FILE_TEST_EXISTS))\n        return 0;\n\n    if (create_parent_path (dst_path) < 0) {\n        seaf_warning (\"Failed to create dst path %s for block %s.\\n\",\n                      dst_path, block_id);\n        return -1;\n    }\n\n#ifdef WIN32\n    if (!CreateHardLinkA (dst_path, src_path, NULL)) {\n        seaf_warning (\"Failed to link %s to %s: %lu.\\n\",\n                      src_path, dst_path, GetLastError());\n        return -1;\n    }\n    return 0;\n#else\n    int ret = link (src_path, dst_path);\n    if (ret < 0 && errno != EEXIST) {\n        seaf_warning (\"Failed to link %s to %s: %s.\\n\",\n                      src_path, dst_path, strerror(errno));\n        return -1;\n    }\n    return ret;\n#endif\n}\n\nstatic int\nblock_backend_fs_remove_store (BlockBackend *bend, const char *store_id)\n{\n    FsPriv *priv = bend->be_priv;\n    char *block_dir = NULL;\n    GDir *dir1, *dir2;\n    const char *dname1, *dname2;\n    char *path1, *path2;\n\n    block_dir = g_build_filename (priv->block_dir, store_id, NULL);\n\n    dir1 = g_dir_open (block_dir, 0, NULL);\n    if (!dir1) {\n        g_free (block_dir);\n        return 0;\n    }\n\n    while ((dname1 = g_dir_read_name(dir1)) != NULL) {\n        path1 = g_build_filename (block_dir, dname1, NULL);\n\n        dir2 = g_dir_open (path1, 0, NULL);\n        if (!dir2) {\n            seaf_warning (\"Failed to open block dir %s.\\n\", path1);\n            g_dir_close (dir1);\n            g_free (path1);\n            g_free (block_dir);\n            return -1;\n        }\n\n        while ((dname2 = g_dir_read_name(dir2)) != NULL) {\n            path2 = g_build_filename (path1, dname2, NULL);\n            g_unlink (path2);\n            g_free (path2);\n        }\n        g_dir_close (dir2);\n\n        g_rmdir (path1);\n        g_free (path1);\n    }\n\n    g_dir_close (dir1);\n    g_rmdir (block_dir);\n    g_free (block_dir);\n\n    return 0;\n}\n\nstatic int\nblock_backend_fs_rewind_block (BlockBackend *bend,\n                              BHandle *handle)\n{\n    return seaf_util_lseek (handle->fd, 0, SEEK_SET);\n}\n\nstatic char *\nget_block_path (BlockBackend *bend,\n                const char *block_sha1,\n                char path[],\n                const char *store_id,\n                int version)\n{\n    FsPriv *priv = bend->be_priv;\n    char *pos = path;\n    int n;\n\n#if defined MIGRATION\n    if (version > 0) {\n        n = snprintf (path, SEAF_PATH_MAX, \"%s/%s/\", priv->block_dir, store_id);\n        pos += n;\n    } else {\n        memcpy (pos, priv->v0_block_dir, priv->v0_block_dir_len);\n        pos[priv->v0_block_dir_len] = '/';\n        pos += priv->v0_block_dir_len + 1;\n    }\n#else\n    n = snprintf (path, SEAF_PATH_MAX, \"%s/%s/\", priv->block_dir, store_id);\n    pos += n;\n#endif\n\n    memcpy (pos, block_sha1, 2);\n    pos[2] = '/';\n    pos += 3;\n\n    memcpy (pos, block_sha1 + 2, 41 - 2);\n\n    return path;\n}\n\nstatic int\nopen_tmp_file (BlockBackend *bend,\n               const char *basename,\n               char **path)\n{\n    FsPriv *priv = bend->be_priv;\n    int fd;\n\n    *path = g_strdup_printf (\"%s/%s.XXXXXX\", priv->tmp_dir, basename);\n    fd = g_mkstemp (*path);\n    if (fd < 0)\n        g_free (*path);\n\n    return fd;\n}\n\nBlockBackend *\nblock_backend_fs_new (const char *seaf_dir, const char *tmp_dir)\n{\n    BlockBackend *bend;\n    FsPriv *priv;\n\n    bend = g_new0(BlockBackend, 1);\n    priv = g_new0(FsPriv, 1);\n    bend->be_priv = priv;\n\n    priv->v0_block_dir = g_build_filename (seaf_dir, \"blocks\", NULL);\n    priv->v0_block_dir_len = strlen(priv->v0_block_dir);\n\n    priv->block_dir = g_build_filename (seaf_dir, \"storage\", \"blocks\", NULL);\n    priv->block_dir_len = strlen (priv->block_dir);\n\n    priv->tmp_dir = g_strdup (tmp_dir);\n    priv->tmp_dir_len = strlen (tmp_dir);\n\n    if (checkdir_with_mkdir (priv->block_dir) < 0) {\n        seaf_warning (\"Block dir %s does not exist and\"\n                   \" is unable to create\\n\", priv->block_dir);\n        goto onerror;\n    }\n\n    if (checkdir_with_mkdir (tmp_dir) < 0) {\n        seaf_warning (\"Blocks tmp dir %s does not exist and\"\n                   \" is unable to create\\n\", tmp_dir);\n        goto onerror;\n    }\n\n    bend->open_block = block_backend_fs_open_block;\n    bend->read_block = block_backend_fs_read_block;\n    bend->write_block = block_backend_fs_write_block;\n    bend->commit_block = block_backend_fs_commit_block;\n    bend->close_block = block_backend_fs_close_block;\n    bend->exists = block_backend_fs_block_exists;\n    bend->remove_block = block_backend_fs_remove_block;\n    bend->stat_block = block_backend_fs_stat_block;\n    bend->stat_block_by_handle = block_backend_fs_stat_block_by_handle;\n    bend->block_handle_free = block_backend_fs_block_handle_free;\n    bend->foreach_block = block_backend_fs_foreach_block;\n    bend->remove_store = block_backend_fs_remove_store;\n    bend->copy = block_backend_fs_copy;\n    bend->rewind_block = block_backend_fs_rewind_block;\n\n    return bend;\n\nonerror:\n    g_free (bend);\n    g_free (bend->be_priv);\n\n    return NULL;\n}\n"
  },
  {
    "path": "common/block-backend.c",
    "content": "\n#include \"common.h\"\n\n#include \"log.h\"\n\n#include \"block-backend.h\"\n\nextern BlockBackend *\nblock_backend_fs_new (const char *block_dir, const char *tmp_dir);\n\nBlockBackend*\nload_filesystem_block_backend(GKeyFile *config)\n{\n    BlockBackend *bend;\n    char *tmp_dir;\n    char *block_dir;\n    \n    block_dir = g_key_file_get_string (config, \"block_backend\", \"block_dir\", NULL);\n    if (!block_dir) {\n        seaf_warning (\"Block dir not set in config.\\n\");\n        return NULL;\n    }\n\n    tmp_dir = g_key_file_get_string (config, \"block_backend\", \"tmp_dir\", NULL);\n    if (!tmp_dir) {\n        seaf_warning (\"Block tmp dir not set in config.\\n\");\n        return NULL;\n    }\n\n    bend = block_backend_fs_new (block_dir, tmp_dir);\n\n    g_free (block_dir);\n    g_free (tmp_dir);\n    return bend;\n}\n\nBlockBackend*\nload_block_backend (GKeyFile *config)\n{\n    char *backend;\n    BlockBackend *bend;\n\n    backend = g_key_file_get_string (config, \"block_backend\", \"name\", NULL);\n    if (!backend) {\n        return NULL;\n    }\n\n    if (strcmp(backend, \"filesystem\") == 0) {\n        bend = load_filesystem_block_backend(config);\n        g_free (backend);\n        return bend;\n    }\n\n    seaf_warning (\"Unknown backend\\n\");\n    return NULL;\n}\n"
  },
  {
    "path": "common/block-backend.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef BLOCK_BACKEND_H\n#define BLOCK_BACKEND_H\n\n#include \"block.h\"\n\ntypedef struct BlockBackend BlockBackend;\n\nstruct BlockBackend {\n    \n    BHandle* (*open_block) (BlockBackend *bend,\n                            const char *store_id, int version,\n                            const char *block_id, int rw_type);\n\n    int      (*read_block) (BlockBackend *bend, BHandle *handle, void *buf, int len);\n    \n    int      (*write_block) (BlockBackend *bend, BHandle *handle, const void *buf, int len);\n    \n    int      (*commit_block) (BlockBackend *bend, BHandle *handle);\n\n    int      (*close_block) (BlockBackend *bend, BHandle *handle);\n\n    int      (*exists) (BlockBackend *bend,\n                        const char *store_id, int version,\n                        const char *block_id);\n\n    int      (*remove_block) (BlockBackend *bend,\n                              const char *store_id, int version,\n                              const char *block_id);\n\n    BMetadata* (*stat_block) (BlockBackend *bend,\n                              const char *store_id, int version,\n                              const char *block_id);\n    \n    BMetadata* (*stat_block_by_handle) (BlockBackend *bend, BHandle *handle);\n\n    void     (*block_handle_free) (BlockBackend *bend, BHandle *handle);\n\n    int      (*foreach_block) (BlockBackend *bend,\n                               const char *store_id,\n                               int version,\n                               SeafBlockFunc process,\n                               void *user_data);\n\n    int         (*copy) (BlockBackend *bend,\n                         const char *src_store_id,\n                         int src_version,\n                         const char *dst_store_id,\n                         int dst_version,\n                         const char *block_id);\n\n    /* Only valid for version 1 repo. Remove all blocks for the repo. */\n    int      (*remove_store) (BlockBackend *bend,\n                              const char *store_id);\n\n    int      (*rewind_block) (BlockBackend *bend,\n                              BHandle *handle);\n\n    void*    be_priv;           /* backend private field */\n\n};\n\n\nBlockBackend* load_block_backend (GKeyFile *config);\n\n#endif\n"
  },
  {
    "path": "common/block-mgr.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#include \"seafile-session.h\"\n#include \"utils.h\"\n#include \"block-mgr.h\"\n#include \"log.h\"\n\n#include <stdio.h>\n#include <errno.h>\n#ifndef WIN32\n#include <unistd.h>\n#include <dirent.h>\n#endif\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <sys/types.h>\n#include <glib/gstdio.h>\n\n#include \"block-backend.h\"\n\n#define SEAF_BLOCK_DIR \"blocks\"\n\n\nextern BlockBackend *\nblock_backend_fs_new (const char *block_dir, const char *tmp_dir);\n\n\nSeafBlockManager *\nseaf_block_manager_new (struct _SeafileSession *seaf,\n                        const char *seaf_dir)\n{\n    SeafBlockManager *mgr;\n\n    mgr = g_new0 (SeafBlockManager, 1);\n    mgr->seaf = seaf;\n\n    mgr->backend = block_backend_fs_new (seaf_dir, seaf->tmp_file_dir);\n    if (!mgr->backend) {\n        seaf_warning (\"[Block mgr] Failed to load backend.\\n\");\n        goto onerror;\n    }\n\n    return mgr;\n\nonerror:\n    g_free (mgr);\n\n    return NULL;\n}\n\nint\nseaf_block_manager_init (SeafBlockManager *mgr)\n{\n    return 0;\n}\n\n\nBlockHandle *\nseaf_block_manager_open_block (SeafBlockManager *mgr,\n                               const char *store_id,\n                               int version,\n                               const char *block_id,\n                               int rw_type)\n{\n    if (!store_id || !is_uuid_valid(store_id) ||\n        !block_id || !is_object_id_valid(block_id))\n        return NULL;\n\n    return mgr->backend->open_block (mgr->backend,\n                                     store_id, version,\n                                     block_id, rw_type);\n}\n\nint\nseaf_block_manager_read_block (SeafBlockManager *mgr,\n                               BlockHandle *handle,\n                               void *buf, int len)\n{\n    return mgr->backend->read_block (mgr->backend, handle, buf, len);\n}\n\nint\nseaf_block_manager_write_block (SeafBlockManager *mgr,\n                                BlockHandle *handle,\n                                const void *buf, int len)\n{\n    return mgr->backend->write_block (mgr->backend, handle, buf, len);\n}\n\nint\nseaf_block_manager_close_block (SeafBlockManager *mgr,\n                                BlockHandle *handle)\n{\n    return mgr->backend->close_block (mgr->backend, handle);\n}\n\nvoid\nseaf_block_manager_block_handle_free (SeafBlockManager *mgr,\n                                      BlockHandle *handle)\n{\n    mgr->backend->block_handle_free (mgr->backend, handle);\n}\n\nint\nseaf_block_manager_commit_block (SeafBlockManager *mgr,\n                                 BlockHandle *handle)\n{\n    return mgr->backend->commit_block (mgr->backend, handle);\n}\n    \ngboolean seaf_block_manager_block_exists (SeafBlockManager *mgr,\n                                          const char *store_id,\n                                          int version,\n                                          const char *block_id)\n{\n    if (!store_id || !is_uuid_valid(store_id) ||\n        !block_id || !is_object_id_valid(block_id))\n        return FALSE;\n\n    return mgr->backend->exists (mgr->backend, store_id, version, block_id);\n}\n\nint\nseaf_block_manager_remove_block (SeafBlockManager *mgr,\n                                 const char *store_id,\n                                 int version,\n                                 const char *block_id)\n{\n    if (!store_id || !is_uuid_valid(store_id) ||\n        !block_id || !is_object_id_valid(block_id))\n        return -1;\n\n    return mgr->backend->remove_block (mgr->backend, store_id, version, block_id);\n}\n\nBlockMetadata *\nseaf_block_manager_stat_block (SeafBlockManager *mgr,\n                               const char *store_id,\n                               int version,\n                               const char *block_id)\n{\n    if (!store_id || !is_uuid_valid(store_id) ||\n        !block_id || !is_object_id_valid(block_id))\n        return NULL;\n\n    return mgr->backend->stat_block (mgr->backend, store_id, version, block_id);\n}\n\nBlockMetadata *\nseaf_block_manager_stat_block_by_handle (SeafBlockManager *mgr,\n                                         BlockHandle *handle)\n{\n    return mgr->backend->stat_block_by_handle (mgr->backend, handle);\n}\n\nint\nseaf_block_manager_foreach_block (SeafBlockManager *mgr,\n                                  const char *store_id,\n                                  int version,\n                                  SeafBlockFunc process,\n                                  void *user_data)\n{\n    return mgr->backend->foreach_block (mgr->backend,\n                                        store_id, version,\n                                        process, user_data);\n}\n\nint\nseaf_block_manager_copy_block (SeafBlockManager *mgr,\n                               const char *src_store_id,\n                               int src_version,\n                               const char *dst_store_id,\n                               int dst_version,\n                               const char *block_id)\n{\n    if (strcmp (block_id, EMPTY_SHA1) == 0)\n        return 0;\n\n    return mgr->backend->copy (mgr->backend,\n                               src_store_id,\n                               src_version,\n                               dst_store_id,\n                               dst_version,\n                               block_id);\n}\n\nstatic gboolean\nget_block_number (const char *store_id,\n                  int version,\n                  const char *block_id,\n                  void *data)\n{\n    guint64 *n_blocks = data;\n\n    ++(*n_blocks);\n\n    return TRUE;\n}\n\nguint64\nseaf_block_manager_get_block_number (SeafBlockManager *mgr,\n                                     const char *store_id,\n                                     int version)\n{\n    guint64 n_blocks = 0;\n\n    seaf_block_manager_foreach_block (mgr, store_id, version,\n                                      get_block_number, &n_blocks);\n\n    return n_blocks;\n}\n\ngboolean\nseaf_block_manager_verify_block (SeafBlockManager *mgr,\n                                 const char *store_id,\n                                 int version,\n                                 const char *block_id,\n                                 gboolean *io_error)\n{\n    BlockHandle *h;\n    char buf[10240];\n    int n;\n    GChecksum *cs;\n    const char *check_id;\n    gboolean ret;\n\n    h = seaf_block_manager_open_block (mgr,\n                                       store_id, version,\n                                       block_id, BLOCK_READ);\n    if (!h) {\n        seaf_warning (\"Failed to open block %s:%.8s.\\n\", store_id, block_id);\n        *io_error = TRUE;\n        return FALSE;\n    }\n\n    cs = g_checksum_new (G_CHECKSUM_SHA1);\n    while (1) {\n        n = seaf_block_manager_read_block (mgr, h, buf, sizeof(buf));\n        if (n < 0) {\n            seaf_warning (\"Failed to read block %s:%.8s.\\n\", store_id, block_id);\n            *io_error = TRUE;\n            g_checksum_free (cs);\n            return FALSE;\n        }\n        if (n == 0)\n            break;\n\n        g_checksum_update (cs, (guchar *)buf, n);\n    }\n\n    seaf_block_manager_close_block (mgr, h);\n    seaf_block_manager_block_handle_free (mgr, h);\n\n    check_id = g_checksum_get_string (cs);\n\n    if (strcmp (check_id, block_id) == 0)\n        ret = TRUE;\n    else\n        ret = FALSE;\n\n    g_checksum_free (cs);\n    return ret;\n}\n\nint\nseaf_block_manager_remove_store (SeafBlockManager *mgr,\n                                 const char *store_id)\n{\n    return mgr->backend->remove_store (mgr->backend, store_id);\n}\n\nint\nseaf_block_manager_rewind_block (SeafBlockManager *mgr,\n                                 BlockHandle *handle)\n{\n    return mgr->backend->rewind_block (mgr->backend, handle);\n}\n"
  },
  {
    "path": "common/block-mgr.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef SEAF_BLOCK_MGR_H\n#define SEAF_BLOCK_MGR_H\n\n#include <glib.h>\n#include <glib-object.h>\n#include <stdint.h>\n\n#include \"block.h\"\n\nstruct _SeafileSession;\n\ntypedef struct _SeafBlockManager SeafBlockManager;\n\nstruct _SeafBlockManager {\n    struct _SeafileSession *seaf;\n\n    struct BlockBackend *backend;\n};\n\n\nSeafBlockManager *\nseaf_block_manager_new (struct _SeafileSession *seaf,\n                        const char *seaf_dir);\n\n/*\n * Open a block for read or write.\n *\n * @store_id: id for the block store\n * @version: data format version for the repo\n * @block_id: ID of block.\n * @rw_type: BLOCK_READ or BLOCK_WRITE.\n * Returns: A handle for the block.\n */\nBlockHandle *\nseaf_block_manager_open_block (SeafBlockManager *mgr,\n                               const char *store_id,\n                               int version,\n                               const char *block_id,\n                               int rw_type);\n\n/*\n * Read data from a block.\n * The semantics is similar to readn.\n *\n * @handle: Hanlde returned by seaf_block_manager_open_block().\n * @buf: Data wuold be copied into this buf.\n * @len: At most @len bytes would be read.\n *\n * Returns: the bytes read.\n */\nint\nseaf_block_manager_read_block (SeafBlockManager *mgr,\n                               BlockHandle *handle,\n                               void *buf, int len);\n\n/*\n * Write data to a block.\n * The semantics is similar to writen.\n *\n * @handle: Hanlde returned by seaf_block_manager_open_block().\n * @buf: Data to be written to the block.\n * @len: At most @len bytes would be written.\n *\n * Returns: the bytes written.\n */\nint\nseaf_block_manager_write_block (SeafBlockManager *mgr,\n                                BlockHandle *handle,\n                                const void *buf, int len);\n\n/*\n * Commit a block to storage.\n * The block must be opened for write.\n *\n * @handle: Hanlde returned by seaf_block_manager_open_block().\n *\n * Returns: 0 on success, -1 on error.\n */\nint\nseaf_block_manager_commit_block (SeafBlockManager *mgr,\n                                 BlockHandle *handle);\n\n/*\n * Close an open block.\n *\n * @handle: Hanlde returned by seaf_block_manager_open_block().\n *\n * Returns: 0 on success, -1 on error.\n */\nint\nseaf_block_manager_close_block (SeafBlockManager *mgr,\n                                BlockHandle *handle);\n\nvoid\nseaf_block_manager_block_handle_free (SeafBlockManager *mgr,\n                                      BlockHandle *handle);\n\ngboolean \nseaf_block_manager_block_exists (SeafBlockManager *mgr,\n                                 const char *store_id,\n                                 int version,\n                                 const char *block_id);\n\nint\nseaf_block_manager_remove_block (SeafBlockManager *mgr,\n                                 const char *store_id,\n                                 int version,\n                                 const char *block_id);\n\nBlockMetadata *\nseaf_block_manager_stat_block (SeafBlockManager *mgr,\n                               const char *store_id,\n                               int version,\n                               const char *block_id);\n\nBlockMetadata *\nseaf_block_manager_stat_block_by_handle (SeafBlockManager *mgr,\n                                         BlockHandle *handle);\n\nint\nseaf_block_manager_foreach_block (SeafBlockManager *mgr,\n                                  const char *store_id,\n                                  int version,\n                                  SeafBlockFunc process,\n                                  void *user_data);\n\nint\nseaf_block_manager_copy_block (SeafBlockManager *mgr,\n                               const char *src_store_id,\n                               int src_version,\n                               const char *dst_store_id,\n                               int dst_version,\n                               const char *block_id);\n\n/* Remove all blocks for a repo. Only valid for version 1 repo. */\nint\nseaf_block_manager_remove_store (SeafBlockManager *mgr,\n                                 const char *store_id);\n\nguint64\nseaf_block_manager_get_block_number (SeafBlockManager *mgr,\n                                     const char *store_id,\n                                     int version);\n\ngboolean\nseaf_block_manager_verify_block (SeafBlockManager *mgr,\n                                 const char *store_id,\n                                 int version,\n                                 const char *block_id,\n                                 gboolean *io_error);\n\nint\nseaf_block_manager_rewind_block (SeafBlockManager *mgr,\n                                 BlockHandle *handle);\n\n#endif\n"
  },
  {
    "path": "common/block.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef BLOCK_H\n#define BLOCK_H\n\ntypedef struct _BMetadata BlockMetadata;\ntypedef struct _BMetadata BMetadata;\n\nstruct _BMetadata {\n    char        id[41];\n    uint32_t    size;\n};\n\n/* Opaque block handle.\n */\ntypedef struct _BHandle BlockHandle;\ntypedef struct _BHandle BHandle;\n\nenum {\n    BLOCK_READ,\n    BLOCK_WRITE,\n};\n\ntypedef gboolean (*SeafBlockFunc) (const char *store_id,\n                                   int version,\n                                   const char *block_id,\n                                   void *user_data);\n\n#endif\n"
  },
  {
    "path": "common/branch-mgr.c",
    "content": "#include \"common.h\"\n\n#include \"log.h\"\n\n#ifndef SEAFILE_SERVER\n#include \"db.h\"\n#else\n#include \"seaf-db.h\"\n#endif\n\n#include \"seafile-session.h\"\n\n#include \"branch-mgr.h\"\n\n#define BRANCH_DB \"branch.db\"\n\nSeafBranch *\nseaf_branch_new (const char *name, const char *repo_id, const char *commit_id)\n{\n    SeafBranch *branch;\n\n    branch = g_new0 (SeafBranch, 1);\n\n    branch->name = g_strdup (name);\n    memcpy (branch->repo_id, repo_id, 36);\n    branch->repo_id[36] = '\\0';\n    memcpy (branch->commit_id, commit_id, 40);\n    branch->commit_id[40] = '\\0';\n\n    branch->ref = 1;\n\n    return branch;\n}\n\nvoid\nseaf_branch_free (SeafBranch *branch)\n{\n    if (branch == NULL) return;\n    g_free (branch->name);\n    g_free (branch);\n}\n\nvoid\nseaf_branch_list_free (GList *blist)\n{\n    GList *ptr;\n\n    for (ptr = blist; ptr; ptr = ptr->next) {\n        seaf_branch_unref (ptr->data);\n    }\n    g_list_free (blist);\n}\n\n\nvoid\nseaf_branch_set_commit (SeafBranch *branch, const char *commit_id)\n{\n    memcpy (branch->commit_id, commit_id, 40);\n    branch->commit_id[40] = '\\0';\n}\n\nvoid\nseaf_branch_ref (SeafBranch *branch)\n{\n    branch->ref++;\n}\n\nvoid\nseaf_branch_unref (SeafBranch *branch)\n{\n    if (!branch)\n        return;\n\n    if (--branch->ref <= 0)\n        seaf_branch_free (branch);\n}\n\nstruct _SeafBranchManagerPriv {\n    sqlite3 *db;\n#ifndef SEAFILE_SERVER\n    pthread_mutex_t db_lock;\n#endif\n\n#if defined( SEAFILE_SERVER ) && defined( FULL_FEATURE )\n    uint32_t cevent_id;\n#endif    \n};\n\n#if defined( SEAFILE_SERVER ) && defined( FULL_FEATURE )\n\n#include \"mq-mgr.h\"\n#include <ccnet/cevent.h>\nstatic void publish_repo_update_event (CEvent *event, void *data);\n\n#endif    \n\nstatic int open_db (SeafBranchManager *mgr);\n\nSeafBranchManager *\nseaf_branch_manager_new (struct _SeafileSession *seaf)\n{\n    SeafBranchManager *mgr;\n\n    mgr = g_new0 (SeafBranchManager, 1);\n    mgr->priv = g_new0 (SeafBranchManagerPriv, 1);\n    mgr->seaf = seaf;\n\n#ifndef SEAFILE_SERVER\n    pthread_mutex_init (&mgr->priv->db_lock, NULL);\n#endif\n\n    return mgr;\n}\n\nint\nseaf_branch_manager_init (SeafBranchManager *mgr)\n{\n#if defined( SEAFILE_SERVER ) && defined( FULL_FEATURE )\n    mgr->priv->cevent_id = cevent_manager_register (seaf->ev_mgr,\n                                    (cevent_handler)publish_repo_update_event,\n                                                    NULL);\n#endif    \n\n    return open_db (mgr);\n}\n\nstatic int\nopen_db (SeafBranchManager *mgr)\n{\n#ifndef SEAFILE_SERVER\n\n    char *db_path;\n    const char *sql;\n\n    db_path = g_build_filename (mgr->seaf->seaf_dir, BRANCH_DB, NULL);\n    if (sqlite_open_db (db_path, &mgr->priv->db) < 0) {\n        g_critical (\"[Branch mgr] Failed to open branch db\\n\");\n        g_free (db_path);\n        return -1;\n    }\n    g_free (db_path);\n\n    sql = \"CREATE TABLE IF NOT EXISTS Branch (\"\n          \"name TEXT, repo_id TEXT, commit_id TEXT);\";\n    if (sqlite_query_exec (mgr->priv->db, sql) < 0)\n        return -1;\n\n    sql = \"CREATE INDEX IF NOT EXISTS branch_index ON Branch(repo_id, name);\";\n    if (sqlite_query_exec (mgr->priv->db, sql) < 0)\n        return -1;\n\n#elif defined FULL_FEATURE\n\n    char *sql;\n    switch (seaf_db_type (mgr->seaf->db)) {\n    case SEAF_DB_TYPE_MYSQL:\n        sql = \"CREATE TABLE IF NOT EXISTS Branch (\"\n            \"name VARCHAR(10), repo_id CHAR(41), commit_id CHAR(41),\"\n            \"PRIMARY KEY (repo_id, name)) ENGINE = INNODB\";\n        if (seaf_db_query (mgr->seaf->db, sql) < 0)\n            return -1;\n        break;\n    case SEAF_DB_TYPE_PGSQL:\n        sql = \"CREATE TABLE IF NOT EXISTS Branch (\"\n            \"name VARCHAR(10), repo_id CHAR(40), commit_id CHAR(40),\"\n            \"PRIMARY KEY (repo_id, name))\";\n        if (seaf_db_query (mgr->seaf->db, sql) < 0)\n            return -1;\n        break;\n    case SEAF_DB_TYPE_SQLITE:\n        sql = \"CREATE TABLE IF NOT EXISTS Branch (\"\n            \"name VARCHAR(10), repo_id CHAR(41), commit_id CHAR(41),\"\n            \"PRIMARY KEY (repo_id, name))\";\n        if (seaf_db_query (mgr->seaf->db, sql) < 0)\n            return -1;\n        break;\n    }\n\n#endif\n\n    return 0;\n}\n\nint\nseaf_branch_manager_add_branch (SeafBranchManager *mgr, SeafBranch *branch)\n{\n#ifndef SEAFILE_SERVER\n    char sql[256];\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    sqlite3_snprintf (sizeof(sql), sql,\n                      \"SELECT 1 FROM Branch WHERE name=%Q and repo_id=%Q\",\n                      branch->name, branch->repo_id);\n    if (sqlite_check_for_existence (mgr->priv->db, sql))\n        sqlite3_snprintf (sizeof(sql), sql,\n                          \"UPDATE Branch SET commit_id=%Q WHERE \"\n                          \"name=%Q and repo_id=%Q\",\n                          branch->commit_id, branch->name, branch->repo_id);\n    else\n        sqlite3_snprintf (sizeof(sql), sql,\n                          \"INSERT INTO Branch VALUES (%Q, %Q, %Q)\",\n                          branch->name, branch->repo_id, branch->commit_id);\n\n    sqlite_query_exec (mgr->priv->db, sql);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    return 0;\n#else\n    char *sql;\n    SeafDB *db = mgr->seaf->db;\n\n    if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) {\n        gboolean exists, err;\n        int rc;\n\n        sql = \"SELECT repo_id FROM Branch WHERE name=? AND repo_id=?\";\n        exists = seaf_db_statement_exists(db, sql, &err,\n                                          2, \"string\", branch->name,\n                                          \"string\", branch->repo_id);\n        if (err)\n            return -1;\n\n        if (exists)\n            rc = seaf_db_statement_query (db,\n                                          \"UPDATE Branch SET commit_id=? \"\n                                          \"WHERE name=? AND repo_id=?\",\n                                          3, \"string\", branch->commit_id,\n                                          \"string\", branch->name,\n                                          \"string\", branch->repo_id);\n        else\n            rc = seaf_db_statement_query (db,\n                                          \"INSERT INTO Branch VALUES (?, ?, ?)\",\n                                          3, \"string\", branch->name,\n                                          \"string\", branch->repo_id,\n                                          \"string\", branch->commit_id);\n        if (rc < 0)\n            return -1;\n    } else {\n        int rc = seaf_db_statement_query (db,\n                                 \"REPLACE INTO Branch VALUES (?, ?, ?)\",\n                                 3, \"string\", branch->name,\n                                 \"string\", branch->repo_id,\n                                 \"string\", branch->commit_id);\n        if (rc < 0)\n            return -1;\n    }\n    return 0;\n#endif\n}\n\nint\nseaf_branch_manager_del_branch (SeafBranchManager *mgr,\n                                const char *repo_id,\n                                const char *name)\n{\n#ifndef SEAFILE_SERVER\n    char *sql;\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    sql = sqlite3_mprintf (\"DELETE FROM Branch WHERE name = %Q AND \"\n                           \"repo_id = '%s'\", name, repo_id);\n    if (sqlite_query_exec (mgr->priv->db, sql) < 0)\n        seaf_warning (\"Delete branch %s failed\\n\", name);\n    sqlite3_free (sql);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    return 0;\n#else\n    int rc = seaf_db_statement_query (mgr->seaf->db,\n                                      \"DELETE FROM Branch WHERE name=? AND repo_id=?\",\n                                      2, \"string\", name, \"string\", repo_id);\n    if (rc < 0)\n        return -1;\n    return 0;\n#endif\n}\n\nint\nseaf_branch_manager_update_branch (SeafBranchManager *mgr, SeafBranch *branch)\n{\n#ifndef SEAFILE_SERVER\n    sqlite3 *db;\n    char *sql;\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    db = mgr->priv->db;\n    sql = sqlite3_mprintf (\"UPDATE Branch SET commit_id = %Q \"\n                           \"WHERE name = %Q AND repo_id = %Q\",\n                           branch->commit_id, branch->name, branch->repo_id);\n    sqlite_query_exec (db, sql);\n    sqlite3_free (sql);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    return 0;\n#else\n    int rc = seaf_db_statement_query (mgr->seaf->db,\n                                      \"UPDATE Branch SET commit_id = ? \"\n                                      \"WHERE name = ? AND repo_id = ?\",\n                                      3, \"string\", branch->commit_id,\n                                      \"string\", branch->name,\n                                      \"string\", branch->repo_id);\n    if (rc < 0)\n        return -1;\n    return 0;\n#endif\n}\n\n#if defined( SEAFILE_SERVER ) && defined( FULL_FEATURE )\n\nstatic gboolean\nget_commit_id (SeafDBRow *row, void *data)\n{\n    char *out_commit_id = data;\n    const char *commit_id;\n\n    commit_id = seaf_db_row_get_column_text (row, 0);\n    memcpy (out_commit_id, commit_id, 41);\n    out_commit_id[40] = '\\0';\n\n    return FALSE;\n}\n\ntypedef struct {\n    char *repo_id;\n    char *commit_id;\n} RepoUpdateEventData;\n\nstatic void\npublish_repo_update_event (CEvent *event, void *data)\n{\n    RepoUpdateEventData *rdata = event->data;\n\n    char buf[128];\n    snprintf (buf, sizeof(buf), \"repo-update\\t%s\\t%s\",\n              rdata->repo_id, rdata->commit_id);\n\n    seaf_mq_manager_publish_event (seaf->mq_mgr, buf);\n\n    g_free (rdata->repo_id);\n    g_free (rdata->commit_id);\n    g_free (rdata);\n}\n\nstatic void\non_branch_updated (SeafBranchManager *mgr, SeafBranch *branch)\n{\n    if (seaf_repo_manager_is_virtual_repo (seaf->repo_mgr, branch->repo_id))\n        return;\n\n    RepoUpdateEventData *rdata = g_new0 (RepoUpdateEventData, 1);\n\n    rdata->repo_id = g_strdup (branch->repo_id);\n    rdata->commit_id = g_strdup (branch->commit_id);\n    \n    cevent_manager_add_event (seaf->ev_mgr, mgr->priv->cevent_id, rdata);\n}\n\nint\nseaf_branch_manager_test_and_update_branch (SeafBranchManager *mgr,\n                                            SeafBranch *branch,\n                                            const char *old_commit_id)\n{\n    SeafDBTrans *trans;\n    char *sql;\n    char commit_id[41] = { 0 };\n\n    trans = seaf_db_begin_transaction (mgr->seaf->db);\n    if (!trans)\n        return -1;\n\n    switch (seaf_db_type (mgr->seaf->db)) {\n    case SEAF_DB_TYPE_MYSQL:\n    case SEAF_DB_TYPE_PGSQL:\n        sql = \"SELECT commit_id FROM Branch WHERE name=? \"\n            \"AND repo_id=? FOR UPDATE\";\n        break;\n    case SEAF_DB_TYPE_SQLITE:\n        sql = \"SELECT commit_id FROM Branch WHERE name=? \"\n            \"AND repo_id=?\";\n        break;\n    default:\n        g_return_val_if_reached (-1);\n    }\n    if (seaf_db_trans_foreach_selected_row (trans, sql,\n                                            get_commit_id, commit_id,\n                                            2, \"string\", branch->name,\n                                            \"string\", branch->repo_id) < 0) {\n        seaf_db_rollback (trans);\n        seaf_db_trans_close (trans);\n        return -1;\n    }\n    if (strcmp (old_commit_id, commit_id) != 0) {\n        seaf_db_rollback (trans);\n        seaf_db_trans_close (trans);\n        return -1;\n    }\n\n    sql = \"UPDATE Branch SET commit_id = ? \"\n        \"WHERE name = ? AND repo_id = ?\";\n    if (seaf_db_trans_query (trans, sql, 3, \"string\", branch->commit_id,\n                             \"string\", branch->name,\n                             \"string\", branch->repo_id) < 0) {\n        seaf_db_rollback (trans);\n        seaf_db_trans_close (trans);\n        return -1;\n    }\n\n    if (seaf_db_commit (trans) < 0) {\n        seaf_db_rollback (trans);\n        seaf_db_trans_close (trans);\n        return -1;\n    }\n\n    seaf_db_trans_close (trans);\n\n    on_branch_updated (mgr, branch);\n\n    return 0;\n}\n\n#endif\n\n#ifndef SEAFILE_SERVER\nstatic SeafBranch *\nreal_get_branch (SeafBranchManager *mgr,\n                 const char *repo_id,\n                 const char *name)\n{\n    SeafBranch *branch = NULL;\n    sqlite3_stmt *stmt;\n    sqlite3 *db;\n    char *sql;\n    int result;\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    db = mgr->priv->db;\n    sql = sqlite3_mprintf (\"SELECT commit_id FROM Branch \"\n                           \"WHERE name = %Q and repo_id='%s'\",\n                           name, repo_id);\n    if (!(stmt = sqlite_query_prepare (db, sql))) {\n        seaf_warning (\"[Branch mgr] Couldn't prepare query %s\\n\", sql);\n        sqlite3_free (sql);\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        return NULL;\n    }\n    sqlite3_free (sql);\n\n    result = sqlite3_step (stmt);\n    if (result == SQLITE_ROW) {\n        char *commit_id = (char *)sqlite3_column_text (stmt, 0);\n\n        branch = seaf_branch_new (name, repo_id, commit_id);\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        sqlite3_finalize (stmt);\n        return branch;\n    } else if (result == SQLITE_ERROR) {\n        const char *str = sqlite3_errmsg (db);\n        seaf_warning (\"Couldn't prepare query, error: %d->'%s'\\n\",\n                   result, str ? str : \"no error given\");\n    }\n\n    sqlite3_finalize (stmt);\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n    return NULL;\n}\n\nSeafBranch *\nseaf_branch_manager_get_branch (SeafBranchManager *mgr,\n                                const char *repo_id,\n                                const char *name)\n{\n    SeafBranch *branch;\n\n    /* \"fetch_head\" maps to \"local\" or \"master\" on client (LAN sync) */\n    if (strcmp (name, \"fetch_head\") == 0) {\n        branch = real_get_branch (mgr, repo_id, \"local\");\n        if (!branch) {\n            branch = real_get_branch (mgr, repo_id, \"master\");\n        }\n        return branch;\n    } else {\n        return real_get_branch (mgr, repo_id, name);\n    }\n}\n\n#else\n\nstatic gboolean\nget_branch (SeafDBRow *row, void *vid)\n{\n    char *ret = vid;\n    const char *commit_id;\n\n    commit_id = seaf_db_row_get_column_text (row, 0);\n    memcpy (ret, commit_id, 41);\n\n    return FALSE;\n}\n\nstatic SeafBranch *\nreal_get_branch (SeafBranchManager *mgr,\n                 const char *repo_id,\n                 const char *name)\n{\n    char commit_id[41];\n    char *sql;\n\n    commit_id[0] = 0;\n    sql = \"SELECT commit_id FROM Branch WHERE name=? AND repo_id=?\";\n    if (seaf_db_statement_foreach_row (mgr->seaf->db, sql, \n                                       get_branch, commit_id,\n                                       2, \"string\", name, \"string\", repo_id) < 0) {\n        seaf_warning (\"[branch mgr] DB error when get branch %s.\\n\", name);\n        return NULL;\n    }\n\n    if (commit_id[0] == 0)\n        return NULL;\n\n    return seaf_branch_new (name, repo_id, commit_id);\n}\n\nSeafBranch *\nseaf_branch_manager_get_branch (SeafBranchManager *mgr,\n                                const char *repo_id,\n                                const char *name)\n{\n    SeafBranch *branch;\n\n    /* \"fetch_head\" maps to \"master\" on server. */\n    if (strcmp (name, \"fetch_head\") == 0) {\n        branch = real_get_branch (mgr, repo_id, \"master\");\n        return branch;\n    } else {\n        return real_get_branch (mgr, repo_id, name);\n    }\n}\n\n#endif  /* not SEAFILE_SERVER */\n\ngboolean\nseaf_branch_manager_branch_exists (SeafBranchManager *mgr,\n                                   const char *repo_id,\n                                   const char *name)\n{\n#ifndef SEAFILE_SERVER\n    char *sql;\n    gboolean ret;\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    sql = sqlite3_mprintf (\"SELECT name FROM Branch WHERE name = %Q \"\n                           \"AND repo_id='%s'\", name, repo_id);\n    ret = sqlite_check_for_existence (mgr->priv->db, sql);\n    sqlite3_free (sql);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n    return ret;\n#else\n    gboolean db_err = FALSE;\n\n    return seaf_db_statement_exists (mgr->seaf->db,\n                                     \"SELECT name FROM Branch WHERE name=? \"\n                                     \"AND repo_id=?\", &db_err,\n                                     2, \"string\", name, \"string\", repo_id);\n#endif\n}\n\n#ifndef SEAFILE_SERVER\nGList *\nseaf_branch_manager_get_branch_list (SeafBranchManager *mgr,\n                                     const char *repo_id)\n{\n    sqlite3 *db = mgr->priv->db;\n    \n    int result;\n    sqlite3_stmt *stmt;\n    char sql[256];\n    char *name;\n    char *commit_id;\n    GList *ret = NULL;\n    SeafBranch *branch;\n\n    snprintf (sql, 256, \"SELECT name, commit_id FROM branch WHERE repo_id ='%s'\",\n              repo_id);\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    if ( !(stmt = sqlite_query_prepare(db, sql)) ) {\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        return NULL;\n    }\n\n    while (1) {\n        result = sqlite3_step (stmt);\n        if (result == SQLITE_ROW) {\n            name = (char *)sqlite3_column_text(stmt, 0);\n            commit_id = (char *)sqlite3_column_text(stmt, 1);\n            branch = seaf_branch_new (name, repo_id, commit_id);\n            ret = g_list_prepend (ret, branch);\n        }\n        if (result == SQLITE_DONE)\n            break;\n        if (result == SQLITE_ERROR) {\n            const gchar *str = sqlite3_errmsg (db);\n            seaf_warning (\"Couldn't prepare query, error: %d->'%s'\\n\", \n                       result, str ? str : \"no error given\");\n            sqlite3_finalize (stmt);\n            seaf_branch_list_free (ret);\n            pthread_mutex_unlock (&mgr->priv->db_lock);\n            return NULL;\n        }\n    }\n\n    sqlite3_finalize (stmt);\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n    return g_list_reverse(ret);\n}\n#else\nstatic gboolean\nget_branches (SeafDBRow *row, void *vplist)\n{\n    GList **plist = vplist;\n    const char *commit_id;\n    const char *name;\n    const char *repo_id;\n    SeafBranch *branch;\n\n    name = seaf_db_row_get_column_text (row, 0);\n    repo_id = seaf_db_row_get_column_text (row, 1);\n    commit_id = seaf_db_row_get_column_text (row, 2);\n\n    branch = seaf_branch_new (name, repo_id, commit_id);\n    *plist = g_list_prepend (*plist, branch);\n\n    return TRUE;\n}\n\nGList *\nseaf_branch_manager_get_branch_list (SeafBranchManager *mgr,\n                                     const char *repo_id)\n{\n    GList *ret = NULL;\n    char *sql;\n\n    sql = \"SELECT name, repo_id, commit_id FROM Branch WHERE repo_id=?\";\n    if (seaf_db_statement_foreach_row (mgr->seaf->db, sql, \n                                       get_branches, &ret,\n                                       1, \"string\", repo_id) < 0) {\n        seaf_warning (\"[branch mgr] DB error when get branch list.\\n\");\n        return NULL;\n    }\n\n    return ret;\n}\n#endif\n"
  },
  {
    "path": "common/branch-mgr.h",
    "content": "#ifndef SEAF_BRANCH_MGR_H\n#define SEAF_BRANCH_MGR_H\n\n#include \"commit-mgr.h\"\n#define NO_BRANCH \"-\"\n\ntypedef struct _SeafBranch SeafBranch;\n\nstruct _SeafBranch {\n    int   ref;\n    char *name;\n    char  repo_id[37];\n    char  commit_id[41];\n};\n\nSeafBranch *seaf_branch_new (const char *name,\n                             const char *repo_id,\n                             const char *commit_id);\nvoid seaf_branch_free (SeafBranch *branch);\nvoid seaf_branch_set_commit (SeafBranch *branch, const char *commit_id);\n\nvoid seaf_branch_ref (SeafBranch *branch);\nvoid seaf_branch_unref (SeafBranch *branch);\n\n\ntypedef struct _SeafBranchManager SeafBranchManager;\ntypedef struct _SeafBranchManagerPriv SeafBranchManagerPriv;\n\nstruct _SeafileSession;\nstruct _SeafBranchManager {\n    struct _SeafileSession *seaf;\n\n    SeafBranchManagerPriv *priv;\n};\n\nSeafBranchManager *seaf_branch_manager_new (struct _SeafileSession *seaf);\nint seaf_branch_manager_init (SeafBranchManager *mgr);\n\nint\nseaf_branch_manager_add_branch (SeafBranchManager *mgr, SeafBranch *branch);\n\nint\nseaf_branch_manager_del_branch (SeafBranchManager *mgr,\n                                const char *repo_id,\n                                const char *name);\n\nvoid\nseaf_branch_list_free (GList *blist);\n\nint\nseaf_branch_manager_update_branch (SeafBranchManager *mgr,\n                                   SeafBranch *branch);\n\n#ifdef SEAFILE_SERVER\n/**\n * Atomically test whether the current head commit id on @branch\n * is the same as @old_commit_id and update branch in db.\n */\nint\nseaf_branch_manager_test_and_update_branch (SeafBranchManager *mgr,\n                                            SeafBranch *branch,\n                                            const char *old_commit_id);\n#endif\n\nSeafBranch *\nseaf_branch_manager_get_branch (SeafBranchManager *mgr,\n                                const char *repo_id,\n                                const char *name);\n\n\ngboolean\nseaf_branch_manager_branch_exists (SeafBranchManager *mgr,\n                                   const char *repo_id,\n                                   const char *name);\n\nGList *\nseaf_branch_manager_get_branch_list (SeafBranchManager *mgr,\n                                     const char *repo_id);\n\ngint64\nseaf_branch_manager_calculate_branch_size (SeafBranchManager *mgr,\n                                           const char *repo_id, \n                                           const char *commit_id);\n#endif /* SEAF_BRANCH_MGR_H */\n"
  },
  {
    "path": "common/cdc/Makefile.am",
    "content": "AM_CFLAGS = -I$(top_srcdir)/common -I$(top_srcdir)/lib \\\n\t-Wall @GLIB2_CFLAGS@ @MSVC_CFLAGS@\n\nif MACOS\nif COMPILE_UNIVERSAL\nAM_CFLAGS += -arch x86_64 -arch arm64\nendif\nendif\n\nnoinst_LTLIBRARIES = libcdc.la\n\nnoinst_HEADERS = cdc.h rabin-checksum.h\n\nlibcdc_la_SOURCES = cdc.c rabin-checksum.c\n\nlibcdc_la_LDFLAGS = -Wl,-z -Wl,defs\nlibcdc_la_LIBADD = @GLIB2_LIBS@ \\\n\t$(top_builddir)/lib/libseafile_common.la\n"
  },
  {
    "path": "common/cdc/cdc.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#include \"log.h\"\n\n#ifndef WIN32\n#include <unistd.h>\n#endif\n#include <stdio.h>\n#include <stdlib.h>\n#include <fcntl.h>\n#include <string.h>\n#include <sys/stat.h>\n#include <errno.h>\n#include <glib/gstdio.h>\n\n#include \"utils.h\"\n\n#include \"cdc.h\"\n#include \"../seafile-crypt.h\"\n\n#include \"rabin-checksum.h\"\n#define finger rabin_checksum\n#define rolling_finger rabin_rolling_checksum\n\n#define BLOCK_SZ        (1024*1024*1)\n#define BLOCK_MIN_SZ    (1024*256)\n#define BLOCK_MAX_SZ    (1024*1024*4)\n#define BLOCK_WIN_SZ    48\n\n#define NAME_MAX_SZ     4096\n\n#define BREAK_VALUE     0x0013    ///0x0513\n\n#define READ_SIZE 1024 * 4\n\n#define BYTE_TO_HEX(b)  (((b)>=10)?('a'+b-10):('0'+b))\n\nstatic int default_write_chunk (CDCDescriptor *chunk_descr)\n{\n    char filename[NAME_MAX_SZ];\n    char chksum_str[CHECKSUM_LENGTH *2 + 1];\n    int fd_chunk, ret;\n\n    memset(chksum_str, 0, sizeof(chksum_str));\n    rawdata_to_hex (chunk_descr->checksum, chksum_str, CHECKSUM_LENGTH);\n    snprintf (filename, NAME_MAX_SZ, \"./%s\", chksum_str);\n    fd_chunk = g_open (filename, O_RDWR | O_CREAT | O_BINARY, 0644);\n    if (fd_chunk < 0)\n        return -1;    \n    \n    ret = writen (fd_chunk, chunk_descr->block_buf, chunk_descr->len);\n    close (fd_chunk);\n    return ret;\n}\n\nstatic int init_cdc_file_descriptor (int fd,\n                                     uint64_t file_size,\n                                     CDCFileDescriptor *file_descr)\n{\n    int max_block_nr = 0;\n    int block_min_sz = 0;\n\n    file_descr->block_nr = 0;\n\n    if (file_descr->block_min_sz <= 0)\n        file_descr->block_min_sz = BLOCK_MIN_SZ;\n    if (file_descr->block_max_sz <= 0)\n        file_descr->block_max_sz = BLOCK_MAX_SZ;\n    if (file_descr->block_sz <= 0)\n        file_descr->block_sz = BLOCK_SZ;\n\n    if (file_descr->write_block == NULL)\n        file_descr->write_block = (WriteblockFunc)default_write_chunk;\n\n    block_min_sz = file_descr->block_min_sz;\n    max_block_nr = ((file_size + block_min_sz - 1) / block_min_sz);\n    file_descr->blk_sha1s = (uint8_t *)calloc (sizeof(uint8_t),\n                                               max_block_nr * CHECKSUM_LENGTH);\n    file_descr->max_block_nr = max_block_nr;\n\n    return 0;\n}\n\n#define WRITE_CDC_BLOCK(block_sz, write_data)                \\\ndo {                                                         \\\n    int _block_sz = (block_sz);                              \\\n    chunk_descr.len = _block_sz;                             \\\n    chunk_descr.offset = offset;                             \\\n    ret = file_descr->write_block (file_descr->repo_id,      \\\n                                   file_descr->version,      \\\n                                   &chunk_descr,             \\\n            crypt, chunk_descr.checksum,                     \\\n                                   (write_data));            \\\n    if (ret < 0) {                                           \\\n        free (buf);                                          \\\n        g_warning (\"CDC: failed to write chunk.\\n\");         \\\n        return -1;                                           \\\n    }                                                        \\\n    memcpy (file_descr->blk_sha1s +                          \\\n            file_descr->block_nr * CHECKSUM_LENGTH,          \\\n            chunk_descr.checksum, CHECKSUM_LENGTH);          \\\n    g_checksum_update (file_ctx, chunk_descr.checksum, 20);       \\\n    file_descr->block_nr++;                                  \\\n    offset += _block_sz;                                     \\\n                                                             \\\n    memmove (buf, buf + _block_sz, tail - _block_sz);        \\\n    tail = tail - _block_sz;                                 \\\n    cur = 0;                                                 \\\n}while(0);\n\n/* content-defined chunking */\nint file_chunk_cdc(int fd_src,\n                   CDCFileDescriptor *file_descr,\n                   SeafileCrypt *crypt,\n                   gboolean write_data)\n{\n    char *buf = NULL;\n    uint32_t buf_sz;\n    GChecksum *file_ctx = g_checksum_new (G_CHECKSUM_SHA1);\n    CDCDescriptor chunk_descr;\n    int ret = 0;\n\n    SeafStat sb;\n    if (seaf_fstat (fd_src, &sb) < 0) {\n        seaf_warning (\"CDC: failed to stat: %s.\\n\", strerror(errno));\n        ret = -1;\n        goto out;\n    }\n    uint64_t expected_size = sb.st_size;\n\n    init_cdc_file_descriptor (fd_src, expected_size, file_descr);\n    uint32_t block_min_sz = file_descr->block_min_sz;\n    uint32_t block_mask = file_descr->block_sz - 1;\n\n    int fingerprint = 0;\n    int offset = 0;\n    int tail, cur, rsize;\n\n    buf_sz = file_descr->block_max_sz;\n    buf = chunk_descr.block_buf = malloc (buf_sz);\n    if (!buf) {\n        ret = -1;\n        goto out;\n    }\n\n    /* buf: a fix-sized buffer.\n     * cur: data behind (inclusive) this offset has been scanned.\n     *      cur + 1 is the bytes that has been scanned.\n     * tail: length of data loaded into memory. buf[tail] is invalid.\n     */\n    tail = cur = 0;\n    while (1) {\n        if (tail < block_min_sz) {\n            rsize = block_min_sz - tail + READ_SIZE;\n        } else {\n            rsize = (buf_sz - tail < READ_SIZE) ? (buf_sz - tail) : READ_SIZE;\n        }\n        ret = readn (fd_src, buf + tail, rsize);\n        if (ret < 0) {\n            seaf_warning (\"CDC: failed to read: %s.\\n\", strerror(errno));\n            ret = -1;\n            goto out;\n        }\n        tail += ret;\n        file_descr->file_size += ret;\n\n        if (file_descr->file_size > expected_size) {\n            seaf_warning (\"File size changed while chunking.\\n\");\n            ret = -1;\n            goto out;\n        }\n\n        /* We've read all the data in this file. Output the block immediately\n         * in two cases:\n         * 1. The data left in the file is less than block_min_sz;\n         * 2. We cannot find the break value until the end of this file.\n         */\n        if (tail < block_min_sz || cur >= tail) {\n            if (tail > 0) {\n                if (file_descr->block_nr == file_descr->max_block_nr) {\n                    seaf_warning (\"Block id array is not large enough, bail out.\\n\");\n                    ret = -1;\n                    goto out;\n                }\n                WRITE_CDC_BLOCK (tail, write_data);\n            }\n            break;\n        }\n\n        /* \n         * A block is at least of size block_min_sz.\n         */\n        if (cur < block_min_sz - 1)\n            cur = block_min_sz - 1;\n\n        while (cur < tail) {\n            fingerprint = (cur == block_min_sz - 1) ?\n                finger(buf + cur - BLOCK_WIN_SZ + 1, BLOCK_WIN_SZ) :\n                rolling_finger (fingerprint, BLOCK_WIN_SZ, \n                                *(buf+cur-BLOCK_WIN_SZ), *(buf + cur));\n\n            /* get a chunk, write block info to chunk file */\n            if (((fingerprint & block_mask) ==  ((BREAK_VALUE & block_mask)))\n                || cur + 1 >= file_descr->block_max_sz)\n            {\n                if (file_descr->block_nr == file_descr->max_block_nr) {\n                    seaf_warning (\"Block id array is not large enough, bail out.\\n\");\n                    ret = -1;\n                    goto out;\n                }\n\n                WRITE_CDC_BLOCK (cur + 1, write_data);\n                break;\n            } else {\n                cur ++;\n            }\n        }\n    }\n\n    gsize chk_sum_len = CHECKSUM_LENGTH;\n    g_checksum_get_digest (file_ctx, file_descr->file_sum, &chk_sum_len);\n\nout:\n    free (buf);\n    g_checksum_free (file_ctx);\n\n    return ret;\n}\n\nint filename_chunk_cdc(const char *filename,\n                       CDCFileDescriptor *file_descr,\n                       SeafileCrypt *crypt,\n                       gboolean write_data)\n{\n    int fd_src = seaf_util_open (filename, O_RDONLY | O_BINARY);\n    if (fd_src < 0) {\n        seaf_warning (\"CDC: failed to open %s.\\n\", filename);\n        return -1;\n    }\n\n    int ret = file_chunk_cdc (fd_src, file_descr, crypt, write_data);\n    close (fd_src);\n    return ret;\n}\n\nvoid cdc_init ()\n{\n    rabin_init (BLOCK_WIN_SZ);\n}\n"
  },
  {
    "path": "common/cdc/cdc.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef _CDC_H\n#define _CDC_H\n\n#include <glib.h>\n#include <stdint.h>\n\n#define CHECKSUM_LENGTH 20\n\n#ifndef O_BINARY\n#define O_BINARY 0\n#endif\n\nstruct _CDCFileDescriptor;\nstruct _CDCDescriptor;\nstruct SeafileCrypt;\n\ntypedef int (*WriteblockFunc)(const char *repo_id,\n                              int version,\n                              struct _CDCDescriptor *chunk_descr,\n                              struct SeafileCrypt *crypt,\n                              uint8_t *checksum,\n                              gboolean write_data);\n\n/* define chunk file header and block entry */\ntypedef struct _CDCFileDescriptor {\n    uint32_t block_min_sz;\n    uint32_t block_max_sz;\n    uint32_t block_sz;\n    uint64_t file_size;\n\n    uint32_t block_nr;\n    uint8_t *blk_sha1s;\n    int max_block_nr;\n    uint8_t  file_sum[CHECKSUM_LENGTH];\n\n    WriteblockFunc write_block;\n\n    char repo_id[37];\n    int version;\n} CDCFileDescriptor;\n\ntypedef struct _CDCDescriptor {\n    uint64_t offset;\n    uint32_t len;\n    uint8_t  checksum[CHECKSUM_LENGTH];\n    char    *block_buf;\n    int result;\n} CDCDescriptor;\n\nint file_chunk_cdc(int fd_src,\n                   CDCFileDescriptor *file_descr,\n                   struct SeafileCrypt *crypt,\n                   gboolean write_data);\n\nint filename_chunk_cdc(const char *filename,\n                       CDCFileDescriptor *file_descr,\n                       struct SeafileCrypt *crypt,\n                       gboolean write_data);\n\nvoid cdc_init ();\n\n#endif\n"
  },
  {
    "path": "common/cdc/rabin-checksum.c",
    "content": "#include <sys/types.h>\n#include \"rabin-checksum.h\"\n\n#ifdef WIN32\n#include <stdint.h>\n#ifndef u_int\ntypedef unsigned int u_int;\n#endif\n\n#ifndef u_char\ntypedef unsigned char u_char;\n#endif\n\n#ifndef u_short\ntypedef unsigned short u_short;\n#endif\n\n#ifndef u_long\ntypedef unsigned long u_long;\n#endif\n\n#ifndef u_int16_t\ntypedef uint16_t u_int16_t;\n#endif\n\n#ifndef u_int32_t\ntypedef uint32_t u_int32_t;\n#endif\n\n#ifndef u_int64_t\ntypedef uint64_t u_int64_t;\n#endif\n#endif\n\n#define INT64(n) n##LL\n#define MSB64 INT64(0x8000000000000000)\n\nstatic u_int64_t poly = 0xbfe6b8a5bf378d83LL;\nstatic u_int64_t T[256];\nstatic u_int64_t U[256];\nstatic int shift;\n\n/* Highest bit set in a byte */\nstatic const char bytemsb[0x100] = {\n  0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5,\n  5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,\n  6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7,\n  7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,\n  7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,\n  7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,\n  8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,\n  8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,\n  8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,\n  8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,\n  8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,\n};\n\n/* Find last set (most significant bit) */\nstatic inline u_int fls32 (u_int32_t v)\n{\n    if (v & 0xffff0000) {\n        if (v & 0xff000000)\n            return 24 + bytemsb[v>>24];\n        else\n            return 16 + bytemsb[v>>16];\n    }\n    if (v & 0x0000ff00)\n        return 8 + bytemsb[v>>8];\n    else\n        return bytemsb[v];\n}\n\nstatic inline char fls64 (u_int64_t v)\n{\n    u_int32_t h;\n    if ((h = v >> 32))\n        return 32 + fls32 (h);\n    else\n        return fls32 ((u_int32_t) v);\n}\n\nu_int64_t polymod (u_int64_t nh, u_int64_t nl, u_int64_t d)\n{\n    int i = 0;\n    int k = fls64 (d) - 1;\n\n    d <<= 63 - k;\n\n    if (nh) {\n        if (nh & MSB64)\n            nh ^= d;\n        for (i = 62; i >= 0; i--)\n            if (nh & ((u_int64_t) 1) << i) {\n                nh ^= d >> (63 - i);\n                nl ^= d << (i + 1);\n            }\n    }\n    for (i = 63; i >= k; i--)\n    {  \n        if (nl & INT64 (1) << i)\n            nl ^= d >> (63 - i);\n    }\n  \n    return nl;\n}\n\nvoid polymult (u_int64_t *php, u_int64_t *plp, u_int64_t x, u_int64_t y)\n{\n    int i;\n    u_int64_t ph = 0, pl = 0;\n    if (x & 1)\n        pl = y;\n    for (i = 1; i < 64; i++)\n        if (x & (INT64 (1) << i)) {\n            ph ^= y >> (64 - i);\n            pl ^= y << i;\n        }\n    if (php)\n        *php = ph;\n    if (plp)\n        *plp = pl;\n}\n\nu_int64_t polymmult (u_int64_t x, u_int64_t y, u_int64_t d)\n{\n    u_int64_t h, l;\n    polymult (&h, &l, x, y);\n    return polymod (h, l, d);\n}\n\nstatic u_int64_t append8 (u_int64_t p, u_char m)\n{\n    return ((p << 8) | m) ^ T[p >> shift];\n}\n\nstatic void calcT (u_int64_t poly)\n{\n    int j = 0;\n    int xshift = fls64 (poly) - 1;\n    shift = xshift - 8;\n    u_int64_t T1 = polymod (0, INT64 (1) << xshift, poly);\n    for (j = 0; j < 256; j++) {\n        T[j] = polymmult (j, T1, poly) | ((u_int64_t) j << xshift);\n    }\n}\n\nstatic void calcU(int size)\n{\n    int i;\n    u_int64_t sizeshift = 1;\n    for (i = 1; i < size; i++)\n        sizeshift = append8 (sizeshift, 0);\n    for (i = 0; i < 256; i++)\n        U[i] = polymmult (i, sizeshift, poly);\n}\n\nvoid rabin_init(int len)\n{\n    calcT(poly);\n    calcU(len);\n}\n\n/*\n *   a simple 32 bit checksum that can be upadted from end\n */\nunsigned int rabin_checksum(char *buf, int len)\n{\n    int i;\n    unsigned int sum = 0;\n    for (i = 0; i < len; ++i) {\n        sum = rabin_rolling_checksum (sum, len, 0, buf[i]);\n    }\n    return sum;\n}\n\nunsigned int rabin_rolling_checksum(unsigned int csum, int len,\n                                    char c1, char c2)\n{\n    return append8(csum ^ U[(unsigned char)c1], c2);\n}\n"
  },
  {
    "path": "common/cdc/rabin-checksum.h",
    "content": "#ifndef _RABIN_CHECKSUM_H\n#define _RABIN_CHECKSUM_H\n\nunsigned int rabin_checksum(char *buf, int len);\n\nunsigned int rabin_rolling_checksum(unsigned int csum, int len, char c1, char c2);\n\nvoid rabin_init (int len);\n\n#endif\n"
  },
  {
    "path": "common/commit-mgr.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#include \"log.h\"\n\n#include <jansson.h>\n\n#include \"utils.h\"\n#include \"db.h\"\n#include \"searpc-utils.h\"\n\n#include \"seafile-session.h\"\n#include \"commit-mgr.h\"\n\n#define MAX_TIME_SKEW 259200    /* 3 days */\n\nstruct _SeafCommitManagerPriv {\n    int dummy;\n};\n\nstatic SeafCommit *\nload_commit (SeafCommitManager *mgr,\n             const char *repo_id, int version,\n             const char *commit_id);\nstatic int\nsave_commit (SeafCommitManager *manager,\n             const char *repo_id, int version,\n             SeafCommit *commit);\nstatic void\ndelete_commit (SeafCommitManager *mgr,\n               const char *repo_id, int version,\n               const char *id);\nstatic json_t *\ncommit_to_json_object (SeafCommit *commit);\nstatic SeafCommit *\ncommit_from_json_object (const char *id, json_t *object);\n\nstatic void compute_commit_id (SeafCommit* commit)\n{\n    GChecksum *ctx = g_checksum_new(G_CHECKSUM_SHA1);\n    uint8_t sha1[20];    \n    gint64 ctime_n;\n\n    g_checksum_update (ctx, (guchar *)commit->root_id, 41);\n    g_checksum_update (ctx, (guchar *)commit->creator_id, 41);\n    if (commit->creator_name)\n        g_checksum_update (ctx, (guchar *)commit->creator_name, strlen(commit->creator_name)+1);\n    g_checksum_update (ctx, (guchar *)commit->desc, strlen(commit->desc)+1);\n\n    /* convert to network byte order */\n    ctime_n = hton64 (commit->ctime);\n    g_checksum_update (ctx, (guchar *)&ctime_n, sizeof(ctime_n));\n\n    gsize len = 20;\n    g_checksum_get_digest (ctx, sha1, &len);\n    \n    rawdata_to_hex (sha1, commit->commit_id, 20);\n    g_checksum_free (ctx);\n}\n\nSeafCommit*\nseaf_commit_new (const char *commit_id,\n                 const char *repo_id,\n                 const char *root_id,\n                 const char *creator_name,\n                 const char *creator_id,\n                 const char *desc,\n                 guint64 ctime)\n{\n    SeafCommit *commit;\n\n    g_return_val_if_fail (repo_id != NULL, NULL);\n    g_return_val_if_fail (root_id != NULL && creator_id != NULL, NULL);\n\n    commit = g_new0 (SeafCommit, 1);\n\n    memcpy (commit->repo_id, repo_id, 36);\n    commit->repo_id[36] = '\\0';\n    \n    memcpy (commit->root_id, root_id, 40);\n    commit->root_id[40] = '\\0';\n\n    commit->creator_name = g_strdup (creator_name);\n\n    memcpy (commit->creator_id, creator_id, 40);\n    commit->creator_id[40] = '\\0';\n\n    commit->desc = g_strdup (desc);\n    \n    if (ctime == 0) {\n        /* TODO: use more precise timer */\n        commit->ctime = (gint64)time(NULL);\n    } else\n        commit->ctime = ctime;\n\n    if (commit_id == NULL)\n        compute_commit_id (commit);\n    else {\n        memcpy (commit->commit_id, commit_id, 40);\n        commit->commit_id[40] = '\\0';        \n    }\n\n    commit->ref = 1;\n    return commit;\n}\n\nchar *\nseaf_commit_to_data (SeafCommit *commit, gsize *len)\n{\n    json_t *object;\n    char *json_data;\n    char *ret;\n\n    object = commit_to_json_object (commit);\n\n    json_data = json_dumps (object, 0);\n    *len = strlen (json_data);\n    json_decref (object);\n\n    ret = g_strdup (json_data);\n    free (json_data);\n    return ret;\n}\n\nSeafCommit *\nseaf_commit_from_data (const char *id, char *data, gsize len)\n{\n    json_t *object;\n    SeafCommit *commit;\n    json_error_t jerror;\n\n    object = json_loadb (data, len, 0, &jerror);\n    if (!object) {\n        /* Perhaps the commit object contains invalid UTF-8 character. */\n        if (data[len-1] == 0)\n            clean_utf8_data (data, len - 1);\n        else\n            clean_utf8_data (data, len);\n\n        object = json_loadb (data, len, 0, &jerror);\n        if (!object) {\n            seaf_warning (\"Failed to load commit json: %s.\\n\", jerror.text);\n            return NULL;\n        }\n    }\n\n    commit = commit_from_json_object (id, object);\n\n    json_decref (object);\n\n    return commit;\n}\n\nstatic void\nseaf_commit_free (SeafCommit *commit)\n{\n    g_free (commit->desc);\n    g_free (commit->creator_name);\n    if (commit->parent_id) g_free (commit->parent_id);\n    if (commit->second_parent_id) g_free (commit->second_parent_id);\n    if (commit->repo_name) g_free (commit->repo_name);\n    if (commit->repo_desc) g_free (commit->repo_desc);\n    if (commit->device_name) g_free (commit->device_name);\n    g_free (commit->client_version);\n    g_free (commit->magic);\n    g_free (commit->random_key);\n    g_free (commit->pwd_hash);\n    g_free (commit->pwd_hash_algo);\n    g_free (commit->pwd_hash_params);\n    g_free (commit);\n}\n\nvoid\nseaf_commit_ref (SeafCommit *commit)\n{\n    commit->ref++;\n}\n\nvoid\nseaf_commit_unref (SeafCommit *commit)\n{\n    if (!commit)\n        return;\n\n    if (--commit->ref <= 0)\n        seaf_commit_free (commit);\n}\n\nSeafCommitManager*\nseaf_commit_manager_new (SeafileSession *seaf)\n{\n    SeafCommitManager *mgr = g_new0 (SeafCommitManager, 1);\n\n    mgr->priv = g_new0 (SeafCommitManagerPriv, 1);\n    mgr->seaf = seaf;\n    mgr->obj_store = seaf_obj_store_new (mgr->seaf, \"commits\");\n\n    return mgr;\n}\n\nint\nseaf_commit_manager_init (SeafCommitManager *mgr)\n{\n#ifdef SEAFILE_SERVER\n\n#ifdef FULL_FEATURE\n    if (seaf_obj_store_init (mgr->obj_store, TRUE, seaf->ev_mgr) < 0) {\n        seaf_warning (\"[commit mgr] Failed to init commit object store.\\n\");\n        return -1;\n    }\n#else\n    if (seaf_obj_store_init (mgr->obj_store, FALSE, NULL) < 0) {\n        seaf_warning (\"[commit mgr] Failed to init commit object store.\\n\");\n        return -1;\n    }\n#endif\n\n#else\n    if (seaf_obj_store_init (mgr->obj_store, TRUE, seaf->ev_mgr) < 0) {\n        seaf_warning (\"[commit mgr] Failed to init commit object store.\\n\");\n        return -1;\n    }\n#endif\n\n    return 0;\n}\n\n#if 0\ninline static void\nadd_commit_to_cache (SeafCommitManager *mgr, SeafCommit *commit)\n{\n    g_hash_table_insert (mgr->priv->commit_cache,\n                         g_strdup(commit->commit_id),\n                         commit);\n    seaf_commit_ref (commit);\n}\n\ninline static void\nremove_commit_from_cache (SeafCommitManager *mgr, SeafCommit *commit)\n{\n    g_hash_table_remove (mgr->priv->commit_cache, commit->commit_id);\n    seaf_commit_unref (commit);\n}\n#endif\n\nint\nseaf_commit_manager_add_commit (SeafCommitManager *mgr,\n                                SeafCommit *commit)\n{\n    int ret;\n\n    /* add_commit_to_cache (mgr, commit); */\n    if ((ret = save_commit (mgr, commit->repo_id, commit->version, commit)) < 0)\n        return -1;\n    \n    return 0;\n}\n\nvoid\nseaf_commit_manager_del_commit (SeafCommitManager *mgr,\n                                const char *repo_id,\n                                int version,\n                                const char *id)\n{\n    g_return_if_fail (id != NULL);\n\n#if 0\n    commit = g_hash_table_lookup(mgr->priv->commit_cache, id);\n    if (!commit)\n        goto delete;\n\n    /*\n     * Catch ref count bug here. We have bug in commit ref, the\n     * following assert can't pass. TODO: fix the commit ref bug\n     */\n    /* g_assert (commit->ref <= 1); */\n    remove_commit_from_cache (mgr, commit);\n\ndelete:\n#endif\n\n    delete_commit (mgr, repo_id, version, id);\n}\n\nSeafCommit* \nseaf_commit_manager_get_commit (SeafCommitManager *mgr,\n                                const char *repo_id,\n                                int version,\n                                const char *id)\n{\n    SeafCommit *commit;\n\n#if 0\n    commit = g_hash_table_lookup (mgr->priv->commit_cache, id);\n    if (commit != NULL) {\n        seaf_commit_ref (commit);\n        return commit;\n    }\n#endif\n\n    commit = load_commit (mgr, repo_id, version, id);\n    if (!commit)\n        return NULL;\n\n    /* add_commit_to_cache (mgr, commit); */\n\n    return commit;\n}\n\nSeafCommit *\nseaf_commit_manager_get_commit_compatible (SeafCommitManager *mgr,\n                                           const char *repo_id,\n                                           const char *id)\n{\n    SeafCommit *commit = NULL;\n\n    /* First try version 1 layout. */\n    commit = seaf_commit_manager_get_commit (mgr, repo_id, 1, id);\n    if (commit)\n        return commit;\n\n#if defined MIGRATION || defined SEAFILE_CLIENT\n    /* For compatibility with version 0. */\n    commit = seaf_commit_manager_get_commit (mgr, repo_id, 0, id);\n#endif\n    return commit;\n}\n\nstatic gint\ncompare_commit_by_time (gconstpointer a, gconstpointer b, gpointer unused)\n{\n    const SeafCommit *commit_a = a;\n    const SeafCommit *commit_b = b;\n\n    /* Latest commit comes first in the list. */\n    return (commit_b->ctime - commit_a->ctime);\n}\n\ninline static int\ninsert_parent_commit (GList **list, GHashTable *hash,\n                      const char *repo_id, int version,\n                      const char *parent_id, gboolean allow_truncate)\n{\n    SeafCommit *p;\n    char *key;\n\n    if (g_hash_table_lookup (hash, parent_id) != NULL)\n        return 0;\n\n    p = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                        repo_id, version,\n                                        parent_id);\n    if (!p) {\n        if (allow_truncate)\n            return 0;\n        seaf_warning (\"Failed to find commit %s\\n\", parent_id);\n        return -1;\n    }\n\n    *list = g_list_insert_sorted_with_data (*list, p,\n                                           compare_commit_by_time,\n                                           NULL);\n\n    key = g_strdup (parent_id);\n    g_hash_table_replace (hash, key, key);\n\n    return 0;\n}\n\ngboolean\nseaf_commit_manager_traverse_commit_tree_with_limit (SeafCommitManager *mgr,\n                                                     const char *repo_id,\n                                                     int version,\n                                                     const char *head,\n                                                     CommitTraverseFunc func,\n                                                     int limit,\n                                                     void *data,\n                                                     gboolean skip_errors)\n{\n    SeafCommit *commit;\n    GList *list = NULL;\n    GHashTable *commit_hash;\n    gboolean ret = TRUE;\n\n    /* A hash table for recording id of traversed commits. */\n    commit_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);\n\n    commit = seaf_commit_manager_get_commit (mgr, repo_id, version, head);\n    if (!commit) {\n        seaf_warning (\"Failed to find commit %s.\\n\", head);\n        return FALSE;\n    }\n\n    list = g_list_insert_sorted_with_data (list, commit,\n                                           compare_commit_by_time,\n                                           NULL);\n\n    char *key = g_strdup (commit->commit_id);\n    g_hash_table_replace (commit_hash, key, key);\n\n    int count = 0;\n    while (list) {\n        gboolean stop = FALSE;\n        commit = list->data;\n        list = g_list_delete_link (list, list);\n\n        if (!func (commit, data, &stop)) {\n            if (!skip_errors) {\n                seaf_commit_unref (commit);\n                ret = FALSE;\n                goto out;\n            }\n        }\n\n        /* Stop when limit is reached. If limit < 0, there is no limit; */\n        if (limit > 0 && ++count == limit) {\n            seaf_commit_unref (commit);\n            break;\n        }\n        \n        if (stop) {\n            seaf_commit_unref (commit);\n            /* stop traverse down from this commit,\n             * but not stop traversing the tree \n             */\n            continue;\n        }\n\n        if (commit->parent_id) {\n            if (insert_parent_commit (&list, commit_hash, repo_id, version,\n                                      commit->parent_id, FALSE) < 0) {\n                if (!skip_errors) {\n                    seaf_commit_unref (commit);\n                    ret = FALSE;\n                    goto out;\n                }\n            }\n        }\n        if (commit->second_parent_id) {\n            if (insert_parent_commit (&list, commit_hash, repo_id, version,\n                                      commit->second_parent_id, FALSE) < 0) {\n                if (!skip_errors) {\n                    seaf_commit_unref (commit);\n                    ret = FALSE;\n                    goto out;\n                }\n            }\n        }\n        seaf_commit_unref (commit);\n    }\n\nout:\n    g_hash_table_destroy (commit_hash);\n    while (list) {\n        commit = list->data;\n        seaf_commit_unref (commit);\n        list = g_list_delete_link (list, list);\n    }\n    return ret;\n}\n\nstatic gboolean\ntraverse_commit_tree_common (SeafCommitManager *mgr,\n                             const char *repo_id,\n                             int version,\n                             const char *head,\n                             CommitTraverseFunc func,\n                             void *data,\n                             gboolean skip_errors,\n                             gboolean allow_truncate)\n{\n    SeafCommit *commit;\n    GList *list = NULL;\n    GHashTable *commit_hash;\n    gboolean ret = TRUE;\n\n    commit = seaf_commit_manager_get_commit (mgr, repo_id, version, head);\n    if (!commit) {\n        seaf_warning (\"Failed to find commit %s.\\n\", head);\n        // For head commit corrupted, directly return FALSE\n        // user can repair head by fsck then retraverse the tree\n        return FALSE;\n    }\n\n    /* A hash table for recording id of traversed commits. */\n    commit_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);\n\n    list = g_list_insert_sorted_with_data (list, commit,\n                                           compare_commit_by_time,\n                                           NULL);\n\n    char *key = g_strdup (commit->commit_id);\n    g_hash_table_replace (commit_hash, key, key);\n\n    while (list) {\n        gboolean stop = FALSE;\n        commit = list->data;\n        list = g_list_delete_link (list, list);\n\n        if (!func (commit, data, &stop)) {\n            seaf_warning(\"[comit-mgr] CommitTraverseFunc failed\\n\");\n\n            /* If skip errors, continue to traverse parents. */\n            if (!skip_errors) {\n                seaf_commit_unref (commit);\n                ret = FALSE;\n                goto out;\n            }\n        }\n        if (stop) {\n            seaf_commit_unref (commit);\n            /* stop traverse down from this commit,\n             * but not stop traversing the tree \n             */\n            continue;\n        }\n\n        if (commit->parent_id) {\n            if (insert_parent_commit (&list, commit_hash, repo_id, version,\n                                      commit->parent_id, allow_truncate) < 0) {\n                seaf_warning(\"[comit-mgr] insert parent commit failed\\n\");\n\n                /* If skip errors, try insert second parent. */\n                if (!skip_errors) {\n                    seaf_commit_unref (commit);\n                    ret = FALSE;\n                    goto out;\n                }\n            }\n        }\n        if (commit->second_parent_id) {\n            if (insert_parent_commit (&list, commit_hash, repo_id, version,\n                                      commit->second_parent_id, allow_truncate) < 0) {\n                seaf_warning(\"[comit-mgr]insert second parent commit failed\\n\");\n\n                if (!skip_errors) {\n                    seaf_commit_unref (commit);\n                    ret = FALSE;\n                    goto out;\n                }\n            }\n        }\n        seaf_commit_unref (commit);\n    }\n\nout:\n    g_hash_table_destroy (commit_hash);\n    while (list) {\n        commit = list->data;\n        seaf_commit_unref (commit);\n        list = g_list_delete_link (list, list);\n    }\n    return ret;\n}\n\ngboolean\nseaf_commit_manager_traverse_commit_tree (SeafCommitManager *mgr,\n                                          const char *repo_id,\n                                          int version,\n                                          const char *head,\n                                          CommitTraverseFunc func,\n                                          void *data,\n                                          gboolean skip_errors)\n{\n    return traverse_commit_tree_common (mgr, repo_id, version, head,\n                                        func, data, skip_errors, FALSE);\n}\n\ngboolean\nseaf_commit_manager_traverse_commit_tree_truncated (SeafCommitManager *mgr,\n                                                    const char *repo_id,\n                                                    int version,\n                                                    const char *head,\n                                                    CommitTraverseFunc func,\n                                                    void *data,\n                                                    gboolean skip_errors)\n{\n    return traverse_commit_tree_common (mgr, repo_id, version, head,\n                                        func, data, skip_errors, TRUE);\n}\n\ngboolean\nseaf_commit_manager_commit_exists (SeafCommitManager *mgr,\n                                   const char *repo_id,\n                                   int version,\n                                   const char *id)\n{\n#if 0\n    commit = g_hash_table_lookup (mgr->priv->commit_cache, id);\n    if (commit != NULL)\n        return TRUE;\n#endif\n\n    return seaf_obj_store_obj_exists (mgr->obj_store, repo_id, version, id);\n}\n\nstatic json_t *\ncommit_to_json_object (SeafCommit *commit)\n{\n    json_t *object;\n    \n    object = json_object ();\n \n    json_object_set_string_member (object, \"commit_id\", commit->commit_id);\n    json_object_set_string_member (object, \"root_id\", commit->root_id);\n    json_object_set_string_member (object, \"repo_id\", commit->repo_id);\n    if (commit->creator_name)\n        json_object_set_string_member (object, \"creator_name\", commit->creator_name);\n    json_object_set_string_member (object, \"creator\", commit->creator_id);\n    json_object_set_string_member (object, \"description\", commit->desc);\n    json_object_set_int_member (object, \"ctime\", (gint64)commit->ctime);\n    json_object_set_string_or_null_member (object, \"parent_id\", commit->parent_id);\n    json_object_set_string_or_null_member (object, \"second_parent_id\",\n                                           commit->second_parent_id);\n    /*\n     * also save repo's properties to commit file, for easy sharing of\n     * repo info \n     */\n    json_object_set_string_member (object, \"repo_name\", commit->repo_name);\n    json_object_set_string_member (object, \"repo_desc\",\n                                   commit->repo_desc);\n    json_object_set_string_or_null_member (object, \"repo_category\",\n                                           commit->repo_category);\n    if (commit->device_name)\n        json_object_set_string_member (object, \"device_name\", commit->device_name);\n\n    if (commit->client_version)\n        json_object_set_string_member (object, \"client_version\", commit->client_version);\n\n    if (commit->encrypted)\n        json_object_set_string_member (object, \"encrypted\", \"true\");\n\n    if (commit->encrypted) {\n        json_object_set_int_member (object, \"enc_version\", commit->enc_version);\n        if (commit->enc_version >= 1 && !commit->pwd_hash)\n            json_object_set_string_member (object, \"magic\", commit->magic);\n        if (commit->enc_version >= 2)\n            json_object_set_string_member (object, \"key\", commit->random_key);\n        if (commit->enc_version >= 3)\n            json_object_set_string_member (object, \"salt\", commit->salt);\n        if (commit->pwd_hash) {\n            json_object_set_string_member (object, \"pwd_hash\", commit->pwd_hash);\n            json_object_set_string_member (object, \"pwd_hash_algo\", commit->pwd_hash_algo);\n            json_object_set_string_member (object, \"pwd_hash_params\", commit->pwd_hash_params);\n        }\n    }\n    if (commit->no_local_history)\n        json_object_set_int_member (object, \"no_local_history\", 1);\n    if (commit->version != 0)\n        json_object_set_int_member (object, \"version\", commit->version);\n    if (commit->conflict)\n        json_object_set_int_member (object, \"conflict\", 1);\n    if (commit->new_merge)\n        json_object_set_int_member (object, \"new_merge\", 1);\n    if (commit->repaired)\n        json_object_set_int_member (object, \"repaired\", 1);\n\n    return object;\n}\n\nstatic SeafCommit *\ncommit_from_json_object (const char *commit_id, json_t *object)\n{\n    SeafCommit *commit = NULL;\n    const char *root_id;\n    const char *repo_id;\n    const char *creator_name = NULL;\n    const char *creator;\n    const char *desc;\n    gint64 ctime;\n    const char *parent_id, *second_parent_id;\n    const char *repo_name;\n    const char *repo_desc;\n    const char *repo_category;\n    const char *device_name;\n    const char *client_version;\n    const char *encrypted = NULL;\n    int enc_version = 0;\n    const char *magic = NULL;\n    const char *random_key = NULL;\n    const char *salt = NULL;\n    const char *pwd_hash = NULL;\n    const char *pwd_hash_algo = NULL;\n    const char *pwd_hash_params = NULL;\n    int no_local_history = 0;\n    int version = 0;\n    int conflict = 0, new_merge = 0;\n    int repaired = 0;\n\n    root_id = json_object_get_string_member (object, \"root_id\");\n    repo_id = json_object_get_string_member (object, \"repo_id\");\n    if (json_object_has_member (object, \"creator_name\"))\n        creator_name = json_object_get_string_or_null_member (object, \"creator_name\");\n    creator = json_object_get_string_member (object, \"creator\");\n    desc = json_object_get_string_member (object, \"description\");\n    if (!desc)\n        desc = \"\";\n    ctime = (guint64) json_object_get_int_member (object, \"ctime\");\n    parent_id = json_object_get_string_or_null_member (object, \"parent_id\");\n    second_parent_id = json_object_get_string_or_null_member (object, \"second_parent_id\");\n\n    repo_name = json_object_get_string_member (object, \"repo_name\");\n    if (!repo_name)\n        repo_name = \"\";\n    repo_desc = json_object_get_string_member (object, \"repo_desc\");\n    if (!repo_desc)\n        repo_desc = \"\";\n    repo_category = json_object_get_string_or_null_member (object, \"repo_category\");\n    device_name = json_object_get_string_or_null_member (object, \"device_name\");\n    client_version = json_object_get_string_or_null_member (object, \"client_version\");\n\n    if (json_object_has_member (object, \"encrypted\"))\n        encrypted = json_object_get_string_or_null_member (object, \"encrypted\");\n\n    if (encrypted && strcmp(encrypted, \"true\") == 0\n        && json_object_has_member (object, \"enc_version\")) {\n        enc_version = json_object_get_int_member (object, \"enc_version\");\n        magic = json_object_get_string_member (object, \"magic\");\n        pwd_hash = json_object_get_string_member (object, \"pwd_hash\");\n        pwd_hash_algo = json_object_get_string_member (object, \"pwd_hash_algo\");\n        pwd_hash_params = json_object_get_string_member (object, \"pwd_hash_params\");\n    }\n\n    if (enc_version >= 2)\n        random_key = json_object_get_string_member (object, \"key\");\n    if (enc_version >= 3)\n        salt = json_object_get_string_member (object, \"salt\");\n\n    if (json_object_has_member (object, \"no_local_history\"))\n        no_local_history = json_object_get_int_member (object, \"no_local_history\");\n\n    if (json_object_has_member (object, \"version\"))\n        version = json_object_get_int_member (object, \"version\");\n    if (json_object_has_member (object, \"new_merge\"))\n        new_merge = json_object_get_int_member (object, \"new_merge\");\n\n    if (json_object_has_member (object, \"conflict\"))\n        conflict = json_object_get_int_member (object, \"conflict\");\n\n    if (json_object_has_member (object, \"repaired\"))\n        repaired = json_object_get_int_member (object, \"repaired\");\n\n\n    /* sanity check for incoming values. */\n    if (!repo_id || !is_uuid_valid(repo_id)  ||\n        !root_id || !is_object_id_valid(root_id) ||\n        !creator || strlen(creator) != 40 ||\n        (parent_id && !is_object_id_valid(parent_id)) ||\n        (second_parent_id && !is_object_id_valid(second_parent_id)))\n        return commit;\n\n    // If pwd_hash is set, the magic field is no longer included in the commit of the newly created repo.\n    if (!magic)\n        magic = pwd_hash;\n\n    switch (enc_version) {\n    case 0:\n        break;\n    case 1:\n        if (!magic || strlen(magic) != 32)\n            return NULL;\n        break;\n    case 2:\n        if (!magic || strlen(magic) != 64)\n            return NULL;\n        if (!random_key || strlen(random_key) != 96)\n            return NULL;\n        break;\n    case 3:\n        if (!magic || strlen(magic) != 64)\n            return NULL;\n        if (!random_key || strlen(random_key) != 96)\n            return NULL;\n        if (!salt || strlen(salt) != 64)\n            return NULL;\n        break;\n    case 4:\n        if (!magic || strlen(magic) != 64)\n            return NULL;\n        if (!random_key || strlen(random_key) != 96)\n            return NULL;\n        if (!salt || strlen(salt) != 64)\n            return NULL;\n        break;\n    default:\n        seaf_warning (\"Unknown encryption version %d.\\n\", enc_version);\n        return NULL;\n    }\n\n    char *creator_name_l = creator_name ? g_ascii_strdown (creator_name, -1) : NULL;\n    commit = seaf_commit_new (commit_id, repo_id, root_id,\n                              creator_name_l, creator, desc, ctime);\n    g_free (creator_name_l);\n\n    commit->parent_id = parent_id ? g_strdup(parent_id) : NULL;\n    commit->second_parent_id = second_parent_id ? g_strdup(second_parent_id) : NULL;\n\n    commit->repo_name = g_strdup(repo_name);\n    commit->repo_desc = g_strdup(repo_desc);\n    if (encrypted && strcmp(encrypted, \"true\") == 0)\n        commit->encrypted = TRUE;\n    else\n        commit->encrypted = FALSE;\n    if (repo_category)\n        commit->repo_category = g_strdup(repo_category);\n    commit->device_name = g_strdup(device_name);\n    commit->client_version = g_strdup(client_version);\n\n    if (commit->encrypted) {\n        commit->enc_version = enc_version;\n        if (enc_version >= 1 && !pwd_hash)\n            commit->magic = g_strdup(magic);\n        if (enc_version >= 2)\n            commit->random_key = g_strdup (random_key);\n        if (enc_version >= 3)\n            commit->salt = g_strdup(salt);\n        if (pwd_hash) {\n            commit->pwd_hash = g_strdup (pwd_hash);\n            commit->pwd_hash_algo = g_strdup (pwd_hash_algo);\n            commit->pwd_hash_params = g_strdup (pwd_hash_params);\n        }\n    }\n    if (no_local_history)\n        commit->no_local_history = TRUE;\n    commit->version = version;\n    if (new_merge)\n        commit->new_merge = TRUE;\n    if (conflict)\n        commit->conflict = TRUE;\n    if (repaired)\n        commit->repaired = TRUE;\n\n    return commit;\n}\n\nstatic SeafCommit *\nload_commit (SeafCommitManager *mgr,\n             const char *repo_id,\n             int version,\n             const char *commit_id)\n{\n    char *data = NULL;\n    int len;\n    SeafCommit *commit = NULL;\n    json_t *object = NULL;\n    json_error_t jerror;\n\n    if (!commit_id || strlen(commit_id) != 40)\n        return NULL;\n\n    if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version,\n                                 commit_id, (void **)&data, &len) < 0)\n        return NULL;\n\n    object = json_loadb (data, len, 0, &jerror);\n    if (!object) {\n        /* Perhaps the commit object contains invalid UTF-8 character. */\n        if (data[len-1] == 0)\n            clean_utf8_data (data, len - 1);\n        else\n            clean_utf8_data (data, len);\n\n        object = json_loadb (data, len, 0, &jerror);\n        if (!object) {\n            seaf_warning (\"Failed to load commit json object: %s.\\n\", jerror.text);\n            goto out;\n        }\n    }\n\n    commit = commit_from_json_object (commit_id, object);\n    if (commit)\n        commit->manager = mgr;\n\nout:\n    if (object) json_decref (object);\n    g_free (data);\n\n    return commit;\n}\n\nstatic int\nsave_commit (SeafCommitManager *manager,\n             const char *repo_id,\n             int version,\n             SeafCommit *commit)\n{\n    json_t *object = NULL;\n    char *data;\n    gsize len;\n\n    object = commit_to_json_object (commit);\n\n    data = json_dumps (object, 0);\n    len = strlen (data);\n\n    json_decref (object);\n\n#ifdef SEAFILE_SERVER\n    if (seaf_obj_store_write_obj (manager->obj_store,\n                                  repo_id, version,\n                                  commit->commit_id,\n                                  data, (int)len, TRUE) < 0) {\n        g_free (data);\n        return -1;\n    }\n#else\n    if (seaf_obj_store_write_obj (manager->obj_store,\n                                  repo_id, version,\n                                  commit->commit_id,\n                                  data, (int)len, FALSE) < 0) {\n        g_free (data);\n        return -1;\n    }\n#endif\n    free (data);\n\n    return 0;\n}\n\nstatic void\ndelete_commit (SeafCommitManager *mgr,\n               const char *repo_id,\n               int version,\n               const char *id)\n{\n    seaf_obj_store_delete_obj (mgr->obj_store, repo_id, version, id);\n}\n\nint\nseaf_commit_manager_remove_store (SeafCommitManager *mgr,\n                                  const char *store_id)\n{\n    return seaf_obj_store_remove_store (mgr->obj_store, store_id);\n}\n"
  },
  {
    "path": "common/commit-mgr.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef SEAF_COMMIT_MGR_H\n#define SEAF_COMMIT_MGR_H\n\nstruct _SeafCommitManager;\ntypedef struct _SeafCommit SeafCommit;\n\n#include <glib/gstdio.h>\n#include \"db.h\"\n\n#include \"obj-store.h\"\n\nstruct _SeafCommit {\n    struct _SeafCommitManager *manager;\n\n    int         ref;\n\n    char        commit_id[41];\n    char        repo_id[37];\n    char        root_id[41];    /* the fs root */\n    char       *desc;\n    char       *creator_name; // creator_name is user's email.\n    char        creator_id[41];\n    guint64     ctime;          /* creation time */\n    char       *parent_id;\n    char       *second_parent_id;\n    char       *repo_name;\n    char       *repo_desc;\n    char       *repo_category;\n    char       *device_name;\n    char       *client_version;\n\n    gboolean    encrypted;         \n    int         enc_version;\n    char       *magic;\n    char       *random_key;\n    char       *salt;\n    char       *pwd_hash;\n    char       *pwd_hash_algo;\n    char       *pwd_hash_params;\n    gboolean    no_local_history;\n\n    int         version;\n    gboolean    new_merge;\n    gboolean    conflict;\n    gboolean    repaired;\n};\n\n\n/**\n * @commit_id: if this is NULL, will create a new id.\n * @ctime: if this is 0, will use current time.\n * \n * Any new commit should be added to commit manager before used.\n */\nSeafCommit *\nseaf_commit_new (const char *commit_id,\n                 const char *repo_id,\n                 const char *root_id,\n                 const char *author_name,\n                 const char *creator_id,\n                 const char *desc,\n                 guint64 ctime);\n\nchar *\nseaf_commit_to_data (SeafCommit *commit, gsize *len);\n\nSeafCommit *\nseaf_commit_from_data (const char *id, char *data, gsize len);\n\nvoid\nseaf_commit_ref (SeafCommit *commit);\n\nvoid\nseaf_commit_unref (SeafCommit *commit);\n\n/* Set stop to TRUE if you want to stop traversing a branch in the history graph. \n   Note, if currently there are multi branches, this function will be called again. \n   So, set stop to TRUE not always stop traversing the history graph.\n*/\ntypedef gboolean (*CommitTraverseFunc) (SeafCommit *commit, void *data, gboolean *stop);\n\nstruct _SeafileSession;\n\ntypedef struct _SeafCommitManager SeafCommitManager;\ntypedef struct _SeafCommitManagerPriv SeafCommitManagerPriv;\n\nstruct _SeafCommitManager {\n    struct _SeafileSession *seaf;\n\n    sqlite3    *db;\n    struct SeafObjStore *obj_store;\n\n    SeafCommitManagerPriv *priv;\n};\n\nSeafCommitManager *\nseaf_commit_manager_new (struct _SeafileSession *seaf);\n\nint\nseaf_commit_manager_init (SeafCommitManager *mgr);\n\n/**\n * Add a commit to commit manager and persist it to disk.\n * Any new commit should be added to commit manager before used.\n * This function increments ref count of the commit object.\n * Not MT safe.\n */\nint\nseaf_commit_manager_add_commit (SeafCommitManager *mgr, SeafCommit *commit);\n\n/**\n * Delete a commit from commit manager and permanently remove it from disk.\n * A commit object to be deleted should have ref cournt <= 1.\n * Not MT safe.\n */\nvoid\nseaf_commit_manager_del_commit (SeafCommitManager *mgr,\n                                const char *repo_id,\n                                int version,\n                                const char *id);\n\n/**\n * Find a commit object.\n * This function increments ref count of returned object.\n * Not MT safe.\n */\nSeafCommit* \nseaf_commit_manager_get_commit (SeafCommitManager *mgr,\n                                const char *repo_id,\n                                int version,\n                                const char *id);\n\n/**\n * Get a commit object, with compatibility between version 0 and version 1.\n * It will first try to get commit with version 1 layout; if fails, will\n * try version 0 layout for compatibility.\n * This is useful for loading a repo. In that case, we don't know the version\n * of the repo before loading its head commit.\n */\nSeafCommit *\nseaf_commit_manager_get_commit_compatible (SeafCommitManager *mgr,\n                                           const char *repo_id,\n                                           const char *id);\n\n/**\n * Traverse the commits DAG start from head in topological order.\n * The ordering is based on commit time.\n * return FALSE if some commits is missing, TRUE otherwise.\n */\ngboolean\nseaf_commit_manager_traverse_commit_tree (SeafCommitManager *mgr,\n                                          const char *repo_id,\n                                          int version,\n                                          const char *head,\n                                          CommitTraverseFunc func,\n                                          void *data,\n                                          gboolean skip_errors);\n\n/*\n * The same as the above function, but stops traverse down if parent commit\n * doesn't exists, instead of returning error.\n */\ngboolean\nseaf_commit_manager_traverse_commit_tree_truncated (SeafCommitManager *mgr,\n                                                    const char *repo_id,\n                                                    int version,\n                                                    const char *head,\n                                                    CommitTraverseFunc func,\n                                                    void *data,\n                                                    gboolean skip_errors);\n\n/**\n * Works the same as seaf_commit_manager_traverse_commit_tree, but stops\n * traversing when a total number of _limit_ commits is reached. If\n * limit <= 0, there is no limit\n */\ngboolean\nseaf_commit_manager_traverse_commit_tree_with_limit (SeafCommitManager *mgr,\n                                                     const char *repo_id,\n                                                     int version,\n                                                     const char *head,\n                                                     CommitTraverseFunc func,\n                                                     int limit,\n                                                     void *data,\n                                                     gboolean skip_errors);\n\ngboolean\nseaf_commit_manager_commit_exists (SeafCommitManager *mgr,\n                                   const char *repo_id,\n                                   int version,\n                                   const char *id);\n\nint\nseaf_commit_manager_remove_store (SeafCommitManager *mgr,\n                                  const char *store_id);\n\n#endif\n"
  },
  {
    "path": "common/common.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef COMMON_H\n#define COMMON_H\n\n#ifdef HAVE_CONFIG_H\n #include <config.h>\n#endif\n\n#ifndef WIN32\n#include <unistd.h>\n#include <utime.h>\n#endif\n#include <stdlib.h>\n#include <stdint.h>             /* uint32_t */\n#include <sys/types.h>          /* size_t */\n#include <errno.h>\n#include <string.h>\n#include <limits.h>\n#include <stdio.h>\n\n#include <glib.h>\n#include <glib/gstdio.h>\n\n#ifdef WIN32\n#define strcasecmp _stricmp\n#define strncasecmp _strnicmp\n\n#if !defined S_ISDIR\n#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)\n#endif\n\n#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)\n#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)\n#endif\n\n#define F_OK 0\n#endif\n\n#define EMPTY_SHA1  \"0000000000000000000000000000000000000000\"\n\n#define CURRENT_ENC_VERSION 3\n\n#define DEFAULT_PROTO_VERSION 1\n#define CURRENT_PROTO_VERSION 7\n\n#define CURRENT_REPO_VERSION 1\n\n#define CURRENT_SYNC_PROTO_VERSION 2\n\n/* For compatibility with the old protocol, use an UUID for signature.\n * Listen manager on the server will use the new block tx protocol if it\n * receives this signature as \"token\".\n */\n#define BLOCK_PROTOCOL_SIGNATURE \"529319a0-577f-4d6b-a6c3-3c20f56f290c\"\n\n#define SEAF_PATH_MAX 4096\n\n#endif\n"
  },
  {
    "path": "common/curl-init.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#ifndef USE_GPL_CRYPTO\n\n#include <curl/curl.h>\n#include <openssl/crypto.h>\n#include <pthread.h>\n\n#include \"curl-init.h\"\n\npthread_mutex_t* curl_locks = NULL;\n\nvoid seafile_curl_locking_callback(int mode, int n, const char* file, int line)\n{\n    if (mode & CRYPTO_LOCK) {\n        pthread_mutex_lock (&curl_locks[n]);\n    } else {\n        pthread_mutex_unlock (&curl_locks[n]);\n    }\n}\n\nvoid seafile_curl_init()\n{\n    int i;\n    curl_locks = malloc (sizeof(pthread_mutex_t) * CRYPTO_num_locks());\n    for (i = 0; i < CRYPTO_num_locks(); ++i) {\n        pthread_mutex_init (&curl_locks[i], NULL);\n    }\n\n#ifndef WIN32\n    /* On Windows it's better to use the default id_function.\n     * As per http://linux.die.net/man/3/crypto_set_id_callback,\n     * the default id_functioin uses system's default thread\n     * identifying API.\n     */\n    CRYPTO_set_id_callback (pthread_self);\n#endif\n    CRYPTO_set_locking_callback (seafile_curl_locking_callback);\n}\n\nvoid seafile_curl_deinit()\n{\n    int i;\n    CRYPTO_set_id_callback (0);\n    CRYPTO_set_locking_callback (0);\n\n    for (i = 0; i < CRYPTO_num_locks(); ++i) {\n        pthread_mutex_destroy (&curl_locks[i]);\n    }\n    free (curl_locks);\n}\n\n#endif\n"
  },
  {
    "path": "common/curl-init.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef __CURL_INIT_H__\n#define __CURL_INIT_H__\n\nvoid seafile_curl_init(void);\nvoid seafile_curl_deinit(void);\n\n#endif\n"
  },
  {
    "path": "common/diff-simple.c",
    "content": "#include \"common.h\"\n#include \"diff-simple.h\"\n#include \"utils.h\"\n#include \"log.h\"\n\nDiffEntry *\ndiff_entry_new (char type, char status, unsigned char *sha1, const char *name)\n{\n    DiffEntry *de = g_new0 (DiffEntry, 1);\n\n    de->type = type;\n    de->status = status;\n    memcpy (de->sha1, sha1, 20);\n    de->name = g_strdup(name);\n\n    return de;\n}\n\nDiffEntry *\ndiff_entry_new_from_dirent (char type, char status,\n                            SeafDirent *dent, const char *basedir)\n{\n    DiffEntry *de = g_new0 (DiffEntry, 1);\n    unsigned char sha1[20];\n    char *path;\n\n    hex_to_rawdata (dent->id, sha1, 20);\n    path = g_strconcat (basedir, dent->name, NULL);\n\n    de->type = type;\n    de->status = status;\n    memcpy (de->sha1, sha1, 20);\n    de->name = path;\n\n    if (type == DIFF_TYPE_COMMITS &&\n        (status == DIFF_STATUS_ADDED ||\n         status == DIFF_STATUS_MODIFIED ||\n         status == DIFF_STATUS_DIR_ADDED ||\n         status == DIFF_STATUS_DIR_DELETED)) {\n        de->mtime = dent->mtime;\n        de->mode = dent->mode;\n        de->modifier = g_strdup(dent->modifier);\n        de->size = dent->size;\n    }\n\n    return de;\n}\n\nvoid\ndiff_entry_free (DiffEntry *de)\n{\n    g_free (de->name);\n    if (de->new_name)\n        g_free (de->new_name);\n\n    g_free (de->modifier);\n\n    g_free (de);\n}\n\ninline static gboolean\ndirent_same (SeafDirent *denta, SeafDirent *dentb)\n{\n    return (strcmp (dentb->id, denta->id) == 0 &&\n\t    denta->mode == dentb->mode &&\n\t    denta->mtime == dentb->mtime);\n}\n\nstatic int\ndiff_files (int n, SeafDirent *dents[], const char *basedir, DiffOptions *opt)\n{\n    SeafDirent *files[3];\n    int i, n_files = 0;\n\n    memset (files, 0, sizeof(files[0])*n);\n    for (i = 0; i < n; ++i) {\n        if (dents[i] && S_ISREG(dents[i]->mode)) {\n            files[i] = dents[i];\n            ++n_files;\n        }\n    }\n\n    if (n_files == 0)\n        return 0;\n\n    return opt->file_cb (n, basedir, files, opt->data);\n}\n\nstatic int\ndiff_trees_recursive (int n, SeafDir *trees[],\n                      const char *basedir, DiffOptions *opt);\n\nstatic int\ndiff_directories (int n, SeafDirent *dents[], const char *basedir, DiffOptions *opt)\n{\n    SeafDirent *dirs[3];\n    int i, n_dirs = 0;\n    char *dirname = \"\";\n    int ret;\n    SeafDir *sub_dirs[3], *dir;\n\n    memset (dirs, 0, sizeof(dirs[0])*n);\n    for (i = 0; i < n; ++i) {\n        if (dents[i] && S_ISDIR(dents[i]->mode)) {\n            dirs[i] = dents[i];\n            ++n_dirs;\n        }\n    }\n\n    if (n_dirs == 0)\n        return 0;\n\n    gboolean recurse = TRUE;\n    ret = opt->dir_cb (n, basedir, dirs, opt->data, &recurse);\n    if (ret < 0)\n        return ret;\n\n    if (!recurse)\n        return 0;\n\n    memset (sub_dirs, 0, sizeof(sub_dirs[0])*n);\n    for (i = 0; i < n; ++i) {\n        if (dents[i] != NULL && S_ISDIR(dents[i]->mode)) {\n            dir = seaf_fs_manager_get_seafdir (seaf->fs_mgr,\n                                               opt->store_id,\n                                               opt->version,\n                                               dents[i]->id);\n            if (!dir) {\n                seaf_warning (\"Failed to find dir %s:%s.\\n\",\n                              opt->store_id, dents[i]->id);\n                ret = -1;\n                goto free_sub_dirs;\n            }\n            sub_dirs[i] = dir;\n\n            dirname = dents[i]->name;\n        }\n    }\n\n    char *new_basedir = g_strconcat (basedir, dirname, \"/\", NULL);\n\n    ret = diff_trees_recursive (n, sub_dirs, new_basedir, opt);\n\n    g_free (new_basedir);\n\nfree_sub_dirs:\n    for (i = 0; i < n; ++i)\n        seaf_dir_free (sub_dirs[i]);\n    return ret;\n}\n\nstatic int\ndiff_trees_recursive (int n, SeafDir *trees[],\n                      const char *basedir, DiffOptions *opt)\n{\n    GList *ptrs[3];\n    SeafDirent *dents[3];\n    int i;\n    SeafDirent *dent;\n    char *first_name;\n    gboolean done;\n    int ret = 0;\n\n    for (i = 0; i < n; ++i) {\n        if (trees[i])\n            ptrs[i] = trees[i]->entries;\n        else\n            ptrs[i] = NULL;\n    }\n\n    while (1) {\n        first_name = NULL;\n        memset (dents, 0, sizeof(dents[0])*n);\n        done = TRUE;\n\n        /* Find the \"largest\" name, assuming dirents are sorted. */\n        for (i = 0; i < n; ++i) {\n            if (ptrs[i] != NULL) {\n                done = FALSE;\n                dent = ptrs[i]->data;\n                if (!first_name)\n                    first_name = dent->name;\n                else if (strcmp(dent->name, first_name) > 0)\n                    first_name = dent->name;\n            }\n        }\n\n        if (done)\n            break;\n\n        /*\n         * Setup dir entries for all names that equal to first_name\n         */\n        for (i = 0; i < n; ++i) {\n            if (ptrs[i] != NULL) {\n                dent = ptrs[i]->data;\n                if (strcmp(first_name, dent->name) == 0) {\n                    dents[i] = dent;\n                    ptrs[i] = ptrs[i]->next;\n                }\n            }\n        }\n\n        if (n == 2 && dents[0] && dents[1] && dirent_same(dents[0], dents[1]))\n            continue;\n\n        if (n == 3 && dents[0] && dents[1] && dents[2] &&\n            dirent_same(dents[0], dents[1]) && dirent_same(dents[0], dents[2]))\n            continue;\n\n        /* Diff files of this level. */\n        ret = diff_files (n, dents, basedir, opt);\n        if (ret < 0)\n            return ret;\n\n        /* Recurse into sub level. */\n        ret = diff_directories (n, dents, basedir, opt);\n        if (ret < 0)\n            return ret;\n    }\n\n    return ret;\n}\n\nint\ndiff_trees (int n, const char *roots[], DiffOptions *opt)\n{\n    SeafDir **trees, *root;\n    int i, ret;\n\n    g_return_val_if_fail (n == 2 || n == 3, -1);\n\n    trees = g_new0 (SeafDir *, n);\n    for (i = 0; i < n; ++i) {\n        root = seaf_fs_manager_get_seafdir (seaf->fs_mgr,\n                                            opt->store_id,\n                                            opt->version,\n                                            roots[i]);\n        if (!root) {\n            seaf_warning (\"Failed to find dir %s:%s.\\n\", opt->store_id, roots[i]);\n            g_free (trees);\n            return -1;\n        }\n        trees[i] = root;\n    }\n\n    ret = diff_trees_recursive (n, trees, \"\", opt);\n\n    for (i = 0; i < n; ++i)\n        seaf_dir_free (trees[i]);\n    g_free (trees);\n\n    return ret;\n}\n\ntypedef struct DiffData {\n    GList **results;\n    gboolean fold_dir_diff;\n} DiffData;\n\nstatic int\ntwoway_diff_files (int n, const char *basedir, SeafDirent *files[], void *vdata)\n{\n    DiffData *data = vdata;\n    GList **results = data->results;\n    DiffEntry *de;\n    SeafDirent *tree1 = files[0];\n    SeafDirent *tree2 = files[1];\n\n    if (!tree1) {\n        de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_ADDED,\n                                         tree2, basedir);\n        *results = g_list_prepend (*results, de);\n        return 0;\n    }\n\n    if (!tree2) {\n        de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_DELETED,\n                                         tree1, basedir);\n        *results = g_list_prepend (*results, de);\n        return 0;\n    }\n\n    if (!dirent_same (tree1, tree2)) {\n        de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_MODIFIED,\n                                         tree2, basedir);\n        *results = g_list_prepend (*results, de);\n    }\n\n    return 0;\n}\n\nstatic int\ntwoway_diff_dirs (int n, const char *basedir, SeafDirent *dirs[], void *vdata,\n                  gboolean *recurse)\n{\n    DiffData *data = vdata;\n    GList **results = data->results;\n    DiffEntry *de;\n    SeafDirent *tree1 = dirs[0];\n    SeafDirent *tree2 = dirs[1];\n\n    if (!tree1) {\n        if (strcmp (tree2->id, EMPTY_SHA1) == 0 || data->fold_dir_diff) {\n            de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_DIR_ADDED,\n                                             tree2, basedir);\n            *results = g_list_prepend (*results, de);\n            *recurse = FALSE;\n        } else\n            *recurse = TRUE;\n        return 0;\n    }\n\n    if (!tree2) {\n        de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS,\n                                         DIFF_STATUS_DIR_DELETED,\n                                         tree1, basedir);\n        *results = g_list_prepend (*results, de);\n\n        if (data->fold_dir_diff) {\n            *recurse = FALSE;\n        } else\n            *recurse = TRUE;\n        return 0;\n    }\n\n    return 0;\n}\n\nint\ndiff_commits (SeafCommit *commit1, SeafCommit *commit2, GList **results,\n              gboolean fold_dir_diff)\n{\n    SeafRepo *repo = NULL;\n    DiffOptions opt;\n    const char *roots[2];\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, commit1->repo_id);\n    if (!repo) {\n        seaf_warning (\"Failed to get repo %s.\\n\", commit1->repo_id);\n        return -1;\n    }\n\n    DiffData data;\n    memset (&data, 0, sizeof(data));\n    data.results = results;\n    data.fold_dir_diff = fold_dir_diff;\n\n    memset (&opt, 0, sizeof(opt));\n    memcpy (opt.store_id, repo->id, 36);\n    opt.version = repo->version;\n    opt.file_cb = twoway_diff_files;\n    opt.dir_cb = twoway_diff_dirs;\n    opt.data = &data;\n\n    roots[0] = commit1->root_id;\n    roots[1] = commit2->root_id;\n\n    diff_trees (2, roots, &opt);\n    diff_resolve_renames (results);\n\n    return 0;\n}\n\nint\ndiff_commit_roots (const char *store_id, int version,\n                   const char *root1, const char *root2, GList **results,\n                   gboolean fold_dir_diff)\n{\n    DiffOptions opt;\n    const char *roots[2];\n\n    DiffData data;\n    memset (&data, 0, sizeof(data));\n    data.results = results;\n    data.fold_dir_diff = fold_dir_diff;\n\n    memset (&opt, 0, sizeof(opt));\n    memcpy (opt.store_id, store_id, 36);\n    opt.version = version;\n    opt.file_cb = twoway_diff_files;\n    opt.dir_cb = twoway_diff_dirs;\n    opt.data = &data;\n\n    roots[0] = root1;\n    roots[1] = root2;\n\n    diff_trees (2, roots, &opt);\n    diff_resolve_renames (results);\n\n    return 0;\n}\n\nstatic int\nthreeway_diff_files (int n, const char *basedir, SeafDirent *files[], void *vdata)\n{\n    DiffData *data = vdata;\n    SeafDirent *m = files[0];\n    SeafDirent *p1 = files[1];\n    SeafDirent *p2 = files[2];\n    GList **results = data->results;\n    DiffEntry *de;\n\n    /* diff m with both p1 and p2. */\n    if (m && p1 && p2) {\n        if (!dirent_same(m, p1) && !dirent_same (m, p2)) {\n            de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_MODIFIED,\n                                             m, basedir);\n            *results = g_list_prepend (*results, de);\n        }\n    } else if (!m && p1 && p2) {\n        de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_DELETED,\n                                         p1, basedir);\n        *results = g_list_prepend (*results, de);\n    } else if (m && !p1 && p2) {\n        if (!dirent_same (m, p2)) {\n            de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_MODIFIED,\n                                             m, basedir);\n            *results = g_list_prepend (*results, de);\n        }\n    } else if (m && p1 && !p2) {\n        if (!dirent_same (m, p1)) {\n            de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_MODIFIED,\n                                             m, basedir);\n            *results = g_list_prepend (*results, de);\n        }\n    } else if (m && !p1 && !p2) {\n        de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_ADDED,\n                                         m, basedir);\n        *results = g_list_prepend (*results, de);\n    }\n    /* Nothing to do for:\n     * 1. !m && p1 && !p2;\n     * 2. !m && !p1 && p2;\n     * 3. !m && !p1 && !p2 (should not happen)\n     */\n\n    return 0;\n}\n\nstatic int\nthreeway_diff_dirs (int n, const char *basedir, SeafDirent *dirs[], void *vdata,\n                    gboolean *recurse)\n{\n    *recurse = TRUE;\n    return 0;\n}\n\nint\ndiff_merge (SeafCommit *merge, GList **results, gboolean fold_dir_diff)\n{\n    SeafRepo *repo = NULL;\n    DiffOptions opt;\n    const char *roots[3];\n    SeafCommit *parent1, *parent2;\n\n    g_return_val_if_fail (*results == NULL, -1);\n    g_return_val_if_fail (merge->parent_id != NULL &&\n                          merge->second_parent_id != NULL,\n                          -1);\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, merge->repo_id);\n    if (!repo) {\n        seaf_warning (\"Failed to get repo %s.\\n\", merge->repo_id);\n        return -1;\n    }\n\n    parent1 = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                              repo->id,\n                                              repo->version,\n                                              merge->parent_id);\n    if (!parent1) {\n        seaf_warning (\"failed to find commit %s:%s.\\n\", repo->id, merge->parent_id);\n        return -1;\n    }\n\n    parent2 = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                              repo->id,\n                                              repo->version,\n                                              merge->second_parent_id);\n    if (!parent2) {\n        seaf_warning (\"failed to find commit %s:%s.\\n\",\n                      repo->id, merge->second_parent_id);\n        seaf_commit_unref (parent1);\n        return -1;\n    }\n\n    DiffData data;\n    memset (&data, 0, sizeof(data));\n    data.results = results;\n    data.fold_dir_diff = fold_dir_diff;\n\n    memset (&opt, 0, sizeof(opt));\n    memcpy (opt.store_id, repo->id, 36);\n    opt.version = repo->version;\n    opt.file_cb = threeway_diff_files;\n    opt.dir_cb = threeway_diff_dirs;\n    opt.data = &data;\n\n    roots[0] = merge->root_id;\n    roots[1] = parent1->root_id;\n    roots[2] = parent2->root_id;\n\n    int ret = diff_trees (3, roots, &opt);\n    diff_resolve_renames (results);\n\n    seaf_commit_unref (parent1);\n    seaf_commit_unref (parent2);\n\n    return ret;\n}\n\nint\ndiff_merge_roots (const char *store_id, int version,\n                  const char *merged_root, const char *p1_root, const char *p2_root,\n                  GList **results, gboolean fold_dir_diff)\n{\n    DiffOptions opt;\n    const char *roots[3];\n\n    g_return_val_if_fail (*results == NULL, -1);\n\n    DiffData data;\n    memset (&data, 0, sizeof(data));\n    data.results = results;\n    data.fold_dir_diff = fold_dir_diff;\n\n    memset (&opt, 0, sizeof(opt));\n    memcpy (opt.store_id, store_id, 36);\n    opt.version = version;\n    opt.file_cb = threeway_diff_files;\n    opt.dir_cb = threeway_diff_dirs;\n    opt.data = &data;\n\n    roots[0] = merged_root;\n    roots[1] = p1_root;\n    roots[2] = p2_root;\n\n    diff_trees (3, roots, &opt);\n    diff_resolve_renames (results);\n\n    return 0;\n}\n\n/* This function only resolve \"strict\" rename, i.e. two files must be\n * exactly the same.\n * Don't detect rename of empty files and empty dirs.\n */\nvoid\ndiff_resolve_renames (GList **diff_entries)\n{\n    GHashTable *deleted;\n    GList *p;\n    GList *added = NULL;\n    DiffEntry *de;\n    unsigned char empty_sha1[20];\n\n    memset (empty_sha1, 0, 20);\n\n    /* Hash and equal functions for raw sha1. */\n    deleted = g_hash_table_new (ccnet_sha1_hash, ccnet_sha1_equal);\n\n    /* Collect all \"deleted\" entries. */\n    for (p = *diff_entries; p != NULL; p = p->next) {\n        de = p->data;\n        if ((de->status == DIFF_STATUS_DELETED ||\n             de->status == DIFF_STATUS_DIR_DELETED) &&\n            memcmp (de->sha1, empty_sha1, 20) != 0)\n            g_hash_table_insert (deleted, de->sha1, p);\n    }\n\n    /* Collect all \"added\" entries into a separate list. */\n    for (p = *diff_entries; p != NULL; p = p->next) {\n        de = p->data;\n        if ((de->status == DIFF_STATUS_ADDED ||\n             de->status == DIFF_STATUS_DIR_ADDED) &&\n            memcmp (de->sha1, empty_sha1, 20) != 0)\n            added = g_list_prepend (added, p);\n    }\n\n    /* For each \"added\" entry, if we find a \"deleted\" entry with\n     * the same content, we find a rename pair.\n     */\n    p = added;\n    while (p != NULL) {\n        GList *p_add, *p_del;\n        DiffEntry *de_add, *de_del, *de_rename;\n        int rename_status;\n\n        p_add = p->data;\n        de_add = p_add->data;\n\n        p_del = g_hash_table_lookup (deleted, de_add->sha1);\n        if (p_del) {\n            de_del = p_del->data;\n\n            if (de_add->status == DIFF_STATUS_DIR_ADDED)\n                rename_status = DIFF_STATUS_DIR_RENAMED;\n            else\n                rename_status = DIFF_STATUS_RENAMED;\n\n            de_rename = diff_entry_new (de_del->type, rename_status, \n                                        de_del->sha1, de_del->name);\n            de_rename->new_name = g_strdup(de_add->name);\n\n            *diff_entries = g_list_delete_link (*diff_entries, p_add);\n            *diff_entries = g_list_delete_link (*diff_entries, p_del);\n            *diff_entries = g_list_prepend (*diff_entries, de_rename);\n\n            g_hash_table_remove (deleted, de_add->sha1);\n\n            diff_entry_free (de_add);\n            diff_entry_free (de_del);\n        }\n\n        p = g_list_delete_link (p, p);\n    }\n\n    g_hash_table_destroy (deleted);\n}\n\nstatic gboolean\nis_redundant_empty_dir (DiffEntry *de_dir, DiffEntry *de_file)\n{\n    int dir_len;\n\n    if (de_dir->status == DIFF_STATUS_DIR_ADDED &&\n        de_file->status == DIFF_STATUS_DELETED)\n    {\n        dir_len = strlen (de_dir->name);\n        if (strlen (de_file->name) > dir_len &&\n            strncmp (de_dir->name, de_file->name, dir_len) == 0)\n            return TRUE;\n    }\n\n    if (de_dir->status == DIFF_STATUS_DIR_DELETED &&\n        de_file->status == DIFF_STATUS_ADDED)\n    {\n        dir_len = strlen (de_dir->name);\n        if (strlen (de_file->name) > dir_len &&\n            strncmp (de_dir->name, de_file->name, dir_len) == 0)\n            return TRUE;\n    }\n\n    return FALSE;\n}\n\n/*\n * An empty dir entry may be added by deleting all the files under it.\n * Similarly, an empty dir entry may be deleted by adding some file in it.\n * In both cases, we don't want to include the empty dir entry in the\n * diff results.\n */\nvoid\ndiff_resolve_empty_dirs (GList **diff_entries)\n{\n    GList *empty_dirs = NULL;\n    GList *p, *dir, *file;\n    DiffEntry *de, *de_dir, *de_file;\n\n    for (p = *diff_entries; p != NULL; p = p->next) {\n        de = p->data;\n        if (de->status == DIFF_STATUS_DIR_ADDED ||\n            de->status == DIFF_STATUS_DIR_DELETED)\n            empty_dirs = g_list_prepend (empty_dirs, p);\n    }\n\n    for (dir = empty_dirs; dir != NULL; dir = dir->next) {\n        de_dir = ((GList *)dir->data)->data;\n        for (file = *diff_entries; file != NULL; file = file->next) {\n            de_file = file->data;\n            if (is_redundant_empty_dir (de_dir, de_file)) {\n                *diff_entries = g_list_delete_link (*diff_entries, dir->data);\n                break;\n            }\n        }\n    }\n\n    g_list_free (empty_dirs);\n}\n\nint diff_unmerged_state(int mask)\n{\n    mask >>= 1;\n    switch (mask) {\n        case 7:\n            return STATUS_UNMERGED_BOTH_CHANGED;\n        case 3:\n            return STATUS_UNMERGED_OTHERS_REMOVED;\n        case 5:\n            return STATUS_UNMERGED_I_REMOVED;\n        case 6:\n            return STATUS_UNMERGED_BOTH_ADDED;\n        case 2:\n            return STATUS_UNMERGED_DFC_I_ADDED_FILE;\n        case 4:\n            return STATUS_UNMERGED_DFC_OTHERS_ADDED_FILE;\n        default:\n            seaf_warning (\"Unexpected unmerged case\\n\");\n    }\n    return 0;\n}\n\nchar *\nformat_diff_results(GList *results)\n{\n    GList *ptr;\n    GString *fmt_status;\n    DiffEntry *de;\n\n    fmt_status = g_string_new(\"\");\n\n    for (ptr = results; ptr; ptr = ptr->next) {\n        de = ptr->data;\n\n        if (de->status != DIFF_STATUS_RENAMED)\n            g_string_append_printf(fmt_status, \"%c %c %d %u %s\\n\",\n                                   de->type, de->status, de->unmerge_state,\n                                   (int)strlen(de->name), de->name);\n        else\n            g_string_append_printf(fmt_status, \"%c %c %d %u %s %u %s\\n\",\n                                   de->type, de->status, de->unmerge_state,\n                                   (int)strlen(de->name), de->name,\n                                   (int)strlen(de->new_name), de->new_name);\n    }\n\n    return g_string_free(fmt_status, FALSE);\n}\n\ninline static char *\nget_basename (char *path)\n{\n    char *slash;\n    slash = strrchr (path, '/');\n    if (!slash)\n        return path;\n    return (slash + 1);\n}\n\nchar *\ndiff_results_to_description (GList *results)\n{\n    GList *p;\n    DiffEntry *de;\n    char *add_mod_file = NULL, *removed_file = NULL;\n    char *renamed_file = NULL, *renamed_dir = NULL;\n    char *new_dir = NULL, *removed_dir = NULL;\n    int n_add_mod = 0, n_removed = 0, n_renamed = 0;\n    int n_new_dir = 0, n_removed_dir = 0, n_renamed_dir = 0;\n    GString *desc;\n\n    if (results == NULL)\n        return NULL;\n\n    for (p = results; p != NULL; p = p->next) {\n        de = p->data;\n        switch (de->status) {\n        case DIFF_STATUS_ADDED:\n            if (n_add_mod == 0)\n                add_mod_file = get_basename(de->name);\n            n_add_mod++;\n            break;\n        case DIFF_STATUS_DELETED:\n            if (n_removed == 0)\n                removed_file = get_basename(de->name);\n            n_removed++;\n            break;\n        case DIFF_STATUS_RENAMED:\n            if (n_renamed == 0)\n                renamed_file = get_basename(de->name);\n            n_renamed++;\n            break;\n        case DIFF_STATUS_DIR_RENAMED:\n            if (n_renamed_dir == 0)\n                renamed_dir = get_basename(de->name);\n            n_renamed_dir++;\n            break;\n        case DIFF_STATUS_MODIFIED:\n            if (n_add_mod == 0)\n                add_mod_file = get_basename(de->name);\n            n_add_mod++;\n            break;\n        case DIFF_STATUS_DIR_ADDED:\n            if (n_new_dir == 0)\n                new_dir = get_basename(de->name);\n            n_new_dir++;\n            break;\n        case DIFF_STATUS_DIR_DELETED:\n            if (n_removed_dir == 0)\n                removed_dir = get_basename(de->name);\n            n_removed_dir++;\n            break;\n        }\n    }\n\n    desc = g_string_new (\"\");\n\n    if (n_add_mod == 1)\n        g_string_append_printf (desc, \"Added or modified \\\"%s\\\".\\n\", add_mod_file);\n    else if (n_add_mod > 1)\n        g_string_append_printf (desc, \"Added or modified \\\"%s\\\" and %d more files.\\n\",\n                                add_mod_file, n_add_mod - 1);\n\n    if (n_removed == 1)\n        g_string_append_printf (desc, \"Deleted \\\"%s\\\".\\n\", removed_file);\n    else if (n_removed > 1)\n        g_string_append_printf (desc, \"Deleted \\\"%s\\\" and %d more files.\\n\",\n                                removed_file, n_removed - 1);\n\n    if (n_renamed == 1)\n        g_string_append_printf (desc, \"Renamed \\\"%s\\\".\\n\", renamed_file);\n    else if (n_renamed > 1)\n        g_string_append_printf (desc, \"Renamed \\\"%s\\\" and %d more files.\\n\",\n                                renamed_file, n_renamed - 1);\n\n    if (n_renamed_dir == 1)\n        g_string_append_printf (desc, \"Renamed directory \\\"%s\\\".\\n\", renamed_dir);\n    else if (n_renamed_dir > 1)\n        g_string_append_printf (desc, \"Renamed \\\"%s\\\" and %d more directories.\\n\",\n                                renamed_dir, n_renamed_dir - 1);\n\n    if (n_new_dir == 1)\n        g_string_append_printf (desc, \"Added directory \\\"%s\\\".\\n\", new_dir);\n    else if (n_new_dir > 1)\n        g_string_append_printf (desc, \"Added \\\"%s\\\" and %d more directories.\\n\",\n                                new_dir, n_new_dir - 1);\n\n    if (n_removed_dir == 1)\n        g_string_append_printf (desc, \"Removed directory \\\"%s\\\".\\n\", removed_dir);\n    else if (n_removed_dir > 1)\n        g_string_append_printf (desc, \"Removed \\\"%s\\\" and %d more directories.\\n\",\n                                removed_dir, n_removed_dir - 1);\n\n    return g_string_free (desc, FALSE);\n}\n"
  },
  {
    "path": "common/diff-simple.h",
    "content": "#ifndef DIFF_SIMPLE_H\n#define DIFF_SIMPLE_H\n\n#include <glib.h>\n#ifndef SEAFILE_SERVER\n#include \"index/index.h\"\n#endif\n#include \"seafile-session.h\"\n\n#define DIFF_TYPE_WORKTREE              'W' /* diff from index to worktree */\n#define DIFF_TYPE_INDEX                 'I' /* diff from commit to index */\n#define DIFF_TYPE_COMMITS               'C' /* diff between two commits*/\n\n#define DIFF_STATUS_ADDED               'A'\n#define DIFF_STATUS_DELETED             'D'\n#define DIFF_STATUS_MODIFIED\t        'M'\n#define DIFF_STATUS_RENAMED             'R'\n#define DIFF_STATUS_UNMERGED\t\t'U'\n#define DIFF_STATUS_DIR_ADDED           'B'\n#define DIFF_STATUS_DIR_DELETED         'C'\n#define DIFF_STATUS_DIR_RENAMED         'E'\n\nenum {\n    STATUS_UNMERGED_NONE,\n    /* I and others modified the same file differently. */\n    STATUS_UNMERGED_BOTH_CHANGED,\n    /* I and others created the same file with different contents. */\n    STATUS_UNMERGED_BOTH_ADDED,\n    /* I removed a file while others modified it. */\n    STATUS_UNMERGED_I_REMOVED,\n    /* Others removed a file while I modified it. */\n    STATUS_UNMERGED_OTHERS_REMOVED,\n    /* I replace a directory with a file while others modified files under the directory. */\n    STATUS_UNMERGED_DFC_I_ADDED_FILE,\n    /* Others replace a directory with a file while I modified files under the directory. */\n    STATUS_UNMERGED_DFC_OTHERS_ADDED_FILE,\n};\n\ntypedef struct DiffEntry {\n    char type;\n    char status;\n    int unmerge_state;\n    unsigned char sha1[20];     /* used for resolve rename */\n    char *name;\n    char *new_name;             /* only used in rename. */\n\n#ifdef SEAFILE_CLIENT\n    /* Fields only used for ADDED, DIR_ADDED, MODIFIED types,\n     * used in check out files/dirs.*/\n    gint64 mtime;\n    unsigned int mode;\n    char *modifier;\n    gint64 size;\n#endif\n} DiffEntry;\n\nDiffEntry *\ndiff_entry_new (char type, char status, unsigned char *sha1, const char *name);\n\nvoid\ndiff_entry_free (DiffEntry *de);\n\n#ifndef SEAFILE_SERVER\nint\ndiff_index (const char *repo_id, int version,\n            struct index_state *istate, SeafDir *root, GList **results);\n#endif\n\n/*\n * @fold_dir_diff: if TRUE, only the top level directory will be included\n *                 in the diff result if a directory with files is added or removed.\n *                 Otherwise all the files in the direcotory will be recursively\n *                 included in the diff result.\n */\nint\ndiff_commits (SeafCommit *commit1, SeafCommit *commit2, GList **results,\n              gboolean fold_dir_diff);\n\nint\ndiff_commit_roots (const char *store_id, int version,\n                   const char *root1, const char *root2, GList **results,\n                   gboolean fold_dir_diff);\n\nint\ndiff_merge (SeafCommit *merge, GList **results, gboolean fold_dir_diff);\n\nint\ndiff_merge_roots (const char *store_id, int version,\n                  const char *merged_root, const char *p1_root, const char *p2_root,\n                  GList **results, gboolean fold_dir_diff);\n\nvoid\ndiff_resolve_renames (GList **diff_entries);\n\nvoid\ndiff_resolve_empty_dirs (GList **diff_entries);\n\nint \ndiff_unmerged_state(int mask);\n\nchar *\nformat_diff_results(GList *results);\n\nchar *\ndiff_results_to_description (GList *results);\n\ntypedef int (*DiffFileCB) (int n,\n                           const char *basedir,\n                           SeafDirent *files[],\n                           void *data);\n\ntypedef int (*DiffDirCB) (int n,\n                          const char *basedir,\n                          SeafDirent *dirs[],\n                          void *data,\n                          gboolean *recurse);\n\ntypedef struct DiffOptions {\n    char store_id[37];\n    int version;\n\n    DiffFileCB file_cb;\n    DiffDirCB dir_cb;\n    void *data;\n} DiffOptions;\n\nint\ndiff_trees (int n, const char *roots[], DiffOptions *opt);\n\n#endif\n"
  },
  {
    "path": "common/fs-mgr.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#include <sys/stat.h>\n#include <fcntl.h>\n#ifndef WIN32\n#include <dirent.h>\n#endif\n\n#ifndef WIN32\n    #include <arpa/inet.h>\n#endif\n\n#include <searpc-utils.h>\n\n#include \"seafile-session.h\"\n#include \"seafile-error.h\"\n#include \"fs-mgr.h\"\n#include \"block-mgr.h\"\n#include \"utils.h\"\n#define DEBUG_FLAG SEAFILE_DEBUG_OTHER\n#include \"log.h\"\n#include \"../common/seafile-crypt.h\"\n\n#ifndef SEAFILE_SERVER\n#include \"../daemon/vc-utils.h\"\n#include \"vc-common.h\"\n#endif  /* SEAFILE_SERVER */\n\n#include \"db.h\"\n\nstruct _SeafFSManagerPriv {\n    /* GHashTable      *seafile_cache; */\n    GHashTable      *bl_cache;\n};\n\n#ifdef WIN32\ntypedef struct SeafileOndisk {\n    guint32          type;\n    guint64          file_size;\n    unsigned char    block_ids[0];\n} SeafileOndisk;\n#else\ntypedef struct SeafileOndisk {\n    guint32          type;\n    guint64          file_size;\n    unsigned char    block_ids[0];\n} __attribute__((__packed__)) SeafileOndisk;\n#endif\n\n#ifdef WIN32\ntypedef struct DirentOndisk {\n    guint32 mode;\n    char    id[40];\n    guint32 name_len;\n    char    name[0];\n} DirentOndisk;\n#else\ntypedef struct DirentOndisk {\n    guint32 mode;\n    char    id[40];\n    guint32 name_len;\n    char    name[0];\n} __attribute__((__packed__)) DirentOndisk;\n#endif\n\n#ifdef WIN32\ntypedef struct SeafdirOndisk {\n    guint32 type;\n    char    dirents[0];\n} SeafdirOndisk;\n#else\ntypedef struct SeafdirOndisk {\n    guint32 type;\n    char    dirents[0];\n} __attribute__((__packed__)) SeafdirOndisk;\n#endif\n\n#ifndef SEAFILE_SERVER\nuint32_t\ncalculate_chunk_size (uint64_t total_size);\nstatic int\nwrite_seafile (SeafFSManager *fs_mgr,\n               const char *repo_id, int version,\n               CDCFileDescriptor *cdc,\n               unsigned char *obj_sha1);\n#endif  /* SEAFILE_SERVER */\n\nSeafFSManager *\nseaf_fs_manager_new (SeafileSession *seaf,\n                     const char *seaf_dir)\n{\n    SeafFSManager *mgr = g_new0 (SeafFSManager, 1);\n\n    mgr->seaf = seaf;\n\n    mgr->obj_store = seaf_obj_store_new (seaf, \"fs\");\n    if (!mgr->obj_store) {\n        g_free (mgr);\n        return NULL;\n    }\n\n    mgr->priv = g_new0(SeafFSManagerPriv, 1);\n\n    return mgr;\n}\n\nint\nseaf_fs_manager_init (SeafFSManager *mgr)\n{\n#ifdef SEAFILE_SERVER\n\n#ifdef FULL_FEATURE\n    if (seaf_obj_store_init (mgr->obj_store, TRUE, seaf->ev_mgr) < 0) {\n        seaf_warning (\"[fs mgr] Failed to init fs object store.\\n\");\n        return -1;\n    }\n#else\n    if (seaf_obj_store_init (mgr->obj_store, FALSE, NULL) < 0) {\n        seaf_warning (\"[fs mgr] Failed to init fs object store.\\n\");\n        return -1;\n    }\n#endif\n\n#else\n    if (seaf_obj_store_init (mgr->obj_store, TRUE, seaf->ev_mgr) < 0) {\n        seaf_warning (\"[fs mgr] Failed to init fs object store.\\n\");\n        return -1;\n    }\n#endif\n\n    return 0;\n}\n\n#ifndef SEAFILE_SERVER\nstatic int\ncheckout_block (const char *repo_id,\n                int version,\n                const char *block_id,\n                int wfd,\n                SeafileCrypt *crypt)\n{\n    SeafBlockManager *block_mgr = seaf->block_mgr;\n    BlockHandle *handle;\n    BlockMetadata *bmd;\n    char *dec_out = NULL;\n    int dec_out_len = -1;\n    char *blk_content = NULL;\n\n    handle = seaf_block_manager_open_block (block_mgr,\n                                            repo_id, version,\n                                            block_id, BLOCK_READ);\n    if (!handle) {\n        seaf_warning (\"Failed to open block %s\\n\", block_id);\n        return -1;\n    }\n\n    /* first stat the block to get its size */\n    bmd = seaf_block_manager_stat_block_by_handle (block_mgr, handle);\n    if (!bmd) {\n        seaf_warning (\"can't stat block %s.\\n\", block_id);\n        goto checkout_blk_error;\n    }\n\n    /* empty file, skip it */\n    if (bmd->size == 0) {\n        seaf_block_manager_close_block (block_mgr, handle);\n        seaf_block_manager_block_handle_free (block_mgr, handle);\n        return 0;\n    }\n\n    blk_content = (char *)malloc (bmd->size * sizeof(char));\n\n    /* read the block to prepare decryption */\n    if (seaf_block_manager_read_block (block_mgr, handle,\n                                       blk_content, bmd->size) != bmd->size) {\n        seaf_warning (\"Error when reading from block %s.\\n\", block_id);\n        goto checkout_blk_error;\n    }\n\n    if (crypt != NULL) {\n\n        /* An encrypted block size must be a multiple of\n           ENCRYPT_BLK_SIZE\n        */\n        if (bmd->size % ENCRYPT_BLK_SIZE != 0) {\n            seaf_warning (\"Error: An invalid encrypted block, %s \\n\", block_id);\n            goto checkout_blk_error;\n        }\n\n        /* decrypt the block */\n        int ret = seafile_decrypt (&dec_out,\n                                   &dec_out_len,\n                                   blk_content,\n                                   bmd->size,\n                                   crypt);\n\n        if (ret != 0) {\n            seaf_warning (\"Decryt block %s failed. \\n\", block_id);\n            goto checkout_blk_error;\n        }\n\n        /* write the decrypted content */\n        ret = writen (wfd, dec_out, dec_out_len);\n\n\n        if (ret !=  dec_out_len) {\n            seaf_warning (\"Failed to write the decryted block %s.\\n\",\n                       block_id);\n            goto checkout_blk_error;\n        }\n\n        g_free (blk_content);\n        g_free (dec_out);\n\n    } else {\n        /* not an encrypted block */\n        if (writen(wfd, blk_content, bmd->size) != bmd->size) {\n            seaf_warning (\"Failed to write the decryted block %s.\\n\",\n                       block_id);\n            goto checkout_blk_error;\n        }\n        g_free (blk_content);\n    }\n\n    g_free (bmd);\n    seaf_block_manager_close_block (block_mgr, handle);\n    seaf_block_manager_block_handle_free (block_mgr, handle);\n    return 0;\n\ncheckout_blk_error:\n\n    if (blk_content)\n        free (blk_content);\n    if (dec_out)\n        g_free (dec_out);\n    if (bmd)\n        g_free (bmd);\n\n    seaf_block_manager_close_block (block_mgr, handle);\n    seaf_block_manager_block_handle_free (block_mgr, handle);\n    return -1;\n}\n\nvoid\nfree_checkout_block_aux (CheckoutBlockAux *aux)\n{\n    if (!aux)\n        return;\n    g_free (aux->repo_id);\n    g_free (aux->host);\n    g_free (aux->token);\n    g_free (aux);\n}\n\n#define SEAF_BACKUP_EXT \".sbak\"\n\n/*\n * File updating procedure:\n * 1. Checkout server versioin to tmp file.\n * 2. If there is a local version, move it to a backup file.\n * 3. Rename the tmp file to the destination path.\n * 4. Remove the backup file if exists.\n */\nint\nseaf_fs_manager_checkout_file (SeafFSManager *mgr,\n                               FileCheckoutData *data,\n                               int *error_id,\n                               CheckoutBlockCallback callback,\n                               CheckoutBlockAux *user_data)\n{\n    Seafile *seafile = NULL;\n    char *blk_id;\n    int wfd = -1;\n    int i;\n    char *tmp_path = NULL;\n    char *backup_path = NULL;\n    char *conflict_path = NULL;\n    SeafStat st;\n    gboolean path_exists = FALSE;\n\n    const char *repo_id = data->repo_id;\n    int version = data->version;\n    const char *file_id = data->file_id; \n    const char *file_path = data->file_path;\n    guint32 mode = data->mode;\n    guint64 mtime = data->mtime;\n    struct SeafileCrypt *crypt = data->crypt;\n    const char *in_repo_path = data->in_repo_path;\n    const char *conflict_head_id = data->conflict_head_id;\n    gboolean force_conflict = data->force_conflict;\n    gboolean *conflicted = data->conflicted;\n    const char *email = data->email;\n    gint64 skip_buffer_size = data->skip_buffer_size;\n    int block_offset = data->block_offset;\n\n    *conflicted = FALSE;\n\n    /* Check out server version to tmp file. */\n\n    seafile = seaf_fs_manager_get_seafile (mgr, repo_id, version, file_id);\n    if (!seafile) {\n        *error_id = FETCH_CHECKOUT_FAILED;\n        seaf_warning (\"File %s does not exist.\\n\", file_id);\n        return -1;\n    }\n\n    tmp_path = g_strconcat (file_path, SEAF_TMP_EXT, NULL);\n\n    path_exists = (seaf_stat (tmp_path, &st) == 0);\n\n    mode_t rmode = mode & 0100 ? 0777 : 0666;\n    if  (!path_exists || crypt || (block_offset == 0)) {\n        if (path_exists)\n            seaf_util_unlink (tmp_path);\n        wfd = seaf_util_create (tmp_path, O_WRONLY | O_TRUNC | O_CREAT | O_BINARY,\n                                rmode & ~S_IFMT);\n        if (wfd < 0) {\n            *error_id = FETCH_CHECKOUT_FAILED;\n            seaf_warning (\"Failed to open file %s for checkout: %s.\\n\",\n                       tmp_path, strerror(errno));\n            goto bad;\n        }\n        seaf_setxattr (tmp_path, SEAFILE_FILE_ID_ATTR, file_id, 41);\n    } else {\n        wfd = seaf_util_open (tmp_path, O_WRONLY | O_BINARY);\n        if (wfd < 0) {\n            *error_id = FETCH_CHECKOUT_FAILED;\n            seaf_warning (\"Failed to open file %s for checkout: %s.\\n\",\n                       tmp_path, strerror(errno));\n            goto bad;\n        }\n        if (seaf_util_lseek (wfd, skip_buffer_size, SEEK_SET) < 0) {\n            *error_id = FETCH_CHECKOUT_FAILED;\n            seaf_warning (\"Failed to seek file %s for checkout: %s.\\n\",\n                       tmp_path, strerror(errno));\n            goto bad;\n        }\n    }\n\n    i = block_offset;\n    for (; i < seafile->n_blocks; ++i) {\n        blk_id = seafile->blk_sha1s[i];\n        if (callback (repo_id, blk_id, wfd, crypt, user_data)) {\n            *error_id = FETCH_CHECKOUT_TRANSFER_ERROR;\n            goto bad;\n        }\n    }\n\n    close (wfd);\n    wfd = -1;\n\n    seaf_removexattr (tmp_path, SEAFILE_FILE_ID_ATTR);\n\n    /* Move existing file to backup file. */\n\n    backup_path = g_strconcat (file_path, SEAF_BACKUP_EXT, NULL);\n\n    if (seaf_util_exists (file_path) &&\n        seaf_util_rename (file_path, backup_path) < 0) {\n        seaf_warning (\"Failed to rename %s to %s: %s. \"\n                      \"Checkout server version as conflict file.\\n\",\n                      file_path, backup_path, strerror(errno));\n\n        *conflicted = TRUE;\n\n        conflict_path = gen_conflict_path_wrapper (repo_id, version,\n                                                   conflict_head_id, in_repo_path,\n                                                   file_path);\n        if (!conflict_path) {\n            *error_id = FETCH_CHECKOUT_FAILED;\n            goto bad;\n        }\n\n        if (seaf_util_rename (tmp_path, conflict_path) < 0) {\n            *error_id = FETCH_CHECKOUT_FAILED;\n            goto bad;\n        }\n\n        goto out;\n    }\n\n    /* Now that the old existing file has been renamed to backup file,\n     * this rename operation usually succeeds.\n     */\n    if (seaf_util_rename (tmp_path, file_path) < 0) {\n        seaf_warning (\"Failed to rename %s to %s: %s. \"\n                      \"Checkout server version as conflict file.\\n\",\n                      tmp_path, file_path, strerror(errno));\n\n        *conflicted = TRUE;\n\n        /* Restore the existing file. */\n        if (seaf_util_rename (backup_path, file_path) < 0) {\n            seaf_warning (\"Failed to rename %s to %s: %s. \"\n                          \"Failed to restore backup file.\\n\",\n                          backup_path, file_path, strerror(errno));\n        }\n\n        conflict_path = gen_conflict_path_wrapper (repo_id, version,\n                                                   conflict_head_id, in_repo_path,\n                                                   file_path);\n        if (!conflict_path) {\n            *error_id = FETCH_CHECKOUT_FAILED;\n            goto bad;\n        }\n\n        if (seaf_util_rename (tmp_path, conflict_path) < 0) {\n            *error_id = FETCH_CHECKOUT_FAILED;\n            goto bad;\n        }\n\n        goto out;\n    }\n\n    if (force_conflict) {\n        *conflicted = TRUE;\n\n        /* XXX\n         * In new syncing protocol and http sync, files are checked out before\n         * the repo is created. So we can't get user email from repo at this point.\n         * So a email parameter is needed.\n         * For old syncing protocol, repo always exists when files are checked out.\n         * This is a quick and dirty hack. A cleaner solution should modifiy the\n         * code of old syncing protocol to pass in email too. But I don't want to\n         * spend more time on the nearly obsoleted code.\n         */\n        const char *suffix = NULL;\n        if (email) {\n            suffix = email;\n        } else {\n            SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n            if (!repo) {\n                *error_id = FETCH_CHECKOUT_FAILED;\n                goto bad;\n            }\n            suffix = email;\n        }\n\n        conflict_path = gen_conflict_path (file_path, suffix, (gint64)time(NULL));\n\n        if (seaf_util_exists (backup_path) &&\n            seaf_util_rename (backup_path, conflict_path) < 0) {\n            seaf_warning (\"Failed to rename %s to %s: %s. \"\n                          \"Failed to move backup file to conflict file.\\n\",\n                          backup_path, conflict_path, strerror(errno));\n            if (mtime > 0) {\n                /*\n                 * Set the checked out file mtime to what it has to be.\n                 */\n                if (seaf_set_file_time (file_path, mtime) < 0) {\n                    seaf_warning (\"Failed to set mtime for %s.\\n\", file_path);\n                }\n            }\n            //Don't delete local file when failed to rename backup file to conflict file.\n            goto out;\n        }\n    }\n\n    if (mtime > 0) {\n        /* \n         * Set the checked out file mtime to what it has to be.\n         */\n        if (seaf_set_file_time (file_path, mtime) < 0) {\n            seaf_warning (\"Failed to set mtime for %s.\\n\", file_path);\n        }\n    }\n\n    seaf_util_unlink (backup_path);\n\nout:\n    g_free (tmp_path);\n    g_free (backup_path);\n    g_free (conflict_path);\n    seafile_unref (seafile);\n    return 0;\n\nbad:\n    if (wfd >= 0)\n        close (wfd);\n    /* Remove the tmp file if it still exists, in case that rename fails. */\n    if ((*error_id == FETCH_CHECKOUT_FAILED) || crypt)\n        seaf_util_unlink (tmp_path);\n    g_free (tmp_path);\n    g_free (backup_path);\n    g_free (conflict_path);\n    seafile_unref (seafile);\n    return -1;\n}\n\n#endif /* SEAFILE_SERVER */\n\nstatic void *\ncreate_seafile_v0 (CDCFileDescriptor *cdc, int *ondisk_size, char *seafile_id)\n{\n    SeafileOndisk *ondisk;\n\n    rawdata_to_hex (cdc->file_sum, seafile_id, 20);\n\n    *ondisk_size = sizeof(SeafileOndisk) + cdc->block_nr * 20;\n    ondisk = (SeafileOndisk *)g_new0 (char, *ondisk_size);\n\n    ondisk->type = htonl(SEAF_METADATA_TYPE_FILE);\n    ondisk->file_size = hton64 (cdc->file_size);\n    memcpy (ondisk->block_ids, cdc->blk_sha1s, cdc->block_nr * 20);\n\n    return ondisk;\n}\n\nstatic void *\ncreate_seafile_json (int repo_version,\n                     CDCFileDescriptor *cdc,\n                     int *ondisk_size,\n                     char *seafile_id)\n{\n    json_t *object, *block_id_array;\n\n    object = json_object ();\n\n    json_object_set_int_member (object, \"type\", SEAF_METADATA_TYPE_FILE);\n    json_object_set_int_member (object, \"version\",\n                                seafile_version_from_repo_version(repo_version));\n\n    json_object_set_int_member (object, \"size\", cdc->file_size);\n\n    block_id_array = json_array ();\n    int i;\n    uint8_t *ptr = cdc->blk_sha1s;\n    char block_id[41];\n    for (i = 0; i < cdc->block_nr; ++i) {\n        rawdata_to_hex (ptr, block_id, 20);\n        json_array_append_new (block_id_array, json_string(block_id));\n        ptr += 20;\n    }\n    json_object_set_new (object, \"block_ids\", block_id_array);\n\n    char *data = json_dumps (object, JSON_SORT_KEYS);\n    *ondisk_size = strlen(data);\n\n    /* The seafile object id is sha1 hash of the json object. */\n    unsigned char sha1[20];\n    calculate_sha1 (sha1, data, *ondisk_size);\n    rawdata_to_hex (sha1, seafile_id, 20);\n\n    json_decref (object);\n    return data;\n}\n\nvoid\nseaf_fs_manager_calculate_seafile_id_json (int repo_version,\n                                           CDCFileDescriptor *cdc,\n                                           guint8 *file_id_sha1)\n{\n    json_t *object, *block_id_array;\n\n    object = json_object ();\n\n    json_object_set_int_member (object, \"type\", SEAF_METADATA_TYPE_FILE);\n    json_object_set_int_member (object, \"version\",\n                                seafile_version_from_repo_version(repo_version));\n\n    json_object_set_int_member (object, \"size\", cdc->file_size);\n\n    block_id_array = json_array ();\n    int i;\n    uint8_t *ptr = cdc->blk_sha1s;\n    char block_id[41];\n    for (i = 0; i < cdc->block_nr; ++i) {\n        rawdata_to_hex (ptr, block_id, 20);\n        json_array_append_new (block_id_array, json_string(block_id));\n        ptr += 20;\n    }\n    json_object_set_new (object, \"block_ids\", block_id_array);\n\n    char *data = json_dumps (object, JSON_SORT_KEYS);\n    int ondisk_size = strlen(data);\n\n    /* The seafile object id is sha1 hash of the json object. */\n    calculate_sha1 (file_id_sha1, data, ondisk_size);\n\n    json_decref (object);\n    free (data);\n}\n\nstatic int\nwrite_seafile (SeafFSManager *fs_mgr,\n               const char *repo_id,\n               int version,\n               CDCFileDescriptor *cdc,\n               unsigned char *obj_sha1)\n{\n    int ret = 0;\n    char seafile_id[41];\n    void *ondisk;\n    int ondisk_size;\n\n    if (version > 0) {\n        ondisk = create_seafile_json (version, cdc, &ondisk_size, seafile_id);\n\n        guint8 *compressed;\n        int outlen;\n\n        if (seaf_compress (ondisk, ondisk_size, &compressed, &outlen) < 0) {\n            seaf_warning (\"Failed to compress seafile obj %s:%s.\\n\",\n                          repo_id, seafile_id);\n            ret = -1;\n            free (ondisk);\n            goto out;\n        }\n\n        if (seaf_obj_store_write_obj (fs_mgr->obj_store, repo_id, version, seafile_id,\n                                      compressed, outlen, FALSE) < 0)\n            ret = -1;\n        g_free (compressed);\n        free (ondisk);\n    } else {\n        ondisk = create_seafile_v0 (cdc, &ondisk_size, seafile_id);\n\n        if (seaf_obj_store_write_obj (fs_mgr->obj_store, repo_id, version, seafile_id,\n                                      ondisk, ondisk_size, FALSE) < 0)\n            ret = -1;\n        g_free (ondisk);\n    }\n\nout:\n    if (ret == 0)\n        hex_to_rawdata (seafile_id, obj_sha1, 20);\n\n    return ret;\n}\n\nuint32_t\ncalculate_chunk_size (uint64_t total_size)\n{\n    const uint64_t GiB = 1073741824;\n    const uint64_t MiB = 1048576;\n\n    if (total_size >= (8 * GiB)) return 8 * MiB;\n    if (total_size >= (4 * GiB)) return 4 * MiB;\n    if (total_size >= (2 * GiB)) return 2 * MiB;\n\n    return 1 * MiB;\n}\n\nstatic int\ndo_write_chunk (const char *repo_id, int version,\n                uint8_t *checksum, const char *buf, int len)\n{\n    SeafBlockManager *blk_mgr = seaf->block_mgr;\n    char chksum_str[41];\n    BlockHandle *handle;\n    int n;\n\n    rawdata_to_hex (checksum, chksum_str, 20);\n\n    /* Don't write if the block already exists. */\n    if (seaf_block_manager_block_exists (seaf->block_mgr,\n                                         repo_id, version,\n                                         chksum_str))\n        return 0;\n\n    handle = seaf_block_manager_open_block (blk_mgr,\n                                            repo_id, version,\n                                            chksum_str, BLOCK_WRITE);\n    if (!handle) {\n        seaf_warning (\"Failed to open block %s.\\n\", chksum_str);\n        return -1;\n    }\n\n    n = seaf_block_manager_write_block (blk_mgr, handle, buf, len);\n    if (n < 0) {\n        seaf_warning (\"Failed to write chunk %s.\\n\", chksum_str);\n        seaf_block_manager_close_block (blk_mgr, handle);\n        seaf_block_manager_block_handle_free (blk_mgr, handle);\n        return -1;\n    }\n\n    if (seaf_block_manager_close_block (blk_mgr, handle) < 0) {\n        seaf_warning (\"failed to close block %s.\\n\", chksum_str);\n        seaf_block_manager_block_handle_free (blk_mgr, handle);\n        return -1;\n    }\n\n    if (seaf_block_manager_commit_block (blk_mgr, handle) < 0) {\n        seaf_warning (\"failed to commit chunk %s.\\n\", chksum_str);\n        seaf_block_manager_block_handle_free (blk_mgr, handle);\n        return -1;\n    }\n\n    seaf_block_manager_block_handle_free (blk_mgr, handle);\n    return 0;\n}\n\n/* write the chunk and store its checksum */\nint\nseafile_write_chunk (const char *repo_id,\n                     int version,\n                     CDCDescriptor *chunk,\n                     SeafileCrypt *crypt,\n                     uint8_t *checksum,\n                     gboolean write_data)\n{\n    GChecksum *ctx = g_checksum_new (G_CHECKSUM_SHA1);\n    gsize len = 20;\n    int ret = 0;\n\n    /* Encrypt before write to disk if needed, and we don't encrypt\n     * empty files. */\n    if (crypt != NULL && chunk->len) {\n        char *encrypted_buf = NULL;         /* encrypted output */\n        int enc_len = -1;                /* encrypted length */\n\n        ret = seafile_encrypt (&encrypted_buf, /* output */\n                               &enc_len,      /* output len */\n                               chunk->block_buf, /* input */\n                               chunk->len,       /* input len */\n                               crypt);\n        if (ret != 0) {\n            seaf_warning (\"Error: failed to encrypt block\\n\");\n            g_checksum_free (ctx);\n            return -1;\n        }\n\n        if (seaf->disable_block_hash) {\n            char *uuid = gen_uuid();\n            g_checksum_update (ctx, (unsigned char *)uuid, strlen(uuid));\n            g_free(uuid);\n        } else {\n            g_checksum_update (ctx, (unsigned char *)encrypted_buf, enc_len);\n        }\n        g_checksum_get_digest (ctx, checksum, &len);\n\n        if (write_data)\n            ret = do_write_chunk (repo_id, version, checksum, encrypted_buf, enc_len);\n        g_free (encrypted_buf);\n    } else {\n        /* not a encrypted repo, go ahead */\n        if (seaf->disable_block_hash) {\n            char *uuid = gen_uuid();\n            g_checksum_update (ctx, (unsigned char *)uuid, strlen(uuid));\n            g_free(uuid);\n        }\n        else {\n            g_checksum_update (ctx, (unsigned char *)chunk->block_buf, chunk->len);\n        }\n        g_checksum_get_digest (ctx, checksum, &len);\n\n        if (write_data)\n            ret = do_write_chunk (repo_id, version, checksum, chunk->block_buf, chunk->len);\n    }\n\n    g_checksum_free (ctx);\n\n    return ret;\n}\n\nstatic void\ncreate_cdc_for_empty_file (CDCFileDescriptor *cdc)\n{\n    memset (cdc, 0, sizeof(CDCFileDescriptor));\n}\n\ntypedef struct ChunkingData {\n    const char *repo_id;\n    int version;\n    uint32_t blk_size;\n    const char *file_path;\n    SeafileCrypt *crypt;\n    guint8 *blk_sha1s;\n    GAsyncQueue *finished_tasks;\n} ChunkingData;\n\nstatic void\nchunking_worker (gpointer vdata, gpointer user_data)\n{\n    ChunkingData *data = user_data;\n    CDCDescriptor *chunk = vdata;\n    int fd = -1;\n    ssize_t n;\n    int idx;\n\n    chunk->block_buf = g_new0 (char, chunk->len);\n    if (!chunk->block_buf) {\n        seaf_warning (\"Failed to allow chunk buffer\\n\");\n        goto out;\n    }\n\n    fd = seaf_util_open (data->file_path, O_RDONLY | O_BINARY);\n    if (fd < 0) {\n        seaf_warning (\"Failed to open %s: %s\\n\", data->file_path, strerror(errno));\n        chunk->result = -1;\n        goto out;\n    }\n\n    if (seaf_util_lseek (fd, chunk->offset, SEEK_SET) == (gint64)-1) {\n        seaf_warning (\"Failed to lseek %s: %s\\n\", data->file_path, strerror(errno));\n        chunk->result = -1;\n        goto out;\n    }\n\n    n = readn (fd, chunk->block_buf, chunk->len);\n    if (n < 0) {\n        seaf_warning (\"Failed to read chunk from %s: %s\\n\",\n                      data->file_path, strerror(errno));\n        chunk->result = -1;\n        goto out;\n    }\n\n    chunk->result = seafile_write_chunk (data->repo_id, data->version,\n                                         chunk, data->crypt,\n                                         chunk->checksum, 1);\n    if (chunk->result < 0)\n        goto out;\n\n    idx = chunk->offset / data->blk_size;\n    memcpy (data->blk_sha1s + idx * CHECKSUM_LENGTH, chunk->checksum, CHECKSUM_LENGTH);\n\nout:\n    g_free (chunk->block_buf);\n    close (fd);\n    g_async_queue_push (data->finished_tasks, chunk);\n}\n\n#define DEFAULT_SPLIT_FILE_TO_BLOCK_THREADS 3\n\nstatic int\nsplit_file_to_block (const char *repo_id,\n                     int version,\n                     const char *file_path,\n                     gint64 file_size,\n                     SeafileCrypt *crypt,\n                     CDCFileDescriptor *cdc,\n                     gboolean write_data)\n{\n    int n_blocks;\n    uint8_t *block_sha1s = NULL;\n    GThreadPool *tpool = NULL;\n    GAsyncQueue *finished_tasks = NULL;\n    GList *pending_tasks = NULL;\n    int n_pending = 0;\n    CDCDescriptor *chunk;\n    int ret = 0;\n\n    n_blocks = (file_size + cdc->block_sz - 1) / cdc->block_sz;\n    block_sha1s = g_new0 (uint8_t, n_blocks * CHECKSUM_LENGTH);\n    if (!block_sha1s) {\n        seaf_warning (\"Failed to allocate block_sha1s.\\n\");\n        ret = -1;\n        goto out;\n    }\n\n    finished_tasks = g_async_queue_new ();\n\n    ChunkingData data;\n    memset (&data, 0, sizeof(data));\n    data.repo_id = repo_id;\n    data.version = version;\n    data.file_path = file_path;\n    data.crypt = crypt;\n    data.blk_sha1s = block_sha1s;\n    data.finished_tasks = finished_tasks;\n    data.blk_size = cdc->block_sz;\n    \n    tpool = g_thread_pool_new (chunking_worker, &data,\n                               DEFAULT_SPLIT_FILE_TO_BLOCK_THREADS, FALSE, NULL);\n    if (!tpool) {\n        seaf_warning (\"Failed to allocate thread pool\\n\");\n        ret = -1;\n        goto out;\n    }\n\n    guint64 offset = 0;\n    guint64 len;\n    guint64 left = (guint64)file_size;\n    while (left > 0) {\n        len = ((left >= cdc->block_sz) ? cdc->block_sz : left);\n\n        chunk = g_new0 (CDCDescriptor, 1);\n        chunk->offset = offset;\n        chunk->len = (guint32)len;\n\n        g_thread_pool_push (tpool, chunk, NULL);\n        pending_tasks = g_list_prepend (pending_tasks, chunk);\n        n_pending++;\n\n        left -= len;\n        offset += len;\n    }\n\n    while ((chunk = g_async_queue_pop (finished_tasks)) != NULL) {\n        if (chunk->result < 0) {\n            ret = -1;\n            goto out;\n        }\n\n        if ((--n_pending) <= 0) {\n            break;\n        }\n    }\n\n    cdc->block_nr = n_blocks;\n    cdc->blk_sha1s = block_sha1s;\n\n\nout:\n    if (tpool)\n        g_thread_pool_free (tpool, TRUE, TRUE);\n    if (finished_tasks)\n        g_async_queue_unref (finished_tasks);\n    g_list_free_full (pending_tasks, g_free);\n    if (ret < 0)\n        g_free (block_sha1s);\n\n    return ret;\n}\n\n\nint\nseaf_fs_manager_index_blocks (SeafFSManager *mgr,\n                              const char *repo_id,\n                              int version,\n                              const char *file_path,\n                              unsigned char sha1[],\n                              gint64 *size,\n                              SeafileCrypt *crypt,\n                              gboolean write_data,\n                              gboolean use_cdc)\n{\n    SeafStat sb;\n    CDCFileDescriptor cdc;\n\n    if (seaf_stat (file_path, &sb) < 0) {\n        seaf_warning (\"Bad file %s: %s.\\n\", file_path, strerror(errno));\n        return -1;\n    }\n\n    g_return_val_if_fail (S_ISREG(sb.st_mode), -1);\n\n    if (sb.st_size == 0) {\n        /* handle empty file. */\n        memset (sha1, 0, 20);\n        create_cdc_for_empty_file (&cdc);\n    } else {\n        memset (&cdc, 0, sizeof(cdc));\n\n        if (seaf->cdc_average_block_size == 0) {\n            cdc.block_sz = CDC_AVERAGE_BLOCK_SIZE;\n            cdc.block_min_sz = CDC_MIN_BLOCK_SIZE;\n            cdc.block_max_sz = CDC_MAX_BLOCK_SIZE;\n        } else {\n            cdc.block_sz = seaf->cdc_average_block_size;\n            cdc.block_min_sz = seaf->cdc_average_block_size >> 1;\n            cdc.block_max_sz = seaf->cdc_average_block_size << 1;\n        }\n        \n        if (use_cdc) {\n            cdc.write_block = seafile_write_chunk;\n            memcpy (cdc.repo_id, repo_id, 36);\n            cdc.version = version;\n            if (filename_chunk_cdc (file_path, &cdc, crypt, write_data) < 0) {\n                seaf_warning (\"Failed to chunk file with CDC.\\n\");\n                return -1;\n            }\n        } else {\n            memcpy (cdc.repo_id, repo_id, 36);\n            cdc.version = version;\n            cdc.file_size = sb.st_size;\n            if (split_file_to_block (repo_id, version, file_path, sb.st_size,\n                                     crypt, &cdc, write_data) < 0) {\n                return -1;\n            }            \n        }\n\n        if (write_data && write_seafile (mgr, repo_id, version, &cdc, sha1) < 0) {\n            g_free (cdc.blk_sha1s);\n            seaf_warning (\"Failed to write seafile for %s.\\n\", file_path);\n            return -1;\n        }\n    }\n\n    *size = (gint64)sb.st_size;\n\n    if (cdc.blk_sha1s)\n        free (cdc.blk_sha1s);\n\n    return 0;\n}\n\nvoid\nseafile_ref (Seafile *seafile)\n{\n    ++seafile->ref_count;\n}\n\nstatic void\nseafile_free (Seafile *seafile)\n{\n    int i;\n\n    if (seafile->blk_sha1s) {\n        for (i = 0; i < seafile->n_blocks; ++i)\n            g_free (seafile->blk_sha1s[i]);\n        g_free (seafile->blk_sha1s);\n    }\n\n    g_free (seafile);\n}\n\nvoid\nseafile_unref (Seafile *seafile)\n{\n    if (!seafile)\n        return;\n\n    if (--seafile->ref_count <= 0)\n        seafile_free (seafile);\n}\n\nstatic Seafile *\nseafile_from_v0_data (const char *id, const void *data, int len)\n{\n    const SeafileOndisk *ondisk = data;\n    Seafile *seafile;\n    int id_list_len, n_blocks;\n\n    if (len < sizeof(SeafileOndisk)) {\n        seaf_warning (\"[fs mgr] Corrupt seafile object %s.\\n\", id);\n        return NULL;\n    }\n\n    if (ntohl(ondisk->type) != SEAF_METADATA_TYPE_FILE) {\n        seaf_warning (\"[fd mgr] %s is not a file.\\n\", id);\n        return NULL;\n    }\n\n    id_list_len = len - sizeof(SeafileOndisk);\n    if (id_list_len % 20 != 0) {\n        seaf_warning (\"[fs mgr] Corrupt seafile object %s.\\n\", id);\n        return NULL;\n    }\n    n_blocks = id_list_len / 20;\n\n    seafile = g_new0 (Seafile, 1);\n\n    seafile->object.type = SEAF_METADATA_TYPE_FILE;\n    seafile->version = 0;\n    memcpy (seafile->file_id, id, 41);\n    seafile->file_size = ntoh64 (ondisk->file_size);\n    seafile->n_blocks = n_blocks;\n\n    seafile->blk_sha1s = g_new0 (char*, seafile->n_blocks);\n    const unsigned char *blk_sha1_ptr = ondisk->block_ids;\n    int i;\n    for (i = 0; i < seafile->n_blocks; ++i) {\n        char *blk_sha1 = g_new0 (char, 41);\n        seafile->blk_sha1s[i] = blk_sha1;\n        rawdata_to_hex (blk_sha1_ptr, blk_sha1, 20);\n        blk_sha1_ptr += 20;\n    }\n\n    seafile->ref_count = 1;\n    return seafile;\n}\n\nstatic Seafile *\nseafile_from_json_object (const char *id, json_t *object)\n{\n    json_t *block_id_array = NULL;\n    int type;\n    int version;\n    guint64 file_size;\n    Seafile *seafile = NULL;\n\n    /* Sanity checks. */\n    type = json_object_get_int_member (object, \"type\");\n    if (type != SEAF_METADATA_TYPE_FILE) {\n        seaf_debug (\"Object %s is not a file.\\n\", id);\n        return NULL;\n    }\n\n    version = (int) json_object_get_int_member (object, \"version\");\n    if (version < 1) {\n        seaf_debug (\"Seafile object %s version should be > 0, version is %d.\\n\",\n                    id, version);\n        return NULL;\n    }\n\n    file_size = (guint64) json_object_get_int_member (object, \"size\");\n\n    block_id_array = json_object_get (object, \"block_ids\");\n    if (!block_id_array) {\n        seaf_debug (\"No block id array in seafile object %s.\\n\", id);\n        return NULL;\n    }\n\n    seafile = g_new0 (Seafile, 1);\n\n    seafile->object.type = SEAF_METADATA_TYPE_FILE;\n\n    memcpy (seafile->file_id, id, 40);\n    seafile->version = version;\n    seafile->file_size = file_size;\n    seafile->n_blocks = json_array_size (block_id_array);\n    seafile->blk_sha1s = g_new0 (char *, seafile->n_blocks);\n\n    int i;\n    json_t *block_id_obj;\n    const char *block_id;\n    for (i = 0; i < seafile->n_blocks; ++i) {\n        block_id_obj = json_array_get (block_id_array, i);\n        block_id = json_string_value (block_id_obj);\n        if (!block_id || !is_object_id_valid(block_id)) {\n            seafile_free (seafile);\n            return NULL;\n        }\n        seafile->blk_sha1s[i] = g_strdup(block_id);\n    }\n\n    seafile->ref_count = 1;\n\n    return seafile;\n}\n\nstatic Seafile *\nseafile_from_json (const char *id, void *data, int len)\n{\n    guint8 *decompressed;\n    int outlen;\n    json_t *object = NULL;\n    json_error_t error;\n    Seafile *seafile;\n\n    if (seaf_decompress (data, len, &decompressed, &outlen) < 0) {\n        seaf_warning (\"Failed to decompress seafile object %s.\\n\", id);\n        return NULL;\n    }\n\n    object = json_loadb ((const char *)decompressed, outlen, 0, &error);\n    g_free (decompressed);\n    if (!object) {\n        seaf_warning (\"Failed to load seafile json object: %s.\\n\", error.text);\n        return NULL;\n    }\n\n    seafile = seafile_from_json_object (id, object);\n\n    json_decref (object);\n    return seafile;\n}\n\nstatic Seafile *\nseafile_from_data (const char *id, void *data, int len, gboolean is_json)\n{\n    if (is_json)\n        return seafile_from_json (id, data, len);\n    else\n        return seafile_from_v0_data (id, data, len);\n}\n\nSeafile *\nseaf_fs_manager_get_seafile (SeafFSManager *mgr,\n                             const char *repo_id,\n                             int version,\n                             const char *file_id)\n{\n    void *data;\n    int len;\n    Seafile *seafile;\n\n#if 0\n    seafile = g_hash_table_lookup (mgr->priv->seafile_cache, file_id);\n    if (seafile) {\n        seafile_ref (seafile);\n        return seafile;\n    }\n#endif\n\n    if (memcmp (file_id, EMPTY_SHA1, 40) == 0) {\n        seafile = g_new0 (Seafile, 1);\n        memset (seafile->file_id, '0', 40);\n        seafile->ref_count = 1;\n        return seafile;\n    }\n\n    if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version,\n                                 file_id, &data, &len) < 0) {\n        seaf_warning (\"[fs mgr] Failed to read file %s.\\n\", file_id);\n        return NULL;\n    }\n\n    seafile = seafile_from_data (file_id, data, len, (version > 0));\n    g_free (data);\n\n#if 0\n    /*\n     * Add to cache. Also increase ref count.\n     */\n    seafile_ref (seafile);\n    g_hash_table_insert (mgr->priv->seafile_cache, g_strdup(file_id), seafile);\n#endif\n\n    return seafile;\n}\n\nstatic guint8 *\nseafile_to_v0_data (Seafile *file, int *len)\n{\n    SeafileOndisk *ondisk;\n\n    *len = sizeof(SeafileOndisk) + file->n_blocks * 20;\n    ondisk = (SeafileOndisk *)g_new0 (char, *len);\n\n    ondisk->type = htonl(SEAF_METADATA_TYPE_FILE);\n    ondisk->file_size = hton64 (file->file_size);\n\n    guint8 *ptr = ondisk->block_ids;\n    int i;\n    for (i = 0; i < file->n_blocks; ++i) {\n        hex_to_rawdata (file->blk_sha1s[i], ptr, 20);\n        ptr += 20;\n    }\n\n    return (guint8 *)ondisk;\n}\n\nstatic guint8 *\nseafile_to_json (Seafile *file, int *len)\n{\n    json_t *object, *block_id_array;\n\n    object = json_object ();\n\n    json_object_set_int_member (object, \"type\", SEAF_METADATA_TYPE_FILE);\n    json_object_set_int_member (object, \"version\", file->version);\n\n    json_object_set_int_member (object, \"size\", file->file_size);\n\n    block_id_array = json_array ();\n    int i;\n    for (i = 0; i < file->n_blocks; ++i) {\n        json_array_append_new (block_id_array, json_string(file->blk_sha1s[i]));\n    }\n    json_object_set_new (object, \"block_ids\", block_id_array);\n\n    char *data = json_dumps (object, JSON_SORT_KEYS);\n    *len = strlen(data);\n\n    unsigned char sha1[20];\n    calculate_sha1 (sha1, data, *len);\n    rawdata_to_hex (sha1, file->file_id, 20);\n\n    json_decref (object);\n    return (guint8 *)data;\n}\n\nstatic guint8 *\nseafile_to_data (Seafile *file, int *len)\n{\n    if (file->version > 0) {\n        guint8 *data;\n        int orig_len;\n        guint8 *compressed;\n\n        data = seafile_to_json (file, &orig_len);\n        if (!data)\n            return NULL;\n\n        if (seaf_compress (data, orig_len, &compressed, len) < 0) {\n            seaf_warning (\"Failed to compress file object %s.\\n\", file->file_id);\n            g_free (data);\n            return NULL;\n        }\n        g_free (data);\n        return compressed;\n    } else\n        return seafile_to_v0_data (file, len);\n}\n\nint\nseafile_save (SeafFSManager *fs_mgr,\n              const char *repo_id,\n              int version,\n              Seafile *file)\n{\n    guint8 *data;\n    int len;\n    int ret = 0;\n\n    data = seafile_to_data (file, &len);\n    if (!data)\n        return -1;\n\n    if (seaf_obj_store_write_obj (fs_mgr->obj_store, repo_id, version, file->file_id,\n                                  data, len, FALSE) < 0)\n        ret = -1;\n\n    g_free (data);\n    return ret;\n}\n\nstatic void compute_dir_id_v0 (SeafDir *dir, GList *entries)\n{\n    GChecksum *ctx;\n    GList *p;\n    uint8_t sha1[20];\n    gsize len = 20;\n    SeafDirent *dent;\n    guint32 mode_le;\n\n    /* ID for empty dirs is EMPTY_SHA1. */\n    if (entries == NULL) {\n        memset (dir->dir_id, '0', 40);\n        return;\n    }\n\n    ctx = g_checksum_new (G_CHECKSUM_SHA1);\n    for (p = entries; p; p = p->next) {\n        dent = (SeafDirent *)p->data;\n        g_checksum_update (ctx, (unsigned char *)dent->id, 40);\n        g_checksum_update (ctx, (unsigned char *)dent->name, dent->name_len);\n        /* Convert mode to little endian before compute. */\n        if (G_BYTE_ORDER == G_BIG_ENDIAN)\n            mode_le = GUINT32_SWAP_LE_BE (dent->mode);\n        else\n            mode_le = dent->mode;\n        g_checksum_update (ctx, (unsigned char *)&mode_le, sizeof(mode_le));\n    }\n    g_checksum_get_digest (ctx, sha1, &len);\n\n    rawdata_to_hex (sha1, dir->dir_id, 20);\n}\n\nSeafDir *\nseaf_dir_new (const char *id, GList *entries, int version)\n{\n    SeafDir *dir;\n\n    dir = g_new0(SeafDir, 1);\n\n    dir->version = version;\n    if (id != NULL) {\n        memcpy(dir->dir_id, id, 40);\n        dir->dir_id[40] = '\\0';\n    } else if (version == 0) {\n        compute_dir_id_v0 (dir, entries);\n    }\n    dir->entries = entries;\n\n    if (dir->entries != NULL)\n        dir->ondisk = seaf_dir_to_data (dir, &dir->ondisk_size);\n    else\n        memcpy (dir->dir_id, EMPTY_SHA1, 40);\n\n    return dir;\n}\n\nvoid\nseaf_dir_free (SeafDir *dir)\n{\n    if (dir == NULL)\n        return;\n\n    GList *ptr = dir->entries;\n    while (ptr) {\n        seaf_dirent_free ((SeafDirent *)ptr->data);\n        ptr = ptr->next;\n    }\n\n    g_list_free (dir->entries);\n    g_free (dir->ondisk);\n    g_free(dir);\n}\n\nSeafDirent *\nseaf_dirent_new (int version, const char *sha1, int mode, const char *name,\n                 gint64 mtime, const char *modifier, gint64 size)\n{\n    SeafDirent *dent;\n\n    dent = g_new0 (SeafDirent, 1);\n    dent->version = version;\n    memcpy(dent->id, sha1, 40);\n    dent->id[40] = '\\0';\n    /* Mode for files must have 0644 set. To prevent the caller from forgetting,\n     * we set the bits here.\n     */\n    if (S_ISREG(mode))\n        dent->mode = (mode | 0644);\n    else\n        dent->mode = mode;\n    dent->name = g_strdup(name);\n    dent->name_len = strlen(name);\n\n    if (version > 0) {\n        dent->mtime = mtime;\n        if (S_ISREG(mode)) {\n            dent->modifier = g_strdup(modifier);\n            dent->size = size;\n        }\n    }\n\n    return dent;\n}\n\nvoid \nseaf_dirent_free (SeafDirent *dent)\n{\n    if (!dent)\n        return;\n    g_free (dent->name);\n    g_free (dent->modifier);\n    g_free (dent);\n}\n\nSeafDirent *\nseaf_dirent_dup (SeafDirent *dent)\n{\n    SeafDirent *new_dent;\n\n    new_dent = g_memdup (dent, sizeof(SeafDirent));\n    new_dent->name = g_strdup(dent->name);\n    new_dent->modifier = g_strdup(dent->modifier);\n\n    return new_dent;\n}\n\nstatic SeafDir *\nseaf_dir_from_v0_data (const char *dir_id, const uint8_t *data, int len)\n{\n    SeafDir *root;\n    SeafDirent *dent;\n    const uint8_t *ptr;\n    int remain;\n    int dirent_base_size;\n    guint32 meta_type;\n    guint32 name_len;\n\n    ptr = data;\n    remain = len;\n\n    meta_type = get32bit (&ptr);\n    remain -= 4;\n    if (meta_type != SEAF_METADATA_TYPE_DIR) {\n        seaf_warning (\"Data does not contain a directory.\\n\");\n        return NULL;\n    }\n\n    root = g_new0(SeafDir, 1);\n    root->object.type = SEAF_METADATA_TYPE_DIR;\n    root->version = 0;\n    memcpy(root->dir_id, dir_id, 40);\n    root->dir_id[40] = '\\0';\n\n    dirent_base_size = 2 * sizeof(guint32) + 40;\n    while (remain > dirent_base_size) {\n        dent = g_new0(SeafDirent, 1);\n\n        dent->version = 0;\n        dent->mode = get32bit (&ptr);\n        memcpy (dent->id, ptr, 40);\n        dent->id[40] = '\\0';\n        ptr += 40;\n        name_len = get32bit (&ptr);\n        remain -= dirent_base_size;\n        if (remain >= name_len) {\n            dent->name_len = MIN (name_len, SEAF_DIR_NAME_LEN - 1);\n            dent->name = g_strndup((const char *)ptr, dent->name_len);\n            ptr += dent->name_len;\n            remain -= dent->name_len;\n        } else {\n            seaf_warning (\"Bad data format for dir objcet %s.\\n\", dir_id);\n            g_free (dent);\n            goto bad;\n        }\n\n        root->entries = g_list_prepend (root->entries, dent);\n    }\n\n    root->entries = g_list_reverse (root->entries);\n\n    return root;\n\nbad:\n    seaf_dir_free (root);\n    return NULL;\n}\n\nstatic SeafDirent *\nparse_dirent (const char *dir_id, int version, json_t *object)\n{\n    guint32 mode;\n    const char *id;\n    const char *name;\n    gint64 mtime;\n    const char *modifier;\n    gint64 size;\n\n    mode = (guint32) json_object_get_int_member (object, \"mode\");\n\n    id = json_object_get_string_member (object, \"id\");\n    if (!id) {\n        seaf_debug (\"Dirent id not set for dir object %s.\\n\", dir_id);\n        return NULL;\n    }\n    if (!is_object_id_valid (id)) {\n        seaf_debug (\"Dirent id is invalid for dir object %s.\\n\", dir_id);\n        return NULL;\n    }\n\n    name = json_object_get_string_member (object, \"name\");\n    if (!name) {\n        seaf_debug (\"Dirent name not set for dir object %s.\\n\", dir_id);\n        return NULL;\n    }\n\n    mtime = json_object_get_int_member (object, \"mtime\");\n    if (S_ISREG(mode)) {\n        modifier = json_object_get_string_member (object, \"modifier\");\n        if (!modifier) {\n            seaf_debug (\"Dirent modifier not set for dir object %s.\\n\", dir_id);\n            return NULL;\n        }\n        size = json_object_get_int_member (object, \"size\");\n    }\n\n    SeafDirent *dirent = g_new0 (SeafDirent, 1);\n    dirent->version = version;\n    dirent->mode = mode;\n    memcpy (dirent->id, id, 40);\n    dirent->name_len = strlen(name);\n    dirent->name = g_strdup(name);\n    dirent->mtime = mtime;\n    if (S_ISREG(mode)) {\n        dirent->modifier = g_strdup(modifier);\n        dirent->size = size;\n    }\n\n    return dirent;\n}\n\nstatic SeafDir *\nseaf_dir_from_json_object (const char *dir_id, json_t *object)\n{\n    json_t *dirent_array = NULL;\n    int type;\n    int version;\n    SeafDir *dir = NULL;\n\n    /* Sanity checks. */\n    type = json_object_get_int_member (object, \"type\");\n    if (type != SEAF_METADATA_TYPE_DIR) {\n        seaf_debug (\"Object %s is not a dir.\\n\", dir_id);\n        return NULL;\n    }\n\n    version = (int) json_object_get_int_member (object, \"version\");\n    if (version < 1) {\n        seaf_debug (\"Dir object %s version should be > 0, version is %d.\\n\",\n                    dir_id, version);\n        return NULL;\n    }\n\n    dirent_array = json_object_get (object, \"dirents\");\n    if (!dirent_array) {\n        seaf_debug (\"No dirents in dir object %s.\\n\", dir_id);\n        return NULL;\n    }\n\n    dir = g_new0 (SeafDir, 1);\n\n    dir->object.type = SEAF_METADATA_TYPE_DIR;\n\n    memcpy (dir->dir_id, dir_id, 40);\n    dir->version = version;\n\n    size_t n_dirents = json_array_size (dirent_array);\n    int i;\n    json_t *dirent_obj;\n    SeafDirent *dirent;\n    for (i = 0; i < n_dirents; ++i) {\n        dirent_obj = json_array_get (dirent_array, i);\n        dirent = parse_dirent (dir_id, version, dirent_obj);\n        if (!dirent) {\n            seaf_dir_free (dir);\n            return NULL;\n        }\n        dir->entries = g_list_prepend (dir->entries, dirent);\n    }\n    dir->entries = g_list_reverse (dir->entries);\n\n    return dir;\n}\n\nstatic SeafDir *\nseaf_dir_from_json (const char *dir_id, uint8_t *data, int len)\n{\n    guint8 *decompressed;\n    int outlen;\n    json_t *object = NULL;\n    json_error_t error;\n    SeafDir *dir;\n\n    if (seaf_decompress (data, len, &decompressed, &outlen) < 0) {\n        seaf_warning (\"Failed to decompress dir object %s.\\n\", dir_id);\n        return NULL;\n    }\n\n    object = json_loadb ((const char *)decompressed, outlen, 0, &error);\n    g_free (decompressed);\n    if (!object) {\n        seaf_warning (\"Failed to load seafdir json object: %s.\\n\", error.text);\n        return NULL;\n    }\n\n    dir = seaf_dir_from_json_object (dir_id, object);\n\n    json_decref (object);\n    return dir;\n}\n\nSeafDir *\nseaf_dir_from_data (const char *dir_id, uint8_t *data, int len,\n                    gboolean is_json)\n{\n    if (is_json)\n        return seaf_dir_from_json (dir_id, data, len);\n    else\n        return seaf_dir_from_v0_data (dir_id, data, len);\n}\n\ninline static int\nondisk_dirent_size (SeafDirent *dirent)\n{\n    return sizeof(DirentOndisk) + dirent->name_len;\n}\n\nstatic void *\nseaf_dir_to_v0_data (SeafDir *dir, int *len)\n{\n    SeafdirOndisk *ondisk;\n    int dir_ondisk_size = sizeof(SeafdirOndisk);\n    GList *dirents = dir->entries;\n    GList *ptr;\n    SeafDirent *de;\n    char *p;\n    DirentOndisk *de_ondisk;\n\n    for (ptr = dirents; ptr; ptr = ptr->next) {\n        de = ptr->data;\n        dir_ondisk_size += ondisk_dirent_size (de);\n    }\n\n    *len = dir_ondisk_size;\n    ondisk = (SeafdirOndisk *) g_new0 (char, dir_ondisk_size);\n\n    ondisk->type = htonl (SEAF_METADATA_TYPE_DIR);\n    p = ondisk->dirents;\n    for (ptr = dirents; ptr; ptr = ptr->next) {\n        de = ptr->data;\n        de_ondisk = (DirentOndisk *) p;\n\n        de_ondisk->mode = htonl(de->mode);\n        memcpy (de_ondisk->id, de->id, 40);\n        de_ondisk->name_len = htonl (de->name_len);\n        memcpy (de_ondisk->name, de->name, de->name_len);\n\n        p += ondisk_dirent_size (de);\n    }\n\n    return (void *)ondisk;\n}\n\nstatic void\nadd_to_dirent_array (json_t *array, SeafDirent *dirent)\n{\n    json_t *object;\n\n    object = json_object ();\n    json_object_set_int_member (object, \"mode\", dirent->mode);\n    json_object_set_string_member (object, \"id\", dirent->id);\n    json_object_set_string_member (object, \"name\", dirent->name);\n    json_object_set_int_member (object, \"mtime\", dirent->mtime);\n    if (S_ISREG(dirent->mode)) {\n        json_object_set_string_member (object, \"modifier\", dirent->modifier);\n        json_object_set_int_member (object, \"size\", dirent->size);\n    }\n\n    json_array_append_new (array, object);\n}\n\nstatic void *\nseaf_dir_to_json (SeafDir *dir, int *len)\n{\n    json_t *object, *dirent_array;\n    GList *ptr;\n    SeafDirent *dirent;\n\n    object = json_object ();\n\n    json_object_set_int_member (object, \"type\", SEAF_METADATA_TYPE_DIR);\n    json_object_set_int_member (object, \"version\", dir->version);\n\n    dirent_array = json_array ();\n    for (ptr = dir->entries; ptr; ptr = ptr->next) {\n        dirent = ptr->data;\n        add_to_dirent_array (dirent_array, dirent);\n    }\n    json_object_set_new (object, \"dirents\", dirent_array);\n\n    char *data = json_dumps (object, JSON_SORT_KEYS);\n    *len = strlen(data);\n\n    /* The dir object id is sha1 hash of the json object. */\n    unsigned char sha1[20];\n    calculate_sha1 (sha1, data, *len);\n    rawdata_to_hex (sha1, dir->dir_id, 20);\n\n    json_decref (object);\n    return data;\n}\n\nvoid *\nseaf_dir_to_data (SeafDir *dir, int *len)\n{\n    if (dir->version > 0) {\n        guint8 *data;\n        int orig_len;\n        guint8 *compressed;\n\n        data = seaf_dir_to_json (dir, &orig_len);\n        if (!data)\n            return NULL;\n\n        if (seaf_compress (data, orig_len, &compressed, len) < 0) {\n            seaf_warning (\"Failed to compress dir object %s.\\n\", dir->dir_id);\n            g_free (data);\n            return NULL;\n        }\n\n        g_free (data);\n        return compressed;\n    } else\n        return seaf_dir_to_v0_data (dir, len);\n}\n\nint\nseaf_dir_save (SeafFSManager *fs_mgr,\n               const char *repo_id,\n               int version,\n               SeafDir *dir)\n{\n    int ret = 0;\n\n    /* Don't need to save empty dir on disk. */\n    if (memcmp (dir->dir_id, EMPTY_SHA1, 40) == 0)\n        return 0;\n\n    if (seaf_obj_store_write_obj (fs_mgr->obj_store, repo_id, version, dir->dir_id,\n                                  dir->ondisk, dir->ondisk_size, FALSE) < 0)\n        ret = -1;\n\n    return ret;\n}\n\nSeafDir *\nseaf_fs_manager_get_seafdir (SeafFSManager *mgr,\n                             const char *repo_id,\n                             int version,\n                             const char *dir_id)\n{\n    void *data;\n    int len;\n    SeafDir *dir;\n\n    /* TODO: add hash cache */\n\n    if (memcmp (dir_id, EMPTY_SHA1, 40) == 0) {\n        dir = g_new0 (SeafDir, 1);\n        dir->version = version;\n        memset (dir->dir_id, '0', 40);\n        return dir;\n    }\n\n    if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version,\n                                 dir_id, &data, &len) < 0) {\n        seaf_warning (\"[fs mgr] Failed to read dir %s.\\n\", dir_id);\n        return NULL;\n    }\n\n    dir = seaf_dir_from_data (dir_id, data, len, (version > 0));\n    g_free (data);\n\n    return dir;\n}\n\nstatic gint\ncompare_dirents (gconstpointer a, gconstpointer b)\n{\n    const SeafDirent *denta = a, *dentb = b;\n\n    return strcmp (dentb->name, denta->name);\n}\n\nstatic gboolean\nis_dirents_sorted (GList *dirents)\n{\n    GList *ptr;\n    SeafDirent *dent, *dent_n;\n    gboolean ret = TRUE;\n\n    for (ptr = dirents; ptr != NULL; ptr = ptr->next) {\n        dent = ptr->data;\n        if (!ptr->next)\n            break;\n        dent_n = ptr->next->data;\n\n        /* If dirents are not sorted in descending order, return FALSE. */\n        if (strcmp (dent->name, dent_n->name) < 0) {\n            ret = FALSE;\n            break;\n        }\n    }\n\n    return ret;\n}\n\nSeafDir *\nseaf_fs_manager_get_seafdir_sorted (SeafFSManager *mgr,\n                                    const char *repo_id,\n                                    int version,\n                                    const char *dir_id)\n{\n    SeafDir *dir = seaf_fs_manager_get_seafdir(mgr, repo_id, version, dir_id);\n\n    if (!dir)\n        return NULL;\n\n    /* Only some very old dir objects are not sorted. */\n    if (version > 0)\n        return dir;\n\n    if (!is_dirents_sorted (dir->entries))\n        dir->entries = g_list_sort (dir->entries, compare_dirents);\n\n    return dir;\n}\n\nSeafDir *\nseaf_fs_manager_get_seafdir_sorted_by_path (SeafFSManager *mgr,\n                                            const char *repo_id,\n                                            int version,\n                                            const char *root_id,\n                                            const char *path)\n{\n    SeafDir *dir = seaf_fs_manager_get_seafdir_by_path (mgr, repo_id,\n                                                        version, root_id,\n                                                        path, NULL);\n\n    if (!dir)\n        return NULL;\n\n    /* Only some very old dir objects are not sorted. */\n    if (version > 0)\n        return dir;\n\n    if (!is_dirents_sorted (dir->entries))\n        dir->entries = g_list_sort (dir->entries, compare_dirents);\n\n    return dir;\n}\n\nstatic int\nparse_metadata_type_v0 (const uint8_t *data, int len)\n{\n    const uint8_t *ptr = data;\n\n    if (len < sizeof(guint32))\n        return SEAF_METADATA_TYPE_INVALID;\n\n    return (int)(get32bit(&ptr));\n}\n\nstatic int\nparse_metadata_type_json (const char *obj_id, uint8_t *data, int len)\n{\n    guint8 *decompressed;\n    int outlen;\n    json_t *object;\n    json_error_t error;\n    int type;\n\n    if (seaf_decompress (data, len, &decompressed, &outlen) < 0) {\n        seaf_warning (\"Failed to decompress fs object %s.\\n\", obj_id);\n        return SEAF_METADATA_TYPE_INVALID;\n    }\n\n    object = json_loadb ((const char *)decompressed, outlen, 0, &error);\n    g_free (decompressed);\n    if (!object) {\n        seaf_warning (\"Failed to load fs json object: %s.\\n\", error.text);\n        return SEAF_METADATA_TYPE_INVALID;\n    }\n\n    type = json_object_get_int_member (object, \"type\");\n\n    json_decref (object);\n    return type;\n}\n\nint\nseaf_metadata_type_from_data (const char *obj_id,\n                              uint8_t *data, int len, gboolean is_json)\n{\n    if (is_json)\n        return parse_metadata_type_json (obj_id, data, len);\n    else\n        return parse_metadata_type_v0 (data, len);\n}\n\nSeafFSObject *\nfs_object_from_v0_data (const char *obj_id, const uint8_t *data, int len)\n{\n    int type = parse_metadata_type_v0 (data, len);\n\n    if (type == SEAF_METADATA_TYPE_FILE)\n        return (SeafFSObject *)seafile_from_v0_data (obj_id, data, len);\n    else if (type == SEAF_METADATA_TYPE_DIR)\n        return (SeafFSObject *)seaf_dir_from_v0_data (obj_id, data, len);\n    else {\n        seaf_warning (\"Invalid object type %d.\\n\", type);\n        return NULL;\n    }\n}\n\nSeafFSObject *\nfs_object_from_json (const char *obj_id, uint8_t *data, int len)\n{\n    guint8 *decompressed;\n    int outlen;\n    json_t *object;\n    json_error_t error;\n    int type;\n    SeafFSObject *fs_obj;\n\n    if (seaf_decompress (data, len, &decompressed, &outlen) < 0) {\n        seaf_warning (\"Failed to decompress fs object %s.\\n\", obj_id);\n        return NULL;\n    }\n\n    object = json_loadb ((const char *)decompressed, outlen, 0, &error);\n    g_free (decompressed);\n    if (!object) {\n        seaf_warning (\"Failed to load fs json object: %s.\\n\", error.text);\n        return NULL;\n    }\n\n    type = json_object_get_int_member (object, \"type\");\n\n    if (type == SEAF_METADATA_TYPE_FILE)\n        fs_obj = (SeafFSObject *)seafile_from_json_object (obj_id, object);\n    else if (type == SEAF_METADATA_TYPE_DIR)\n        fs_obj = (SeafFSObject *)seaf_dir_from_json_object (obj_id, object);\n    else {\n        seaf_warning (\"Invalid fs type %d.\\n\", type);\n        json_decref (object);\n        return NULL;\n    }\n\n    json_decref (object);\n\n    return fs_obj;\n}\n\nSeafFSObject *\nseaf_fs_object_from_data (const char *obj_id,\n                          uint8_t *data, int len,\n                          gboolean is_json)\n{\n    if (is_json)\n        return fs_object_from_json (obj_id, data, len);\n    else\n        return fs_object_from_v0_data (obj_id, data, len);\n}\n\nvoid\nseaf_fs_object_free (SeafFSObject *obj)\n{\n    if (!obj)\n        return;\n\n    if (obj->type == SEAF_METADATA_TYPE_FILE)\n        seafile_unref ((Seafile *)obj);\n    else if (obj->type == SEAF_METADATA_TYPE_DIR)\n        seaf_dir_free ((SeafDir *)obj);\n}\n\nBlockList *\nblock_list_new ()\n{\n    BlockList *bl = g_new0 (BlockList, 1);\n\n    bl->block_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);\n    bl->block_ids = g_ptr_array_new_with_free_func (g_free);\n\n    return bl;\n}\n\nvoid\nblock_list_free (BlockList *bl)\n{\n    if (bl->block_hash)\n        g_hash_table_destroy (bl->block_hash);\n    g_ptr_array_free (bl->block_ids, TRUE);\n    g_free (bl);\n}\n\nvoid\nblock_list_insert (BlockList *bl, const char *block_id)\n{\n    if (g_hash_table_lookup (bl->block_hash, block_id))\n        return;\n\n    char *key = g_strdup(block_id);\n    g_hash_table_replace (bl->block_hash, key, key);\n    g_ptr_array_add (bl->block_ids, g_strdup(block_id));\n    ++bl->n_blocks;\n}\n\nBlockList *\nblock_list_difference (BlockList *bl1, BlockList *bl2)\n{\n    BlockList *bl;\n    int i;\n    char *block_id;\n    char *key;\n\n    bl = block_list_new ();\n\n    for (i = 0; i < bl1->block_ids->len; ++i) {\n        block_id = g_ptr_array_index (bl1->block_ids, i);\n        if (g_hash_table_lookup (bl2->block_hash, block_id) == NULL) {\n            key = g_strdup(block_id);\n            g_hash_table_replace (bl->block_hash, key, key);\n            g_ptr_array_add (bl->block_ids, g_strdup(block_id));\n            ++bl->n_blocks;\n        }\n    }\n\n    return bl;\n}\n\nstatic int\ntraverse_file (SeafFSManager *mgr,\n               const char *repo_id,\n               int version,\n               const char *id,\n               TraverseFSTreeCallback callback,\n               void *user_data,\n               gboolean skip_errors)\n{\n    gboolean stop = FALSE;\n\n    if (memcmp (id, EMPTY_SHA1, 40) == 0)\n        return 0;\n\n    if (!callback (mgr, repo_id, version, id, SEAF_METADATA_TYPE_FILE, user_data, &stop) &&\n        !skip_errors)\n        return -1;\n\n    return 0;\n}\n\nstatic int\ntraverse_dir (SeafFSManager *mgr,\n              const char *repo_id,\n              int version,\n              const char *id,\n              TraverseFSTreeCallback callback,\n              void *user_data,\n              gboolean skip_errors)\n{\n    SeafDir *dir;\n    GList *p;\n    SeafDirent *seaf_dent;\n    gboolean stop = FALSE;\n\n    if (!callback (mgr, repo_id, version,\n                   id, SEAF_METADATA_TYPE_DIR, user_data, &stop) &&\n        !skip_errors)\n        return -1;\n\n    if (stop)\n        return 0;\n\n    dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, id);\n    if (!dir) {\n        seaf_warning (\"[fs-mgr]get seafdir %s failed\\n\", id);\n        if (skip_errors)\n            return 0;\n        return -1;\n    }\n    for (p = dir->entries; p; p = p->next) {\n        seaf_dent = (SeafDirent *)p->data;\n\n        if (S_ISREG(seaf_dent->mode)) {\n            if (traverse_file (mgr, repo_id, version, seaf_dent->id,\n                               callback, user_data, skip_errors) < 0) {\n                if (!skip_errors) {\n                    seaf_dir_free (dir);\n                    return -1;\n                }\n            }\n        } else if (S_ISDIR(seaf_dent->mode)) {\n            if (traverse_dir (mgr, repo_id, version, seaf_dent->id,\n                              callback, user_data, skip_errors) < 0) {\n                if (!skip_errors) {\n                    seaf_dir_free (dir);\n                    return -1;\n                }\n            }\n        }\n    }\n\n    seaf_dir_free (dir);\n    return 0;\n}\n\nint\nseaf_fs_manager_traverse_tree (SeafFSManager *mgr,\n                               const char *repo_id,\n                               int version,\n                               const char *root_id,\n                               TraverseFSTreeCallback callback,\n                               void *user_data,\n                               gboolean skip_errors)\n{\n    if (strcmp (root_id, EMPTY_SHA1) == 0) {\n        return 0;\n    }\n    return traverse_dir (mgr, repo_id, version, root_id, callback, user_data, skip_errors);\n}\n\nstatic int\ntraverse_dir_path (SeafFSManager *mgr,\n                   const char *repo_id,\n                   int version,\n                   const char *dir_path,\n                   SeafDirent *dent,\n                   TraverseFSPathCallback callback,\n                   void *user_data)\n{\n    SeafDir *dir;\n    GList *p;\n    SeafDirent *seaf_dent;\n    gboolean stop = FALSE;\n    char *sub_path;\n    int ret = 0;\n\n    if (!callback (mgr, dir_path, dent, user_data, &stop))\n        return -1;\n\n    if (stop)\n        return 0;\n\n    dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, dent->id);\n    if (!dir) {\n        seaf_warning (\"get seafdir %s:%s failed\\n\", repo_id, dent->id);\n        return -1;\n    }\n\n    for (p = dir->entries; p; p = p->next) {\n        seaf_dent = (SeafDirent *)p->data;\n        sub_path = g_strconcat (dir_path, \"/\", seaf_dent->name, NULL);\n\n        if (S_ISREG(seaf_dent->mode)) {\n            if (!callback (mgr, sub_path, seaf_dent, user_data, &stop)) {\n                g_free (sub_path);\n                ret = -1;\n                break;\n            }\n        } else if (S_ISDIR(seaf_dent->mode)) {\n            if (traverse_dir_path (mgr, repo_id, version, sub_path, seaf_dent,\n                                   callback, user_data) < 0) {\n                g_free (sub_path);\n                ret = -1;\n                break;\n            }\n        }\n        g_free (sub_path);\n    }\n\n    seaf_dir_free (dir);\n    return ret;\n}\n\nint\nseaf_fs_manager_traverse_path (SeafFSManager *mgr,\n                               const char *repo_id,\n                               int version,\n                               const char *root_id,\n                               const char *dir_path,\n                               TraverseFSPathCallback callback,\n                               void *user_data)\n{\n    SeafDirent *dent;\n    int ret = 0;\n\n    dent = seaf_fs_manager_get_dirent_by_path (mgr, repo_id, version,\n                                               root_id, dir_path, NULL);\n    if (!dent) {\n        seaf_warning (\"Failed to get dirent for %.8s:%s.\\n\", repo_id, dir_path);\n        return -1;\n    }\n\n    ret = traverse_dir_path (mgr, repo_id, version, dir_path, dent,\n                             callback, user_data);\n\n    seaf_dirent_free (dent);\n    return ret;\n}\n\nstatic gboolean\nfill_blocklist (SeafFSManager *mgr,\n                const char *repo_id, int version,\n                const char *obj_id, int type,\n                void *user_data, gboolean *stop)\n{\n    BlockList *bl = user_data;\n    Seafile *seafile;\n    int i;\n\n    if (type == SEAF_METADATA_TYPE_FILE) {\n        seafile = seaf_fs_manager_get_seafile (mgr, repo_id, version, obj_id);\n        if (!seafile) {\n            seaf_warning (\"[fs mgr] Failed to find file %s.\\n\", obj_id);\n            return FALSE;\n        }\n\n        for (i = 0; i < seafile->n_blocks; ++i)\n            block_list_insert (bl, seafile->blk_sha1s[i]);\n\n        seafile_unref (seafile);\n    }\n\n    return TRUE;\n}\n\nint\nseaf_fs_manager_populate_blocklist (SeafFSManager *mgr,\n                                    const char *repo_id,\n                                    int version,\n                                    const char *root_id,\n                                    BlockList *bl)\n{\n    return seaf_fs_manager_traverse_tree (mgr, repo_id, version, root_id,\n                                          fill_blocklist,\n                                          bl, FALSE);\n}\n\ngboolean\nseaf_fs_manager_object_exists (SeafFSManager *mgr,\n                               const char *repo_id,\n                               int version,\n                               const char *id)\n{\n    /* Empty file and dir always exists. */\n    if (memcmp (id, EMPTY_SHA1, 40) == 0)\n        return TRUE;\n\n    return seaf_obj_store_obj_exists (mgr->obj_store, repo_id, version, id);\n}\n\nvoid\nseaf_fs_manager_delete_object (SeafFSManager *mgr,\n                               const char *repo_id,\n                               int version,\n                               const char *id)\n{\n    seaf_obj_store_delete_obj (mgr->obj_store, repo_id, version, id);\n}\n\ngint64\nseaf_fs_manager_get_file_size (SeafFSManager *mgr,\n                               const char *repo_id,\n                               int version,\n                               const char *file_id)\n{\n    Seafile *file;\n    gint64 file_size;\n\n    file = seaf_fs_manager_get_seafile (seaf->fs_mgr, repo_id, version, file_id);\n    if (!file) {\n        seaf_warning (\"Couldn't get file %s:%s\\n\", repo_id, file_id);\n        return -1;\n    }\n\n    file_size = file->file_size;\n\n    seafile_unref (file);\n    return file_size;\n}\n\nstatic gint64\nget_dir_size (SeafFSManager *mgr, const char *repo_id, int version, const char *id)\n{\n    SeafDir *dir;\n    SeafDirent *seaf_dent;\n    guint64 size = 0;\n    gint64 result;\n    GList *p;\n\n    dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, id);\n    if (!dir)\n        return -1;\n\n    for (p = dir->entries; p; p = p->next) {\n        seaf_dent = (SeafDirent *)p->data;\n\n        if (S_ISREG(seaf_dent->mode)) {\n            if (dir->version > 0)\n                result = seaf_dent->size;\n            else {\n                result = seaf_fs_manager_get_file_size (mgr,\n                                                        repo_id,\n                                                        version,\n                                                        seaf_dent->id);\n                if (result < 0) {\n                    seaf_dir_free (dir);\n                    return result;\n                }\n            }\n            size += result;\n        } else if (S_ISDIR(seaf_dent->mode)) {\n            result = get_dir_size (mgr, repo_id, version, seaf_dent->id);\n            if (result < 0) {\n                seaf_dir_free (dir);\n                return result;\n            }\n            size += result;\n        }\n    }\n\n    seaf_dir_free (dir);\n    return size;\n}\n\ngint64\nseaf_fs_manager_get_fs_size (SeafFSManager *mgr,\n                             const char *repo_id,\n                             int version,\n                             const char *root_id)\n{\n     if (strcmp (root_id, EMPTY_SHA1) == 0)\n        return 0;\n     return get_dir_size (mgr, repo_id, version, root_id);\n}\n\nstatic int\ncount_dir_files (SeafFSManager *mgr, const char *repo_id, int version, const char *id)\n{\n    SeafDir *dir;\n    SeafDirent *seaf_dent;\n    int count = 0;\n    int result;\n    GList *p;\n\n    dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, id);\n    if (!dir)\n        return -1;\n\n    for (p = dir->entries; p; p = p->next) {\n        seaf_dent = (SeafDirent *)p->data;\n\n        if (S_ISREG(seaf_dent->mode)) {\n            count ++;\n        } else if (S_ISDIR(seaf_dent->mode)) {\n            result = count_dir_files (mgr, repo_id, version, seaf_dent->id);\n            if (result < 0) {\n                seaf_dir_free (dir);\n                return result;\n            }\n            count += result;\n        }\n    }\n\n    seaf_dir_free (dir);\n    return count;\n}\n\nint\nseaf_fs_manager_count_fs_files (SeafFSManager *mgr,\n                                const char *repo_id,\n                                int version,\n                                const char *root_id)\n{\n     if (strcmp (root_id, EMPTY_SHA1) == 0)\n        return 0;\n     return count_dir_files (mgr, repo_id, version, root_id);\n}\n\nSeafDir *\nseaf_fs_manager_get_seafdir_by_path (SeafFSManager *mgr,\n                                     const char *repo_id,\n                                     int version,\n                                     const char *root_id,\n                                     const char *path,\n                                     GError **error)\n{\n    SeafDir *dir;\n    SeafDirent *dent;\n    const char *dir_id = root_id;\n    char *name, *saveptr;\n    char *tmp_path = g_strdup(path);\n\n    dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, dir_id);\n    if (!dir) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_DIR_MISSING, \"directory is missing\");\n        g_free (tmp_path);\n        return NULL;\n    }\n\n    name = strtok_r (tmp_path, \"/\", &saveptr);\n    while (name != NULL) {\n        GList *l;\n        for (l = dir->entries; l != NULL; l = l->next) {\n            dent = l->data;\n\n            if (strcmp(dent->name, name) == 0 && S_ISDIR(dent->mode)) {\n                dir_id = dent->id;\n                break;\n            }\n        }\n\n        if (!l) {\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_PATH_NO_EXIST,\n                         \"Path does not exists %s\", path);\n            seaf_dir_free (dir);\n            dir = NULL;\n            break;\n        }\n\n        SeafDir *prev = dir;\n        dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, dir_id);\n        seaf_dir_free (prev);\n\n        if (!dir) {\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_DIR_MISSING,\n                         \"directory is missing\");\n            break;\n        }\n\n        name = strtok_r (NULL, \"/\", &saveptr);\n    }\n\n    g_free (tmp_path);\n    return dir;\n}\n\nchar *\nseaf_fs_manager_path_to_obj_id (SeafFSManager *mgr,\n                                const char *repo_id,\n                                int version,\n                                const char *root_id,\n                                const char *path,\n                                guint32 *mode,\n                                GError **error)\n{\n    char *copy = g_strdup (path);\n    int off = strlen(copy) - 1;\n    char *slash, *name;\n    SeafDir *base_dir = NULL;\n    SeafDirent *dent;\n    GList *p;\n    char *obj_id = NULL;\n\n    while (off >= 0 && copy[off] == '/')\n        copy[off--] = 0;\n\n    if (strlen(copy) == 0) {\n        /* the path is root \"/\" */\n        if (mode) {\n            *mode = S_IFDIR;\n        }\n        obj_id = g_strdup(root_id);\n        goto out;\n    }\n\n    slash = strrchr (copy, '/');\n    if (!slash) {\n        base_dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, root_id);\n        if (!base_dir) {\n            seaf_warning (\"Failed to find root dir %s.\\n\", root_id);\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, \" \");\n            goto out;\n        }\n        name = copy;\n    } else {\n        *slash = 0;\n        name = slash + 1;\n        GError *tmp_error = NULL;\n        base_dir = seaf_fs_manager_get_seafdir_by_path (mgr,\n                                                        repo_id,\n                                                        version,\n                                                        root_id,\n                                                        copy,\n                                                        &tmp_error);\n        if (tmp_error &&\n            !g_error_matches(tmp_error,\n                             SEAFILE_DOMAIN,\n                             SEAF_ERR_PATH_NO_EXIST)) {\n            seaf_warning (\"Failed to get dir for %s.\\n\", copy);\n            g_propagate_error (error, tmp_error);\n            goto out;\n        }\n\n        /* The path doesn't exist in this commit. */\n        if (!base_dir) {\n            g_propagate_error (error, tmp_error);\n            goto out;\n        }\n    }\n\n    for (p = base_dir->entries; p != NULL; p = p->next) {\n        dent = p->data;\n\n        if (!is_object_id_valid (dent->id))\n            continue;\n\n        if (strcmp (dent->name, name) == 0) {\n            obj_id = g_strdup (dent->id);\n            if (mode) {\n                *mode = dent->mode;\n            }\n            break;\n        }\n    }\n\nout:\n    if (base_dir)\n        seaf_dir_free (base_dir);\n    g_free (copy);\n    return obj_id;\n}\n\nchar *\nseaf_fs_manager_get_seafile_id_by_path (SeafFSManager *mgr,\n                                        const char *repo_id,\n                                        int version,\n                                        const char *root_id,\n                                        const char *path,\n                                        GError **error)\n{\n    guint32 mode;\n    char *file_id;\n\n    file_id = seaf_fs_manager_path_to_obj_id (mgr, repo_id, version,\n                                              root_id, path, &mode, error);\n\n    if (!file_id)\n        return NULL;\n\n    if (file_id && S_ISDIR(mode)) {\n        g_free (file_id);\n        return NULL;\n    }\n\n    return file_id;\n}\n\nchar *\nseaf_fs_manager_get_seafdir_id_by_path (SeafFSManager *mgr,\n                                        const char *repo_id,\n                                        int version,\n                                        const char *root_id,\n                                        const char *path,\n                                        GError **error)\n{\n    guint32 mode = 0;\n    char *dir_id;\n\n    dir_id = seaf_fs_manager_path_to_obj_id (mgr, repo_id, version,\n                                             root_id, path, &mode, error);\n\n    if (!dir_id)\n        return NULL;\n\n    if (dir_id && !S_ISDIR(mode)) {\n        g_free (dir_id);\n        return NULL;\n    }\n\n    return dir_id;\n}\n\nSeafDirent *\nseaf_fs_manager_get_dirent_by_path (SeafFSManager *mgr,\n                                    const char *repo_id,\n                                    int version,\n                                    const char *root_id,\n                                    const char *path,\n                                    GError **error)\n{\n    SeafDirent *dent = NULL;\n    SeafDir *dir = NULL;\n    char *parent_dir = NULL;\n    char *file_name = NULL;\n\n    parent_dir  = g_path_get_dirname(path);\n    file_name = g_path_get_basename(path);\n\n    if (strcmp (parent_dir, \".\") == 0) {\n        dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, root_id);\n        if (!dir) {\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_DIR_MISSING, \"directory is missing\");\n        }\n    } else\n        dir = seaf_fs_manager_get_seafdir_by_path (mgr, repo_id, version,\n                                                   root_id, parent_dir, error);\n\n    if (!dir) {\n        seaf_warning (\"dir %s doesn't exist in repo %.8s.\\n\", parent_dir, repo_id);\n        goto out;\n    }\n\n    GList *p;\n    for (p = dir->entries; p; p = p->next) {\n        SeafDirent *d = p->data;\n        if (strcmp (d->name, file_name) == 0) {\n            dent = seaf_dirent_dup(d);\n            break;\n        }\n    }\n\nout:\n    if (dir)\n        seaf_dir_free (dir);\n    g_free (parent_dir);\n    g_free (file_name);\n\n    return dent;\n}\n\nstatic gboolean\nverify_seafdir_v0 (const char *dir_id, const uint8_t *data, int len,\n                   gboolean verify_id)\n{\n    guint32 meta_type;\n    guint32 mode;\n    char id[41];\n    guint32 name_len;\n    char name[SEAF_DIR_NAME_LEN];\n    const uint8_t *ptr;\n    int remain;\n    int dirent_base_size;\n    GChecksum *ctx;\n    uint8_t sha1[20];\n    gsize cs_len = 20;\n    char check_id[41];\n\n    if (len < sizeof(SeafdirOndisk)) {\n        seaf_warning (\"[fs mgr] Corrupt seafdir object %s.\\n\", dir_id);\n        return FALSE;\n    }\n\n    ptr = data;\n    remain = len;\n\n    meta_type = get32bit (&ptr);\n    remain -= 4;\n    if (meta_type != SEAF_METADATA_TYPE_DIR) {\n        seaf_warning (\"Data does not contain a directory.\\n\");\n        return FALSE;\n    }\n\n    if (verify_id)\n        ctx = g_checksum_new (G_CHECKSUM_SHA1);\n\n    dirent_base_size = 2 * sizeof(guint32) + 40;\n    while (remain > dirent_base_size) {\n        mode = get32bit (&ptr);\n        memcpy (id, ptr, 40);\n        id[40] = '\\0';\n        ptr += 40;\n        name_len = get32bit (&ptr);\n        remain -= dirent_base_size;\n        if (remain >= name_len) {\n            name_len = MIN (name_len, SEAF_DIR_NAME_LEN - 1);\n            memcpy (name, ptr, name_len);\n            ptr += name_len;\n            remain -= name_len;\n        } else {\n            seaf_warning (\"Bad data format for dir objcet %s.\\n\", dir_id);\n            return FALSE;\n        }\n\n        if (verify_id) {\n            /* Convert mode to little endian before compute. */\n            if (G_BYTE_ORDER == G_BIG_ENDIAN)\n                mode = GUINT32_SWAP_LE_BE (mode);\n\n            g_checksum_update (ctx, (unsigned char *)id, 40);\n            g_checksum_update (ctx, (unsigned char *)name, name_len);\n            g_checksum_update (ctx, (unsigned char *)&mode, sizeof(mode));\n        }\n    }\n\n    if (!verify_id)\n        return TRUE;\n\n    g_checksum_get_digest (ctx, sha1, &cs_len);\n    rawdata_to_hex (sha1, check_id, 20);\n    g_checksum_free (ctx);\n\n    if (strcmp (check_id, dir_id) == 0)\n        return TRUE;\n    else\n        return FALSE;\n}\n\nstatic gboolean\nverify_fs_object_json (const char *obj_id, uint8_t *data, int len)\n{\n    guint8 *decompressed;\n    int outlen;\n    unsigned char sha1[20];\n    char hex[41];\n\n    if (seaf_decompress (data, len, &decompressed, &outlen) < 0) {\n        seaf_warning (\"Failed to decompress fs object %s.\\n\", obj_id);\n        return FALSE;\n    }\n\n    calculate_sha1 (sha1, (const char *)decompressed, outlen);\n    rawdata_to_hex (sha1, hex, 20);\n\n    g_free (decompressed);\n    return (strcmp(hex, obj_id) == 0);\n}\n\nstatic gboolean\nverify_seafdir (const char *dir_id, uint8_t *data, int len,\n                gboolean verify_id, gboolean is_json)\n{\n    if (is_json)\n        return verify_fs_object_json (dir_id, data, len);\n    else\n        return verify_seafdir_v0 (dir_id, data, len, verify_id);\n}\n                                        \ngboolean\nseaf_fs_manager_verify_seafdir (SeafFSManager *mgr,\n                                const char *repo_id,\n                                int version,\n                                const char *dir_id,\n                                gboolean verify_id,\n                                gboolean *io_error)\n{\n    void *data;\n    int len;\n\n    if (memcmp (dir_id, EMPTY_SHA1, 40) == 0) {\n        return TRUE;\n    }\n\n    if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version,\n                                 dir_id, &data, &len) < 0) {\n        seaf_warning (\"[fs mgr] Failed to read dir %s:%s.\\n\", repo_id, dir_id);\n        *io_error = TRUE;\n        return FALSE;\n    }\n\n    gboolean ret = verify_seafdir (dir_id, data, len, verify_id, (version > 0));\n    g_free (data);\n\n    return ret;\n}\n\nstatic gboolean\nverify_seafile_v0 (const char *id, const void *data, int len, gboolean verify_id)\n{\n    const SeafileOndisk *ondisk = data;\n    GChecksum *ctx;\n    uint8_t sha1[20];\n    gsize cs_len = 20;\n    char check_id[41];\n\n    if (len < sizeof(SeafileOndisk)) {\n        seaf_warning (\"[fs mgr] Corrupt seafile object %s.\\n\", id);\n        return FALSE;\n    }\n\n    if (ntohl(ondisk->type) != SEAF_METADATA_TYPE_FILE) {\n        seaf_warning (\"[fd mgr] %s is not a file.\\n\", id);\n        return FALSE;\n    }\n\n    int id_list_length = len - sizeof(SeafileOndisk);\n    if (id_list_length % 20 != 0) {\n        seaf_warning (\"[fs mgr] Bad seafile id list length %d.\\n\", id_list_length);\n        return FALSE;\n    }\n\n    if (!verify_id)\n        return TRUE;\n\n    ctx = g_checksum_new (G_CHECKSUM_SHA1);\n    g_checksum_update (ctx, ondisk->block_ids, len - sizeof(SeafileOndisk));\n    g_checksum_get_digest (ctx, sha1, &cs_len);\n    g_checksum_free (ctx);\n\n    rawdata_to_hex (sha1, check_id, 20);\n\n    if (strcmp (check_id, id) == 0)\n        return TRUE;\n    else\n        return FALSE;\n}\n\nstatic gboolean\nverify_seafile (const char *id, void *data, int len,\n                gboolean verify_id, gboolean is_json)\n{\n    if (is_json)\n        return verify_fs_object_json (id, data, len);\n    else\n        return verify_seafile_v0 (id, data, len, verify_id);\n}\n\ngboolean\nseaf_fs_manager_verify_seafile (SeafFSManager *mgr,\n                                const char *repo_id,\n                                int version,\n                                const char *file_id,\n                                gboolean verify_id,\n                                gboolean *io_error)\n{\n    void *data;\n    int len;\n\n    if (memcmp (file_id, EMPTY_SHA1, 40) == 0) {\n        return TRUE;\n    }\n\n    if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version,\n                                 file_id, &data, &len) < 0) {\n        seaf_warning (\"[fs mgr] Failed to read file %s:%s.\\n\", repo_id, file_id);\n        *io_error = TRUE;\n        return FALSE;\n    }\n\n    gboolean ret = verify_seafile (file_id, data, len, verify_id, (version > 0));\n    g_free (data);\n\n    return ret;\n}\n\nstatic gboolean\nverify_fs_object_v0 (const char *obj_id,\n                     uint8_t *data,\n                     int len,\n                     gboolean verify_id)\n{\n    gboolean ret = TRUE;\n\n    int type = seaf_metadata_type_from_data (obj_id, data, len, FALSE);\n    switch (type) {\n    case SEAF_METADATA_TYPE_FILE:\n        ret = verify_seafile_v0 (obj_id, data, len, verify_id);\n        break;\n    case SEAF_METADATA_TYPE_DIR:\n        ret = verify_seafdir_v0 (obj_id, data, len, verify_id);\n        break;\n    default:\n        seaf_warning (\"Invalid meta data type: %d.\\n\", type);\n        return FALSE;\n    }\n\n    return ret;\n}\n\ngboolean\nseaf_fs_manager_verify_object (SeafFSManager *mgr,\n                               const char *repo_id,\n                               int version,\n                               const char *obj_id,\n                               gboolean verify_id,\n                               gboolean *io_error)\n{\n    void *data;\n    int len;\n    gboolean ret = TRUE;\n\n    if (memcmp (obj_id, EMPTY_SHA1, 40) == 0) {\n        return TRUE;\n    }\n\n    if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version,\n                                 obj_id, &data, &len) < 0) {\n        seaf_warning (\"[fs mgr] Failed to read object %s:%s.\\n\", repo_id, obj_id);\n        *io_error = TRUE;\n        return FALSE;\n    }\n\n    if (version == 0)\n        ret = verify_fs_object_v0 (obj_id, data, len, verify_id);\n    else\n        ret = verify_fs_object_json (obj_id, data, len);\n\n    g_free (data);\n    return ret;\n}\n\nint\ndir_version_from_repo_version (int repo_version)\n{\n    if (repo_version == 0)\n        return 0;\n    else\n        return CURRENT_DIR_OBJ_VERSION;\n}\n\nint\nseafile_version_from_repo_version (int repo_version)\n{\n    if (repo_version == 0)\n        return 0;\n    else\n        return CURRENT_SEAFILE_OBJ_VERSION;\n}\n\nint\nseaf_fs_manager_remove_store (SeafFSManager *mgr,\n                              const char *store_id)\n{\n    return seaf_obj_store_remove_store (mgr->obj_store, store_id);\n}\n"
  },
  {
    "path": "common/fs-mgr.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef SEAF_FILE_MGR_H\n#define SEAF_FILE_MGR_H\n\n#include <glib.h>\n\n#include \"seafile-object.h\"\n\n#include \"obj-store.h\"\n\n#include \"cdc/cdc.h\"\n#include \"../common/seafile-crypt.h\"\n\n#define CURRENT_DIR_OBJ_VERSION 1\n#define CURRENT_SEAFILE_OBJ_VERSION 1\n\n#define CDC_AVERAGE_BLOCK_SIZE (1 << 23) /* 8MB */\n#define CDC_MIN_BLOCK_SIZE (6 * (1 << 20)) /* 6MB */\n#define CDC_MAX_BLOCK_SIZE (10 * (1 << 20)) /* 10MB */\n\n#define SEAF_TMP_EXT \"~\"\n#define SEAFILE_FILE_ID_ATTR \"user.file-id\"\n\ntypedef struct _SeafFSManager SeafFSManager;\ntypedef struct _SeafFSObject SeafFSObject;\ntypedef struct _Seafile Seafile;\ntypedef struct _SeafDir SeafDir;\ntypedef struct _SeafDirent SeafDirent;\n\ntypedef enum {\n    SEAF_METADATA_TYPE_INVALID,\n    SEAF_METADATA_TYPE_FILE,\n    SEAF_METADATA_TYPE_LINK,\n    SEAF_METADATA_TYPE_DIR,\n} SeafMetadataType;\n\n/* Common to seafile and seafdir objects. */\nstruct _SeafFSObject {\n    int type;\n};\n\nstruct _Seafile {\n    SeafFSObject object;\n    int         version;\n    char        file_id[41];\n    guint64     file_size;\n    guint32     n_blocks;\n    char        **blk_sha1s;\n    int         ref_count;\n};\n\nvoid\nseafile_ref (Seafile *seafile);\n\nvoid\nseafile_unref (Seafile *seafile);\n\nint\nseafile_save (SeafFSManager *fs_mgr,\n              const char *repo_id,\n              int version,\n              Seafile *file);\n\n#define SEAF_DIR_NAME_LEN 256\n\nstruct _SeafDirent {\n    int        version;\n    guint32    mode;\n    char       id[41];\n    guint32    name_len;\n    char       *name;\n\n    /* attributes for version > 0 */\n    gint64     mtime;\n    char       *modifier;       /* for files only */\n    gint64     size;            /* for files only */\n};\n\nstruct _SeafDir {\n    SeafFSObject object;\n    int    version;\n    char   dir_id[41];\n    GList *entries;\n\n    /* data in on-disk format. */\n    void  *ondisk;\n    int    ondisk_size;\n};\n\nSeafDir *\nseaf_dir_new (const char *id, GList *entries, int version);\n\nvoid \nseaf_dir_free (SeafDir *dir);\n\nSeafDir *\nseaf_dir_from_data (const char *dir_id, uint8_t *data, int len,\n                    gboolean is_json);\n\nvoid *\nseaf_dir_to_data (SeafDir *dir, int *len);\n\nint \nseaf_dir_save (SeafFSManager *fs_mgr,\n               const char *repo_id,\n               int version,\n               SeafDir *dir);\n\nSeafDirent *\nseaf_dirent_new (int version, const char *sha1, int mode, const char *name,\n                 gint64 mtime, const char *modifier, gint64 size);\n\nvoid\nseaf_dirent_free (SeafDirent *dent);\n\nSeafDirent *\nseaf_dirent_dup (SeafDirent *dent);\n\nint\nseaf_metadata_type_from_data (const char *obj_id,\n                              uint8_t *data, int len, gboolean is_json);\n\n/* Parse an fs object without knowing its type. */\nSeafFSObject *\nseaf_fs_object_from_data (const char *obj_id,\n                          uint8_t *data, int len,\n                          gboolean is_json);\n\nvoid\nseaf_fs_object_free (SeafFSObject *obj);\n\ntypedef struct {\n    /* TODO: GHashTable may be inefficient when we have large number of IDs. */\n    GHashTable  *block_hash;\n    GPtrArray   *block_ids;\n    uint32_t     n_blocks;\n    uint32_t     n_valid_blocks;\n} BlockList;\n\nBlockList *\nblock_list_new ();\n\nvoid\nblock_list_free (BlockList *bl);\n\nvoid\nblock_list_insert (BlockList *bl, const char *block_id);\n\n/* Return a blocklist containing block ids which are in @bl1 but\n * not in @bl2.\n */\nBlockList *\nblock_list_difference (BlockList *bl1, BlockList *bl2);\n\nstruct _SeafileSession;\n\ntypedef struct _SeafFSManagerPriv SeafFSManagerPriv;\n\nstruct _SeafFSManager {\n    struct _SeafileSession *seaf;\n\n    struct SeafObjStore *obj_store;\n\n    SeafFSManagerPriv *priv;\n};\n\nSeafFSManager *\nseaf_fs_manager_new (struct _SeafileSession *seaf,\n                     const char *seaf_dir);\n\nint\nseaf_fs_manager_init (SeafFSManager *mgr);\n\n#ifndef SEAFILE_SERVER\n\nstruct _CheckoutBlockAux {\n    char *repo_id;\n    char *host;\n    char *token;\n    gboolean use_fileserver_port;\n    void *task;\n};\ntypedef struct _CheckoutBlockAux CheckoutBlockAux; \n\nvoid\nfree_checkout_block_aux (CheckoutBlockAux *aux);\n\ntypedef int (*CheckoutBlockCallback) (const char *, const char *, int, SeafileCrypt *, CheckoutBlockAux *);\n\nstruct _FileCheckoutData {\n    const char *repo_id;\n    int version;\n    const char *file_id; \n    const char *file_path;\n    guint32 mode;\n    guint64 mtime;\n    struct SeafileCrypt *crypt;\n    const char *in_repo_path;\n    const char *conflict_head_id;\n    gboolean force_conflict;\n    gboolean *conflicted;\n    const char *email;\n    gint64 skip_buffer_size;\n    int block_offset;\n};\ntypedef struct _FileCheckoutData FileCheckoutData;\n\nint \nseaf_fs_manager_checkout_file (SeafFSManager *mgr, \n                               FileCheckoutData *data,\n                               int *error_id,\n                               CheckoutBlockCallback callback,\n                               CheckoutBlockAux *user_data);\n\n#endif  /* not SEAFILE_SERVER */\n\n/**\n * Check in blocks and create seafile/symlink object.\n * Returns sha1 id for the seafile/symlink object in @sha1 parameter.\n */\nint\nseaf_fs_manager_index_file_blocks (SeafFSManager *mgr,\n                                   const char *repo_id,\n                                   int version,\n                                   GList *paths,\n                                   GList *blockids,\n                                   unsigned char sha1[],\n                                   gint64 file_size);\n\nint\nseaf_fs_manager_index_raw_blocks (SeafFSManager *mgr,\n                                  const char *repo_id,\n                                  int version,\n                                  GList *paths,\n                                  GList *blockids);\n\nint\nseaf_fs_manager_index_existed_file_blocks (SeafFSManager *mgr,\n                                           const char *repo_id,\n                                           int version,\n                                           GList *blockids,\n                                           unsigned char sha1[],\n                                           gint64 file_size);\nint\nseaf_fs_manager_index_blocks (SeafFSManager *mgr,\n                              const char *repo_id,\n                              int version,\n                              const char *file_path,\n                              unsigned char sha1[],\n                              gint64 *size,\n                              SeafileCrypt *crypt,\n                              gboolean write_data,\n                              gboolean use_cdc);\n\nSeafile *\nseaf_fs_manager_get_seafile (SeafFSManager *mgr,\n                             const char *repo_id,\n                             int version,\n                             const char *file_id);\n\nSeafDir *\nseaf_fs_manager_get_seafdir (SeafFSManager *mgr,\n                             const char *repo_id,\n                             int version,\n                             const char *dir_id);\n\n/* Make sure entries in the returned dir is sorted in descending order.\n */\nSeafDir *\nseaf_fs_manager_get_seafdir_sorted (SeafFSManager *mgr,\n                                    const char *repo_id,\n                                    int version,\n                                    const char *dir_id);\n\nSeafDir *\nseaf_fs_manager_get_seafdir_sorted_by_path (SeafFSManager *mgr,\n                                            const char *repo_id,\n                                            int version,\n                                            const char *root_id,\n                                            const char *path);\n\nint\nseaf_fs_manager_populate_blocklist (SeafFSManager *mgr,\n                                    const char *repo_id,\n                                    int version,\n                                    const char *root_id,\n                                    BlockList *bl);\n\n/*\n * For dir object, set *stop to TRUE to stop traversing the subtree.\n */\ntypedef gboolean (*TraverseFSTreeCallback) (SeafFSManager *mgr,\n                                            const char *repo_id,\n                                            int version,\n                                            const char *obj_id,\n                                            int type,\n                                            void *user_data,\n                                            gboolean *stop);\n\nint\nseaf_fs_manager_traverse_tree (SeafFSManager *mgr,\n                               const char *repo_id,\n                               int version,\n                               const char *root_id,\n                               TraverseFSTreeCallback callback,\n                               void *user_data,\n                               gboolean skip_errors);\n\ntypedef gboolean (*TraverseFSPathCallback) (SeafFSManager *mgr,\n                                            const char *path,\n                                            SeafDirent *dent,\n                                            void *user_data,\n                                            gboolean *stop);\n\nint\nseaf_fs_manager_traverse_path (SeafFSManager *mgr,\n                               const char *repo_id,\n                               int version,\n                               const char *root_id,\n                               const char *dir_path,\n                               TraverseFSPathCallback callback,\n                               void *user_data);\n\ngboolean\nseaf_fs_manager_object_exists (SeafFSManager *mgr,\n                               const char *repo_id,\n                               int version,\n                               const char *id);\n\nvoid\nseaf_fs_manager_delete_object (SeafFSManager *mgr,\n                               const char *repo_id,\n                               int version,\n                               const char *id);\n\ngint64\nseaf_fs_manager_get_file_size (SeafFSManager *mgr,\n                               const char *repo_id,\n                               int version,\n                               const char *file_id);\n\ngint64\nseaf_fs_manager_get_fs_size (SeafFSManager *mgr,\n                             const char *repo_id,\n                             int version,\n                             const char *root_id);\n\n#ifndef SEAFILE_SERVER\nint\nseafile_write_chunk (const char *repo_id,\n                     int version,\n                     CDCDescriptor *chunk,\n                     SeafileCrypt *crypt,\n                     uint8_t *checksum,\n                     gboolean write_data);\nint\nseafile_check_write_chunk (CDCDescriptor *chunk,\n                           uint8_t *sha1,\n                           gboolean write_data);\n#endif /* SEAFILE_SERVER */\n\nuint32_t\ncalculate_chunk_size (uint64_t total_size);\n\nint\nseaf_fs_manager_count_fs_files (SeafFSManager *mgr,\n                                const char *repo_id,\n                                int version,\n                                const char *root_id);\n\nSeafDir *\nseaf_fs_manager_get_seafdir_by_path(SeafFSManager *mgr,\n                                    const char *repo_id,\n                                    int version,\n                                    const char *root_id,\n                                    const char *path,\n                                    GError **error);\nchar *\nseaf_fs_manager_get_seafile_id_by_path (SeafFSManager *mgr,\n                                        const char *repo_id,\n                                        int version,\n                                        const char *root_id,\n                                        const char *path,\n                                        GError **error);\n\nchar *\nseaf_fs_manager_path_to_obj_id (SeafFSManager *mgr,\n                                const char *repo_id,\n                                int version,\n                                const char *root_id,\n                                const char *path,\n                                guint32 *mode,\n                                GError **error);\n\nchar *\nseaf_fs_manager_get_seafdir_id_by_path (SeafFSManager *mgr,\n                                        const char *repo_id,\n                                        int version,\n                                        const char *root_id,\n                                        const char *path,\n                                        GError **error);\n\nSeafDirent *\nseaf_fs_manager_get_dirent_by_path (SeafFSManager *mgr,\n                                    const char *repo_id,\n                                    int version,\n                                    const char *root_id,\n                                    const char *path,\n                                    GError **error);\n\n/* Check object integrity. */\n\ngboolean\nseaf_fs_manager_verify_seafdir (SeafFSManager *mgr,\n                                const char *repo_id,\n                                int version,\n                                const char *dir_id,\n                                gboolean verify_id,\n                                gboolean *io_error);\n\ngboolean\nseaf_fs_manager_verify_seafile (SeafFSManager *mgr,\n                                const char *repo_id,\n                                int version,\n                                const char *file_id,\n                                gboolean verify_id,\n                                gboolean *io_error);\n\ngboolean\nseaf_fs_manager_verify_object (SeafFSManager *mgr,\n                               const char *repo_id,\n                               int version,\n                               const char *obj_id,\n                               gboolean verify_id,\n                               gboolean *io_error);\n\nint\ndir_version_from_repo_version (int repo_version);\n\nint\nseafile_version_from_repo_version (int repo_version);\n\nstruct _CDCFileDescriptor;\nvoid\nseaf_fs_manager_calculate_seafile_id_json (int repo_version,\n                                           struct _CDCFileDescriptor *cdc,\n                                           guint8 *file_id_sha1);\n\nint\nseaf_fs_manager_remove_store (SeafFSManager *mgr,\n                              const char *store_id);\n\n#endif\n"
  },
  {
    "path": "common/index/Makefile.am",
    "content": "AM_CPPFLAGS = -Wall -I${top_srcdir}/common -I${top_srcdir}/lib \\\n\t@MSVC_CFLAGS@\n\nif MACOS\nif COMPILE_UNIVERSAL\nAM_CPPFLAGS += -arch x86_64 -arch arm64\nendif\nendif\n\nnoinst_LTLIBRARIES = libindex.la\n\nnoinst_HEADERS = index.h cache-tree.h\n\nlibindex_la_SOURCES = index.c cache-tree.c\n\nlibindex_la_CFLAGS = @GLIB2_CFLAGS@\nlibindex_la_LDFLAGS = -Wl,-z -Wl,defs\nlibindex_la_LIBADD = @GLIB2_LIBS@ \\\n\t$(top_builddir)/lib/libseafile_common.la\n"
  },
  {
    "path": "common/index/cache-tree.c",
    "content": "\n#include \"common.h\"\n\n#include \"log.h\"\n\n#include \"index.h\"\n#if 0\n#include \"tree.h\"\n#include \"tree-walk.h\"\n#endif\n#include \"cache-tree.h\"\n\n#include <glib.h>\n\n#ifndef DEBUG\n#define DEBUG 0\n#endif\n\nstruct cache_tree *cache_tree(void)\n{\n    struct cache_tree *it = calloc(1, sizeof(struct cache_tree));\n    it->entry_count = -1;\n    return it;\n}\n\nvoid cache_tree_free(struct cache_tree **it_p)\n{\n    int i;\n    struct cache_tree *it = *it_p;\n\n    if (!it)\n        return;\n    for (i = 0; i < it->subtree_nr; i++)\n        if (it->down[i]) {\n            cache_tree_free(&it->down[i]->cache_tree);\n            free(it->down[i]);\n        }\n    free(it->down);\n    free(it);\n    *it_p = NULL;\n}\n\nstatic int subtree_name_cmp(const char *one, int onelen,\n                            const char *two, int twolen)\n{\n    if (onelen < twolen)\n        return -1;\n    if (twolen < onelen)\n        return 1;\n    return memcmp(one, two, onelen);\n}\n\nstatic int subtree_pos(struct cache_tree *it, const char *path, int pathlen)\n{\n    struct cache_tree_sub **down = it->down;\n    int lo, hi;\n    lo = 0;\n    hi = it->subtree_nr;\n    while (lo < hi) {\n        int mi = (lo + hi) / 2;\n        struct cache_tree_sub *mdl = down[mi];\n        int cmp = subtree_name_cmp(path, pathlen,\n                                   mdl->name, mdl->namelen);\n        if (!cmp)\n            return mi;\n        if (cmp < 0)\n            hi = mi;\n        else\n            lo = mi + 1;\n    }\n    return -lo-1;\n}\n\nstatic struct cache_tree_sub *find_subtree(struct cache_tree *it,\n                                           const char *path,\n                                           int pathlen,\n                                           int create)\n{\n    struct cache_tree_sub *down;\n    int pos = subtree_pos(it, path, pathlen);\n    if (0 <= pos)\n        return it->down[pos];\n    if (!create)\n        return NULL;\n\n    pos = -pos-1;\n    if (it->subtree_alloc <= it->subtree_nr) {\n        it->subtree_alloc = alloc_nr(it->subtree_alloc);\n        it->down = realloc(it->down, it->subtree_alloc *\n                           sizeof(*it->down));\n    }\n    it->subtree_nr++;\n\n    down = malloc(sizeof(*down) + pathlen + 1);\n    down->cache_tree = NULL;\n    down->namelen = pathlen;\n    memcpy(down->name, path, pathlen);\n    down->name[pathlen] = 0;\n\n    if (pos < it->subtree_nr)\n        memmove(it->down + pos + 1,\n                it->down + pos,\n                sizeof(down) * (it->subtree_nr - pos - 1));\n    it->down[pos] = down;\n    return down;\n}\n\nstruct cache_tree_sub *cache_tree_find_subtree(struct cache_tree *it,\n                                               const char *path, int pathlen, int create)\n{\n    return find_subtree(it, path, pathlen, create);\n}\n\n#if 0\nstruct cache_tree_sub *cache_tree_sub(struct cache_tree *it, const char *path)\n{\n    int pathlen = strlen(path);\n    return find_subtree(it, path, pathlen, 1);\n}\n\nvoid cache_tree_invalidate_path(struct cache_tree *it, const char *path)\n{\n    /* a/b/c\n     * ==> invalidate self\n     * ==> find \"a\", have it invalidate \"b/c\"\n     * a\n     * ==> invalidate self\n     * ==> if \"a\" exists as a subtree, remove it.\n     */\n    const char *slash;\n    int namelen;\n    struct cache_tree_sub *down;\n\n#if DEBUG\n    fprintf(stderr, \"cache-tree invalidate <%s>\\n\", path);\n#endif\n\n    if (!it)\n        return;\n    slash = strchr(path, '/');\n    it->entry_count = -1;\n    if (!slash) {\n        int pos;\n        namelen = strlen(path);\n        pos = subtree_pos(it, path, namelen);\n        if (0 <= pos) {\n            cache_tree_free(&it->down[pos]->cache_tree);\n            free(it->down[pos]);\n            /* 0 1 2 3 4 5\n             *       ^     ^subtree_nr = 6\n             *       pos\n             * move 4 and 5 up one place (2 entries)\n             * 2 = 6 - 3 - 1 = subtree_nr - pos - 1\n             */\n            memmove(it->down+pos, it->down+pos+1,\n                    sizeof(struct cache_tree_sub *) *\n                    (it->subtree_nr - pos - 1));\n            it->subtree_nr--;\n        }\n        return;\n    }\n    namelen = slash - path;\n    down = find_subtree(it, path, namelen, 0);\n    if (down)\n        cache_tree_invalidate_path(down->cache_tree, slash + 1);\n}\n#endif\n\nstatic int verify_cache(struct cache_entry **cache,\n                        int entries)\n{\n    int i, funny;\n\n    /* Verify that the tree is merged */\n    funny = 0;\n    for (i = 0; i < entries; i++) {\n        struct cache_entry *ce = cache[i];\n        if (ce_stage(ce) || (ce->ce_flags & CE_INTENT_TO_ADD)) {\n            if (10 < ++funny) {\n                /*fprintf(stderr, \"...\\n\");*/\n                break;\n            }\n#if 0   \n            if (ce_stage(ce))\n                fprintf(stderr, \"%s: unmerged (%s)\\n\",\n                        ce->name, sha1_to_hex(ce->sha1));\n            else\n                fprintf(stderr, \"%s: not added yet\\n\",\n                        ce->name);\n#endif\n        }\n    }\n    if (funny)\n        return -1;\n\n    /* Also verify that the cache does not have path and path/file\n     * at the same time.  At this point we know the cache has only\n     * stage 0 entries.\n     */\n    funny = 0;\n    for (i = 0; i < entries - 1; i++) {\n        /* path/file always comes after path because of the way\n         * the cache is sorted.  Also path can appear only once,\n         * which means conflicting one would immediately follow.\n         */\n        const char *this_name = cache[i]->name;\n        const char *next_name = cache[i+1]->name;\n        int this_len = strlen(this_name);\n        if (this_len < strlen(next_name) &&\n            strncmp(this_name, next_name, this_len) == 0 &&\n            next_name[this_len] == '/') {\n            if (10 < ++funny) {\n                fprintf(stderr, \"...\\n\");\n                break;\n            }\n            seaf_warning(\"You have both %s and %s\\n\",\n                      this_name, next_name);\n        }\n    }\n    if (funny)\n        return -1;\n    return 0;\n}\n\nstatic void discard_unused_subtrees(struct cache_tree *it)\n{\n    struct cache_tree_sub **down = it->down;\n    int nr = it->subtree_nr;\n    int dst, src;\n    for (dst = src = 0; src < nr; src++) {\n        struct cache_tree_sub *s = down[src];\n        if (s->used)\n            down[dst++] = s;\n        else {\n            cache_tree_free(&s->cache_tree);\n            free(s);\n            it->subtree_nr--;\n        }\n    }\n}\n\n#if 0\nint cache_tree_fully_valid(struct cache_tree *it)\n{\n    int i;\n    if (!it)\n        return 0;\n    if (it->entry_count < 0 || !has_sha1_file(it->sha1))\n        return 0;\n    for (i = 0; i < it->subtree_nr; i++) {\n        if (!cache_tree_fully_valid(it->down[i]->cache_tree))\n            return 0;\n    }\n    return 1;\n}\n#endif\n\nstatic int update_one(const char *repo_id,\n                      int version,\n                      const char *worktree,\n                      struct cache_tree *it,\n                      struct cache_entry **cache,\n                      int entries,\n                      const char *base,\n                      int baselen,\n                      int missing_ok,\n                      int dryrun,\n                      CommitCB commit_cb)\n{\n    int i;\n\n    if (0 <= it->entry_count)\n        return it->entry_count;\n\n    /*\n     * We first scan for subtrees and update them; we start by\n     * marking existing subtrees -- the ones that are unmarked\n     * should not be in the result.\n     */\n    for (i = 0; i < it->subtree_nr; i++)\n        it->down[i]->used = 0;\n\n    /*\n     * Find the subtrees and update them.\n     */\n    for (i = 0; i < entries; i++) {\n        struct cache_entry *ce = cache[i];\n        struct cache_tree_sub *sub;\n        const char *path, *slash;\n        int pathlen, sublen, subcnt;\n\n        path = ce->name;\n        pathlen = ce_namelen(ce);\n        if (pathlen <= baselen || memcmp(base, path, baselen))\n            break; /* at the end of this level */\n\n        slash = strchr(path + baselen, '/');\n        if (!slash)\n            continue;\n        /*\n         * a/bbb/c (base = a/, slash = /c)\n         * ==>\n         * path+baselen = bbb/c, sublen = 3\n         */\n        sublen = slash - (path + baselen);\n        sub = find_subtree(it, path + baselen, sublen, 1);\n        if (!sub->cache_tree)\n            sub->cache_tree = cache_tree();\n        subcnt = update_one(repo_id, version,\n                            worktree,\n                            sub->cache_tree,\n                            cache + i, entries - i,\n                            path,\n                            baselen + sublen + 1,\n                            missing_ok,\n                            dryrun,\n                            commit_cb);\n        if (subcnt < 0)\n            return subcnt;\n        i += subcnt - 1;\n        sub->used = 1;\n    }\n    it->entry_count = i;\n\n    discard_unused_subtrees(it);\n\n    if (commit_cb (repo_id, version, worktree,\n                   it, cache, entries, base, baselen) < 0) {\n        seaf_warning (\"save seafile dirent failed\");\n        return -1;\n    }\n\n    return i;\n}\n\nint cache_tree_update(const char *repo_id,\n                      int repo_version,\n                      const char *worktree,\n                      struct cache_tree *it,\n                      struct cache_entry **cache,\n                      int entries,\n                      int missing_ok,\n                      int dryrun,\n                      CommitCB commit_cb)\n{\n    int i;\n    i = verify_cache(cache, entries);\n    if (i)\n        return i;\n    i = update_one(repo_id, repo_version, worktree,\n                   it, cache, entries, \"\", 0, missing_ok, dryrun, commit_cb);\n    if (i < 0)\n        return i;\n    return 0;\n}\n\n#if 0\nstatic void write_one(struct strbuf *buffer, struct cache_tree *it,\n                      const char *path, int pathlen)\n{\n    int i;\n\n    /* One \"cache-tree\" entry consists of the following:\n     * path (NUL terminated)\n     * entry_count, subtree_nr (\"%d %d\\n\")\n     * tree-sha1 (missing if invalid)\n     * subtree_nr \"cache-tree\" entries for subtrees.\n     */\n    strbuf_grow(buffer, pathlen + 100);\n    strbuf_add(buffer, path, pathlen);\n    strbuf_addf(buffer, \"%c%d %d\\n\", 0, it->entry_count, it->subtree_nr);\n\n#if DEBUG\n    if (0 <= it->entry_count)\n        fprintf(stderr, \"cache-tree <%.*s> (%d ent, %d subtree) %s\\n\",\n                pathlen, path, it->entry_count, it->subtree_nr,\n                sha1_to_hex(it->sha1));\n    else\n        fprintf(stderr, \"cache-tree <%.*s> (%d subtree) invalid\\n\",\n                pathlen, path, it->subtree_nr);\n#endif\n\n    if (0 <= it->entry_count) {\n        strbuf_add(buffer, it->sha1, 20);\n    }\n    for (i = 0; i < it->subtree_nr; i++) {\n        struct cache_tree_sub *down = it->down[i];\n        if (i) {\n            struct cache_tree_sub *prev = it->down[i-1];\n            if (subtree_name_cmp(down->name, down->namelen,\n                                 prev->name, prev->namelen) <= 0)\n                die(\"fatal - unsorted cache subtree\");\n        }\n        write_one(buffer, down->cache_tree, down->name, down->namelen);\n    }\n}\n\nvoid cache_tree_write(struct strbuf *sb, struct cache_tree *root)\n{\n    write_one(sb, root, \"\", 0);\n}\n\nstatic struct cache_tree *read_one(const char **buffer, unsigned long *size_p)\n{\n    const char *buf = *buffer;\n    unsigned long size = *size_p;\n    const char *cp;\n    char *ep;\n    struct cache_tree *it;\n    int i, subtree_nr;\n\n    it = NULL;\n    /* skip name, but make sure name exists */\n    while (size && *buf) {\n        size--;\n        buf++;\n    }\n    if (!size)\n        goto free_return;\n    buf++; size--;\n    it = cache_tree();\n\n    cp = buf;\n    it->entry_count = strtol(cp, &ep, 10);\n    if (cp == ep)\n        goto free_return;\n    cp = ep;\n    subtree_nr = strtol(cp, &ep, 10);\n    if (cp == ep)\n        goto free_return;\n    while (size && *buf && *buf != '\\n') {\n        size--;\n        buf++;\n    }\n    if (!size)\n        goto free_return;\n    buf++; size--;\n    if (0 <= it->entry_count) {\n        if (size < 20)\n            goto free_return;\n        hashcpy(it->sha1, (const unsigned char*)buf);\n        buf += 20;\n        size -= 20;\n    }\n\n#if DEBUG\n    if (0 <= it->entry_count)\n        fprintf(stderr, \"cache-tree <%s> (%d ent, %d subtree) %s\\n\",\n                *buffer, it->entry_count, subtree_nr,\n                sha1_to_hex(it->sha1));\n    else\n        fprintf(stderr, \"cache-tree <%s> (%d subtrees) invalid\\n\",\n                *buffer, subtree_nr);\n#endif\n\n    /*\n     * Just a heuristic -- we do not add directories that often but\n     * we do not want to have to extend it immediately when we do,\n     * hence +2.\n     */\n    it->subtree_alloc = subtree_nr + 2;\n    it->down = xcalloc(it->subtree_alloc, sizeof(struct cache_tree_sub *));\n    for (i = 0; i < subtree_nr; i++) {\n        /* read each subtree */\n        struct cache_tree *sub;\n        struct cache_tree_sub *subtree;\n        const char *name = buf;\n\n        sub = read_one(&buf, &size);\n        if (!sub)\n            goto free_return;\n        subtree = cache_tree_sub(it, name);\n        subtree->cache_tree = sub;\n    }\n    if (subtree_nr != it->subtree_nr)\n        die(\"cache-tree: internal error\");\n    *buffer = buf;\n    *size_p = size;\n    return it;\n\nfree_return:\n    cache_tree_free(&it);\n    return NULL;\n}\n\nstruct cache_tree *cache_tree_read(const char *buffer, unsigned long size)\n{\n    if (buffer[0])\n        return NULL; /* not the whole tree */\n    return read_one(&buffer, &size);\n}\n\nstatic struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path)\n{\n    if (!it)\n        return NULL;\n    while (*path) {\n        const char *slash;\n        struct cache_tree_sub *sub;\n\n        slash = strchr(path, '/');\n        if (!slash)\n            slash = path + strlen(path);\n        /* between path and slash is the name of the\n         * subtree to look for.\n         */\n        sub = find_subtree(it, path, slash - path, 0);\n        if (!sub)\n            return NULL;\n        it = sub->cache_tree;\n        if (slash)\n            while (*slash && *slash == '/')\n                slash++;\n        if (!slash || !*slash)\n            return it; /* prefix ended with slashes */\n        path = slash;\n    }\n    return it;\n}\n\n#if 0\n\nint write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)\n{\n    int entries, was_valid, newfd;\n    struct lock_file *lock_file;\n\n    /*\n     * We can't free this memory, it becomes part of a linked list\n     * parsed atexit()\n     */\n    lock_file = xcalloc(1, sizeof(struct lock_file));\n\n    newfd = hold_locked_index(lock_file, 1);\n\n    entries = read_cache();\n    if (entries < 0)\n        return WRITE_TREE_UNREADABLE_INDEX;\n    if (flags & WRITE_TREE_IGNORE_CACHE_TREE)\n        cache_tree_free(&(active_cache_tree));\n\n    if (!active_cache_tree)\n        active_cache_tree = cache_tree();\n\n    was_valid = cache_tree_fully_valid(active_cache_tree);\n    if (!was_valid) {\n        int missing_ok = flags & WRITE_TREE_MISSING_OK;\n\n        if (cache_tree_update(active_cache_tree,\n                              active_cache, active_nr,\n                              missing_ok, 0) < 0)\n            return WRITE_TREE_UNMERGED_INDEX;\n        if (0 <= newfd) {\n            if (!write_cache(newfd, active_cache, active_nr) &&\n                !commit_lock_file(lock_file))\n                newfd = -1;\n        }\n        /* Not being able to write is fine -- we are only interested\n         * in updating the cache-tree part, and if the next caller\n         * ends up using the old index with unupdated cache-tree part\n         * it misses the work we did here, but that is just a\n         * performance penalty and not a big deal.\n         */\n    }\n\n    if (prefix) {\n        struct cache_tree *subtree =\n            cache_tree_find(active_cache_tree, prefix);\n        if (!subtree)\n            return WRITE_TREE_PREFIX_ERROR;\n        hashcpy(sha1, subtree->sha1);\n    }\n    else\n        hashcpy(sha1, active_cache_tree->sha1);\n\n    if (0 <= newfd)\n        rollback_lock_file(lock_file);\n\n    return 0;\n}\n\n#endif  /* 0 */\n\nstatic void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)\n{\n    struct tree_desc desc;\n    struct name_entry entry;\n    int cnt;\n\n    hashcpy(it->sha1, tree->object.sha1);\n    init_tree_desc(&desc, tree->buffer, tree->size);\n    cnt = 0;\n    while (tree_entry(&desc, &entry)) {\n        if (!S_ISDIR(entry.mode))\n            cnt++;\n        else {\n            struct cache_tree_sub *sub;\n            struct tree *subtree = lookup_tree(entry.sha1);\n            if (!subtree->object.parsed)\n                parse_tree(subtree);\n            sub = cache_tree_sub(it, entry.path);\n            sub->cache_tree = cache_tree();\n            prime_cache_tree_rec(sub->cache_tree, subtree);\n            cnt += sub->cache_tree->entry_count;\n        }\n    }\n    it->entry_count = cnt;\n}\n\nvoid prime_cache_tree(struct cache_tree **it, struct tree *tree)\n{\n    cache_tree_free(it);\n    *it = cache_tree();\n    prime_cache_tree_rec(*it, tree);\n}\n\n/*\n * find the cache_tree that corresponds to the current level without\n * exploding the full path into textual form.  The root of the\n * cache tree is given as \"root\", and our current level is \"info\".\n * (1) When at root level, info->prev is NULL, so it is \"root\" itself.\n * (2) Otherwise, find the cache_tree that corresponds to one level\n *     above us, and find ourselves in there.\n */\nstatic struct cache_tree *find_cache_tree_from_traversal(struct cache_tree *root,\n                                                         struct traverse_info *info)\n{\n    struct cache_tree *our_parent;\n\n    if (!info->prev)\n        return root;\n    our_parent = find_cache_tree_from_traversal(root, info->prev);\n    return cache_tree_find(our_parent, info->name.path);\n}\n\nint cache_tree_matches_traversal(struct cache_tree *root,\n                                 struct name_entry *ent,\n                                 struct traverse_info *info)\n{\n    struct cache_tree *it;\n\n    it = find_cache_tree_from_traversal(root, info);\n    it = cache_tree_find(it, ent->path);\n    if (it && it->entry_count > 0 && !hashcmp(ent->sha1, it->sha1))\n        return it->entry_count;\n    return 0;\n}\n#endif\n"
  },
  {
    "path": "common/index/cache-tree.h",
    "content": "#ifndef CACHE_TREE_H\n#define CACHE_TREE_H\n\n#include <glib.h>\n\nstruct cache_tree;\nstruct cache_tree_sub {\n    struct cache_tree *cache_tree;\n    int namelen;\n    int used;\n    char name[0];\n};\n\nstruct cache_tree {\n    int entry_count; /* negative means \"invalid\" */\n    unsigned char sha1[20];\n    int subtree_nr;\n    int subtree_alloc;\n    guint64 mtime;\n    struct cache_tree_sub **down;\n};\n\ntypedef int (*CommitCB) (const char *, int,\n                         const char *,\n                         struct cache_tree *,\n                         struct cache_entry **, int, const char *, int);\nstruct cache_tree_sub *cache_tree_find_subtree(struct cache_tree *,\n                                               const char *, int, int);\n\nstruct cache_tree *cache_tree(void);\nvoid cache_tree_free(struct cache_tree **);\nvoid cache_tree_invalidate_path(struct cache_tree *, const char *);\nstruct cache_tree_sub *cache_tree_sub(struct cache_tree *, const char *);\n\n/* void cache_tree_write(struct strbuf *, struct cache_tree *root); */\n/* struct cache_tree *cache_tree_read(const char *buffer, unsigned long size); */\n\nint cache_tree_fully_valid(struct cache_tree *);\nint cache_tree_update(const char *repo_id, int version,\n                      const char *worktree,\n                      struct cache_tree *, struct cache_entry **, int, int, int, CommitCB);\n\n/* bitmasks to write_cache_as_tree flags */\n#define WRITE_TREE_MISSING_OK 1\n#define WRITE_TREE_IGNORE_CACHE_TREE 2\n\n/* error return codes */\n#define WRITE_TREE_UNREADABLE_INDEX (-1)\n#define WRITE_TREE_UNMERGED_INDEX (-2)\n#define WRITE_TREE_PREFIX_ERROR (-3)\n\nint write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix);\n/* void prime_cache_tree(struct cache_tree **, struct tree *); */\n\n/* extern int cache_tree_matches_traversal(struct cache_tree *, struct name_entry *ent, struct traverse_info *info); */\n\n#endif\n"
  },
  {
    "path": "common/index/hash.c",
    "content": "/*\n * Some generic hashing helpers.\n */\n#include \"index.h\"\n#include \"hash.h\"\n\n/*\n * Look up a hash entry in the hash table. Return the pointer to\n * the existing entry, or the empty slot if none existed. The caller\n * can then look at the (*ptr) to see whether it existed or not.\n */\nstatic struct hash_table_entry *lookup_hash_entry(unsigned int hash, const struct hash_table *table)\n{\n    unsigned int size = table->size, nr = hash % size;\n    struct hash_table_entry *array = table->array;\n\n    while (array[nr].ptr) {\n        if (array[nr].hash == hash)\n            break;\n        nr++;\n        if (nr >= size)\n            nr = 0;\n    }\n    return array + nr;\n}\n\n\n/*\n * Insert a new hash entry pointer into the table.\n *\n * If that hash entry already existed, return the pointer to\n * the existing entry (and the caller can create a list of the\n * pointers or do anything else). If it didn't exist, return\n * NULL (and the caller knows the pointer has been inserted).\n */\nstatic void **insert_hash_entry(unsigned int hash, void *ptr, struct hash_table *table)\n{\n    struct hash_table_entry *entry = lookup_hash_entry(hash, table);\n\n    if (!entry->ptr) {\n        entry->ptr = ptr;\n        entry->hash = hash;\n        table->nr++;\n        return NULL;\n    }\n    return &entry->ptr;\n}\n\nstatic void grow_hash_table(struct hash_table *table)\n{\n    unsigned int i;\n    unsigned int old_size = table->size, new_size;\n    struct hash_table_entry *old_array = table->array, *new_array;\n\n    new_size = alloc_nr(old_size);\n    new_array = calloc(sizeof(struct hash_table_entry), new_size);\n    table->size = new_size;\n    table->array = new_array;\n    table->nr = 0;\n    for (i = 0; i < old_size; i++) {\n        unsigned int hash = old_array[i].hash;\n        void *ptr = old_array[i].ptr;\n        if (ptr)\n            insert_hash_entry(hash, ptr, table);\n    }\n    free(old_array);\n}\n\nvoid *lookup_hash(unsigned int hash, const struct hash_table *table)\n{\n    if (!table->array)\n        return NULL;\n    return lookup_hash_entry(hash, table)->ptr;\n}\n\nvoid **insert_hash(unsigned int hash, void *ptr, struct hash_table *table)\n{\n    unsigned int nr = table->nr;\n    if (nr >= table->size/2)\n        grow_hash_table(table);\n    return insert_hash_entry(hash, ptr, table);\n}\n\nint for_each_hash(const struct hash_table *table, int (*fn)(void *, void *), void *data)\n{\n    int sum = 0;\n    unsigned int i;\n    unsigned int size = table->size;\n    struct hash_table_entry *array = table->array;\n\n    for (i = 0; i < size; i++) {\n\t    void *ptr = array->ptr;\n\t    array++;\n\t    if (ptr) {\n\t\t    int val = fn(ptr, data);\n\t\t    if (val < 0)\n\t\t\t    return val;\n\t\t    sum += val;\n\t    }\n    }\n    return sum;\n}\n\nvoid free_hash(struct hash_table *table)\n{\n    free(table->array);\n    table->array = NULL;\n    table->size = 0;\n    table->nr = 0;\n}\n"
  },
  {
    "path": "common/index/hash.h",
    "content": "#ifndef HASH_H\n#define HASH_H\n\n/*\n * These are some simple generic hash table helper functions.\n * Not necessarily suitable for all users, but good for things\n * where you want to just keep track of a list of things, and\n * have a good hash to use on them.\n *\n * It keeps the hash table at roughly 50-75% free, so the memory\n * cost of the hash table itself is roughly\n *\n *  3 * 2*sizeof(void *) * nr_of_objects\n *\n * bytes.\n *\n * FIXME: on 64-bit architectures, we waste memory. It would be\n * good to have just 32-bit pointers, requiring a special allocator\n * for hashed entries or something.\n */\nstruct hash_table_entry {\n    unsigned int hash;\n    void *ptr;\n};\n\nstruct hash_table {\n    unsigned int size, nr;\n    struct hash_table_entry *array;\n};\n\nextern void *lookup_hash(unsigned int hash, const struct hash_table *table);\nextern void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table);\nextern int for_each_hash(const struct hash_table *table, int (*fn)(void *, void *), void *data);\nextern void free_hash(struct hash_table *table);\n\nstatic inline void init_hash(struct hash_table *table)\n{\n    table->size = 0;\n    table->nr = 0;\n    table->array = NULL;\n}\n\n#endif\n"
  },
  {
    "path": "common/index/index.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n/*\n * GIT - The information manager from hell\n *\n * Copyright (C) Linus Torvalds, 2005\n */\n#define NO_THE_INDEX_COMPATIBILITY_MACROS\n\n#ifdef HAVE_CONFIG_H\n#include <config.h>\n#endif\n\n#include \"common.h\"\n#include \"utils.h\"\n\n#include \"log.h\"\n\n#include \"index.h\"\n#include \"../seafile-crypt.h\"\n/* #include \"../vc-utils.h\" */\n/* #include \"cache-tree.h\" */\n\n#include <glib.h>\n#include <glib/gstdio.h>\n\n#ifdef WIN32\n\nvoid *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)\n{\n    HANDLE hmap;\n    void *temp;\n    uint64_t o = offset;\n    uint32_t l = o & 0xFFFFFFFF;\n    uint32_t h = (o >> 32) & 0xFFFFFFFF;\n\n    hmap = CreateFileMapping((HANDLE)_get_osfhandle(fd), 0, PAGE_WRITECOPY,\n                             0, 0, 0);\n    if (!hmap) {\n        seaf_warning (\"CreateFileMapping error: %lu.\\n\", GetLastError());\n        return MAP_FAILED;\n    }\n\n    temp = MapViewOfFileEx(hmap, FILE_MAP_COPY, h, l, length, start);\n    if (!temp)\n        seaf_warning (\"MapViewOfFileEx error: %lu.\\n\", GetLastError());\n\n    if (!CloseHandle(hmap))\n        seaf_warning (\"unable to close file mapping handle\\n\");\n\n    return temp ? temp : MAP_FAILED;\n}\n\nint git_munmap(void *start, size_t length)\n{\n    return !UnmapViewOfFile(start);\n}\n#endif //for WIN32\n\nstatic void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n{\n    istate->cache[nr] = ce;\n    add_name_hash(istate, ce);\n}\n\nstatic void replace_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n{\n    struct cache_entry *old = istate->cache[nr];\n\n    remove_name_hash(istate, old);\n    cache_entry_free (old);\n    set_index_entry(istate, nr, ce);\n    istate->cache_changed = 1;\n}\n\nstatic int verify_hdr(struct cache_header *hdr, unsigned long size)\n{\n    GChecksum *c;\n    unsigned char sha1[20];\n    gsize len = 20;\n\n    if (hdr->hdr_signature != htonl(CACHE_SIGNATURE)) {\n        g_critical(\"bad signature\\n\");\n        return -1;\n    }\n    if (hdr->hdr_version != htonl(2) && hdr->hdr_version != htonl(3) &&\n        hdr->hdr_version != htonl(4)) {\n        g_critical(\"bad index version\\n\");\n        return -1;\n    }\n    c = g_checksum_new (G_CHECKSUM_SHA1);\n    g_checksum_update(c, (unsigned char *)hdr, size - 20);\n    g_checksum_get_digest (c, sha1, &len);\n    g_checksum_free (c);\n    if (hashcmp(sha1, (unsigned char *)hdr + size - 20)) {\n        g_critical(\"bad index file sha1 signature\\n\");\n        return -1;\n    }\n    return 0;\n}\n\nstatic int convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_entry **ce)\n{\n    size_t len;\n    const char *name;\n    unsigned int flags = 0;\n    struct cache_entry *ret;\n\n    flags = ntohs(ondisk->flags);\n\n    len = flags & CE_NAMEMASK;\n\n    /* if (flags & CE_EXTENDED) { */\n    /*     struct ondisk_cache_entry_extended *ondisk2; */\n    /*     int extended_flags; */\n    /*     ondisk2 = (struct ondisk_cache_entry_extended *)ondisk; */\n    /*     extended_flags = ntohs(ondisk2->flags2) << 16; */\n    /*     /\\* We do not yet understand any bit out of CE_EXTENDED_FLAGS *\\/ */\n    /*     if (extended_flags & ~CE_EXTENDED_FLAGS) { */\n    /*         g_critical(\"Unknown index entry format %08x\\n\", extended_flags); */\n    /*         return -1; */\n    /*     } */\n    /*     flags |= extended_flags; */\n    /*     name = ondisk2->name; */\n    /* } */\n    /* else */\n    name = ondisk->name;\n\n    if (len == CE_NAMEMASK)\n        len = strlen(name);\n\n    ret = calloc(1, cache_entry_size(len));\n\n    ret->ce_ctime.sec = ntohl(ondisk->ctime.sec);\n    ret->ce_mtime.sec = ntohl(ondisk->mtime.sec);\n    ret->ce_dev   = ntohl(ondisk->dev);\n    ret->ce_ino   = ntohl(ondisk->ino);\n    ret->ce_mode  = ntohl(ondisk->mode);\n    ret->ce_uid   = ntohl(ondisk->uid);\n    ret->ce_gid   = ntohl(ondisk->gid);\n    ret->ce_size  = ntoh64(ondisk->size);\n    /* On-disk flags are just 16 bits */\n    ret->ce_flags = flags;\n\n    hashcpy(ret->sha1, ondisk->sha1);\n\n    /*\n     * NEEDSWORK: If the original index is crafted, this copy could\n     * go unchecked.\n     */\n    memcpy(ret->name, name, len + 1);\n\n    *ce = ret;\n\n    return 0;\n}\n\nstatic int convert_from_disk2(struct ondisk_cache_entry2 *ondisk, struct cache_entry **ce)\n{\n    size_t len;\n    const char *name;\n    unsigned int flags = 0;\n    struct cache_entry *ret;\n\n    flags = ntohs(ondisk->flags);\n\n    len = flags & CE_NAMEMASK;\n\n    name = ondisk->name;\n\n    if (len == CE_NAMEMASK)\n        len = strlen(name);\n\n    ret = calloc(1, cache_entry_size(len));\n\n    ret->ce_ctime.sec = ntoh64(ondisk->ctime.sec);\n    ret->ce_mtime.sec = ntoh64(ondisk->mtime.sec);\n    ret->ce_dev   = ntohl(ondisk->dev);\n    ret->ce_ino   = ntohl(ondisk->ino);\n    ret->ce_mode  = ntohl(ondisk->mode);\n    ret->ce_uid   = ntohl(ondisk->uid);\n    ret->ce_gid   = ntohl(ondisk->gid);\n    ret->ce_size  = ntoh64(ondisk->size);\n    /* On-disk flags are just 16 bits */\n    ret->ce_flags = flags;\n\n    hashcpy(ret->sha1, ondisk->sha1);\n\n    /*\n     * NEEDSWORK: If the original index is crafted, this copy could\n     * go unchecked.\n     */\n    memcpy(ret->name, name, len + 1);\n\n    *ce = ret;\n\n    return 0;\n}\n\nstatic int read_modifiers (struct index_state *istate, void *data, unsigned int size)\n{\n    char *p = data, *sep = data, *modifier;\n    unsigned int i;\n    unsigned int idx = 0;\n\n    for (i = 0; i < size; ++i) {\n        if (*sep == '\\n') {\n            while (idx < istate->cache_nr &&\n                   S_ISDIR(istate->cache[idx]->ce_mode))\n                ++idx;\n            if (idx >= istate->cache_nr) {\n                seaf_warning (\"More modifiers than cache entries.\\n\");\n                return -1;\n            }\n\n            modifier = g_strndup(p, sep - p);\n            istate->cache[idx]->modifier = modifier;\n            idx++;\n            p = sep + 1;\n        }\n        ++sep;\n    }\n\n    while (idx < istate->cache_nr &&\n           S_ISDIR(istate->cache[idx]->ce_mode))\n        ++idx;\n\n    if (idx != istate->cache_nr) {\n        seaf_warning (\"Less modifiers than cached entries.\\n\");\n        return -1;\n    }\n\n    istate->has_modifier = 1;\n\n    return 0;\n}\n\nstatic int read_index_extension(struct index_state *istate,\n                                unsigned int ext, void *data, unsigned int sz)\n{\n    switch (ext) {\n    case CACHE_EXT_MODIFIER:\n        return read_modifiers (istate, data, sz);\n    default:\n        g_critical(\"unknown extension %u.\\n\", ext);\n        break;\n    }\n    return 0;\n}\n\nstatic void alloc_index (struct index_state *istate)\n{\n    istate->cache_alloc = alloc_nr(istate->cache_nr);\n    istate->cache = calloc(istate->cache_alloc, sizeof(struct cache_entry *));\n    istate->name_hash = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                               g_free, NULL);\n#if defined WIN32 || defined __APPLE__\n    istate->i_name_hash = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                                 g_free, NULL);\n#endif\n    istate->initialized = 1;\n    istate->name_hash_initialized = 1;\n}\n\n/* remember to discard_cache() before reading a different cache! */\nint read_index_from(struct index_state *istate, const char *path, int repo_version)\n{\n    int fd, i;\n    SeafStat st;\n    unsigned long src_offset;\n    struct cache_header *hdr;\n    void *mm;\n    size_t mmap_size;\n\n    if (istate->initialized)\n        return istate->cache_nr;\n\n    /* All newly created index files are version 4. */\n    istate->version = 4;\n    /* Index file stores modifier info if repo version > 0 */\n    if (repo_version > 0)\n        istate->has_modifier = 1;\n    istate->timestamp.sec = 0;\n    istate->timestamp.nsec = 0;\n    fd = seaf_util_open (path, O_RDONLY | O_BINARY);\n    if (fd < 0) {\n        if (errno == ENOENT) {\n            alloc_index (istate);\n            return 0;\n        }\n        g_critical(\"index file open failed\\n\");\n        return -1;\n    }\n\n    if (seaf_fstat(fd, &st)) {\n        g_critical(\"cannot stat the open index\\n\");\n        close(fd);\n        return -1;\n    }\n\n    mmap_size = (size_t)st.st_size;\n    if (mmap_size < sizeof(struct cache_header) + 20) {\n        g_critical(\"index file smaller than expected\\n\");\n        close(fd);\n        return -1;\n    }\n\n    mm = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);\n    close(fd);\n    if (mm == MAP_FAILED) {\n        g_critical(\"unable to map index file\\n\");\n        return -1;\n    }\n\n    hdr = mm;\n    if (verify_hdr(hdr, mmap_size) < 0)\n        goto unmap;\n\n    /* Index version will be set to on-disk value here.\n     * If the index is from an old repo, it will be set to 2.\n     * But when we write the index, it'll be updated to version 4.\n     */\n    istate->version = ntohl(hdr->hdr_version);\n    istate->cache_nr = ntohl(hdr->hdr_entries);\n    alloc_index (istate);\n\n    /*\n     * The disk format is actually larger than the in-memory format,\n     * due to space for nsec etc, so even though the in-memory one\n     * has room for a few  more flags, we can allocate using the same\n     * index size\n     */\n\n    src_offset = sizeof(*hdr);\n    for (i = 0; i < istate->cache_nr; i++) {\n        struct ondisk_cache_entry *disk_ce;\n        struct ondisk_cache_entry2 *disk_ce2;\n        struct cache_entry *ce;\n\n        if (istate->version < 4) {\n            disk_ce = (struct ondisk_cache_entry *)((char *)mm + src_offset);\n\n            /* allocate each ce separately so that we can free new\n             * entries added by add_index_entry() later.\n             */\n            if (convert_from_disk(disk_ce, &ce) < 0)\n                return -1;\n\n            src_offset += ondisk_ce_size(ce);\n        } else {\n            disk_ce2 = (struct ondisk_cache_entry2 *)((char *)mm + src_offset);\n\n            /* allocate each ce separately so that we can free new\n             * entries added by add_index_entry() later.\n             */\n            if (convert_from_disk2(disk_ce2, &ce) < 0)\n                return -1;\n\n            src_offset += ondisk_ce_size2(ce);\n        }\n        set_index_entry(istate, i, ce);\n    }\n    istate->timestamp.sec = st.st_mtime;\n    istate->timestamp.nsec = 0;\n\n    while (src_offset <= mmap_size - 20 - sizeof(struct cache_ext_hdr)) {\n        /* After an array of active_nr index entries,\n         * there can be arbitrary number of extended\n         * sections, each of which is prefixed with\n         * extension name (4-byte) and section length\n         * in 4-byte network byte order.\n         */\n        struct cache_ext_hdr *exthdr;\n        exthdr = (struct cache_ext_hdr *) ((char *)mm + src_offset);\n        unsigned int name = ntohl(exthdr->ext_name);\n        unsigned int size = ntohl(exthdr->ext_size);\n        if (read_index_extension(istate,\n                                 name,\n                                 (char *) mm + src_offset + sizeof(struct cache_ext_hdr),\n                                 size) < 0)\n            goto unmap;\n        src_offset += sizeof(struct cache_ext_hdr);\n        src_offset += size;\n    }\n\n    munmap(mm, mmap_size);\n    return istate->cache_nr;\n\nunmap:\n    munmap(mm, mmap_size);\n    g_critical(\"index file corrupt\\n\");\n    return -1;\n}\n\nint is_index_unborn(struct index_state *istate)\n{\n    return (!istate->cache_nr && !istate->alloc && !istate->timestamp.sec);\n}\n\nint unmerged_index(const struct index_state *istate)\n{\n    int i;\n    for (i = 0; i < istate->cache_nr; i++) {\n        if (ce_stage(istate->cache[i]))\n            return 1;\n    }\n    return 0;\n}\n\nint cache_name_compare(const char *name1, int flags1, const char *name2, int flags2)\n{\n    int len1 = flags1 & CE_NAMEMASK;\n    int len2 = flags2 & CE_NAMEMASK;\n    int len = len1 < len2 ? len1 : len2;\n    int cmp;\n\n    cmp = memcmp(name1, name2, len);\n    if (cmp)\n        return cmp;\n    if (len1 < len2)\n        return -1;\n    if (len1 > len2)\n        return 1;\n\n    /* Compare stages  */\n    flags1 &= CE_STAGEMASK;\n    flags2 &= CE_STAGEMASK;\n\n    if (flags1 < flags2)\n        return -1;\n    if (flags1 > flags2)\n        return 1;\n    return 0;\n}\n\n/*\n * This only updates the \"non-critical\" parts of the directory\n * cache, ie the parts that aren't tracked by GIT, and only used\n * to validate the cache.\n */\nvoid fill_stat_cache_info(struct cache_entry *ce, SeafStat *st)\n{\n    ce->ce_ctime.sec = st->st_ctime;\n    ce->ce_mtime.sec = st->st_mtime;\n    ce->ce_ctime.nsec = 0;\n    ce->ce_mtime.nsec = 0;\n    ce->ce_dev = st->st_dev;\n    ce->ce_ino = st->st_ino;\n    ce->ce_uid = st->st_uid;\n    ce->ce_gid = st->st_gid;\n    ce->ce_size = st->st_size;\n\n    /* if (assume_unchanged) */\n    /*     ce->ce_flags |= CE_VALID; */\n\n    if (S_ISREG(st->st_mode))\n        ce_mark_uptodate(ce);\n}\n\nvoid mark_all_ce_unused(struct index_state *index)\n{\n    int i;\n    for (i = 0; i < index->cache_nr; i++)\n        index->cache[i]->ce_flags &= ~(CE_UNPACKED | CE_ADDED | CE_NEW_SKIP_WORKTREE);\n}\n\nstatic int ce_match_stat_basic(struct cache_entry *ce, SeafStat *st)\n{\n    unsigned int changed = 0;\n\n    if (ce->ce_flags & CE_REMOVE)\n        return MODE_CHANGED | DATA_CHANGED | TYPE_CHANGED;\n\n    switch (ce->ce_mode & S_IFMT) {\n    case S_IFREG:\n        changed |= !S_ISREG(st->st_mode) ? TYPE_CHANGED : 0;\n        /* We consider only the owner x bit to be relevant for\n         * \"mode changes\"\n         */\n#ifndef WIN32\n        if ((0100 & (ce->ce_mode ^ st->st_mode)))\n            changed |= MODE_CHANGED;\n#endif\n        break;\n    case S_IFLNK:\n        if (!S_ISLNK(st->st_mode))\n            changed |= TYPE_CHANGED;\n        break;\n    case S_IFGITLINK:\n        /* We ignore most of the st_xxx fields for gitlinks */\n        if (!S_ISDIR(st->st_mode))\n            changed |= TYPE_CHANGED;\n        /* else if (ce_compare_gitlink(ce)) */\n        /*     changed |= DATA_CHANGED; */\n        return changed;\n    default:\n        seaf_warning(\"internal error: ce_mode is %o\\n\", ce->ce_mode);\n        return -1;\n    }\n    if (!is_eml_file (ce->name) && ce->ce_mtime.sec != st->st_mtime)\n        changed |= MTIME_CHANGED;\n    /* if (ce->ce_ctime.sec != st->st_ctime) */\n    /*     changed |= CTIME_CHANGED; */\n\n    if (ce->ce_size != st->st_size)\n        changed |= DATA_CHANGED;\n\n#if 0\n    if (ce->ce_uid != (unsigned int) st->st_uid ||\n        ce->ce_gid != (unsigned int) st->st_gid)\n        changed |= OWNER_CHANGED;\n    if (ce->ce_ino != (unsigned int) st->st_ino)\n        changed |= INODE_CHANGED;\n\n    /* Racily smudged entry? */\n    if (!ce->ce_size) {\n        if (!is_empty_blob_sha1(ce->sha1))\n            changed |= DATA_CHANGED;\n    }\n#endif\n\n    return changed;\n}\n\n#if 0\nstatic int is_racy_timestamp(const struct index_state *istate, struct cache_entry *ce)\n{\n    return (!S_ISGITLINK(ce->ce_mode) &&\n            istate->timestamp.sec &&\n#ifdef USE_NSEC\n            /* nanosecond timestamped files can also be racy! */\n            (istate->timestamp.sec < ce->ce_mtime.sec ||\n             (istate->timestamp.sec == ce->ce_mtime.sec &&\n              istate->timestamp.nsec <= ce->ce_mtime.nsec))\n#else\n            istate->timestamp.sec <= ce->ce_mtime.sec\n#endif\n        );\n}\n#endif\n\n#if 0\nstatic int ce_compare_data(struct cache_entry *ce, SeafStat *st)\n{\n    int match = -1;\n    int fd = g_open (ce->name, O_RDONLY | O_BINARY);\n\n    if (fd >= 0) {\n        unsigned char sha1[20];\n        if (!index_fd(sha1, fd, st, OBJ_BLOB, ce->name))\n            match = hashcmp(sha1, ce->sha1);\n        /* index_fd() closed the file descriptor already */\n    }\n    return match;\n}\n\nstatic int ce_compare_link(struct cache_entry *ce, SeafStat *st)\n{\n    int match = -1;\n    unsigned char sha1[20];\n\n    if (!index_path(sha1, ce->name, st))\n        match = hashcmp(sha1, ce->sha1);\n\n    return match;\n}\n\nstatic int ce_modified_check_fs(struct cache_entry *ce, SeafStat *st)\n{\n    switch (st->st_mode & S_IFMT) {\n    case S_IFREG:\n        if (ce_compare_data(ce, st))\n            return DATA_CHANGED;\n        break;\n    case S_IFLNK:\n        if (ce_compare_link(ce, st))\n            return DATA_CHANGED;\n        break;\n    default:\n        return TYPE_CHANGED;\n    }\n    return 0;\n}\n#endif\n\nint ie_match_stat(struct cache_entry *ce, SeafStat *st,\n                  unsigned int options)\n{\n    unsigned int changed;\n    int ignore_valid = options & CE_MATCH_IGNORE_VALID;\n    int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;\n    /* int assume_racy_is_modified = options & CE_MATCH_RACY_IS_DIRTY; */\n\n    /*\n     * If it's marked as always valid in the index, it's\n     * valid whatever the checked-out copy says.\n     *\n     * skip-worktree has the same effect with higher precedence\n     */\n    if (!ignore_skip_worktree && ce_skip_worktree(ce))\n        return 0;\n    if (!ignore_valid && (ce->ce_flags & CE_VALID))\n        return 0;\n\n    /*\n     * Intent-to-add entries have not been added, so the index entry\n     * by definition never matches what is in the work tree until it\n     * actually gets added.\n     */\n    /* if (ce->ce_flags & CE_INTENT_TO_ADD) */\n    /*     return DATA_CHANGED | TYPE_CHANGED | MODE_CHANGED; */\n\n    changed = ce_match_stat_basic(ce, st);\n\n    /*\n     * Within 1 second of this sequence:\n     *     echo xyzzy >file && git-update-index --add file\n     * running this command:\n     *     echo frotz >file\n     * would give a falsely clean cache entry.  The mtime and\n     * length match the cache, and other stat fields do not change.\n     *\n     * We could detect this at update-index time (the cache entry\n     * being registered/updated records the same time as \"now\")\n     * and delay the return from git-update-index, but that would\n     * effectively mean we can make at most one commit per second,\n     * which is not acceptable.  Instead, we check cache entries\n     * whose mtime are the same as the index file timestamp more\n     * carefully than others.\n     */\n#if 0\n    if (!changed && is_racy_timestamp(istate, ce)) {\n        /* if (assume_racy_is_modified) */\n        /*     changed |= DATA_CHANGED; */\n        /* else */\n        /*     changed |= ce_modified_check_fs(ce, st); */\n        changed = DATA_CHANGED;\n    }\n#endif\n\n    return changed;\n}\n\n/*\n * df_name_compare() is identical to base_name_compare(), except it\n * compares conflicting directory/file entries as equal. Note that\n * while a directory name compares as equal to a regular file, they\n * then individually compare _differently_ to a filename that has\n * a dot after the basename (because '\\0' < '.' < '/').\n *\n * This is used by routines that want to traverse the git namespace\n * but then handle conflicting entries together when possible.\n */\nint df_name_compare(const char *name1, int len1, int mode1,\n                    const char *name2, int len2, int mode2)\n{\n    int len = len1 < len2 ? len1 : len2, cmp;\n    unsigned char c1, c2;\n\n    cmp = memcmp(name1, name2, len);\n    if (cmp)\n        return cmp;\n    /* Directories and files compare equal (same length, same name) */\n    if (len1 == len2)\n        return 0;\n    c1 = name1[len];\n    if (!c1 && S_ISDIR(mode1))\n        c1 = '/';\n    c2 = name2[len];\n    if (!c2 && S_ISDIR(mode2))\n        c2 = '/';\n    if (c1 == '/' && !c2)\n        return 0;\n    if (c2 == '/' && !c1)\n        return 0;\n    return c1 - c2;\n}\n\nint index_name_pos(const struct index_state *istate, const char *name, int namelen)\n{\n    int first, last;\n\n    first = 0;\n    last = istate->cache_nr;\n    while (last > first) {\n        int next = (last + first) >> 1;\n        struct cache_entry *ce = istate->cache[next];\n        int cmp = cache_name_compare(name, namelen, ce->name, ce->ce_flags);\n        if (!cmp)\n            return next;\n        if (cmp < 0) {\n            last = next;\n            continue;\n        }\n        first = next+1;\n    }\n    return -first-1;\n}\n\n/* Remove entry, return true if there are more entries to go.. */\nint remove_index_entry_at(struct index_state *istate, int pos)\n{\n    struct cache_entry *ce = istate->cache[pos];\n\n    /* record_resolve_undo(istate, ce); */\n    remove_name_hash(istate, ce);\n    cache_entry_free (ce);\n    istate->cache_changed = 1;\n    istate->cache_nr--;\n    if (pos >= istate->cache_nr)\n        return 0;\n    memmove(istate->cache + pos,\n            istate->cache + pos + 1,\n            (istate->cache_nr - pos) * sizeof(struct cache_entry *));\n    return 1;\n}\n\n/*\n * Remove all cache ententries marked for removal, that is where\n * CE_REMOVE is set in ce_flags.  This is much more effective than\n * calling remove_index_entry_at() for each entry to be removed.\n */\nvoid remove_marked_cache_entries(struct index_state *istate)\n{\n    struct cache_entry **ce_array = istate->cache;\n    unsigned int i, j;\n    gboolean removed = FALSE;\n\n    for (i = j = 0; i < istate->cache_nr; i++) {\n        if (ce_array[i]->ce_flags & CE_REMOVE) {\n            remove_name_hash(istate, ce_array[i]);\n            cache_entry_free (ce_array[i]);\n            removed = TRUE;\n        } else {\n            ce_array[j++] = ce_array[i];\n        }\n    }\n    if (removed) {\n        istate->cache_changed = 1;\n        istate->cache_nr = j;\n    }\n}\n\nint remove_file_from_index(struct index_state *istate, const char *path)\n{\n    int pos = index_name_pos(istate, path, strlen(path));\n    if (pos < 0)\n        pos = -pos-1;\n    /* cache_tree_invalidate_path(istate->cache_tree, path); */\n    while (pos < istate->cache_nr && !strcmp(istate->cache[pos]->name, path))\n        remove_index_entry_at(istate, pos);\n    return 0;\n}\n\nint ce_same_name(struct cache_entry *a, struct cache_entry *b)\n{\n    int len = ce_namelen(a);\n    return ce_namelen(b) == len && !memcmp(a->name, b->name, len);\n}\n\nint ce_path_match(const struct cache_entry *ce, const char **pathspec)\n{\n    const char *match, *name;\n    int len;\n\n    if (!pathspec)\n        return 1;\n\n    len = ce_namelen(ce);\n    name = ce->name;\n    while ((match = *pathspec++) != NULL) {\n        int matchlen = strlen(match);\n        if (matchlen > len)\n            continue;\n        if (memcmp(name, match, matchlen))\n            continue;\n        if (matchlen && name[matchlen-1] == '/')\n            return 1;\n        if (name[matchlen] == '/' || !name[matchlen])\n            return 1;\n        if (!matchlen)\n            return 1;\n    }\n    return 0;\n}\n\n/*\n * We fundamentally don't like some paths: we don't want\n * dot or dot-dot anywhere, and for obvious reasons don't\n * want to recurse into \".git\" either.\n *\n * Also, we don't want double slashes or slashes at the\n * end that can make pathnames ambiguous.\n */\nstatic int verify_dotfile(const char *rest)\n{\n    /*\n     * The first character was '.', but that\n     * has already been discarded, we now test\n     * the rest.\n     */\n    switch (*rest) {\n        /* \".\" is not allowed */\n    case '\\0': case '/':\n        return 0;\n\n        /*\n         * \".git\" followed by  NUL or slash is bad. This\n         * shares the path end test with the \"..\" case.\n         */\n    case 'g':\n        if (rest[1] != 'i')\n            break;\n        if (rest[2] != 't')\n            break;\n        rest += 2;\n        /* fallthrough */\n    case '.':\n        if (rest[1] == '\\0' || rest[1] == '/')\n            return 0;\n    }\n    return 1;\n}\n\nint verify_path(const char *path)\n{\n    char c;\n\n    goto inside;\n    for (;;) {\n        if (!c)\n            return 1;\n        if (c == '/') {\n        inside:\n            c = *path++;\n            switch (c) {\n            default:\n                continue;\n            case '/': case '\\0':\n                break;\n            case '.':\n                if (verify_dotfile(path))\n                    continue;\n            }\n            return 0;\n        }\n        c = *path++;\n    }\n}\n\nvoid remove_empty_parent_dir_entry (struct index_state *istate, const char *path)\n{\n    char *parent = g_strdup(path);\n    char *slash;\n\n    /* Find and remove empty dir entry from low level to top level. */\n    while (1) {\n        slash = strrchr (parent, '/');\n        if (!slash)\n            break;\n\n        *slash = 0;\n\n        if (index_name_exists (istate, parent, strlen(parent), 0) != NULL) {\n            remove_file_from_index (istate, parent);\n            break;\n        }\n    }\n\n    g_free (parent);\n}\n\nstatic int add_index_entry_with_check(struct index_state *istate, struct cache_entry *ce, int option)\n{\n    int pos;\n    int ok_to_add = option & ADD_CACHE_OK_TO_ADD;\n    /* int ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE; */\n    /* int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK; */\n    int new_only = option & ADD_CACHE_NEW_ONLY;\n\n    remove_empty_parent_dir_entry (istate, ce->name);\n\n    pos = index_name_pos(istate, ce->name, ce->ce_flags);\n\n    /* existing match? Just replace it. */\n    if (pos >= 0) {\n        if (!new_only)\n            replace_index_entry(istate, pos, ce);\n        return 0;\n    }\n    pos = -pos-1;\n\n    /*\n     * Inserting a merged entry (\"stage 0\") into the index\n     * will always replace all non-merged entries..\n     */\n    if (pos < istate->cache_nr && ce_stage(ce) == 0) {\n        while (ce_same_name(istate->cache[pos], ce)) {\n            ok_to_add = 1;\n            if (!remove_index_entry_at(istate, pos))\n                break;\n        }\n    }\n\n    if (!ok_to_add)\n        return -1;\n    /* if (!verify_path(ce->name)) { */\n    /*     seaf_warning(\"Invalid path '%s'\\n\", ce->name); */\n    /*     return -1; */\n    /* } */\n\n    /* if (!skip_df_check && */\n    /*     check_file_directory_conflict(istate, ce, pos, ok_to_replace)) { */\n    /*     if (!ok_to_replace) */\n    /*         return error(\"'%s' appears as both a file and as a directory\", */\n    /*                  ce->name); */\n    /*     pos = index_name_pos(istate, ce->name, ce->ce_flags); */\n    /*     pos = -pos-1; */\n    /* } */\n    return pos + 1;\n}\n\nint add_index_entry(struct index_state *istate, struct cache_entry *ce, int option)\n{\n    int pos;\n\n    if (option & ADD_CACHE_JUST_APPEND)\n        pos = istate->cache_nr;\n    else {\n        int ret;\n        ret = add_index_entry_with_check(istate, ce, option);\n        if (ret <= 0)\n            return ret;\n        pos = ret - 1;\n    }\n\n    /* Make sure the array is big enough .. */\n    if (istate->cache_nr == istate->cache_alloc) {\n        istate->cache_alloc = alloc_nr(istate->cache_alloc);\n        istate->cache = realloc(istate->cache,\n                                istate->cache_alloc * sizeof(struct cache_entry *));\n    }\n\n    /* Add it in.. */\n    istate->cache_nr++;\n    if (istate->cache_nr > pos + 1)\n        memmove(istate->cache + pos + 1,\n                istate->cache + pos,\n                (istate->cache_nr - pos - 1) * sizeof(ce));\n    set_index_entry(istate, pos, ce);\n    istate->cache_changed = 1;\n    return 0;\n}\n\nint add_to_index(const char *repo_id,\n                 int version,\n                 struct index_state *istate,\n                 const char *path,\n                 const char *full_path,\n                 SeafStat *st,\n                 int flags,\n                 SeafileCrypt *crypt,\n                 IndexCB index_cb,\n                 const char *modifier,\n                 gboolean *added)\n{\n    int size, namelen;\n    mode_t st_mode = st->st_mode;\n    struct cache_entry *ce, *alias;\n    unsigned char sha1[20];\n    unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE|CE_MATCH_RACY_IS_DIRTY;\n    int add_option = (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);\n\n    *added = FALSE;\n\n    if (!S_ISREG(st_mode) && !S_ISLNK(st_mode) && !S_ISDIR(st_mode)) {\n        seaf_warning(\"%s: can only add regular files, symbolic links or git-directories\\n\", path);\n        return -1;\n    }\n\n    namelen = strlen(path);\n    /* if (S_ISDIR(st_mode)) { */\n    /*     while (namelen && path[namelen-1] == '/') */\n    /*         namelen--; */\n    /* } */\n    size = cache_entry_size(namelen);\n    ce = calloc(1, size);\n    memcpy(ce->name, path, namelen);\n    ce->ce_flags = namelen;\n    fill_stat_cache_info(ce, st);\n\n    ce->ce_mode = create_ce_mode(st_mode);\n\n    alias = index_name_exists(istate, ce->name, ce_namelen(ce), 0);\n    if (alias) {\n        if (!ce_stage(alias) && !ie_match_stat(alias, st, ce_option)) {\n            free(ce);\n            return 0;\n        }\n    } else {\n#if defined WIN32 || defined __APPLE__\n        alias = index_name_exists (istate, ce->name, ce_namelen(ce), 1);\n        /* If file exists case-insensitively but doesn't exist case-sensitively,\n         * that file is actually being renamed.\n         */\n        if (alias) {\n            remove_file_from_index (istate, alias->name);\n            alias = NULL;\n        }\n#endif\n    }\n\n#ifdef WIN32\n    /* On Windows, no 'x' bit in file mode.\n     * To prevent overwriting 'x' bit, we directly use existing ce mode. \n     */\n    if (alias)\n        ce->ce_mode = alias->ce_mode;\n#endif\n\n#if 0\n#ifdef WIN32\n    /* Fix daylight saving time bug on Windows.\n     * See http://www.codeproject.com/Articles/1144/Beating-the-Daylight-Savings-Time-bug-and-getting\n     * If ce and wt timestamp has a 1 hour gap, it may be affected by the bug.\n     * We then compare the file's id with the id in ce. If they're the same,\n     * we don't need to copy the blocks again. Only update the index.\n     */\n    if (alias && !ce_stage(alias) &&\n        (ABS(alias->ce_mtime.sec - st->st_mtime) == 3600 ||\n         ABS(alias->ce_ctime.sec - st->st_ctime) == 3600)) {\n        if (index_cb (repo_id, version, full_path, sha1, crypt, FALSE) < 0) {\n            free (ce);\n            return 0;\n        }\n        if (memcmp (alias->sha1, sha1, 20) == 0)\n            goto update_index;\n    }\n#endif\n#endif  /* 0 */\n\n    if (index_cb (repo_id, version, full_path, sha1, crypt, TRUE) < 0) {\n        free (ce);\n        return -1;\n    }\n\n    memcpy (ce->sha1, sha1, 20);\n    ce->ce_flags |= CE_ADDED;\n    ce->modifier = g_strdup(modifier);\n\n    if (add_index_entry(istate, ce, add_option)) {\n        seaf_warning(\"unable to add %s to index\\n\",path);\n        return -1;\n    }\n\n    /* As long as the timestamp or mode is changed, we consider\n       the cache enrty as changed. This has been tested by ie_match_stat().\n    */\n    *added = TRUE;\n\n    return 0;\n}\n\n/*\n * Check whether the empty dir conflicts with existing files\n */\nstatic int is_garbage_empty_dir (struct index_state *istate, struct cache_entry *ce)\n{\n    int ret = 0;\n    int pos = index_name_pos (istate, ce->name, ce->ce_flags);\n\n    /* Empty folder already exists in the index. */\n    if (pos >= 0)\n        return 0;\n\n    /* -pos = (the position this entry *should* be) + 1.\n     * So -pos-1 is the first entry larger than this entry.\n     */\n    pos = -pos-1;\n\n    struct cache_entry *next;\n    char *dir_name = g_strconcat (ce->name, \"/\", NULL);\n    int this_len = strlen (ce->name) + 1;\n    while (pos < istate->cache_nr) {\n        next = istate->cache[pos];\n        int rc = strncmp (next->name, dir_name, this_len);\n        if (rc == 0) {\n            ret = 1;\n            break;\n        } else if (rc < 0) {\n            ++pos;\n        } else\n            break;\n    }\n\n    g_free (dir_name);\n    return ret;\n}\n\nstatic struct cache_entry *\ncreate_empty_dir_index_entry (const char *path, SeafStat *st)\n{\n    int namelen, size;\n    struct cache_entry *ce;\n\n    namelen = strlen(path);\n    size = cache_entry_size(namelen);\n    ce = calloc(1, size);\n    memcpy(ce->name, path, namelen);\n    ce->ce_flags = namelen;\n\n    ce->ce_mtime.sec = st->st_mtime;\n    ce->ce_ctime.sec = st->st_ctime;\n\n    ce->ce_mode = S_IFDIR;\n    /* sha1 is all-zero. */\n\n    return ce;\n}\n\nint\nadd_empty_dir_to_index (struct index_state *istate, const char *path, SeafStat *st)\n{\n    struct cache_entry *ce, *alias;\n    int add_option = (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);\n\n    ce = create_empty_dir_index_entry (path, st);\n\n    if (is_garbage_empty_dir (istate, ce)) {\n        free (ce);\n        return 0;\n    }\n\n    alias = index_name_exists(istate, ce->name, ce_namelen(ce), 0);\n    if (alias) {\n        free (ce);\n        return 0;\n    } else {\n#if defined WIN32 || defined __APPLE__\n        alias = index_name_exists (istate, ce->name, ce_namelen(ce), 1);\n        /* If file exists case-insensitively but doesn't exist case-sensitively,\n         * that file is actually being renamed.\n         */\n        if (alias) {\n            remove_file_from_index (istate, alias->name);\n            alias = NULL;\n        }\n#endif\n    }\n\n    ce->ce_flags |= CE_ADDED;\n\n    if (add_index_entry(istate, ce, add_option)) {\n        seaf_warning(\"unable to add %s to index\\n\",path);\n        free (ce);\n        return -1;\n    }\n\n    return 1;\n}\n\nint\nremove_from_index_with_prefix (struct index_state *istate, const char *path_prefix,\n                               gboolean *not_found)\n{\n    int pathlen = strlen(path_prefix);\n    int pos = index_name_pos (istate, path_prefix, pathlen);\n    struct cache_entry *ce;\n\n    if (not_found)\n        *not_found = FALSE;\n\n    /* Exact match, remove that entry. */\n    if (pos >= 0) {\n        remove_index_entry_at (istate, pos);\n        return 0;\n    }\n\n    /* Otherwise it may be a prefix match, remove all entries begin with this prefix.\n     */\n\n    /* -pos = (the position this entry *should* be) + 1.\n     * So -pos-1 is the first entry larger than this entry.\n     */\n    pos = -pos-1;\n\n    /* Add '/' to the end of prefix so that we won't match a partial path component.\n     * e.g. we don't want to match 'abc' with 'abcd/ef'\n     */\n    char *full_path_prefix = g_strconcat (path_prefix, \"/\", NULL);\n    ++pathlen;\n\n    while (pos < istate->cache_nr) {\n        ce = istate->cache[pos];\n        if (strncmp (ce->name, full_path_prefix, pathlen) < 0) {\n            ++pos;\n        } else\n            break;\n    }\n\n    if (pos == istate->cache_nr) {\n        g_free (full_path_prefix);\n        if (not_found)\n            *not_found = TRUE;\n        return 0;\n    }\n\n    int i = pos;\n    while (i < istate->cache_nr) {\n        ce = istate->cache[i];\n        if (strncmp (ce->name, full_path_prefix, pathlen) == 0) {\n            remove_name_hash(istate, ce);\n            cache_entry_free (ce);\n            ++i;\n        } else\n            break;\n    }\n    g_free (full_path_prefix);\n\n    /* No match. */\n    if (i == pos) {\n        if (not_found)\n            *not_found = TRUE;\n        return 0;\n    }\n\n    if (i < istate->cache_nr)\n        memmove (istate->cache + pos,\n                 istate->cache + i,\n                 (istate->cache_nr - i) * sizeof(struct cache_entry *));\n    istate->cache_nr -= (i - pos);\n    istate->cache_changed = 1;\n\n    return 0;\n}\n\nstatic struct cache_entry *\ncreate_renamed_cache_entry (struct cache_entry *ce,\n                            const char *src_path, const char *dst_path)\n{\n    struct cache_entry *new_ce;\n    char *new_ce_name;\n    int src_pathlen = strlen(src_path);\n\n    new_ce_name = g_strconcat (dst_path, &ce->name[src_pathlen], NULL);\n\n    int namelen = strlen(new_ce_name);\n    int size = cache_entry_size (namelen);\n    new_ce = calloc (size, 1);\n    memcpy (new_ce, ce, sizeof(struct cache_entry));\n    new_ce->ce_flags = namelen;\n    new_ce->current_mtime = 0;\n    new_ce->modifier = g_strdup(ce->modifier);\n    new_ce->next = NULL;\n    memcpy (new_ce->name, new_ce_name, namelen);\n    g_free (new_ce_name);\n\n    return new_ce;\n}\n\nstatic struct cache_entry **\ncreate_renamed_cache_entries (struct index_state *istate,\n                              const char *src_path, const char *dst_path,\n                              int *n_entries,\n                              CECallback cb_after_rename,\n                              void *user_data)\n{\n    struct cache_entry *ce, **ret = NULL;\n\n    int src_pathlen = strlen(src_path);\n    int pos = index_name_pos (istate, src_path, src_pathlen);\n\n    /* Exact match, rename that entry. */\n    if (pos >= 0) {\n        ce = istate->cache[pos];\n        ret = calloc (1, sizeof(struct cache_entry *));\n        *ret = create_renamed_cache_entry (ce, src_path, dst_path);\n        *n_entries = 1;\n\n        remove_index_entry_at (istate, pos);\n\n        return ret;\n    }\n\n    /* Otherwise it may be a prefix match, rename all entries begin with this prefix.\n     */\n\n    /* -pos = (the position this entry *should* be) + 1.\n     * So -pos-1 is the first entry larger than this entry.\n     */\n    pos = -pos-1;\n\n    /* Add '/' to the end of prefix so that we won't match a partial path component.\n     * e.g. we don't want to match 'abc' with 'abcd/ef'\n     */\n    char *full_src_path = g_strconcat (src_path, \"/\", NULL);\n    ++src_pathlen;\n\n    while (pos < istate->cache_nr) {\n        ce = istate->cache[pos];\n        if (strncmp (ce->name, full_src_path, src_pathlen) < 0) {\n            ++pos;\n        } else\n            break;\n    }\n\n    if (pos == istate->cache_nr) {\n        g_free (full_src_path);\n        *n_entries = 0;\n        return NULL;\n    }\n\n    int i = pos;\n    while (i < istate->cache_nr) {\n        ce = istate->cache[i];\n        if (strncmp (ce->name, full_src_path, src_pathlen) == 0) {\n            ++i;\n        } else\n            break;\n    }\n    g_free (full_src_path);\n\n    if (i == pos) {\n        *n_entries = 0;\n        return NULL;\n    }\n\n    *n_entries = (i - pos);\n    ret = calloc (*n_entries, sizeof(struct cache_entry *));\n\n    for (i = pos; i < pos + *n_entries; ++i) {\n        ce = istate->cache[i];\n\n        ret[i - pos] = create_renamed_cache_entry (ce, src_path, dst_path);\n\n        if (cb_after_rename)\n            cb_after_rename (ret[i-pos], user_data);\n\n        remove_name_hash(istate, ce);\n        cache_entry_free (ce);\n    }\n\n    if (i < istate->cache_nr)\n        memmove (istate->cache + pos,\n                 istate->cache + i,\n                 (istate->cache_nr - i) * sizeof(struct cache_entry *));\n    istate->cache_nr -= (i - pos);\n    istate->cache_changed = 1;\n\n    return ret;\n}\n\nint\nrename_index_entries (struct index_state *istate,\n                      const char *src_path,\n                      const char *dst_path,\n                      gboolean *not_found,\n                      CECallback cb_after_rename,\n                      void *cb_data)\n{\n    struct cache_entry **new_ces;\n    int n_entries;\n    int ret = 0;\n    int i;\n\n    if (not_found)\n        *not_found = FALSE;\n\n    new_ces = create_renamed_cache_entries (istate, src_path, dst_path, &n_entries,\n                                            cb_after_rename, cb_data);\n    if (n_entries == 0) {\n        if (not_found)\n            *not_found = TRUE;\n        return 0;\n    }\n\n    /* Remove entries under dst_path. It's necessary for the situation that\n     * one file is renamed to overwrite another file.\n     */\n    remove_from_index_with_prefix (istate, dst_path, NULL);\n\n    remove_empty_parent_dir_entry (istate, dst_path);\n\n    /* Insert the renamed entries to their position. */\n    int dst_pathlen = strlen(dst_path);\n    int pos = index_name_pos (istate, dst_path, dst_pathlen);\n    if (pos >= 0) {\n        seaf_warning (\"BUG: %s should not exist in index after remove.\\n\", dst_path);\n        ret = -1;\n        goto out;\n    }\n\n    pos = -pos-1;\n\n    /* There should be at least n_entries free room in istate->cache array,\n     * since we just removed n_entries from the index in\n     * create_renamed_cache_entires(). \n     */\n    if (istate->cache_alloc - istate->cache_nr < n_entries) {\n        seaf_warning (\"BUG: not enough room to insert renamed entries.\\n\"\n                   \"cache_alloc: %u, cache_nr: %u, n_entries: %d.\\n\",\n                   istate->cache_alloc, istate->cache_nr, n_entries);\n        ret = -1;\n        goto out;\n    }\n\n    if (pos < istate->cache_nr)\n        memmove (istate->cache + pos + n_entries,\n                 istate->cache + pos,\n                 (istate->cache_nr - pos) * sizeof(struct cache_entry *));\n\n    memcpy (istate->cache + pos, new_ces, n_entries * sizeof(struct cache_entry *));\n    for (i = 0; i < n_entries; ++i)\n        add_name_hash (istate, new_ces[i]);\n    istate->cache_nr += n_entries;\n    istate->cache_changed = 1;\n\nout:\n    if (ret < 0) {\n        for (i = 0; i < n_entries; ++i)\n            cache_entry_free (new_ces[i]);\n    }\n    free (new_ces);\n\n    return ret;\n}\n\n/*\n * If there is no file under @path, add an empty dir entry for this @path.\n */\nint\nadd_empty_dir_to_index_with_check (struct index_state *istate,\n                                   const char *path, SeafStat *st)\n{\n    int pathlen = strlen(path);\n    int pos = index_name_pos (istate, path, pathlen);\n    struct cache_entry *ce;\n\n    /* Exact match, empty dir entry already exists. */\n    if (pos >= 0) {\n        return 0;\n    }\n\n    /* Otherwise it may be a prefix match, remove all entries begin with this prefix.\n     */\n\n    /* -pos = (the position this entry *should* be) + 1.\n     * So -pos-1 is the first entry larger than this entry.\n     */\n    pos = -pos-1;\n\n    /* Add '/' to the end of prefix so that we won't match a partial path component.\n     * e.g. we don't want to match 'abc' with 'abcd/ef'\n     */\n    char *full_path = g_strconcat (path, \"/\", NULL);\n    ++pathlen;\n\n    gboolean is_empty = TRUE;\n\n    while (pos < istate->cache_nr) {\n        ce = istate->cache[pos];\n        int rc = strncmp (ce->name, full_path, pathlen);\n        if (rc < 0) {\n            ++pos;\n        } else if (rc == 0) {\n            is_empty = FALSE;\n            break;\n        } else\n            break;\n    }\n\n    g_free (full_path);\n\n    if (is_empty) {\n        ce = create_empty_dir_index_entry (path, st);\n\n        /* Make sure the array is big enough .. */\n        if (istate->cache_nr == istate->cache_alloc) {\n            istate->cache_alloc = alloc_nr(istate->cache_alloc);\n            istate->cache = realloc(istate->cache,\n                                    istate->cache_alloc * sizeof(struct cache_entry *));\n        }\n\n        /* Add it in.. */\n        istate->cache_nr++;\n        if (istate->cache_nr > pos + 1)\n            memmove(istate->cache + pos + 1,\n                    istate->cache + pos,\n                    (istate->cache_nr - pos - 1) * sizeof(ce));\n        set_index_entry(istate, pos, ce);\n        istate->cache_changed = 1;\n    }\n\n    return 0;\n}\n\ninline static gboolean\nis_duplicate_dirent (GList *dirents, const char *dname)\n{\n    if (!dirents)\n        return FALSE;\n\n    IndexDirent *dent = dirents->data;\n\n    return (strcmp(dent->dname, dname) == 0);\n}\n\nstatic IndexDirent *\nindex_dirent_new (char *dname, gboolean is_dir, struct cache_entry *ce)\n{\n    IndexDirent *dent = g_new0 (IndexDirent, 1);\n    dent->dname = dname;\n    dent->is_dir = is_dir;\n    if (!is_dir)\n        dent->ce = ce;\n    return dent;\n}\n\nvoid\nindex_dirent_free (IndexDirent *dent)\n{\n    if (!dent)\n        return;\n\n    g_free (dent->dname);\n    g_free (dent);\n}\n\nstatic gint\ncompare_index_dents (gconstpointer a, gconstpointer b)\n{\n    const IndexDirent *denta = a, *dentb = b;\n\n    return (strcmp(denta->dname, dentb->dname));\n}\n\nGList *\nlist_dirents_from_index (struct index_state *istate, const char *dir)\n{\n    char *full_dir;\n    int pathlen;\n    int pos;\n    struct cache_entry *ce;\n    GList *dirents = NULL;\n    char *path, *slash, *dname;\n    gboolean is_dir;\n    IndexDirent *dirent;\n\n    if (dir[0] == 0) {\n        pos = 0;\n        full_dir = g_strdup(dir);\n        pathlen = 0;\n        goto collect_dents;\n    } else {\n        pathlen = strlen(dir);\n        pos = index_name_pos (istate, dir, pathlen);\n    }\n\n    /* Exact match, it's an empty dir. */\n    if (pos >= 0) {\n        return NULL;\n    }\n\n    /* Otherwise it may be a prefix match, there may be dirents under the dir.\n     */\n\n    /* -pos = (the position this entry *should* be) + 1.\n     * So -pos-1 is the first entry larger than this entry.\n     */\n    pos = -pos-1;\n\n    /* Add '/' to the end of prefix so that we won't match a partial path component.\n     * e.g. we don't want to match 'abc' with 'abcd/ef'\n     */\n    full_dir = g_strconcat (dir, \"/\", NULL);\n    ++pathlen;\n\n    while (pos < istate->cache_nr) {\n        ce = istate->cache[pos];\n        if (strncmp (ce->name, full_dir, pathlen) < 0) {\n            ++pos;\n        } else\n            break;\n    }\n\n    /* The dir actually doesn't exist. */\n    if (pos == istate->cache_nr) {\n        g_free (full_dir);\n        return NULL;\n    }\n\ncollect_dents:\n    for (; pos < istate->cache_nr; ++pos) {\n        ce = istate->cache[pos];\n\n        if (strncmp (full_dir, ce->name, pathlen) != 0)\n            break;\n\n        path = ce->name + pathlen;\n        slash = strchr(path, '/');\n        if (slash) {\n            dname = g_strndup(path, slash - path);\n            if (is_duplicate_dirent (dirents, dname)) {\n                g_free (dname);\n                continue;\n            }\n\n            dirent = index_dirent_new (dname, TRUE, NULL);\n            dirents = g_list_prepend (dirents, dirent);\n        } else {\n            dname = g_strdup(path);\n            is_dir = S_ISDIR(ce->ce_mode);\n            dirent = index_dirent_new (dname, is_dir, ce);\n            dirents = g_list_prepend (dirents, dirent);\n        }\n    }\n\n    dirents = g_list_sort (dirents, compare_index_dents);\n\n    g_free (full_dir);\n    return dirents;\n}\n\nstatic struct cache_entry *refresh_cache_entry(struct cache_entry *ce,\n                                               const char *full_path)\n{\n    SeafStat st;\n\n    if (seaf_stat (full_path, &st) < 0) {\n        seaf_warning(\"Failed to stat %s.\\n\", full_path);\n        return NULL;\n    }\n    fill_stat_cache_info(ce, &st);\n\n    return ce;\n}\n\nstruct cache_entry *make_cache_entry(unsigned int mode,\n                                     const unsigned char *sha1, \n                                     const char *path, const char *full_path, \n                                     int stage, int refresh)\n{\n    int size, len;\n    struct cache_entry *ce;\n\n    /* if (!verify_path(path)) { */\n    /*     seaf_warning(\"Invalid path '%s'\", path); */\n    /*     return NULL; */\n    /* } */\n\n    len = strlen(path);\n    size = cache_entry_size(len);\n    ce = calloc(1, size);\n\n    hashcpy(ce->sha1, sha1);\n    memcpy(ce->name, path, len);\n    ce->ce_flags = create_ce_flags(len, stage);\n    ce->ce_mode = create_ce_mode(mode);\n\n    if (refresh)\n        return refresh_cache_entry(ce, full_path);\n\n    return ce;\n}\n\n\n#if 0\nint add_file_to_index(struct index_state *istate, const char *path, int flags)\n{\n    SeafStat st;\n    if (seaf_stat (path, &st)) {\n        seaf_warning(\"unable to stat '%s'\\n\", path);\n        return -1;\n    }\n    return add_to_index(istate, path, &st, flags);\n}\n#endif\n\n#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))\n\nstatic const char *object_type_strings[] = {\n    NULL,        /* OBJ_NONE = 0 */\n    \"commit\",    /* OBJ_COMMIT = 1 */\n    \"tree\",        /* OBJ_TREE = 2 */\n    \"blob\",        /* OBJ_BLOB = 3 */\n    \"tag\",        /* OBJ_TAG = 4 */\n};\n\nstatic const char *typename(unsigned int type)\n{\n    if (type >= ARRAY_SIZE(object_type_strings))\n        return NULL;\n    return object_type_strings[type];\n}\n\n#if 0\nstatic int type_from_string(const char *str)\n{\n    int i;\n\n    for (i = 1; i < ARRAY_SIZE(object_type_strings); i++)\n        if (!strcmp(str, object_type_strings[i]))\n            return i;\n    seaf_warning(\"invalid object type \\\"%s\\\"\\n\", str);\n    return -1;\n}\n#endif\n\nstatic void hash_sha1_file(const void *buf, unsigned long len,\n                           const char *type, unsigned char *sha1)\n{\n    GChecksum *c;\n    gsize cs_len = 20;\n\n    /* Sha1.. */\n    c = g_checksum_new (G_CHECKSUM_SHA1);\n    g_checksum_update(c, buf, len);\n    g_checksum_get_digest (c, sha1, &cs_len);\n    g_checksum_free (c);\n}\n\nstatic int index_mem(unsigned char *sha1, void *buf, uint64_t size,\n                     enum object_type type, const char *path)\n{\n    if (!type)\n        type = OBJ_BLOB;\n\n    hash_sha1_file(buf, size, typename(type), sha1);\n    return 0;\n}\n\n#define SMALL_FILE_SIZE (32*1024)\n\nint index_fd(unsigned char *sha1, int fd, SeafStat *st,\n             enum object_type type, const char *path)\n{\n    int ret;\n    uint64_t size = st->st_size;\n\n    if (!size) {\n        ret = index_mem(sha1, NULL, size, type, path);\n    } else if (size <= SMALL_FILE_SIZE) {\n        char *buf = malloc(size);\n        if (size == readn(fd, buf, size)) {\n            ret = index_mem(sha1, buf, size, type, path);\n        } else {\n            seaf_warning(\"short read %s\\n\", strerror(errno));\n            ret = -1;\n        }\n        free(buf);\n    } else {\n        void *buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);\n        ret = index_mem(sha1, buf, size, type, path);\n        munmap(buf, size);\n    }\n    close(fd);\n    return ret;\n}\n\nint index_path(unsigned char *sha1, const char *path, SeafStat *st)\n{\n    int fd;\n    char buf[SEAF_PATH_MAX];\n    int pathlen;\n\n    switch (st->st_mode & S_IFMT) {\n    case S_IFREG:\n        fd = seaf_util_open (path, O_RDONLY | O_BINARY);\n        if (fd < 0) {\n            seaf_warning(\"g_open (\\\"%s\\\"): %s\\n\", path, strerror(errno));\n            return -1;\n        }\n        if (index_fd(sha1, fd, st, OBJ_BLOB, path) < 0) {\n            return -1;\n        }\n        break;\n#ifndef WIN32        \n    case S_IFLNK:\n        pathlen = readlink(path, buf, SEAF_PATH_MAX);\n        if (pathlen != st->st_size) {\n            char *errstr = strerror(errno);\n            seaf_warning(\"readlink(\\\"%s\\\"): %s\\n\", path, errstr);\n            return -1;\n        }\n        hash_sha1_file(buf, pathlen, typename(OBJ_BLOB), sha1);\n        break;\n#endif        \n    default:\n        seaf_warning(\"%s: unsupported file type\\n\", path);\n        return -1;\n    }\n    return 0;\n}\n\n#if 0\nstatic unsigned char write_buffer[WRITE_BUFFER_SIZE];\nstatic unsigned long write_buffer_len;\n#endif\n\n#define WRITE_BUFFER_SIZE 8192\n\ntypedef struct {\n    GChecksum *context;\n    unsigned char write_buffer[WRITE_BUFFER_SIZE];\n    unsigned long write_buffer_len;\n} WriteIndexInfo;\n\nstatic int ce_write_flush(WriteIndexInfo *info, int fd)\n{\n    unsigned int buffered = info->write_buffer_len;\n    if (buffered) {\n        g_checksum_update(info->context, info->write_buffer, buffered);\n        if (writen(fd, info->write_buffer, buffered) != buffered)\n            return -1;\n        info->write_buffer_len = 0;\n    }\n    return 0;\n}\n\nstatic int ce_write(WriteIndexInfo *info, int fd, void *data, unsigned int len)\n{\n    while (len) {\n        unsigned int buffered = info->write_buffer_len;\n        unsigned int partial = WRITE_BUFFER_SIZE - buffered;\n        if (partial > len)\n            partial = len;\n        memcpy(info->write_buffer + buffered, data, partial);\n        buffered += partial;\n        if (buffered == WRITE_BUFFER_SIZE) {\n            info->write_buffer_len = buffered;\n            if (ce_write_flush(info, fd))\n                return -1;\n            buffered = 0;\n        }\n        info->write_buffer_len = buffered;\n        len -= partial;\n        data = (char *) data + partial;\n    }\n    return 0;\n}\n\nstatic int write_index_ext_header(WriteIndexInfo *info, int fd,\n                                  unsigned int ext, unsigned int sz)\n{\n    ext = htonl(ext);\n    sz = htonl(sz);\n    return ((ce_write(info, fd, &ext, 4) < 0) ||\n            (ce_write(info, fd, &sz, 4) < 0)) ? -1 : 0;\n}\n\nstatic int ce_flush(WriteIndexInfo *info, int fd)\n{\n    unsigned int left = info->write_buffer_len;\n    gsize len = 20;\n\n    if (left) {\n        info->write_buffer_len = 0;\n        g_checksum_update(info->context, info->write_buffer, left);\n    }\n\n    /* Flush first if not enough space for SHA1 signature */\n    if (left + 20 > WRITE_BUFFER_SIZE) {\n        if (writen(fd, info->write_buffer, left) != left)\n            return -1;\n        left = 0;\n    }\n\n    /* Append the SHA1 signature at the end */\n    g_checksum_get_digest (info->context, info->write_buffer + left, &len);\n    left += 20;\n    return (writen(fd, info->write_buffer, left) != left) ? -1 : 0;\n}\n\n#if 0\nstatic void ce_smudge_racily_clean_entry(struct cache_entry *ce)\n{\n    /*\n     * The only thing we care about in this function is to smudge the\n     * falsely clean entry due to touch-update-touch race, so we leave\n     * everything else as they are.  We are called for entries whose\n     * ce_mtime match the index file mtime.\n     *\n     * Note that this actually does not do much for gitlinks, for\n     * which ce_match_stat_basic() always goes to the actual\n     * contents.  The caller checks with is_racy_timestamp() which\n     * always says \"no\" for gitlinks, so we are not called for them ;-)\n     */\n    SeafStat st;\n\n    if (seaf_stat (ce->name, &st) < 0)\n        return;\n    if (ce_match_stat_basic(ce, &st))\n        return;\n\n    /* This is \"racily clean\"; smudge it.  Note that this\n     * is a tricky code.  At first glance, it may appear\n     * that it can break with this sequence:\n     *\n     * $ echo xyzzy >frotz\n     * $ git-update-index --add frotz\n     * $ : >frotz\n     * $ sleep 3\n     * $ echo filfre >nitfol\n     * $ git-update-index --add nitfol\n     *\n     * but it does not.  When the second update-index runs,\n     * it notices that the entry \"frotz\" has the same timestamp\n     * as index, and if we were to smudge it by resetting its\n     * size to zero here, then the object name recorded\n     * in index is the 6-byte file but the cached stat information\n     * becomes zero --- which would then match what we would\n     * obtain from the filesystem next time we stat(\"frotz\").\n     *\n     * However, the second update-index, before calling\n     * this function, notices that the cached size is 6\n     * bytes and what is on the filesystem is an empty\n     * file, and never calls us, so the cached size information\n     * for \"frotz\" stays 6 which does not match the filesystem.\n     */\n    ce->ce_size = 0;\n}\n#endif\n\nstatic int ce_write_entry2(WriteIndexInfo *info, int fd, struct cache_entry *ce)\n{\n    int size = ondisk_ce_size2(ce);\n    struct ondisk_cache_entry2 *ondisk = calloc(1, size);\n    char *name;\n    int result;\n\n    ondisk->ctime.sec = hton64(ce->ce_ctime.sec);\n    ondisk->mtime.sec = hton64(ce->ce_mtime.sec);\n    ondisk->dev  = htonl(ce->ce_dev);\n    ondisk->ino  = htonl(ce->ce_ino);\n    ondisk->mode = htonl(ce->ce_mode);\n    ondisk->uid  = htonl(ce->ce_uid);\n    ondisk->gid  = htonl(ce->ce_gid);\n    ondisk->size = hton64(ce->ce_size);\n    hashcpy(ondisk->sha1, ce->sha1);\n    ondisk->flags = htons(ce->ce_flags);\n    name = ondisk->name;\n    memcpy(name, ce->name, ce_namelen(ce));\n\n    result = ce_write(info, fd, ondisk, size);\n    free(ondisk);\n    return result;\n}\n\nstatic int\nmodifiers_to_string (GString *buf, struct index_state *istate)\n{\n    int i;\n    struct cache_entry *ce;\n\n    for (i = 0; i < istate->cache_nr; ++i) {\n        ce = istate->cache[i];\n        if (S_ISDIR(ce->ce_mode) || (ce->ce_flags & CE_REMOVE))\n            continue;\n        if (!ce->modifier) {\n            seaf_warning (\"BUG: index entry %s doesn't have modifier info.\\n\",\n                       ce->name);\n            return -1;\n        }\n        g_string_append_printf (buf, \"%s\\n\", ce->modifier);\n    }\n\n    return 0;\n}\n\nint write_index(struct index_state *istate, int newfd)\n{\n    WriteIndexInfo info;\n    struct cache_header hdr;\n    int i, removed, extended;\n    struct cache_entry **cache = istate->cache;\n    int entries = istate->cache_nr;\n    SeafStat st;\n    const char *prev_name = NULL;\n    int ret = 0;\n\n    memset (&info, 0, sizeof(info));\n\n    for (i = removed = extended = 0; i < entries; i++) {\n        if (cache[i]->ce_flags & CE_REMOVE)\n            removed++;\n\n        /* reduce extended entries if possible */\n        /* cache[i]->ce_flags &= ~CE_EXTENDED; */\n        /* if (cache[i]->ce_flags & CE_EXTENDED_FLAGS) { */\n        /*     extended++; */\n        /*     cache[i]->ce_flags |= CE_EXTENDED; */\n        /* } */\n    }\n\n    hdr.hdr_signature = htonl(CACHE_SIGNATURE);\n    /* Always use version 4 for newly created index files */\n    hdr.hdr_version = htonl(4);\n    hdr.hdr_entries = htonl(entries - removed);\n\n    info.context = g_checksum_new (G_CHECKSUM_SHA1);\n    if (ce_write(&info, newfd, &hdr, sizeof(hdr)) < 0) {\n        ret = -1;\n        goto out;\n    }\n\n    for (i = 0; i < entries; i++) {\n        struct cache_entry *ce = cache[i];\n        if (ce->ce_flags & CE_REMOVE)\n            continue;\n        // skip duplicate cache.\n        if (prev_name && g_strcmp0 (ce->name, prev_name) ==0) {\n            continue;\n        }\n        prev_name = ce->name;\n        /* if (!ce_uptodate(ce) && is_racy_timestamp(istate, ce)) */\n        /*     ce_smudge_racily_clean_entry(ce); */\n        if (ce_write_entry2(&info, newfd, ce) < 0) {\n            ret = -1;\n            goto out;\n        }\n    }\n\n    /* Write extension data here */\n    if (istate->has_modifier) {\n        GString *buf = g_string_new (\"\");\n        int err;\n\n        if (modifiers_to_string (buf, istate) < 0) {\n            g_string_free (buf, TRUE);\n            ret = -1;\n            goto out;\n        }\n\n        err = write_index_ext_header(&info, newfd, CACHE_EXT_MODIFIER, buf->len) < 0\n            || ce_write(&info, newfd, buf->str, buf->len) < 0;\n        g_string_free (buf, TRUE);\n        if (err) {\n            ret = -1;\n            goto out;\n        }\n    }\n\n    if (ce_flush(&info, newfd) || seaf_fstat(newfd, &st)) {\n        ret = -1;\n        goto out;\n    }\n\n    istate->timestamp.sec = (unsigned int)st.st_mtime;\n    istate->timestamp.nsec = 0;\n\nout:\n    g_checksum_free (info.context);\n    return ret;\n}\n\nint discard_index(struct index_state *istate)\n{\n    int i;\n    for (i = 0; i < istate->cache_nr; ++i)\n        cache_entry_free (istate->cache[i]);\n\n    istate->cache_nr = 0;\n    istate->cache_changed = 0;\n    istate->timestamp.sec = 0;\n    istate->timestamp.nsec = 0;\n    istate->name_hash_initialized = 0;\n    g_hash_table_destroy (istate->name_hash);\n#if defined WIN32 || defined __APPLE__\n    g_hash_table_destroy (istate->i_name_hash);\n#endif\n    /* cache_tree_free(&(istate->cache_tree)); */\n    /* free(istate->alloc); */\n    free(istate->cache);\n    istate->alloc = NULL;\n    istate->initialized = 0;\n\n    /* no need to throw away allocated active_cache */\n    return 0;\n}\n\nvoid cache_entry_free (struct cache_entry *ce)\n{\n    g_free (ce->modifier);\n    free (ce);\n}\n\nvoid remove_name_hash(struct index_state *istate, struct cache_entry *ce)\n{\n    g_hash_table_remove (istate->name_hash, ce->name);\n\n#if defined WIN32 || defined __APPLE__\n    char *i_name = g_utf8_strdown (ce->name, -1);\n    g_hash_table_remove (istate->i_name_hash, i_name);\n    g_free (i_name);\n#endif\n}\n\nvoid add_name_hash(struct index_state *istate, struct cache_entry *ce)\n{\n    g_hash_table_insert (istate->name_hash, g_strdup(ce->name), ce);\n#if defined WIN32 || defined __APPLE__\n    g_hash_table_insert (istate->i_name_hash, g_utf8_strdown(ce->name, -1), ce);\n#endif\n}\n\nstruct cache_entry *index_name_exists(struct index_state *istate,\n                                      const char *name, int namelen,\n                                      int igncase)\n{\n#if defined WIN32 || defined __APPLE__\n    if (!igncase)\n        return g_hash_table_lookup (istate->name_hash, name);\n    else {\n        struct cache_entry *ce;\n        char *i_name = g_utf8_strdown (name, -1);\n        ce = g_hash_table_lookup (istate->i_name_hash, i_name);\n        g_free (i_name);\n        return ce;\n    }\n#else\n    return g_hash_table_lookup (istate->name_hash, name);\n#endif\n}\n"
  },
  {
    "path": "common/index/index.h",
    "content": "#ifndef INDEX_H\n#define INDEX_H\n\n#include \"common.h\"\n\n#ifndef WIN32\n#include <unistd.h>\n#include <sys/param.h>\n#include <dirent.h>\n#include <sys/time.h>\n#endif\n#include <stdio.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <stddef.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <errno.h>\n#ifndef __APPLE__\n#include <limits.h>\n#endif\n#include <sys/types.h>\n#include <time.h>\n\n#include \"utils.h\"\n\n#ifdef WIN32\n#include <inttypes.h>\n#include <winsock2.h>\n#include <windows.h>   \n\n#define DT_UNKNOWN 0\n#define DT_DIR     1\n#define DT_REG     2\n#define DT_LNK     3\n#define DTYPE(de)    DT_UNKNOWN\n\n#define S_IFLNK    0120000 /* Symbolic link */\n#define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK)\n#define S_ISSOCK(x) 0\n\n#ifndef PROT_READ\n#define PROT_READ 1\n#define PROT_WRITE 2\n#define MAP_PRIVATE 1\n#endif\n\n#ifndef MAP_FAILED\n#define MAP_FAILED ((void *)-1)\n#endif\n\n#define mmap git_mmap\n#define munmap git_munmap\nextern void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);\nextern int git_munmap(void *start, size_t length);\n\n#else\n    #include <netinet/in.h>\n    #include <arpa/inet.h>\n    #include <netdb.h>\n    #include <sys/mman.h>\n\n#define DTYPE(de)    ((de)->d_type)\n\n#endif\n\n#ifndef O_BINARY\n#define O_BINARY 0\n#endif\n\n/* unknown mode (impossible combination S_IFIFO|S_IFCHR) */\n#define S_IFINVALID     0030000\n\n/*\n * A \"directory link\" is a link to another git directory.\n *\n * The value 0160000 is not normally a valid mode, and\n * also just happens to be S_IFDIR + S_IFLNK\n *\n * NOTE! We *really* shouldn't depend on the S_IFxxx macros\n * always having the same values everywhere. We should use\n * our internal git values for these things, and then we can\n * translate that to the OS-specific value. It just so\n * happens that everybody shares the same bit representation\n * in the UNIX world (and apparently wider too..)\n */\n#define S_IFGITLINK    0160000\n#define S_ISGITLINK(m)    (((m) & S_IFMT) == S_IFGITLINK)\n\nstruct SeafileCrypt;\n\n/*\n * Basic data structures for the directory cache\n */\n\n#define CACHE_SIGNATURE 0x44495243    /* \"DIRC\" */\nstruct cache_header {\n    unsigned int hdr_signature;\n    unsigned int hdr_version;\n    unsigned int hdr_entries;\n};\n\n/*\n * The \"cache_time\" is just the low 32 bits of the\n * time. It doesn't matter if it overflows - we only\n * check it for equality in the 32 bits we save.\n */\nstruct cache_time {\n    unsigned int sec;\n    unsigned int nsec;\n};\n\n/*\n * dev/ino/uid/gid/size are also just tracked to the low 32 bits\n * Again - this is just a (very strong in practice) heuristic that\n * the inode hasn't changed.\n *\n * We save the fields in big-endian order to allow using the\n * index file over NFS transparently.\n */\n#ifdef WIN32\nstruct ondisk_cache_entry {\n    struct cache_time ctime;\n    struct cache_time mtime;\n    unsigned int dev;\n    unsigned int ino;\n    unsigned int mode;\n    unsigned int uid;\n    unsigned int gid;\n    uint64_t     size;\n    unsigned char sha1[20];\n    unsigned short flags;\n    char name[0]; /* more */\n};\n#else\nstruct ondisk_cache_entry {\n    struct cache_time ctime;\n    struct cache_time mtime;\n    unsigned int dev;\n    unsigned int ino;\n    unsigned int mode;\n    unsigned int uid;\n    unsigned int gid;\n    uint64_t     size;\n    unsigned char sha1[20];\n    unsigned short flags;\n    char name[0]; /* more */\n} __attribute__((__packed__));\n#endif\n\nstruct cache_time64 {\n    guint64 sec;\n    guint64 nsec;\n};\n\n#ifdef WIN32\nstruct ondisk_cache_entry2 {\n    struct cache_time64 ctime;\n    struct cache_time64 mtime;\n    unsigned int dev;\n    unsigned int ino;\n    unsigned int mode;\n    unsigned int uid;\n    unsigned int gid;\n    uint64_t     size;\n    unsigned char sha1[20];\n    unsigned short flags;\n    char name[0]; /* more */\n};\n#else\nstruct ondisk_cache_entry2 {\n    struct cache_time64 ctime;\n    struct cache_time64 mtime;\n    unsigned int dev;\n    unsigned int ino;\n    unsigned int mode;\n    unsigned int uid;\n    unsigned int gid;\n    uint64_t     size;\n    unsigned char sha1[20];\n    unsigned short flags;\n    char name[0]; /* more */\n} __attribute__((__packed__));\n#endif\n\n/*\n * This struct is used when CE_EXTENDED bit is 1\n * The struct must match ondisk_cache_entry exactly from\n * ctime till flags\n */\n#ifdef WIN32\n__pragma(pack(push, 1))\nstruct ondisk_cache_entry_extended {\n    struct cache_time ctime;\n    struct cache_time mtime;\n    unsigned int dev;\n    unsigned int ino;\n    unsigned int mode;\n    unsigned int uid;\n    unsigned int gid;\n    uint64_t     size;\n    unsigned char sha1[20];\n    unsigned short flags;\n    unsigned short flags2;\n    char name[0]; /* more */\n};\n__pragma(pack(pop))\n#else\nstruct ondisk_cache_entry_extended {\n    struct cache_time ctime;\n    struct cache_time mtime;\n    unsigned int dev;\n    unsigned int ino;\n    unsigned int mode;\n    unsigned int uid;\n    unsigned int gid;\n    uint64_t     size;\n    unsigned char sha1[20];\n    unsigned short flags;\n    unsigned short flags2;\n    char name[0]; /* more */\n} __attribute__((__packed__));\n#endif\n\n#define CACHE_EXT_MODIFIER 1\n\n#ifdef WIN32\nstruct cache_ext_hdr {\n    unsigned int ext_name;\n    unsigned int ext_size;\n};\n#else\nstruct cache_ext_hdr {\n    unsigned int ext_name;\n    unsigned int ext_size;\n} __attribute__((__packed__));\n#endif\n\nstruct cache_entry {\n    struct cache_time64 ce_ctime;\n    struct cache_time64 ce_mtime;\n    guint64 current_mtime;      /* used in merge */\n    unsigned int ce_dev;\n    unsigned int ce_ino;\n    unsigned int ce_mode;\n    unsigned int ce_uid;\n    unsigned int ce_gid;\n    uint64_t     ce_size;\n    unsigned int ce_flags;\n    unsigned char sha1[20];\n    char *modifier;\n    struct cache_entry *next;\n    char name[0]; /* more */\n};\n\n#define CE_NAMEMASK  (0x0fff)\n#define CE_STAGEMASK (0x3000)\n#define CE_EXTENDED  (0x4000)\n#define CE_VALID     (0x8000)\n#define CE_STAGESHIFT 12\n\n/*\n * Range 0xFFFF0000 in ce_flags is divided into\n * two parts: in-memory flags and on-disk ones.\n * Flags in CE_EXTENDED_FLAGS will get saved on-disk\n * if you want to save a new flag, add it in\n * CE_EXTENDED_FLAGS\n *\n * In-memory only flags\n */\n#define CE_UPDATE            (1 << 16)\n#define CE_REMOVE            (1 << 17)\n#define CE_UPTODATE          (1 << 18)\n#define CE_ADDED             (1 << 19)\n\n#define CE_HASHED            (1 << 20)\n#define CE_UNHASHED          (1 << 21)\n#define CE_WT_REMOVE         (1 << 22) /* remove in work directory */\n#define CE_CONFLICTED        (1 << 23)\n\n#define CE_UNPACKED          (1 << 24)\n#define CE_NEW_SKIP_WORKTREE (1 << 25)\n\n/*\n * Extended on-disk flags\n */\n#define CE_INTENT_TO_ADD     (1 << 29)\n#define CE_SKIP_WORKTREE     (1 << 30)\n/* CE_EXTENDED2 is for future extension */\n#define CE_EXTENDED2         (1 << 31)\n\n#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD | CE_SKIP_WORKTREE)\n\n/*\n * Safeguard to avoid saving wrong flags:\n *  - CE_EXTENDED2 won't get saved until its semantic is known\n *  - Bits in 0x0000FFFF have been saved in ce_flags already\n *  - Bits in 0x003F0000 are currently in-memory flags\n */\n#if CE_EXTENDED_FLAGS & 0x803FFFFF\n#error \"CE_EXTENDED_FLAGS out of range\"\n#endif\n\n/*\n * Copy the sha1 and stat state of a cache entry from one to\n * another. But we never change the name, or the hash state!\n */\n#define CE_STATE_MASK (CE_HASHED | CE_UNHASHED)\n\nstatic inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry *src)\n{\n    unsigned int state = dst->ce_flags & CE_STATE_MASK;\n\n    /* Don't copy modifier, hash chain and name */\n    memcpy(dst, src, offsetof(struct cache_entry, modifier));\n\n    /* Restore the hash state */\n    dst->ce_flags = (dst->ce_flags & ~CE_STATE_MASK) | state;\n}\n\nstatic inline unsigned create_ce_flags(size_t len, unsigned stage)\n{\n    if (len >= CE_NAMEMASK)\n        len = CE_NAMEMASK;\n    return (len | (stage << CE_STAGESHIFT));\n}\n\nstatic inline size_t ce_namelen(const struct cache_entry *ce)\n{\n    size_t len = ce->ce_flags & CE_NAMEMASK;\n    if (len < CE_NAMEMASK)\n        return len;\n    return strlen(ce->name + CE_NAMEMASK) + CE_NAMEMASK;\n}\n\n#define ce_size(ce) cache_entry_size(ce_namelen(ce))\n#define ondisk_ce_size(ce) ondisk_cache_entry_size(ce_namelen(ce))\n#define ondisk_ce_size2(ce) ondisk_cache_entry_size2(ce_namelen(ce))\n#define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT)\n#define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE)\n#define ce_skip_worktree(ce) ((ce)->ce_flags & CE_SKIP_WORKTREE)\n#define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE)\n\n#define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644)\nstatic inline unsigned int create_ce_mode(unsigned int mode)\n{\n    if (S_ISLNK(mode))\n        return S_IFLNK;\n    if (S_ISDIR(mode))\n        return S_IFDIR;\n    return S_IFREG | ce_permissions(mode);\n}\nstatic inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)\n{\n    return create_ce_mode(mode);\n}\nstatic inline int ce_to_dtype(const struct cache_entry *ce)\n{\n    unsigned ce_mode = ntohl(ce->ce_mode);\n    if (S_ISREG(ce_mode))\n        return DT_REG;\n    else if (S_ISDIR(ce_mode) || S_ISGITLINK(ce_mode))\n        return DT_DIR;\n    else if (S_ISLNK(ce_mode))\n        return DT_LNK;\n    else\n        return DT_UNKNOWN;\n}\nstatic inline unsigned int canon_mode(unsigned int mode)\n{\n    if (S_ISREG(mode))\n        return S_IFREG | ce_permissions(mode);\n    if (S_ISLNK(mode))\n        return S_IFLNK;\n    if (S_ISDIR(mode))\n        return S_IFDIR;\n    return S_IFGITLINK;\n}\n\n#define flexible_size(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7)\n#define cache_entry_size(len) flexible_size(cache_entry,len)\n#define ondisk_cache_entry_size(len) flexible_size(ondisk_cache_entry,len)\n#define ondisk_cache_entry_size2(len) flexible_size(ondisk_cache_entry2,len)\n#define ondisk_cache_entry_extended_size(len) flexible_size(ondisk_cache_entry_extended,len)\n\nstruct index_state {\n    unsigned int version;\n    struct cache_entry **cache;\n    unsigned int cache_nr, cache_alloc, cache_changed;\n    /* struct cache_tree *cache_tree; */\n    struct cache_time timestamp;\n    void *alloc;\n    unsigned name_hash_initialized : 1,\n         initialized : 1;\n    GHashTable *name_hash;\n#if defined WIN32 || defined __APPLE__\n    GHashTable *i_name_hash;    /* ignore case */\n#endif\n    int has_modifier;\n};\n\nextern struct index_state the_index;\n\n/* Name hashing */\nextern unsigned int hash_name(const char *name, int namelen);\n\nenum object_type {\n    OBJ_BAD = -1,\n    OBJ_NONE = 0,\n    OBJ_COMMIT = 1,\n    OBJ_TREE = 2,\n    OBJ_BLOB = 3,\n    OBJ_TAG = 4,\n    /* 5 for future expansion */\n    OBJ_OFS_DELTA = 6,\n    OBJ_REF_DELTA = 7,\n    OBJ_ANY,\n    OBJ_MAX\n};\n\nstatic inline enum object_type object_type(unsigned int mode)\n{\n    return S_ISDIR(mode) ? OBJ_TREE :\n        S_ISGITLINK(mode) ? OBJ_COMMIT :\n        OBJ_BLOB;\n}\n\n#define alloc_nr(x) (((x)+16)*3/2)\n\n/*\n * Realloc the buffer pointed at by variable 'x' so that it can hold\n * at least 'nr' entries; the number of entries currently allocated\n * is 'alloc', using the standard growing factor alloc_nr() macro.\n *\n * DO NOT USE any expression with side-effect for 'x', 'nr', or 'alloc'.\n */\n#define ALLOC_GROW(x, nr, alloc) \\\n    do { \\\n        if ((nr) > alloc) { \\\n            if (alloc_nr(alloc) < (nr)) \\\n                alloc = (nr); \\\n            else \\\n                alloc = alloc_nr(alloc); \\\n            x = realloc((x), alloc * sizeof(*(x))); \\\n        } \\\n    } while (0)\n\n/* Initialize and use the cache information */\nextern int read_index(struct index_state *);\nextern int read_index_preload(struct index_state *, const char **pathspec);\nextern int read_index_from(struct index_state *, const char *path, int repo_version);\nextern int is_index_unborn(struct index_state *);\nextern int read_index_unmerged(struct index_state *);\nextern int write_index(struct index_state *, int newfd);\nextern int discard_index(struct index_state *);\nextern int unmerged_index(const struct index_state *);\nextern int verify_path(const char *path);\nextern int index_name_pos(const struct index_state *, const char *name, int namelen);\n#define ADD_CACHE_OK_TO_ADD 1        /* Ok to add */\n#define ADD_CACHE_OK_TO_REPLACE 2    /* Ok to replace file/directory */\n#define ADD_CACHE_SKIP_DFCHECK 4    /* Ok to skip DF conflict checks */\n#define ADD_CACHE_JUST_APPEND 8        /* Append only; tree.c::read_tree() */\n#define ADD_CACHE_NEW_ONLY 16        /* Do not replace existing ones */\nextern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);\nextern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);\nextern int remove_index_entry_at(struct index_state *, int pos);\nextern void remove_marked_cache_entries(struct index_state *istate);\nextern int remove_file_from_index(struct index_state *, const char *path);\n\n#define ADD_CACHE_VERBOSE 1\n#define ADD_CACHE_PRETEND 2\n#define ADD_CACHE_IGNORE_ERRORS    4\n#define ADD_CACHE_IGNORE_REMOVAL 8\n#define ADD_CACHE_INTENT 16\n\ntypedef int (*IndexCB) (const char *repo_id,\n                        int version,\n                        const char *path,\n                        unsigned char sha1[],\n                        struct SeafileCrypt *crypt,\n                        gboolean write_data);\n\nint add_to_index(const char *repo_id,\n                 int version,\n                 struct index_state *istate,\n                 const char *path,\n                 const char *full_path,\n                 SeafStat *st,\n                 int flags,\n                 struct SeafileCrypt *crypt,\n                 IndexCB index_cb,\n                 const char *modifier,\n                 gboolean *added);\n\nint\nadd_empty_dir_to_index (struct index_state *istate,\n                        const char *path,\n                        SeafStat *st);\n\ntypedef void (*CECallback) (struct cache_entry *ce, void *user_data);\n\nint\nremove_from_index_with_prefix (struct index_state *istate, const char *path_prefix,\n                               gboolean *not_found);\n\nint\nrename_index_entries (struct index_state *istate,\n                      const char *src_path,\n                      const char *dst_path,\n                      gboolean *not_found,\n                      CECallback cb_after_rename,\n                      void *cb_data);\n\nint\nadd_empty_dir_to_index_with_check (struct index_state *istate,\n                                   const char *path, SeafStat *st);\n\nvoid remove_empty_parent_dir_entry (struct index_state *istate, const char *path);\n\nstruct _IndexDirent {\n    char *dname;\n    gboolean is_dir;\n    struct cache_entry *ce;\n};\ntypedef struct _IndexDirent IndexDirent;\n\nvoid\nindex_dirent_free (IndexDirent *dent);\n\nGList *\nlist_dirents_from_index (struct index_state *istate, const char *dir);\n\nextern int add_file_to_index(struct index_state *, const char *path, int flags);\nextern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, const char *full_path, int stage, int refresh);\nextern int ce_same_name(struct cache_entry *a, struct cache_entry *b);\nextern int index_name_is_other(const struct index_state *, const char *, int);\n\nvoid cache_entry_free (struct cache_entry *ce);\n\n/* do stat comparison even if CE_VALID is true */\n#define CE_MATCH_IGNORE_VALID        01\n/* do not check the contents but report dirty on racily-clean entries */\n#define CE_MATCH_RACY_IS_DIRTY        02\n/* do stat comparison even if CE_SKIP_WORKTREE is true */\n#define CE_MATCH_IGNORE_SKIP_WORKTREE    04\nextern int ie_match_stat(struct cache_entry *, SeafStat *, unsigned int);\nextern int ie_modified(const struct index_state *, struct cache_entry *, SeafStat *, unsigned int);\n\nextern int ce_path_match(const struct cache_entry *ce, const char **pathspec);\nextern int index_fd(unsigned char *sha1, int fd, SeafStat *st, enum object_type type, const char *path);\nextern int index_path(unsigned char *sha1, const char *path, SeafStat *st);\nextern void fill_stat_cache_info(struct cache_entry *ce, SeafStat *st);\nextern void mark_all_ce_unused(struct index_state *index);\n\nvoid remove_name_hash(struct index_state *istate, struct cache_entry *ce);\nvoid add_name_hash(struct index_state *istate, struct cache_entry *ce);\nstruct cache_entry *index_name_exists(struct index_state *istate,\n                                      const char *name, int namelen,\n                                      int igncase);\n\n#define MTIME_CHANGED    0x0001\n#define CTIME_CHANGED    0x0002\n#define OWNER_CHANGED    0x0004\n#define MODE_CHANGED    0x0008\n#define INODE_CHANGED   0x0010\n#define DATA_CHANGED    0x0020\n#define TYPE_CHANGED    0x0040\n\nextern const unsigned char null_sha1[20];\nstatic inline int is_null_sha1(const unsigned char *sha1)\n{\n    return !memcmp(sha1, null_sha1, 20);\n}\nstatic inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)\n{\n    return memcmp(sha1, sha2, 20);\n}\nstatic inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src)\n{\n    memcpy(sha_dst, sha_src, 20);\n}\nstatic inline void hashclr(unsigned char *hash)\n{\n    memset(hash, 0, 20);\n}\n\nextern int cache_name_compare(const char *name1, int len1, const char *name2, int len2);\nextern int df_name_compare(const char *name1, int len1, int mode1,\n               const char *name2, int len2, int mode2);\n\n\n#endif\n"
  },
  {
    "path": "common/index/name-hash.c",
    "content": "/*\n * name-hash.c\n *\n * Hashing names in the index state\n *\n * Copyright (C) 2008 Linus Torvalds\n */\n#define NO_THE_INDEX_COMPATIBILITY_MACROS\n#include \"index.h\"\n\n/*\n * This removes bit 5 if bit 6 is set.\n *\n * That will make US-ASCII characters hash to their upper-case\n * equivalent. We could easily do this one whole word at a time,\n * but that's for future worries.\n */\nstatic inline unsigned char icase_hash(unsigned char c)\n{\n    return c & ~((c & 0x40) >> 1);\n}\n\nunsigned int hash_name(const char *name, int namelen)\n{\n    unsigned int hash = 0x123;\n\n    do {\n        unsigned char c = *name++;\n        c = icase_hash(c);\n        hash = hash*101 + c;\n    } while (--namelen);\n    return hash;\n}\n\n#if 0\nstatic void hash_index_entry_directories(struct index_state *istate, struct cache_entry *ce)\n{\n    /*\n     * Throw each directory component in the hash for quick lookup\n     * during a git status. Directory components are stored with their\n     * closing slash.  Despite submodules being a directory, they never\n     * reach this point, because they are stored without a closing slash\n     * in the cache.\n     *\n     * Note that the cache_entry stored with the directory does not\n     * represent the directory itself.  It is a pointer to an existing\n     * filename, and its only purpose is to represent existence of the\n     * directory in the cache.  It is very possible multiple directory\n     * hash entries may point to the same cache_entry.\n     */\n    unsigned int hash;\n    void **pos;\n\n    const char *ptr = ce->name;\n    while (*ptr) {\n        while (*ptr && *ptr != '/')\n            ++ptr;\n        if (*ptr == '/') {\n            ++ptr;\n            hash = hash_name(ce->name, ptr - ce->name);\n            if (!lookup_hash(hash, &istate->name_hash)) {\n                pos = insert_hash(hash, ce, &istate->name_hash);\n                if (pos) {\n                    ce->next = *pos;\n                    *pos = ce;\n                }\n            }\n        }\n    }\n}\n#endif\n\nstatic void hash_index_entry(struct index_state *istate, struct cache_entry *ce)\n{\n    void **pos;\n    unsigned int hash;\n\n    if (ce->ce_flags & CE_HASHED)\n        return;\n    ce->ce_flags |= CE_HASHED;\n    ce->next = NULL;\n    hash = hash_name(ce->name, ce_namelen(ce));\n    pos = insert_hash(hash, ce, &istate->name_hash);\n    if (pos) {\n        ce->next = *pos;\n        *pos = ce;\n    }\n\n    /* if (ignore_case) */\n    /*  hash_index_entry_directories(istate, ce); */\n}\n\nstatic void lazy_init_name_hash(struct index_state *istate)\n{\n    int nr;\n\n    if (istate->name_hash_initialized)\n        return;\n    for (nr = 0; nr < istate->cache_nr; nr++)\n        hash_index_entry(istate, istate->cache[nr]);\n    istate->name_hash_initialized = 1;\n}\n\nvoid add_name_hash(struct index_state *istate, struct cache_entry *ce)\n{\n    ce->ce_flags &= ~CE_UNHASHED;\n    if (istate->name_hash_initialized)\n        hash_index_entry(istate, ce);\n}\n\n#if 0\nstatic int slow_same_name(const char *name1, int len1, const char *name2, int len2)\n{\n    if (len1 != len2)\n        return 0;\n\n    while (len1) {\n        unsigned char c1 = *name1++;\n        unsigned char c2 = *name2++;\n        len1--;\n        if (c1 != c2) {\n            c1 = toupper(c1);\n            c2 = toupper(c2);\n            if (c1 != c2)\n                return 0;\n        }\n    }\n    return 1;\n}\n#endif\n\nstatic int same_name(const struct cache_entry *ce, const char *name, int namelen, int icase)\n{\n    int len = ce_namelen(ce);\n\n    /*\n     * Always do exact compare, even if we want a case-ignoring comparison;\n     * we do the quick exact one first, because it will be the common case.\n     */\n    if (len == namelen && !cache_name_compare(name, namelen, ce->name, len))\n        return 1;\n    else\n        return 0;\n\n    /* if (!icase) */\n    /*  return 0; */\n\n    /*\n     * If the entry we're comparing is a filename (no trailing slash), then compare\n     * the lengths exactly.\n     */\n    /* if (name[namelen - 1] != '/') */\n    /*  return slow_same_name(name, namelen, ce->name, len); */\n\n    /*\n     * For a directory, we point to an arbitrary cache_entry filename.  Just\n     * make sure the directory portion matches.\n     */\n    /* return slow_same_name(name, namelen, ce->name, namelen < len ? namelen : len); */\n}\n\nstruct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int icase)\n{\n    unsigned int hash = hash_name(name, namelen);\n    struct cache_entry *ce;\n\n    lazy_init_name_hash(istate);\n    ce = lookup_hash(hash, &istate->name_hash);\n\n    while (ce) {\n        if (!(ce->ce_flags & CE_UNHASHED)) {\n            if (same_name(ce, name, namelen, icase))\n                return ce;\n        }\n        ce = ce->next;\n    }\n\n    /*\n     * Might be a submodule.  Despite submodules being directories,\n     * they are stored in the name hash without a closing slash.\n     * When ignore_case is 1, directories are stored in the name hash\n     * with their closing slash.\n     *\n     * The side effect of this storage technique is we have need to\n     * remove the slash from name and perform the lookup again without\n     * the slash.  If a match is made, S_ISGITLINK(ce->mode) will be\n     * true.\n     */\n    /* if (icase && name[namelen - 1] == '/') { */\n    /*  ce = index_name_exists(istate, name, namelen - 1, icase); */\n    /*  if (ce && S_ISGITLINK(ce->ce_mode)) */\n    /*      return ce; */\n    /* } */\n    return NULL;\n}\n"
  },
  {
    "path": "common/log.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#include <stdio.h>\n#include <glib/gstdio.h>\n#include <pthread.h>\n\n#ifndef WIN32\n#ifdef SEAFILE_SERVER\n#include <sys/syslog.h>\n#endif\n#endif\n\n#include \"log.h\"\n#include \"utils.h\"\n\n#define MAX_LOG_SIZE 300 * 1024 * 1024 \n\n/* message with greater log levels will be ignored */\nstatic int ccnet_log_level;\nstatic int seafile_log_level;\nstatic char *logfile;\nstatic FILE *logfp;\n\n#ifndef WIN32\n#ifdef SEAFILE_SERVER\nstatic gboolean enable_syslog;\n#endif\n#endif\n\n#ifndef WIN32\n#ifdef SEAFILE_SERVER\nstatic int\nget_syslog_level (GLogLevelFlags level)\n{\n    switch (level) {\n        case G_LOG_LEVEL_DEBUG:\n            return LOG_DEBUG;\n        case G_LOG_LEVEL_INFO:\n            return LOG_INFO;\n        case G_LOG_LEVEL_WARNING:\n            return LOG_WARNING;\n        case G_LOG_LEVEL_ERROR:\n            return LOG_ERR;\n        default:\n            return LOG_DEBUG;\n    }\n}\n#endif\n#endif\n\nstatic void \nseafile_log (const gchar *log_domain, GLogLevelFlags log_level,\n             const gchar *message,    gpointer user_data)\n{\n    time_t t;\n    struct tm *tm;\n    char buf[1024];\n    int len;\n\n    if (log_level > seafile_log_level)\n        return;\n\n    t = time(NULL);\n    tm = localtime(&t);\n    len = strftime (buf, 1024, \"[%x %X] \", tm);\n    g_return_if_fail (len < 1024);\n    if (logfp != NULL) {    \n        fputs (buf, logfp);\n        fputs (message, logfp);\n        fflush (logfp);\n    } else { // log file not available\n        printf(\"%s %s\", buf, message);\n    }\n\n#ifndef WIN32\n#ifdef SEAFILE_SERVER\n    if (enable_syslog)\n        syslog (get_syslog_level (log_level), \"%s\", message);\n#endif\n#endif\n}\n\nstatic void \nccnet_log (const gchar *log_domain, GLogLevelFlags log_level,\n             const gchar *message,    gpointer user_data)\n{\n    time_t t;\n    struct tm *tm;\n    char buf[1024];\n    int len;\n\n    if (log_level > ccnet_log_level)\n        return;\n\n    t = time(NULL);\n    tm = localtime(&t);\n    len = strftime (buf, 1024, \"[%x %X] \", tm);\n    g_return_if_fail (len < 1024);\n    if (logfp != NULL) {\n        fputs (buf, logfp);\n        fputs (message, logfp);\n        fflush (logfp);\n    } else { // log file not available\n        printf(\"%s %s\", buf, message);\n    }\n\n#ifndef WIN32\n#ifdef SEAFILE_SERVER\n    if (enable_syslog)\n        syslog (get_syslog_level (log_level), \"%s\", message);\n#endif\n#endif\n}\n\nstatic int\nget_debug_level(const char *str, int default_level)\n{\n    if (strcmp(str, \"debug\") == 0)\n        return G_LOG_LEVEL_DEBUG;\n    if (strcmp(str, \"info\") == 0)\n        return G_LOG_LEVEL_INFO;\n    if (strcmp(str, \"warning\") == 0)\n        return G_LOG_LEVEL_WARNING;\n    return default_level;\n}\n\nint\nseafile_log_init (const char *_logfile, const char *ccnet_debug_level_str,\n                  const char *seafile_debug_level_str)\n{\n    g_log_set_handler (NULL, G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL\n                       | G_LOG_FLAG_RECURSION, seafile_log, NULL);\n    g_log_set_handler (\"Ccnet\", G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL\n                       | G_LOG_FLAG_RECURSION, ccnet_log, NULL);\n\n    /* record all log message */\n    ccnet_log_level = get_debug_level(ccnet_debug_level_str, G_LOG_LEVEL_INFO);\n    seafile_log_level = get_debug_level(seafile_debug_level_str, G_LOG_LEVEL_DEBUG);\n\n    if (strcmp(_logfile, \"-\") == 0) {\n        logfp = stdout;\n        logfile = g_strdup (_logfile);\n    }\n    else {\n        logfile = ccnet_expand_path(_logfile);\n        if ((logfp = g_fopen (logfile, \"a+\")) == NULL) {\n            seaf_message (\"Failed to open file %s\\n\", logfile);\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nconst int\nseafile_log_reopen (const char *logfile_old)\n{\n    FILE *fp;\n\n    if (fclose(logfp) < 0) {\n        seaf_warning (\"Failed to close file %s\\n\", logfile);\n        return -1;\n    }\n    logfp = NULL;\n\n    if (seaf_util_rename (logfile, logfile_old) < 0) {\n        seaf_warning (\"Failed to rename %s to %s, error: %s\\n\", logfile, logfile_old, strerror(errno));\n        return -1;\n    }\n\n    if ((fp = g_fopen (logfile, \"a+\")) == NULL) {\n        seaf_warning (\"Failed to open file %s\\n\", logfile);\n        return -1;\n    }\n    logfp = fp;\n\n    return 0;\n}\n\nstatic SeafileDebugFlags debug_flags = 0;\n\nstatic GDebugKey debug_keys[] = {\n  { \"Transfer\", SEAFILE_DEBUG_TRANSFER },\n  { \"Sync\", SEAFILE_DEBUG_SYNC },\n  { \"Watch\", SEAFILE_DEBUG_WATCH },\n  { \"Http\", SEAFILE_DEBUG_HTTP },\n  { \"Merge\", SEAFILE_DEBUG_MERGE },\n  { \"Curl\", SEAFILE_DEBUG_CURL },\n  { \"Notification\", SEAFILE_DEBUG_NOTIFICATION },\n  { \"Other\", SEAFILE_DEBUG_OTHER },\n};\n\ngboolean\nseafile_debug_flag_is_set (SeafileDebugFlags flag)\n{\n    return (debug_flags & flag) != 0;\n}\n\nvoid\nseafile_debug_set_flags (SeafileDebugFlags flags)\n{\n    g_message (\"Set debug flags %#x\\n\", flags);\n    debug_flags |= flags;\n}\n\nvoid\nseafile_debug_set_flags_string (const gchar *flags_string)\n{\n    guint nkeys = G_N_ELEMENTS (debug_keys);\n\n    if (flags_string)\n        seafile_debug_set_flags (\n            g_parse_debug_string (flags_string, debug_keys, nkeys));\n}\n\nvoid\nseafile_debug_impl (SeafileDebugFlags flag, const gchar *format, ...)\n{\n    if (flag & debug_flags) {\n        va_list args;\n        va_start (args, format);\n        g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, format, args);\n        va_end (args);\n    }\n}\n\n#ifndef WIN32\n#ifdef SEAFILE_SERVER\nvoid\nset_syslog_config (GKeyFile *config)\n{\n    enable_syslog = g_key_file_get_boolean (config,\n                                            \"general\", \"enable_syslog\",\n                                            NULL);\n    if (enable_syslog)\n        openlog (NULL, LOG_NDELAY | LOG_PID, LOG_USER);\n}\n#endif\n#endif\n\nFILE *\nseafile_get_log_fp ()\n{\n    return logfp;\n}\n\n// seafile event log\nstatic char *eventfile;\nstatic FILE *eventfp;\nstatic pthread_mutex_t event_lock = PTHREAD_MUTEX_INITIALIZER;\n\nint\nseafile_event_log_init (const char *_logfile)\n{\n    eventfile = ccnet_expand_path(_logfile);\n    \n    if ((eventfp = g_fopen (eventfile, \"a+\")) == NULL) {\n        g_free (eventfile);\n        seaf_message (\"Failed to open event file %s\\n\", eventfile);\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int\nseafile_event_log_reopen (const char *logfile_old)\n{\n    FILE *fp;\n\n    if (fclose(eventfp) < 0) {\n        seaf_warning (\"Failed to close file %s\\n\", eventfile);\n        return -1;\n    }\n    eventfp = NULL;\n\n    if (seaf_util_rename (eventfile, logfile_old) < 0) {\n        seaf_warning (\"Failed to rename %s to %s\\n\", eventfile, logfile_old);\n        return -1;\n    }\n\n    if ((fp = g_fopen (eventfile, \"a+\")) == NULL) {\n        seaf_warning (\"Failed to open file %s\\n\", eventfile);\n        return -1;\n    }\n    eventfp = fp;\n\n    return 0;\n}\n\nvoid \nseafile_event_message(const char *message)\n{\n    time_t t;\n    struct tm *tm;\n    char buf[1024];\n    int len;\n\n    t = time(NULL);\n    tm = localtime(&t);\n    len = strftime (buf, 1024, \"[%x %X] \", tm);\n    g_return_if_fail (len < 1024);\n    pthread_mutex_lock (&event_lock);\n    if (eventfp != NULL) {    \n        fputs (buf, eventfp);\n        fputs (message, eventfp);\n        fflush (eventfp);\n    }\n    pthread_mutex_unlock (&event_lock);\n}\n\nstatic void\ncheck_and_reopen_log ()\n{\n    SeafStat st;\n\n    if (g_strcmp0(logfile, \"-\") != 0 && seaf_stat (logfile, &st) >= 0) {\n        if (st.st_size >= MAX_LOG_SIZE) {\n            char *dirname = g_path_get_dirname (logfile);\n            char *logfile_old  = g_build_filename (dirname, \"seafile-old.log\", NULL);\n\n            seafile_log_reopen (logfile_old);\n\n            g_free (dirname);\n            g_free (logfile_old);\n        }\n    }\n\n    if (seaf_stat (eventfile, &st) >= 0) {\n        if (st.st_size >= MAX_LOG_SIZE) {\n            char *dirname = g_path_get_dirname (eventfile);\n            char *eventfile_old  = g_build_filename (dirname, \"events-old.log\", NULL);\n\n            seafile_event_log_reopen (eventfile_old);\n\n            g_free (dirname);\n            g_free (eventfile_old);\n        }\n    }\n}\n\nstatic void*\nlog_rotate (void *vdata)\n{\n    while (1) {\n        check_and_reopen_log ();\n        g_usleep (3600LL * G_USEC_PER_SEC);\n    }\n    return NULL;\n}\n\nint\nseafile_log_rotate_start ()\n{\n    pthread_t tid;\n    pthread_attr_t attr;\n    pthread_attr_init(&attr);\n    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);\n\n    int rc = pthread_create (&tid, &attr, log_rotate, NULL);\n    if (rc != 0) {\n        seaf_warning (\"Failed to start log rotate thread: %s\\n\", strerror(rc));\n        return -1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "common/log.h",
    "content": "#ifndef LOG_H\n#define LOG_H\n\n#define SEAFILE_DOMAIN g_quark_from_string(\"seafile\")\n\n#ifndef seaf_warning\n#ifndef WIN32\n#define seaf_warning(fmt, ...) g_warning(\"%s(%d): \" fmt, __FILE__, __LINE__, ##__VA_ARGS__)\n#else\n#define seaf_warning(...) g_warning(__VA_ARGS__)\n#endif\n#endif\n\n#ifndef seaf_message\n#ifndef WIN32\n#define seaf_message(fmt, ...) g_message(\"%s(%d): \" fmt, __FILE__, __LINE__, ##__VA_ARGS__)\n#else\n#define seaf_message(...) g_message(__VA_ARGS__)\n#endif\n#endif\n\n\nint seafile_log_init (const char *logfile, const char *ccnet_debug_level_str,\n                      const char *seafile_debug_level_str);\n\n#ifndef WIN32\n#ifdef SEAFILE_SERVER\nvoid\nset_syslog_config (GKeyFile *config);\n#endif\n#endif\n\nvoid\nseafile_debug_set_flags_string (const gchar *flags_string);\n\ntypedef enum\n{\n    SEAFILE_DEBUG_TRANSFER = 1 << 1,\n    SEAFILE_DEBUG_SYNC = 1 << 2,\n    SEAFILE_DEBUG_WATCH = 1 << 3, /* wt-monitor */\n    SEAFILE_DEBUG_HTTP = 1 << 4,  /* http server */\n    SEAFILE_DEBUG_MERGE = 1 << 5,\n    SEAFILE_DEBUG_CURL = 1 << 6,\n    SEAFILE_DEBUG_NOTIFICATION = 1 << 7,\n    SEAFILE_DEBUG_OTHER = 1 << 8,\n} SeafileDebugFlags;\n\ngboolean\nseafile_debug_flag_is_set (SeafileDebugFlags flag);\n\nvoid seafile_debug_impl (SeafileDebugFlags flag, const gchar *format, ...);\n\n#ifdef DEBUG_FLAG\n\n#undef seaf_debug\n#define seaf_debug(fmt, ...)  \\\n    seafile_debug_impl (DEBUG_FLAG, \"%.10s(%d): \" fmt, __FILE__, __LINE__, ##__VA_ARGS__)\n\n#endif  /* DEBUG_FLAG */\n\n#endif\n\nFILE *seafile_get_log_fp ();\n\n// seafile event log\nint\nseafile_event_log_init (const char *_logfile);\n\nvoid seafile_event_message (const char *msg);\n\nint\nseafile_log_rotate_start ();\n"
  },
  {
    "path": "common/mq-mgr.c",
    "content": "#include \"common.h\"\n#include \"log.h\"\n#include \"utils.h\"\n#include \"mq-mgr.h\"\n\ntypedef struct SeafMqManagerPriv {\n    // chan <-> async_queue\n    GHashTable *chans;\n} SeafMqManagerPriv;\n\nSeafMqManager *\nseaf_mq_manager_new ()\n{\n    SeafMqManager *mgr = g_new0 (SeafMqManager, 1);\n    mgr->priv = g_new0 (SeafMqManagerPriv, 1);\n    mgr->priv->chans = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                              (GDestroyNotify)g_free,\n                                              (GDestroyNotify)g_async_queue_unref);\n\n    return mgr;\n}\n\nvoid\nseaf_mq_manager_init (SeafMqManager *mgr)\n{\n    g_hash_table_replace (mgr->priv->chans, g_strdup (SEAFILE_NOTIFY_CHAN),\n                          g_async_queue_new_full ((GDestroyNotify)json_decref));\n}\n\nvoid\nseaf_mq_manager_publish_notification (SeafMqManager *mgr, const char *type, const char *content)\n{\n    const char *chan = SEAFILE_NOTIFY_CHAN;\n    GAsyncQueue *async_queue = g_hash_table_lookup (mgr->priv->chans, chan);\n    if (!async_queue) {\n        seaf_warning (\"Unkonwn message channel %s.\\n\", chan);\n        return;\n    }\n\n    if (!type || !content) {\n        seaf_warning (\"type and content should not be NULL.\\n\");\n        return;\n    }\n\n    json_t *msg = json_object ();\n    json_object_set_new (msg, \"type\", json_string(type));\n    json_object_set_new (msg, \"content\", json_string(content));\n\n    g_async_queue_push (async_queue, msg);\n}\n\njson_t *\nseaf_mq_manager_pop_message (SeafMqManager *mgr)\n{\n    const char *chan = SEAFILE_NOTIFY_CHAN;\n    GAsyncQueue *async_queue = g_hash_table_lookup (mgr->priv->chans, chan);\n    if (!async_queue) {\n        seaf_warning (\"Unkonwn message channel %s.\\n\", chan);\n        return NULL;\n    }\n\n    return g_async_queue_try_pop (async_queue);\n}\n"
  },
  {
    "path": "common/mq-mgr.h",
    "content": "#ifndef SEAF_MQ_MANAGER_H\n#define SEAF_MQ_MANAGER_H\n\n#define SEAFILE_NOTIFY_CHAN \"seafile.notification\"\n\nstruct SeafMqManagerPriv;\n\ntypedef struct SeafMqManager {\n    struct SeafMqManagerPriv *priv;\n} SeafMqManager;\n\nSeafMqManager *\nseaf_mq_manager_new ();\n\nvoid\nseaf_mq_manager_init (SeafMqManager *mgr);\n\nvoid\nseaf_mq_manager_publish_notification (SeafMqManager *mgr, const char *type, const char *content);\n\njson_t *\nseaf_mq_manager_pop_message (SeafMqManager *mgr);\n\n#endif\n"
  },
  {
    "path": "common/obj-backend-fs.c",
    "content": "#ifndef _WIN32_WINNT\n#define _WIN32_WINNT 0x500\n#endif\n\n#include \"common.h\"\n#include \"utils.h\"\n#include \"obj-backend.h\"\n\n#ifndef WIN32\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#endif\n\n#ifdef WIN32\n#include <windows.h>\n#include <io.h>\n#endif\n\n#define DEBUG_FLAG SEAFILE_DEBUG_OTHER\n#include \"log.h\"\n\ntypedef struct FsPriv {\n    char *v0_obj_dir;\n    int v0_dir_len;\n    char *obj_dir;\n    int   dir_len;\n} FsPriv;\n\nstatic void\nid_to_path (FsPriv *priv, const char *obj_id, char path[],\n            const char *repo_id, int version)\n{\n    char *pos = path;\n    int n;\n\n#if defined MIGRATION || defined SEAFILE_CLIENT\n    if (version > 0) {\n        n = snprintf (path, SEAF_PATH_MAX, \"%s/%s/\", priv->obj_dir, repo_id);\n        pos += n;\n    } else {\n        memcpy (pos, priv->v0_obj_dir, priv->v0_dir_len);\n        pos[priv->v0_dir_len] = '/';\n        pos += priv->v0_dir_len + 1;\n    }\n#else\n    n = snprintf (path, SEAF_PATH_MAX, \"%s/%s/\", priv->obj_dir, repo_id);\n    pos += n;\n#endif\n\n    memcpy (pos, obj_id, 2);\n    pos[2] = '/';\n    pos += 3;\n\n    memcpy (pos, obj_id + 2, 41 - 2);\n}\n\nstatic int\nobj_backend_fs_read (ObjBackend *bend,\n                     const char *repo_id,\n                     int version,\n                     const char *obj_id,\n                     void **data,\n                     int *len)\n{\n    char path[SEAF_PATH_MAX];\n    gsize tmp_len;\n    GError *error = NULL;\n\n    id_to_path (bend->priv, obj_id, path, repo_id, version);\n\n    /* seaf_debug (\"object path: %s\\n\", path); */\n\n    g_file_get_contents (path, (gchar**)data, &tmp_len, &error);\n    if (error) {\n#ifdef MIGRATION\n        g_clear_error (&error);\n        id_to_path (bend->priv, obj_id, path, repo_id, 1);\n        g_file_get_contents (path, (gchar**)data, &tmp_len, &error);\n        if (error) {\n            seaf_debug (\"[obj backend] Failed to read object %s: %s.\\n\",\n                        obj_id, error->message);\n            g_clear_error (&error);\n            return -1;\n        }\n#else\n        seaf_debug (\"[obj backend] Failed to read object %s: %s.\\n\",\n                    obj_id, error->message);\n        g_clear_error (&error);\n        return -1;\n#endif\n    }\n\n    *len = (int)tmp_len;\n    return 0;\n}\n\n/*\n * Flush operating system and disk caches for @fd.\n */\nstatic int\nfsync_obj_contents (int fd)\n{\n#ifdef __linux__\n    /* Some file systems may not support fsync().\n     * In this case, just skip the error.\n     */\n    if (fsync (fd) < 0) {\n        if (errno == EINVAL)\n            return 0;\n        else {\n            seaf_warning (\"Failed to fsync: %s.\\n\", strerror(errno));\n            return -1;\n        }\n    }\n    return 0;\n#endif\n\n#ifdef __APPLE__\n    /* OS X: fcntl() is required to flush disk cache, fsync() only\n     * flushes operating system cache.\n     */\n    if (fcntl (fd, F_FULLFSYNC, NULL) < 0) {\n        seaf_warning (\"Failed to fsync: %s.\\n\", strerror(errno));\n        return -1;\n    }\n    return 0;\n#endif\n\n#ifdef WIN32\n    HANDLE handle;\n\n    handle = (HANDLE)_get_osfhandle (fd);\n    if (handle == INVALID_HANDLE_VALUE) {\n        seaf_warning (\"Failed to get handle from fd.\\n\");\n        return -1;\n    }\n\n    if (!FlushFileBuffers (handle)) {\n        seaf_warning (\"FlushFileBuffer() failed: %lu.\\n\", GetLastError());\n        return -1;\n    }\n\n    return 0;\n#endif\n}\n\n/*\n * Rename file from @tmp_path to @obj_path.\n * This also makes sure the changes to @obj_path's parent folder\n * is flushed to disk.\n */\nstatic int\nrename_and_sync (const char *tmp_path, const char *obj_path)\n{\n#ifdef __linux__\n    char *parent_dir;\n    int ret = 0;\n\n    if (rename (tmp_path, obj_path) < 0) {\n        seaf_warning (\"Failed to rename from %s to %s: %s.\\n\",\n                      tmp_path, obj_path, strerror(errno));\n        return -1;\n    }\n\n    parent_dir = g_path_get_dirname (obj_path);\n    int dir_fd = open (parent_dir, O_RDONLY);\n    if (dir_fd < 0) {\n        seaf_warning (\"Failed to open dir %s: %s.\\n\", parent_dir, strerror(errno));\n        goto out;\n    }\n\n    /* Some file systems don't support fsyncing a directory. Just ignore the error.\n     */\n    if (fsync (dir_fd) < 0) {\n        if (errno != EINVAL) {\n            seaf_warning (\"Failed to fsync dir %s: %s.\\n\",\n                          parent_dir, strerror(errno));\n            ret = -1;\n        }\n        goto out;\n    }\n\nout:\n    g_free (parent_dir);\n    if (dir_fd >= 0)\n        close (dir_fd);\n    return ret;\n#endif\n\n#ifdef __APPLE__\n    /*\n     * OS X garantees an existence of obj_path always exists,\n     * even when the system crashes.\n     */\n    if (rename (tmp_path, obj_path) < 0) {\n        seaf_warning (\"Failed to rename from %s to %s: %s.\\n\",\n                      tmp_path, obj_path, strerror(errno));\n        return -1;\n    }\n    return 0;\n#endif\n\n#ifdef WIN32\n    wchar_t *w_tmp_path = g_utf8_to_utf16 (tmp_path, -1, NULL, NULL, NULL);\n    wchar_t *w_obj_path = g_utf8_to_utf16 (obj_path, -1, NULL, NULL, NULL);\n    int ret = 0;\n\n    if (!MoveFileExW (w_tmp_path, w_obj_path,\n                      MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) {\n        seaf_warning (\"MoveFilExW failed: %lu.\\n\", GetLastError());\n        ret = -1;\n        goto out;\n    }\n\nout:\n    g_free (w_tmp_path);\n    g_free (w_obj_path);\n    return ret;\n#endif\n}\n\nstatic int\nsave_obj_contents (const char *path, const void *data, int len, gboolean need_sync)\n{\n    char tmp_path[SEAF_PATH_MAX];\n    int fd;\n\n    snprintf (tmp_path, SEAF_PATH_MAX, \"%s.XXXXXX\", path);\n    fd = g_mkstemp (tmp_path);\n    if (fd < 0) {\n        seaf_warning (\"[obj backend] Failed to open tmp file %s: %s.\\n\",\n                      tmp_path, strerror(errno));\n        return -1;\n    }\n\n    if (writen (fd, data, len) < 0) {\n        seaf_warning (\"[obj backend] Failed to write obj %s: %s.\\n\",\n                      tmp_path, strerror(errno));\n        return -1;\n    }\n\n    if (need_sync && fsync_obj_contents (fd) < 0)\n        return -1;\n\n    /* Close may return error, especially in NFS. */\n    if (close (fd) < 0) {\n        seaf_warning (\"[obj backend Failed close obj %s: %s.\\n\",\n                      tmp_path, strerror(errno));\n        return -1;\n    }\n\n    if (need_sync) {\n        if (rename_and_sync (tmp_path, path) < 0)\n            return -1;\n    } else {\n        if (g_rename (tmp_path, path) < 0) {\n            seaf_warning (\"[obj backend] Failed to rename %s: %s.\\n\",\n                          path, strerror(errno));\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nstatic int\ncreate_parent_path (const char *path)\n{\n    char *dir = g_path_get_dirname (path);\n    if (!dir)\n        return -1;\n\n    if (g_file_test (dir, G_FILE_TEST_EXISTS)) {\n        g_free (dir);\n        return 0;\n    }\n\n    if (checkdir_with_mkdir (dir) < 0) {\n        seaf_warning (\"Failed to create object parent path %s: %s.\\n\",\n                      dir, strerror(errno));\n        g_free (dir);\n        return -1;\n    }\n\n    g_free (dir);\n    return 0;\n}\n\nstatic int\nobj_backend_fs_write (ObjBackend *bend,\n                      const char *repo_id,\n                      int version,\n                      const char *obj_id,\n                      void *data,\n                      int len,\n                      gboolean need_sync)\n{\n    char path[SEAF_PATH_MAX];\n\n    id_to_path (bend->priv, obj_id, path, repo_id, version);\n\n    /* GTimeVal s, e; */\n\n    /* g_get_current_time (&s); */\n\n    if (create_parent_path (path) < 0) {\n        seaf_warning (\"[obj backend] Failed to create path for obj %s:%s.\\n\",\n                      repo_id, obj_id);\n        return -1;\n    }\n\n    if (save_obj_contents (path, data, len, need_sync) < 0) {\n        seaf_warning (\"[obj backend] Failed to write obj %s:%s.\\n\",\n                      repo_id, obj_id);\n        return -1;\n    }\n\n    /* g_get_current_time (&e); */\n\n    /* seaf_message (\"write obj time: %ldus.\\n\", */\n    /*               ((e.tv_sec*1000000+e.tv_usec) - (s.tv_sec*1000000+s.tv_usec))); */\n\n    return 0;\n}\n\nstatic gboolean\nobj_backend_fs_exists (ObjBackend *bend,\n                       const char *repo_id,\n                       int version,\n                       const char *obj_id)\n{\n    char path[SEAF_PATH_MAX];\n    SeafStat st;\n\n    id_to_path (bend->priv, obj_id, path, repo_id, version);\n\n    if (seaf_stat (path, &st) == 0)\n        return TRUE;\n\n    return FALSE;\n}\n\nstatic void\nobj_backend_fs_delete (ObjBackend *bend,\n                       const char *repo_id,\n                       int version,\n                       const char *obj_id)\n{\n    char path[SEAF_PATH_MAX];\n\n    id_to_path (bend->priv, obj_id, path, repo_id, version);\n    g_unlink (path);\n}\n\nstatic int\nobj_backend_fs_foreach_obj (ObjBackend *bend,\n                            const char *repo_id,\n                            int version,\n                            SeafObjFunc process,\n                            void *user_data)\n{\n    FsPriv *priv = bend->priv;\n    char *obj_dir = NULL;\n    int dir_len;\n    GDir *dir1 = NULL, *dir2;\n    const char *dname1, *dname2;\n    char obj_id[128];\n    char path[SEAF_PATH_MAX], *pos;\n    int ret = 0;\n\n#if defined MIGRATION || defined SEAFILE_CLIENT\n    if (version > 0)\n        obj_dir = g_build_filename (priv->obj_dir, repo_id, NULL);\n    else\n        obj_dir = g_strdup(priv->v0_obj_dir);\n#else\n    obj_dir = g_build_filename (priv->obj_dir, repo_id, NULL);\n#endif\n    dir_len = strlen (obj_dir);\n\n    dir1 = g_dir_open (obj_dir, 0, NULL);\n    if (!dir1) {\n        goto out;\n    }\n\n    memcpy (path, obj_dir, dir_len);\n    pos = path + dir_len;\n\n    while ((dname1 = g_dir_read_name(dir1)) != NULL) {\n        snprintf (pos, sizeof(path) - dir_len, \"/%s\", dname1);\n\n        dir2 = g_dir_open (path, 0, NULL);\n        if (!dir2) {\n            seaf_warning (\"Failed to open object dir %s.\\n\", path);\n            continue;\n        }\n\n        while ((dname2 = g_dir_read_name(dir2)) != NULL) {\n            snprintf (obj_id, sizeof(obj_id), \"%s%s\", dname1, dname2);\n            if (!process (repo_id, version, obj_id, user_data)) {\n                g_dir_close (dir2);\n                goto out;\n            }\n        }\n        g_dir_close (dir2);\n    }\n\nout:\n    if (dir1)\n        g_dir_close (dir1);\n    g_free (obj_dir);\n\n    return ret;\n}\n\nstatic int\nobj_backend_fs_copy (ObjBackend *bend,\n                     const char *src_repo_id,\n                     int src_version,\n                     const char *dst_repo_id,\n                     int dst_version,\n                     const char *obj_id)\n{\n    char src_path[SEAF_PATH_MAX];\n    char dst_path[SEAF_PATH_MAX];\n\n    id_to_path (bend->priv, obj_id, src_path, src_repo_id, src_version);\n    id_to_path (bend->priv, obj_id, dst_path, dst_repo_id, dst_version);\n\n    if (g_file_test (dst_path, G_FILE_TEST_EXISTS))\n        return 0;\n\n    if (create_parent_path (dst_path) < 0) {\n        seaf_warning (\"Failed to create dst path %s for obj %s.\\n\",\n                      dst_path, obj_id);\n        return -1;\n    }\n\n#ifdef WIN32\n    if (!CreateHardLinkA (dst_path, src_path, NULL)) {\n        seaf_warning (\"Failed to link %s to %s: %lu.\\n\",\n                      src_path, dst_path, GetLastError());\n        return -1;\n    }\n    return 0;\n#else\n    int ret = link (src_path, dst_path);\n    if (ret < 0 && errno != EEXIST) {\n        seaf_warning (\"Failed to link %s to %s: %s.\\n\",\n                      src_path, dst_path, strerror(errno));\n        return -1;\n    }\n    return ret;\n#endif\n}\n\nstatic int\nobj_backend_fs_remove_store (ObjBackend *bend, const char *store_id)\n{\n    FsPriv *priv = bend->priv;\n    char *obj_dir = NULL;\n    GDir *dir1, *dir2;\n    const char *dname1, *dname2;\n    char *path1, *path2;\n\n    obj_dir = g_build_filename (priv->obj_dir, store_id, NULL);\n\n    dir1 = g_dir_open (obj_dir, 0, NULL);\n    if (!dir1) {\n        g_free (obj_dir);\n        return 0;\n    }\n\n    while ((dname1 = g_dir_read_name(dir1)) != NULL) {\n        path1 = g_build_filename (obj_dir, dname1, NULL);\n\n        dir2 = g_dir_open (path1, 0, NULL);\n        if (!dir2) {\n            seaf_warning (\"Failed to open obj dir %s.\\n\", path1);\n            g_dir_close (dir1);\n            g_free (path1);\n            g_free (obj_dir);\n            return -1;\n        }\n\n        while ((dname2 = g_dir_read_name(dir2)) != NULL) {\n            path2 = g_build_filename (path1, dname2, NULL);\n            g_unlink (path2);\n            g_free (path2);\n        }\n        g_dir_close (dir2);\n\n        g_rmdir (path1);\n        g_free (path1);\n    }\n\n    g_dir_close (dir1);\n    g_rmdir (obj_dir);\n    g_free (obj_dir);\n\n    return 0;\n}\n\nObjBackend *\nobj_backend_fs_new (const char *seaf_dir, const char *obj_type)\n{\n    ObjBackend *bend;\n    FsPriv *priv;\n\n    bend = g_new0(ObjBackend, 1);\n    priv = g_new0(FsPriv, 1);\n    bend->priv = priv;\n\n    priv->v0_obj_dir = g_build_filename (seaf_dir, obj_type, NULL);\n    priv->v0_dir_len = strlen(priv->v0_obj_dir);\n\n    priv->obj_dir = g_build_filename (seaf_dir, \"storage\", obj_type, NULL);\n    priv->dir_len = strlen (priv->obj_dir);\n\n    if (checkdir_with_mkdir (priv->v0_obj_dir) < 0) {\n        seaf_warning (\"[Obj Backend] Objects dir %s does not exist and\"\n                   \" is unable to create\\n\", priv->v0_obj_dir);\n        goto onerror;\n    }\n\n    if (checkdir_with_mkdir (priv->obj_dir) < 0) {\n        seaf_warning (\"[Obj Backend] Objects dir %s does not exist and\"\n                   \" is unable to create\\n\", priv->obj_dir);\n        goto onerror;\n    }\n\n    bend->read = obj_backend_fs_read;\n    bend->write = obj_backend_fs_write;\n    bend->exists = obj_backend_fs_exists;\n    bend->delete = obj_backend_fs_delete;\n    bend->foreach_obj = obj_backend_fs_foreach_obj;\n    bend->copy = obj_backend_fs_copy;\n    bend->remove_store = obj_backend_fs_remove_store;\n\n    return bend;\n\nonerror:\n    g_free (priv->v0_obj_dir);\n    g_free (priv->obj_dir);\n    g_free (priv);\n    g_free (bend);\n\n    return NULL;\n}\n"
  },
  {
    "path": "common/obj-backend.h",
    "content": "#ifndef OBJ_BACKEND_H\n#define OBJ_BACKEND_H\n\n#include <glib.h>\n#include \"obj-store.h\"\n\ntypedef struct ObjBackend ObjBackend;\n\nstruct ObjBackend {\n    int         (*read) (ObjBackend *bend,\n                         const char *repo_id,\n                         int version,\n                         const char *obj_id,\n                         void **data,\n                         int *len);\n\n    int         (*write) (ObjBackend *bend,\n                          const char *repo_id,\n                          int version,\n                          const char *obj_id,\n                          void *data,\n                          int len,\n                          gboolean need_sync);\n\n    gboolean    (*exists) (ObjBackend *bend,\n                           const char *repo_id,\n                           int version,\n                           const char *obj_id);\n\n    void        (*delete) (ObjBackend *bend,\n                           const char *repo_id,\n                           int version,\n                           const char *obj_id);\n\n    int         (*foreach_obj) (ObjBackend *bend,\n                                const char *repo_id,\n                                int version,\n                                SeafObjFunc process,\n                                void *user_data);\n\n    int         (*copy) (ObjBackend *bend,\n                         const char *src_repo_id,\n                         int src_version,\n                         const char *dst_repo_id,\n                         int dst_version,\n                         const char *obj_id);\n\n    int        (*remove_store) (ObjBackend *bend,\n                                const char *store_id);\n\n    void *priv;\n};\n\n#endif\n"
  },
  {
    "path": "common/obj-store.c",
    "content": "#include \"common.h\"\n\n#include \"log.h\"\n\n#include \"seafile-session.h\"\n\n#include \"utils.h\"\n\n#include \"obj-backend.h\"\n#include \"obj-store.h\"\n\nstruct SeafObjStore {\n    ObjBackend   *bend;\n};\ntypedef struct SeafObjStore SeafObjStore;\n\nextern ObjBackend *\nobj_backend_fs_new (const char *seaf_dir, const char *obj_type);\n\nstruct SeafObjStore *\nseaf_obj_store_new (SeafileSession *seaf, const char *obj_type)\n{\n    SeafObjStore *store = g_new0 (SeafObjStore, 1);\n\n    if (!store)\n        return NULL;\n\n    store->bend = obj_backend_fs_new (seaf->seaf_dir, obj_type);\n    if (!store->bend) {\n        seaf_warning (\"[Object store] Failed to load backend.\\n\");\n        g_free (store);\n        return NULL;\n    }\n\n    return store;\n}\n\nint\nseaf_obj_store_init (SeafObjStore *obj_store,\n                     gboolean enable_async,\n                     CEventManager *ev_mgr)\n{\n    return 0;\n}\n\nint\nseaf_obj_store_read_obj (struct SeafObjStore *obj_store,\n                         const char *repo_id,\n                         int version,\n                         const char *obj_id,\n                         void **data,\n                         int *len)\n{\n    ObjBackend *bend = obj_store->bend;\n\n    if (!repo_id || !is_uuid_valid(repo_id) ||\n        !obj_id || !is_object_id_valid(obj_id))\n        return -1;\n\n    return bend->read (bend, repo_id, version, obj_id, data, len);\n}\n\nint\nseaf_obj_store_write_obj (struct SeafObjStore *obj_store,\n                          const char *repo_id,\n                          int version,\n                          const char *obj_id,\n                          void *data,\n                          int len,\n                          gboolean need_sync)\n{\n    ObjBackend *bend = obj_store->bend;\n\n    if (!repo_id || !is_uuid_valid(repo_id) ||\n        !obj_id || !is_object_id_valid(obj_id))\n        return -1;\n\n    return bend->write (bend, repo_id, version, obj_id, data, len, need_sync);\n}\n\ngboolean\nseaf_obj_store_obj_exists (struct SeafObjStore *obj_store,\n                           const char *repo_id,\n                           int version,\n                           const char *obj_id)\n{\n    ObjBackend *bend = obj_store->bend;\n\n    if (!repo_id || !is_uuid_valid(repo_id) ||\n        !obj_id || !is_object_id_valid(obj_id))\n        return FALSE;\n\n    return bend->exists (bend, repo_id, version, obj_id);\n}\n\nvoid\nseaf_obj_store_delete_obj (struct SeafObjStore *obj_store,\n                           const char *repo_id,\n                           int version,\n                           const char *obj_id)\n{\n    ObjBackend *bend = obj_store->bend;\n\n    if (!repo_id || !is_uuid_valid(repo_id) ||\n        !obj_id || !is_object_id_valid(obj_id))\n        return;\n\n    bend->delete (bend, repo_id, version, obj_id);\n}\n\nint\nseaf_obj_store_foreach_obj (struct SeafObjStore *obj_store,\n                            const char *repo_id,\n                            int version,\n                            SeafObjFunc process,\n                            void *user_data)\n{\n    ObjBackend *bend = obj_store->bend;\n\n    return bend->foreach_obj (bend, repo_id, version, process, user_data);\n}\n\nint\nseaf_obj_store_copy_obj (struct SeafObjStore *obj_store,\n                         const char *src_repo_id,\n                         int src_version,\n                         const char *dst_repo_id,\n                         int dst_version,\n                         const char *obj_id)\n{\n    ObjBackend *bend = obj_store->bend;\n\n    if (strcmp (obj_id, EMPTY_SHA1) == 0)\n        return 0;\n\n    return bend->copy (bend, src_repo_id, src_version, dst_repo_id, dst_version, obj_id);\n}\n\nint\nseaf_obj_store_remove_store (struct SeafObjStore *obj_store,\n                             const char *store_id)\n{\n    ObjBackend *bend = obj_store->bend;\n\n    return bend->remove_store (bend, store_id);\n}\n"
  },
  {
    "path": "common/obj-store.h",
    "content": "#ifndef OBJ_STORE_H\n#define OBJ_STORE_H\n\n#include <glib.h>\n#include <sys/types.h>\n\nstruct _SeafileSession;\nstruct SeafObjStore;\nstruct CEventManager;\n\nstruct SeafObjStore *\nseaf_obj_store_new (struct _SeafileSession *seaf, const char *obj_type);\n\nint\nseaf_obj_store_init (struct SeafObjStore *obj_store,\n                     gboolean enable_async,\n                     struct CEventManager *ev_mgr);\n\n/* Synchronous I/O interface. */\n\nint\nseaf_obj_store_read_obj (struct SeafObjStore *obj_store,\n                         const char *repo_id,\n                         int version,\n                         const char *obj_id,\n                         void **data,\n                         int *len);\n\nint\nseaf_obj_store_write_obj (struct SeafObjStore *obj_store,\n                          const char *repo_id,\n                          int version,\n                          const char *obj_id,\n                          void *data,\n                          int len,\n                          gboolean need_sync);\n\ngboolean\nseaf_obj_store_obj_exists (struct SeafObjStore *obj_store,\n                           const char *repo_id,\n                           int version,\n                           const char *obj_id);\n\nvoid\nseaf_obj_store_delete_obj (struct SeafObjStore *obj_store,\n                           const char *repo_id,\n                           int version,\n                           const char *obj_id);\n\ntypedef gboolean (*SeafObjFunc) (const char *repo_id,\n                                 int version,\n                                 const char *obj_id,\n                                 void *user_data);\n\nint\nseaf_obj_store_foreach_obj (struct SeafObjStore *obj_store,\n                            const char *repo_id,\n                            int version,\n                            SeafObjFunc process,\n                            void *user_data);\n\nint\nseaf_obj_store_copy_obj (struct SeafObjStore *obj_store,\n                         const char *src_store_id,\n                         int src_version,\n                         const char *dst_store_id,\n                         int dst_version,\n                         const char *obj_id);\n\nint\nseaf_obj_store_remove_store (struct SeafObjStore *obj_store,\n                             const char *store_id);\n#endif\n"
  },
  {
    "path": "common/password-hash.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#include <string.h>\n#include <glib.h>\n#include <argon2.h>\n#include \"password-hash.h\"\n#include \"seafile-crypt.h\"\n#ifdef USE_GPL_CRYPTO\n#include <gnutls/gnutls.h>\n#include <gnutls/crypto.h>\n#include <nettle/pbkdf2.h>\n#else\n#include <openssl/aes.h>\n#include <openssl/evp.h>\n#include <openssl/rand.h>\n#endif\n\n#include \"utils.h\"\n#include \"log.h\"\n\n// pbkdf2\ntypedef struct Pbkdf2Params {\n    int iteration;\n} Pbkdf2Params;\n\nstatic Pbkdf2Params *\nparse_pbkdf2_sha256_params (const char *params_str)\n{\n    Pbkdf2Params *params = NULL;\n    if (!params_str) {\n        params = g_new0 (Pbkdf2Params, 1);\n        params->iteration = 1000;\n        return params;\n    }\n    int iteration;\n    iteration = atoi (params_str);\n    if (iteration <= 0) {\n        iteration = 1000;\n    }\n\n    params = g_new0 (Pbkdf2Params, 1);\n    params->iteration = iteration;\n    return params;\n}\n\nstatic int\npbkdf2_sha256_derive_key (const char *data_in, int in_len,\n                          const char *salt,\n                          Pbkdf2Params *params,\n                          unsigned char *key)\n{\n    int iteration = params->iteration;\n\n    unsigned char salt_bin[32] = {0};\n    hex_to_rawdata (salt, salt_bin, 32);\n\n#ifdef USE_GPL_CRYPTO\n    pbkdf2_hmac_sha256 (in_len, (const guchar *)data_in, iteration,\n                        sizeof(salt_bin), salt_bin, 32, key);\n#else\n    PKCS5_PBKDF2_HMAC (data_in, in_len,\n                       salt_bin, sizeof(salt_bin),\n                       iteration,\n                       EVP_sha256(),\n                       32, key);\n#endif\n    return 0;\n}\n\n// argon2id\ntypedef struct Argon2idParams{\n    gint64 time_cost; \n    gint64 memory_cost;\n    gint64 parallelism;\n} Argon2idParams;\n\n// The arguments to argon2 are separated by commas.\n// Example arguments format:\n// 2,102400,8\n// The parameters are time_cost, memory_cost, parallelism from left to right.\nstatic Argon2idParams *\nparse_argon2id_params (const char *params_str)\n{\n    char **params;\n    Argon2idParams *argon2_params = g_new0 (Argon2idParams, 1);\n    if (params_str)\n        params = g_strsplit (params_str, \",\", 3);\n    if (!params_str || g_strv_length(params) != 3) {\n        if (params_str)\n            g_strfreev (params);\n        argon2_params->time_cost = 2; // 2-pass computation\n        argon2_params->memory_cost = 102400; // 100 mebibytes memory usage\n        argon2_params->parallelism = 8; // number of threads and lanes\n        return argon2_params;\n    }\n\n    char *p = NULL;\n    p = g_strstrip (params[0]);\n    argon2_params->time_cost = atoll (p);\n    if (argon2_params->time_cost <= 0) {\n        argon2_params->time_cost = 2;\n    }\n\n    p = g_strstrip (params[1]);\n    argon2_params->memory_cost = atoll (p);\n    if (argon2_params->memory_cost <= 0) {\n        argon2_params->memory_cost = 102400;\n    }\n\n    p = g_strstrip (params[2]);\n    argon2_params->parallelism = atoll (p);\n    if (argon2_params->parallelism <= 0) {\n        argon2_params->parallelism = 8;\n    }\n\n    g_strfreev (params);\n    return argon2_params;\n}\n\nstatic int\nargon2id_derive_key (const char *data_in, int in_len,\n                     const char *salt,\n                     Argon2idParams *params,\n                     unsigned char *key)\n{\n    unsigned char salt_bin[32] = {0};\n    hex_to_rawdata (salt, salt_bin, 32);\n\n    argon2id_hash_raw(params->time_cost, params->memory_cost, params->parallelism,\n                      data_in, in_len,\n                      salt_bin, sizeof(salt_bin),\n                      key, 32);\n\n    return 0;\n}\n\n// parse_pwd_hash_params is used to parse default pwd hash algorithms.\nvoid\nparse_pwd_hash_params (const char *algo, const char *params_str, PwdHashParams *params)\n{\n    if (g_strcmp0 (algo, PWD_HASH_PDKDF2) == 0) {\n        params->algo = g_strdup (PWD_HASH_PDKDF2);\n        if (params_str)\n            params->params_str = g_strdup (params_str);\n        else\n            params->params_str = g_strdup (\"1000\");\n    } else if (g_strcmp0 (algo, PWD_HASH_ARGON2ID) == 0) {\n        params->algo = g_strdup (PWD_HASH_ARGON2ID);\n        if (params_str)\n            params->params_str = g_strdup (params_str);\n        else\n            params->params_str = g_strdup (\"2,102400,8\");\n    } else {\n        params->algo = NULL;\n    }\n\n    seaf_message (\"password hash algorithms: %s, params: %s\\n \", params->algo, params->params_str);\n}\n\nint\npwd_hash_derive_key (const char *data_in, int in_len,\n                     const char *salt,\n                     const char *algo, const char *params_str,\n                     unsigned char *key)\n{\n    int ret = 0;\n    if (g_strcmp0 (algo, PWD_HASH_ARGON2ID) == 0) {\n        Argon2idParams *algo_params = parse_argon2id_params (params_str);\n        ret = argon2id_derive_key (data_in, in_len,\n                                   salt, algo_params, key);\n        g_free (algo_params);\n        return ret;\n    } else {\n        Pbkdf2Params *algo_params = parse_pbkdf2_sha256_params (params_str);\n        ret = pbkdf2_sha256_derive_key (data_in, in_len,\n                                        salt, algo_params, key);\n        g_free (algo_params);\n        return ret;\n    }\n}\n"
  },
  {
    "path": "common/password-hash.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef _PASSWORD_HASH_H\n#define _PASSWORD_HASH_H\n\n#define PWD_HASH_PDKDF2 \"pbkdf2_sha256\"\n#define PWD_HASH_ARGON2ID \"argon2id\"\n\ntypedef struct _PwdHashParams {\n    char *algo;\n    char *params_str;\n} PwdHashParams;\n\nvoid\nparse_pwd_hash_params (const char *algo, const char *params_str, PwdHashParams *params);\n\nint\npwd_hash_derive_key (const char *data_in, int in_len,\n                     const char *repo_salt,\n                     const char *algo, const char *params_str,\n                     unsigned char *key);\n\n#endif\n"
  },
  {
    "path": "common/rpc-service.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n#include <glib/gstdio.h>\n#include <ctype.h>\n\n#include <sys/stat.h>\n#ifndef WIN32\n#include <dirent.h>\n#endif\n#include \"utils.h\"\n\n#include \"seafile-session.h\"\n#include \"fs-mgr.h\"\n#include \"repo-mgr.h\"\n#include \"seafile-error.h\"\n#include \"seafile-rpc.h\"\n#include \"common/mq-mgr.h\"\n#include \"seafile-config.h\"\n#include \"seafile-object.h\"\n#include \"seafile-error-impl.h\"\n#include \"password-hash.h\"\n#define DEBUG_FLAG SEAFILE_DEBUG_OTHER\n#include \"log.h\"\n\n#include \"../daemon/vc-utils.h\"\n\n\n/* -------- Utilities -------- */\nstatic GObject*\nconvert_repo (SeafRepo *r)\n{\n    SeafileRepo *repo = NULL;\n\n    if (r->head == NULL)\n        return NULL;\n\n    if (r->worktree_invalid && !seafile_session_config_get_allow_invalid_worktree(seaf))\n        return NULL;\n\n    repo = seafile_repo_new ();\n    if (!repo)\n        return NULL;\n\n    g_object_set (repo, \"id\", r->id, \"name\", r->name,\n                  \"desc\", r->desc, \"encrypted\", r->encrypted,\n                  \"magic\", r->magic, \"enc_version\", r->enc_version,\n                  \"head_cmmt_id\", r->head ? r->head->commit_id : NULL,\n                  \"root\", r->root_id,\n                  \"version\", r->version, \"last_modify\", (int)r->last_modify,\n                  NULL);\n    g_object_set (repo,\n                  \"repo_id\", r->id, \"repo_name\", r->name,\n                  \"repo_desc\", r->desc, \"last_modified\", (int)r->last_modify,\n                  NULL);\n\n    g_object_set (repo, \"worktree\", r->worktree,\n                  \"relay-id\", r->relay_id,\n                  \"worktree-invalid\", r->worktree_invalid,\n                  \"last-sync-time\", r->last_sync_time,\n                  \"auto-sync\", r->auto_sync,\n                  NULL);\n\n    return (GObject *)repo;\n}\n\nstatic void\nfree_repo_obj (gpointer repo)\n{\n    if (!repo)\n        return;\n    g_object_unref ((GObject *)repo);\n}\n\nstatic GList *\nconvert_repo_list (GList *inner_repos)\n{\n    GList *ret = NULL, *ptr;\n    GObject *repo = NULL;\n\n    for (ptr = inner_repos; ptr; ptr=ptr->next) {\n        SeafRepo *r = ptr->data;\n        repo = convert_repo (r);\n        if (!repo) {\n            g_list_free_full (ret, free_repo_obj);\n            return NULL;\n        }\n\n        ret = g_list_prepend (ret, repo);\n    }\n\n    return g_list_reverse (ret);\n}\n\n/*\n * RPC functions only available for clients.\n */\n\n#include \"sync-mgr.h\"\n\nint\nseafile_set_config (const char *key, const char *value, GError **error)\n{\n    return seafile_session_config_set_string(seaf, key, value);\n}\n\nchar *\nseafile_get_config (const char *key, GError **error)\n{\n    return seafile_session_config_get_string(seaf, key);\n}\n\nint\nseafile_set_config_int (const char *key, int value, GError **error)\n{\n    return seafile_session_config_set_int(seaf, key, value);\n}\n\nint\nseafile_get_config_int (const char *key, GError **error)\n{\n    gboolean exists = TRUE;\n\n    int ret = seafile_session_config_get_int(seaf, key, &exists);\n\n    if (!exists) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, \"Config not exists\");\n        return -1;\n    }\n\n    return ret;\n}\n\nint\nseafile_set_upload_rate_limit (int limit, GError **error)\n{\n    if (limit < 0)\n        limit = 0;\n\n    seaf->sync_mgr->upload_limit = limit;\n\n    return seafile_session_config_set_int (seaf, KEY_UPLOAD_LIMIT, limit);\n}\n\nint\nseafile_set_download_rate_limit (int limit, GError **error)\n{\n    if (limit < 0)\n        limit = 0;\n\n    seaf->sync_mgr->download_limit = limit;\n\n    return seafile_session_config_set_int (seaf, KEY_DOWNLOAD_LIMIT, limit);\n}\n\nchar *\nseafile_gen_default_worktree (const char *worktree_parent,\n                              const char *repo_name,\n                              GError **error)\n{\n    if (!worktree_parent || !repo_name) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Empty args\");\n        return NULL;\n    }\n\n    return seaf_clone_manager_gen_default_worktree (seaf->clone_mgr,\n                                                    worktree_parent,\n                                                    repo_name);\n}\n\nint\nseafile_check_path_for_clone (const char *path, GError **error)\n{\n    if (!seaf_clone_manager_check_worktree_path(seaf->clone_mgr, path, error)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nchar *\nseafile_clone (const char *repo_id,\n               int repo_version,\n               const char *repo_name,\n               const char *worktree,\n               const char *token,\n               const char *passwd,\n               const char *magic,\n               const char *email,\n               const char *random_key,\n               int enc_version,\n               const char *more_info,\n               GError **error)\n{\n    if (!repo_id || strlen(repo_id) != 36) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Invalid repo id\");\n        return NULL;\n    }\n\n    if (!worktree) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                     \"Worktre must be specified\");\n        return NULL;\n    }\n\n    if (!token || !email ) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                     \"Argument can't be NULL\");\n        return NULL;\n    }\n\n    return seaf_clone_manager_add_task (seaf->clone_mgr,\n                                        repo_id, repo_version,\n                                        repo_name, token,\n                                        passwd, magic,\n                                        enc_version,\n                                        random_key,\n                                        worktree,\n                                        email, more_info,\n                                        error);\n}\n\nchar *\nseafile_download (const char *repo_id,\n                  int repo_version,\n                  const char *repo_name,\n                  const char *wt_parent,\n                  const char *token,\n                  const char *passwd,\n                  const char *magic,\n                  const char *email,\n                  const char *random_key,\n                  int enc_version,\n                  const char *more_info,\n                  GError **error)\n{\n    if (!repo_id || strlen(repo_id) != 36) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Invalid repo id\");\n        return NULL;\n    }\n\n    if (!wt_parent) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                     \"Worktre must be specified\");\n        return NULL;\n    }\n\n    if (!token || !email ) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                     \"Argument can't be NULL\");\n        return NULL;\n    }\n\n    return seaf_clone_manager_add_download_task (seaf->clone_mgr,\n                                                 repo_id, repo_version,\n                                                 repo_name, token,\n                                                 passwd, magic,\n                                                 enc_version, random_key,\n                                                 wt_parent,\n                                                 email, more_info,\n                                                 error);\n}\n\nint\nseafile_cancel_clone_task (const char *repo_id, GError **error)\n{\n    return seaf_clone_manager_cancel_task (seaf->clone_mgr, repo_id);\n}\n\nGList *\nseafile_get_clone_tasks (GError **error)\n{\n    GList *tasks, *ptr;\n    GList *ret = NULL;\n    CloneTask *task;\n    SeafileCloneTask *t;\n\n    tasks = seaf_clone_manager_get_tasks (seaf->clone_mgr);\n    for (ptr = tasks; ptr != NULL; ptr = ptr->next) {\n        task = ptr->data;\n        t = g_object_new (SEAFILE_TYPE_CLONE_TASK,\n                          \"state\", clone_task_state_to_str(task->state),\n                          \"error\", task->error,\n                          \"repo_id\", task->repo_id,\n                          \"repo_name\", task->repo_name,\n                          \"worktree\", task->worktree,\n                          NULL);\n        ret = g_list_prepend (ret, t);\n    }\n\n    g_list_free (tasks);\n    return ret;\n}\n\nint\nseafile_sync (const char *repo_id, const char *peer_id, GError **error)\n{\n    if (!repo_id) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Repo ID should not be null\");\n        return -1;\n    }\n\n    return seaf_sync_manager_add_sync_task (seaf->sync_mgr, repo_id, error);\n}\n\nstatic SeafileTask *\nconvert_http_task (HttpTxTask *task)\n{\n    SeafileTask *t = seafile_task_new();\n\n    g_object_set (t,\n                  \"repo_id\", task->repo_id,\n                  \"state\", http_task_state_to_str(task->state),\n                  \"rt_state\", http_task_rt_state_to_str(task->runtime_state),\n                  NULL);\n\n    if (task->type == HTTP_TASK_TYPE_DOWNLOAD) {\n        g_object_set (t, \"ttype\", \"download\", NULL);\n        if (task->runtime_state == HTTP_TASK_RT_STATE_BLOCK) {\n            g_object_set (t, \"block_total\", task->total_download,\n                          \"block_done\", task->done_download,\n                          NULL);\n            g_object_set (t, \"rate\", http_tx_task_get_rate(task), NULL);\n        } else if (task->runtime_state == HTTP_TASK_RT_STATE_FS) {\n            g_object_set (t, \"fs_objects_total\", task->n_fs_objs,\n                          \"fs_objects_done\", task->done_fs_objs,\n                          NULL);\n        }\n    } else {\n        g_object_set (t, \"ttype\", \"upload\", NULL);\n        if (task->runtime_state == HTTP_TASK_RT_STATE_BLOCK) {\n            SyncInfo *info = seaf_sync_manager_get_sync_info (seaf->sync_mgr, task->repo_id);\n            if (info && info->multipart_upload) {\n                g_object_set (t, \"block_total\", info->total_bytes,\n                              \"block_done\", info->uploaded_bytes,\n                              NULL);\n            } else {\n                g_object_set (t, \"block_total\", (gint64)task->n_blocks,\n                              \"block_done\", (gint64)task->done_blocks,\n                              NULL);\n            }\n            g_object_set (t, \"rate\", http_tx_task_get_rate(task), NULL);\n        }\n    }\n\n    return t;\n}\n\nGObject *\nseafile_find_transfer_task (const char *repo_id, GError *error)\n{\n    HttpTxTask *http_task;\n\n    http_task = http_tx_manager_find_task (seaf->http_tx_mgr, repo_id);\n    if (http_task)\n        return (GObject *)convert_http_task (http_task);\n\n    return NULL;\n}\n\nint\nseafile_get_upload_rate(GError **error)\n{\n    return seaf->sync_mgr->last_sent_bytes;\n}\n\nint\nseafile_get_download_rate(GError **error)\n{\n    return seaf->sync_mgr->last_recv_bytes;\n}\n\nGObject *\nseafile_get_repo_sync_task (const char *repo_id, GError **error)\n{\n    SeafRepo *repo;\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n\n    if (!repo) {\n        return NULL;\n    }\n\n    if (!repo->token) {\n        SeafileSyncTask *s_task;\n        s_task = g_object_new (SEAFILE_TYPE_SYNC_TASK,\n                               \"force_upload\", FALSE,\n                               \"state\", \"error\",\n                               \"error\", SYNC_ERROR_ID_STOPPED_BY_LOGOUT,\n                               \"repo_id\", repo_id,\n                               NULL);\n\n        return (GObject *)s_task;\n    }\n\n    if (repo->empty_enc_key) {\n        SeafileSyncTask *s_task;\n        s_task = g_object_new (SEAFILE_TYPE_SYNC_TASK,\n                               \"force_upload\", FALSE,\n                               \"state\", \"error\",\n                               \"error\", SYNC_ERROR_ID_CORRUPTED_ENC_KEY,\n                               \"repo_id\", repo_id,\n                               NULL);\n\n        return (GObject *)s_task;\n    }\n\n    SyncInfo *info = seaf_sync_manager_get_sync_info (seaf->sync_mgr, repo_id);\n    if (!info || !info->current_task)\n        return NULL;\n\n    SyncTask *task = info->current_task;\n    const char *sync_state;\n    char allzeros[41] = {0};\n\n    if (!info->in_sync && memcmp(allzeros, info->head_commit, 41) == 0) {\n        sync_state = \"waiting for sync\";\n    } else {\n        sync_state = sync_state_to_str(task->state);\n    }\n\n    SeafileSyncTask *s_task;\n    s_task = g_object_new (SEAFILE_TYPE_SYNC_TASK,\n                           \"force_upload\", task->is_manual_sync,\n                           \"state\", sync_state,\n                           \"error\", task->error,\n                           \"repo_id\", info->repo_id,\n                           NULL);\n\n    return (GObject *)s_task;\n}\n\nint\nseafile_set_repo_property (const char *repo_id,\n                           const char *key,\n                           const char *value,\n                           GError **error)\n{\n    int ret;\n\n    if (repo_id == NULL || key == NULL || value == NULL) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Arguments should not be empty\");\n        return -1;\n    }\n\n    SeafRepo *repo;\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_REPO, \"Can't find Repo %s\", repo_id);\n        return -1;\n    }\n\n    ret = seaf_repo_manager_set_repo_property (seaf->repo_mgr,\n                                               repo->id, key, value);\n    if (ret < 0) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL,\n                     \"Failed to set key for repo %s\", repo_id);\n        return -1;\n    }\n\n    return 0;\n}\n\ngchar *\nseafile_get_repo_property (const char *repo_id,\n                           const char *key,\n                           GError **error)\n{\n    char *value = NULL;\n\n    if (!repo_id || !key) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Arguments should not be empty\");\n        return NULL;\n    }\n\n    SeafRepo *repo;\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_REPO, \"Can't find Repo %s\", repo_id);\n        return NULL;\n    }\n\n    value = seaf_repo_manager_get_repo_property (seaf->repo_mgr, repo->id, key);\n    return value;\n}\n\nint\nseafile_update_repos_server_host (const char *old_server_url,\n                                  const char *new_server_url,\n                                  GError **error)\n{\n    if (!old_server_url || !new_server_url) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Argument should not be null\");\n        return -1;\n    }\n\n    return seaf_repo_manager_update_repos_server_host(\n        seaf->repo_mgr, old_server_url, new_server_url);\n}\n\nint\nseafile_calc_dir_size (const char *path, GError **error)\n{\n    if (!path) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Argument should not be null\");\n        return -1;\n    }\n\n    gint64 size_64 = ccnet_calc_directory_size(path, error);\n    if (size_64 < 0) {\n        seaf_warning (\"failed to calculate dir size for %s\\n\", path);\n        return -1;\n    }\n\n    /* get the size in MB */\n    int size = (int) (size_64 >> 20);\n    return size;\n}\n\nint\nseafile_disable_auto_sync (GError **error)\n{\n    return seaf_sync_manager_disable_auto_sync (seaf->sync_mgr);\n}\n\nint\nseafile_enable_auto_sync (GError **error)\n{\n    return seaf_sync_manager_enable_auto_sync (seaf->sync_mgr);\n}\n\nint seafile_is_auto_sync_enabled (GError **error)\n{\n    return seaf_sync_manager_is_auto_sync_enabled (seaf->sync_mgr);\n}\n\nchar *\nseafile_get_path_sync_status (const char *repo_id,\n                              const char *path,\n                              int is_dir,\n                              GError **error)\n{\n    char *canon_path = NULL;\n    int len;\n    char *status;\n\n    if (!repo_id || !path) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Argument should not be null\");\n        return NULL;\n    }\n\n    /* Empty path means to get status of the worktree folder. */\n    if (strcmp (path, \"\") != 0) {\n        if (*path == '/')\n            ++path;\n        canon_path = g_strdup(path);\n        len = strlen(canon_path);\n        if (canon_path[len-1] == '/')\n            canon_path[len-1] = 0;\n    } else {\n        canon_path = g_strdup(path);\n    }\n\n    status = seaf_sync_manager_get_path_sync_status (seaf->sync_mgr,\n                                                     repo_id,\n                                                     canon_path,\n                                                     is_dir);\n    g_free (canon_path);\n    return status;\n}\n\nint\nseafile_mark_file_locked (const char *repo_id, const char *path, GError **error)\n{\n    char *canon_path = NULL;\n    int len;\n    int ret;\n\n    if (!repo_id || !path) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Argument should not be null\");\n        return -1;\n    }\n\n    if (*path == '/')\n        ++path;\n\n    if (path[0] == 0) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Invalid path\");\n        return -1;\n    }\n\n    canon_path = g_strdup(path);\n    len = strlen(canon_path);\n    if (canon_path[len-1] == '/')\n        canon_path[len-1] = 0;\n\n    ret = seaf_filelock_manager_mark_file_locked (seaf->filelock_mgr,\n                                                  repo_id, path, LOCKED_MANUAL);\n\n    g_free (canon_path);\n    return ret;\n}\n\nint\nseafile_mark_file_unlocked (const char *repo_id, const char *path, GError **error)\n{\n    char *canon_path = NULL;\n    int len;\n    int ret;\n\n    if (!repo_id || !path) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Argument should not be null\");\n        return -1;\n    }\n\n    if (*path == '/')\n        ++path;\n\n    if (path[0] == 0) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Invalid path\");\n        return -1;\n    }\n\n    canon_path = g_strdup(path);\n    len = strlen(canon_path);\n    if (canon_path[len-1] == '/')\n        canon_path[len-1] = 0;\n\n    ret = seaf_filelock_manager_mark_file_unlocked (seaf->filelock_mgr,\n                                                    repo_id, path);\n\n    g_free (canon_path);\n    return ret;\n}\n\njson_t *\nseafile_get_sync_notification (GError **error)\n{\n    return seaf_mq_manager_pop_message (seaf->mq_mgr);\n}\n\nchar *\nseafile_get_server_property (const char *server_url, const char *key, GError **error)\n{\n    if (!server_url || !key) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                     \"Argument should not be null\");\n        return NULL;\n    }\n\n    return seaf_repo_manager_get_server_property (seaf->repo_mgr,\n                                                  server_url,\n                                                  key);\n}\n\nint\nseafile_set_server_property (const char *server_url,\n                             const char *key,\n                             const char *value,\n                             GError **error)\n{\n    if (!server_url || !key || !value) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                     \"Argument should not be null\");\n        return -1;\n    }\n\n    return seaf_repo_manager_set_server_property (seaf->repo_mgr,\n                                                  server_url,\n                                                  key, value);\n}\n\nGList *\nseafile_get_file_sync_errors (int offset, int limit, GError **error)\n{\n    return seaf_repo_manager_get_file_sync_errors (seaf->repo_mgr, offset, limit);\n}\n\nint\nseafile_del_file_sync_error_by_id (int id, GError **error)\n{\n    return seaf_repo_manager_del_file_sync_error_by_id (seaf->repo_mgr, id);\n}\n\nGList*\nseafile_get_repo_list (int start, int limit, GError **error)\n{\n    GList *repos = seaf_repo_manager_get_repo_list(seaf->repo_mgr, start, limit);\n    GList *ret = NULL;\n\n    ret = convert_repo_list (repos);\n\n    g_list_free (repos);\n\n    return ret;\n}\n\nGObject*\nseafile_get_repo (const char *repo_id, GError **error)\n{\n    SeafRepo *r;\n\n    if (!repo_id) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Argument should not be null\");\n        return NULL;\n    }\n    if (!is_uuid_valid (repo_id)) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Invalid repo id\");\n        return NULL;\n    }\n\n    r = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    /* Don't return repo that's not checked out. */\n    if (r == NULL)\n        return NULL;\n\n    GObject *repo = convert_repo (r);\n\n    return repo;\n}\n\nstatic\nint do_unsync_repo(SeafRepo *repo)\n{\n    if (!seaf->started) {\n        seaf_message (\"System not started, skip removing repo.\\n\");\n        return -1;\n    }\n\n    if (repo->auto_sync && (repo->sync_interval == 0))\n        seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id);\n\n    seaf_sync_manager_cancel_sync_task (seaf->sync_mgr, repo->id);\n\n    SyncInfo *info = seaf_sync_manager_get_sync_info (seaf->sync_mgr, repo->id);\n\n    /* If we are syncing the repo,\n     * we just mark the repo as deleted and let sync-mgr actually delete it.\n     * Otherwise we are safe to delete the repo.\n     */\n    char *worktree = g_strdup (repo->worktree);\n    if (info != NULL && info->in_sync) {\n        seaf_repo_manager_mark_repo_deleted (seaf->repo_mgr, repo);\n    } else {\n        seaf_repo_manager_del_repo (seaf->repo_mgr, repo);\n    }\n\n    g_free (worktree);\n\n    return 0;\n}\n\nstatic void\ncancel_clone_tasks_by_account (const char *account_server_url, const char *account_email)\n{\n    GList *ptr, *tasks;\n    CloneTask *task;\n\n    tasks = seaf_clone_manager_get_tasks (seaf->clone_mgr);\n    for (ptr = tasks; ptr != NULL; ptr = ptr->next) {\n        task = ptr->data;\n\n        if (g_strcmp0(account_server_url, task->server_url) == 0\n            && g_strcmp0(account_email, task->email) == 0) {\n            seaf_clone_manager_cancel_task (seaf->clone_mgr, task->repo_id);\n        }\n    }\n\n    g_list_free (tasks);\n}\n\nint\nseafile_unsync_repos_by_account (const char *server_url, const char *email, GError **error)\n{\n    if (!server_url || !email) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Argument should not be null\");\n        return -1;\n    }\n    char *canon_server_url = canonical_server_url (server_url);\n\n    GList *ptr, *repos = seaf_repo_manager_get_repo_list(seaf->repo_mgr, -1, -1);\n    if (!repos) {\n        return 0;\n    }\n\n    for (ptr = repos; ptr; ptr = ptr->next) {\n        SeafRepo *repo = (SeafRepo*)ptr->data;\n        if (g_strcmp0(repo->server_url, canon_server_url) == 0 && g_strcmp0(repo->email, email) == 0) {\n            if (do_unsync_repo(repo) < 0) {\n                return -1;\n            }\n        }\n    }\n\n    g_list_free (repos);\n\n    cancel_clone_tasks_by_account (canon_server_url, email);\n    g_free (canon_server_url);\n\n    return 0;\n}\n\nint\nseafile_remove_repo_tokens_by_account (const char *server_url, const char *email, GError **error)\n{\n    if (!server_url || !email) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Argument should not be null\");\n        return -1;\n    }\n    char *canon_server_url = canonical_server_url (server_url);\n\n    GList *ptr, *repos = seaf_repo_manager_get_repo_list(seaf->repo_mgr, -1, -1);\n    if (!repos) {\n        return 0;\n    }\n\n    for (ptr = repos; ptr; ptr = ptr->next) {\n        SeafRepo *repo = (SeafRepo*)ptr->data;\n        if (g_strcmp0(repo->server_url, canon_server_url) == 0 && g_strcmp0(repo->email, email) == 0) {\n            if (seaf_repo_manager_remove_repo_token(seaf->repo_mgr, repo) < 0) {\n                return -1;\n            }\n        }\n    }\n\n    g_list_free (repos);\n\n    cancel_clone_tasks_by_account (canon_server_url, email);\n    g_free (canon_server_url);\n\n    return 0;\n}\n\nint\nseafile_set_repo_token (const char *repo_id,\n                        const char *token,\n                        GError **error)\n{\n    int ret;\n\n    if (repo_id == NULL || token == NULL) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Arguments should not be empty\");\n        return -1;\n    }\n\n    SeafRepo *repo;\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_REPO, \"Can't find Repo %s\", repo_id);\n        return -1;\n    }\n\n    ret = seaf_repo_manager_set_repo_token (seaf->repo_mgr,\n                                            repo, token);\n    if (ret < 0) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL,\n                     \"Failed to set token for repo %s\", repo_id);\n        return -1;\n    }\n\n    return 0;\n}\n\nint\nseafile_destroy_repo (const char *repo_id, GError **error)\n{\n    if (!repo_id) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Argument should not be null\");\n        return -1;\n    }\n    if (!is_uuid_valid (repo_id)) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Invalid repo id\");\n        return -1;\n    }\n\n    SeafRepo *repo;\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"No such repository\");\n        return -1;\n    }\n\n    return do_unsync_repo(repo);\n}\n\n\nGObject *\nseafile_generate_magic_and_random_key(int enc_version,\n                                      const char* repo_id,\n                                      const char *passwd,\n                                      const char *pwd_hash_algo,\n                                      const char *pwd_hash_params,\n                                      GError **error)\n{\n    if (!repo_id || !passwd) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Argument should not be null\");\n        return NULL;\n    }\n\n    gchar salt[65] = {0};\n    gchar magic[65] = {0};\n    gchar pwd_hash[65] = {0};\n    gchar random_key[97] = {0};\n\n    if (enc_version >= 3 && seafile_generate_repo_salt (salt) < 0) {\n        return NULL;\n    }\n\n    if (g_strcmp0 (pwd_hash_algo, PWD_HASH_PDKDF2) == 0 ||\n        g_strcmp0 (pwd_hash_algo, PWD_HASH_ARGON2ID) == 0) {\n        seafile_generate_pwd_hash (enc_version, repo_id, passwd, salt, pwd_hash_algo, pwd_hash_params, pwd_hash);\n    } else {\n        seafile_generate_magic (enc_version, repo_id, passwd, salt, magic);\n    }\n    if (seafile_generate_random_key (passwd, enc_version, salt, random_key) < 0) {\n        return NULL;\n    }\n    \n    SeafileEncryptionInfo *sinfo;\n    sinfo = g_object_new (SEAFILE_TYPE_ENCRYPTION_INFO,\n                          \"repo_id\", repo_id,\n                          \"passwd\", passwd,\n                          \"enc_version\", enc_version,\n                          \"magic\", magic,\n                          \"pwd_hash\", pwd_hash,\n                          \"random_key\", random_key,\n                          NULL);\n\n    if (enc_version >= 3)\n        g_object_set (sinfo, \"salt\", salt, NULL);\n\n    return (GObject *)sinfo;\n\n}\n\n#include \"diff-simple.h\"\n\ninline static const char*\nget_diff_status_str(char status)\n{\n    if (status == DIFF_STATUS_ADDED)\n        return \"add\";\n    if (status == DIFF_STATUS_DELETED)\n        return \"del\";\n    if (status == DIFF_STATUS_MODIFIED)\n        return \"mod\";\n    if (status == DIFF_STATUS_RENAMED)\n        return \"mov\";\n    if (status == DIFF_STATUS_DIR_ADDED)\n        return \"newdir\";\n    if (status == DIFF_STATUS_DIR_DELETED)\n        return \"deldir\";\n    return NULL;\n}\n\nGList *\nseafile_diff (const char *repo_id, const char *arg1, const char *arg2, int fold_dir_diff, GError **error)\n{\n    SeafRepo *repo;\n    char *err_msgs = NULL;\n    GList *diff_entries, *p;\n    GList *ret = NULL;\n\n    if (!repo_id || !arg1 || !arg2) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Argument should not be null\");\n        return NULL;\n    }\n\n    if (!is_uuid_valid (repo_id)) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Invalid repo id\");\n        return NULL;\n    }\n\n    if ((arg1[0] != 0 && !is_object_id_valid (arg1)) || !is_object_id_valid(arg2)) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Invalid commit id\");\n        return NULL;\n    }\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"No such repository\");\n        return NULL;\n    }\n\n    diff_entries = seaf_repo_diff (repo, arg1, arg2, fold_dir_diff, &err_msgs);\n    if (err_msgs) {\n        g_set_error (error, SEAFILE_DOMAIN, -1, \"%s\", err_msgs);\n        g_free (err_msgs);\n        return NULL;\n    }\n\n    for (p = diff_entries; p != NULL; p = p->next) {\n        DiffEntry *de = p->data;\n        SeafileDiffEntry *entry = g_object_new (\n            SEAFILE_TYPE_DIFF_ENTRY,\n            \"status\", get_diff_status_str(de->status),\n            \"name\", de->name,\n            \"new_name\", de->new_name,\n            NULL);\n        ret = g_list_prepend (ret, entry);\n    }\n\n    for (p = diff_entries; p != NULL; p = p->next) {\n        DiffEntry *de = p->data;\n        diff_entry_free (de);\n    }\n    g_list_free (diff_entries);\n\n    return g_list_reverse (ret);\n}\n\nint\nseafile_shutdown (GError **error)\n{\n    seaf_warning (\"Got an exit command. Now exiting\\n\");\n    exit(0);\n    return 0;\n}\n\nchar*\nseafile_sync_error_id_to_str (int error_id, GError **error)\n{\n    return g_strdup(sync_error_id_to_str (error_id));\n}\n\nint\nseafile_add_del_confirmation (const char *confirmation_id, int resync, GError **error)\n{\n    return seaf_sync_manager_add_del_confirmation (seaf->sync_mgr, confirmation_id, resync);\n}\n"
  },
  {
    "path": "common/seafile-crypt.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#include <string.h>\n#include <glib.h>\n#include \"seafile-crypt.h\"\n#include \"password-hash.h\"\n\n#ifdef USE_GPL_CRYPTO\n#include <gnutls/gnutls.h>\n#include <gnutls/crypto.h>\n#include <nettle/pbkdf2.h>\n#else\n#include <openssl/aes.h>\n#include <openssl/evp.h>\n#include <openssl/rand.h>\n#endif\n\n#include \"utils.h\"\n#include \"log.h\"\n\n/*\n  The EVP_EncryptXXX and EVP_DecryptXXX series of functions have a\n  weird choice of returned value.\n*/\n#define ENC_SUCCESS 1\n#define ENC_FAILURE 0\n#define DEC_SUCCESS 1\n#define DEC_FAILURE 0\n\n#define KEYGEN_ITERATION 1 << 19\n#define KEYGEN_ITERATION2 1000\n/* Should generate random salt for each repo. */\nstatic unsigned char salt[8] = { 0xda, 0x90, 0x45, 0xc3, 0x06, 0xc7, 0xcc, 0x26 };\n\nSeafileCrypt *\nseafile_crypt_new (int version, unsigned char *key, unsigned char *iv)\n{\n    SeafileCrypt *crypt = g_new0 (SeafileCrypt, 1);\n    crypt->version = version;\n    if (version == 1)\n        memcpy (crypt->key, key, 16);\n    else\n        memcpy (crypt->key, key, 32);\n    memcpy (crypt->iv, iv, 16);\n    return crypt;\n}\n\nint\nseafile_derive_key (const char *data_in, int in_len, int version,\n                    const char *repo_salt,\n                    unsigned char *key, unsigned char *iv)\n{\n#ifdef USE_GPL_CRYPTO\n    unsigned char repo_salt_bin[32];\n\n    if (version == 4)\n        hex_to_rawdata (repo_salt, repo_salt_bin, 32);\n\n    switch (version) {\n    case 1:\n        seaf_warning (\"Encrypted library version %d is not supported.\\n\", version);\n        return -1;\n    case 2:\n        pbkdf2_hmac_sha256 (in_len, (const guchar *)data_in, KEYGEN_ITERATION2,\n                            sizeof(salt), salt, 32, key);\n        pbkdf2_hmac_sha256 (32, (const guchar *)key, 10, sizeof(salt), salt, 16, iv);\n        break;\n    case 3:\n        seaf_warning (\"Encrypted library version %d is not supported.\\n\", version);\n        return -1;\n    case 4:\n        pbkdf2_hmac_sha256 (in_len, (const guchar *)data_in, KEYGEN_ITERATION2,\n                            sizeof(repo_salt_bin), repo_salt_bin, 32, key);\n        pbkdf2_hmac_sha256 (32, (const guchar *)key, 10, sizeof(repo_salt_bin), repo_salt_bin, 16, iv);\n        break;\n    default:\n        seaf_warning (\"Encrypted library version %d is not supported.\\n\", version);\n        return -1;\n    }\n\n    return 0;\n#else\n    if (version >= 3) {\n        unsigned char repo_salt_bin[32];\n\n        hex_to_rawdata (repo_salt, repo_salt_bin, 32);\n\n        PKCS5_PBKDF2_HMAC (data_in, in_len,\n                           repo_salt_bin, sizeof(repo_salt_bin),\n                           KEYGEN_ITERATION2,\n                           EVP_sha256(),\n                           32, key);\n        PKCS5_PBKDF2_HMAC ((char *)key, 32,\n                           repo_salt_bin, sizeof(repo_salt_bin),\n                           10,\n                           EVP_sha256(),\n                           16, iv);\n        return 0;\n    } else if (version == 2) {\n        PKCS5_PBKDF2_HMAC (data_in, in_len,\n                           salt, sizeof(salt),\n                           KEYGEN_ITERATION2,\n                           EVP_sha256(),\n                           32, key);\n        PKCS5_PBKDF2_HMAC ((char *)key, 32,\n                           salt, sizeof(salt),\n                           10,\n                           EVP_sha256(),\n                           16, iv);\n        return 0;\n    } else if (version == 1) {\n        EVP_BytesToKey (EVP_aes_128_cbc(), /* cipher mode */\n                        EVP_sha1(),        /* message digest */\n                        salt,              /* salt */\n                        (unsigned char*)data_in,\n                        in_len,\n                        KEYGEN_ITERATION,   /* iteration times */\n                        key, /* the derived key */\n                        iv); /* IV, initial vector */\n        return 0;\n    } else {\n        EVP_BytesToKey (EVP_aes_128_ecb(), /* cipher mode */\n                        EVP_sha1(),        /* message digest */\n                        NULL,              /* salt */\n                        (unsigned char*)data_in,\n                        in_len,\n                        3,   /* iteration times */\n                        key, /* the derived key */\n                        iv); /* IV, initial vector */\n        return 0;\n    }\n#endif\n}\n\nint\nseafile_generate_repo_salt (char *repo_salt)\n{\n    unsigned char repo_salt_bin[32];\n\n#ifdef USE_GPL_CRYPTO\n    int rc = gnutls_rnd (GNUTLS_RND_RANDOM, repo_salt_bin, sizeof(repo_salt_bin));\n    if (rc != 0) {\n        seaf_warning (\"Failed to generate salt for repo encryption.\\n\");\n        return -1;\n    }\n#else\n    int rc = RAND_bytes (repo_salt_bin, sizeof(repo_salt_bin));\n    if (rc != 1) {\n        seaf_warning (\"Failed to generate salt for repo encryption.\\n\");\n        return -1;\n    }\n#endif\n\n    rawdata_to_hex (repo_salt_bin, repo_salt, 32);\n\n    return 0;\n}\n\nint\nseafile_generate_random_key (const char *passwd,\n                             int version,\n                             const char *repo_salt,\n                             char *random_key)\n{\n    SeafileCrypt *crypt;\n    unsigned char secret_key[32], *rand_key;\n    int outlen;\n    unsigned char key[32], iv[16];\n\n#ifdef USE_GPL_CRYPTO\n    if (gnutls_rnd (GNUTLS_RND_RANDOM, secret_key, sizeof(secret_key)) < 0) {\n        seaf_warning (\"Failed to generate secret key for repo encryption.\\n\");\n        return -1;\n    }\n#else\n    if (RAND_bytes (secret_key, sizeof(secret_key)) != 1) {\n        seaf_warning (\"Failed to generate secret key for repo encryption.\\n\");\n        return -1;\n    }\n#endif\n\n    seafile_derive_key (passwd, strlen(passwd), version, repo_salt, key, iv);\n\n    crypt = seafile_crypt_new (version, key, iv);\n\n    seafile_encrypt ((char **)&rand_key, &outlen,\n                     (char *)secret_key, sizeof(secret_key), crypt);\n\n    rawdata_to_hex (rand_key, random_key, 48);\n\n    g_free (crypt);\n    g_free (rand_key);\n\n    return 0;\n}\n\nvoid\nseafile_generate_magic (int version, const char *repo_id,\n                        const char *passwd,\n                        const char *repo_salt,\n                        char *magic)\n{\n    GString *buf = g_string_new (NULL);\n    unsigned char key[32], iv[16];\n\n    /* Compute a \"magic\" string from repo_id and passwd.\n     * This is used to verify the password given by user before decrypting\n     * data.\n     */\n    g_string_append_printf (buf, \"%s%s\", repo_id, passwd);\n\n    seafile_derive_key (buf->str, buf->len, version, repo_salt, key, iv);\n\n    g_string_free (buf, TRUE);\n    rawdata_to_hex (key, magic, 32);\n}\n\nvoid\nseafile_generate_pwd_hash (int version,\n                           const char *repo_id,\n                           const char *passwd,\n                           const char *repo_salt,\n                           const char *algo,\n                           const char *params_str,\n                           char *pwd_hash)\n{\n    GString *buf = g_string_new (NULL);\n    unsigned char key[32];\n\n    /* Compute a \"pwd_hash\" string from repo_id and passwd.\n    * This is used to verify the password given by user before decrypting\n    * data.\n    */\n    g_string_append_printf (buf, \"%s%s\", repo_id, passwd);\n\n    if (version <= 2) {\n        // use fixed repo salt\n        char fixed_salt[64] = {0};\n        rawdata_to_hex(salt, fixed_salt, 8);\n        pwd_hash_derive_key (buf->str, buf->len, fixed_salt, algo, params_str, key);\n    } else {\n        pwd_hash_derive_key (buf->str, buf->len, repo_salt, algo, params_str, key);\n    }\n\n    g_string_free (buf, TRUE);\n    rawdata_to_hex (key, pwd_hash, 32);\n}\n\nint\nseafile_verify_repo_passwd (const char *repo_id,\n                            const char *passwd,\n                            const char *magic,\n                            int version,\n                            const char *repo_salt)\n{\n    GString *buf = g_string_new (NULL);\n    unsigned char key[32], iv[16];\n    char hex[65];\n\n    if (version != 1 && version != 2 && version != 3 && version != 4) {\n        seaf_warning (\"Unsupported enc_version %d.\\n\", version);\n        return -1;\n    }\n\n    /* Recompute the magic and compare it with the one comes with the repo. */\n    g_string_append_printf (buf, \"%s%s\", repo_id, passwd);\n\n    seafile_derive_key (buf->str, buf->len, version, repo_salt, key, iv);\n\n    g_string_free (buf, TRUE);\n\n    if (version >= 2)\n        rawdata_to_hex (key, hex, 32);\n    else\n        rawdata_to_hex (key, hex, 16);\n\n    if (g_strcmp0 (hex, magic) == 0)\n        return 0;\n    else\n        return -1;\n}\n\nint\nseafile_pwd_hash_verify_repo_passwd (int version,\n                                     const char *repo_id,\n                                     const char *passwd,\n                                     const char *repo_salt,\n                                     const char *pwd_hash,\n                                     const char *algo,\n                                     const char *params_str)\n{\n    GString *buf = g_string_new (NULL);\n    unsigned char key[32];\n    char hex[65];\n\n    g_string_append_printf (buf, \"%s%s\", repo_id, passwd);\n\n    if (version <= 2) {\n        // use fixed repo salt\n        char fixed_salt[64] = {0};\n        rawdata_to_hex(salt, fixed_salt, 8);\n        pwd_hash_derive_key (buf->str, buf->len, fixed_salt, algo, params_str, key);\n    } else {\n        pwd_hash_derive_key (buf->str, buf->len, repo_salt, algo, params_str, key);\n    }\n\n    g_string_free (buf, TRUE);\n    rawdata_to_hex (key, hex, 32);\n    \n    if (g_strcmp0 (hex, pwd_hash) == 0)\n        return 0;\n    else\n        return -1;\n}\n\nint\nseafile_decrypt_repo_enc_key (int enc_version,\n                              const char *passwd, const char *random_key,\n                              const char *repo_salt,\n                              unsigned char *key_out, unsigned char *iv_out)\n{\n    unsigned char key[32], iv[16];\n\n    seafile_derive_key (passwd, strlen(passwd), enc_version, repo_salt, key, iv);\n\n    if (enc_version == 1) {\n        memcpy (key_out, key, 16);\n        memcpy (iv_out, iv, 16);\n        return 0;\n    } else if (enc_version >= 2) {\n        unsigned char enc_random_key[48], *dec_random_key;\n        int outlen;\n        SeafileCrypt *crypt;\n\n        if (random_key == NULL || random_key[0] == 0) {\n            seaf_warning (\"Empty random key.\\n\");\n            return -1;\n        }\n\n        hex_to_rawdata (random_key, enc_random_key, 48);\n\n        crypt = seafile_crypt_new (enc_version, key, iv);\n        if (seafile_decrypt ((char **)&dec_random_key, &outlen,\n                             (char *)enc_random_key, 48,\n                             crypt) < 0) {\n            seaf_warning (\"Failed to decrypt random key.\\n\");\n            g_free (crypt);\n            return -1;\n        }\n        g_free (crypt);\n\n        seafile_derive_key ((char *)dec_random_key, 32, enc_version,\n                            repo_salt,\n                            key, iv);\n        memcpy (key_out, key, 32);\n        memcpy (iv_out, iv, 16);\n\n        g_free (dec_random_key);\n        return 0;\n    }\n\n    return -1;\n}\n\nint\nseafile_update_random_key (const char *old_passwd, const char *old_random_key,\n                           const char *new_passwd, char *new_random_key,\n                           int enc_version, const char *repo_salt)\n{\n    unsigned char key[32], iv[16];\n    unsigned char random_key_raw[48], *secret_key, *new_random_key_raw;\n    int secret_key_len, random_key_len;\n    SeafileCrypt *crypt;\n\n    /* First, use old_passwd to decrypt secret key from old_random_key. */\n    seafile_derive_key (old_passwd, strlen(old_passwd), enc_version,\n                        repo_salt, key, iv);\n\n    hex_to_rawdata (old_random_key, random_key_raw, 48);\n\n    crypt = seafile_crypt_new (enc_version, key, iv);\n    if (seafile_decrypt ((char **)&secret_key, &secret_key_len,\n                         (char *)random_key_raw, 48,\n                         crypt) < 0) {\n        seaf_warning (\"Failed to decrypt random key.\\n\");\n        g_free (crypt);\n        return -1;\n    }\n    g_free (crypt);\n\n    /* Second, use new_passwd to encrypt secret key. */\n    seafile_derive_key (new_passwd, strlen(new_passwd), enc_version,\n                        repo_salt, key, iv);\n    crypt = seafile_crypt_new (enc_version, key, iv);\n\n    seafile_encrypt ((char **)&new_random_key_raw, &random_key_len,\n                     (char *)secret_key, secret_key_len, crypt);\n\n    rawdata_to_hex (new_random_key_raw, new_random_key, 48);\n\n    g_free (secret_key);\n    g_free (new_random_key_raw);\n    g_free (crypt);\n\n    return 0;\n}\n\n#ifdef USE_GPL_CRYPTO\n\nint\nseafile_encrypt (char **data_out,\n                 int *out_len,\n                 const char *data_in,\n                 const int in_len,\n                 SeafileCrypt *crypt)\n{\n    char *buf = NULL, *enc_buf = NULL;\n    int buf_size, remain;\n    guint8 padding;\n    gnutls_cipher_hd_t handle;\n    gnutls_datum_t key, iv;\n    int rc, ret = 0;\n\n    buf_size = BLK_SIZE * ((in_len / BLK_SIZE) + 1);\n    remain = buf_size - in_len;\n    buf = g_new (char, buf_size);\n\n    memcpy (buf, data_in, in_len);\n    padding = (guint8)remain;\n    memset (buf + in_len, padding, remain);\n\n    key.data = crypt->key;\n    key.size = sizeof(crypt->key);\n    iv.data = crypt->iv;\n    iv.size = sizeof(crypt->iv);\n\n    if (crypt->version == 1 || crypt->version == 3) {\n        seaf_warning (\"Encrypted library version % is not supported.\\n\", crypt->version);\n        ret = -1;\n        goto out;\n    } else {\n        rc = gnutls_cipher_init (&handle, GNUTLS_CIPHER_AES_256_CBC, &key, &iv);\n        if (rc < 0) {\n            seaf_warning (\"Failed to init cipher: %s\\n\", gnutls_strerror(rc));\n            ret = -1;\n            goto out;\n        }\n    }\n\n    enc_buf = g_new (char, buf_size);\n    rc = gnutls_cipher_encrypt2 (handle, buf, buf_size, enc_buf, buf_size);\n    if (rc < 0) {\n        seaf_warning (\"Failed to encrypt: %s\\n\", gnutls_strerror(rc));\n        ret = -1;\n        gnutls_cipher_deinit (handle);\n        goto out;\n    }\n\n    gnutls_cipher_deinit (handle);\n\nout:\n    g_free (buf);\n    if (ret < 0) {\n        g_free (enc_buf);\n        *data_out = NULL;\n        *out_len = -1;\n    } else {\n        *data_out = enc_buf;\n        *out_len = buf_size;\n    }\n    return ret;\n}\n\nint\nseafile_decrypt (char **data_out,\n                 int *out_len,\n                 const char *data_in,\n                 const int in_len,\n                 SeafileCrypt *crypt)\n{\n    char *dec_buf = NULL;\n    gnutls_cipher_hd_t handle;\n    gnutls_datum_t key, iv;\n    int rc, ret = 0;\n    guint8 padding;\n    int remain;\n\n    if (in_len <= 0 || in_len % BLK_SIZE != 0) {\n        seaf_warning (\"Invalid encrypted buffer size.\\n\");\n        return -1;\n    }\n\n    key.data = crypt->key;\n    key.size = sizeof(crypt->key);\n    iv.data = crypt->iv;\n    iv.size = sizeof(crypt->iv);\n\n    if (crypt->version == 1 || crypt->version == 3) {\n        seaf_warning (\"Encrypted library version %d is not supported.\\n\", crypt->version);\n        ret = -1;\n        goto out;\n    } else {\n        rc = gnutls_cipher_init (&handle, GNUTLS_CIPHER_AES_256_CBC, &key, &iv);\n        if (rc < 0) {\n            seaf_warning (\"Failed to init cipher: %s\\n\", gnutls_strerror(rc));\n            ret = -1;\n            goto out;\n        }\n    }\n\n    dec_buf = g_new (char, in_len);\n    rc = gnutls_cipher_decrypt2 (handle, data_in, in_len, dec_buf, in_len);\n    if (rc < 0) {\n        seaf_warning (\"Failed to decrypt data: %s\\n\", gnutls_strerror(rc));\n        ret = -1;\n        gnutls_cipher_deinit (handle);\n        goto out;\n    }\n\n    padding = dec_buf[in_len - 1];\n    remain = padding;\n    *out_len = (in_len - remain);\n    *data_out = dec_buf;\n\n    gnutls_cipher_deinit (handle);\nout:\n    if (ret < 0) {\n        g_free (dec_buf);\n        *data_out = NULL;\n        *out_len = -1;\n    }\n    return ret;\n}\n\n#else\n\nint\nseafile_encrypt (char **data_out,\n                 int *out_len,\n                 const char *data_in,\n                 const int in_len,\n                 SeafileCrypt *crypt)\n{\n    *data_out = NULL;\n    *out_len = -1;\n\n    /* check validation */\n    if ( data_in == NULL || in_len <= 0 || crypt == NULL) {\n        seaf_warning (\"Invalid params.\\n\");\n        return -1;\n    }\n\n    EVP_CIPHER_CTX *ctx;\n    int ret;\n    int blks;\n\n    /* Prepare CTX for encryption. */\n    ctx = EVP_CIPHER_CTX_new ();\n\n    if (crypt->version == 1)\n        ret = EVP_EncryptInit_ex (ctx,\n                                  EVP_aes_128_cbc(), /* cipher mode */\n                                  NULL, /* engine, NULL for default */\n                                  crypt->key,  /* derived key */\n                                  crypt->iv);  /* initial vector */\n    else if (crypt->version == 3)\n        ret = EVP_EncryptInit_ex (ctx,\n                                  EVP_aes_128_ecb(), /* cipher mode */\n                                  NULL, /* engine, NULL for default */\n                                  crypt->key,  /* derived key */\n                                  crypt->iv);  /* initial vector */\n    else\n        ret = EVP_EncryptInit_ex (ctx,\n                                  EVP_aes_256_cbc(), /* cipher mode */\n                                  NULL, /* engine, NULL for default */\n                                  crypt->key,  /* derived key */\n                                  crypt->iv);  /* initial vector */\n\n    if (ret == ENC_FAILURE) {\n        EVP_CIPHER_CTX_free (ctx);\n        return -1;\n    }\n\n    /* Allocating output buffer. */\n    \n    /*\n      For EVP symmetric encryption, padding is always used __even if__\n      data size is a multiple of block size, in which case the padding\n      length is the block size. so we have the following:\n    */\n    \n    blks = (in_len / BLK_SIZE) + 1;\n\n    *data_out = (char *)g_malloc (blks * BLK_SIZE);\n\n    if (*data_out == NULL) {\n        seaf_warning (\"failed to allocate the output buffer.\\n\");\n        goto enc_error;\n    }                \n\n    int update_len, final_len;\n\n    /* Do the encryption. */\n    ret = EVP_EncryptUpdate (ctx,\n                             (unsigned char*)*data_out,\n                             &update_len,\n                             (unsigned char*)data_in,\n                             in_len);\n\n    if (ret == ENC_FAILURE)\n        goto enc_error;\n\n\n    /* Finish the possible partial block. */\n    ret = EVP_EncryptFinal_ex (ctx,\n                               (unsigned char*)*data_out + update_len,\n                               &final_len);\n\n    *out_len = update_len + final_len;\n\n    /* out_len should be equal to the allocated buffer size. */\n    if (ret == ENC_FAILURE || *out_len != (blks * BLK_SIZE))\n        goto enc_error;\n    \n    EVP_CIPHER_CTX_free (ctx);\n\n    return 0;\n\nenc_error:\n\n    EVP_CIPHER_CTX_free (ctx);\n\n    *out_len = -1;\n\n    if (*data_out != NULL)\n        g_free (*data_out);\n\n    *data_out = NULL;\n\n    return -1;\n    \n}\n                               \nint\nseafile_decrypt (char **data_out,\n                 int *out_len,\n                 const char *data_in,\n                 const int in_len,\n                 SeafileCrypt *crypt)\n{\n    *data_out = NULL;\n    *out_len = -1;\n\n    /* Check validation. Because padding is always used, in_len must\n     * be a multiple of BLK_SIZE */\n    if ( data_in == NULL || in_len <= 0 || in_len % BLK_SIZE != 0 ||\n         crypt == NULL) {\n\n        seaf_warning (\"Invalid param(s).\\n\");\n        return -1;\n    }\n\n    EVP_CIPHER_CTX *ctx;\n    int ret;\n\n    /* Prepare CTX for decryption. */\n    ctx = EVP_CIPHER_CTX_new ();\n\n    if (crypt->version == 1)\n        ret = EVP_DecryptInit_ex (ctx,\n                                  EVP_aes_128_cbc(), /* cipher mode */\n                                  NULL, /* engine, NULL for default */\n                                  crypt->key,  /* derived key */\n                                  crypt->iv);  /* initial vector */\n    else if (crypt->version == 3)\n        ret = EVP_DecryptInit_ex (ctx,\n                                  EVP_aes_128_ecb(), /* cipher mode */\n                                  NULL, /* engine, NULL for default */\n                                  crypt->key,  /* derived key */\n                                  crypt->iv);  /* initial vector */\n    else\n        ret = EVP_DecryptInit_ex (ctx,\n                                  EVP_aes_256_cbc(), /* cipher mode */\n                                  NULL, /* engine, NULL for default */\n                                  crypt->key,  /* derived key */\n                                  crypt->iv);  /* initial vector */\n\n    if (ret == DEC_FAILURE) {\n        EVP_CIPHER_CTX_free (ctx);\n        return -1;\n    }\n\n    /* Allocating output buffer. */\n    \n    *data_out = (char *)g_malloc (in_len);\n\n    if (*data_out == NULL) {\n        seaf_warning (\"failed to allocate the output buffer.\\n\");\n        goto dec_error;\n    }                \n\n    int update_len, final_len;\n\n    /* Do the decryption. */\n    ret = EVP_DecryptUpdate (ctx,\n                             (unsigned char*)*data_out,\n                             &update_len,\n                             (unsigned char*)data_in,\n                             in_len);\n\n    if (ret == DEC_FAILURE)\n        goto dec_error;\n\n\n    /* Finish the possible partial block. */\n    ret = EVP_DecryptFinal_ex (ctx,\n                               (unsigned char*)*data_out + update_len,\n                               &final_len);\n\n    *out_len = update_len + final_len;\n\n    /* out_len should be smaller than in_len. */\n    if (ret == DEC_FAILURE || *out_len > in_len)\n        goto dec_error;\n\n    EVP_CIPHER_CTX_free (ctx);\n    \n    return 0;\n\ndec_error:\n\n    EVP_CIPHER_CTX_free (ctx);\n\n    *out_len = -1;\n    if (*data_out != NULL)\n        g_free (*data_out);\n\n    *data_out = NULL;\n\n    return -1;\n    \n}\n\n#endif  /* USE_GPL_CRYPTO */\n"
  },
  {
    "path": "common/seafile-crypt.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef _SEAFILE_CRYPT_H\n#define _SEAFILE_CRYPT_H\n\n/* Block size, in bytes. For AES it can only be 16 bytes. */\n#define BLK_SIZE 16\n#define ENCRYPT_BLK_SIZE BLK_SIZE\n\nstruct SeafileCrypt {\n    int version;\n    unsigned char key[32];   /* set when enc_version >= 1 */\n    unsigned char iv[16];\n};\n\ntypedef struct SeafileCrypt SeafileCrypt;\n\nSeafileCrypt *\nseafile_crypt_new (int version, unsigned char *key, unsigned char *iv);\n\n/*\n  Derive key and iv used by AES encryption from @data_in.\n  key and iv is 16 bytes for version 1, and 32 bytes for version 2.\n\n  @data_out: pointer to the output of the encrpyted/decrypted data,\n  whose content must be freed by g_free when not used.\n\n  @out_len: pointer to length of output, in bytes\n\n  @data_in: address of input buffer\n\n  @in_len: length of data to be encrpyted/decrypted, in bytes \n\n  @crypt: container of crypto info.\n  \n  RETURN VALUES:\n\n  On success, 0 is returned, and the encrpyted/decrypted data is in\n  *data_out, with out_len set to its length. On failure, -1 is returned\n  and *data_out is set to NULL, with out_len set to -1;\n*/\n\nint\nseafile_derive_key (const char *data_in, int in_len, int version,\n                    const char *repo_salt,\n                    unsigned char *key, unsigned char *iv);\n\n/* @salt must be an char array of size 65 bytes. */\nint\nseafile_generate_repo_salt (char *repo_salt);\n\n/*\n * Generate the real key used to encrypt data.\n * The key 32 bytes long and encrpted with @passwd.\n */\nint\nseafile_generate_random_key (const char *passwd,\n                             int version,\n                             const char *repo_salt,\n                             char *random_key);\n\nvoid\nseafile_generate_magic (int version, const char *repo_id,\n                        const char *repo_salt,\n                        const char *passwd,\n                        char *magic);\n\nvoid\nseafile_generate_pwd_hash (int version,\n                           const char *repo_id,\n                           const char *passwd,\n                           const char *repo_salt,\n                           const char *algo,\n                           const char *params_str,\n                           char *pwd_hash);\n\nint\nseafile_verify_repo_passwd (const char *repo_id,\n                            const char *passwd,\n                            const char *magic,\n                            int version,\n                            const char *repo_salt);\n\nint\nseafile_pwd_hash_verify_repo_passwd (int version,\n                                     const char *repo_id,\n                                     const char *passwd,\n                                     const char *repo_salt,\n                                     const char *pwd_hash,\n                                     const char *algo,\n                                     const char *params_str);\n\nint\nseafile_decrypt_repo_enc_key (int enc_version,\n                              const char *passwd, const char *random_key,\n                              const char *repo_salt,\n                              unsigned char *key_out, unsigned char *iv_out);\n\nint\nseafile_update_random_key (const char *old_passwd, const char *old_random_key,\n                           const char *new_passwd, char *new_random_key,\n                           int enc_version, const char *repo_salt);\n\nint\nseafile_encrypt (char **data_out,\n                 int *out_len,\n                 const char *data_in,\n                 const int in_len,\n                 SeafileCrypt *crypt);\n\n\nint\nseafile_decrypt (char **data_out,\n                 int *out_len,\n                 const char *data_in,\n                 const int in_len,\n                 SeafileCrypt *crypt);\n\n#endif  /* _SEAFILE_CRYPT_H */\n"
  },
  {
    "path": "common/vc-common.c",
    "content": "#include \"common.h\"\n\n#include \"seafile-session.h\"\n#include \"vc-common.h\"\n\n#include \"log.h\"\n#include \"seafile-error.h\"\n\nstatic GList *\nmerge_bases_many (SeafCommit *one, int n, SeafCommit **twos);\n\nstatic gint\ncompare_commit_by_time (gconstpointer a, gconstpointer b, gpointer unused)\n{\n    const SeafCommit *commit_a = a;\n    const SeafCommit *commit_b = b;\n\n    /* Latest commit comes first in the list. */\n    return (commit_b->ctime - commit_a->ctime);\n}\n\nstatic gint\ncompare_commit (gconstpointer a, gconstpointer b)\n{\n    const SeafCommit *commit_a = a;\n    const SeafCommit *commit_b = b;\n\n    return strcmp (commit_a->commit_id, commit_b->commit_id);\n}\n\nstatic gboolean\nadd_to_commit_hash (SeafCommit *commit, void *vhash, gboolean *stop)\n{\n    GHashTable *hash = vhash;\n\n    char *key = g_strdup (commit->commit_id);\n    g_hash_table_replace (hash, key, key);\n\n    return TRUE;\n}\n\nstatic GHashTable *\ncommit_tree_to_hash (SeafCommit *head)\n{\n    GHashTable *hash;\n    gboolean res;\n\n    hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);\n\n    res = seaf_commit_manager_traverse_commit_tree (seaf->commit_mgr,\n                                                    head->repo_id,\n                                                    head->version,\n                                                    head->commit_id,\n                                                    add_to_commit_hash,\n                                                    hash, FALSE);\n    if (!res)\n        goto fail;\n\n    return hash;\n\nfail:\n    g_hash_table_destroy (hash);\n    return NULL;\n}\n\nstatic GList *\nget_independent_commits (GList *commits)\n{\n    SeafCommit **rslt;\n    GList *list, *result;\n    int cnt, i, j;\n    SeafCommit *c;\n\n    g_debug (\"Get independent commits.\\n\");\n\n    cnt = g_list_length (commits);\n\n    rslt = calloc(cnt, sizeof(*rslt));\n    for (list = commits, i = 0; list; list = list->next)\n        rslt[i++] = list->data;\n    g_list_free (commits);\n\n    for (i = 0; i < cnt - 1; i++) {\n        for (j = i+1; j < cnt; j++) {\n            if (!rslt[i] || !rslt[j])\n                continue;\n            result = merge_bases_many(rslt[i], 1, &rslt[j]);\n            for (list = result; list; list = list->next) {\n                c = list->data;\n                /* If two commits have fast-forward relationship,\n                 * drop the older one.\n                 */\n                if (strcmp (rslt[i]->commit_id, c->commit_id) == 0) {\n                    seaf_commit_unref (rslt[i]);\n                    rslt[i] = NULL;\n                }\n                if (strcmp (rslt[j]->commit_id, c->commit_id) == 0) {\n                    seaf_commit_unref (rslt[j]);\n                    rslt[j] = NULL;\n                }\n                seaf_commit_unref (c);\n            }\n        }\n    }\n\n    /* Surviving ones in rslt[] are the independent results */\n    result = NULL;\n    for (i = 0; i < cnt; i++) {\n        if (rslt[i])\n            result = g_list_insert_sorted_with_data (result, rslt[i],\n                                                     compare_commit_by_time,\n                                                     NULL);\n    }\n    free(rslt);\n    return result;\n}\n\ntypedef struct {\n    GList *result;\n    GHashTable *commit_hash;\n} MergeTraverseData;\n\nstatic gboolean\nget_merge_bases (SeafCommit *commit, void *vdata, gboolean *stop)\n{\n    MergeTraverseData *data = vdata;\n\n    /* Found a common ancestor.\n     * Dont traverse its parenets.\n     */\n    if (g_hash_table_lookup (data->commit_hash, commit->commit_id)) {\n        if (!g_list_find_custom (data->result, commit, compare_commit)) {\n            data->result = g_list_insert_sorted_with_data (data->result, commit,\n                                                     compare_commit_by_time,\n                                                     NULL);\n            seaf_commit_ref (commit);\n        }\n        *stop = TRUE;\n    }\n\n    return TRUE;\n}\n\n/*\n * Merge \"one\" with commits in \"twos\".\n * The ancestors returned may not be ancestors for all the input commits.\n * They are common ancestors for one and some commits in twos array.\n */\nstatic GList *\nmerge_bases_many (SeafCommit *one, int n, SeafCommit **twos)\n{\n    GHashTable *commit_hash;\n    GList *result = NULL;\n    SeafCommit *commit;\n    int i;\n    MergeTraverseData data;\n    gboolean res;\n\n    for (i = 0; i < n; i++) {\n        if (one == twos[i])\n            return g_list_append (result, one);\n    }\n\n    /* First construct a hash table of all commit ids rooted at one. */\n    commit_hash = commit_tree_to_hash (one);\n    if (!commit_hash) {\n        g_warning (\"Failed to load commit hash.\\n\");\n        return NULL;\n    }\n\n    data.commit_hash = commit_hash;\n    data.result = NULL;\n\n    for (i = 0; i < n; i++) {\n        res = seaf_commit_manager_traverse_commit_tree (seaf->commit_mgr,\n                                                        twos[i]->repo_id,\n                                                        twos[i]->version,\n                                                        twos[i]->commit_id,\n                                                        get_merge_bases,\n                                                        &data, FALSE);\n        if (!res)\n            goto fail;\n    }\n\n    g_hash_table_destroy (commit_hash);\n    result = data.result;\n\n    if (!result || !result->next)\n        return result;\n\n    /* There are more than one. Try to find out independent ones. */\n    result = get_independent_commits (result);\n\n    return result;\n\nfail:\n    result = data.result;\n    while (result) {\n        commit = result->data;\n        seaf_commit_unref (commit);\n        result = g_list_delete_link (result, result);\n    }\n    g_hash_table_destroy (commit_hash);\n    return NULL;\n}\n\n/*\n * Returns common ancesstor for two branches.\n * Any two commits should have a common ancestor.\n * So returning NULL indicates an error, for e.g. corupt commit.\n */\nSeafCommit *\nget_merge_base (SeafCommit *head, SeafCommit *remote)\n{\n    GList *result, *iter;\n    SeafCommit *one, **twos;\n    int n, i;\n    SeafCommit *ret = NULL;\n\n    one = head;\n    twos = (SeafCommit **) calloc (1, sizeof(SeafCommit *));\n    twos[0] = remote;\n    n = 1;\n    result = merge_bases_many (one, n, twos);\n    free (twos);\n    if (!result || !result->next)\n        goto done;\n\n    /*\n     * More than one common ancestors.\n     * Loop until the oldest common ancestor is found.\n     */\n    while (1) {\n        n = g_list_length (result) - 1;\n        one = result->data;\n        twos = calloc (n, sizeof(SeafCommit *));\n        for (iter = result->next, i = 0; i < n; iter = iter->next, i++) {\n            twos[i] = iter->data;\n        }\n        g_list_free (result);\n\n        result = merge_bases_many (one, n, twos);\n        free (twos);\n        if (!result || !result->next)\n            break;\n    }\n\ndone:\n    if (result)\n        ret = result->data;\n    g_list_free (result);\n\n    return ret;\n}\n\n/*\n * Returns true if src_head is ahead of dst_head.\n */\ngboolean\nis_fast_forward (const char *repo_id, int version,\n                 const char *src_head, const char *dst_head)\n{\n    VCCompareResult res;\n\n    res = vc_compare_commits (repo_id, version, src_head, dst_head);\n\n    return (res == VC_FAST_FORWARD);\n}\n\nVCCompareResult\nvc_compare_commits (const char *repo_id, int version,\n                    const char *c1, const char *c2)\n{\n    SeafCommit *commit1, *commit2, *ca;\n    VCCompareResult ret;\n\n    /* Treat the same as up-to-date. */\n    if (strcmp (c1, c2) == 0)\n        return VC_UP_TO_DATE;\n\n    commit1 = seaf_commit_manager_get_commit (seaf->commit_mgr, repo_id, version, c1);\n    if (!commit1)\n        return VC_INDEPENDENT;\n\n    commit2 = seaf_commit_manager_get_commit (seaf->commit_mgr, repo_id, version, c2);\n    if (!commit2) {\n        seaf_commit_unref (commit1);\n        return VC_INDEPENDENT;\n    }\n\n    ca = get_merge_base (commit1, commit2);\n\n    if (!ca)\n        ret = VC_INDEPENDENT;\n    else if (strcmp(ca->commit_id, commit1->commit_id) == 0)\n        ret = VC_UP_TO_DATE;\n    else if (strcmp(ca->commit_id, commit2->commit_id) == 0)\n        ret = VC_FAST_FORWARD;\n    else\n        ret = VC_INDEPENDENT;\n\n    if (ca) seaf_commit_unref (ca);\n    seaf_commit_unref (commit1);\n    seaf_commit_unref (commit2);\n    return ret;\n}\n\n/**\n * Diff a specific file with parent(s).\n * If @commit is a merge, both parents will be compared.\n * @commit must have this file and it's id is given in @file_id.\n * \n * Returns 0 if there is no difference; 1 otherwise.\n * If returns 0, @parent will point to the next commit to traverse.\n * If I/O error occurs, @error will be set.\n */\nstatic int\ndiff_parents_with_path (SeafCommit *commit,\n                        const char *repo_id,\n                        const char *store_id,\n                        int version,\n                        const char *path,\n                        const char *file_id,\n                        char *parent,\n                        GError **error)\n{\n    SeafCommit *p1 = NULL, *p2 = NULL;\n    char *file_id_p1 = NULL, *file_id_p2 = NULL;\n    int ret = 0;\n\n    p1 = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                         commit->repo_id,\n                                         commit->version,\n                                         commit->parent_id);\n    if (!p1) {\n        g_warning (\"Failed to find commit %s.\\n\", commit->parent_id);\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, \" \");\n        return 0;\n    }\n\n    if (strcmp (p1->root_id, EMPTY_SHA1) == 0) {\n        seaf_commit_unref (p1);\n        return 1;\n    }\n\n    if (commit->second_parent_id) {\n        p2 = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                             commit->repo_id,\n                                             commit->version,\n                                             commit->second_parent_id);\n        if (!p2) {\n            g_warning (\"Failed to find commit %s.\\n\", commit->second_parent_id);\n            seaf_commit_unref (p1);\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, \" \");\n            return 0;\n        }\n    }\n\n    if (!p2) {\n        file_id_p1 = seaf_fs_manager_path_to_obj_id (seaf->fs_mgr,\n                                                     store_id,\n                                                     version,\n                                                     p1->root_id, path,\n                                                     NULL,\n                                                     error);\n        if (*error)\n            goto out;\n        if (!file_id_p1 || strcmp (file_id, file_id_p1) != 0)\n            ret = 1;\n        else\n            memcpy (parent, p1->commit_id, 41);\n    } else {\n        file_id_p1 = seaf_fs_manager_path_to_obj_id (seaf->fs_mgr,\n                                                     store_id,\n                                                     version,\n                                                     p1->root_id, path,\n                                                     NULL, error);\n        if (*error)\n            goto out;\n        file_id_p2 = seaf_fs_manager_path_to_obj_id (seaf->fs_mgr,\n                                                     store_id,\n                                                     version,\n                                                     p2->root_id, path,\n                                                     NULL, error);\n        if (*error)\n            goto out;\n\n        if (file_id_p1 && file_id_p2) {\n            if (strcmp(file_id, file_id_p1) != 0 &&\n                strcmp(file_id, file_id_p2) != 0)\n                ret = 1;\n            else if (strcmp(file_id, file_id_p1) == 0)\n                memcpy (parent, p1->commit_id, 41);\n            else\n                memcpy (parent, p2->commit_id, 41);\n        } else if (file_id_p1 && !file_id_p2) {\n            if (strcmp(file_id, file_id_p1) != 0)\n                ret = 1;\n            else\n                memcpy (parent, p1->commit_id, 41);\n        } else if (!file_id_p1 && file_id_p2) {\n            if (strcmp(file_id, file_id_p2) != 0)\n                ret = 1;\n            else\n                memcpy (parent, p2->commit_id, 41);\n        } else {\n            ret = 1;\n        }\n    }\n\nout:\n    g_free (file_id_p1);\n    g_free (file_id_p2);\n\n    if (p1)\n        seaf_commit_unref (p1);\n    if (p2)\n        seaf_commit_unref (p2);\n\n    return ret;\n}\n\nstatic int\nget_file_modifier_mtime_v0 (const char *repo_id, const char *store_id, int version,\n                            const char *head, const char *path,\n                            char **modifier, gint64 *mtime)\n{\n    char commit_id[41];\n    SeafCommit *commit = NULL;\n    char *file_id = NULL;\n    int changed;\n    int ret = 0;\n    GError *error = NULL;\n\n    *modifier = NULL;\n    *mtime = 0;\n\n    memcpy (commit_id, head, 41);\n\n    while (1) {\n        commit = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                                 repo_id, version,\n                                                 commit_id);\n        if (!commit) {\n            ret = -1;\n            break;\n        }\n\n        /* We hit the initial commit. */\n        if (!commit->parent_id)\n            break;\n\n        file_id = seaf_fs_manager_path_to_obj_id (seaf->fs_mgr,\n                                                  store_id, version,\n                                                  commit->root_id,\n                                                  path,\n                                                  NULL,\n                                                  &error);\n        if (error) {\n            g_clear_error (&error);\n            ret = -1;\n            break;\n        }\n        /* We expect commit to have this file. */\n        if (!file_id) {\n            ret = -1;\n            break;\n        }\n\n        changed = diff_parents_with_path (commit,\n                                          repo_id, store_id, version,\n                                          path, file_id,\n                                          commit_id, &error);\n        if (error) {\n            g_clear_error (&error);\n            ret = -1;\n            break;\n        }\n\n        if (changed) {\n            *modifier = g_strdup (commit->creator_name);\n            *mtime = commit->ctime;\n            break;\n        } else {\n            /* If this commit doesn't change the file, commit_id will be set\n             * to the parent commit to traverse.\n             */\n            g_free (file_id);\n            seaf_commit_unref (commit);\n        }\n    }\n\n    g_free (file_id);\n    if (commit)\n        seaf_commit_unref (commit);\n    return ret;\n}\n\nstatic int\nget_file_modifier_mtime_v1 (const char *repo_id, const char *store_id, int version,\n                            const char *head, const char *path,\n                            char **modifier, gint64 *mtime)\n{\n    SeafCommit *commit = NULL;\n    SeafDir *dir = NULL;\n    SeafDirent *dent = NULL;\n    int ret = 0;\n\n    commit = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                             repo_id, version,\n                                             head);\n    if (!commit) {\n        seaf_warning (\"Failed to get commit %s.\\n\", head);\n        return -1;\n    }\n\n    char *parent = g_path_get_dirname (path);\n    if (strcmp(parent, \".\") == 0) {\n        g_free (parent);\n        parent = g_strdup(\"\");\n    }\n    char *filename = g_path_get_basename (path);\n\n    dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,\n                                               store_id, version,\n                                               commit->root_id,\n                                               parent, NULL);\n    if (!dir) {\n        seaf_warning (\"dir %s doesn't exist in repo %s.\\n\", parent, repo_id);\n        ret = -1;\n        goto out;\n    }\n\n    GList *p;\n    for (p = dir->entries; p; p = p->next) {\n        SeafDirent *d = p->data;\n        if (strcmp (d->name, filename) == 0) {\n            dent = d;\n            break;\n        }\n    }\n\n    if (!dent) {\n        goto out;\n    }\n\n    *modifier = g_strdup(dent->modifier);\n    *mtime = dent->mtime;\n\nout:\n    g_free (parent);\n    g_free (filename);\n    seaf_commit_unref (commit);\n    seaf_dir_free (dir);\n\n    return ret;\n}\n\n/**\n * Get the user who last changed a file and the mtime.\n * @head: head commit to start the search.\n * @path: path of the file.\n */\nint\nget_file_modifier_mtime (const char *repo_id,\n                         const char *store_id,\n                         int version,\n                         const char *head,\n                         const char *path,\n                         char **modifier,\n                         gint64 *mtime)\n{\n    if (version > 0)\n        return get_file_modifier_mtime_v1 (repo_id, store_id, version,\n                                           head, path,\n                                           modifier, mtime);\n    else\n        return get_file_modifier_mtime_v0 (repo_id, store_id, version,\n                                           head, path,\n                                           modifier, mtime);\n}\n\nchar *\ngen_conflict_path (const char *origin_path,\n                   const char *modifier,\n                   gint64 mtime)\n{\n    char time_buf[64];\n    time_t t = (time_t)mtime;\n    char *copy = g_strdup (origin_path);\n    GString *conflict_path = g_string_new (NULL);\n    char *dot, *ext;\n\n    strftime(time_buf, 64, \"%Y-%m-%d-%H-%M-%S\", localtime(&t));\n\n    dot = strrchr (copy, '.');\n\n    if (dot != NULL) {\n        *dot = '\\0';\n        ext = dot + 1;\n        if (modifier)\n            g_string_printf (conflict_path, \"%s (SFConflict %s %s).%s\",\n                             copy, modifier, time_buf, ext);\n        else\n            g_string_printf (conflict_path, \"%s (SFConflict %s).%s\",\n                             copy, time_buf, ext);\n    } else {\n        if (modifier)\n            g_string_printf (conflict_path, \"%s (SFConflict %s %s)\",\n                             copy, modifier, time_buf);\n        else\n            g_string_printf (conflict_path, \"%s (SFConflict %s)\",\n                             copy, time_buf);\n    }\n\n    g_free (copy);\n    return g_string_free (conflict_path, FALSE);\n}\n\nchar *\ngen_conflict_path_wrapper (const char *repo_id, int version,\n                           const char *head, const char *in_repo_path,\n                           const char *original_path)\n{\n    char *modifier;\n    gint64 mtime;\n\n    /* XXX: this function is only used in client, so store_id is always\n     * the same as repo_id. This can be changed if it's also called in\n     * server.\n     */\n    if (get_file_modifier_mtime (repo_id, repo_id, version, head, in_repo_path,\n                                 &modifier, &mtime) < 0)\n        return NULL;\n\n    return gen_conflict_path (original_path, modifier, mtime);\n}\n"
  },
  {
    "path": "common/vc-common.h",
    "content": "#ifndef VC_COMMON_H\n#define VC_COMMON_H\n\n#include \"commit-mgr.h\"\n\nSeafCommit *\nget_merge_base (SeafCommit *head, SeafCommit *remote);\n\n/*\n * Returns true if src_head is ahead of dst_head.\n */\ngboolean\nis_fast_forward (const char *repo_id, int version,\n                 const char *src_head, const char *dst_head);\n\ntypedef enum {\n    VC_UP_TO_DATE,\n    VC_FAST_FORWARD,\n    VC_INDEPENDENT,\n} VCCompareResult;\n\n/*\n * Compares commits c1 and c2 as if we were going to merge c1 into c2.\n * \n * Returns:\n * VC_UP_TO_DATE: if c2 is ahead of c1, or c1 == c2;\n * VC_FAST_FORWARD: if c1 is ahead of c2;\n * VC_INDEPENDENT: if c1 and c2 has no inheritent relationship.\n * Returns VC_INDEPENDENT if c1 or c2 doesn't exist.\n */\nVCCompareResult\nvc_compare_commits (const char *repo_id, int version,\n                    const char *c1, const char *c2);\n\nchar *\ngen_conflict_path (const char *original_path,\n                   const char *modifier,\n                   gint64 mtime);\n\nint\nget_file_modifier_mtime (const char *repo_id, const char *store_id, int version,\n                         const char *head, const char *path,\n                         char **modifier, gint64 *mtime);\n\n/* Wrapper around the above two functions */\nchar *\ngen_conflict_path_wrapper (const char *repo_id, int version,\n                           const char *head, const char *in_repo_path,\n                           const char *original_path);\n\n#endif\n"
  },
  {
    "path": "configure.ac",
    "content": "dnl Process this file with autoconf to produce a configure script.\n\n\nAC_PREREQ(2.61)\nAC_INIT([seafile], [9.0.16], [info@seafile.com])\nAC_CONFIG_HEADER([config.h])\n\nAC_CONFIG_MACRO_DIR([m4])\n\nAM_INIT_AUTOMAKE([1.9 foreign])\n\n#AC_MINGW32\nAC_CANONICAL_BUILD\n\ndnl enable the build of share library by default\nAC_ENABLE_SHARED\n\nAC_SUBST(LIBTOOL_DEPS)\n\n# Checks for programs.\nAC_PROG_CC\n#AM_C_PROTOTYPES\nAC_C_CONST\nAC_PROG_MAKE_SET\n# AC_PROG_RANLIB\nLT_INIT\n\nAM_PROG_VALAC([], [], [AC_MSG_ERROR([*** Unable to find Vala compiler])])\n\n# Checks for headers.\n#AC_CHECK_HEADERS([arpa/inet.h fcntl.h inttypes.h libintl.h limits.h locale.h netdb.h netinet/in.h stdint.h stdlib.h string.h strings.h sys/ioctl.h sys/socket.h sys/time.h termios.h unistd.h utime.h utmp.h])\n\n# Checks for typedefs, structures, and compiler characteristics.\nAC_SYS_LARGEFILE\n\n# Checks for library functions.\n#AC_CHECK_FUNCS([alarm dup2 ftruncate getcwd gethostbyname gettimeofday memmove memset mkdir rmdir select setlocale socket strcasecmp strchr strdup strrchr strstr strtol uname utime strtok_r sendfile])\n\n# check platform\nAC_MSG_CHECKING(for WIN32)\nif test \"$build_os\" = \"mingw32\" -o \"$build_os\" = \"mingw64\"; then\n  bwin32=true\n  AC_MSG_RESULT(compile in mingw)\nelse\n  AC_MSG_RESULT(no)\nfi\n\nAC_MSG_CHECKING(for Mac)\nif test \"$(uname)\" = \"Darwin\"; then\n  bmac=true\n  AC_MSG_RESULT(compile in mac)\nelse\n  AC_MSG_RESULT(no)\nfi\n\nAC_MSG_CHECKING(for Linux)\nif test \"$bmac\" != \"true\" -a \"$bwin32\" != \"true\"; then\n  blinux=true\n  AC_MSG_RESULT(compile in linux)\nelse\n  AC_MSG_RESULT(no)\nfi\n\nAM_CONDITIONAL([WIN32], [test \"$bwin32\" = \"true\"])\nAM_CONDITIONAL([MACOS], [test \"$bmac\" = \"true\"])\nAM_CONDITIONAL([LINUX], [test \"$blinux\" = \"true\"])\n\n\n# check libraries\nif test \"$bwin32\" != true; then\n  if test \"$bmac\" = true; then\n  AC_CHECK_LIB(c, uuid_generate, [echo \"found library uuid\"],\n          AC_MSG_ERROR([*** Unable to find uuid_generate in libc]), )\n  else\n  AC_CHECK_LIB(uuid, uuid_generate, [echo \"found library uuid\"],\n          AC_MSG_ERROR([*** Unable to find uuid library]), )\n  fi\nfi\n\nAC_CHECK_LIB(pthread, pthread_create, [echo \"found library pthread\"], AC_MSG_ERROR([*** Unable to find pthread library]), )\nAC_CHECK_LIB(sqlite3, sqlite3_open,[echo \"found library sqlite3\"] , AC_MSG_ERROR([*** Unable to find sqlite3 library]), )\n\ndnl Do we need to use AX_LIB_SQLITE3 to check sqlite?\ndnl AX_LIB_SQLITE3\n\nCONSOLE=\nif test \"$bwin32\" = \"true\"; then\n  AC_ARG_ENABLE(console, AC_HELP_STRING([--enable-console], [enable console]),\n      [console=$enableval],[console=\"yes\"])\n  if test x${console} != xyes ; then\n    CONSOLE=\"-Wl,--subsystem,windows -Wl,--entry,_mainCRTStartup\"\n  fi\nfi\nAC_SUBST(CONSOLE)\n\nif test \"$bwin32\" = true; then\n  LIB_WS32=-lws2_32\n  LIB_GDI32=-lgdi32\n  LIB_RT=\n  LIB_INTL=-lintl\n  LIBS=\n  LIB_RESOLV=\n  LIB_UUID=-lRpcrt4\n  LIB_IPHLPAPI=-liphlpapi\n  LIB_SHELL32=-lshell32\n  LIB_PSAPI=-lpsapi\n  LIB_MAC=\n  MSVC_CFLAGS=\"-D__MSVCRT__ -D__MSVCRT_VERSION__=0x0601\"\n  LIB_CRYPT32=-lcrypt32\nelif test \"$bmac\" = true ; then\n  LIB_WS32=\n  LIB_GDI32=\n  LIB_RT=\n  LIB_INTL=\n  LIB_RESOLV=-lresolv\n  LIB_UUID=\n  LIB_IPHLPAPI=\n  LIB_SHELL32=\n  LIB_PSAPI=\n  MSVC_CFLAGS=\n  LIB_MAC=\"-framework CoreServices\"\n  LIB_CRYPT32=\n  LIB_ICONV=-liconv\nelse\n  LIB_WS32=\n  LIB_GDI32=\n  LIB_RT=\n  LIB_INTL=\n  LIB_RESOLV=-lresolv\n  LIB_UUID=-luuid\n  LIB_IPHLPAPI=\n  LIB_SHELL32=\n  LIB_PSAPI=\n  LIB_MAC=\n  MSVC_CFLAGS=\n  LIB_CRYPT32=\nfi\n\nAC_SUBST(LIB_WS32)\nAC_SUBST(LIB_GDI32)\nAC_SUBST(LIB_RT)\nAC_SUBST(LIB_INTL)\nAC_SUBST(LIB_RESOLV)\nAC_SUBST(LIB_UUID)\nAC_SUBST(LIB_IPHLPAPI)\nAC_SUBST(LIB_SHELL32)\nAC_SUBST(LIB_PSAPI)\nAC_SUBST(LIB_MAC)\nAC_SUBST(MSVC_CFLAGS)\nAC_SUBST(LIB_CRYPT32)\nAC_SUBST(LIB_ICONV)\n\n\nLIBEVENT_REQUIRED=2.0\nLIBEVENT_PTHREADS_REQUIRED=2.0\nGLIB_REQUIRED=2.16.0\nSEARPC_REQUIRED=1.0\nJANSSON_REQUIRED=2.2.1\nCURL_REQUIRED=7.17\nZLIB_REQUIRED=1.2.0\nGNUTLS_REQUIRED=3.3.0\nWS_REQUIRED=4.0.20\n\nPKG_CHECK_MODULES(GLIB2, [glib-2.0 >= $GLIB_REQUIRED])\nAC_SUBST(GLIB2_CFLAGS)\nAC_SUBST(GLIB2_LIBS)\n\nPKG_CHECK_MODULES(GOBJECT, [gobject-2.0 >= $GLIB_REQUIRED])\nAC_SUBST(GOBJECT_CFLAGS)\nAC_SUBST(GOBJECT_LIBS)\n\nPKG_CHECK_MODULES(SEARPC, [libsearpc >= $SEARPC_REQUIRED])\nAC_SUBST(SEARPC_CFLAGS)\nAC_SUBST(SEARPC_LIBS)\n\nPKG_CHECK_MODULES(JANSSON, [jansson >= $JANSSON_REQUIRED])\nAC_SUBST(JANSSON_CFLAGS)\nAC_SUBST(JANSSON_LIBS)\n\nPKG_CHECK_MODULES(LIBEVENT, [libevent >= $LIBEVENT_REQUIRED])\nAC_SUBST(LIBEVENT_CFLAGS)\nAC_SUBST(LIBEVENT_LIBS)\n\nPKG_CHECK_MODULES(ZLIB, [zlib >= $ZLIB_REQUIRED])\nAC_SUBST(ZLIB_CFLAGS)\nAC_SUBST(ZLIB_LIBS)\n\nPKG_CHECK_MODULES(CURL, [libcurl >= $CURL_REQUIRED])\nAC_SUBST(CURL_CFLAGS)\nAC_SUBST(CURL_LIBS)\n\nPKG_CHECK_MODULES(ARGON2, [libargon2])\nAC_SUBST(ARGON2_CFLAGS)\nAC_SUBST(ARGON2_LIBS)\n\nAC_ARG_ENABLE(ws, AC_HELP_STRING([--enable-ws], [enable build websockets]),\n  [compile_linux_ws=$enableval],[compile_linux_ws=\"yes\"])\nAM_CONDITIONAL([COMPILE_LINUX_WS], [test \"${compile_linux_ws}\" = \"yes\"])\nif test \"${compile_linux_ws}\" = \"yes\"; then\n   PKG_CHECK_MODULES(WS, [libwebsockets >= $WS_REQUIRED])\n   AC_DEFINE(COMPILE_LINUX_WS, 1, [compile linux websockets])\n   AC_SUBST(WS_CFLAGS)\n   AC_SUBST(WS_LIBS)\nfi\n\nif test \"$bwin32\" != true; then\n# do not check libevent_pthreads in win32\nPKG_CHECK_MODULES(LIBEVENT_PTHREADS, [libevent_pthreads >= $LIBEVENT_PTHREADS_REQUIRED])\nAC_SUBST(LIBEVENT_PTHREADS_CFLAGS)\nAC_SUBST(LIBEVENT_PTHREADS_LIBS)\nfi\n\nAC_ARG_WITH([python3], [AS_HELP_STRING([--with-python3], [use python3])],\n\t[with_python3=\"yes\"],[])\n\nif test \"$with_python3\" = \"yes\"; then\n   AM_PATH_PYTHON([3.5])\nelse\n   AM_PATH_PYTHON([2.7])\nfi\n\nif test \"$bwin32\" = true; then\n    # set pyexecdir to somewhere like /c/Python26/Lib/site-packages\n    pyexecdir=${PYTHON_DIR}/Lib/site-packages\n    pythondir=${pyexecdir}\n    pkgpyexecdir=${pyexecdir}/${PACKAGE}\n    pkgpythondir=${pythondir}/${PACKAGE}\n\nfi # end for bwin32\n\n\nBPWRAPPER_REQUIRED=0.1\nAC_ARG_ENABLE(breakpad, AC_HELP_STRING([--enable-breakpad], [build google breadpad support]),\n                               [compile_breakpad=$enableval],[compile_breakpad=\"no\"])\n\nAM_CONDITIONAL([HAVE_BREAKPAD_SUPPORT], [test \"${compile_breakpad}\" = \"yes\"])\nif test \"${compile_breakpad}\" = \"yes\"; then\n   PKG_CHECK_MODULES(BPWRAPPER, [bpwrapper])\n   AC_DEFINE(HAVE_BREAKPAD_SUPPORT, 1, [Breakpad support enabled])\n   AC_SUBST(BPWRAPPER_CFLAGS)\n   AC_SUBST(BPWRAPPER_LIBS)\nfi\n\nAC_ARG_WITH([gpl-crypto],\n            AS_HELP_STRING([--with-gpl-crypto=[yes|no]],\n                [Use GPL compatible crypto libraries. Default no.]),\n            [ gpl_crypto=$with_gpl_crypto ],\n            [ gpl_crypto=\"no\"])\nif test \"xyes\" = \"x$gpl_crypto\"; then\n   PKG_CHECK_MODULES(GNUTLS, [gnutls >= $GNUTLS_REQUIRED])\n   AC_SUBST(GNUTLS_CFLAGS)\n   AC_SUBST(GNUTLS_LIBS)\n\n   PKG_CHECK_MODULES(NETTLE, [nettle])\n   AC_SUBST(NETTLE_CFLAGS)\n   AC_SUBST(NETTLE_LIBS)\n\n   AC_DEFINE(USE_GPL_CRYPTO, 1, [Use GPL-compatible crypto libraries])\nelse\n   AC_CHECK_LIB(crypto, SHA1_Init, [echo \"found library crypto\"], AC_MSG_ERROR([*** Unable to find openssl crypto library]), )\n\n   PKG_CHECK_MODULES(SSL, [openssl])\n   AC_SUBST(SSL_CFLAGS)\n   AC_SUBST(SSL_LIBS)\nfi\n\n# option: compile-universal\n# default: no\nAC_ARG_ENABLE([compile-universal],\n[AS_HELP_STRING([--enable-compile-universal],\n[compile seafile universal @<:@default: no@:>@])],\n[compile_universal=${enableval}], [compile_demo=no])\n\nAM_CONDITIONAL([COMPILE_UNIVERSAL], [test x${compile_universal} = xyes])\n\nac_configure_args=\"$ac_configure_args -q\"\n\nAC_CONFIG_FILES(\n    Makefile\n    include/Makefile\n    lib/Makefile\n    lib/libseafile.pc\n    common/Makefile\n    common/cdc/Makefile\n    common/index/Makefile\n    daemon/Makefile\n    app/Makefile\n    doc/Makefile\n    python/Makefile\n    python/seafile/Makefile\n)\n\nAC_OUTPUT\n"
  },
  {
    "path": "daemon/Makefile.am",
    "content": "\nAM_CFLAGS = -DPKGDATADIR=\\\"$(pkgdatadir)\\\" \\\n\t-DPACKAGE_DATA_DIR=\\\"\"$(pkgdatadir)\"\\\" \\\n\t-DSEAFILE_CLIENT \\\n\t-D__USE_MINGW_ANSI_STDIO=1 \\\n\t-I$(top_srcdir)/include \\\n\t-I$(top_srcdir)/lib \\\n\t-I$(top_builddir)/lib \\\n\t-I$(top_srcdir)/common \\\n\t@SEARPC_CFLAGS@ \\\n\t@GLIB2_CFLAGS@ \\\n\t@MSVC_CFLAGS@ \\\n\t@CURL_CFLAGS@ \\\n\t@BPWRAPPER_CFLAGS@ \\\n\t@GNUTLS_CFLAGS@ \\\n\t-Wall\n\nif MACOS\nif COMPILE_UNIVERSAL\nAM_CFLAGS += -arch x86_64 -arch arm64\nendif\nendif\n\nbin_PROGRAMS = seaf-daemon\n\nnoinst_HEADERS = \\\n\tjob-mgr.h \\\n\ttimer.h \\\n\tcevent.h \\\n\trepo-mgr.h \\\n\tsync-mgr.h  \\\n\twt-monitor.h \\\n\tvc-utils.h seafile-session.h \\\n\tclone-mgr.h \\\n\twt-monitor-structs.h \\\n\tseafile-config.h \\\n\thttp-tx-mgr.h \\\n\tsync-status-tree.h \\\n\tfilelock-mgr.h \\\n\tset-perm.h \\\n\tchange-set.h \\\n\tseafile-error-impl.h \\\n\tnotif-mgr.h\n\nif LINUX\nwt_monitor_src = wt-monitor.c wt-monitor-linux.c wt-monitor-structs.c\nendif\n\nif WIN32\nwt_monitor_src = wt-monitor.c wt-monitor-win32.c wt-monitor-structs.c\nendif\n\nif MACOS\nwt_monitor_src = wt-monitor.c wt-monitor-macos.c wt-monitor-structs.c\nendif\n\nif MACOS\nws_src = notif-mgr.c\nelse\nif COMPILE_LINUX_WS\nws_src = notif-mgr.c\nendif\nendif\n\ncommon_src = \\\n\tjob-mgr.c timer.c cevent.c \\\n\thttp-tx-mgr.c \\\n\tvc-utils.c \\\n\tsync-mgr.c seafile-session.c \\\n\t../common/seafile-crypt.c ../common/diff-simple.c $(wt_monitor_src) \\\n\tclone-mgr.c \\\n\tseafile-config.c \\\n\tseafile-error.c \\\n\t../common/branch-mgr.c ../common/fs-mgr.c \\\n\trepo-mgr.c ../common/commit-mgr.c \\\n\t../common/log.c \\\n\t../common/rpc-service.c \\\n\t../common/vc-common.c \\\n\t../common/obj-store.c \\\n\t../common/obj-backend-fs.c \\\n\t../common/block-mgr.c \\\n\t../common/block-backend.c \\\n\t../common/block-backend-fs.c \\\n\t../common/mq-mgr.c \\\n\t../common/curl-init.c \\\n\t../common/password-hash.c \\\n\tsync-status-tree.c \\\n\tfilelock-mgr.c \\\n\tset-perm.c \\\n\tchange-set.c \\\n\t$(ws_src)\n\n\nseaf_daemon_SOURCES = seaf-daemon.c $(common_src)\n\nseaf_daemon_LDADD = $(top_builddir)/lib/libseafile_common.la \\\n\t@GLIB2_LIBS@  @GOBJECT_LIBS@ @SSL_LIBS@ @GNUTLS_LIBS@ @NETTLE_LIBS@ \\\n\t@LIB_RT@ @LIB_UUID@ -lsqlite3 @LIBEVENT_LIBS@ @LIBEVENT_PTHREADS_LIBS@\\\n\t$(top_builddir)/common/cdc/libcdc.la \\\n\t$(top_builddir)/common/index/libindex.la @LIB_WS32@ @LIB_CRYPT32@ \\\n\t@SEARPC_LIBS@ @JANSSON_LIBS@ @LIB_MAC@ @ZLIB_LIBS@ @CURL_LIBS@ @BPWRAPPER_LIBS@ \\\n\t@WS_LIBS@ @ARGON2_LIBS@\n\nseaf_daemon_LDFLAGS = @CONSOLE@\n"
  },
  {
    "path": "daemon/c_bpwrapper.cpp",
    "content": "#ifdef ENABLE_BREAKPAD\n\n#if defined(WIN32)\n#include <client/windows/handler/exception_handler.h>\n#endif\n#include <stdio.h>\n#include <string>\n\n#include \"c_bpwrapper.h\"\n\nwchar_t *utf8ToWString(const std::string &src)\n{\n    wchar_t dst[4096];\n    int len;\n\n    len = MultiByteToWideChar\n        (CP_UTF8,                        /* multibyte code page */\n         0,                              /* flags */\n         src.c_str(),                    /* src */\n         -1,                             /* src len, -1 for all includes \\0 */\n         dst,                            /* dst */\n         sizeof(dst) / sizeof(wchar_t)); /* dst buf len */\n\n    if (len <= 0) {\n        return NULL;\n    }\n\n    return _wcsdup(dst);\n}\n\nstd::string wStringToUtf8(const wchar_t *src)\n{\n    char dst[4096];\n    int len;\n\n    len = WideCharToMultiByte\n        (CP_UTF8,               /* multibyte code page */\n         0,                     /* flags */\n         src,                   /* src */\n         -1,                    /* src len, -1 for all includes \\0 */\n         dst,                   /* dst */\n         sizeof(dst),           /* dst buf len */\n         NULL,                  /* default char */\n         NULL);                 /* BOOL flag indicates default char is used */\n\n    if (len <= 0) {\n        return \"\";\n    }\n\n    return dst;\n}\n\nstd::string wStringToLocale(const wchar_t *src)\n{\n    char dst[4096];\n    int len;\n\n    len = WideCharToMultiByte\n        (CP_ACP,        /* multibyte code page */\n         0,             /* flags */\n         src,           /* src */\n         -1,            /* src len, -1 for all includes \\0 */\n         dst,           /* dst */\n         sizeof(dst),   /* dst buf len */\n         NULL,          /* default char */\n         NULL);         /* BOOL flag indicates default char is used */\n\n    if (len <= 0) {\n        return \"\";\n    }\n\n    return dst;\n}\n\nbool DumpCallback(const wchar_t *dump_path,\n                  const wchar_t *minidump_id,\n                  void *context,\n                  EXCEPTION_POINTERS *exinfo,\n                  MDRawAssertionInfo *assertion,\n                  bool succeeded)\n{\n    printf(\"program dump %s\\n\", succeeded ? \"succeeded\" : \"failed\");\n    printf(\"program crashed, you can find the minidump in %s\\n\", wStringToLocale(dump_path).c_str());\n    printf(\"minidump id: %s\\n\", wStringToLocale(minidump_id).c_str());\n    return succeeded;\n}\n\n\nextern \"C\" {\n    CBPWrapperExceptionHandler newCBPWrapperExceptionHandler(const char *dump_dir)\n    {\n        printf(\"initializing crash reporter\\n\");\n        std::wstring path = utf8ToWString(dump_dir);\n        return reinterpret_cast<void *>(new google_breakpad::ExceptionHandler(\n            path, NULL,\n            (google_breakpad::ExceptionHandler::MinidumpCallback)DumpCallback, NULL,\n            google_breakpad::ExceptionHandler::HANDLER_ALL));\n    }\n}\n\n#endif"
  },
  {
    "path": "daemon/c_bpwrapper.h",
    "content": "#pragma once\n\ntypedef void *CBPWrapperExceptionHandler;\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n    CBPWrapperExceptionHandler newCBPWrapperExceptionHandler(const char *dump_dir);\n\n#ifdef __cplusplus\n}\n#endif"
  },
  {
    "path": "daemon/cevent.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"include.h\"\n#include \"cevent.h\"\n\n#include \"seafile-session.h\"\n\n#define CEVENT_SIZE  (sizeof(CEvent))\n\ntypedef struct Handler {\n    cevent_handler handler;\n    void *handler_data;\n} Handler;\n\nCEventManager* cevent_manager_new ()\n{\n    CEventManager *manager;\n\n    manager = g_new0 (CEventManager, 1);\n    pthread_mutex_init (&manager->mutex, NULL);\n    manager->handler_table = g_hash_table_new_full (g_direct_hash,\n                                        g_direct_equal, NULL, g_free);\n    \n    return manager;\n}\n\nvoid pipe_callback (evutil_socket_t fd, short event, void *vmgr)\n{\n    CEventManager *manager = (CEventManager *) vmgr;\n    CEvent *cevent;\n    char buf[CEVENT_SIZE];\n    \n    if (seaf_pipe_readn(fd, buf, CEVENT_SIZE) != CEVENT_SIZE) {\n        return;\n    }\n\n    cevent = (CEvent *)buf;\n    Handler *h = g_hash_table_lookup (manager->handler_table,\n                                      (gconstpointer)(long)cevent->id);\n    if (h == NULL) {\n        g_warning (\"no handler for event type %d\\n\", cevent->id);\n        return;\n    }\n\n    h->handler(cevent, h->handler_data);\n}\n\nint cevent_manager_start (CEventManager *manager)\n{\n    if (seaf_pipe(manager->pipefd) < 0) {\n        g_warning (\"pipe error: %s\\n\", strerror(errno));\n        return -1;\n    }\n\n    manager->event = event_new (seaf->ev_base, manager->pipefd[0],\n               EV_READ | EV_PERSIST, pipe_callback, manager);\n    event_add (manager->event, NULL);\n\n    return 0;\n}\n\nuint32_t cevent_manager_register (CEventManager *manager,\n                                  cevent_handler handler, void *handler_data)\n{\n    uint32_t id;\n    Handler *h;\n\n    h = g_new0(Handler, 1);\n    h->handler = handler;\n    h->handler_data = handler_data;\n\n    /* Since we're using 32-bit int for id, it may wrap around to 0.\n     * If some caller persistently use one id, it's handler may be\n     * overwritten by others.\n     */\n    do {\n        id = manager->next_id++;\n    } while (g_hash_table_lookup (manager->handler_table, (gpointer)(long)id));\n\n    g_hash_table_insert (manager->handler_table, (gpointer)(long)id, h);\n\n    return id;\n}\n\nvoid cevent_manager_unregister (CEventManager *manager, uint32_t id)\n{\n    g_hash_table_remove (manager->handler_table, (gpointer)(long)id);\n}\n\nvoid\ncevent_manager_add_event (CEventManager *manager, uint32_t id,\n                          void *data)\n{\n    pthread_mutex_lock (&manager->mutex);\n\n    struct CEvent cevent;\n    char *buf = (char *) &cevent;\n\n    cevent.id = id;\n    cevent.data = data;\n    if (seaf_pipe_writen(manager->pipefd[1], buf, CEVENT_SIZE) != CEVENT_SIZE) {\n        g_warning (\"add event error\\n\");\n    }\n\n    pthread_mutex_unlock (&manager->mutex);\n}\n"
  },
  {
    "path": "daemon/cevent.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n/* \n * CEvent is used for send message from a work thread to main thread.\n */\n#ifndef CEVENT_H\n#define CEVENT_H\n\n#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)\n#include <event2/event.h>\n#include <event2/event_compat.h>\n#include <event2/event_struct.h>\n#else\n#include <event.h>\n#endif\n\n#include <glib.h>\n\n#include <pthread.h>\n\n#include \"utils.h\"\n\ntypedef struct CEvent  CEvent;\n\ntypedef void (*cevent_handler) (CEvent *event, void *handler_data);\n\nstruct CEvent {\n    uint32_t  id;\n    void     *data;\n};\n\n\ntypedef struct CEventManager CEventManager;\n\nstruct CEventManager {    \n    seaf_pipe_t  pipefd[2];\n    struct event  *event;\n    GHashTable   *handler_table;\n    uint32_t      next_id;\n    \n    pthread_mutex_t  mutex;\n};\n\nCEventManager* cevent_manager_new ();\n\nint cevent_manager_start (CEventManager *manager);\n\nuint32_t cevent_manager_register (CEventManager *manager,\n                                  cevent_handler handler, void *handler_data);\n\nvoid cevent_manager_unregister (CEventManager *manager, uint32_t id);\n\nvoid cevent_manager_add_event (CEventManager *manager, uint32_t id,\n                               void *event_data);\n\n#endif\n"
  },
  {
    "path": "daemon/change-set.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#include \"seafile-session.h\"\n\n#include \"utils.h\"\n#include \"log.h\"\n\n#include \"index/index.h\"\n\n#include \"diff-simple.h\"\n#include \"change-set.h\"\n\nstruct _ChangeSetDir {\n    int version;\n    char dir_id[41];\n    /* A hash table of dirents for fast lookup and insertion. */\n    GHashTable *dents;\n#if defined WIN32 || defined __APPLE__\n    /* Case-insensitive hash table. */\n    GHashTable *dents_i;\n#endif\n\n};\ntypedef struct _ChangeSetDir ChangeSetDir;\n\nstruct _ChangeSetDirent {\n    guint32 mode;\n    char id[41];\n    char *name;\n    gint64 mtime;\n    char *modifier;\n    gint64 size;\n    /* Only used for directory. Most of time this is NULL\n     * unless we change the subdir too.\n     */\n    ChangeSetDir *subdir;\n};\ntypedef struct _ChangeSetDirent ChangeSetDirent;\n\n/* Change set dirent. */\n\nstatic ChangeSetDirent *\nchangeset_dirent_new (const char *id, guint32 mode, const char *name,\n                      gint64 mtime, const char *modifier, gint64 size)\n{\n    ChangeSetDirent *dent = g_new0 (ChangeSetDirent, 1);\n\n    dent->mode = mode;\n    memcpy (dent->id, id, 40);\n    dent->name = g_strdup(name);\n    dent->mtime = mtime;\n    dent->modifier = g_strdup(modifier);\n    dent->size = size;\n\n    return dent;    \n}\n\nstatic ChangeSetDirent *\nseaf_dirent_to_changeset_dirent (SeafDirent *seaf_dent)\n{\n    return changeset_dirent_new (seaf_dent->id, seaf_dent->mode, seaf_dent->name,\n                                 seaf_dent->mtime, seaf_dent->modifier, seaf_dent->size);\n}\n\nstatic SeafDirent *\nchangeset_dirent_to_seaf_dirent (int version, ChangeSetDirent *dent)\n{\n    return seaf_dirent_new (version, dent->id, dent->mode, dent->name,\n                            dent->mtime, dent->modifier, dent->size);\n}\n\nstatic void\nchangeset_dir_free (ChangeSetDir *dir);\n\nstatic void\nchangeset_dirent_free (ChangeSetDirent *dent)\n{\n    if (!dent)\n        return;\n\n    g_free (dent->name);\n    g_free (dent->modifier);\n    /* Recursively free subdir. */\n    if (dent->subdir)\n        changeset_dir_free (dent->subdir);\n    g_free (dent);\n}\n\n/* Change set dir. */\n\nstatic void\nadd_dent_to_dir (ChangeSetDir *dir, ChangeSetDirent *dent)\n{\n    g_hash_table_insert (dir->dents,\n                         g_strdup(dent->name),\n                         dent);\n#if defined WIN32 || defined __APPLE__\n    g_hash_table_insert (dir->dents_i,\n                         g_utf8_strdown(dent->name, -1),\n                         dent);\n#endif\n}\n\nstatic void\nremove_dent_from_dir (ChangeSetDir *dir, const char *dname)\n{\n    char *key;\n\n    if (g_hash_table_lookup_extended (dir->dents, dname,\n                                      (gpointer*)&key, NULL)) {\n        g_hash_table_steal (dir->dents, dname);\n        g_free (key);\n    }\n#if defined WIN32 || defined __APPLE__\n    char *dname_i = g_utf8_strdown (dname, -1);\n    g_hash_table_remove (dir->dents_i, dname_i);\n    g_free (dname_i);\n#endif\n}\n\nstatic ChangeSetDir *\nchangeset_dir_new (int version, const char *id, GList *dirents)\n{\n    ChangeSetDir *dir = g_new0 (ChangeSetDir, 1);\n    GList *ptr;\n    SeafDirent *dent;\n    ChangeSetDirent *changeset_dent;\n\n    dir->version = version;\n    if (id)\n        memcpy (dir->dir_id, id, 40);\n    dir->dents = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                        g_free, (GDestroyNotify)changeset_dirent_free);\n#if defined WIN32 || defined __APPLE__\n    dir->dents_i = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                          g_free, NULL);\n#endif\n    for (ptr = dirents; ptr; ptr = ptr->next) {\n        dent = ptr->data;\n        changeset_dent = seaf_dirent_to_changeset_dirent(dent);\n        add_dent_to_dir (dir, changeset_dent);\n    }\n\n    return dir;\n} \n\nstatic void\nchangeset_dir_free (ChangeSetDir *dir)\n{\n    if (!dir)\n        return;\n    g_hash_table_destroy (dir->dents);\n#if defined WIN32 || defined __APPLE__\n    g_hash_table_destroy (dir->dents_i);\n#endif\n    g_free (dir);\n}\n\nstatic ChangeSetDir *\nseaf_dir_to_changeset_dir (SeafDir *seaf_dir)\n{\n    return changeset_dir_new (seaf_dir->version, seaf_dir->dir_id, seaf_dir->entries);\n}\n\nstatic gint\ncompare_dents (gconstpointer a, gconstpointer b)\n{\n    const SeafDirent *denta = a, *dentb = b;\n\n    return strcmp(dentb->name, denta->name);\n}\n\nstatic SeafDir *\nchangeset_dir_to_seaf_dir (ChangeSetDir *dir)\n{\n    GList *dents = NULL, *seaf_dents = NULL;\n    GList *ptr;\n    ChangeSetDirent *dent;\n    SeafDirent *seaf_dent;\n    SeafDir *seaf_dir;\n\n    dents = g_hash_table_get_values (dir->dents);\n    for (ptr = dents; ptr; ptr = ptr->next) {\n        dent = ptr->data;\n        seaf_dent = changeset_dirent_to_seaf_dirent (dir->version, dent);\n        seaf_dents = g_list_prepend (seaf_dents, seaf_dent);\n    }\n    /* Sort it in descending order. */\n    seaf_dents = g_list_sort (seaf_dents, compare_dents);\n\n    /* seaf_dir_new() computes the dir id. */\n    seaf_dir = seaf_dir_new (NULL, seaf_dents, dir->version);\n\n    g_list_free (dents);\n    return seaf_dir;\n}\n\n/* Change set. */\n\n#define CASE_CONFLICT_PATTERN \" \\\\(case conflict \\\\d+\\\\)\"\n\nChangeSet *\nchangeset_new (const char *repo_id)\n{\n    SeafRepo *repo;\n    SeafCommit *commit = NULL;\n    SeafDir *seaf_dir = NULL;\n    ChangeSetDir *changeset_dir = NULL;\n    ChangeSet *changeset = NULL;\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo) {\n        seaf_warning (\"Failed to find repo %s.\\n\", repo_id);\n        return NULL;\n    }\n\n    commit = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                             repo_id,\n                                             repo->version,\n                                             repo->head->commit_id);\n    if (!commit) {\n        seaf_warning (\"Failed to find head commit %s for repo %s.\\n\",\n                      repo->head->commit_id, repo_id);\n        return NULL;\n    }\n\n    seaf_dir = seaf_fs_manager_get_seafdir_sorted (seaf->fs_mgr,\n                                                   repo_id,\n                                                   repo->version,\n                                                   commit->root_id);\n    if (!seaf_dir) {\n        seaf_warning (\"Failed to find root dir %s in repo %s\\n\",\n                      repo->root_id, repo_id);\n        goto out;\n    }\n\n    changeset_dir = seaf_dir_to_changeset_dir (seaf_dir);\n    if (!changeset_dir)\n        goto out;\n\n    GError *error = NULL;\n    GRegex *case_conflict_pattern = g_regex_new(CASE_CONFLICT_PATTERN,\n                                                0, 0, &error);\n    if (error) {\n        seaf_warning (\"Failed to create regex '%s': %s\\n\",\n                      CASE_CONFLICT_PATTERN, error->message);\n        goto out;\n    }\n\n    changeset = g_new0 (ChangeSet, 1);\n    memcpy (changeset->repo_id, repo_id, 36);\n    changeset->tree_root = changeset_dir;\n    changeset->case_conflict_pattern = case_conflict_pattern;\n\nout:\n    seaf_commit_unref (commit);\n    seaf_dir_free (seaf_dir);\n    return changeset;\n}\n\nvoid\nchangeset_free (ChangeSet *changeset)\n{\n    if (!changeset)\n        return;\n\n    changeset_dir_free (changeset->tree_root);\n    g_regex_unref (changeset->case_conflict_pattern);\n    g_free (changeset);\n}\n\nstatic gboolean\nupdate_file (ChangeSetDirent *dent,\n             unsigned char *sha1,\n             SeafStat *st,\n             const char *modifier)\n{\n    if (!sha1 || !st || !S_ISREG(st->st_mode))\n        return FALSE;\n    dent->mode = create_ce_mode(st->st_mode);\n    dent->mtime = (gint64)st->st_mtime;\n    dent->size = (gint64)st->st_size;\n    rawdata_to_hex (sha1, dent->id, 20);\n\n    g_free (dent->modifier);\n    dent->modifier = g_strdup(modifier);\n\n    return TRUE;\n}\n\nstatic void\ncreate_new_dent (ChangeSetDir *dir,\n                 const char *dname,\n                 unsigned char *sha1,\n                 SeafStat *st,\n                 const char *modifier,\n                 ChangeSetDirent *in_new_dent)\n{\n    if (in_new_dent) {\n        g_free (in_new_dent->name);\n        in_new_dent->name = g_strdup(dname);\n        add_dent_to_dir (dir, in_new_dent);\n        return;\n    }\n\n    char id[41];\n    rawdata_to_hex (sha1, id, 20);\n    ChangeSetDirent *new_dent;\n    new_dent = changeset_dirent_new (id, create_ce_mode(st->st_mode), dname,\n                                     st->st_mtime, modifier, st->st_size);\n\n    add_dent_to_dir (dir, new_dent);\n}\n\nstatic ChangeSetDir *\ncreate_intermediate_dir (ChangeSetDir *parent, const char *dname, SeafStat *st)\n{\n    ChangeSetDirent *dent;\n    gint64 mtime = 0;\n\n    if (st) {\n        mtime = st->st_mtime;\n    }\n\n    dent = changeset_dirent_new (EMPTY_SHA1, S_IFDIR, dname, mtime, NULL, 0);\n    dent->subdir = changeset_dir_new (parent->version, EMPTY_SHA1, NULL);\n    add_dent_to_dir (parent, dent);\n\n    return dent->subdir;\n}\n\n#if defined WIN32 || defined __APPLE__\nstatic void\nhandle_case_conflict (ChangeSet *changeset,\n                      ChangeSetDir *dir,\n                      const char *dname)\n{\n    char *conflict_dname;\n    ChangeSetDirent *dent;\n    GError *error = NULL;\n\n    if (g_regex_match (changeset->case_conflict_pattern,\n                       dname, 0, NULL)) {\n        conflict_dname = g_regex_replace_literal (changeset->case_conflict_pattern,\n                                                  dname, -1, 0, \"\", 0, &error);\n        if (!conflict_dname) {\n            seaf_warning (\"Failed to replace regex for %s: %s\\n\",\n                          dname, error->message);\n            return;\n        }\n\n        dent = g_hash_table_lookup (dir->dents, conflict_dname);\n        if (dent) {\n            remove_dent_from_dir (dir, conflict_dname);\n            changeset_dirent_free (dent);\n        }\n        g_free (conflict_dname);\n    }\n}\n#endif\n\nstatic void\nadd_to_tree (ChangeSet *changeset,\n             unsigned char *sha1,\n             SeafStat *st,\n             const char *modifier,\n             const char *path,\n             ChangeSetDirent *new_dent)\n{\n    char *repo_id = changeset->repo_id;\n    ChangeSetDir *root = changeset->tree_root;\n    char **parts, *dname;\n    int n, i;\n    ChangeSetDir *dir;\n    ChangeSetDirent *dent;\n    SeafDir *seaf_dir;\n    GList *parent_dents = NULL;\n    gboolean changed = FALSE;\n\n    parts = g_strsplit (path, \"/\", 0);\n    n = g_strv_length(parts);\n    dir = root;\n    for (i = 0; i < n; i++) {\n#if defined WIN32 || defined __APPLE__\n    try_again:\n#endif\n        dname = parts[i];\n        dent = g_hash_table_lookup (dir->dents, dname);\n\n        if (dent) {\n            if (S_ISDIR(dent->mode)) {\n                if (i == (n-1))\n                    /* Don't need to update empty dir */\n                    break;\n\n                if (!dent->subdir) {\n                    seaf_dir = seaf_fs_manager_get_seafdir(seaf->fs_mgr,\n                                                           repo_id,\n                                                           root->version,\n                                                           dent->id);\n                    if (!seaf_dir) {\n                        seaf_warning (\"Failed to load seafdir %s:%s\\n\",\n                                      repo_id, dent->id);\n                        break;\n                    }\n                    dent->subdir = seaf_dir_to_changeset_dir (seaf_dir);\n                    seaf_dir_free (seaf_dir);\n                }\n                dir = dent->subdir;\n                parent_dents = g_list_prepend (parent_dents, dent);\n            } else if (S_ISREG(dent->mode)) {\n                if (i == (n-1)) {\n                    /* File exists, update it. */\n                    changed = update_file (dent, sha1, st, modifier);\n                    break;\n                }\n            }\n        } else {\n#if defined WIN32 || defined __APPLE__\n            /* Only effective for add operation, not applicable to rename. */\n            if (!new_dent) {\n                char *search_key = g_utf8_strdown (dname, -1);\n                dent = g_hash_table_lookup (dir->dents_i, search_key);\n                g_free (search_key);\n                if (dent) {\n                    remove_dent_from_dir (dir, dent->name);\n\n                    g_free (dent->name);\n                    dent->name = g_strdup(dname);\n                    add_dent_to_dir (dir, dent);\n\n                    goto try_again;\n                }\n\n                handle_case_conflict (changeset, dir, dname);\n            }\n#endif\n\n            if (i == (n-1)) {\n                create_new_dent (dir, dname, sha1, st, modifier, new_dent);\n                changed = TRUE;\n            } else {\n                dir = create_intermediate_dir (dir, dname, st);\n            }\n        }\n    }\n\n    if (changed) {\n        GList *ptr;\n        time_t now  = time(NULL);\n        for (ptr = parent_dents; ptr; ptr = ptr->next) {\n            dent = ptr->data;\n            // update parent dir mtime when add or modify or rename files locally.\n            dent->mtime = now;\n        }\n    }\n    g_list_free (parent_dents);\n\n    g_strfreev (parts);\n}\n\nstatic ChangeSetDirent *\ndelete_from_tree (ChangeSet *changeset,\n                  const char *path,\n                  gboolean *parent_empty)\n{\n    char *repo_id = changeset->repo_id;\n    ChangeSetDir *root = changeset->tree_root;\n    char **parts, *dname;\n    int n, i;\n    ChangeSetDir *dir;\n    ChangeSetDirent *dent, *ret = NULL;\n    SeafDir *seaf_dir;\n    GList *parent_dents = NULL;\n\n    *parent_empty = FALSE;\n\n    parts = g_strsplit (path, \"/\", 0);\n    n = g_strv_length(parts);\n    dir = root;\n    for (i = 0; i < n; i++) {\n        dname = parts[i];\n\n        dent = g_hash_table_lookup (dir->dents, dname);\n        if (!dent)\n            break;\n\n        if (S_ISDIR(dent->mode)) {\n            if (i == (n-1)) {\n                /* Remove from hash table without freeing dent. */\n                remove_dent_from_dir (dir, dname);\n                if (g_hash_table_size (dir->dents) == 0)\n                    *parent_empty = TRUE;\n                ret = dent;\n                break;\n            }\n\n            if (!dent->subdir) {\n                seaf_dir = seaf_fs_manager_get_seafdir(seaf->fs_mgr,\n                                                       repo_id,\n                                                       root->version,\n                                                       dent->id);\n                if (!seaf_dir) {\n                    seaf_warning (\"Failed to load seafdir %s:%s\\n\",\n                                  repo_id, dent->id);\n                    break;\n                }\n                dent->subdir = seaf_dir_to_changeset_dir (seaf_dir);\n                seaf_dir_free (seaf_dir);\n            }\n            dir = dent->subdir;\n            parent_dents = g_list_prepend (parent_dents, dent);\n        } else if (S_ISREG(dent->mode)) {\n            if (i == (n-1)) {\n                /* Remove from hash table without freeing dent. */\n                remove_dent_from_dir (dir, dname);\n                if (g_hash_table_size (dir->dents) == 0)\n                    *parent_empty = TRUE;\n                ret = dent;\n                break;\n            }\n        }\n    }\n\n    if (ret) {\n        GList *ptr;\n        time_t now  = time(NULL);\n        for (ptr = parent_dents; ptr; ptr = ptr->next) {\n            dent = ptr->data;\n            // update parent dir mtime when delete dirs or files locally.\n            dent->mtime = now;\n        }\n    }\n    g_list_free (parent_dents);\n\n    g_strfreev (parts);\n    return ret;\n}\n\nstatic void\napply_to_tree (ChangeSet *changeset,\n               char status,\n               unsigned char *sha1,\n               SeafStat *st,\n               const char *modifier,\n               const char *path,\n               const char *new_path)\n{\n    ChangeSetDirent *dent, *dent_dst;\n    gboolean dummy;\n\n    switch (status) {\n    case DIFF_STATUS_ADDED:\n    case DIFF_STATUS_MODIFIED:\n    case DIFF_STATUS_DIR_ADDED:\n        add_to_tree (changeset, sha1, st, modifier, path, NULL);\n        break;\n    case DIFF_STATUS_RENAMED:\n        dent = delete_from_tree (changeset, path, &dummy);\n        if (!dent)\n            break;\n\n        dent_dst = delete_from_tree (changeset, new_path, &dummy);\n        changeset_dirent_free (dent_dst);\n        add_to_tree (changeset, NULL, NULL, NULL, new_path, dent);\n\n        break;\n    }\n}\n\nvoid\nadd_to_changeset (ChangeSet *changeset,\n                  char status,\n                  unsigned char *sha1,\n                  SeafStat *st,\n                  const char *modifier,\n                  const char *path,\n                  const char *new_path)\n{\n    apply_to_tree (changeset,\n                   status, sha1, st, modifier, path, new_path);\n}\n\nstatic void\nremove_from_changeset_recursive (ChangeSet *changeset,\n                                 const char *path,\n                                 gboolean remove_parent,\n                                 const char *top_dir)\n{\n    ChangeSetDirent *dent;\n    gboolean parent_empty = FALSE;\n\n    dent = delete_from_tree (changeset, path, &parent_empty);\n    changeset_dirent_free (dent);\n\n    if (remove_parent && parent_empty) {\n        char *parent = g_strdup(path);\n        char *slash = strrchr (parent, '/');\n        if (slash) {\n            *slash = '\\0';\n            if (strlen(parent) >= strlen(top_dir)) {\n                /* Recursively remove parent dirs. */\n                remove_from_changeset_recursive (changeset,\n                                                 parent,\n                                                 remove_parent,\n                                                 top_dir);\n            }\n        }\n        g_free (parent);\n    }\n}\n\nvoid\nremove_from_changeset (ChangeSet *changeset,\n                       char status,\n                       const char *path,\n                       gboolean remove_parent,\n                       const char *top_dir)\n{\n    remove_from_changeset_recursive (changeset, path, remove_parent, top_dir);\n}\n\nstatic char *\ncommit_tree_recursive (const char *repo_id, ChangeSetDir *dir)\n{\n    ChangeSetDirent *dent;\n    GHashTableIter iter;\n    gpointer key, value;\n    char *new_id;\n    SeafDir *seaf_dir;\n    char *ret = NULL;\n\n    g_hash_table_iter_init (&iter, dir->dents);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        dent = value;\n        if (dent->subdir) {\n            new_id = commit_tree_recursive (repo_id, dent->subdir);\n            if (!new_id)\n                return NULL;\n\n            memcpy (dent->id, new_id, 40);\n            g_free (new_id);\n        }\n    }\n\n    seaf_dir = changeset_dir_to_seaf_dir (dir);\n\n    memcpy (dir->dir_id, seaf_dir->dir_id, 40);\n\n    if (!seaf_fs_manager_object_exists (seaf->fs_mgr,\n                                        repo_id, dir->version,\n                                        seaf_dir->dir_id)) {\n        if (seaf_dir_save (seaf->fs_mgr, repo_id, dir->version, seaf_dir) < 0) {\n            seaf_warning (\"Failed to save dir object %s to repo %s.\\n\",\n                          seaf_dir->dir_id, repo_id);\n            goto out;\n        }\n    }\n\n    ret = g_strdup(seaf_dir->dir_id);\n\nout:\n    seaf_dir_free (seaf_dir);\n    return ret;\n}\n\n/*\n * This function does two things:\n * - calculate dir id from bottom up;\n * - create and save seaf dir objects.\n * It returns root dir id of the new commit.\n */\nchar *\ncommit_tree_from_changeset (ChangeSet *changeset)\n{\n    char *root_id = commit_tree_recursive (changeset->repo_id,\n                                           changeset->tree_root);\n\n    return root_id;\n}\n\ngboolean\nchangeset_check_path (ChangeSet *changeset,\n                      const char *path,\n                      unsigned char *sha1,\n                      guint32 mode,\n                      gint64 mtime)\n{\n    ChangeSetDir *root = changeset->tree_root;\n    char **parts, *dname;\n    int n, i;\n    ChangeSetDir *dir;\n    ChangeSetDirent *dent;\n    gboolean ret = FALSE;\n    char id[41];\n\n    rawdata_to_hex (sha1, id, 20);\n\n    parts = g_strsplit (path, \"/\", 0);\n    n = g_strv_length(parts);\n    dir = root;\n    for (i = 0; i < n; i++) {\n        dname = parts[i];\n\n        dent = g_hash_table_lookup (dir->dents, dname);\n        if (!dent) {\n            seaf_message (\"Changeset mismatch: path component %s of %s not found\\n\",\n                          dname, path);\n            break;\n        }\n\n        if (S_ISDIR(dent->mode)) {\n            if (i == (n-1)) {\n                if (dent->mode != mode) {\n                    seaf_message (\"Changeset mismatch: %s is not a dir\\n\", path);\n                    break;\n                } else if (strcmp (dent->id, EMPTY_SHA1) != 0) {\n                    seaf_message (\"Changeset mismatch: %s is not a empty dir\\n\", path);\n                    break;\n                }\n                ret = TRUE;\n                break;\n            }\n\n            if (!dent->subdir) {\n                seaf_message (\"Changeset mismatch: path component %s of %s is not in changeset\\n\",\n                              dname, path);\n                break;\n            }\n            dir = dent->subdir;\n        } else if (S_ISREG(dent->mode)) {\n            if (i == (n-1)) {\n                if (dent->mode != mode) {\n                    seaf_message (\"Changeset mismatch: %s mode mismatch, \"\n                                  \"index: %u, changeset: %u\\n\",\n                                  path, mode, dent->mode);\n                    break;\n                } else if (dent->mtime != mtime) {\n                    seaf_message (\"Changeset mismatch: %s mtime mismatch, \"\n                                  \"index: %\"G_GINT64_FORMAT\n                                  \", changeset: %\"G_GINT64_FORMAT\"\\n\",\n                                  path, mtime, dent->mtime);\n                    break;\n                } else if (strcmp (dent->id, id) != 0) {\n                    seaf_message (\"Changeset mismatch: %s id mismatch, \"\n                                  \"index: %s, changeset: %s\\n\",\n                                  path, id, dent->id);\n                    break;\n                }\n                ret = TRUE;\n                break;\n            }\n\n            /* We find a file in the middle of the path, this is invalid. */\n            seaf_message (\"Changeset mismatch: path component %s of %s is a file\\n\",\n                          dname, path);\n            break;\n        }\n    }\n\n    g_strfreev (parts);\n    return ret;\n}\n"
  },
  {
    "path": "daemon/change-set.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef SEAF_CHANGE_SET_H\n#define SEAF_CHANGE_SET_H\n\n#include <glib.h>\n#include \"utils.h\"\n\nstruct _ChangeSetDir;\n\nstruct _ChangeSet {\n    char repo_id[37];\n    /* A partial tree for all changed directories. */\n    struct _ChangeSetDir *tree_root;\n    /* Used to match case conflict paths. */\n    GRegex *case_conflict_pattern;\n};\ntypedef struct _ChangeSet ChangeSet;\n\nChangeSet *\nchangeset_new (const char *repo_id);\n\nvoid\nchangeset_free (ChangeSet *changeset);\n\nvoid\nadd_to_changeset (ChangeSet *changeset,\n                  char status,\n                  unsigned char *sha1,\n                  SeafStat *st,\n                  const char *modifier,\n                  const char *path,\n                  const char *new_path);\n\n/*\n  @remove_parent: remove the parent dir when it becomes empty.\n*/\nvoid\nremove_from_changeset (ChangeSet *changeset,\n                       char status,\n                       const char *path,\n                       gboolean remove_parent,\n                       const char *top_dir);\n\nchar *\ncommit_tree_from_changeset (ChangeSet *changeset);\n\ngboolean\nchangeset_check_path (ChangeSet *changeset,\n                      const char *path,\n                      unsigned char *sha1,\n                      guint32 mode,\n                      gint64 mtime);\n\n#endif\n"
  },
  {
    "path": "daemon/clone-mgr.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#define DEBUG_FLAG SEAFILE_DEBUG_SYNC\n#include \"log.h\"\n\n#include \"seafile-error-impl.h\"\n#include \"seafile-session.h\"\n#include \"vc-utils.h\"\n#include \"utils.h\"\n#include \"seafile-config.h\"\n\n#include \"timer.h\"\n\n#define CLONE_DB \"clone.db\"\n\n#define CHECK_CONNECT_INTERVAL 5\n\nstatic void\non_repo_http_fetched (SeafileSession *seaf,\n                      HttpTxTask *tx_task,\n                      SeafCloneManager *mgr);\n\nstatic void\ntransition_state (CloneTask *task, int new_state);\n\nstatic void\ntransition_to_error (CloneTask *task, int error);\n\nstatic int\nadd_transfer_task (CloneTask *task, GError **error);\n\nstatic const char *state_str[] = {\n    \"init\",\n    \"check server\",\n    \"fetch\",\n    \"done\",\n    \"error\",\n    \"canceling\",\n    \"canceled\",\n    /* States only used by old protocol. */\n    \"connect\",\n    \"connect\",                  /* Use \"connect\" for CHECK_PROTOCOL */\n    \"index\",\n    \"checkout\",\n    \"merge\",\n};\n\nstatic void\nmark_clone_done_v2 (SeafRepo *repo, CloneTask *task)\n{\n    SeafBranch *local = NULL;\n\n    seaf_repo_manager_set_repo_worktree (repo->manager,\n                                         repo,\n                                         task->worktree);\n\n    local = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, \"local\");\n    if (!local) {\n        seaf_warning (\"Cannot get branch local for repo %s(%.10s).\\n\",\n                      repo->name, repo->id);\n        transition_to_error (task, SYNC_ERROR_ID_LOCAL_DATA_CORRUPT);\n        return;\n    }\n    /* Set repo head to mark checkout done. */\n    seaf_repo_set_head (repo, local);\n    seaf_branch_unref (local);\n\n    if (repo->encrypted) {\n        if (!task->resync_enc_repo) {\n            if (seaf_repo_manager_set_repo_passwd (seaf->repo_mgr,\n                                                   repo,\n                                                   task->passwd) < 0) {\n                seaf_warning (\"[Clone mgr] failed to set passwd for %s.\\n\", repo->id);\n                transition_to_error (task, SYNC_ERROR_ID_GENERAL_ERROR);\n                return;\n            }\n        } else {\n            if (seaf_repo_manager_load_repo_enc_info (seaf->repo_mgr, repo) < 0) {\n                seaf_warning (\"[Clone mgr] failed to set enc info for %s.\\n\", repo->id);\n                transition_to_error (task, SYNC_ERROR_ID_GENERAL_ERROR);\n                return;\n            }\n        }\n    }\n\n    if (task->is_readonly) {\n        seaf_repo_set_readonly (repo);\n    }\n\n    if (task->sync_wt_name) {\n        seaf_repo_manager_set_repo_property (seaf->repo_mgr,\n                                             repo->id,\n                                             REPO_SYNC_WORKTREE_NAME,\n                                             \"true\");\n    }\n\n    if (task->server_url)\n        repo->server_url = g_strdup(task->server_url);\n\n    if (repo->auto_sync && (repo->sync_interval == 0)) {\n        if (seaf_wt_monitor_watch_repo (seaf->wt_monitor,\n                                        repo->id, repo->worktree) < 0) {\n            seaf_warning (\"failed to watch repo %s(%.10s).\\n\", repo->name, repo->id);\n            transition_to_error (task, SYNC_ERROR_ID_GENERAL_ERROR);\n            return;\n        }\n    }\n\n    /* For compatibility, still set these two properties.\n     * So that if we downgrade to an old version, the syncing can still work.\n     */\n    seaf_repo_manager_set_repo_property (seaf->repo_mgr,\n                                         repo->id,\n                                         REPO_REMOTE_HEAD,\n                                         repo->head->commit_id);\n    seaf_repo_manager_set_repo_property (seaf->repo_mgr,\n                                         repo->id,\n                                         REPO_LOCAL_HEAD,\n                                         repo->head->commit_id);\n\n    transition_state (task, CLONE_STATE_DONE);\n}\n\nstatic void\nstart_clone_v2 (CloneTask *task)\n{\n    GError *error = NULL;\n\n    if (g_access (task->worktree, F_OK) != 0 &&\n        checkdir_with_mkdir (task->worktree) < 0) {\n        seaf_warning (\"[clone mgr] Failed to create worktree %s.\\n\",\n                      task->worktree);\n        transition_to_error (task, SYNC_ERROR_ID_WRITE_LOCAL_DATA);\n        return;\n    }\n\n    SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, task->repo_id);\n    if (repo != NULL) {\n        seaf_repo_manager_set_repo_token (seaf->repo_mgr, repo, task->token);\n        seaf_repo_manager_set_repo_email (seaf->repo_mgr, repo, task->email);\n        seaf_repo_manager_set_repo_relay_info (seaf->repo_mgr, repo->id,\n                                               task->peer_addr, task->peer_port);\n        if (task->server_url) {\n            seaf_repo_manager_set_repo_property (seaf->repo_mgr,\n                                                 repo->id,\n                                                 REPO_PROP_SERVER_URL,\n                                                 task->server_url);\n        }\n        if (task->username) {\n            seaf_repo_manager_set_repo_property (seaf->repo_mgr,\n                                                 repo->id,\n                                                 REPO_PROP_USERNAME,\n                                                 task->username);\n        }\n\n        mark_clone_done_v2 (repo, task);\n        return;\n    }\n\n    if (add_transfer_task (task, &error) == 0)\n        transition_state (task, CLONE_STATE_FETCH);\n    else\n        transition_to_error (task, SYNC_ERROR_ID_NOT_ENOUGH_MEMORY);\n}\n\nstatic void\ncheck_head_commit_done (HttpHeadCommit *result, void *user_data)\n{\n    CloneTask *task = user_data;\n\n    if (task->state == CLONE_STATE_CANCEL_PENDING) {\n        transition_state (task, CLONE_STATE_CANCELED);\n        return;\n    }\n\n    if (result->check_success && !result->is_corrupt && !result->is_deleted) {\n        memcpy (task->server_head_id, result->head_commit, 40);\n        start_clone_v2 (task);\n    } else {\n        transition_to_error (task, result->error_code);\n    }\n}\n\nstatic void\nhttp_check_head_commit (CloneTask *task)\n{\n    int ret = http_tx_manager_check_head_commit (seaf->http_tx_mgr,\n                                                 task->repo_id,\n                                                 task->repo_version,\n                                                 task->effective_url,\n                                                 task->token,\n                                                 task->use_fileserver_port,\n                                                 check_head_commit_done,\n                                                 task);\n    if (ret < 0)\n        transition_to_error (task, SYNC_ERROR_ID_NOT_ENOUGH_MEMORY);\n}\n\nstatic char *\nhttp_fileserver_url (const char *url)\n{\n    const char *host;\n    char *colon;\n    char *url_no_port;\n    char *ret = NULL;\n\n    /* Just return the url itself if it's invalid. */\n    if (strlen(url) <= strlen(\"http://\"))\n        return g_strdup(url);\n\n    /* Skip protocol schem. */\n    host = url + strlen(\"http://\");\n\n    colon = strrchr (host, ':');\n    if (colon) {\n        url_no_port = g_strndup(url, colon - url);\n        ret = g_strconcat(url_no_port, \":8082\", NULL);\n        g_free (url_no_port);\n    } else {\n        ret = g_strconcat(url, \":8082\", NULL);\n    }\n\n    return ret;\n}\n\nstatic void\ncheck_http_fileserver_protocol_done (HttpProtocolVersion *result, void *user_data)\n{\n    CloneTask *task = user_data;\n\n    if (task->state == CLONE_STATE_CANCEL_PENDING) {\n        transition_state (task, CLONE_STATE_CANCELED);\n        return;\n    }\n\n    if (result->check_success && !result->not_supported) {\n        task->http_protocol_version = result->version;\n        task->effective_url = http_fileserver_url (task->server_url);\n        task->use_fileserver_port = TRUE;\n        http_check_head_commit (task);\n    } else {\n        /* Wait for periodic retry. */\n        transition_to_error (task, result->error_code);\n    }\n}\n\nstatic void\ncheck_http_protocol_done (HttpProtocolVersion *result, void *user_data)\n{\n    CloneTask *task = user_data;\n\n    if (task->state == CLONE_STATE_CANCEL_PENDING) {\n        transition_state (task, CLONE_STATE_CANCELED);\n        return;\n    }\n\n    if (result->check_success && !result->not_supported) {\n        task->http_protocol_version = result->version;\n        task->effective_url = g_strdup(task->server_url);\n        http_check_head_commit (task);\n    } else if (strncmp(task->server_url, \"https\", 5) != 0) {\n        char *host_fileserver = http_fileserver_url(task->server_url);\n        if (http_tx_manager_check_protocol_version (seaf->http_tx_mgr,\n                                                    host_fileserver,\n                                                    TRUE,\n                                                    check_http_fileserver_protocol_done,\n                                                    task) < 0)\n            transition_to_error (task, SYNC_ERROR_ID_NOT_ENOUGH_MEMORY);\n        g_free (host_fileserver);\n    } else {\n        /* Wait for periodic retry. */\n        transition_to_error (task, result->error_code);\n    }\n}\n\nstatic void\ncheck_http_protocol (CloneTask *task)\n{\n    if (http_tx_manager_check_protocol_version (seaf->http_tx_mgr,\n                                                task->server_url,\n                                                FALSE,\n                                                check_http_protocol_done,\n                                                task) < 0) {\n        transition_to_error (task, SYNC_ERROR_ID_NOT_ENOUGH_MEMORY);\n        return;\n    }\n\n    transition_state (task, CLONE_STATE_CHECK_SERVER);\n}\n\nstatic CloneTask *\nclone_task_new (const char *repo_id,\n                const char *repo_name,\n                const char *token,\n                const char *worktree,\n                const char *passwd,\n                const char *email)\n{\n    CloneTask *task = g_new0 (CloneTask, 1);\n\n    memcpy (task->repo_id, repo_id, 37);\n    task->token = g_strdup (token);\n    task->worktree = g_strdup(worktree);\n    task->email = g_strdup(email);\n    if (repo_name)\n        task->repo_name = g_strdup(repo_name);\n    if (passwd)\n        task->passwd = g_strdup (passwd);\n    task->error = SYNC_ERROR_ID_NO_ERROR;\n\n    return task;\n}\n\nstatic void\nclone_task_free (CloneTask *task)\n{\n    g_free (task->tx_id);\n    g_free (task->worktree);\n    g_free (task->passwd);\n    g_free (task->token);\n    g_free (task->repo_name);\n    g_free (task->peer_addr);\n    g_free (task->peer_port);\n    g_free (task->email);\n    g_free (task->username);\n    g_free (task->random_key);\n    g_free (task->server_url);\n    g_free (task->effective_url);\n\n    g_free (task);\n}\n\nconst char *\nclone_task_state_to_str (int state)\n{\n    if (state < 0 || state >= N_CLONE_STATES)\n        return NULL;\n    return state_str[state];\n}\n\nSeafCloneManager *\nseaf_clone_manager_new (SeafileSession *session)\n{\n    SeafCloneManager *mgr = g_new0 (SeafCloneManager, 1);\n\n    char *db_path = g_build_path (\"/\", session->seaf_dir, CLONE_DB, NULL);\n    if (sqlite_open_db (db_path, &mgr->db) < 0) {\n        g_critical (\"[Clone mgr] Failed to open db\\n\");\n        g_free (db_path);\n        g_free (mgr);\n        return NULL;\n    }\n\n    mgr->seaf = session;\n    mgr->tasks = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                        g_free, (GDestroyNotify)clone_task_free);\n    return mgr;\n}\n\nstatic gboolean\nload_enc_info_cb (sqlite3_stmt *stmt, void *data)\n{\n    CloneTask *task = data;\n    int enc_version;\n    const char *random_key;\n\n    enc_version = sqlite3_column_int (stmt, 0);\n    random_key = (const char *)sqlite3_column_text (stmt, 1);\n\n    task->enc_version = enc_version;\n    task->random_key = g_strdup (random_key);\n\n    return FALSE;\n}\n\nstatic int\nload_clone_enc_info (CloneTask *task)\n{\n    char sql[256];\n\n    snprintf (sql, sizeof(sql),\n              \"SELECT enc_version, random_key FROM CloneEncInfo WHERE repo_id='%s'\",\n              task->repo_id);\n\n    if (sqlite_foreach_selected_row (task->manager->db, sql,\n                                     load_enc_info_cb, task) < 0)\n        return -1;\n\n    return 0;\n}\n\nstatic gboolean\nload_version_info_cb (sqlite3_stmt *stmt, void *data)\n{\n    CloneTask *task = data;\n    int repo_version;\n\n    repo_version = sqlite3_column_int (stmt, 0);\n\n    task->repo_version = repo_version;\n\n    return FALSE;\n}\n\nstatic void\nload_clone_repo_version_info (CloneTask *task)\n{\n    char sql[256];\n\n    snprintf (sql, sizeof(sql),\n              \"SELECT repo_version FROM CloneVersionInfo WHERE repo_id='%s'\",\n              task->repo_id);\n\n    sqlite_foreach_selected_row (task->manager->db, sql,\n                                 load_version_info_cb, task);\n}\n\nstatic gboolean\nload_more_info_cb (sqlite3_stmt *stmt, void *data)\n{\n    CloneTask *task = data;\n    json_error_t jerror;\n    json_t *object = NULL;\n    const char *more_info;\n\n    more_info = (const char *)sqlite3_column_text (stmt, 0);\n    object = json_loads (more_info, 0, &jerror);\n    if (!object) {\n        seaf_warning (\"Failed to load more sync info from json: %s.\\n\", jerror.text);\n        return FALSE;\n    }\n        \n    json_t *integer = json_object_get (object, \"is_readonly\");\n    task->is_readonly = json_integer_value (integer);\n    json_t *string = json_object_get (object, \"server_url\");\n    if (string)\n        task->server_url = g_strdup (json_string_value (string));\n    json_t *repo_salt = json_object_get (object, \"repo_salt\");\n    if (repo_salt)\n        task->repo_salt = g_strdup (json_string_value (repo_salt));\n    json_t *username = json_object_get (object, \"username\");\n    if (username)\n        task->username = g_strdup (json_string_value (username));\n    json_decref (object);\n\n    return FALSE;\n}\n\nstatic void\nload_clone_more_info (CloneTask *task)\n{\n    char sql[256];\n\n    snprintf (sql, sizeof(sql),\n              \"SELECT more_info FROM CloneTasksMoreInfo WHERE repo_id='%s'\",\n              task->repo_id);\n\n    sqlite_foreach_selected_row (task->manager->db, sql,\n                                 load_more_info_cb, task);\n}\n\nstatic gboolean\nrestart_task (sqlite3_stmt *stmt, void *data)\n{\n    SeafCloneManager *mgr = data;\n    const char *repo_id, *repo_name, *token, *worktree, *passwd;\n    const char *email;\n    CloneTask *task;\n    SeafRepo *repo;\n\n    repo_id = (const char *)sqlite3_column_text (stmt, 0);\n    repo_name = (const char *)sqlite3_column_text (stmt, 1);\n    token = (const char *)sqlite3_column_text (stmt, 2);\n    worktree = (const char *)sqlite3_column_text (stmt, 4);\n    passwd = (const char *)sqlite3_column_text (stmt, 5);\n    email = (const char *)sqlite3_column_text (stmt, 8);\n\n    task = clone_task_new (repo_id, repo_name, token,\n                           worktree, passwd, email);\n    task->manager = mgr;\n    /* Default to 1. */\n    task->enc_version = 1;\n\n    if (passwd && load_clone_enc_info (task) < 0) {\n        clone_task_free (task);\n        return TRUE;\n    }\n\n    task->repo_version = 0;\n    load_clone_repo_version_info (task);\n\n    load_clone_more_info (task);\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n\n    if (repo != NULL && repo->head != NULL) {\n        transition_state (task, CLONE_STATE_DONE);\n        return TRUE;\n    }\n\n    if (task->repo_version > 0) {\n        if (task->server_url) {\n            check_http_protocol (task);\n        } else {\n            transition_to_error (task, SYNC_ERROR_ID_GENERAL_ERROR);\n            return TRUE;\n        }\n    }\n\n    g_hash_table_insert (mgr->tasks, g_strdup(task->repo_id), task);\n\n    return TRUE;\n}\n\nint\nseaf_clone_manager_init (SeafCloneManager *mgr)\n{\n    const char *sql;\n\n    sql = \"CREATE TABLE IF NOT EXISTS CloneTasks \"\n        \"(repo_id TEXT PRIMARY KEY, repo_name TEXT, \"\n        \"token TEXT, dest_id TEXT,\"\n        \"worktree_parent TEXT, passwd TEXT, \"\n        \"server_addr TEXT, server_port TEXT, email TEXT);\";\n    if (sqlite_query_exec (mgr->db, sql) < 0)\n        return -1;\n\n    sql = \"CREATE TABLE IF NOT EXISTS CloneTasksMoreInfo \"\n        \"(repo_id TEXT PRIMARY KEY, more_info TEXT);\";\n    if (sqlite_query_exec (mgr->db, sql) < 0)\n        return -1;\n\n    sql = \"CREATE TABLE IF NOT EXISTS CloneEncInfo \"\n        \"(repo_id TEXT PRIMARY KEY, enc_version INTEGER, random_key TEXT);\";\n    if (sqlite_query_exec (mgr->db, sql) < 0)\n        return -1;\n\n    sql = \"CREATE TABLE IF NOT EXISTS CloneVersionInfo \"\n        \"(repo_id TEXT PRIMARY KEY, repo_version INTEGER);\";\n    if (sqlite_query_exec (mgr->db, sql) < 0)\n        return -1;\n\n    sql = \"CREATE TABLE IF NOT EXISTS CloneServerURL \"\n        \"(repo_id TEXT PRIMARY KEY, server_url TEXT);\";\n    if (sqlite_query_exec (mgr->db, sql) < 0)\n        return -1;\n\n    return 0;\n}\n\nstatic int check_connect_pulse (void *vmanager)\n{\n    SeafCloneManager *mgr = vmanager;\n    CloneTask *task;\n    GHashTableIter iter;\n    gpointer key, value;\n\n    g_hash_table_iter_init (&iter, mgr->tasks);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        task = value;\n        if (task->state == CLONE_STATE_ERROR &&\n            task->repo_version > 0 &&\n            sync_error_level (task->error) == SYNC_ERROR_LEVEL_NETWORK) {\n            task->error = SYNC_ERROR_ID_NO_ERROR;\n            check_http_protocol (task);\n        }\n    }\n\n    return TRUE;\n}\n\nint\nseaf_clone_manager_start (SeafCloneManager *mgr)\n{\n    mgr->check_timer = seaf_timer_new (check_connect_pulse, mgr,\n                                       CHECK_CONNECT_INTERVAL * 1000);\n\n    char *sql = \"SELECT * FROM CloneTasks\";\n    if (sqlite_foreach_selected_row (mgr->db, sql, restart_task, mgr) < 0)\n        return -1;\n\n    g_signal_connect (seaf, \"repo-http-fetched\",\n                      (GCallback)on_repo_http_fetched, mgr);\n\n    return 0;\n}\n\nstatic int\nsave_task_to_db (SeafCloneManager *mgr, CloneTask *task)\n{\n    char *sql;\n\n    if (task->passwd)\n        sql = sqlite3_mprintf (\"REPLACE INTO CloneTasks VALUES \"\n            \"('%q', '%q', '%q', NULL, '%q', '%q', NULL, NULL, '%q')\",\n                                task->repo_id, task->repo_name,\n                                task->token,\n                                task->worktree, task->passwd,\n                                task->email);\n    else\n        sql = sqlite3_mprintf (\"REPLACE INTO CloneTasks VALUES \"\n            \"('%q', '%q', '%q', NULL, '%q', NULL, NULL, NULL, '%q')\",\n                                task->repo_id, task->repo_name,\n                                task->token,\n                                task->worktree, task->email);\n\n    if (sqlite_query_exec (mgr->db, sql) < 0) {\n        sqlite3_free (sql);\n        return -1;\n    }\n    sqlite3_free (sql);\n\n    if (task->passwd && task->enc_version >= 2 && task->random_key) {\n        sql = sqlite3_mprintf (\"REPLACE INTO CloneEncInfo VALUES \"\n                               \"('%q', %d, '%q')\",\n                               task->repo_id, task->enc_version, task->random_key);\n        if (sqlite_query_exec (mgr->db, sql) < 0) {\n            sqlite3_free (sql);\n            return -1;\n        }\n        sqlite3_free (sql);\n    }\n\n    sql = sqlite3_mprintf (\"REPLACE INTO CloneVersionInfo VALUES \"\n                           \"('%q', %d)\",\n                           task->repo_id, task->repo_version);\n    if (sqlite_query_exec (mgr->db, sql) < 0) {\n        sqlite3_free (sql);\n        return -1;\n    }\n    sqlite3_free (sql);\n\n    if (task->is_readonly || task->server_url || task->repo_salt || task->username) {\n        /* need to store more info */\n        json_t *object = NULL;\n        gchar *info = NULL;\n\n        object = json_object ();\n        json_object_set_new (object, \"is_readonly\", json_integer (task->is_readonly));\n        if (task->server_url)\n            json_object_set_new (object, \"server_url\", json_string(task->server_url));\n        if (task->repo_salt)\n            json_object_set_new (object, \"repo_salt\", json_string(task->repo_salt));\n        if (task->username)\n            json_object_set_new (object, \"username\", json_string(task->username));\n    \n        info = json_dumps (object, 0);\n        json_decref (object);\n        sql = sqlite3_mprintf (\"REPLACE INTO CloneTasksMoreInfo VALUES \"\n                           \"('%q', '%q')\", task->repo_id, info);\n        if (sqlite_query_exec (mgr->db, sql) < 0) {\n            sqlite3_free (sql);\n            g_free (info);\n            return -1;\n        }\n        sqlite3_free (sql);\n        g_free (info);\n    }\n\n    return 0;\n}\n\nstatic int\nremove_task_from_db (SeafCloneManager *mgr, const char *repo_id)\n{\n    char sql[256];\n\n    snprintf (sql, sizeof(sql), \n              \"DELETE FROM CloneTasks WHERE repo_id='%s'\",\n              repo_id);\n    if (sqlite_query_exec (mgr->db, sql) < 0)\n        return -1;\n\n    snprintf (sql, sizeof(sql), \n              \"DELETE FROM CloneEncInfo WHERE repo_id='%s'\",\n              repo_id);\n    if (sqlite_query_exec (mgr->db, sql) < 0)\n        return -1;\n\n    snprintf (sql, sizeof(sql), \n              \"DELETE FROM CloneVersionInfo WHERE repo_id='%s'\",\n              repo_id);\n    if (sqlite_query_exec (mgr->db, sql) < 0)\n        return -1;\n\n    snprintf (sql, sizeof(sql), \n              \"DELETE FROM CloneTasksMoreInfo WHERE repo_id='%s'\",\n              repo_id);\n    if (sqlite_query_exec (mgr->db, sql) < 0)\n        return -1;\n\n    return 0;\n}\n\nstatic void\ntransition_state (CloneTask *task, int new_state)\n{\n    seaf_message (\"Transition clone state for %.8s from [%s] to [%s].\\n\",\n                  task->repo_id,\n                  state_str[task->state], state_str[new_state]);\n\n    if (new_state == CLONE_STATE_DONE ||\n        new_state == CLONE_STATE_CANCELED) {\n        /* Remove from db but leave in memory. */\n        remove_task_from_db (task->manager, task->repo_id);\n    }\n\n    task->state = new_state;\n}\n\nstatic void\ntransition_to_error (CloneTask *task, int error)\n{\n    seaf_message (\"Transition clone state for %.8s from [%s] to [error]: %s.\\n\",\n                  task->repo_id,\n                  state_str[task->state], \n                  sync_error_id_to_str(error));\n\n    task->state = CLONE_STATE_ERROR;\n    task->error = error;\n}\n\nstatic int\nadd_transfer_task (CloneTask *task, GError **error)\n{\n    int ret = http_tx_manager_add_download (seaf->http_tx_mgr,\n                                            task->repo_id,\n                                            task->repo_version,\n                                            task->effective_url,\n                                            task->token,\n                                            task->server_head_id,\n                                            TRUE,\n                                            task->passwd,\n                                            task->worktree,\n                                            task->http_protocol_version,\n                                            task->email,\n                                            task->username,\n                                            task->use_fileserver_port,\n                                            task->repo_name,\n                                            error);\n    if (ret < 0)\n        return -1;\n    task->tx_id = g_strdup(task->repo_id);\n    return 0;\n}\n\nstatic gboolean\nis_duplicate_task (SeafCloneManager *mgr, const char *repo_id)\n{\n    CloneTask *task = g_hash_table_lookup (mgr->tasks, repo_id);\n    if (task != NULL &&\n        task->state != CLONE_STATE_DONE &&\n        task->state != CLONE_STATE_CANCELED)\n        return TRUE;\n    return FALSE;\n}\n\nstatic gboolean\nis_worktree_of_repo (SeafCloneManager *mgr, const char *path)\n{\n    GList *repos, *ptr;\n    SeafRepo *repo;\n    GHashTableIter iter;\n    gpointer key, value;\n    CloneTask *task;\n\n    repos = seaf_repo_manager_get_repo_list (seaf->repo_mgr, -1, -1);\n    for (ptr = repos; ptr != NULL; ptr = ptr->next) {\n        repo = ptr->data;\n        if (g_strcmp0 (path, repo->worktree) == 0) {\n            g_list_free (repos);\n            return TRUE;\n        }\n    }\n    g_list_free (repos);\n\n    g_hash_table_iter_init (&iter, mgr->tasks);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        task = value;\n        if (task->state == CLONE_STATE_DONE ||\n            task->state == CLONE_STATE_CANCELED)\n            continue;\n        if (g_strcmp0 (path, task->worktree) == 0)\n            return TRUE;\n    }\n\n    return FALSE;\n}\n\nstatic char *\ntry_worktree (const char *worktree)\n{\n    char *tmp;\n    unsigned int cnt;\n\n    /* There is a repo name conflict, so we try to add a postfix */\n    cnt = 1;\n    while (1) {\n        tmp = g_strdup_printf(\"%s-%d\", worktree, cnt++);\n        if (g_access(tmp, F_OK) < 0) {\n            return tmp;\n        }\n\n        if (cnt == -1U) {\n            /* we have tried too much times, so give up */\n            g_free(tmp);\n            return NULL;\n        }\n\n        g_free(tmp);\n    }\n\n    /* XXX: never reach here */\n}\n\nstatic inline void\nremove_trail_slash (char *path)\n{\n    int tail = strlen (path) - 1;\n    while (tail >= 0 && (path[tail] == '/' || path[tail] == '\\\\'))\n        path[tail--] = '\\0';\n}\n\nstatic char *\nmake_worktree (SeafCloneManager *mgr,\n               const char *worktree,\n               gboolean dry_run,\n               GError **error)\n{\n    char *wt = g_strdup (worktree);\n    SeafStat st;\n    int rc;\n    char *ret;\n\n    remove_trail_slash (wt);\n\n    rc = seaf_stat (wt, &st);\n    if (rc < 0) {\n        ret = wt;\n        return ret;\n    } else if (!S_ISDIR(st.st_mode)) {\n        if (!dry_run) {\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,\n                         \"Invalid local directory\");\n            g_free (wt);\n            return NULL;\n        }\n        ret = try_worktree (wt);\n        g_free (wt);\n        return ret;\n    }\n\n    /* OK, wt is an existing dir. Let's see if it's the worktree for\n     * another repo. */\n    if (is_worktree_of_repo (mgr, wt)) {\n        if (!dry_run) {\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,\n                         \"Already in sync\");\n            g_free (wt);\n            return NULL;\n        }\n        ret = try_worktree (wt);\n        g_free (wt);\n    } else {\n        return wt;\n    }\n\n    return ret;\n}\n\n/*\n * Generate a conflict-free path to be used as worktree.\n * This worktree path can be used as the @worktree parameter\n * for seaf_clone_manager_add_task().\n */\nchar *\nseaf_clone_manager_gen_default_worktree (SeafCloneManager *mgr,\n                                         const char *worktree_parent,\n                                         const char *repo_name)\n{\n    char *wt = g_build_filename (worktree_parent, repo_name, NULL);\n    char *worktree;\n\n    worktree = make_worktree (mgr, wt, TRUE, NULL);\n    if (!worktree)\n        return wt;\n\n    g_free (wt);\n    return worktree;\n}\n\ninline static gboolean is_separator (char c)\n{\n    return (c == '/' || c == '\\\\');\n}\n\n/*\n * Returns < 0 if dira includes dirb or dira == dirb;\n * Returns 0 if no inclusive relationship;\n * Returns > 0 if dirb includes dira.\n */\nstatic int\ncheck_dir_inclusiveness (const char *dira, const char *dirb)\n{\n    char *a, *b;\n    char *p1, *p2;\n    int ret = 0;\n\n    a = g_strdup(dira);\n    b = g_strdup(dirb);\n    remove_trail_slash (a);\n    remove_trail_slash (b);\n\n    p1 = a;\n    p2 = b;\n    while (*p1 != 0 && *p2 != 0) {\n        /* Go to the last one in a path separator sequence. */\n        while (is_separator(*p1) && is_separator(p1[1]))\n            ++p1;\n        while (is_separator(*p2) && is_separator(p2[1]))\n            ++p2;\n\n        if (!(is_separator(*p1) && is_separator(*p2)) && *p1 != *p2)\n            goto out;\n\n        ++p1;\n        ++p2;\n    }\n\n    /* Example:\n     *            p1\n     * a: /abc/def/ghi\n     *            p2\n     * b: /abc/def\n     */\n    if (*p1 == 0 && *p2 == 0)\n        ret = -1;\n    else if (*p1 != 0 && is_separator(*p1))\n        ret = 1;\n    else if (*p2 != 0 && is_separator(*p2))\n        ret = -1;\n\nout:\n    g_free (a);\n    g_free (b);\n    return ret;\n}\n\ngboolean\nseaf_clone_manager_check_worktree_path (SeafCloneManager *mgr, const char *path, GError **error)\n{\n    GList *repos, *ptr;\n    SeafRepo *repo;\n    GHashTableIter iter;\n    gpointer key, value;\n    CloneTask *task;\n\n    if (check_dir_inclusiveness (path, seaf->seaf_dir) != 0 ||\n        /* It's OK if path is included by the default worktree parent. */\n        check_dir_inclusiveness (path, seaf->worktree_dir) < 0 ||\n        check_dir_inclusiveness (path, seaf->ccnet_dir) != 0) {\n        seaf_warning (\"Worktree path conflicts with seafile system path.\\n\");\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,\n                     \"Worktree conflicts system path\");\n        return FALSE;\n    }\n\n    repos = seaf_repo_manager_get_repo_list (seaf->repo_mgr, -1, -1);\n    for (ptr = repos; ptr != NULL; ptr = ptr->next) {\n        repo = ptr->data;\n        if (repo->worktree != NULL &&\n            check_dir_inclusiveness (path, repo->worktree) != 0) {\n            seaf_warning (\"Worktree path conflict with repo %s.\\n\", repo->name);\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,\n                         \"Worktree conflicts existing repo\");\n            g_list_free (repos);\n            return FALSE;\n        }\n    }\n    g_list_free (repos);\n\n    g_hash_table_iter_init (&iter, mgr->tasks);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        task = value;\n        if (task->state == CLONE_STATE_DONE ||\n            task->state == CLONE_STATE_CANCELED)\n            continue;\n        if (check_dir_inclusiveness (path, task->worktree) != 0) {\n            seaf_warning (\"Worktree path conflict with clone %.8s.\\n\",\n                          task->repo_id);\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,\n                         \"Worktree conflicts existing repo\");\n            return FALSE;\n        }\n    }\n\n    return TRUE;\n}\n\nstatic char *\nadd_task_common (SeafCloneManager *mgr, \n                 const char *repo_id,\n                 int repo_version,\n                 const char *repo_name,\n                 const char *token,\n                 const char *passwd,\n                 int enc_version,\n                 const char *random_key,\n                 const char *worktree,\n                 const char *email,\n                 const char *more_info,\n                 gboolean sync_wt_name,\n                 GError **error)\n{\n    CloneTask *task;\n\n    task = clone_task_new (repo_id, repo_name,\n                           token, worktree,\n                           passwd, email);\n    task->manager = mgr;\n    task->enc_version = enc_version;\n    task->random_key = g_strdup (random_key);\n    task->repo_version = repo_version;\n    task->sync_wt_name = sync_wt_name;\n    if (more_info) {\n        json_error_t jerror;\n        json_t *object = NULL;\n\n        object = json_loads (more_info, 0, &jerror);\n        if (!object) {\n            seaf_warning (\"Failed to load more sync info from json: %s.\\n\", jerror.text);\n            clone_task_free (task);\n            return NULL;\n        }\n        \n        json_t *integer = json_object_get (object, \"is_readonly\");\n        task->is_readonly = json_integer_value (integer);\n        json_t *string = json_object_get (object, \"server_url\");\n        if (string)\n            task->server_url = canonical_server_url (json_string_value (string));\n        json_t *repo_salt = json_object_get (object, \"repo_salt\");\n        if (repo_salt)\n            task->repo_salt = g_strdup (json_string_value (repo_salt));\n        json_t *username = json_object_get (object, \"username\");\n        if (username)\n            task->username = g_strdup (json_string_value (username));\n        integer = json_object_get (object, \"resync_enc_repo\");\n        task->resync_enc_repo = json_integer_value (integer);\n        json_decref (object);\n    }\n\n    if (save_task_to_db (mgr, task) < 0) {\n        seaf_warning (\"[Clone mgr] failed to save task.\\n\");\n        clone_task_free (task);\n        return NULL;\n    }\n\n    if (task->repo_version > 0) {\n        if (task->server_url) {\n            check_http_protocol (task);\n        } else {\n            clone_task_free (task);\n            return NULL;\n        }\n    } \n\n    /* The old task for this repo will be freed. */\n    g_hash_table_insert (mgr->tasks, g_strdup(task->repo_id), task);\n\n    return g_strdup(repo_id);\n}\n\nstatic gboolean\ncheck_encryption_args (const char *magic, int enc_version, const char *random_key,\n                       const char *repo_salt,\n                       GError **error)\n{\n    if (!magic) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                     \"Magic must be specified\");\n        return FALSE;\n    }\n\n    if (enc_version != 1 && enc_version != 2 && enc_version != 3 && enc_version != 4) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                     \"Unsupported enc version\");\n        return FALSE;\n    }\n\n    if (enc_version >= 2) {\n        if (!random_key || strlen(random_key) != 96) {\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                         \"Random key not specified\");\n            return FALSE;\n        }\n        if (enc_version >= 3 && (!(repo_salt) || strlen(repo_salt) != 64) ) {\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                         \"Repo salt not specified\");\n            return FALSE;\n        }\n    }\n\n    return TRUE;\n}\n\nstatic gboolean\ncheck_pwd_hash_encryption_args (const char *pwd_hash, int enc_version,\n                                const char *random_key,\n                                const char *repo_salt,\n                                GError **error)\n{\n    if (!pwd_hash) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                     \"Password hash must be specified\");\n        return FALSE;\n    }\n\n    if (enc_version != 1 && enc_version != 2 && enc_version != 3 && enc_version != 4) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                     \"Unsupported enc version\");\n        return FALSE;\n    }\n\n    if (enc_version >= 2) {\n        if (!random_key || strlen(random_key) != 96) {\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                         \"Random key not specified\");\n            return FALSE;\n        }\n        if (enc_version >= 3 && (!(repo_salt) || strlen(repo_salt) != 64) ) {\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                         \"Repo salt not specified\");\n            return FALSE;\n        }\n    }\n\n    return TRUE;\n}\n\nstatic gboolean\nis_wt_repo_name_same (const char *worktree, const char *repo_name)\n{\n    char *basename = g_path_get_basename (worktree);\n    gboolean ret = FALSE;\n    ret = (strcmp (basename, repo_name) == 0);\n    g_free (basename);\n    return ret;\n}\n\nchar *\nseaf_clone_manager_add_task (SeafCloneManager *mgr, \n                             const char *repo_id,\n                             int repo_version,\n                             const char *repo_name,\n                             const char *token,\n                             const char *passwd,\n                             const char *magic,\n                             int enc_version,\n                             const char *random_key,\n                             const char *worktree_in,\n                             const char *email,\n                             const char *more_info,\n                             GError **error)\n{\n    SeafRepo *repo = NULL;\n    char *worktree = NULL;\n    char *ret = NULL;\n    gboolean sync_wt_name = FALSE;\n    char *repo_salt = NULL;\n    char *algo = NULL;\n    char *params = NULL;\n    char *pwd_hash = NULL;\n    gboolean resync_enc_repo = FALSE;\n\n    if (!seaf->started) {\n        seaf_message (\"System not started, skip adding clone task.\\n\");\n        goto out;\n    }\n\n#ifdef USE_GPL_CRYPTO\n    if (repo_version == 0 || (passwd && enc_version < 2)) {\n        seaf_warning (\"Don't support syncing old version libraries.\\n\");\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                     \"Don't support syncing old version libraries\");\n        goto out;\n    }\n#endif\n\n    if (more_info) {\n        json_error_t jerror;\n        json_t *object;\n\n        object = json_loads (more_info, 0, &jerror);\n        if (!object) {\n            seaf_warning (\"Failed to load more sync info from json: %s.\\n\", jerror.text);\n            goto out;\n        }\n        json_t *string = json_object_get (object, \"repo_salt\");\n        if (string)\n            repo_salt = g_strdup (json_string_value (string));\n        json_t *integer = json_object_get (object, \"resync_enc_repo\");\n        resync_enc_repo = json_integer_value (integer);\n        string = json_object_get (object, \"pwd_hash_algo\");\n        if (string)\n            algo = g_strdup (json_string_value (string));\n        string = json_object_get (object, \"pwd_hash_params\");\n        if (string)\n            params = g_strdup (json_string_value (string));\n        string = json_object_get (object, \"pwd_hash\");\n        if (string)\n            pwd_hash = g_strdup (json_string_value (string));\n        json_decref (object);\n    }\n\n    if (passwd && algo &&\n        !check_pwd_hash_encryption_args (pwd_hash, enc_version, random_key, repo_salt, error)) {\n        goto out;\n    } else if (passwd &&\n        !check_encryption_args (magic, enc_version, random_key, repo_salt, error)) {\n        goto out;\n    }\n    /* After a repo was unsynced, the sync task may still be blocked in the\n     * network, so the repo is not actually deleted yet.\n     * In this case just return an error to the user.\n     */\n    SyncInfo *sync_info = seaf_sync_manager_get_sync_info (seaf->sync_mgr,\n                                                           repo_id);\n    if (sync_info && sync_info->in_sync) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,\n                     \"Repo already exists\");\n        goto out;\n    }\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n\n    if (repo != NULL && repo->head != NULL) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,\n                     \"Repo already exists\");\n        goto out;\n    }   \n\n    if (is_duplicate_task (mgr, repo_id)) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, \n                     \"Task is already in progress\");\n        goto out;\n    }\n\n    if (passwd && algo) {\n        if (seafile_pwd_hash_verify_repo_passwd(enc_version, repo_id, passwd, repo_salt, pwd_hash, algo, params) < 0) {\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,\n                         \"Incorrect password\");\n            goto out;\n        }\n    } else if (passwd &&\n        seafile_verify_repo_passwd(repo_id, passwd, magic, enc_version, repo_salt) < 0) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,\n                     \"Incorrect password\");\n        goto out;\n    }\n\n    if (!seaf_clone_manager_check_worktree_path (mgr, worktree_in, error))\n        goto out;\n\n    /* Return error if worktree_in conflicts with another repo or\n     * is not a directory.\n     */\n    worktree = make_worktree (mgr, worktree_in, FALSE, error);\n    if (!worktree) {\n        goto out;\n    }\n\n    /* Don't sync worktree folder name with library name later if they're not the same\n     * at the beginning.\n     */\n    sync_wt_name = is_wt_repo_name_same (worktree, repo_name);\n\n    /* If a repo was unsynced and then downloaded again, there may be\n     * a garbage record for this repo. We don't want the downloaded blocks\n     * be removed by GC.\n     */\n    if (repo_version > 0)\n        seaf_repo_manager_remove_garbage_repo (seaf->repo_mgr, repo_id);\n\n    /* Delete orphan information in the db in case the repo was corrupt. */\n    if (!repo && !resync_enc_repo)\n        seaf_repo_manager_remove_repo_ondisk (seaf->repo_mgr, repo_id, FALSE);\n\n    ret = add_task_common (mgr, repo_id, repo_version,\n                           repo_name, token, passwd,\n                           enc_version, random_key,\n                           worktree, email, more_info,\n                           sync_wt_name,\n                           error);\n\nout:\n    g_free (worktree);\n    g_free (repo_salt);\n    g_free (algo);\n    g_free (params);\n    g_free (pwd_hash);\n\n    return ret;\n}\n\nstatic char *\nmake_worktree_for_download (SeafCloneManager *mgr,\n                            const char *wt_tmp,\n                            GError **error)\n{\n    char *worktree;\n\n    if (g_access (wt_tmp, F_OK) == 0) {\n        worktree = try_worktree (wt_tmp);\n    } else {\n        worktree = g_strdup(wt_tmp);\n    }\n\n    if (!seaf_clone_manager_check_worktree_path (mgr, worktree, error)) {\n        g_free (worktree);\n        return NULL;\n    }\n\n    return worktree;\n}\n\nchar *\nseaf_clone_manager_add_download_task (SeafCloneManager *mgr, \n                                      const char *repo_id,\n                                      int repo_version,\n                                      const char *repo_name,\n                                      const char *token,\n                                      const char *passwd,\n                                      const char *magic,\n                                      int enc_version,\n                                      const char *random_key,\n                                      const char *wt_parent,\n                                      const char *email,\n                                      const char *more_info,\n                                      GError **error)\n{\n    SeafRepo *repo = NULL;\n    char *wt_tmp = NULL;\n    char *worktree = NULL;\n    char *ret = NULL;\n    char *repo_salt = NULL;\n    char *algo = NULL;\n    char *params = NULL;\n    char *pwd_hash = NULL;\n\n    if (!seaf->started) {\n        seaf_message (\"System not started, skip adding clone task.\\n\");\n        goto out;\n    }\n\n#ifdef USE_GPL_CRYPTO\n    if (repo_version == 0 || (passwd && enc_version < 2)) {\n        seaf_warning (\"Don't support syncing old version libraries.\\n\");\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                     \"Don't support syncing old version libraries\");\n        goto out;\n    }\n#endif\n\n    if (more_info) {\n        json_error_t jerror;\n        json_t *object;\n\n        object = json_loads (more_info, 0, &jerror);\n        if (!object) {\n            seaf_warning (\"Failed to load more sync info from json: %s.\\n\", jerror.text);\n            goto out;\n        }\n        json_t *string = json_object_get (object, \"repo_salt\");\n        if (string)\n            repo_salt = g_strdup (json_string_value (string));\n        string = json_object_get (object, \"pwd_hash_algo\");\n        if (string)\n            algo = g_strdup (json_string_value (string));\n        string = json_object_get (object, \"pwd_hash_params\");\n        if (string)\n            params = g_strdup (json_string_value (string));\n        string = json_object_get (object, \"pwd_hash\");\n        if (string)\n            pwd_hash = g_strdup (json_string_value (string));\n        json_decref (object);\n    }\n\n    if (passwd && algo &&\n        !check_pwd_hash_encryption_args (pwd_hash, enc_version, random_key, repo_salt, error)) {\n        goto out;\n    } else if (passwd &&\n        !check_encryption_args (magic, enc_version, random_key, repo_salt, error)) {\n        goto out;\n    }\n\n    /* After a repo was unsynced, the sync task may still be blocked in the\n     * network, so the repo is not actually deleted yet.\n     * In this case just return an error to the user.\n     */\n    SyncInfo *sync_info = seaf_sync_manager_get_sync_info (seaf->sync_mgr,\n                                                           repo_id);\n    if (sync_info && sync_info->in_sync) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,\n                     \"Repo already exists\");\n        goto out;\n    }\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n\n    if (repo != NULL && repo->head != NULL) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,\n                     \"Repo already exists\");\n        goto out;\n    }\n\n    if (is_duplicate_task (mgr, repo_id)) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, \n                     \"Task is already in progress\");\n        goto out;\n    }\n\n    if (passwd && algo) {\n        if (seafile_pwd_hash_verify_repo_passwd(enc_version, repo_id, passwd, repo_salt, pwd_hash, algo, params) < 0) {\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,\n                         \"Incorrect password\");\n            goto out;\n        }\n    } else if (passwd &&\n        seafile_verify_repo_passwd(repo_id, passwd, magic, enc_version, repo_salt) < 0) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,\n                     \"Incorrect password\");\n        goto out;\n    }\n\n    IgnoreReason reason;\n    if (should_ignore_on_checkout (repo_name, &reason)) {\n        if (reason == IGNORE_REASON_END_SPACE_PERIOD)\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                         \"Library name ends with space or period character\");\n        else\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                         \"Library name contains invalid characters such as ':', '*', '|', '?'\");\n        goto out;\n    }\n\n    wt_tmp = g_build_filename (wt_parent, repo_name, NULL);\n\n    worktree = make_worktree_for_download (mgr, wt_tmp, error);\n    if (!worktree) {\n        goto out;\n    }\n\n    /* If a repo was unsynced and then downloaded again, there may be\n     * a garbage record for this repo. We don't want the downloaded blocks\n     * be removed by GC.\n     */\n    if (repo_version > 0)\n        seaf_repo_manager_remove_garbage_repo (seaf->repo_mgr, repo_id);\n\n    /* Delete orphan information in the db in case the repo was corrupt. */\n    if (!repo)\n        seaf_repo_manager_remove_repo_ondisk (seaf->repo_mgr, repo_id, FALSE);\n\n    ret = add_task_common (mgr, repo_id, repo_version,\n                           repo_name, token, passwd,\n                           enc_version, random_key,\n                           worktree, email, more_info,\n                           TRUE, error);\n\nout:\n    g_free (worktree);\n    g_free (wt_tmp);\n    g_free (repo_salt);\n    g_free (algo);\n    g_free (params);\n    g_free (pwd_hash);\n\n    return ret;\n}\n\nint\nseaf_clone_manager_cancel_task (SeafCloneManager *mgr,\n                                const char *repo_id)\n{\n    CloneTask *task;\n\n    if (!seaf->started) {\n        seaf_message (\"System not started, skip canceling clone task.\\n\");\n        return -1;\n    }\n\n    task = g_hash_table_lookup (mgr->tasks, repo_id);\n    if (!task)\n        return -1;\n\n    switch (task->state) {\n    case CLONE_STATE_INIT:\n    case CLONE_STATE_CONNECT:\n    case CLONE_STATE_ERROR:\n        transition_state (task, CLONE_STATE_CANCELED);\n        break;\n    case CLONE_STATE_CHECK_SERVER:\n        transition_state (task, CLONE_STATE_CANCEL_PENDING);\n    case CLONE_STATE_FETCH:\n        http_tx_manager_cancel_task (seaf->http_tx_mgr,\n                                     task->repo_id,\n                                     HTTP_TASK_TYPE_DOWNLOAD);\n        transition_state (task, CLONE_STATE_CANCEL_PENDING);\n        break;\n    case CLONE_STATE_INDEX:\n    case CLONE_STATE_CHECKOUT:\n    case CLONE_STATE_MERGE:\n    case CLONE_STATE_CHECK_PROTOCOL:\n        /* We cannot cancel an in-progress checkout, just\n         * wait until it finishes.\n         */\n        transition_state (task, CLONE_STATE_CANCEL_PENDING);\n        break;\n    case CLONE_STATE_CANCEL_PENDING:\n        break;\n    default:\n        seaf_warning (\"[Clone mgr] cannot cancel a not-running task.\\n\");\n        return -1;\n    }\n\n    return 0;\n}\n\nCloneTask *\nseaf_clone_manager_get_task (SeafCloneManager *mgr,\n                             const char *repo_id)\n{\n    return (CloneTask *) g_hash_table_lookup (mgr->tasks, repo_id);\n}\n\nGList *\nseaf_clone_manager_get_tasks (SeafCloneManager *mgr)\n{\n    return g_hash_table_get_values (mgr->tasks);\n}\n\nstatic void\ncheck_folder_permissions (CloneTask *task);\n\nstatic void\non_repo_http_fetched (SeafileSession *seaf,\n                      HttpTxTask *tx_task,\n                      SeafCloneManager *mgr)\n{\n    CloneTask *task;\n\n    /* Only handle clone task. */\n    if (!tx_task->is_clone)\n        return;\n\n    task = g_hash_table_lookup (mgr->tasks, tx_task->repo_id);\n    g_return_if_fail (task != NULL);\n\n    if (tx_task->state == HTTP_TASK_STATE_CANCELED) {\n        /* g_assert (task->state == CLONE_STATE_CANCEL_PENDING); */\n        transition_state (task, CLONE_STATE_CANCELED);\n        return;\n    } else if (tx_task->state == HTTP_TASK_STATE_ERROR) {\n        transition_to_error (task, tx_task->error);\n        return;\n    }\n\n    SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr,\n                                                 tx_task->repo_id);\n    if (repo == NULL) {\n        seaf_warning (\"[Clone mgr] cannot find repo %s after fetched.\\n\", \n                   tx_task->repo_id);\n        transition_to_error (task, SYNC_ERROR_ID_LOCAL_DATA_CORRUPT);\n        return;\n    }\n\n    seaf_repo_manager_set_repo_token (seaf->repo_mgr, repo, task->token);\n    seaf_repo_manager_set_repo_email (seaf->repo_mgr, repo, task->email);\n    seaf_repo_manager_set_repo_relay_info (seaf->repo_mgr, repo->id,\n                                           task->peer_addr, task->peer_port);\n    if (task->server_url) {\n        seaf_repo_manager_set_repo_property (seaf->repo_mgr,\n                                             repo->id,\n                                             REPO_PROP_SERVER_URL,\n                                             task->server_url);\n    }\n    if (task->username) {\n        seaf_repo_manager_set_repo_property (seaf->repo_mgr,\n                                             repo->id,\n                                             REPO_PROP_USERNAME,\n                                             task->username);\n    }\n\n    check_folder_permissions (task);\n}\n\nstatic void\ncheck_folder_perms_done (HttpFolderPerms *result, void *user_data)\n{\n    CloneTask *task = user_data;\n    GList *ptr;\n    HttpFolderPermRes *res;\n\n    SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr,\n                                                 task->repo_id);\n    if (repo == NULL) {\n        seaf_warning (\"[Clone mgr] cannot find repo %s after fetched.\\n\", \n                   task->repo_id);\n        transition_to_error (task, SYNC_ERROR_ID_LOCAL_DATA_CORRUPT);\n        return;\n    }\n\n    if (!result->success) {\n        goto out;\n    }\n\n    for (ptr = result->results; ptr; ptr = ptr->next) {\n        res = ptr->data;\n\n        seaf_repo_manager_update_folder_perms (seaf->repo_mgr, res->repo_id,\n                                               FOLDER_PERM_TYPE_USER,\n                                               res->user_perms);\n        seaf_repo_manager_update_folder_perms (seaf->repo_mgr, res->repo_id,\n                                               FOLDER_PERM_TYPE_GROUP,\n                                               res->group_perms);\n        seaf_repo_manager_update_folder_perm_timestamp (seaf->repo_mgr,\n                                                        res->repo_id,\n                                                        res->timestamp);\n    }\n\nout:\n    mark_clone_done_v2 (repo, task);\n}\n\nstatic void\ncheck_folder_permissions (CloneTask *task)\n{\n    SeafRepo *repo = NULL;\n    HttpFolderPermReq *req;\n    GList *requests = NULL;\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, task->repo_id);\n    if (repo == NULL) {\n        seaf_warning (\"[Clone mgr] cannot find repo %s after fetched.\\n\", \n                      task->repo_id);\n        transition_to_error (task, SYNC_ERROR_ID_LOCAL_DATA_CORRUPT);\n        return;\n    }\n\n    if (!seaf_repo_manager_server_is_pro (seaf->repo_mgr, task->server_url)) {\n        mark_clone_done_v2 (repo, task);\n        return;\n    }\n\n    req = g_new0 (HttpFolderPermReq, 1);\n    memcpy (req->repo_id, task->repo_id, 36);\n    req->token = g_strdup(task->token);\n    req->timestamp = 0;\n\n    requests = g_list_append (requests, req);\n\n    /* The requests list will be freed in http tx manager. */\n    if (http_tx_manager_get_folder_perms (seaf->http_tx_mgr,\n                                          task->effective_url,\n                                          task->use_fileserver_port,\n                                          requests,\n                                          check_folder_perms_done,\n                                          task) < 0)\n        transition_to_error (task, SYNC_ERROR_ID_NOT_ENOUGH_MEMORY);\n}\n"
  },
  {
    "path": "daemon/clone-mgr.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef CLONE_MGR_H\n#define CLONE_MGR_H\n\n#include <glib.h>\n#include \"db.h\"\n\nstruct _SeafileSession;\n\ntypedef struct _CloneTask CloneTask;\ntypedef struct _SeafCloneManager SeafCloneManager;\n\nenum {\n    CLONE_STATE_INIT,\n    CLONE_STATE_CHECK_SERVER,\n    CLONE_STATE_FETCH,\n    CLONE_STATE_DONE,\n    CLONE_STATE_ERROR,\n    CLONE_STATE_CANCEL_PENDING,\n    CLONE_STATE_CANCELED,\n    /* States only used by non-http protocol. */\n    CLONE_STATE_CONNECT,\n    CLONE_STATE_CHECK_PROTOCOL,\n    CLONE_STATE_INDEX,\n    CLONE_STATE_CHECKOUT,\n    CLONE_STATE_MERGE,\n    N_CLONE_STATES,\n};\n\nstruct _CloneTask {\n    SeafCloneManager    *manager;\n    int                  state;\n    int                  error;\n    char                 repo_id[37];\n    int                  repo_version;\n    char                 peer_id[41];\n    char                *peer_addr;\n    char                *peer_port; \n    char                *token;\n    char                *email;\n    char                *username;\n    char                *repo_name; /* For better display. */\n    char                *tx_id;\n    char                *worktree;\n    char                *passwd;\n    int                  enc_version;\n    char                *repo_salt;\n    char                *random_key;\n    gboolean            resync_enc_repo;\n    char                 root_id[41];\n    gboolean             is_readonly;\n    /* Set to true when the local folder name is the same as library name.\n     * Worktree folder name will be kept in sync with library name if this is true.\n     */\n    gboolean             sync_wt_name;\n\n    /* Http sync fields */\n    char                *server_url;\n    char                *effective_url;\n    gboolean             use_fileserver_port;\n    int                  http_protocol_version;\n    char                 server_head_id[41];\n};\n\nconst char *\nclone_task_state_to_str (int state);\n\nstruct _SeafCloneManager {\n    struct _SeafileSession  *seaf;\n    sqlite3                 *db;\n    GHashTable              *tasks;\n    struct SeafTimer       *check_timer;\n};\n\nSeafCloneManager *\nseaf_clone_manager_new (struct _SeafileSession *session);\n\nint\nseaf_clone_manager_init (SeafCloneManager *mgr);\n\nint\nseaf_clone_manager_start (SeafCloneManager *mgr);\n\nchar *\nseaf_clone_manager_gen_default_worktree (SeafCloneManager *mgr,\n                                         const char *worktree_parent,\n                                         const char *repo_name);\n\nchar *\nseaf_clone_manager_add_task (SeafCloneManager *mgr, \n                             const char *repo_id,\n                             int repo_version,\n                             const char *repo_name,\n                             const char *token,\n                             const char *passwd,\n                             const char *magic,\n                             int enc_version,\n                             const char *random_key,\n                             const char *worktree,\n                             const char *email,\n                             const char *more_info,\n                             GError **error);\n\n/*\n * Similar to seaf_clone_manager_add_task. \n * But create a new dir for worktree under @wt_parent.\n * The semantics is to \"download\" the repo into @wt_parent.\n */\nchar *\nseaf_clone_manager_add_download_task (SeafCloneManager *mgr, \n                                      const char *repo_id,\n                                      int repo_version,\n                                      const char *repo_name,\n                                      const char *token,\n                                      const char *passwd,\n                                      const char *magic,\n                                      int enc_version,\n                                      const char *random_key,\n                                      const char *wt_parent,\n                                      const char *email,\n                                      const char *more_info,\n                                      GError **error);\n\nint\nseaf_clone_manager_cancel_task (SeafCloneManager *mgr,\n                                const char *repo_id);\n\nCloneTask *\nseaf_clone_manager_get_task (SeafCloneManager *mgr,\n                             const char *repo_id);\n\nGList *\nseaf_clone_manager_get_tasks (SeafCloneManager *mgr);\n\ngboolean\nseaf_clone_manager_check_worktree_path (SeafCloneManager *mgr, const char *path, GError **error);\n\n#endif\n"
  },
  {
    "path": "daemon/filelock-mgr.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#include <pthread.h>\n\n#include \"seafile-session.h\"\n#include \"filelock-mgr.h\"\n#include \"set-perm.h\"\n#include \"log.h\"\n\n#include \"db.h\"\n\nstruct _FilelockMgrPriv {\n    GHashTable *repo_locked_files;\n    pthread_mutex_t hash_lock;\n    sqlite3 *db;\n    pthread_mutex_t db_lock;\n};\ntypedef struct _FilelockMgrPriv FilelockMgrPriv;\n\ntypedef struct _LockInfo {\n    int locked_by_me;\n} LockInfo;\n\n/* When a file is locked by me, it can have two reasons:\n * - Locked by the user manually\n * - Auto-Locked by Seafile when it detects Office opens the file.\n */\n\nstruct _SeafFilelockManager *\nseaf_filelock_manager_new (struct _SeafileSession *session)\n{\n    SeafFilelockManager *mgr = g_new0 (SeafFilelockManager, 1);\n    FilelockMgrPriv *priv = g_new0 (FilelockMgrPriv, 1);\n\n    mgr->session = session;\n    mgr->priv = priv;\n\n    priv->repo_locked_files = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                                     g_free,\n                                                     (GDestroyNotify)g_hash_table_destroy);\n\n    pthread_mutex_init (&priv->hash_lock, NULL);\n    pthread_mutex_init (&priv->db_lock, NULL);\n\n    return mgr;\n}\n\nstatic void\nlock_info_free (LockInfo *info)\n{\n    g_free (info);\n}\n\nstatic gboolean\nload_locked_files (sqlite3_stmt *stmt, void *data)\n{\n    GHashTable *repo_locked_files = data, *files;\n    const char *repo_id, *path;\n    int locked_by_me;\n\n    repo_id = (const char *)sqlite3_column_text (stmt, 0);\n    path = (const char *)sqlite3_column_text (stmt, 1);\n    locked_by_me = sqlite3_column_int (stmt, 2);\n\n    files = g_hash_table_lookup (repo_locked_files, repo_id);\n    if (!files) {\n        files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)lock_info_free);\n        g_hash_table_insert (repo_locked_files, g_strdup(repo_id), files);\n    }\n\n    char *key = g_strdup(path);\n    LockInfo *info = g_new0 (LockInfo, 1);\n    info->locked_by_me = locked_by_me;\n    g_hash_table_replace (files, key, info);\n\n    return TRUE;\n}\n\nint\nseaf_filelock_manager_init (SeafFilelockManager *mgr)\n{\n    char *db_path;\n    sqlite3 *db;\n    char *sql;\n\n    db_path = g_build_filename (seaf->seaf_dir, \"filelocks.db\", NULL);\n    if (sqlite_open_db (db_path, &db) < 0)\n        return -1;\n    g_free (db_path);\n    mgr->priv->db = db;\n\n    sql = \"CREATE TABLE IF NOT EXISTS ServerLockedFiles (\"\n        \"repo_id TEXT, path TEXT, locked_by_me INTEGER);\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE INDEX IF NOT EXISTS server_locked_files_repo_id_idx \"\n        \"ON ServerLockedFiles (repo_id);\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE TABLE IF NOT EXISTS ServerLockedFilesTimestamp (\"\n        \"repo_id TEXT, timestamp INTEGER, PRIMARY KEY (repo_id));\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"SELECT repo_id, path, locked_by_me FROM ServerLockedFiles\";\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n    pthread_mutex_lock (&mgr->priv->hash_lock);\n\n    if (sqlite_foreach_selected_row (mgr->priv->db, sql,\n                                     load_locked_files,\n                                     mgr->priv->repo_locked_files) < 0) {\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        pthread_mutex_unlock (&mgr->priv->hash_lock);\n        g_hash_table_destroy (mgr->priv->repo_locked_files);\n        return -1;\n    }\n\n    pthread_mutex_unlock (&mgr->priv->hash_lock);\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    return 0;\n}\n\nstatic void\ninit_locks (gpointer key, gpointer value, gpointer user_data)\n{\n    char *repo_id = user_data;\n    char *path = key;\n    LockInfo *info = value;\n\n    if (!info->locked_by_me) {\n        seaf_filelock_manager_lock_wt_file (seaf->filelock_mgr,\n                                            repo_id,\n                                            path);\n    }\n}\n\nint\nseaf_filelock_manager_start (SeafFilelockManager *mgr)\n{\n    GHashTableIter iter;\n    gpointer key, value;\n    char *repo_id;\n    GHashTable *locks;\n\n    pthread_mutex_lock (&mgr->priv->hash_lock);\n\n    g_hash_table_iter_init (&iter, mgr->priv->repo_locked_files);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        repo_id = key;\n        locks = value;\n        g_hash_table_foreach (locks, init_locks, repo_id);\n    }\n\n    pthread_mutex_unlock (&mgr->priv->hash_lock);\n\n    return 0;\n}\n\ngboolean\nseaf_filelock_manager_is_file_locked (SeafFilelockManager *mgr,\n                                      const char *repo_id,\n                                      const char *path)\n{\n    gboolean ret;\n\n    pthread_mutex_lock (&mgr->priv->hash_lock);\n\n    GHashTable *locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);\n    if (!locks) {\n        pthread_mutex_unlock (&mgr->priv->hash_lock);\n        return FALSE;\n    }\n\n    LockInfo *info = g_hash_table_lookup (locks, path);\n    if (!info) {\n        pthread_mutex_unlock (&mgr->priv->hash_lock);\n        return FALSE;\n    }\n    ret = !info->locked_by_me;\n\n    pthread_mutex_unlock (&mgr->priv->hash_lock);\n    return ret;\n}\n\ngboolean\nseaf_filelock_manager_is_file_locked_by_me (SeafFilelockManager *mgr,\n                                            const char *repo_id,\n                                            const char *path)\n{\n    gboolean ret;\n\n    pthread_mutex_lock (&mgr->priv->hash_lock);\n\n    GHashTable *locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);\n    if (!locks) {\n        pthread_mutex_unlock (&mgr->priv->hash_lock);\n        return FALSE;\n    }\n\n    LockInfo *info = g_hash_table_lookup (locks, path);\n    if (!info) {\n        pthread_mutex_unlock (&mgr->priv->hash_lock);\n        return FALSE;\n    }\n    ret = (info->locked_by_me > 0);\n\n    pthread_mutex_unlock (&mgr->priv->hash_lock);\n    return ret;\n}\n\nint\nseaf_filelock_manager_get_lock_status (SeafFilelockManager *mgr,\n                                       const char *repo_id,\n                                       const char *path)\n{\n    int ret;\n\n    pthread_mutex_lock (&mgr->priv->hash_lock);\n\n    GHashTable *locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);\n    if (!locks) {\n        pthread_mutex_unlock (&mgr->priv->hash_lock);\n        return FILE_NOT_LOCKED;\n    }\n\n    LockInfo *info = g_hash_table_lookup (locks, path);\n    if (!info) {\n        pthread_mutex_unlock (&mgr->priv->hash_lock);\n        return FILE_NOT_LOCKED;\n    }\n\n    if (info->locked_by_me == LOCKED_MANUAL)\n        ret = FILE_LOCKED_BY_ME_MANUAL;\n    else if (info->locked_by_me == LOCKED_AUTO)\n        ret = FILE_LOCKED_BY_ME_AUTO;\n    else\n        ret = FILE_LOCKED_BY_OTHERS;\n\n    pthread_mutex_unlock (&mgr->priv->hash_lock);\n    return ret;\n}\n\nvoid\nseaf_filelock_manager_lock_wt_file (SeafFilelockManager *mgr,\n                                    const char *repo_id,\n                                    const char *path)\n{\n    SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo)\n        return;\n\n    char *fullpath = g_build_filename (repo->worktree, path, NULL);\n    if (seaf_util_exists (fullpath))\n        seaf_set_path_permission (fullpath, SEAF_PATH_PERM_RO, FALSE);\n    g_free (fullpath);\n}\n\nvoid\nseaf_filelock_manager_unlock_wt_file (SeafFilelockManager *mgr,\n                                      const char *repo_id,\n                                      const char *path)\n{\n    SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo)\n        return;\n\n    char *fullpath = g_build_filename (repo->worktree, path, NULL);\n\n#ifdef WIN32\n    if (seaf_util_exists (fullpath))\n        seaf_unset_path_permission (fullpath, FALSE);\n#else\n    if (seaf_util_exists (fullpath))\n        seaf_set_path_permission (fullpath, SEAF_PATH_PERM_RW, FALSE);\n#endif\n    g_free (fullpath);\n}\n\nstatic void\nupdate_in_memory (SeafFilelockManager *mgr, const char *repo_id, GHashTable *new_locks)\n{\n    GHashTable *repo_hash = mgr->priv->repo_locked_files;\n\n    pthread_mutex_lock (&mgr->priv->hash_lock);\n\n    GHashTable *locks = g_hash_table_lookup (repo_hash, repo_id);\n\n    if (!locks) {\n        if (g_hash_table_size (new_locks) == 0) {\n            pthread_mutex_unlock (&mgr->priv->hash_lock);\n            return;\n        }\n        locks = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                       g_free, (GDestroyNotify)lock_info_free);\n        g_hash_table_insert (repo_hash, g_strdup(repo_id), locks);\n    }\n\n    GHashTableIter iter;\n    gpointer key, value;\n    gpointer new_key, new_val;\n    char *path;\n#ifdef WIN32\n    char *fullpath;\n#endif\n    LockInfo *info;\n    gboolean exists;\n    int locked_by_me;\n    SeafRepo *repo;\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo) {\n        seaf_warning (\"Failed to find repo %s\\n\", repo_id);\n        return;\n    }\n\n    g_hash_table_iter_init (&iter, locks);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        path = key;\n        info = value;\n\n        exists = g_hash_table_lookup_extended (new_locks, path, &new_key, &new_val);\n        if (!exists) {\n#ifdef WIN32\n            fullpath = g_build_path (\"/\", repo->worktree, path, NULL);\n            seaf_sync_manager_add_refresh_path (seaf->sync_mgr, fullpath);\n            g_free (fullpath);\n#endif\n            seaf_filelock_manager_unlock_wt_file (mgr, repo_id, path);\n            g_hash_table_iter_remove (&iter);\n        } else {\n            locked_by_me = (int)(long)new_val;\n            if (!info->locked_by_me && locked_by_me) {\n#ifdef WIN32\n                fullpath = g_build_path (\"/\", repo->worktree, path, NULL);\n                seaf_sync_manager_add_refresh_path (seaf->sync_mgr, fullpath);\n                g_free (fullpath);\n#endif\n                seaf_filelock_manager_unlock_wt_file (mgr, repo_id, path);\n                info->locked_by_me = locked_by_me;\n            } else if (info->locked_by_me && !locked_by_me) {\n#ifdef WIN32\n                fullpath = g_build_path (\"/\", repo->worktree, path, NULL);\n                seaf_sync_manager_add_refresh_path (seaf->sync_mgr, fullpath);\n                g_free (fullpath);\n#endif\n                seaf_filelock_manager_lock_wt_file (mgr, repo_id, path);\n                info->locked_by_me = locked_by_me;\n            }\n        }\n    }\n\n    g_hash_table_iter_init (&iter, new_locks);\n    while (g_hash_table_iter_next (&iter, &new_key, &new_val)) {\n        path = new_key;\n        locked_by_me = (int)(long)new_val;\n        if (!g_hash_table_lookup (locks, path)) {\n            info = g_new0 (LockInfo, 1);\n            info->locked_by_me = locked_by_me;\n            g_hash_table_insert (locks, g_strdup(path), info);\n#ifdef WIN32\n            fullpath = g_build_path (\"/\", repo->worktree, path, NULL);\n            seaf_sync_manager_add_refresh_path (seaf->sync_mgr, fullpath);\n            g_free (fullpath);\n#endif\n            if (!locked_by_me) {\n                seaf_filelock_manager_lock_wt_file (mgr, repo_id, path);\n            }\n        }\n    }\n\n    pthread_mutex_unlock (&mgr->priv->hash_lock);\n}\n\nstatic gint\ncompare_paths (gconstpointer a, gconstpointer b)\n{\n    const char *patha = a, *pathb = b;\n\n    return strcmp (patha, pathb);\n}\n\nstatic int\nupdate_db (SeafFilelockManager *mgr, const char *repo_id)\n{\n    char *sql;\n    sqlite3_stmt *stmt;\n    GHashTable *locks;\n    GList *paths, *ptr;\n    char *path;\n    LockInfo *info;\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    sql = \"DELETE FROM ServerLockedFiles WHERE repo_id = ?\";\n    stmt = sqlite_query_prepare (mgr->priv->db, sql);\n    sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);\n    if (sqlite3_step (stmt) != SQLITE_DONE) {\n        seaf_warning (\"Failed to remove server locked files for %.8s: %s.\\n\",\n                      repo_id, sqlite3_errmsg (mgr->priv->db));\n        sqlite3_finalize (stmt);\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        return -1;\n    }\n    sqlite3_finalize (stmt);\n\n    locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);\n    if (!locks || g_hash_table_size (locks) == 0) {\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        return 0;\n    }\n\n    paths = g_hash_table_get_keys (locks);\n    paths = g_list_sort (paths, compare_paths);\n\n    sql = \"INSERT INTO ServerLockedFiles (repo_id, path, locked_by_me) VALUES (?, ?, ?)\";\n    stmt = sqlite_query_prepare (mgr->priv->db, sql);\n\n    for (ptr = paths; ptr; ptr = ptr->next) {\n        path = ptr->data;\n        info = g_hash_table_lookup (locks, path);\n\n        sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);\n        sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT);\n        sqlite3_bind_int (stmt, 3, info->locked_by_me);\n\n        if (sqlite3_step (stmt) != SQLITE_DONE) {\n            seaf_warning (\"Failed to insert server file lock for %.8s: %s.\\n\",\n                          repo_id, sqlite3_errmsg (mgr->priv->db));\n            sqlite3_finalize (stmt);\n            pthread_mutex_unlock (&mgr->priv->db_lock);\n            return -1;\n        }\n\n        sqlite3_reset (stmt);\n        sqlite3_clear_bindings (stmt);\n    }\n\n    sqlite3_finalize (stmt);\n    g_list_free (paths);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    return 0;\n}\n\nint\nseaf_filelock_manager_update (SeafFilelockManager *mgr,\n                              const char *repo_id,\n                              GHashTable *new_locked_files)\n{\n    update_in_memory (mgr, repo_id, new_locked_files);\n\n    int ret = update_db (mgr, repo_id);\n\n    return ret;\n}\n\nint\nseaf_filelock_manager_update_timestamp (SeafFilelockManager *mgr,\n                                        const char *repo_id,\n                                        gint64 timestamp)\n{\n    char sql[256];\n    int ret;\n\n    snprintf (sql, sizeof(sql),\n              \"REPLACE INTO ServerLockedFilesTimestamp VALUES ('%s', %\"G_GINT64_FORMAT\")\",\n              repo_id, timestamp);\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    ret = sqlite_query_exec (mgr->priv->db, sql);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    return ret;\n}\n\ngint64\nseaf_filelock_manager_get_timestamp (SeafFilelockManager *mgr,\n                                     const char *repo_id)\n{\n    char sql[256];\n    gint64 ret;\n\n    sqlite3_snprintf (sizeof(sql), sql,\n                      \"SELECT timestamp FROM ServerLockedFilesTimestamp WHERE repo_id = '%q'\",\n                      repo_id);\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    ret = sqlite_get_int64 (mgr->priv->db, sql);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    return ret;\n}\n\nint\nseaf_filelock_manager_remove (SeafFilelockManager *mgr,\n                              const char *repo_id)\n{\n    char *sql;\n    sqlite3_stmt *stmt;\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    sql = \"DELETE FROM ServerLockedFiles WHERE repo_id = ?\";\n    stmt = sqlite_query_prepare (mgr->priv->db, sql);\n    sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);\n    if (sqlite3_step (stmt) != SQLITE_DONE) {\n        seaf_warning (\"Failed to remove server locked files for %.8s: %s.\\n\",\n                      repo_id, sqlite3_errmsg (mgr->priv->db));\n        sqlite3_finalize (stmt);\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        return -1;\n    }\n    sqlite3_finalize (stmt);\n\n    sql = \"DELETE FROM ServerLockedFilesTimestamp WHERE repo_id = ?\";\n    stmt = sqlite_query_prepare (mgr->priv->db, sql);\n    sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);\n    if (sqlite3_step (stmt) != SQLITE_DONE) {\n        seaf_warning (\"Failed to remove server locked files timestamp for %.8s: %s.\\n\",\n                      repo_id, sqlite3_errmsg (mgr->priv->db));\n        sqlite3_finalize (stmt);\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        return -1;\n    }\n    sqlite3_finalize (stmt);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    pthread_mutex_lock (&mgr->priv->hash_lock);\n    g_hash_table_remove (mgr->priv->repo_locked_files, repo_id);\n    pthread_mutex_unlock (&mgr->priv->hash_lock);\n\n    return 0;\n}\n\n#ifdef WIN32\n\nstatic void\nrefresh_locked_path_status (const char *repo_id, const char *path)\n{\n    SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo)\n        return;\n\n    char *fullpath = g_build_path (\"/\", repo->worktree, path, NULL);\n    seaf_sync_manager_refresh_path (seaf->sync_mgr, fullpath);\n    g_free (fullpath);\n}\n\n#endif\n\nstatic int\nmark_file_locked_in_db (SeafFilelockManager *mgr,\n                        const char *repo_id,\n                        const char *path,\n                        int locked_by_me)\n{\n    char *sql;\n    sqlite3_stmt *stmt;\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    sql = \"REPLACE INTO ServerLockedFiles (repo_id, path, locked_by_me) VALUES (?, ?, ?)\";\n    stmt = sqlite_query_prepare (mgr->priv->db, sql);\n    sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);\n    sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT);\n    sqlite3_bind_int (stmt, 3, locked_by_me);\n    if (sqlite3_step (stmt) != SQLITE_DONE) {\n        seaf_warning (\"Failed to update server locked files for %.8s: %s.\\n\",\n                      repo_id, sqlite3_errmsg (mgr->priv->db));\n        sqlite3_finalize (stmt);\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        return -1;\n    }\n    sqlite3_finalize (stmt);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    return 0;\n}\n\nint\nseaf_filelock_manager_mark_file_locked (SeafFilelockManager *mgr,\n                                        const char *repo_id,\n                                        const char *path,\n                                        FileLockType type)\n{\n    GHashTable *locks;\n    LockInfo *info;\n\n    pthread_mutex_lock (&mgr->priv->hash_lock);\n\n    locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);\n    if (!locks) {\n        locks = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                       g_free, (GDestroyNotify)lock_info_free);\n        g_hash_table_insert (mgr->priv->repo_locked_files,\n                             g_strdup(repo_id), locks);\n    }\n\n    info = g_hash_table_lookup (locks, path);\n    if (!info) {\n        info = g_new0 (LockInfo, 1);\n        g_hash_table_insert (locks, g_strdup(path), info);\n    }\n\n    info->locked_by_me = type;\n\n    pthread_mutex_unlock (&mgr->priv->hash_lock);\n\n#ifdef WIN32\n    refresh_locked_path_status (repo_id, path);\n#endif\n\n    return mark_file_locked_in_db (mgr, repo_id, path, info->locked_by_me);\n}\n\nstatic int\nremove_locked_file_from_db (SeafFilelockManager *mgr,\n                            const char *repo_id,\n                            const char *path)\n{\n    char *sql;\n    sqlite3_stmt *stmt;\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    sql = \"DELETE FROM ServerLockedFiles WHERE repo_id = ? AND path = ?\";\n    stmt = sqlite_query_prepare (mgr->priv->db, sql);\n    sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);\n    sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT);\n    if (sqlite3_step (stmt) != SQLITE_DONE) {\n        seaf_warning (\"Failed to remove locked file %s from %.8s: %s.\\n\",\n                      path, repo_id, sqlite3_errmsg (mgr->priv->db));\n        sqlite3_finalize (stmt);\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        return -1;\n    }\n    sqlite3_finalize (stmt);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    return 0;\n}\n\nint\nseaf_filelock_manager_mark_file_unlocked (SeafFilelockManager *mgr,\n                                          const char *repo_id,\n                                          const char *path)\n{\n    GHashTable *locks;\n\n    pthread_mutex_lock (&mgr->priv->hash_lock);\n\n    locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);\n    if (!locks) {\n        pthread_mutex_unlock (&mgr->priv->hash_lock);\n        return 0;\n    }\n\n    g_hash_table_remove (locks, path);\n\n    pthread_mutex_unlock (&mgr->priv->hash_lock);\n\n#ifdef WIN32\n    refresh_locked_path_status (repo_id, path);\n#endif\n\n    return remove_locked_file_from_db (mgr, repo_id, path);\n}\n\nvoid file_lock_info_free (FileLockInfo *info)\n{\n    if (!info)\n        return;\n    g_free (info->path);\n    g_free (info);\n}\n\nstatic gboolean\ncollect_auto_locked_files (sqlite3_stmt *stmt, void *vret)\n{\n    GList **pret = vret;\n    const char *repo_id, *path;\n    FileLockInfo *info;\n\n    repo_id = (const char *)sqlite3_column_text (stmt, 0);\n    path = (const char *)sqlite3_column_text (stmt, 1);\n\n    info = g_new0 (FileLockInfo, 1);\n    memcpy (info->repo_id, repo_id, 36);\n    info->path = g_strdup(path);\n\n    *pret = g_list_prepend (*pret, info);\n\n    return TRUE;\n}\n\nGList *\nseaf_filelock_manager_get_auto_locked_files (SeafFilelockManager *mgr)\n{\n    char *sql;\n    GList *ret = NULL;\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    sql = sqlite3_mprintf (\"SELECT repo_id, path FROM ServerLockedFiles \"\n                           \"WHERE locked_by_me = %d\", LOCKED_AUTO);\n    sqlite_foreach_selected_row (mgr->priv->db, sql,\n                                 collect_auto_locked_files,\n                                 &ret);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    ret = g_list_reverse (ret);\n\n    sqlite3_free (sql);\n    return ret;\n}\n\nint\nseaf_filelock_manager_lock_file (SeafFilelockManager *mgr,\n                                 const char *repo_id,\n                                 const char *path,\n                                 FileLockType type)\n{\n    int ret = -1;\n\n    ret = seaf_filelock_manager_mark_file_locked (seaf->filelock_mgr, repo_id, path, type);\n    if (ret < 0) {\n        return ret;\n    }\n\n    if (!type) {\n        seaf_filelock_manager_lock_wt_file (mgr, repo_id, path);\n    }\n\n    return 0;\n}\n\nint\nseaf_filelock_manager_unlock_file (SeafFilelockManager *mgr,\n                                   const char *repo_id,\n                                   const char *path)\n{\n    int ret = -1;\n\n    ret = seaf_filelock_manager_mark_file_unlocked (mgr, repo_id, path);\n    if (ret < 0) {\n        return ret;\n    }\n\n    seaf_filelock_manager_unlock_wt_file (mgr, repo_id, path);\n\n    return 0;\n}\n"
  },
  {
    "path": "daemon/filelock-mgr.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef SEAF_FILELOCK_MGR_H\n#define SEAF_FILELOCK_MGR_H\n\n#include <glib.h>\n\nstruct _SeafileSession;\nstruct _FilelockMgrPriv;\n\nstruct _SeafFilelockManager {\n    struct _SeafileSession *session;\n\n    struct _FilelockMgrPriv *priv;\n};\ntypedef struct _SeafFilelockManager SeafFilelockManager;\n\nstruct _SeafFilelockManager *\nseaf_filelock_manager_new (struct _SeafileSession *session);\n\nint\nseaf_filelock_manager_init (SeafFilelockManager *mgr);\n\nint\nseaf_filelock_manager_start (SeafFilelockManager *mgr);\n\ngboolean\nseaf_filelock_manager_is_file_locked (SeafFilelockManager *mgr,\n                                      const char *repo_id,\n                                      const char *path);\n\ntypedef enum FileLockStatus {\n    FILE_NOT_LOCKED = 0,\n    FILE_LOCKED_BY_OTHERS,\n    FILE_LOCKED_BY_ME_MANUAL,\n    FILE_LOCKED_BY_ME_AUTO,\n} FileLockStatus;\n\n/* When a file is locked by me, it can have two reasons:\n * - Locked by the user manually\n * - Auto-Locked by Seafile when it detects Office opens the file.\n */\ntypedef enum FileLockType {\n    LOCKED_OTHERS = 0,\n    LOCKED_MANUAL,\n    LOCKED_AUTO,\n} FileLockType;\n\nint\nseaf_filelock_manager_get_lock_status (SeafFilelockManager *mgr,\n                                       const char *repo_id,\n                                       const char *path);\n\ngboolean\nseaf_filelock_manager_is_file_locked_by_me (SeafFilelockManager *mgr,\n                                            const char *repo_id,\n                                            const char *path);\n\n/* Remove locking from the file on worktree */\nvoid\nseaf_filelock_manager_lock_wt_file (SeafFilelockManager *mgr,\n                                    const char *repo_id,\n                                    const char *path);\n\n/* Add locking to the file on worktree */\nvoid\nseaf_filelock_manager_unlock_wt_file (SeafFilelockManager *mgr,\n                                      const char *repo_id,\n                                      const char *path);\n\nint\nseaf_filelock_manager_update (SeafFilelockManager *mgr,\n                              const char *repo_id,\n                              GHashTable *new_locked_files);\n\nint\nseaf_filelock_manager_update_timestamp (SeafFilelockManager *mgr,\n                                        const char *repo_id,\n                                        gint64 timestamp);\n\ngint64\nseaf_filelock_manager_get_timestamp (SeafFilelockManager *mgr,\n                                     const char *repo_id);\n\nint\nseaf_filelock_manager_remove (SeafFilelockManager *mgr,\n                              const char *repo_id);\n\nint\nseaf_filelock_manager_mark_file_locked (SeafFilelockManager *mgr,\n                                        const char *repo_id,\n                                        const char *path,\n                                        FileLockType type);\n\nint\nseaf_filelock_manager_mark_file_unlocked (SeafFilelockManager *mgr,\n                                          const char *repo_id,\n                                          const char *path);\n\nstruct FileLockInfo {\n    char repo_id[37];\n    char *path;\n    int status;\n};\ntypedef struct FileLockInfo FileLockInfo;\n\nvoid file_lock_info_free (FileLockInfo *info);\n\nGList *\nseaf_filelock_manager_get_auto_locked_files (SeafFilelockManager *mgr);\n\nint\nseaf_filelock_manager_lock_file (SeafFilelockManager *mgr,\n                                 const char *repo_id,\n                                 const char *path,\n                                 FileLockType type);\n\nint\nseaf_filelock_manager_unlock_file (SeafFilelockManager *mgr,\n                                   const char *repo_id,\n                                   const char *path);\n\n#endif\n"
  },
  {
    "path": "daemon/http-tx-mgr.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#include \"net.h\"\n\n#include <pthread.h>\n#include <curl/curl.h>\n#include <jansson.h>\n#include <event2/buffer.h>\n\n#ifdef WIN32\n#include <windows.h>\n#include <wincrypt.h>\n#endif\n\n#ifndef USE_GPL_CRYPTO\n#include <openssl/pem.h>\n#include <openssl/x509.h>\n#include <openssl/bio.h>\n#include <openssl/asn1.h>\n#include <openssl/ssl.h>\n#endif\n\n#include \"seafile-config.h\"\n\n#include \"seafile-session.h\"\n#include \"http-tx-mgr.h\"\n\n#include \"seafile-error-impl.h\"\n#include \"utils.h\"\n#include \"diff-simple.h\"\n\n#define DEBUG_FLAG SEAFILE_DEBUG_TRANSFER\n#include \"log.h\"\n\n#include \"timer.h\"\n\n#define HTTP_OK 200\n#define HTTP_BAD_REQUEST 400\n#define HTTP_REQUEST_TIME_OUT 408\n#define HTTP_FORBIDDEN 403\n#define HTTP_NOT_FOUND 404\n#define HTTP_NO_QUOTA 443\n#define HTTP_REPO_DELETED 444\n#define HTTP_REPO_TOO_LARGE 447\n#define HTTP_REPO_CORRUPTED 445\n#define HTTP_BLOCK_MISSING 446\n#define HTTP_INTERNAL_SERVER_ERROR 500\n\n#define RESET_BYTES_INTERVAL_MSEC 1000\n\n#define CLEAR_POOL_ERR_CNT 3\n\nstruct _Connection {\n    CURL *curl;\n    gint64 ctime;               /* Used to clean up unused connection. */\n    gboolean release;           /* If TRUE, the connection will be released. */\n};\ntypedef struct _Connection Connection;\n\nstruct _ConnectionPool {\n    char *host;\n    GQueue *queue;\n    pthread_mutex_t lock;\n    int err_cnt;\n};\ntypedef struct _ConnectionPool ConnectionPool;\n\nstruct _HttpTxPriv {\n    GHashTable *download_tasks;\n    GHashTable *upload_tasks;\n\n    GHashTable *connection_pools; /* host -> connection pool */\n    pthread_mutex_t pools_lock;\n\n    SeafTimer *reset_bytes_timer;\n\n    char *ca_bundle_path;\n    char *env_ca_bundle_path;\n\n    /* Regex to parse error message returned by update-branch. */\n    GRegex *locked_error_regex;\n    GRegex *folder_perm_error_regex;\n    GRegex *too_many_files_error_regex;\n};\ntypedef struct _HttpTxPriv HttpTxPriv;\n\n/* Http Tx Task */\n\nHttpTxTask *\nhttp_tx_task_new (HttpTxManager *mgr,\n                  const char *repo_id,\n                  int repo_version,\n                  int type,\n                  gboolean is_clone,\n                  const char *host,\n                  const char *token,\n                  const char *passwd,\n                  const char *worktree)\n{\n    HttpTxTask *task = g_new0 (HttpTxTask, 1);\n\n    task->manager = mgr;\n    memcpy (task->repo_id, repo_id, 36);\n    task->repo_version = repo_version;\n    task->type = type;\n    task->is_clone = is_clone;\n\n    task->host = g_strdup(host);\n    task->token = g_strdup(token);\n\n    if (passwd)\n        task->passwd = g_strdup(passwd);\n    if (worktree)\n        task->worktree = g_strdup(worktree);\n\n    task->error = SYNC_ERROR_ID_NO_ERROR;\n\n    return task;\n}\n\nvoid\nhttp_tx_task_free (HttpTxTask *task)\n{\n    g_free (task->host);\n    g_free (task->token);\n    g_free (task->passwd);\n    g_free (task->worktree);\n    g_free (task->email);\n    g_free (task->username);\n    g_free (task->repo_name);\n    if (task->type == HTTP_TASK_TYPE_DOWNLOAD) {\n        if (task->blk_ref_cnts)\n            g_hash_table_destroy (task->blk_ref_cnts);\n    }\n    g_free (task);\n}\n\nstatic const char *http_task_state_str[] = {\n    \"normal\",\n    \"canceled\",\n    \"finished\",\n    \"error\",\n};\n\nstatic const char *http_task_rt_state_str[] = {\n    \"init\",\n    \"check\",\n    \"commit\",\n    \"fs\",\n    \"data\",\n    \"update-branch\",\n    \"finished\",\n};\n\n/* Http connection and connection pool. */\n\nstatic Connection *\nconnection_new ()\n{\n    Connection *conn = g_new0 (Connection, 1);\n\n    conn->curl = curl_easy_init();\n    conn->ctime = (gint64)time(NULL);\n\n    return conn;\n}\n\nstatic void\nconnection_free (Connection *conn)\n{\n    curl_easy_cleanup (conn->curl);\n    g_free (conn);\n}\n\nstatic ConnectionPool *\nconnection_pool_new (const char *host)\n{\n    ConnectionPool *pool = g_new0 (ConnectionPool, 1);\n    pool->host = g_strdup(host);\n    pool->queue = g_queue_new ();\n    pthread_mutex_init (&pool->lock, NULL);\n    return pool;\n}\n\nstatic ConnectionPool *\nfind_connection_pool (HttpTxPriv *priv, const char *host)\n{\n    ConnectionPool *pool;\n\n    pthread_mutex_lock (&priv->pools_lock);\n    pool = g_hash_table_lookup (priv->connection_pools, host);\n    if (!pool) {\n        pool = connection_pool_new (host);\n        g_hash_table_insert (priv->connection_pools, g_strdup(host), pool);\n    }\n    pthread_mutex_unlock (&priv->pools_lock);\n\n    return pool;\n}\n\nstatic Connection *\nconnection_pool_get_connection (ConnectionPool *pool)\n{\n    Connection *conn = NULL;\n\n    pthread_mutex_lock (&pool->lock);\n    conn = g_queue_pop_head (pool->queue);\n    if (!conn) {\n        conn = connection_new ();\n    }\n    pthread_mutex_unlock (&pool->lock);\n\n    return conn;\n}\n\nstatic void\nconnection_pool_clear (ConnectionPool *pool)\n{\n    Connection *conn = NULL;\n\n    while (1) {\n        conn = g_queue_pop_head (pool->queue);\n        if (!conn)\n            break;\n        connection_free (conn);\n    }\n}\n\nstatic void\nconnection_pool_return_connection (ConnectionPool *pool, Connection *conn)\n{\n    if (!conn)\n        return;\n\n    if (conn->release) {\n        connection_free (conn);\n\n        pthread_mutex_lock (&pool->lock);\n        if (++pool->err_cnt >= CLEAR_POOL_ERR_CNT) {\n            connection_pool_clear (pool);\n        }\n        pthread_mutex_unlock (&pool->lock);\n\n        return;\n    }\n\n    curl_easy_reset (conn->curl);\n\n    /* Reset error count when one connection succeeded. */\n    pthread_mutex_lock (&pool->lock);\n    pool->err_cnt = 0;\n    g_queue_push_tail (pool->queue, conn);\n    pthread_mutex_unlock (&pool->lock);\n}\n\n#define LOCKED_ERROR_PATTERN \"File (.+) is locked\"\n#define FOLDER_PERM_ERROR_PATTERN \"Update to path (.+) is not allowed by folder permission settings\"\n#define TOO_MANY_FILES_ERROR_PATTERN \"Too many files in library\"\n\nHttpTxManager *\nhttp_tx_manager_new (struct _SeafileSession *seaf)\n{\n    HttpTxManager *mgr = g_new0 (HttpTxManager, 1);\n    HttpTxPriv *priv = g_new0 (HttpTxPriv, 1);\n    const char *env_ca_path = NULL;\n\n    mgr->seaf = seaf;\n\n    priv->download_tasks = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                                  g_free,\n                                                  (GDestroyNotify)http_tx_task_free);\n    priv->upload_tasks = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                                g_free,\n                                                (GDestroyNotify)http_tx_task_free);\n\n    priv->connection_pools = g_hash_table_new (g_str_hash, g_str_equal);\n    pthread_mutex_init (&priv->pools_lock, NULL);\n\n    priv->ca_bundle_path = g_build_filename (seaf->seaf_dir, \"ca-bundle.pem\", NULL);\n\n    env_ca_path = g_getenv(\"SEAFILE_SSL_CA_PATH\");\n    if (env_ca_path)\n        priv->env_ca_bundle_path = g_strdup (env_ca_path);\n\n    GError *error = NULL;\n    priv->locked_error_regex = g_regex_new (LOCKED_ERROR_PATTERN, 0, 0, &error);\n    if (error) {\n        seaf_warning (\"Failed to create regex '%s': %s\\n\", LOCKED_ERROR_PATTERN, error->message);\n        g_clear_error (&error);\n    }\n\n    priv->folder_perm_error_regex = g_regex_new (FOLDER_PERM_ERROR_PATTERN, 0, 0, &error);\n    if (error) {\n        seaf_warning (\"Failed to create regex '%s': %s\\n\", FOLDER_PERM_ERROR_PATTERN, error->message);\n        g_clear_error (&error);\n    }\n\n    priv->too_many_files_error_regex = g_regex_new (TOO_MANY_FILES_ERROR_PATTERN, 0, 0, &error);\n    if (error) {\n        seaf_warning (\"Failed to create regex '%s': %s\\n\", TOO_MANY_FILES_ERROR_PATTERN, error->message);\n        g_clear_error (&error);\n    }\n\n    mgr->priv = priv;\n\n    return mgr;\n}\n\nstatic int\nreset_bytes (void *vdata)\n{\n    HttpTxManager *mgr = vdata;\n    HttpTxPriv *priv = mgr->priv;\n    GHashTableIter iter;\n    gpointer key, value;\n    HttpTxTask *task;\n\n    g_hash_table_iter_init (&iter, priv->download_tasks);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        task = value;\n        task->last_tx_bytes = g_atomic_int_get (&task->tx_bytes);\n        g_atomic_int_set (&task->tx_bytes, 0);\n    }\n\n    g_hash_table_iter_init (&iter, priv->upload_tasks);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        task = value;\n        task->last_tx_bytes = g_atomic_int_get (&task->tx_bytes);\n        g_atomic_int_set (&task->tx_bytes, 0);\n    }\n\n    return 1;\n}\n\nint\nhttp_tx_manager_start (HttpTxManager *mgr)\n{\n#ifdef WIN32\n    /* Remove existing ca-bundle file on start. */\n    g_unlink (mgr->priv->ca_bundle_path);\n#endif\n\n    /* TODO: add a timer to clean up unused Http connections. */\n\n    mgr->priv->reset_bytes_timer = seaf_timer_new (reset_bytes,\n                                                   mgr,\n                                                   RESET_BYTES_INTERVAL_MSEC);\n\n    return 0;\n}\n\n/* Common Utility Functions. */\n\n#ifndef USE_GPL_CRYPTO\n\n#ifdef WIN32\n\n/* static void */\n/* write_cert_name_to_pem_file (FILE *f, PCCERT_CONTEXT pc) */\n/* { */\n/*     char *name; */\n/*     DWORD size; */\n\n/*     fprintf (f, \"\\n\"); */\n\n/*     if (!CertGetCertificateContextProperty(pc, */\n/*                                            CERT_FRIENDLY_NAME_PROP_ID, */\n/*                                            NULL, &size)) { */\n/*         return; */\n/*     } */\n\n/*     name = g_malloc ((gsize)size); */\n/*     if (!name) { */\n/*         seaf_warning (\"Failed to alloc memory\\n\"); */\n/*         return; */\n/*     } */\n\n/*     if (!CertGetCertificateContextProperty(pc, */\n/*                                            CERT_FRIENDLY_NAME_PROP_ID, */\n/*                                            name, &size)) { */\n/*         g_free (name); */\n/*         return; */\n/*     } */\n\n/*     if (fwrite(name, (size_t)size, 1, f) != 1) { */\n/*         seaf_warning (\"Failed to write pem file.\\n\"); */\n/*         g_free (name); */\n/*         return; */\n/*     } */\n/*     fprintf (f, \"\\n\"); */\n\n/*     g_free (name); */\n/* } */\n\nstatic void\nwrite_cert_to_pem_file (FILE *f, PCCERT_CONTEXT pc)\n{\n    const unsigned char *der = pc->pbCertEncoded;\n    X509 *cert;\n\n    /* write_cert_name_to_pem_file (f, pc); */\n\n    cert = d2i_X509 (NULL, &der, (int)pc->cbCertEncoded);\n    if (!cert) {\n        seaf_warning (\"Failed to parse certificate from DER.\\n\");\n        return;\n    }\n\n    /* Don't add expired certs to pem file, otherwise openssl will\n     * complain certificate expired.\n     */\n    if (X509_cmp_current_time (X509_get_notAfter(cert)) < 0)\n        return;\n\n    if (!PEM_write_X509 (f, cert)) {\n        seaf_warning (\"Failed to write certificate.\\n\");\n        X509_free (cert);\n        return;\n    }\n\n    X509_free (cert);\n}\n\nstatic int\nload_ca_from_store (FILE *f, const wchar_t *store_name)\n{\n    HCERTSTORE store;\n\n    store = CertOpenSystemStoreW (0, store_name);\n    if (!store) {\n        seaf_warning (\"Failed to open system cert store: %lu\\n\", GetLastError());\n        return -1;\n    }\n\n    PCCERT_CONTEXT pc = NULL;\n    while (1) {\n        pc = CertFindCertificateInStore (store, X509_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, pc);\n        if (!pc)\n            break;\n        write_cert_to_pem_file (f, pc);\n    }\n\n    CertCloseStore(store, 0);\n\n    return 0;\n}\n\nstatic int\ncreate_ca_bundle (const char *ca_bundle_path)\n{\n    FILE *f;\n    int ret = 0;\n\n    f = g_fopen (ca_bundle_path, \"w+b\");\n    if (!f) {\n        seaf_warning (\"Failed to open cabundle file %s: %s\\n\",\n                      ca_bundle_path, strerror(errno));\n        return -1;\n    }\n\n    if (load_ca_from_store (f, L\"ROOT\") < 0) {\n        seaf_warning (\"Failed to load ca from ROOT store.\\n\");\n        ret = -1;\n        goto out;\n    }\n\n    if (load_ca_from_store (f, L\"CA\") < 0) {\n        seaf_warning (\"Failed to load ca from CA store.\\n\");\n        ret = -1;\n        goto out;\n    }\n\nout:\n    fclose (f);\n    return ret;\n}\n\n#endif\t/* WIN32 */\n\n#ifndef __linux__\nstatic void\nload_ca_bundle (CURL *curl)\n{\n    char *ca_bundle_path = seaf->http_tx_mgr->priv->ca_bundle_path;\n\n    /* On MacOS the certs are loaded by seafile applet instead of seaf-daemon  */\n    if (!seaf_util_exists (ca_bundle_path)) {\n#ifdef WIN32\n        if (create_ca_bundle (ca_bundle_path) < 0)\n            return;\n#else\n        return;\n#endif\n    }\n\n    curl_easy_setopt (curl, CURLOPT_CAINFO, ca_bundle_path);\n}\n#else\n\nchar *ca_paths[] = {\n    \"/etc/ssl/certs/ca-certificates.crt\",\n    \"/etc/ssl/certs/ca-bundle.crt\",\n    \"/etc/pki/tls/certs/ca-bundle.crt\",\n    \"/usr/share/ssl/certs/ca-bundle.crt\",\n    \"/usr/local/share/certs/ca-root-nss.crt\",\n    \"/etc/ssl/cert.pem\",\n    \"/etc/ssl/ca-bundle.pem\",\n};\n\nstatic void\nload_ca_bundle(CURL *curl)\n{\n    const char *env_ca_path = seaf->http_tx_mgr->priv->env_ca_bundle_path;\n    int i;\n    const char *ca_path;\n    gboolean found = FALSE;\n\n    for (i = 0; i < sizeof(ca_paths) / sizeof(ca_paths[0]); i++) {\n        ca_path = ca_paths[i];\n        if (seaf_util_exists (ca_path)) {\n            found = TRUE;\n            break;\n        }\n    }\n\n    if (env_ca_path) {\n        if (seaf_util_exists (env_ca_path)) {\n            curl_easy_setopt (curl, CURLOPT_CAINFO, env_ca_path);\n            return;\n        }\n    }\n\n    if (found)\n        curl_easy_setopt (curl, CURLOPT_CAINFO, ca_path);\n}\n#endif  /* __linux__ */\n\nstatic gboolean\nload_certs (sqlite3_stmt *stmt, void *vdata)\n{\n    X509_STORE *store = vdata;\n    X509 *saved = NULL;\n    const char *pem_b64;\n    char *pem = NULL;\n    BIO *b = NULL;\n    gboolean ret = TRUE;\n\n    pem_b64 = (const char *)sqlite3_column_text (stmt, 0);\n\n    gsize len;\n    pem = (char *)g_base64_decode (pem_b64, &len);\n    if (!pem) {\n        seaf_warning (\"Failed to decode base64.\\n\");\n        goto out;\n    }\n\n    b = BIO_new (BIO_s_mem());\n    if (!b) {\n        seaf_warning (\"Failed to alloc BIO\\n\");\n        goto out;\n    }\n\n    if (BIO_write (b, pem, len) != len) {\n        seaf_warning (\"Failed to write pem to BIO\\n\");\n        goto out;\n    }\n\n    saved = PEM_read_bio_X509 (b, NULL, 0, NULL);\n    if (!saved) {\n        seaf_warning (\"Failed to read PEM from BIO\\n\");\n        goto out;\n    }\n\n    X509_STORE_add_cert (store, saved);\n\nout:\n    g_free (pem);\n    if (b)\n        BIO_free (b);\n    if (saved)\n        X509_free (saved);\n\n    return ret;\n}\n\nstatic int\nload_certs_from_db (X509_STORE *store)\n{\n    char *cert_db_path = NULL;\n    sqlite3 *db = NULL;\n    char *sql;\n    int ret = 0;\n\n    cert_db_path = g_build_filename (seaf->seaf_dir, \"certs.db\", NULL);\n    if (sqlite_open_db (cert_db_path, &db) < 0) {\n        seaf_warning (\"Failed to open certs.db\\n\");\n        ret = -1;\n        goto out;\n    }\n\n    sql = \"SELECT 1 FROM sqlite_master WHERE type='table' AND name='Certs';\";\n\n    if (!sqlite_check_for_existence (db, sql)) {\n        ret = -1;\n        goto out;\n    }\n\n    sql = \"SELECT cert FROM Certs;\";\n\n    if (sqlite_foreach_selected_row (db, sql, load_certs, store) < 0) {\n        ret = -1;\n        goto out;\n    }\n\nout:\n    g_free (cert_db_path);\n    if (db)\n        sqlite_close_db (db);\n\n    return ret;\n}\n\nstatic CURLcode\nssl_callback (CURL *curl, void *ssl_ctx, void *userptr)\n{\n    SSL_CTX *ctx = ssl_ctx;\n    X509_STORE *store;\n\n    store = SSL_CTX_get_cert_store(ctx);\n\n    /* Add all certs stored in db as trusted CA certs.\n     * This workaround has one limitation though. The self-signed certs cannot\n     * contain chain. It must be the CA itself.\n     */\n    load_certs_from_db (store);\n\n    return CURLE_OK;\n}\n\n#endif  /* USE_GPL_CRYPTO */\n\nstatic void\nset_proxy (CURL *curl, gboolean is_https)\n{\n    /* Disable proxy if proxy options are not set properly. */\n    if (!seaf->use_http_proxy || !seaf->http_proxy_type || !seaf->http_proxy_addr) {\n        curl_easy_setopt (curl, CURLOPT_PROXY, NULL);\n        return;\n    }\n\n    if (g_strcmp0(seaf->http_proxy_type, PROXY_TYPE_HTTP) == 0) {\n        curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);\n        /* Use CONNECT method create a SSL tunnel if https is used. */\n        if (is_https)\n            curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L);\n        curl_easy_setopt(curl, CURLOPT_PROXY, seaf->http_proxy_addr);\n        curl_easy_setopt(curl, CURLOPT_PROXYPORT,\n                         seaf->http_proxy_port > 0 ? seaf->http_proxy_port : 80);\n        if (seaf->http_proxy_username && seaf->http_proxy_password) {\n            curl_easy_setopt(curl, CURLOPT_PROXYAUTH,\n                             CURLAUTH_BASIC |\n                             CURLAUTH_DIGEST |\n                             CURLAUTH_DIGEST_IE |\n                             CURLAUTH_GSSNEGOTIATE |\n                             CURLAUTH_NTLM);\n            curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME, seaf->http_proxy_username);\n            curl_easy_setopt(curl, CURLOPT_PROXYPASSWORD, seaf->http_proxy_password);\n        }\n    } else if (g_strcmp0(seaf->http_proxy_type, PROXY_TYPE_SOCKS) == 0) {\n        if (seaf->http_proxy_port < 0)\n            return;\n        curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);\n        curl_easy_setopt(curl, CURLOPT_PROXY, seaf->http_proxy_addr);\n        curl_easy_setopt(curl, CURLOPT_PROXYPORT, seaf->http_proxy_port);\n        if (seaf->http_proxy_username && g_strcmp0 (seaf->http_proxy_username, \"\") != 0)\n            curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME, seaf->http_proxy_username);\n        if (seaf->http_proxy_password && g_strcmp0 (seaf->http_proxy_password, \"\") != 0)\n            curl_easy_setopt(curl, CURLOPT_PROXYPASSWORD, seaf->http_proxy_password);\n    }\n}\n\n#ifdef WIN32\nstatic int\nsockopt_callback (void *clientp, curl_socket_t curlfd, curlsocktype purpose)\n{\n    /* Set large enough TCP buffer size.\n     * This greatly enhances sync speed for high latency network.\n     * Windows by default use 8KB buffers, which is too small for such case.\n     * Linux has auto-tuning for TCP buffers, so don't need to set manually.\n     * OSX is TBD.\n     */\n\n#define DEFAULT_SNDBUF_SIZE (1 << 17) /* 128KB */\n\n    /* Set send buffer size. */\n    int sndbuf_size;\n    socklen_t optlen;\n\n    optlen = sizeof(int);\n    getsockopt (curlfd, SOL_SOCKET, SO_SNDBUF, (char *)&sndbuf_size, &optlen);\n\n    if (sndbuf_size < DEFAULT_SNDBUF_SIZE) {\n        sndbuf_size = DEFAULT_SNDBUF_SIZE;\n        optlen = sizeof(int);\n        setsockopt (curlfd, SOL_SOCKET, SO_SNDBUF, (char *)&sndbuf_size, optlen);\n    }\n\n    /* Disable Nagle's algorithm. */\n    int val = 1;\n    optlen = sizeof(int);\n    setsockopt (curlfd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, optlen);\n\n    return CURL_SOCKOPT_OK;\n}\n#endif  /* WIN32 */\n\ntypedef struct _HttpResponse {\n    char *content;\n    size_t size;\n} HttpResponse;\n\nstatic size_t\nrecv_response (void *contents, size_t size, size_t nmemb, void *userp)\n{\n    size_t realsize = size * nmemb;\n    HttpResponse *rsp = userp;\n\n    rsp->content = g_realloc (rsp->content, rsp->size + realsize);\n    if (!rsp->content) {\n        seaf_warning (\"Not enough memory.\\n\");\n        /* return a value other than realsize to signify an error. */\n        return 0;\n    }\n\n    memcpy (rsp->content + rsp->size, contents, realsize);\n    rsp->size += realsize;\n\n    return realsize;\n}\n\nextern FILE *seafile_get_log_fp ();\n\n#define FS_ID_LIST_TIMEOUT_SEC 1800\n#define HTTP_TIMEOUT_SEC 300\n\n/*\n * The @timeout parameter is for detecting network connection problems. \n * The @timeout parameter should be set to TRUE for data-transfer-only operations,\n * such as getting objects, blocks. For operations that requires calculations\n * on the server side, the timeout should be set to FALSE. Otherwise when\n * the server sometimes takes more than 45 seconds to calculate the result,\n * the client will time out.\n */\nstatic int\nhttp_get (CURL *curl, const char *url, const char *token,\n          int *rsp_status, char **rsp_content, gint64 *rsp_size,\n          HttpRecvCallback callback, void *cb_data,\n          gboolean timeout, int timeout_sec, int *pcurl_error)\n{\n    char *token_header;\n    struct curl_slist *headers = NULL;\n    int ret = 0;\n\n    if (seafile_debug_flag_is_set (SEAFILE_DEBUG_CURL)) {\n        curl_easy_setopt (curl, CURLOPT_VERBOSE, 1);\n        curl_easy_setopt (curl, CURLOPT_STDERR, seafile_get_log_fp());\n    }\n\n    headers = curl_slist_append (headers, \"User-Agent: Seafile/\"SEAFILE_CLIENT_VERSION\" (\"USER_AGENT_OS\")\");\n\n    if (token) {\n        token_header = g_strdup_printf (\"Seafile-Repo-Token: %s\", token);\n        headers = curl_slist_append (headers, token_header);\n        g_free (token_header);\n        token_header = g_strdup_printf (\"Authorization: Token %s\", token);\n        headers = curl_slist_append (headers, token_header);\n        g_free (token_header);\n    }\n\n    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);\n\n    curl_easy_setopt(curl, CURLOPT_URL, url);\n    curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);\n\n    if (timeout) {\n        /* Set low speed limit to 1 bytes. This effectively means no data. */\n        curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);\n        curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, timeout_sec);\n    }\n\n    if (seaf->disable_verify_certificate) {\n        curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);\n        curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L);\n    }\n\n    HttpResponse rsp;\n    memset (&rsp, 0, sizeof(rsp));\n    if (rsp_content) {\n        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, recv_response);\n        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &rsp);\n    } else if (callback) {\n        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback);\n        curl_easy_setopt(curl, CURLOPT_WRITEDATA, cb_data);\n    }\n\n    gboolean is_https = (strncasecmp(url, \"https\", strlen(\"https\")) == 0);\n    set_proxy (curl, is_https);\n\n    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);\n\n#ifndef USE_GPL_CRYPTO\n    load_ca_bundle (curl);\n#endif\n\n#ifndef USE_GPL_CRYPTO\n    if (!seaf->disable_verify_certificate) {\n        curl_easy_setopt (curl, CURLOPT_SSL_CTX_FUNCTION, ssl_callback);\n        curl_easy_setopt (curl, CURLOPT_SSL_CTX_DATA, url);\n    }\n#endif\n\n#ifdef WIN32\n    curl_easy_setopt (curl, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);\n#endif\n\n    int rc = curl_easy_perform (curl);\n    if (rc != 0) {\n        seaf_warning (\"libcurl failed to GET %s: %s.\\n\",\n                      url, curl_easy_strerror(rc));\n        if (pcurl_error)\n            *pcurl_error = rc;\n        ret = -1;\n        goto out;\n    }\n\n    long status;\n    rc = curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status);\n    if (rc != CURLE_OK) {\n        seaf_warning (\"Failed to get status code for GET %s.\\n\", url);\n        ret = -1;\n        goto out;\n    }\n\n    *rsp_status = status;\n\n    if (rsp_content) {\n        *rsp_content = rsp.content;\n        *rsp_size = rsp.size;\n    }\n\nout:\n    if (ret < 0)\n        g_free (rsp.content);\n    curl_slist_free_all (headers);\n    return ret;\n}\n\ntypedef struct _HttpRequest {\n    const char *content;\n    const char *origin_content;\n    size_t size;\n    size_t origin_size;\n} HttpRequest;\n\nstatic size_t\nsend_request (void *ptr, size_t size, size_t nmemb, void *userp)\n{\n    size_t realsize = size *nmemb;\n    size_t copy_size;\n    HttpRequest *req = userp;\n\n    if (req->size == 0)\n        return 0;\n\n    copy_size = MIN(req->size, realsize);\n    memcpy (ptr, req->content, copy_size);\n    req->size -= copy_size;\n    req->content = req->content + copy_size;\n\n    return copy_size;\n}\n\n// Only handle rewinding caused by redirects. In this case, offset and origin should both be 0.\n// Do not handle rewinds triggered by other causes.\nstatic size_t\nrewind_http_request (void *clientp, curl_off_t offset, int origin)\n{\n    HttpRequest *req = clientp;\n    if (offset == 0 && origin == 0) {\n        req->content = req->origin_content;\n        req->size = req->origin_size;\n        return CURL_SEEKFUNC_OK;\n    }\n\n    return CURL_SEEKFUNC_FAIL;\n}\n\ntypedef size_t (*HttpRewindCallback) (void *, curl_off_t, int);\n\ntypedef size_t (*HttpSendCallback) (void *, size_t, size_t, void *);\n\nstatic int\nhttp_put (CURL *curl, const char *url, const char *token,\n          const char *req_content, gint64 req_size,\n          HttpSendCallback callback, void *cb_data, HttpRewindCallback rewind_cb,\n          int *rsp_status, char **rsp_content, gint64 *rsp_size,\n          gboolean timeout, int *pcurl_error)\n{\n    char *token_header;\n    struct curl_slist *headers = NULL;\n    int ret = 0;\n\n    if (seafile_debug_flag_is_set (SEAFILE_DEBUG_CURL)) {\n        curl_easy_setopt (curl, CURLOPT_VERBOSE, 1);\n        curl_easy_setopt (curl, CURLOPT_STDERR, seafile_get_log_fp());\n    }\n\n    headers = curl_slist_append (headers, \"User-Agent: Seafile/\"SEAFILE_CLIENT_VERSION\" (\"USER_AGENT_OS\")\");\n    /* Disable the default \"Expect: 100-continue\" header */\n    headers = curl_slist_append (headers, \"Expect:\");\n\n    if (token) {\n        token_header = g_strdup_printf (\"Seafile-Repo-Token: %s\", token);\n        headers = curl_slist_append (headers, token_header);\n        g_free (token_header);\n        token_header = g_strdup_printf (\"Authorization: Token %s\", token);\n        headers = curl_slist_append (headers, token_header);\n        g_free (token_header);\n    }\n\n    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);\n\n    curl_easy_setopt(curl, CURLOPT_URL, url);\n    curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n\n    if (timeout) {\n        /* Set low speed limit to 1 bytes. This effectively means no data. */\n        curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);\n        curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, HTTP_TIMEOUT_SEC);\n    }\n\n    if (seaf->disable_verify_certificate) {\n        curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);\n        curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L);\n    }\n\n    HttpRequest req;\n    if (req_content) {\n        memset (&req, 0, sizeof(req));\n        req.content = req_content;\n        req.origin_content = req_content;\n        req.size = req_size;\n        req.origin_size = req_size;\n        curl_easy_setopt(curl, CURLOPT_READFUNCTION, send_request);\n        curl_easy_setopt(curl, CURLOPT_READDATA, &req);\n        curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)req_size);\n\n        curl_easy_setopt(curl, CURLOPT_SEEKFUNCTION, rewind_http_request);\n        curl_easy_setopt(curl, CURLOPT_SEEKDATA, &req);\n    } else if (callback != NULL) {\n        curl_easy_setopt(curl, CURLOPT_READFUNCTION, callback);\n        curl_easy_setopt(curl, CURLOPT_READDATA, cb_data);\n        curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)req_size);\n\n        if (rewind_cb != NULL) {\n            curl_easy_setopt(curl, CURLOPT_SEEKFUNCTION, rewind_cb);\n            curl_easy_setopt(curl, CURLOPT_SEEKDATA, cb_data);\n        }\n    } else {\n        curl_easy_setopt (curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)0);\n    }\n\n    curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);\n\n    HttpResponse rsp;\n    memset (&rsp, 0, sizeof(rsp));\n    if (rsp_content) {\n        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, recv_response);\n        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &rsp);\n    }\n\n    gboolean is_https = (strncasecmp(url, \"https\", strlen(\"https\")) == 0);\n    set_proxy (curl, is_https);\n\n    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);\n\n#ifndef USE_GPL_CRYPTO\n    load_ca_bundle (curl);\n#endif\n\n#ifndef USE_GPL_CRYPTO\n    if (!seaf->disable_verify_certificate) {\n        curl_easy_setopt (curl, CURLOPT_SSL_CTX_FUNCTION, ssl_callback);\n        curl_easy_setopt (curl, CURLOPT_SSL_CTX_DATA, url);\n    }\n#endif\n\n#ifdef WIN32\n    curl_easy_setopt (curl, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);\n#endif\n\n    int rc = curl_easy_perform (curl);\n    if (rc != 0) {\n        seaf_warning (\"libcurl failed to PUT %s: %s.\\n\",\n                      url, curl_easy_strerror(rc));\n        if (pcurl_error)\n            *pcurl_error = rc;\n        ret = -1;\n        goto out;\n    }\n\n    long status;\n    rc = curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status);\n    if (rc != CURLE_OK) {\n        seaf_warning (\"Failed to get status code for PUT %s.\\n\", url);\n        ret = -1;\n        goto out;\n    }\n\n    *rsp_status = status;\n\n    if (rsp_content) {\n        *rsp_content = rsp.content;\n        *rsp_size = rsp.size;\n    }\n\nout:\n    if (ret < 0)\n        g_free (rsp.content);\n    curl_slist_free_all (headers);\n    return ret;\n}\n\nstatic int\nhttp_post (CURL *curl, const char *url, const char *token,\n           const char *req_content, gint64 req_size,\n           int *rsp_status, char **rsp_content, gint64 *rsp_size,\n           gboolean timeout, int *pcurl_error)\n{\n    char *token_header;\n    struct curl_slist *headers = NULL;\n    int ret = 0;\n\n    g_return_val_if_fail (req_content != NULL, -1);\n\n    if (seafile_debug_flag_is_set (SEAFILE_DEBUG_CURL)) {\n        curl_easy_setopt (curl, CURLOPT_VERBOSE, 1);\n        curl_easy_setopt (curl, CURLOPT_STDERR, seafile_get_log_fp());\n    }\n\n    headers = curl_slist_append (headers, \"User-Agent: Seafile/\"SEAFILE_CLIENT_VERSION\" (\"USER_AGENT_OS\")\");\n    /* Disable the default \"Expect: 100-continue\" header */\n    headers = curl_slist_append (headers, \"Expect:\");\n\n    if (token) {\n        token_header = g_strdup_printf (\"Seafile-Repo-Token: %s\", token);\n        headers = curl_slist_append (headers, token_header);\n        g_free (token_header);\n        token_header = g_strdup_printf (\"Authorization: Token %s\", token);\n        headers = curl_slist_append (headers, token_header);\n        g_free (token_header);\n    }\n\n    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);\n\n    curl_easy_setopt(curl, CURLOPT_URL, url);\n    curl_easy_setopt(curl, CURLOPT_POST, 1L);\n\n    if (timeout) {\n        /* Set low speed limit to 1 bytes. This effectively means no data. */\n        curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);\n        curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, HTTP_TIMEOUT_SEC);\n    }\n\n    if (seaf->disable_verify_certificate) {\n        curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);\n        curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L);\n    }\n\n    HttpRequest req;\n    memset (&req, 0, sizeof(req));\n    req.content = req_content;\n    req.origin_content = req_content;\n    req.size = req_size;\n    req.origin_size = req_size;\n    curl_easy_setopt(curl, CURLOPT_READFUNCTION, send_request);\n    curl_easy_setopt(curl, CURLOPT_READDATA, &req);\n    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)req_size);\n\n    curl_easy_setopt(curl, CURLOPT_SEEKFUNCTION, rewind_http_request);\n    curl_easy_setopt(curl, CURLOPT_SEEKDATA, &req);\n\n    curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);\n\n    HttpResponse rsp;\n    memset (&rsp, 0, sizeof(rsp));\n    if (rsp_content) {\n        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, recv_response);\n        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &rsp);\n    }\n\n#ifndef USE_GPL_CRYPTO\n    load_ca_bundle (curl);\n#endif\n\n#ifndef USE_GPL_CRYPTO\n    if (!seaf->disable_verify_certificate) {\n        curl_easy_setopt (curl, CURLOPT_SSL_CTX_FUNCTION, ssl_callback);\n        curl_easy_setopt (curl, CURLOPT_SSL_CTX_DATA, url);\n    }\n#endif\n\n    gboolean is_https = (strncasecmp(url, \"https\", strlen(\"https\")) == 0);\n    set_proxy (curl, is_https);\n\n    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);\n    /* All POST requests should remain POST after redirect. */\n    curl_easy_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);\n\n#ifdef WIN32\n    curl_easy_setopt (curl, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);\n#endif\n\n    int rc = curl_easy_perform (curl);\n    if (rc != 0) {\n        seaf_warning (\"libcurl failed to POST %s: %s.\\n\",\n                      url, curl_easy_strerror(rc));\n        if (pcurl_error)\n            *pcurl_error = rc;\n        ret = -1;\n        goto out;\n    }\n\n    long status;\n    rc = curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status);\n    if (rc != CURLE_OK) {\n        seaf_warning (\"Failed to get status code for POST %s.\\n\", url);\n        ret = -1;\n        goto out;\n    }\n\n    *rsp_status = status;\n\n    if (rsp_content) {\n        *rsp_content = rsp.content;\n        *rsp_size = rsp.size;\n    }\n\nout:\n    if (ret < 0)\n        g_free (rsp.content);\n    curl_slist_free_all (headers);\n    return ret;\n}\n\nstatic int\nhttp_error_to_http_task_error (int status)\n{\n    if (status == HTTP_BAD_REQUEST)\n        /* This is usually a bug in the client. Set to general error. */\n        return SYNC_ERROR_ID_GENERAL_ERROR;\n    else if (status == HTTP_FORBIDDEN)\n        return SYNC_ERROR_ID_ACCESS_DENIED;\n    else if (status >= HTTP_INTERNAL_SERVER_ERROR)\n        return SYNC_ERROR_ID_SERVER;\n    else if (status == HTTP_NOT_FOUND)\n        return SYNC_ERROR_ID_SERVER;\n    else if (status == HTTP_NO_QUOTA)\n        return SYNC_ERROR_ID_QUOTA_FULL;\n    else if (status == HTTP_REPO_DELETED)\n        return SYNC_ERROR_ID_SERVER_REPO_DELETED;\n    else if (status == HTTP_REPO_CORRUPTED)\n        return SYNC_ERROR_ID_SERVER_REPO_CORRUPT;\n    else if (status == HTTP_REPO_TOO_LARGE || status == HTTP_REQUEST_TIME_OUT)\n        return SYNC_ERROR_ID_LIBRARY_TOO_LARGE;\n    else\n        return SYNC_ERROR_ID_GENERAL_ERROR;\n}\n\nstatic void\nhandle_http_errors (HttpTxTask *task, int status)\n{\n    task->error = http_error_to_http_task_error (status);\n}\n\nstatic int\ncurl_error_to_http_task_error (int curl_error)\n{\n    if (curl_error == CURLE_SSL_CACERT ||\n        curl_error == CURLE_PEER_FAILED_VERIFICATION)\n        return SYNC_ERROR_ID_SSL;\n\n    switch (curl_error) {\n    case CURLE_COULDNT_RESOLVE_PROXY:\n        return SYNC_ERROR_ID_RESOLVE_PROXY;\n    case CURLE_COULDNT_RESOLVE_HOST:\n        return SYNC_ERROR_ID_RESOLVE_HOST;\n    case CURLE_COULDNT_CONNECT:\n        return SYNC_ERROR_ID_CONNECT;\n    case CURLE_OPERATION_TIMEDOUT:\n        return SYNC_ERROR_ID_TX_TIMEOUT;\n    case CURLE_SSL_CONNECT_ERROR:\n    case CURLE_SSL_CERTPROBLEM:\n    case CURLE_SSL_CACERT_BADFILE:\n    case CURLE_SSL_ISSUER_ERROR:\n        return SYNC_ERROR_ID_SSL;\n    case CURLE_GOT_NOTHING:\n    case CURLE_SEND_ERROR:\n    case CURLE_RECV_ERROR:\n        return SYNC_ERROR_ID_TX;\n    case CURLE_SEND_FAIL_REWIND:\n        return SYNC_ERROR_ID_UNHANDLED_REDIRECT;\n    default:\n        return SYNC_ERROR_ID_NETWORK;\n    }\n}\n\nstatic void\nhandle_curl_errors (HttpTxTask *task, int curl_error)\n{\n    task->error = curl_error_to_http_task_error (curl_error);\n}\n\nstatic void\nemit_transfer_done_signal (HttpTxTask *task)\n{\n    if (task->type == HTTP_TASK_TYPE_DOWNLOAD)\n        g_signal_emit_by_name (seaf, \"repo-http-fetched\", task);\n    else\n        g_signal_emit_by_name (seaf, \"repo-http-uploaded\", task);\n}\n\nstatic void\ntransition_state (HttpTxTask *task, int state, int rt_state)\n{\n    seaf_message (\"Transfer repo '%.8s': ('%s', '%s') --> ('%s', '%s')\\n\",\n                  task->repo_id,\n                  http_task_state_to_str(task->state),\n                  http_task_rt_state_to_str(task->runtime_state),\n                  http_task_state_to_str(state),\n                  http_task_rt_state_to_str(rt_state));\n\n    if (state != task->state)\n        task->state = state;\n    task->runtime_state = rt_state;\n\n    if (rt_state == HTTP_TASK_RT_STATE_FINISHED) {\n        /* Clear download head info. */\n        if (task->type == HTTP_TASK_TYPE_DOWNLOAD &&\n            state == HTTP_TASK_STATE_FINISHED)\n            seaf_repo_manager_set_repo_property (seaf->repo_mgr,\n                                                 task->repo_id,\n                                                 REPO_PROP_DOWNLOAD_HEAD,\n                                                 EMPTY_SHA1);\n\n        emit_transfer_done_signal (task);\n    }\n}\n\ntypedef struct {\n    char *host;\n    gboolean use_fileserver_port;\n    HttpProtocolVersionCallback callback;\n    void *user_data;\n\n    gboolean success;\n    gboolean not_supported;\n    int version;\n    int error_code;\n} CheckProtocolData;\n\nstatic int\nparse_protocol_version (const char *rsp_content, int rsp_size, CheckProtocolData *data)\n{\n    json_t *object = NULL;\n    json_error_t jerror;\n    int version;\n\n    object = json_loadb (rsp_content, rsp_size, 0, &jerror);\n    if (!object) {\n        seaf_warning (\"Parse response failed: %s.\\n\", jerror.text);\n        return -1;\n    }\n\n    if (json_object_has_member (object, \"version\")) {\n        version = json_object_get_int_member (object, \"version\");\n        data->version = version;\n    } else {\n        seaf_warning (\"Response doesn't contain protocol version.\\n\");\n        json_decref (object);\n        return -1;\n    }\n\n    json_decref (object);\n    return 0;\n}\n\nstatic void *\ncheck_protocol_version_thread (void *vdata)\n{\n    CheckProtocolData *data = vdata;\n    HttpTxPriv *priv = seaf->http_tx_mgr->priv;\n    ConnectionPool *pool;\n    Connection *conn;\n    CURL *curl;\n    char *url;\n    int status;\n    char *rsp_content = NULL;\n    gint64 rsp_size;\n\n    pool = find_connection_pool (priv, data->host);\n    if (!pool) {\n        seaf_warning (\"Failed to create connection pool for host %s.\\n\", data->host);\n        return vdata;\n    }\n\n    conn = connection_pool_get_connection (pool);\n    if (!conn) {\n        seaf_warning (\"Failed to get connection to host %s.\\n\", data->host);\n        return vdata;\n    }\n\n    curl = conn->curl;\n\n    if (!data->use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/protocol-version\", data->host);\n    else\n        url = g_strdup_printf (\"%s/protocol-version\", data->host);\n\n    int curl_error;\n    if (http_get (curl, url, NULL, &status, &rsp_content, &rsp_size, NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {\n        conn->release = TRUE;\n        data->error_code = curl_error_to_http_task_error (curl_error);\n        goto out;\n    }\n\n    data->success = TRUE;\n\n    if (status == HTTP_OK) {\n        if (rsp_size == 0)\n            data->not_supported = TRUE;\n        else if (parse_protocol_version (rsp_content, rsp_size, data) < 0)\n            data->not_supported = TRUE;\n    } else {\n        seaf_warning (\"Bad response code for GET %s: %d.\\n\", url, status);\n        data->not_supported = TRUE;\n        data->error_code = http_error_to_http_task_error (status);\n    }\n\nout:\n    g_free (url);\n    g_free (rsp_content);\n    connection_pool_return_connection (pool, conn);\n\n    return vdata;\n}\n\nstatic void\ncheck_protocol_version_done (void *vdata)\n{\n    CheckProtocolData *data = vdata;\n    HttpProtocolVersion result;\n\n    memset (&result, 0, sizeof(result));\n    result.check_success = data->success;\n    result.not_supported = data->not_supported;\n    result.version = data->version;\n    result.error_code = data->error_code;\n\n    data->callback (&result, data->user_data);\n\n    g_free (data->host);\n    g_free (data);\n}\n\nint\nhttp_tx_manager_check_protocol_version (HttpTxManager *manager,\n                                        const char *host,\n                                        gboolean use_fileserver_port,\n                                        HttpProtocolVersionCallback callback,\n                                        void *user_data)\n{\n    CheckProtocolData *data = g_new0 (CheckProtocolData, 1);\n\n    data->host = g_strdup(host);\n    data->use_fileserver_port = use_fileserver_port;\n    data->callback = callback;\n    data->user_data = user_data;\n\n    int ret = seaf_job_manager_schedule_job (seaf->job_mgr,\n                                             check_protocol_version_thread,\n                                             check_protocol_version_done,\n                                             data);\n    if (ret < 0) {\n        g_free (data->host);\n        g_free (data);\n    }\n\n    return ret;\n}\n\ntypedef struct {\n    char *host;\n    gboolean use_notif_server_port;\n    HttpNotifServerCallback callback;\n    void *user_data;\n\n    gboolean success;\n    gboolean not_supported;\n} CheckNotifServerData;\n\nstatic void *\ncheck_notif_server_thread (void *vdata)\n{\n    CheckNotifServerData *data = vdata;\n    HttpTxPriv *priv = seaf->http_tx_mgr->priv;\n    ConnectionPool *pool;\n    Connection *conn;\n    CURL *curl;\n    char *url;\n    int status;\n    char *rsp_content = NULL;\n    gint64 rsp_size;\n\n    pool = find_connection_pool (priv, data->host);\n    if (!pool) {\n        seaf_warning (\"Failed to create connection pool for host %s.\\n\", data->host);\n        return vdata;\n    }\n\n    conn = connection_pool_get_connection (pool);\n    if (!conn) {\n        seaf_warning (\"Failed to get connection to host %s.\\n\", data->host);\n        return vdata;\n    }\n\n    curl = conn->curl;\n\n    if (!data->use_notif_server_port)\n        url = g_strdup_printf (\"%s/notification/ping\", data->host);\n    else\n        url = g_strdup_printf (\"%s/ping\", data->host);\n\n    int curl_error;\n    if (http_get (curl, url, NULL, &status, &rsp_content, &rsp_size, NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {\n        conn->release = TRUE;\n        goto out;\n    }\n\n    data->success = TRUE;\n\n    if (status != HTTP_OK) {\n        data->not_supported = TRUE;\n    }\n\nout:\n    g_free (url);\n    g_free (rsp_content);\n    connection_pool_return_connection (pool, conn);\n\n    return vdata;\n}\n\nstatic void\ncheck_notif_server_done (void *vdata)\n{\n    CheckNotifServerData *data = vdata;\n\n    data->callback ((data->success && !data->not_supported), data->user_data);\n\n    g_free (data->host);\n    g_free (data);\n}\n\nint\nhttp_tx_manager_check_notif_server (HttpTxManager *manager,\n                                    const char *host,\n                                    gboolean use_notif_server_port,\n                                    HttpNotifServerCallback callback,\n                                    void *user_data)\n{\n    CheckNotifServerData *data = g_new0 (CheckNotifServerData, 1);\n\n    data->host = g_strdup(host);\n    data->use_notif_server_port = use_notif_server_port;\n    data->callback = callback;\n    data->user_data = user_data;\n\n    int ret = seaf_job_manager_schedule_job (seaf->job_mgr,\n                                             check_notif_server_thread,\n                                             check_notif_server_done,\n                                             data);\n    if (ret < 0) {\n        g_free (data->host);\n        g_free (data);\n    }\n\n    return ret;\n}\n\n/* Check Head Commit. */\n\ntypedef struct {\n    char repo_id[41];\n    int repo_version;\n    char *host;\n    char *token;\n    gboolean use_fileserver_port;\n    HttpHeadCommitCallback callback;\n    void *user_data;\n\n    gboolean success;\n    gboolean is_corrupt;\n    gboolean is_deleted;\n    char head_commit[41];\n    int error_code;\n} CheckHeadData;\n\nstatic int\nparse_head_commit_info (const char *rsp_content, int rsp_size, CheckHeadData *data)\n{\n    json_t *object = NULL;\n    json_error_t jerror;\n    const char *head_commit;\n\n    object = json_loadb (rsp_content, rsp_size, 0, &jerror);\n    if (!object) {\n        seaf_warning (\"Parse response failed: %s.\\n\", jerror.text);\n        return -1;\n    }\n\n    if (json_object_has_member (object, \"is_corrupted\") &&\n        json_object_get_int_member (object, \"is_corrupted\"))\n        data->is_corrupt = TRUE;\n\n    if (!data->is_corrupt) {\n        head_commit = json_object_get_string_member (object, \"head_commit_id\");\n        if (!head_commit) {\n            seaf_warning (\"Check head commit for repo %s failed. \"\n                          \"Response doesn't contain head commit id.\\n\",\n                          data->repo_id);\n            json_decref (object);\n            return -1;\n        }\n        memcpy (data->head_commit, head_commit, 40);\n    }\n\n    json_decref (object);\n    return 0;\n}\n\nstatic void *\ncheck_head_commit_thread (void *vdata)\n{\n    CheckHeadData *data = vdata;\n    HttpTxPriv *priv = seaf->http_tx_mgr->priv;\n    ConnectionPool *pool;\n    Connection *conn;\n    CURL *curl;\n    char *url;\n    int status;\n    char *rsp_content = NULL;\n    gint64 rsp_size;\n\n    pool = find_connection_pool (priv, data->host);\n    if (!pool) {\n        seaf_warning (\"Failed to create connection pool for host %s.\\n\", data->host);\n        return vdata;\n    }\n\n    conn = connection_pool_get_connection (pool);\n    if (!conn) {\n        seaf_warning (\"Failed to get connection to host %s.\\n\", data->host);\n        return vdata;\n    }\n\n    curl = conn->curl;\n\n    if (!data->use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/%s/commit/HEAD\",\n                               data->host, data->repo_id);\n    else\n        url = g_strdup_printf (\"%s/repo/%s/commit/HEAD\",\n                               data->host, data->repo_id);\n\n    int curl_error;\n    if (http_get (curl, url, data->token, &status, &rsp_content, &rsp_size,\n                  NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {\n        conn->release = TRUE;\n        data->error_code = curl_error_to_http_task_error (curl_error);\n        goto out;\n    }\n\n    if (status == HTTP_OK) {\n        if (parse_head_commit_info (rsp_content, rsp_size, data) < 0) {\n            data->error_code = SYNC_ERROR_ID_NETWORK;\n            goto out;\n        }\n        data->success = TRUE;\n    } else if (status == HTTP_REPO_DELETED) {\n        data->is_deleted = TRUE;\n        data->success = TRUE;\n    } else {\n        seaf_warning (\"Bad response code for GET %s: %d.\\n\", url, status);\n        data->error_code = http_error_to_http_task_error (status);\n    }\n\nout:\n    g_free (url);\n    g_free (rsp_content);\n    connection_pool_return_connection (pool, conn);\n    return vdata;\n}\n\nstatic void\ncheck_head_commit_done (void *vdata)\n{\n    CheckHeadData *data = vdata;\n    HttpHeadCommit result;\n\n    memset (&result, 0, sizeof(result));\n    result.check_success = data->success;\n    result.is_corrupt = data->is_corrupt;\n    result.is_deleted = data->is_deleted;\n    memcpy (result.head_commit, data->head_commit, 40);\n    result.error_code = data->error_code;\n\n    data->callback (&result, data->user_data);\n\n    g_free (data->host);\n    g_free (data->token);\n    g_free (data);\n}\n\nint\nhttp_tx_manager_check_head_commit (HttpTxManager *manager,\n                                   const char *repo_id,\n                                   int repo_version,\n                                   const char *host,\n                                   const char *token,\n                                   gboolean use_fileserver_port,\n                                   HttpHeadCommitCallback callback,\n                                   void *user_data)\n{\n    CheckHeadData *data = g_new0 (CheckHeadData, 1);\n\n    memcpy (data->repo_id, repo_id, 36);\n    data->repo_version = repo_version;\n    data->host = g_strdup(host);\n    data->token = g_strdup(token);\n    data->callback = callback;\n    data->user_data = user_data;\n    data->use_fileserver_port = use_fileserver_port;\n\n    if (seaf_job_manager_schedule_job (seaf->job_mgr,\n                                       check_head_commit_thread,\n                                       check_head_commit_done,\n                                       data) < 0) {\n        g_free (data->host);\n        g_free (data->token);\n        g_free (data);\n        return -1;\n    }\n\n    return 0;\n}\n\n/* Get folder permissions. */\n\nvoid\nhttp_folder_perm_req_free (HttpFolderPermReq *req)\n{\n    if (!req)\n        return;\n    g_free (req->token);\n    g_free (req);\n}\n\nvoid\nhttp_folder_perm_res_free (HttpFolderPermRes *res)\n{\n    GList *ptr;\n\n    if (!res)\n        return;\n    for (ptr = res->user_perms; ptr; ptr = ptr->next)\n        folder_perm_free ((FolderPerm *)ptr->data);\n    for (ptr = res->group_perms; ptr; ptr = ptr->next)\n        folder_perm_free ((FolderPerm *)ptr->data);\n    g_free (res);\n}\n\ntypedef struct {\n    char *host;\n    gboolean use_fileserver_port;\n    GList *requests;\n    HttpGetFolderPermsCallback callback;\n    void *user_data;\n\n    gboolean success;\n    GList *results;\n} GetFolderPermsData;\n\n/* Make sure the path starts with '/' but doesn't end with '/'. */\nstatic char *\ncanonical_perm_path (const char *path)\n{\n    int len = strlen(path);\n    char *copy, *ret;\n\n    if (strcmp (path, \"/\") == 0)\n        return g_strdup(path);\n\n    if (path[0] == '/' && path[len-1] != '/')\n        return g_strdup(path);\n\n    copy = g_strdup(path);\n\n    if (copy[len-1] == '/')\n        copy[len-1] = 0;\n\n    if (copy[0] != '/')\n        ret = g_strconcat (\"/\", copy, NULL);\n    else\n        ret = copy;\n\n    return ret;\n}\n\nstatic GList *\nparse_permission_list (json_t *array, gboolean *error)\n{\n    GList *ret = NULL, *ptr;\n    json_t *object, *member;\n    size_t n;\n    int i;\n    FolderPerm *perm;\n    const char *str;\n\n    *error = FALSE;\n\n    n = json_array_size (array);\n    for (i = 0; i < n; ++i) {\n        object = json_array_get (array, i);\n\n        perm = g_new0 (FolderPerm, 1);\n\n        member = json_object_get (object, \"path\");\n        if (!member) {\n            seaf_warning (\"Invalid folder perm response format: no path.\\n\");\n            *error = TRUE;\n            goto out;\n        }\n        str = json_string_value(member);\n        if (!str) {\n            seaf_warning (\"Invalid folder perm response format: invalid path.\\n\");\n            *error = TRUE;\n            goto out;\n        }\n        perm->path = canonical_perm_path (str);\n\n        member = json_object_get (object, \"permission\");\n        if (!member) {\n            seaf_warning (\"Invalid folder perm response format: no permission.\\n\");\n            *error = TRUE;\n            goto out;\n        }\n        str = json_string_value(member);\n        if (!str) {\n            seaf_warning (\"Invalid folder perm response format: invalid permission.\\n\");\n            *error = TRUE;\n            goto out;\n        }\n        perm->permission = g_strdup(str);\n\n        ret = g_list_append (ret, perm);\n    }\n\nout:\n    if (*error) {\n        for (ptr = ret; ptr; ptr = ptr->next)\n            folder_perm_free ((FolderPerm *)ptr->data);\n        g_list_free (ret);\n        ret = NULL;\n    }\n\n    return ret;\n}\n\nstatic int\nparse_folder_perms (const char *rsp_content, int rsp_size, GetFolderPermsData *data)\n{\n    json_t *array = NULL, *object, *member;\n    json_error_t jerror;\n    size_t n;\n    int i;\n    GList *results = NULL, *ptr;\n    HttpFolderPermRes *res;\n    const char *repo_id;\n    int ret = 0;\n    gboolean error;\n\n    array = json_loadb (rsp_content, rsp_size, 0, &jerror);\n    if (!array) {\n        seaf_warning (\"Parse response failed: %s.\\n\", jerror.text);\n        return -1;\n    }\n\n    n = json_array_size (array);\n    for (i = 0; i < n; ++i) {\n        object = json_array_get (array, i);\n\n        res = g_new0 (HttpFolderPermRes, 1);\n\n        member = json_object_get (object, \"repo_id\");\n        if (!member) {\n            seaf_warning (\"Invalid folder perm response format: no repo_id.\\n\");\n            ret = -1;\n            goto out;\n        }\n        repo_id = json_string_value(member);\n        if (strlen(repo_id) != 36) {\n            seaf_warning (\"Invalid folder perm response format: invalid repo_id.\\n\");\n            ret = -1;\n            goto out;\n        }\n        memcpy (res->repo_id, repo_id, 36);\n \n        member = json_object_get (object, \"ts\");\n        if (!member) {\n            seaf_warning (\"Invalid folder perm response format: no timestamp.\\n\");\n            ret = -1;\n            goto out;\n        }\n        res->timestamp = json_integer_value (member);\n\n        member = json_object_get (object, \"user_perms\");\n        if (!member) {\n            seaf_warning (\"Invalid folder perm response format: no user_perms.\\n\");\n            ret = -1;\n            goto out;\n        }\n        res->user_perms = parse_permission_list (member, &error);\n        if (error) {\n            ret = -1;\n            goto out;\n        }\n\n        member = json_object_get (object, \"group_perms\");\n        if (!member) {\n            seaf_warning (\"Invalid folder perm response format: no group_perms.\\n\");\n            ret = -1;\n            goto out;\n        }\n        res->group_perms = parse_permission_list (member, &error);\n        if (error) {\n            ret = -1;\n            goto out;\n        }\n\n        results = g_list_append (results, res);\n    }\n\nout:\n    json_decref (array);\n\n    if (ret < 0) {\n        for (ptr = results; ptr; ptr = ptr->next)\n            http_folder_perm_res_free ((HttpFolderPermRes *)ptr->data);\n        g_list_free (results);\n    } else {\n        data->results = results;\n    }\n\n    return ret;\n}\n\nstatic char *\ncompose_get_folder_perms_request (GList *requests)\n{\n    GList *ptr;\n    HttpFolderPermReq *req;\n    json_t *object, *array;\n    char *req_str = NULL;\n\n    array = json_array ();\n\n    for (ptr = requests; ptr; ptr = ptr->next) {\n        req = ptr->data;\n\n        object = json_object ();\n        json_object_set_new (object, \"repo_id\", json_string(req->repo_id));\n        json_object_set_new (object, \"token\", json_string(req->token));\n        json_object_set_new (object, \"ts\", json_integer(req->timestamp));\n\n        json_array_append_new (array, object);\n    }\n\n    req_str = json_dumps (array, 0);\n    if (!req_str) {\n        seaf_warning (\"Faile to json_dumps.\\n\");\n    }\n\n    json_decref (array);\n    return req_str;\n}\n\nstatic void *\nget_folder_perms_thread (void *vdata)\n{\n    GetFolderPermsData *data = vdata;\n    HttpTxPriv *priv = seaf->http_tx_mgr->priv;\n    ConnectionPool *pool;\n    Connection *conn;\n    CURL *curl;\n    char *url;\n    char *req_content = NULL;\n    int status;\n    char *rsp_content = NULL;\n    gint64 rsp_size;\n    GList *ptr;\n\n    pool = find_connection_pool (priv, data->host);\n    if (!pool) {\n        seaf_warning (\"Failed to create connection pool for host %s.\\n\", data->host);\n        return vdata;\n    }\n\n    conn = connection_pool_get_connection (pool);\n    if (!conn) {\n        seaf_warning (\"Failed to get connection to host %s.\\n\", data->host);\n        return vdata;\n    }\n\n    curl = conn->curl;\n\n    if (!data->use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/folder-perm\", data->host);\n    else\n        url = g_strdup_printf (\"%s/repo/folder-perm\", data->host);\n\n    req_content = compose_get_folder_perms_request (data->requests);\n    if (!req_content)\n        goto out;\n\n    if (http_post (curl, url, NULL, req_content, strlen(req_content),\n                   &status, &rsp_content, &rsp_size, TRUE, NULL) < 0) {\n        conn->release = TRUE;\n        goto out;\n    }\n\n    if (status == HTTP_OK) {\n        if (parse_folder_perms (rsp_content, rsp_size, data) < 0)\n            goto out;\n        data->success = TRUE;\n    } else {\n        seaf_warning (\"Bad response code for GET %s: %d.\\n\", url, status);\n    }\n\nout:\n    for (ptr = data->requests; ptr; ptr = ptr->next)\n        http_folder_perm_req_free ((HttpFolderPermReq *)ptr->data);\n    g_list_free (data->requests);\n\n    g_free (url);\n    g_free (req_content);\n    g_free (rsp_content);\n    connection_pool_return_connection (pool, conn);\n    return vdata;\n}\n\nstatic void\nget_folder_perms_done (void *vdata)\n{\n    GetFolderPermsData *data = vdata;\n    HttpFolderPerms cb_data;\n\n    memset (&cb_data, 0, sizeof(cb_data));\n    cb_data.success = data->success;\n    cb_data.results = data->results;\n\n    data->callback (&cb_data, data->user_data);\n\n    GList *ptr;\n    for (ptr = data->results; ptr; ptr = ptr->next)\n        http_folder_perm_res_free ((HttpFolderPermRes *)ptr->data);\n    g_list_free (data->results);\n\n    g_free (data->host);\n    g_free (data);\n}\n\nint\nhttp_tx_manager_get_folder_perms (HttpTxManager *manager,\n                                  const char *host,\n                                  gboolean use_fileserver_port,\n                                  GList *folder_perm_requests,\n                                  HttpGetFolderPermsCallback callback,\n                                  void *user_data)\n{\n    GetFolderPermsData *data = g_new0 (GetFolderPermsData, 1);\n\n    data->host = g_strdup(host);\n    data->requests = folder_perm_requests;\n    data->callback = callback;\n    data->user_data = user_data;\n    data->use_fileserver_port = use_fileserver_port;\n\n    if (seaf_job_manager_schedule_job (seaf->job_mgr,\n                                       get_folder_perms_thread,\n                                       get_folder_perms_done,\n                                       data) < 0) {\n        g_free (data->host);\n        g_free (data);\n        return -1;\n    }\n\n    return 0;\n}\n\n/* Get Locked Files. */\n\nvoid\nhttp_locked_files_req_free (HttpLockedFilesReq *req)\n{\n    if (!req)\n        return;\n    g_free (req->token);\n    g_free (req);\n}\n\nvoid\nhttp_locked_files_res_free (HttpLockedFilesRes *res)\n{\n    if (!res)\n        return;\n\n    g_hash_table_destroy (res->locked_files);\n    g_free (res);\n}\n\ntypedef struct {\n    char *host;\n    gboolean use_fileserver_port;\n    GList *requests;\n    HttpGetLockedFilesCallback callback;\n    void *user_data;\n\n    gboolean success;\n    GList *results;\n} GetLockedFilesData;\n\nstatic GHashTable *\nparse_locked_file_list (json_t *array)\n{\n    GHashTable *ret = NULL;\n    size_t n, i;\n    json_t *obj, *string, *integer;\n\n    ret = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);\n    if (!ret) {\n        return NULL;\n    }\n\n    n = json_array_size (array);\n    for (i = 0; i < n; ++i) {\n        obj = json_array_get (array, i);\n        string = json_object_get (obj, \"path\");\n        if (!string) {\n            g_hash_table_destroy (ret);\n            return NULL;\n        }\n        integer = json_object_get (obj, \"by_me\");\n        if (!integer) {\n            g_hash_table_destroy (ret);\n            return NULL;\n        }\n        g_hash_table_insert (ret,\n                             g_strdup(json_string_value(string)),\n                             (void*)(long)json_integer_value(integer));\n    }\n\n    return ret;\n}\n\nstatic int\nparse_locked_files (const char *rsp_content, int rsp_size, GetLockedFilesData *data)\n{\n    json_t *array = NULL, *object, *member;\n    json_error_t jerror;\n    size_t n;\n    int i;\n    GList *results = NULL;\n    HttpLockedFilesRes *res;\n    const char *repo_id;\n    int ret = 0;\n\n    array = json_loadb (rsp_content, rsp_size, 0, &jerror);\n    if (!array) {\n        seaf_warning (\"Parse response failed: %s.\\n\", jerror.text);\n        return -1;\n    }\n\n    n = json_array_size (array);\n    for (i = 0; i < n; ++i) {\n        object = json_array_get (array, i);\n\n        res = g_new0 (HttpLockedFilesRes, 1);\n\n        member = json_object_get (object, \"repo_id\");\n        if (!member) {\n            seaf_warning (\"Invalid locked files response format: no repo_id.\\n\");\n            ret = -1;\n            goto out;\n        }\n        repo_id = json_string_value(member);\n        if (strlen(repo_id) != 36) {\n            seaf_warning (\"Invalid locked files response format: invalid repo_id.\\n\");\n            ret = -1;\n            goto out;\n        }\n        memcpy (res->repo_id, repo_id, 36);\n \n        member = json_object_get (object, \"ts\");\n        if (!member) {\n            seaf_warning (\"Invalid locked files response format: no timestamp.\\n\");\n            ret = -1;\n            goto out;\n        }\n        res->timestamp = json_integer_value (member);\n\n        member = json_object_get (object, \"locked_files\");\n        if (!member) {\n            seaf_warning (\"Invalid locked files response format: no locked_files.\\n\");\n            ret = -1;\n            goto out;\n        }\n\n        res->locked_files = parse_locked_file_list (member);\n        if (res->locked_files == NULL) {\n            ret = -1;\n            goto out;\n        }\n\n        results = g_list_append (results, res);\n    }\n\nout:\n    json_decref (array);\n\n    if (ret < 0) {\n        g_list_free_full (results, (GDestroyNotify)http_locked_files_res_free);\n    } else {\n        data->results = results;\n    }\n\n    return ret;\n}\n\nstatic char *\ncompose_get_locked_files_request (GList *requests)\n{\n    GList *ptr;\n    HttpLockedFilesReq *req;\n    json_t *object, *array;\n    char *req_str = NULL;\n\n    array = json_array ();\n\n    for (ptr = requests; ptr; ptr = ptr->next) {\n        req = ptr->data;\n\n        object = json_object ();\n        json_object_set_new (object, \"repo_id\", json_string(req->repo_id));\n        json_object_set_new (object, \"token\", json_string(req->token));\n        json_object_set_new (object, \"ts\", json_integer(req->timestamp));\n\n        json_array_append_new (array, object);\n    }\n\n    req_str = json_dumps (array, 0);\n    if (!req_str) {\n        seaf_warning (\"Faile to json_dumps.\\n\");\n    }\n\n    json_decref (array);\n    return req_str;\n}\n\nstatic void *\nget_locked_files_thread (void *vdata)\n{\n    GetLockedFilesData *data = vdata;\n    HttpTxPriv *priv = seaf->http_tx_mgr->priv;\n    ConnectionPool *pool;\n    Connection *conn;\n    CURL *curl;\n    char *url;\n    char *req_content = NULL;\n    int status;\n    char *rsp_content = NULL;\n    gint64 rsp_size;\n\n    pool = find_connection_pool (priv, data->host);\n    if (!pool) {\n        seaf_warning (\"Failed to create connection pool for host %s.\\n\", data->host);\n        return vdata;\n    }\n\n    conn = connection_pool_get_connection (pool);\n    if (!conn) {\n        seaf_warning (\"Failed to get connection to host %s.\\n\", data->host);\n        return vdata;\n    }\n\n    curl = conn->curl;\n\n    if (!data->use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/locked-files\", data->host);\n    else\n        url = g_strdup_printf (\"%s/repo/locked-files\", data->host);\n\n    req_content = compose_get_locked_files_request (data->requests);\n    if (!req_content)\n        goto out;\n\n    if (http_post (curl, url, NULL, req_content, strlen(req_content),\n                   &status, &rsp_content, &rsp_size, TRUE, NULL) < 0) {\n        conn->release = TRUE;\n        goto out;\n    }\n\n    if (status == HTTP_OK) {\n        if (parse_locked_files (rsp_content, rsp_size, data) < 0)\n            goto out;\n        data->success = TRUE;\n    } else {\n        seaf_warning (\"Bad response code for GET %s: %d.\\n\", url, status);\n    }\n\nout:\n    g_list_free_full (data->requests, (GDestroyNotify)http_locked_files_req_free);\n\n    g_free (url);\n    g_free (req_content);\n    g_free (rsp_content);\n    connection_pool_return_connection (pool, conn);\n    return vdata;\n}\n\nstatic void\nget_locked_files_done (void *vdata)\n{\n    GetLockedFilesData *data = vdata;\n    HttpLockedFiles cb_data;\n\n    memset (&cb_data, 0, sizeof(cb_data));\n    cb_data.success = data->success;\n    cb_data.results = data->results;\n\n    data->callback (&cb_data, data->user_data);\n\n    g_list_free_full (data->results, (GDestroyNotify)http_locked_files_res_free);\n\n    g_free (data->host);\n    g_free (data);\n}\n\nint\nhttp_tx_manager_get_locked_files (HttpTxManager *manager,\n                                  const char *host,\n                                  gboolean use_fileserver_port,\n                                  GList *locked_files_requests,\n                                  HttpGetLockedFilesCallback callback,\n                                  void *user_data)\n{\n    GetLockedFilesData *data = g_new0 (GetLockedFilesData, 1);\n\n    data->host = g_strdup(host);\n    data->requests = locked_files_requests;\n    data->callback = callback;\n    data->user_data = user_data;\n    data->use_fileserver_port = use_fileserver_port;\n\n    if (seaf_job_manager_schedule_job (seaf->job_mgr,\n                                       get_locked_files_thread,\n                                       get_locked_files_done,\n                                       data) < 0) {\n        g_free (data->host);\n        g_free (data);\n        return -1;\n    }\n\n    return 0;\n}\n\n/* Synchronous interfaces for locking/unlocking a file on the server. */\n\nint\nhttp_tx_manager_lock_file (HttpTxManager *manager,\n                           const char *host,\n                           gboolean use_fileserver_port,\n                           const char *token,\n                           const char *repo_id,\n                           const char *path)\n{\n    HttpTxPriv *priv = seaf->http_tx_mgr->priv;\n    ConnectionPool *pool;\n    Connection *conn;\n    CURL *curl;\n    char *url;\n    int status;\n    int ret = 0;\n\n    pool = find_connection_pool (priv, host);\n    if (!pool) {\n        seaf_warning (\"Failed to create connection pool for host %s.\\n\", host);\n        return -1;\n    }\n\n    conn = connection_pool_get_connection (pool);\n    if (!conn) {\n        seaf_warning (\"Failed to get connection to host %s.\\n\", host);\n        return -1;\n    }\n\n    curl = conn->curl;\n\n    char *esc_path = g_uri_escape_string(path, NULL, FALSE);\n    if (!use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/%s/lock-file?p=%s\", host, repo_id, esc_path);\n    else\n        url = g_strdup_printf (\"%s/repo/%s/lock-file?p=%s\", host, repo_id, esc_path);\n    g_free (esc_path);\n\n    if (http_put (curl, url, token, NULL, 0, NULL, NULL, NULL,\n                  &status, NULL, NULL, TRUE, NULL) < 0) {\n        conn->release = TRUE;\n        ret = -1;\n        goto out;\n    }\n\n    if (status != HTTP_OK) {\n        seaf_warning (\"Bad response code for PUT %s: %d.\\n\", url, status);\n        ret = -1;\n    }\n\nout:\n    g_free (url);\n    connection_pool_return_connection (pool, conn);\n    return ret;\n}\n\nint\nhttp_tx_manager_unlock_file (HttpTxManager *manager,\n                             const char *host,\n                             gboolean use_fileserver_port,\n                             const char *token,\n                             const char *repo_id,\n                             const char *path)\n{\n    HttpTxPriv *priv = seaf->http_tx_mgr->priv;\n    ConnectionPool *pool;\n    Connection *conn;\n    CURL *curl;\n    char *url;\n    int status;\n    int ret = 0;\n\n    pool = find_connection_pool (priv, host);\n    if (!pool) {\n        seaf_warning (\"Failed to create connection pool for host %s.\\n\", host);\n        return -1;\n    }\n\n    conn = connection_pool_get_connection (pool);\n    if (!conn) {\n        seaf_warning (\"Failed to get connection to host %s.\\n\", host);\n        return -1;\n    }\n\n    curl = conn->curl;\n\n    char *esc_path = g_uri_escape_string(path, NULL, FALSE);\n    if (!use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/%s/unlock-file?p=%s\", host, repo_id, esc_path);\n    else\n        url = g_strdup_printf (\"%s/repo/%s/unlock-file?p=%s\", host, repo_id, esc_path);\n    g_free (esc_path);\n\n    if (http_put (curl, url, token, NULL, 0, NULL, NULL, NULL,\n                  &status, NULL, NULL, TRUE, NULL) < 0) {\n        conn->release = TRUE;\n        ret = -1;\n        goto out;\n    }\n\n    if (status != HTTP_OK) {\n        seaf_warning (\"Bad response code for PUT %s: %d.\\n\", url, status);\n        ret = -1;\n    }\n\nout:\n    g_free (url);\n    connection_pool_return_connection (pool, conn);\n    return ret;\n}\n\ntypedef struct {\n    char *host;\n    char *url;\n    char *api_token;\n    HttpAPIGetCallback callback;\n    void *user_data;\n\n    gboolean success;\n    char *rsp_content;\n    int rsp_size;\n    int error_code;\n    int http_status;\n} APIGetData;\n\nstatic void *\nfileserver_api_get_request (void *vdata)\n{\n    APIGetData *data = vdata;\n    HttpTxPriv *priv = seaf->http_tx_mgr->priv;\n    ConnectionPool *pool;\n    Connection *conn;\n    CURL *curl;\n    int status;\n    char *rsp_content = NULL;\n    gint64 rsp_size;\n\n\n    pool = find_connection_pool (priv, data->host);\n    if (!pool) {\n        seaf_warning (\"Failed to create connection pool for host %s.\\n\", data->host);\n        return vdata;\n    }\n\n    conn = connection_pool_get_connection (pool);\n    if (!conn) {\n        seaf_warning (\"Failed to get connection to host %s.\\n\", data->host);\n        return vdata;\n    }\n\n    curl = conn->curl;\n\n    int curl_error;\n    if (http_get (curl, data->url, data->api_token, &status, &rsp_content, &rsp_size, NULL, NULL,\n                  TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {\n        conn->release = TRUE;\n        data->error_code = curl_error_to_http_task_error (curl_error);\n        goto out;\n    }\n\n    data->rsp_content = rsp_content;\n    data->rsp_size = rsp_size;\n\n    if (status == HTTP_OK) {\n        data->success = TRUE;\n    } else {\n        seaf_warning (\"Bad response code for GET %s: %d.\\n\", data->url, status);\n        data->error_code = http_error_to_http_task_error (status);\n        data->http_status = status;\n    }\n\nout:\n    connection_pool_return_connection (pool, conn);\n    return vdata;\n\n}\n\nstatic void\nfileserver_api_get_request_done (void *vdata)\n{\n    APIGetData *data = vdata;\n    HttpAPIGetResult cb_data;\n\n    memset (&cb_data, 0, sizeof(cb_data));\n    cb_data.success = data->success;\n    cb_data.rsp_content = data->rsp_content;\n    cb_data.rsp_size = data->rsp_size;\n    cb_data.error_code = data->error_code;\n    cb_data.http_status = data->http_status;\n\n    data->callback (&cb_data, data->user_data);\n\n    g_free (data->rsp_content);\n    g_free (data->host);\n    g_free (data->url);\n    g_free (data->api_token);\n    g_free (data);\n}\n\nint\nhttp_tx_manager_fileserver_api_get  (HttpTxManager *manager,\n                                     const char *host,\n                                     const char *url,\n                                     const char *api_token,\n                                     HttpAPIGetCallback callback,\n                                     void *user_data)\n{\n    APIGetData *data = g_new0 (APIGetData, 1);\n\n    data->host = g_strdup(host);\n    data->url = g_strdup(url);\n    data->api_token = g_strdup(api_token);\n    data->callback = callback;\n    data->user_data = user_data;\n\n    if (seaf_job_manager_schedule_job (seaf->job_mgr,\n                                       fileserver_api_get_request,\n                                       fileserver_api_get_request_done,\n                                       data) < 0) {\n        g_free (data->rsp_content);\n        g_free (data->host);\n        g_free (data->url);\n        g_free (data->api_token);\n        g_free (data);\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic char *\nrepo_id_list_to_json (GList *repo_id_list)\n{\n    json_t *array = json_array();\n    GList *ptr;\n    char *repo_id;\n\n    for (ptr = repo_id_list; ptr; ptr = ptr->next) {\n        repo_id = ptr->data;\n        json_array_append_new (array, json_string(repo_id));\n    }\n\n    char *data = json_dumps (array, JSON_COMPACT);\n    if (!data) {\n        seaf_warning (\"Failed to dump json.\\n\");\n        json_decref (array);\n        return NULL;\n    }\n\n    json_decref (array);\n    return data;\n}\n\nstatic GHashTable *\nrepo_head_commit_map_from_json (const char *json_str, gint64 len)\n{\n    json_t *object;\n    json_error_t jerror;\n    GHashTable *ret;\n\n    object = json_loadb (json_str, (size_t)len, 0, &jerror);\n    if (!object) {\n        seaf_warning (\"Failed to load json: %s\\n\", jerror.text);\n        return NULL;\n    }\n\n    ret = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);\n\n    void *iter = json_object_iter (object);\n    const char *key;\n    json_t *value;\n    while (iter) {\n        key = json_object_iter_key (iter);\n        value = json_object_iter_value (iter);\n        if (json_typeof(value) != JSON_STRING) {\n            seaf_warning (\"Bad json object format when parsing head commit id map.\\n\");\n            g_hash_table_destroy (ret);\n            goto out;\n        }\n        g_hash_table_replace (ret, g_strdup (key), g_strdup(json_string_value(value)));\n        iter = json_object_iter_next (object, iter);\n    }\n\nout:\n    json_decref (object);\n    return ret;\n}\n\nGHashTable *\nhttp_tx_manager_get_head_commit_ids (HttpTxManager *manager,\n                                     const char *host,\n                                     gboolean use_fileserver_port,\n                                     GList *repo_id_list,\n                                     int *ret_status)\n{\n    HttpTxPriv *priv = seaf->http_tx_mgr->priv;\n    ConnectionPool *pool;\n    Connection *conn;\n    CURL *curl;\n    char *url;\n    char *req_content = NULL;\n    gint64 req_size;\n    int status;\n    char *rsp_content = NULL;\n    gint64 rsp_size;\n    GHashTable *map = NULL;\n\n    pool = find_connection_pool (priv, host);\n    if (!pool) {\n        seaf_warning (\"Failed to create connection pool for host %s.\\n\", host);\n        return NULL;\n    }\n\n    conn = connection_pool_get_connection (pool);\n    if (!conn) {\n        seaf_warning (\"Failed to get connection to host %s.\\n\", host);\n        return NULL;\n    }\n\n    curl = conn->curl;\n\n    if (!use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/head-commits-multi/\", host);\n    else\n        url = g_strdup_printf (\"%s/repo/head-commits-multi/\", host);\n\n    req_content = repo_id_list_to_json (repo_id_list);\n    req_size = strlen(req_content);\n\n    if (http_post (curl, url, NULL, req_content, req_size,\n                   &status, &rsp_content, &rsp_size, TRUE, NULL) < 0) {\n        conn->release = TRUE;\n        goto out;\n    }\n\n    *ret_status = status;\n\n    if (status != HTTP_OK) {\n        seaf_warning (\"Bad response code for POST %s: %d\\n\", url, status);\n        goto out;\n    }\n\n    map = repo_head_commit_map_from_json (rsp_content, rsp_size);\n\nout:\n    g_free (url);\n    connection_pool_return_connection (pool, conn);\n    /* returned by json_dumps(). */\n    free (req_content);\n    g_free (rsp_content);\n    return map;\n}\n\nstatic gboolean\nremove_task_help (gpointer key, gpointer value, gpointer user_data)\n{\n    HttpTxTask *task = value;\n    const char *repo_id = user_data;\n\n    if (strcmp(task->repo_id, repo_id) != 0)\n        return FALSE;\n\n    return TRUE;\n}\n\nstatic void\nclean_tasks_for_repo (HttpTxManager *manager, const char *repo_id)\n{\n    g_hash_table_foreach_remove (manager->priv->download_tasks,\n                                 remove_task_help, (gpointer)repo_id);\n\n    g_hash_table_foreach_remove (manager->priv->upload_tasks,\n                                 remove_task_help, (gpointer)repo_id);\n}\n\nstatic int\ncheck_permission (HttpTxTask *task, Connection *conn)\n{\n    CURL *curl;\n    char *url;\n    int status;\n    char *rsp_content = NULL;\n    gint64 rsp_size;\n    int ret = 0;\n    json_t *rsp_obj = NULL, *reason = NULL, *unsyncable_path = NULL;\n    const char *reason_str = NULL, *unsyncable_path_str = NULL;\n    json_error_t jerror;\n\n    curl = conn->curl;\n\n    const char *type = (task->type == HTTP_TASK_TYPE_DOWNLOAD) ? \"download\" : \"upload\";\n    const char *url_prefix = (task->use_fileserver_port) ? \"\" : \"seafhttp/\";\n    if (seaf->client_name) {\n        char *client_name = g_uri_escape_string (seaf->client_name,\n                                                 NULL, FALSE);\n        url = g_strdup_printf (\"%s/%srepo/%s/permission-check/?op=%s\"\n                               \"&client_id=%s&client_name=%s\",\n                               task->host, url_prefix, task->repo_id, type,\n                               seaf->client_id, client_name);\n        g_free (client_name);\n    } else {\n        url = g_strdup_printf (\"%s/%srepo/%s/permission-check/?op=%s\",\n                               task->host, url_prefix, task->repo_id, type);\n    }\n\n    int curl_error;\n    if (http_get (curl, url, task->token, &status, &rsp_content, &rsp_size, NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {\n        conn->release = TRUE;\n        handle_curl_errors (task, curl_error);\n        ret = -1;\n        goto out;\n    }\n\n    if (status != HTTP_OK) {\n        seaf_warning (\"Bad response code for GET %s: %d.\\n\", url, status);\n\n        if (status != HTTP_FORBIDDEN || !rsp_content) {\n            handle_http_errors (task, status);\n            ret = -1;\n            goto out;\n        }\n\n        rsp_obj = json_loadb (rsp_content, rsp_size, 0 ,&jerror);\n        if (!rsp_obj) {\n            seaf_warning (\"Parse check permission response failed: %s.\\n\", jerror.text);\n            handle_http_errors (task, status);\n            json_decref (rsp_obj);\n            ret = -1;\n            goto out;\n        }\n\n        reason = json_object_get (rsp_obj, \"reason\");\n        if (!reason) {\n            handle_http_errors (task, status);\n            json_decref (rsp_obj);\n            ret = -1;\n            goto out;\n        }\n\n        reason_str = json_string_value (reason);\n        if (g_strcmp0 (reason_str, \"no write permission\") == 0) {\n            task->error = SYNC_ERROR_ID_NO_WRITE_PERMISSION;\n        } else if (g_strcmp0 (reason_str, \"unsyncable share permission\") == 0) {\n            task->error = SYNC_ERROR_ID_PERM_NOT_SYNCABLE;\n\n            unsyncable_path = json_object_get (rsp_obj, \"unsyncable_path\");\n            if (!unsyncable_path) {\n                json_decref (rsp_obj);\n                ret = -1;\n                goto out;\n            }\n\n            unsyncable_path_str = json_string_value (unsyncable_path);\n            if (unsyncable_path_str)\n                send_file_sync_error_notification (task->repo_id, task->repo_name,\n                                                   unsyncable_path_str,\n                                                   SYNC_ERROR_ID_PERM_NOT_SYNCABLE);\n        } else {\n            task->error = SYNC_ERROR_ID_ACCESS_DENIED;\n        }\n\n        ret = -1;\n    }\n\nout:\n    g_free (url);\n    g_free (rsp_content);\n    curl_easy_reset (curl);\n\n    return ret;\n}\n\n/* Upload. */\n\nstatic void *http_upload_thread (void *vdata);\nstatic void http_upload_done (void *vdata);\n\nint\nhttp_tx_manager_add_upload (HttpTxManager *manager,\n                            const char *repo_id,\n                            int repo_version,\n                            const char *host,\n                            const char *token,\n                            int protocol_version,\n                            gboolean use_fileserver_port,\n                            GError **error)\n{\n    HttpTxTask *task;\n    SeafRepo *repo;\n\n    if (!repo_id) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Empty argument(repo_id)\");\n        return -1;\n    }\n\n    if (!token) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Empty argument(token)\");\n        return -1;\n    }\n\n    if (!host) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Empty argument(host)\");\n        return -1;\n    }\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Repo not found\");\n        return -1;\n    }\n\n    clean_tasks_for_repo (manager, repo_id);\n\n    task = http_tx_task_new (manager, repo_id, repo_version,\n                             HTTP_TASK_TYPE_UPLOAD, FALSE,\n                             host, token, NULL, NULL);\n\n    task->protocol_version = protocol_version;\n\n    task->state = HTTP_TASK_STATE_NORMAL;\n\n    task->use_fileserver_port = use_fileserver_port;\n\n    task->repo_name = g_strdup(repo->name);\n\n    g_hash_table_insert (manager->priv->upload_tasks,\n                         g_strdup(repo_id),\n                         task);\n\n    if (seaf_job_manager_schedule_job (seaf->job_mgr,\n                                       http_upload_thread,\n                                       http_upload_done,\n                                       task) < 0) {\n        g_hash_table_remove (manager->priv->upload_tasks, repo_id);\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic gboolean\ndirent_same (SeafDirent *dent1, SeafDirent *dent2)\n{\n    return (strcmp(dent1->id, dent2->id) == 0 &&\n            dent1->mode == dent2->mode &&\n            dent1->mtime == dent2->mtime);\n}\n\ntypedef struct {\n    HttpTxTask *task;\n    gint64 delta;\n    GHashTable *active_paths;\n} CalcQuotaDeltaData;\n\nstatic int\ncheck_quota_and_active_paths_diff_files (int n, const char *basedir,\n                                         SeafDirent *files[], void *vdata)\n{\n    CalcQuotaDeltaData *data = vdata;\n    SeafDirent *file1 = files[0];\n    SeafDirent *file2 = files[1];\n    gint64 size1, size2;\n    char *path;\n\n    if (file1 && file2) {\n        size1 = file1->size;\n        size2 = file2->size;\n        data->delta += (size1 - size2);\n\n        if (!dirent_same (file1, file2)) {\n            path = g_strconcat(basedir, file1->name, NULL);\n            g_hash_table_replace (data->active_paths, path, (void*)(long)S_IFREG);\n        }\n    } else if (file1 && !file2) {\n        data->delta += file1->size;\n\n        path = g_strconcat (basedir, file1->name, NULL);\n        g_hash_table_replace (data->active_paths, path, (void*)(long)S_IFREG);\n    } else if (!file1 && file2) {\n        data->delta -= file2->size;\n    }\n\n    return 0;\n}\n\nstatic int\ncheck_quota_and_active_paths_diff_dirs (int n, const char *basedir,\n                                        SeafDirent *dirs[], void *vdata,\n                                        gboolean *recurse)\n{\n    CalcQuotaDeltaData *data = vdata;\n    SeafDirent *dir1 = dirs[0];\n    SeafDirent *dir2 = dirs[1];\n    char *path;\n\n    /* When a new empty dir is created, or a dir became empty. */\n    if ((!dir2 && dir1 && strcmp(dir1->id, EMPTY_SHA1) == 0) ||\n\t(dir2 && dir1 && strcmp(dir1->id, EMPTY_SHA1) == 0 && strcmp(dir2->id, EMPTY_SHA1) != 0)) {\n        path = g_strconcat (basedir, dir1->name, NULL);\n        g_hash_table_replace (data->active_paths, path, (void*)(long)S_IFDIR);\n    }\n\n    return 0;\n}\n\nstatic int\ncalculate_upload_size_delta_and_active_paths (HttpTxTask *task,\n                                              gint64 *delta,\n                                              GHashTable *active_paths)\n{\n    int ret = 0;\n    SeafBranch *local = NULL, *master = NULL;\n    SeafCommit *local_head = NULL, *master_head = NULL;\n\n    local = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, \"local\");\n    if (!local) {\n        seaf_warning (\"Branch local not found for repo %.8s.\\n\", task->repo_id);\n        ret = -1;\n        goto out;\n    }\n    master = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, \"master\");\n    if (!master) {\n        seaf_warning (\"Branch master not found for repo %.8s.\\n\", task->repo_id);\n        ret = -1;\n        goto out;\n    }\n\n    local_head = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                                 task->repo_id, task->repo_version,\n                                                 local->commit_id);\n    if (!local_head) {\n        seaf_warning (\"Local head commit not found for repo %.8s.\\n\",\n                      task->repo_id);\n        ret = -1;\n        goto out;\n    }\n    master_head = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                                 task->repo_id, task->repo_version,\n                                                 master->commit_id);\n    if (!master_head) {\n        seaf_warning (\"Master head commit not found for repo %.8s.\\n\",\n                      task->repo_id);\n        ret = -1;\n        goto out;\n    }\n\n    CalcQuotaDeltaData data;\n    memset (&data, 0, sizeof(data));\n    data.task = task;\n    data.active_paths = active_paths;\n\n    DiffOptions opts;\n    memset (&opts, 0, sizeof(opts));\n    memcpy (opts.store_id, task->repo_id, 36);\n    opts.version = task->repo_version;\n    opts.file_cb = check_quota_and_active_paths_diff_files;\n    opts.dir_cb = check_quota_and_active_paths_diff_dirs;\n    opts.data = &data;\n\n    const char *trees[2];\n    trees[0] = local_head->root_id;\n    trees[1] = master_head->root_id;\n    if (diff_trees (2, trees, &opts) < 0) {\n        seaf_warning (\"Failed to diff local and master head for repo %.8s.\\n\",\n                      task->repo_id);\n        ret = -1;\n        goto out;\n    }\n\n    *delta = data.delta;\n\nout:\n    seaf_branch_unref (local);\n    seaf_branch_unref (master);\n    seaf_commit_unref (local_head);\n    seaf_commit_unref (master_head);\n\n    return ret;\n}\n\nstatic int\ncheck_quota (HttpTxTask *task, Connection *conn, gint64 delta)\n{\n    CURL *curl;\n    char *url;\n    int status;\n    int ret = 0;\n\n    curl = conn->curl;\n\n    if (!task->use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/%s/quota-check/?delta=%\"G_GINT64_FORMAT\"\",\n                               task->host, task->repo_id, delta);\n    else\n        url = g_strdup_printf (\"%s/repo/%s/quota-check/?delta=%\"G_GINT64_FORMAT\"\",\n                               task->host, task->repo_id, delta);\n\n    int curl_error;\n    if (http_get (curl, url, task->token, &status, NULL, NULL, NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {\n        conn->release = TRUE;\n        handle_curl_errors (task, curl_error);\n        ret = -1;\n        goto out;\n    }\n\n    if (status != HTTP_OK) {\n        seaf_warning (\"Bad response code for GET %s: %d.\\n\", url, status);\n        handle_http_errors (task, status);\n        ret = -1;\n    }\n\nout:\n    g_free (url);\n    curl_easy_reset (curl);\n\n    return ret;\n}\n\nstatic int\nsend_commit_object (HttpTxTask *task, Connection *conn)\n{\n    CURL *curl;\n    char *url;\n    int status;\n    char *data;\n    int len;\n    int ret = 0;\n\n    if (seaf_obj_store_read_obj (seaf->commit_mgr->obj_store,\n                                 task->repo_id, task->repo_version,\n                                 task->head, (void**)&data, &len) < 0) {\n        seaf_warning (\"Failed to read commit %s.\\n\", task->head);\n        task->error = SYNC_ERROR_ID_LOCAL_DATA_CORRUPT;\n        return -1;\n    }\n\n    curl = conn->curl;\n\n    if (!task->use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/%s/commit/%s\",\n                               task->host, task->repo_id, task->head);\n    else\n        url = g_strdup_printf (\"%s/repo/%s/commit/%s\",\n                               task->host, task->repo_id, task->head);\n\n    int curl_error;\n    if (http_put (curl, url, task->token,\n                  data, len,\n                  NULL, NULL, NULL,\n                  &status, NULL, NULL, TRUE, &curl_error) < 0) {\n        conn->release = TRUE;\n        handle_curl_errors (task, curl_error);\n        ret = -1;\n        goto out;\n    }\n\n    if (status != HTTP_OK) {\n        seaf_warning (\"Bad response code for PUT %s: %d.\\n\", url, status);\n        handle_http_errors (task, status);\n        ret = -1;\n    }\n\nout:\n    g_free (url);\n    curl_easy_reset (curl);\n    g_free (data);\n\n    return ret;\n}\n\ntypedef struct {\n    GList **pret;\n    GHashTable *checked_objs;\n} CalcFsListData;\n\nstatic int\ncollect_file_ids (int n, const char *basedir, SeafDirent *files[], void *vdata)\n{\n    SeafDirent *file1 = files[0];\n    SeafDirent *file2 = files[1];\n    CalcFsListData *data = vdata;\n    GList **pret = data->pret;\n    int dummy;\n\n    if (!file1 || strcmp (file1->id, EMPTY_SHA1) == 0)\n        return 0;\n\n    if (g_hash_table_lookup (data->checked_objs, file1->id))\n        return 0;\n\n    if (!file2 || strcmp (file1->id, file2->id) != 0) {\n        *pret = g_list_prepend (*pret, g_strdup(file1->id));\n        g_hash_table_insert (data->checked_objs, g_strdup(file1->id), &dummy);\n    }\n\n    return 0;\n}\n\nstatic int\ncollect_dir_ids (int n, const char *basedir, SeafDirent *dirs[], void *vdata,\n                 gboolean *recurse)\n{\n    SeafDirent *dir1 = dirs[0];\n    SeafDirent *dir2 = dirs[1];\n    CalcFsListData *data = vdata;\n    GList **pret = data->pret;\n    int dummy;\n\n    if (!dir1 || strcmp (dir1->id, EMPTY_SHA1) == 0)\n        return 0;\n\n    if (g_hash_table_lookup (data->checked_objs, dir1->id))\n        return 0;\n\n    if (!dir2 || strcmp (dir1->id, dir2->id) != 0) {\n        *pret = g_list_prepend (*pret, g_strdup(dir1->id));\n        g_hash_table_insert (data->checked_objs, g_strdup(dir1->id), &dummy);\n    }\n\n    return 0;\n}\n\nstatic GList *\ncalculate_send_fs_object_list (HttpTxTask *task)\n{\n    GList *ret = NULL;\n    SeafBranch *local = NULL, *master = NULL;\n    SeafCommit *local_head = NULL, *master_head = NULL;\n    GList *ptr;\n\n    local = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, \"local\");\n    if (!local) {\n        seaf_warning (\"Branch local not found for repo %.8s.\\n\", task->repo_id);\n        goto out;\n    }\n    master = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, \"master\");\n    if (!master) {\n        seaf_warning (\"Branch master not found for repo %.8s.\\n\", task->repo_id);\n        goto out;\n    }\n\n    local_head = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                                 task->repo_id, task->repo_version,\n                                                 local->commit_id);\n    if (!local_head) {\n        seaf_warning (\"Local head commit not found for repo %.8s.\\n\",\n                      task->repo_id);\n        goto out;\n    }\n    master_head = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                                  task->repo_id, task->repo_version,\n                                                  master->commit_id);\n    if (!master_head) {\n        seaf_warning (\"Master head commit not found for repo %.8s.\\n\",\n                      task->repo_id);\n        goto out;\n    }\n\n    /* Diff won't traverse the root object itself. */\n    if (strcmp (local_head->root_id, master_head->root_id) != 0)\n        ret = g_list_prepend (ret, g_strdup(local_head->root_id));\n\n    CalcFsListData *data = g_new0(CalcFsListData, 1);\n    data->pret = &ret;\n    data->checked_objs = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                                g_free, NULL);\n\n    DiffOptions opts;\n    memset (&opts, 0, sizeof(opts));\n    memcpy (opts.store_id, task->repo_id, 36);\n    opts.version = task->repo_version;\n    opts.file_cb = collect_file_ids;\n    opts.dir_cb = collect_dir_ids;\n    opts.data = data;\n\n    const char *trees[2];\n    trees[0] = local_head->root_id;\n    trees[1] = master_head->root_id;\n    if (diff_trees (2, trees, &opts) < 0) {\n        seaf_warning (\"Failed to diff local and master head for repo %.8s.\\n\",\n                      task->repo_id);\n        for (ptr = ret; ptr; ptr = ptr->next)\n            g_free (ptr->data);\n        ret = NULL;\n    }\n\n    g_hash_table_destroy (data->checked_objs);\n    g_free (data);\n\nout:\n    seaf_branch_unref (local);\n    seaf_branch_unref (master);\n    seaf_commit_unref (local_head);\n    seaf_commit_unref (master_head);\n    return ret;\n}\n\n#define ID_LIST_SEGMENT_N 1000\n\nstatic int\nupload_check_id_list_segment (HttpTxTask *task, Connection *conn, const char *url,\n                              GList **send_id_list, GList **recv_id_list)\n{\n    json_t *array;\n    json_error_t jerror;\n    char *obj_id;\n    int n_sent = 0;\n    char *data = NULL;\n    int len;\n    CURL *curl;\n    int status;\n    char *rsp_content = NULL;\n    gint64 rsp_size;\n    int ret = 0;\n\n    /* Convert object id list to JSON format. */\n\n    array = json_array ();\n\n    while (*send_id_list != NULL) {\n        obj_id = (*send_id_list)->data;\n        json_array_append_new (array, json_string(obj_id));\n\n        *send_id_list = g_list_delete_link (*send_id_list, *send_id_list);\n        g_free (obj_id);\n\n        if (++n_sent >= ID_LIST_SEGMENT_N)\n            break;\n    }\n\n    seaf_debug (\"Check %d ids for %s:%s.\\n\",\n                n_sent, task->host, task->repo_id);\n\n    data = json_dumps (array, 0);\n    len = strlen(data);\n    json_decref (array);\n\n    /* Send fs object id list. */\n\n    curl = conn->curl;\n\n    int curl_error;\n    if (http_post (curl, url, task->token,\n                   data, len,\n                   &status, &rsp_content, &rsp_size, TRUE, &curl_error) < 0) {\n        conn->release = TRUE;\n        handle_curl_errors (task, curl_error);\n        ret = -1;\n        goto out;\n    }\n\n    if (status != HTTP_OK) {\n        seaf_warning (\"Bad response code for POST %s: %d.\\n\", url, status);\n        handle_http_errors (task, status);\n        ret = -1;\n        goto out;\n    }\n\n    /* Process needed object id list. */\n\n    array = json_loadb (rsp_content, rsp_size, 0, &jerror);\n    if (!array) {\n        seaf_warning (\"Invalid JSON response from the server: %s.\\n\", jerror.text);\n        task->error = SYNC_ERROR_ID_SERVER;\n        ret = -1;\n        goto out;\n    }\n\n    int i;\n    size_t n = json_array_size (array);\n    json_t *str;\n\n    seaf_debug (\"%lu objects or blocks are needed for %s:%s.\\n\",\n                n, task->host, task->repo_id);\n\n    for (i = 0; i < n; ++i) {\n        str = json_array_get (array, i);\n        if (!str) {\n            seaf_warning (\"Invalid JSON response from the server.\\n\");\n            json_decref (array);\n            ret = -1;\n            goto out;\n        }\n\n        *recv_id_list = g_list_prepend (*recv_id_list, g_strdup(json_string_value(str)));\n    }\n\n    json_decref (array);\n\nout:\n    curl_easy_reset (curl);\n    g_free (data);\n    g_free (rsp_content);\n\n    return ret;\n}\n\n#define MAX_OBJECT_PACK_SIZE (1 << 20) /* 1MB */\n\n#ifdef WIN32\n__pragma(pack(push, 1))\ntypedef struct {\n    char obj_id[40];\n    guint32 obj_size;\n    guint8 object[0];\n} ObjectHeader;\n__pragma(pack(pop))\n#else\ntypedef struct {\n    char obj_id[40];\n    guint32 obj_size;\n    guint8 object[0];\n} __attribute__((__packed__)) ObjectHeader;\n#endif\n\nstatic int\nsend_fs_objects (HttpTxTask *task, Connection *conn, GList **send_fs_list)\n{\n    struct evbuffer *buf;\n    ObjectHeader hdr;\n    char *obj_id;\n    char *data;\n    int len;\n    int total_size;\n    unsigned char *package;\n    CURL *curl;\n    char *url = NULL;\n    int status;\n    int ret = 0;\n    int n_sent = 0;\n\n    buf = evbuffer_new ();\n    curl = conn->curl;\n\n    while (*send_fs_list != NULL) {\n        obj_id = (*send_fs_list)->data;\n\n        if (seaf_obj_store_read_obj (seaf->fs_mgr->obj_store,\n                                     task->repo_id, task->repo_version,\n                                     obj_id, (void **)&data, &len) < 0) {\n            seaf_warning (\"Failed to read fs object %s in repo %s.\\n\",\n                          obj_id, task->repo_id);\n            task->error = SYNC_ERROR_ID_LOCAL_DATA_CORRUPT;\n            ret = -1;\n            goto out;\n        }\n\n        ++n_sent;\n\n        memcpy (hdr.obj_id, obj_id, 40);\n        hdr.obj_size = htonl (len);\n\n        evbuffer_add (buf, &hdr, sizeof(hdr));\n        evbuffer_add (buf, data, len);\n\n        g_free (data);\n        *send_fs_list = g_list_delete_link (*send_fs_list, *send_fs_list);\n        g_free (obj_id);\n\n        total_size = evbuffer_get_length (buf);\n        if (total_size >= MAX_OBJECT_PACK_SIZE)\n            break;\n    }\n\n    seaf_debug (\"Sending %d fs objects for %s:%s.\\n\",\n                n_sent, task->host, task->repo_id);\n\n    package = evbuffer_pullup (buf, -1);\n\n    if (!task->use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/%s/recv-fs/\",\n                               task->host, task->repo_id);\n    else\n        url = g_strdup_printf (\"%s/repo/%s/recv-fs/\",\n                               task->host, task->repo_id);\n\n    int curl_error;\n    if (http_post (curl, url, task->token,\n                   (char *)package, evbuffer_get_length(buf),\n                   &status, NULL, NULL, TRUE, &curl_error) < 0) {\n        conn->release = TRUE;\n        handle_curl_errors (task, curl_error);\n        ret = -1;\n        goto out;\n    }\n\n    if (status != HTTP_OK) {\n        seaf_warning (\"Bad response code for POST %s: %d.\\n\", url, status);\n        handle_http_errors (task, status);\n        ret = -1;\n    }\n\nout:\n    g_free (url);\n    evbuffer_free (buf);\n    curl_easy_reset (curl);\n\n    return ret;\n}\n\ntypedef struct {\n    GList *block_list;\n    GHashTable *added_blocks;\n    HttpTxTask *task;\n} CalcBlockListData;\n\nstatic void\nadd_to_block_list (GList **block_list, GHashTable *added_blocks, const char *block_id)\n{\n    int dummy;\n\n    if (g_hash_table_lookup (added_blocks, block_id))\n        return;\n\n    *block_list = g_list_prepend (*block_list, g_strdup(block_id));\n    g_hash_table_insert (added_blocks, g_strdup(block_id), &dummy);\n}\n\nstatic int\nblock_list_diff_files (int n, const char *basedir, SeafDirent *files[], void *vdata)\n{\n    SeafDirent *file1 = files[0];\n    SeafDirent *file2 = files[1];\n    CalcBlockListData *data = vdata;\n    HttpTxTask *task = data->task;\n    Seafile *f1 = NULL, *f2 = NULL;\n    int i;\n\n    if (file1 && strcmp (file1->id, EMPTY_SHA1) != 0) {\n        if (!file2) {\n            f1 = seaf_fs_manager_get_seafile (seaf->fs_mgr,\n                                              task->repo_id, task->repo_version,\n                                              file1->id);\n            if (!f1) {\n                seaf_warning (\"Failed to get seafile object %s:%s.\\n\",\n                              task->repo_id, file1->id);\n                return -1;\n            }\n            for (i = 0; i < f1->n_blocks; ++i)\n                add_to_block_list (&data->block_list, data->added_blocks,\n                                   f1->blk_sha1s[i]);\n            seafile_unref (f1);\n        } else if (strcmp (file1->id, file2->id) != 0) {\n            f1 = seaf_fs_manager_get_seafile (seaf->fs_mgr,\n                                              task->repo_id, task->repo_version,\n                                              file1->id);\n            if (!f1) {\n                seaf_warning (\"Failed to get seafile object %s:%s.\\n\",\n                              task->repo_id, file1->id);\n                return -1;\n            }\n            f2 = seaf_fs_manager_get_seafile (seaf->fs_mgr,\n                                              task->repo_id, task->repo_version,\n                                              file2->id);\n            if (!f2) {\n                seafile_unref (f1);\n                seaf_warning (\"Failed to get seafile object %s:%s.\\n\",\n                              task->repo_id, file2->id);\n                return -1;\n            }\n\n            GHashTable *h = g_hash_table_new (g_str_hash, g_str_equal);\n            int dummy;\n            for (i = 0; i < f2->n_blocks; ++i)\n                g_hash_table_insert (h, f2->blk_sha1s[i], &dummy);\n\n            for (i = 0; i < f1->n_blocks; ++i)\n                if (!g_hash_table_lookup (h, f1->blk_sha1s[i]))\n                    add_to_block_list (&data->block_list, data->added_blocks,\n                                       f1->blk_sha1s[i]);\n\n            seafile_unref (f1);\n            seafile_unref (f2);\n            g_hash_table_destroy (h);\n        }\n    }\n\n    return 0;\n}\n\nstatic int\nblock_list_diff_dirs (int n, const char *basedir, SeafDirent *dirs[], void *data,\n                      gboolean *recurse)\n{\n    /* Do nothing */\n    return 0;\n}\n\nstatic int\ncalculate_block_list (HttpTxTask *task, GList **plist)\n{\n    int ret = 0;\n    SeafBranch *local = NULL, *master = NULL;\n    SeafCommit *local_head = NULL, *master_head = NULL;\n\n    local = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, \"local\");\n    if (!local) {\n        seaf_warning (\"Branch local not found for repo %.8s.\\n\", task->repo_id);\n        ret = -1;\n        goto out;\n    }\n    master = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, \"master\");\n    if (!master) {\n        seaf_warning (\"Branch master not found for repo %.8s.\\n\", task->repo_id);\n        ret = -1;\n        goto out;\n    }\n\n    local_head = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                                 task->repo_id, task->repo_version,\n                                                 local->commit_id);\n    if (!local_head) {\n        seaf_warning (\"Local head commit not found for repo %.8s.\\n\",\n                      task->repo_id);\n        ret = -1;\n        goto out;\n    }\n    master_head = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                                 task->repo_id, task->repo_version,\n                                                 master->commit_id);\n    if (!master_head) {\n        seaf_warning (\"Master head commit not found for repo %.8s.\\n\",\n                      task->repo_id);\n        ret = -1;\n        goto out;\n    }\n\n    CalcBlockListData data;\n    memset (&data, 0, sizeof(data));\n    data.added_blocks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);\n    data.task = task;\n\n    DiffOptions opts;\n    memset (&opts, 0, sizeof(opts));\n    memcpy (opts.store_id, task->repo_id, 36);\n    opts.version = task->repo_version;\n    opts.file_cb = block_list_diff_files;\n    opts.dir_cb = block_list_diff_dirs;\n    opts.data = &data;\n\n    const char *trees[2];\n    trees[0] = local_head->root_id;\n    trees[1] = master_head->root_id;\n    if (diff_trees (2, trees, &opts) < 0) {\n        seaf_warning (\"Failed to diff local and master head for repo %.8s.\\n\",\n                      task->repo_id);\n        g_hash_table_destroy (data.added_blocks);\n\n        GList *ptr;\n        for (ptr = data.block_list; ptr; ptr = ptr->next)\n            g_free (ptr->data);\n\n        ret = -1;\n        goto out;\n    }\n\n    g_hash_table_destroy (data.added_blocks);\n    *plist = data.block_list;\n\nout:\n    seaf_branch_unref (local);\n    seaf_branch_unref (master);\n    seaf_commit_unref (local_head);\n    seaf_commit_unref (master_head);\n    return ret;\n}\n\ntypedef struct {\n    char block_id[41];\n    BlockHandle *block;\n    HttpTxTask *task;\n} SendBlockData;\n\nstatic size_t\nsend_block_callback (void *ptr, size_t size, size_t nmemb, void *userp)\n{\n    size_t realsize = size *nmemb;\n    SendBlockData *data = userp;\n    HttpTxTask *task = data->task;\n    int n;\n\n    if (task->state == HTTP_TASK_STATE_CANCELED || task->all_stop)\n        return CURL_READFUNC_ABORT;\n\n    n = seaf_block_manager_read_block (seaf->block_mgr,\n                                       data->block,\n                                       ptr, realsize);\n    if (n < 0) {\n        seaf_warning (\"Failed to read block %s in repo %.8s.\\n\",\n                      data->block_id, task->repo_id);\n        task->error = SYNC_ERROR_ID_LOCAL_DATA_CORRUPT;\n        return CURL_READFUNC_ABORT;\n    }\n\n    /* Update global transferred bytes. */\n    g_atomic_int_add (&(seaf->sync_mgr->sent_bytes), n);\n\n    /* Update transferred bytes for this task */\n    g_atomic_int_add (&task->tx_bytes, n);\n\n    /* If uploaded bytes exceeds the limit, wait until the counter\n     * is reset. We check the counter every 100 milliseconds, so we\n     * can waste up to 100 milliseconds without sending data after\n     * the counter is reset.\n     */\n    while (1) {\n        gint sent = g_atomic_int_get(&(seaf->sync_mgr->sent_bytes));\n        if (seaf->sync_mgr->upload_limit > 0 &&\n            sent > seaf->sync_mgr->upload_limit)\n            /* 100 milliseconds */\n            g_usleep (100000);\n        else\n            break;\n    }\n\n    return n;\n}\n\nstatic size_t\nrewind_block_callback (void *clientp, curl_off_t offset, int origin)\n{\n    if (offset != 0 || origin != 0) {\n        return CURL_SEEKFUNC_FAIL;\n    }\n\n    SendBlockData *data = clientp;\n    HttpTxTask *task = data->task;\n\n    int rc = seaf_block_manager_rewind_block (seaf->block_mgr,\n                                              data->block);\n    if (rc < 0) {\n        seaf_warning (\"Failed to rewind block %s in repo %s.\\n\",\n                      data->block_id, task->repo_id);\n        return CURL_SEEKFUNC_FAIL;\n    }\n\n    return CURL_SEEKFUNC_OK;\n}\n\nstatic int\nsend_block (HttpTxTask *task, Connection *conn, const char *block_id, guint32 *psize)\n{\n    CURL *curl;\n    char *url;\n    int status;\n    BlockMetadata *bmd;\n    BlockHandle *block;\n    int ret = 0;\n\n    bmd = seaf_block_manager_stat_block (seaf->block_mgr,\n                                         task->repo_id, task->repo_version,\n                                         block_id);\n    if (!bmd) {\n        seaf_warning (\"Failed to stat block %s in repo %s.\\n\",\n                      block_id, task->repo_id);\n        task->error = SYNC_ERROR_ID_LOCAL_DATA_CORRUPT;\n        return -1;\n    }\n\n    block = seaf_block_manager_open_block (seaf->block_mgr,\n                                           task->repo_id, task->repo_version,\n                                           block_id, BLOCK_READ);\n    if (!block) {\n        seaf_warning (\"Failed to open block %s in repo %s.\\n\",\n                      block_id, task->repo_id);\n        task->error = SYNC_ERROR_ID_LOCAL_DATA_CORRUPT;\n        g_free (bmd);\n        return -1;\n    }\n\n    SendBlockData data;\n    memset (&data, 0, sizeof(data));\n    memcpy (data.block_id, block_id, 40);\n    data.block = block;\n    data.task = task;\n\n    curl = conn->curl;\n\n    if (!task->use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/%s/block/%s\",\n                               task->host, task->repo_id, block_id);\n    else\n        url = g_strdup_printf (\"%s/repo/%s/block/%s\",\n                               task->host, task->repo_id, block_id);\n\n    int curl_error;\n    if (http_put (curl, url, task->token,\n                  NULL, bmd->size,\n                  send_block_callback, &data, rewind_block_callback,\n                  &status, NULL, NULL, TRUE, &curl_error) < 0) {\n        if (task->state == HTTP_TASK_STATE_CANCELED)\n            goto out;\n\n        if (task->error == SYNC_ERROR_ID_NO_ERROR) {\n            /* Only release connection when it's a network error */\n            conn->release = TRUE;\n            handle_curl_errors (task, curl_error);\n        }\n        ret = -1;\n        goto out;\n    }\n\n    if (status != HTTP_OK) {\n        seaf_warning (\"Bad response code for PUT %s: %d.\\n\", url, status);\n        handle_http_errors (task, status);\n        ret = -1;\n        goto out;\n    }\n\n    if (psize)\n        *psize = bmd->size;\n\nout:\n    g_free (url);\n    curl_easy_reset (curl);\n    g_free (bmd);\n    seaf_block_manager_close_block (seaf->block_mgr, block);\n    seaf_block_manager_block_handle_free (seaf->block_mgr, block);\n\n    return ret;\n}\n\ntypedef struct BlockUploadData {\n    HttpTxTask *http_task;\n    ConnectionPool *cpool;\n    GAsyncQueue *finished_tasks;\n} BlockUploadData;\n\ntypedef struct BlockUploadTask {\n    char block_id[41];\n    int result;\n    guint32 block_size;\n} BlockUploadTask;\n\nstatic void\nblock_upload_task_free (BlockUploadTask *task)\n{\n    g_free (task);\n}\n\nstatic void\nupload_block_thread_func (gpointer data, gpointer user_data)\n{\n    BlockUploadTask *task = data;\n    BlockUploadData *tx_data = user_data;\n    HttpTxTask *http_task = tx_data->http_task;\n    Connection *conn;\n    int ret = 0;\n\n    conn = connection_pool_get_connection (tx_data->cpool);\n    if (!conn) {\n        seaf_warning (\"Failed to get connection to host %s.\\n\", http_task->host);\n        http_task->error = SYNC_ERROR_ID_NOT_ENOUGH_MEMORY;\n        ret = -1;\n        goto out;\n    }\n\n    ret = send_block (http_task, conn, task->block_id, &task->block_size);\n\n    connection_pool_return_connection (tx_data->cpool, conn);\n\nout:\n    task->result = ret;\n    g_async_queue_push (tx_data->finished_tasks, task);\n}\n\n#define DEFAULT_UPLOAD_BLOCK_THREADS 3\n\nstatic int\nmulti_threaded_send_blocks (HttpTxTask *http_task, GList *block_list)\n{\n    HttpTxPriv *priv = seaf->http_tx_mgr->priv;\n    GThreadPool *tpool;\n    GAsyncQueue *finished_tasks;\n    GHashTable *pending_tasks;\n    ConnectionPool *cpool;\n    GList *ptr;\n    char *block_id;\n    BlockUploadTask *task;\n    SyncInfo *info;\n    int ret = 0;\n\n    if (block_list == NULL)\n        return 0;\n\n    cpool = find_connection_pool (priv, http_task->host);\n    if (!cpool) {\n        seaf_warning (\"Failed to create connection pool for host %s.\\n\", http_task->host);\n        http_task->error = SYNC_ERROR_ID_NOT_ENOUGH_MEMORY;\n        return -1;\n    }\n\n    finished_tasks = g_async_queue_new ();\n\n    BlockUploadData data;\n    data.http_task = http_task;\n    data.finished_tasks = finished_tasks;\n    data.cpool = cpool;\n\n    tpool = g_thread_pool_new (upload_block_thread_func, &data,\n                               DEFAULT_UPLOAD_BLOCK_THREADS, FALSE, NULL);\n\n    pending_tasks = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                           g_free,\n                                           (GDestroyNotify)block_upload_task_free);\n\n    info = seaf_sync_manager_get_sync_info (seaf->sync_mgr, http_task->repo_id);\n\n    for (ptr = block_list; ptr; ptr = ptr->next) {\n        block_id = ptr->data;\n\n        task = g_new0 (BlockUploadTask, 1);\n        memcpy (task->block_id, block_id, 40);\n\n        if (!g_hash_table_lookup (pending_tasks, block_id)) {\n            g_hash_table_insert (pending_tasks, g_strdup(block_id), task);\n            g_thread_pool_push (tpool, task, NULL);\n        } else {\n            g_free (task);\n        }\n    }\n\n    while ((task = g_async_queue_pop (finished_tasks)) != NULL) {\n        if (task->result < 0 || http_task->state == HTTP_TASK_STATE_CANCELED) {\n            ret = task->result;\n            http_task->all_stop = TRUE;\n            break;\n        }\n\n        ++(http_task->done_blocks);\n\n        if (info && info->multipart_upload) {\n            info->uploaded_bytes += (gint64)task->block_size;\n        }\n\n        g_hash_table_remove (pending_tasks, task->block_id);\n        if (g_hash_table_size(pending_tasks) == 0)\n            break;\n    }\n\n    g_thread_pool_free (tpool, TRUE, TRUE);\n\n    g_hash_table_destroy (pending_tasks);\n\n    g_async_queue_unref (finished_tasks);\n\n    return ret;\n}\n\nstatic void\nnotify_permission_error (HttpTxTask *task, const char *error_str)\n{\n    HttpTxPriv *priv = seaf->http_tx_mgr->priv;\n    GMatchInfo *match_info;\n    char *path;\n\n    if (g_regex_match (priv->locked_error_regex, error_str, 0, &match_info)) {\n        path = g_match_info_fetch (match_info, 1);\n        send_file_sync_error_notification (task->repo_id, task->repo_name, path,\n                                           SYNC_ERROR_ID_FILE_LOCKED);\n        g_free (path);\n\n        /* Set more accurate error. */\n        task->error = SYNC_ERROR_ID_FILE_LOCKED;\n    } else if (g_regex_match (priv->folder_perm_error_regex, error_str, 0, &match_info)) {\n        path = g_match_info_fetch (match_info, 1);\n        /* The path returned by server begins with '/'. */\n        send_file_sync_error_notification (task->repo_id, task->repo_name,\n                                           (path[0] == '/') ? (path + 1) : path,\n                                           SYNC_ERROR_ID_FOLDER_PERM_DENIED);\n        g_free (path);\n\n        task->error = SYNC_ERROR_ID_FOLDER_PERM_DENIED;\n    } else if (g_regex_match (priv->too_many_files_error_regex, error_str, 0, &match_info)) {\n        task->error = SYNC_ERROR_ID_TOO_MANY_FILES;\n    }\n\n    g_match_info_free (match_info);\n}\n\nstatic int\nupdate_branch (HttpTxTask *task, Connection *conn)\n{\n    CURL *curl;\n    char *url;\n    int status;\n    char *rsp_content = NULL;\n    char *rsp_content_str = NULL;\n    gint64 rsp_size;\n    int ret = 0;\n\n    curl = conn->curl;\n\n    if (!task->use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/%s/commit/HEAD/?head=%s\",\n                               task->host, task->repo_id, task->head);\n    else\n        url = g_strdup_printf (\"%s/repo/%s/commit/HEAD/?head=%s\",\n                               task->host, task->repo_id, task->head);\n\n    int curl_error;\n    if (http_put (curl, url, task->token,\n                  NULL, 0,\n                  NULL, NULL, NULL,\n                  &status, &rsp_content, &rsp_size, TRUE, &curl_error) < 0) {\n        conn->release = TRUE;\n        handle_curl_errors (task, curl_error);\n        ret = -1;\n        goto out;\n    }\n\n    if (status != HTTP_OK) {\n        seaf_warning (\"Bad response code for PUT %s: %d.\\n\", url, status);\n        handle_http_errors (task, status);\n\n        if (status == HTTP_FORBIDDEN || status == HTTP_BLOCK_MISSING) {\n            rsp_content_str = g_new0 (gchar, rsp_size + 1);\n            memcpy (rsp_content_str, rsp_content, rsp_size);\n            if (status == HTTP_FORBIDDEN) {\n                seaf_warning (\"%s\\n\", rsp_content_str);\n                notify_permission_error (task, rsp_content_str);\n            } else if (status == HTTP_BLOCK_MISSING) {\n                seaf_warning (\"Failed to upload files: %s\\n\", rsp_content_str);\n                task->error = SYNC_ERROR_ID_BLOCK_MISSING;\n            }\n            g_free (rsp_content_str);\n        }\n\n        ret = -1;\n    }\n\nout:\n    g_free (url);\n    g_free (rsp_content);\n    curl_easy_reset (curl);\n\n    return ret;\n}\n\nstatic void\nupdate_master_branch (HttpTxTask *task)\n{\n    SeafBranch *branch;\n    branch = seaf_branch_manager_get_branch (seaf->branch_mgr,\n                                             task->repo_id,\n                                             \"master\");\n    if (!branch) {\n        branch = seaf_branch_new (\"master\", task->repo_id, task->head);\n        seaf_branch_manager_add_branch (seaf->branch_mgr, branch);\n        seaf_branch_unref (branch);\n    } else {\n        seaf_branch_set_commit (branch, task->head);\n        seaf_branch_manager_update_branch (seaf->branch_mgr, branch);\n        seaf_branch_unref (branch);\n    }\n}\n\nstatic void\nset_path_status_syncing (gpointer key, gpointer value, gpointer user_data)\n{\n    HttpTxTask *task = user_data;\n    char *path = key;\n    int mode = (int)(long)value;\n    seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                          task->repo_id,\n                                          path,\n                                          mode,\n                                          SYNC_STATUS_SYNCING,\n                                          TRUE);\n}\n\nstatic void\nset_path_status_synced (gpointer key, gpointer value, gpointer user_data)\n{\n    HttpTxTask *task = user_data;\n    char *path = key;\n    int mode = (int)(long)value;\n    seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                          task->repo_id,\n                                          path,\n                                          mode,\n                                          SYNC_STATUS_SYNCED,\n                                          TRUE);\n}\n\nstatic void *\nhttp_upload_thread (void *vdata)\n{\n    HttpTxTask *task = vdata;\n    HttpTxPriv *priv = seaf->http_tx_mgr->priv;\n    ConnectionPool *pool;\n    Connection *conn = NULL;\n    char *url = NULL;\n    GList *send_fs_list = NULL, *needed_fs_list = NULL;\n    GList *block_list = NULL, *needed_block_list = NULL;\n    GHashTable *active_paths = NULL;\n\n    SeafBranch *local = seaf_branch_manager_get_branch (seaf->branch_mgr,\n                                                        task->repo_id, \"local\");\n    if (!local) {\n        seaf_warning (\"Failed to get branch local of repo %.8s.\\n\", task->repo_id);\n        task->error = SYNC_ERROR_ID_LOCAL_DATA_CORRUPT;\n        return NULL;\n    }\n    memcpy (task->head, local->commit_id, 40);\n    seaf_branch_unref (local);\n\n    pool = find_connection_pool (priv, task->host);\n    if (!pool) {\n        seaf_warning (\"Failed to create connection pool for host %s.\\n\", task->host);\n        task->error = SYNC_ERROR_ID_NOT_ENOUGH_MEMORY;\n        goto out;\n    }\n\n    conn = connection_pool_get_connection (pool);\n    if (!conn) {\n        seaf_warning (\"Failed to get connection to host %s.\\n\", task->host);\n        task->error = SYNC_ERROR_ID_NOT_ENOUGH_MEMORY;\n        goto out;\n    }\n\n    /* seaf_message (\"Upload with HTTP sync protocol version %d.\\n\", */\n    /*               task->protocol_version); */\n\n    transition_state (task, task->state, HTTP_TASK_RT_STATE_CHECK);\n\n    gint64 delta = 0;\n    active_paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);\n\n    if (calculate_upload_size_delta_and_active_paths (task, &delta, active_paths) < 0) {\n        seaf_warning (\"Failed to calculate upload size delta for repo %s.\\n\",\n                      task->repo_id);\n        task->error = SYNC_ERROR_ID_LOCAL_DATA_CORRUPT;\n        goto out;\n    }\n\n    g_hash_table_foreach (active_paths, set_path_status_syncing, task);\n\n    if (check_permission (task, conn) < 0) {\n        seaf_warning (\"Upload permission denied for repo %.8s on server %s.\\n\",\n                      task->repo_id, task->host);\n        goto out;\n    }\n\n    if (check_quota (task, conn, delta) < 0) {\n        seaf_warning (\"Not enough quota for repo %.8s on server %s.\\n\",\n                      task->repo_id, task->host);\n        goto out;\n    }\n\n    if (task->state == HTTP_TASK_STATE_CANCELED)\n        goto out;\n\n    transition_state (task, task->state, HTTP_TASK_RT_STATE_COMMIT);\n\n    if (send_commit_object (task, conn) < 0) {\n        seaf_warning (\"Failed to send head commit for repo %.8s.\\n\", task->repo_id);\n        goto out;\n    }\n\n    if (task->state == HTTP_TASK_STATE_CANCELED)\n        goto out;\n\n    transition_state (task, task->state, HTTP_TASK_RT_STATE_FS);\n\n    send_fs_list = calculate_send_fs_object_list (task);\n    if (!send_fs_list) {\n        seaf_warning (\"Failed to calculate fs object list for repo %.8s.\\n\",\n                      task->repo_id);\n        task->error = SYNC_ERROR_ID_LOCAL_DATA_CORRUPT;\n        goto out;\n    }\n\n    if (!task->use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/%s/check-fs/\",\n                               task->host, task->repo_id);\n    else\n        url = g_strdup_printf (\"%s/repo/%s/check-fs/\",\n                               task->host, task->repo_id);\n\n    while (send_fs_list != NULL) {\n        if (upload_check_id_list_segment (task, conn, url,\n                                          &send_fs_list, &needed_fs_list) < 0) {\n            seaf_warning (\"Failed to check fs list for repo %.8s.\\n\", task->repo_id);\n            goto out;\n        }\n\n        if (task->state == HTTP_TASK_STATE_CANCELED)\n            goto out;\n    }\n    g_free (url);\n    url = NULL;\n\n    while (needed_fs_list != NULL) {\n        if (send_fs_objects (task, conn, &needed_fs_list) < 0) {\n            seaf_warning (\"Failed to send fs objects for repo %.8s.\\n\", task->repo_id);\n            goto out;\n        }\n\n        if (task->state == HTTP_TASK_STATE_CANCELED)\n            goto out;\n    }\n\n    transition_state (task, task->state, HTTP_TASK_RT_STATE_BLOCK);\n\n    if (calculate_block_list (task, &block_list) < 0) {\n        seaf_warning (\"Failed to calculate block list for repo %.8s.\\n\",\n                      task->repo_id);\n        task->error = SYNC_ERROR_ID_LOCAL_DATA_CORRUPT;\n        goto out;\n    }\n\n    if (!task->use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/%s/check-blocks/\",\n                               task->host, task->repo_id);\n    else\n        url = g_strdup_printf (\"%s/repo/%s/check-blocks/\",\n                               task->host, task->repo_id);\n\n    while (block_list != NULL) {\n        if (upload_check_id_list_segment (task, conn, url,\n                                          &block_list, &needed_block_list) < 0) {\n            seaf_warning (\"Failed to check block list for repo %.8s.\\n\",\n                          task->repo_id);\n            goto out;\n        }\n\n        if (task->state == HTTP_TASK_STATE_CANCELED)\n            goto out;\n    }\n    g_free (url);\n    url = NULL;\n\n    task->n_blocks = g_list_length (needed_block_list);\n\n    seaf_debug (\"%d blocks to send for %s:%s.\\n\",\n                task->n_blocks, task->host, task->repo_id);\n\n    if (multi_threaded_send_blocks(task, needed_block_list) < 0 ||\n        task->state == HTTP_TASK_STATE_CANCELED)\n        goto out;\n\n    transition_state (task, task->state, HTTP_TASK_RT_STATE_UPDATE_BRANCH);\n\n    if (update_branch (task, conn) < 0) {\n        seaf_warning (\"Failed to update branch of repo %.8s.\\n\", task->repo_id);\n        goto out;\n    }\n\n    /* After successful upload, the cached 'master' branch should be updated to\n     * the head commit of 'local' branch.\n     */\n    update_master_branch (task);\n\n    if (active_paths != NULL)\n        g_hash_table_foreach (active_paths, set_path_status_synced, task);\n\nout:\n    string_list_free (send_fs_list);\n    string_list_free (needed_fs_list);\n    string_list_free (block_list);\n    string_list_free (needed_block_list);\n\n    if (active_paths)\n        g_hash_table_destroy (active_paths);\n\n    g_free (url);\n\n    connection_pool_return_connection (pool, conn);\n\n    return vdata;\n}\n\nstatic void\nhttp_upload_done (void *vdata)\n{\n    HttpTxTask *task = vdata;\n\n    if (task->error != SYNC_ERROR_ID_NO_ERROR)\n        transition_state (task, HTTP_TASK_STATE_ERROR, HTTP_TASK_RT_STATE_FINISHED);\n    else if (task->state == HTTP_TASK_STATE_CANCELED)\n        transition_state (task, task->state, HTTP_TASK_RT_STATE_FINISHED);\n    else\n        transition_state (task, HTTP_TASK_STATE_FINISHED, HTTP_TASK_RT_STATE_FINISHED);\n}\n\n/* Download */\n\nstatic void *http_download_thread (void *vdata);\nstatic void http_download_done (void *vdata);\n\nint\nhttp_tx_manager_add_download (HttpTxManager *manager,\n                              const char *repo_id,\n                              int repo_version,\n                              const char *host,\n                              const char *token,\n                              const char *server_head_id,\n                              gboolean is_clone,\n                              const char *passwd,\n                              const char *worktree,\n                              int protocol_version,\n                              const char *email,\n                              const char *username,\n                              gboolean use_fileserver_port,\n                              const char *repo_name,\n                              GError **error)\n{\n    HttpTxTask *task;\n    SeafRepo *repo;\n\n    if (!repo_id) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Empty argument(repo_id)\");\n        return -1;\n    }\n\n    if (!token) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Empty argument(token)\");\n        return -1;\n    }\n\n    if (!host) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Empty argument(host)\");\n        return -1;\n    }\n\n    if (!server_head_id) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Empty argument(server_head_id)\");\n        return -1;\n    }\n\n    if (!email) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Empty argument(email)\");\n        return -1;\n    }\n\n    if (!is_clone) {\n        repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n        if (!repo) {\n            g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \"Repo not found\");\n            return -1;\n        }\n    }\n\n    clean_tasks_for_repo (manager, repo_id);\n\n    task = http_tx_task_new (manager, repo_id, repo_version,\n                             HTTP_TASK_TYPE_DOWNLOAD, is_clone,\n                             host, token, passwd, worktree);\n\n    memcpy (task->head, server_head_id, 40);\n    task->protocol_version = protocol_version;\n    task->email = g_strdup(email);\n    if (username)\n        task->username = g_strdup(username);\n\n    task->state = HTTP_TASK_STATE_NORMAL;\n\n    task->use_fileserver_port = use_fileserver_port;\n\n    task->blk_ref_cnts = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                                g_free, g_free);\n    pthread_mutex_init (&task->ref_cnt_lock, NULL);\n\n    g_hash_table_insert (manager->priv->download_tasks,\n                         g_strdup(repo_id),\n                         task);\n\n    task->repo_name = g_strdup(repo_name);\n\n    if (seaf_job_manager_schedule_job (seaf->job_mgr,\n                                       http_download_thread,\n                                       http_download_done,\n                                       task) < 0) {\n        g_hash_table_remove (manager->priv->download_tasks, repo_id);\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int\nget_commit_object (HttpTxTask *task, Connection *conn)\n{\n    CURL *curl;\n    char *url;\n    int status;\n    char *rsp_content = NULL;\n    gint64 rsp_size;\n    int ret = 0;\n\n    curl = conn->curl;\n\n    if (!task->use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/%s/commit/%s\",\n                               task->host, task->repo_id, task->head);\n    else\n        url = g_strdup_printf (\"%s/repo/%s/commit/%s\",\n                               task->host, task->repo_id, task->head);\n\n    int curl_error;\n    if (http_get (curl, url, task->token, &status,\n                  &rsp_content, &rsp_size,\n                  NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {\n        conn->release = TRUE;\n        handle_curl_errors (task, curl_error);\n        ret = -1;\n        goto out;\n    }\n\n    if (status != HTTP_OK) {\n        seaf_warning (\"Bad response code for GET %s: %d.\\n\", url, status);\n        handle_http_errors (task, status);\n        ret = -1;\n        goto out;\n    }\n\n    int rc = seaf_obj_store_write_obj (seaf->commit_mgr->obj_store,\n                                       task->repo_id, task->repo_version,\n                                       task->head,\n                                       rsp_content,\n                                       rsp_size,\n                                       FALSE);\n    if (rc < 0) {\n        seaf_warning (\"Failed to save commit %s in repo %.8s.\\n\",\n                      task->head, task->repo_id);\n        task->error = SYNC_ERROR_ID_WRITE_LOCAL_DATA;\n        ret = -1;\n    }\n\nout:\n    g_free (url);\n    g_free (rsp_content);\n    curl_easy_reset (curl);\n\n    return ret;\n}\n\nstatic int\nget_needed_fs_id_list (HttpTxTask *task, Connection *conn, GList **fs_id_list)\n{\n    SeafBranch *master;\n    CURL *curl;\n    char *url = NULL;\n    int status;\n    char *rsp_content = NULL;\n    gint64 rsp_size;\n    int ret = 0;\n    json_t *array;\n    json_error_t jerror;\n    const char *obj_id;\n\n    const char *url_prefix = (task->use_fileserver_port) ? \"\" : \"seafhttp/\";\n\n    if (!task->is_clone) {\n        master = seaf_branch_manager_get_branch (seaf->branch_mgr,\n                                                 task->repo_id,\n                                                 \"master\");\n        if (!master) {\n            seaf_warning (\"Failed to get branch master for repo %.8s.\\n\",\n                          task->repo_id);\n            return -1;\n        }\n\n        url = g_strdup_printf (\"%s/%srepo/%s/fs-id-list/\"\n                               \"?server-head=%s&client-head=%s\",\n                               task->host, url_prefix, task->repo_id,\n                               task->head, master->commit_id);\n\n        seaf_branch_unref (master);\n    } else {\n        url = g_strdup_printf (\"%s/%srepo/%s/fs-id-list/?server-head=%s\",\n                               task->host, url_prefix, task->repo_id, task->head);\n    }\n\n    curl = conn->curl;\n\n    int curl_error;\n    if (http_get (curl, url, task->token, &status,\n                  &rsp_content, &rsp_size,\n                  NULL, NULL, (!task->is_clone), FS_ID_LIST_TIMEOUT_SEC, &curl_error) < 0) {\n        conn->release = TRUE;\n        handle_curl_errors (task, curl_error);\n        ret = -1;\n        goto out;\n    }\n\n    if (status != HTTP_OK) {\n        seaf_warning (\"Bad response code for GET %s: %d.\\n\", url, status);\n        handle_http_errors (task, status);\n        ret = -1;\n        goto out;\n    }\n\n    array = json_loadb (rsp_content, rsp_size, 0, &jerror);\n    if (!array) {\n        seaf_warning (\"Invalid JSON response from the server: %s.\\n\", jerror.text);\n        task->error = SYNC_ERROR_ID_SERVER;\n        ret = -1;\n        goto out;\n    }\n\n    int i;\n    size_t n = json_array_size (array);\n    json_t *str;\n\n    seaf_debug (\"Received fs object list size %lu from %s:%s.\\n\",\n                n, task->host, task->repo_id);\n\n    task->n_fs_objs = (int)n;\n\n    GHashTable *checked_objs = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                                      g_free, NULL);\n\n    for (i = 0; i < n; ++i) {\n        str = json_array_get (array, i);\n        if (!str) {\n            seaf_warning (\"Invalid JSON response from the server.\\n\");\n            json_decref (array);\n            string_list_free (*fs_id_list);\n            ret = -1;\n            goto out;\n        }\n\n        obj_id = json_string_value(str);\n\n        if (g_hash_table_lookup (checked_objs, obj_id)) {\n            ++(task->done_fs_objs);\n            continue;\n        }\n        char *key = g_strdup(obj_id);\n        g_hash_table_replace (checked_objs, key, key);\n\n        if (!seaf_obj_store_obj_exists (seaf->fs_mgr->obj_store,\n                                        task->repo_id, task->repo_version,\n                                        obj_id)) {\n            *fs_id_list = g_list_prepend (*fs_id_list, g_strdup(obj_id));\n        } else if (task->is_clone) {\n            gboolean io_error = FALSE;\n            gboolean sound;\n            sound = seaf_fs_manager_verify_object (seaf->fs_mgr,\n                                                   task->repo_id, task->repo_version,\n                                                   obj_id, FALSE, &io_error);\n            if (!sound && !io_error) {\n                *fs_id_list = g_list_prepend (*fs_id_list, g_strdup(obj_id));\n            } else {\n                ++(task->done_fs_objs);\n            }\n        } else {\n            ++(task->done_fs_objs);\n        }\n    }\n\n    json_decref (array);\n    g_hash_table_destroy (checked_objs);\n\nout:\n    g_free (url);\n    g_free (rsp_content);\n    curl_easy_reset (curl);\n\n    return ret;\n}\n\n#define GET_FS_OBJECT_N 100\n\nstatic int\nget_fs_objects (HttpTxTask *task, Connection *conn, GList **fs_list)\n{\n    json_t *array;\n    char *obj_id;\n    int n_sent = 0;\n    char *data = NULL;\n    int len;\n    CURL *curl;\n    char *url = NULL;\n    int status;\n    char *rsp_content = NULL;\n    gint64 rsp_size;\n    int ret = 0;\n    GHashTable *requested;\n\n    /* Convert object id list to JSON format. */\n\n    requested = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);\n\n    array = json_array ();\n\n    while (*fs_list != NULL) {\n        obj_id = (*fs_list)->data;\n        json_array_append_new (array, json_string(obj_id));\n\n        *fs_list = g_list_delete_link (*fs_list, *fs_list);\n\n        g_hash_table_replace (requested, obj_id, obj_id);\n\n        if (++n_sent >= GET_FS_OBJECT_N)\n            break;\n    }\n\n    seaf_debug (\"Requesting %d fs objects from %s:%s.\\n\",\n                n_sent, task->host, task->repo_id);\n\n    data = json_dumps (array, 0);\n    len = strlen(data);\n    json_decref (array);\n\n    /* Send fs object id list. */\n\n    curl = conn->curl;\n\n    if (!task->use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/%s/pack-fs/\", task->host, task->repo_id);\n    else\n        url = g_strdup_printf (\"%s/repo/%s/pack-fs/\", task->host, task->repo_id);\n\n    int curl_error;\n    if (http_post (curl, url, task->token,\n                   data, len,\n                   &status, &rsp_content, &rsp_size, TRUE, &curl_error) < 0) {\n        conn->release = TRUE;\n        handle_curl_errors (task, curl_error);\n        ret = -1;\n        goto out;\n    }\n\n    if (status != HTTP_OK) {\n        seaf_warning (\"Bad response code for POST %s: %d.\\n\", url, status);\n        handle_http_errors (task, status);\n        ret = -1;\n        goto out;\n    }\n\n    /* Save received fs objects. */\n\n    int n_recv = 0;\n    char *p = rsp_content;\n    ObjectHeader *hdr = (ObjectHeader *)p;\n    char recv_obj_id[41];\n    int n = 0;\n    int size;\n    int rc;\n    while (n < rsp_size) {\n        memcpy (recv_obj_id, hdr->obj_id, 40);\n        recv_obj_id[40] = 0;\n        size = ntohl (hdr->obj_size);\n        if (n + sizeof(ObjectHeader) + size > rsp_size) {\n            seaf_warning (\"Incomplete object package received for repo %.8s.\\n\",\n                          task->repo_id);\n            task->error = SYNC_ERROR_ID_SERVER;\n            ret = -1;\n            goto out;\n        }\n\n        ++n_recv;\n\n        rc = seaf_obj_store_write_obj (seaf->fs_mgr->obj_store,\n                                       task->repo_id, task->repo_version,\n                                       recv_obj_id,\n                                       hdr->object,\n                                       size, FALSE);\n        if (rc < 0) {\n            seaf_warning (\"Failed to write fs object %s in repo %.8s.\\n\",\n                          recv_obj_id, task->repo_id);\n            task->error = SYNC_ERROR_ID_WRITE_LOCAL_DATA;\n            ret = -1;\n            goto out;\n        }\n\n        g_hash_table_remove (requested, recv_obj_id);\n\n        ++(task->done_fs_objs);\n\n        p += (sizeof(ObjectHeader) + size);\n        n += (sizeof(ObjectHeader) + size);\n        hdr = (ObjectHeader *)p;\n    }\n\n    seaf_debug (\"Received %d fs objects from %s:%s.\\n\",\n                n_recv, task->host, task->repo_id);\n\n    /* The server may not return all the objects we requested.\n     * So we need to add back the remaining object ids into fs_list.\n     */\n    GHashTableIter iter;\n    gpointer key, value;\n    g_hash_table_iter_init (&iter, requested);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        obj_id = key;\n        *fs_list = g_list_prepend (*fs_list, g_strdup(obj_id));\n    }\n    g_hash_table_destroy (requested);\n\nout:\n    g_free (url);\n    g_free (data);\n    g_free (rsp_content);\n    curl_easy_reset (curl);\n\n    return ret;\n}\n\ntypedef struct {\n    char block_id[41];\n    BlockHandle *block;\n    HttpTxTask *task;\n} GetBlockData;\n\nstatic size_t\nget_block_callback (void *ptr, size_t size, size_t nmemb, void *userp)\n{\n    size_t realsize = size *nmemb;\n    SendBlockData *data = userp;\n    HttpTxTask *task = data->task;\n    size_t n;\n\n    if (task->state == HTTP_TASK_STATE_CANCELED || task->all_stop)\n        return 0;\n\n    n = seaf_block_manager_write_block (seaf->block_mgr,\n                                        data->block,\n                                        ptr, realsize);\n    if (n < realsize) {\n        seaf_warning (\"Failed to write block %s in repo %.8s.\\n\",\n                      data->block_id, task->repo_id);\n        task->error = SYNC_ERROR_ID_WRITE_LOCAL_DATA;\n        return n;\n    }\n\n    /* Update global transferred bytes. */\n    g_atomic_int_add (&(seaf->sync_mgr->recv_bytes), n);\n\n    /* Update transferred bytes for this task */\n    g_atomic_int_add (&task->tx_bytes, n);\n\n    /* If uploaded bytes exceeds the limit, wait until the counter\n     * is reset. We check the counter every 100 milliseconds, so we\n     * can waste up to 100 milliseconds without sending data after\n     * the counter is reset.\n     */\n    while (1) {\n        gint sent = g_atomic_int_get(&(seaf->sync_mgr->recv_bytes));\n        if (seaf->sync_mgr->download_limit > 0 &&\n            sent > seaf->sync_mgr->download_limit)\n            /* 100 milliseconds */\n            g_usleep (100000);\n        else\n            break;\n    }\n\n    return n;\n}\n\nint\nget_block (HttpTxTask *task, Connection *conn, const char *block_id)\n{\n    CURL *curl;\n    char *url;\n    int status;\n    BlockHandle *block;\n    int ret = 0;\n    int *pcnt;\n\n    block = seaf_block_manager_open_block (seaf->block_mgr,\n                                           task->repo_id, task->repo_version,\n                                           block_id, BLOCK_WRITE);\n    if (!block) {\n        seaf_warning (\"Failed to open block %s in repo %.8s.\\n\",\n                      block_id, task->repo_id);\n        return -1;\n    }\n\n    GetBlockData data;\n    memcpy (data.block_id, block_id, 40);\n    data.block = block;\n    data.task = task;\n\n    curl = conn->curl;\n\n    if (!task->use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/%s/block/%s\",\n                               task->host, task->repo_id, block_id);\n    else\n        url = g_strdup_printf (\"%s/repo/%s/block/%s\",\n                               task->host, task->repo_id, block_id);\n\n    int curl_error;\n    if (http_get (curl, url, task->token, &status, NULL, NULL,\n                  get_block_callback, &data, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {\n        if (task->state == HTTP_TASK_STATE_CANCELED)\n            goto error;\n\n        if (task->error == SYNC_ERROR_ID_NO_ERROR) {\n            /* Only release the connection when it's a network error. */\n            conn->release = TRUE;\n            handle_curl_errors (task, curl_error);\n        }\n        ret = -1;\n        goto error;\n    }\n\n    if (status != HTTP_OK) {\n        seaf_warning (\"Bad response code for GET %s: %d.\\n\", url, status);\n        handle_http_errors (task, status);\n        ret = -1;\n        goto error;\n    }\n\n    BlockMetadata *bmd = seaf_block_manager_stat_block_by_handle(seaf->block_mgr, block);\n    if (bmd == NULL) {\n        seaf_warning (\"Failed to get block %s meta data in repo %.8s.\\n\", block_id, task->repo_id);\n        ret = -1;\n        goto error;\n    }\n    \n    seaf_block_manager_close_block (seaf->block_mgr, block);\n\n    pthread_mutex_lock (&task->ref_cnt_lock);\n\n    task->done_download += bmd->size;\n    g_free (bmd);    \n\n    /* Don't overwrite the block if other thread already downloaded it.\n     * Since we've locked ref_cnt_lock, we can be sure the block won't be removed.\n     */\n    if (!seaf_block_manager_block_exists (seaf->block_mgr,\n                                          task->repo_id, task->repo_version,\n                                          block_id) &&\n        seaf_block_manager_commit_block (seaf->block_mgr, block) < 0)\n    {\n        seaf_warning (\"Failed to commit block %s in repo %.8s.\\n\",\n                      block_id, task->repo_id);\n        task->error = SYNC_ERROR_ID_WRITE_LOCAL_DATA;\n        ret = -1;\n    }\n\n    if (ret == 0) {\n        pcnt = g_hash_table_lookup (task->blk_ref_cnts, block_id);\n        if (!pcnt) {\n            pcnt = g_new0(int, 1);\n            g_hash_table_insert (task->blk_ref_cnts, g_strdup(block_id), pcnt);\n        }\n        *pcnt += 1;\n    }\n\n    pthread_mutex_unlock (&task->ref_cnt_lock);\n\n    seaf_block_manager_block_handle_free (seaf->block_mgr, block);\n\n    g_free (url);\n\n    return ret;\n\nerror:\n    g_free (url);\n\n    seaf_block_manager_close_block (seaf->block_mgr, block);\n    seaf_block_manager_block_handle_free (seaf->block_mgr, block);\n\n    return ret;\n}\n\n/*\nint\nhttp_tx_task_download_file_blocks (HttpTxTask *task, const char *file_id)\n{\n    Seafile *file;\n    HttpTxPriv *priv = seaf->http_tx_mgr->priv;\n    ConnectionPool *pool;\n    Connection *conn;\n    int ret = 0;\n\n    file = seaf_fs_manager_get_seafile (seaf->fs_mgr,\n                                        task->repo_id,\n                                        task->repo_version,\n                                        file_id);\n    if (!file) {\n        seaf_warning (\"Failed to find seafile object %s in repo %.8s.\\n\",\n                      file_id, task->repo_id);\n        return -1;\n    }\n\n    pool = find_connection_pool (priv, task->host);\n    if (!pool) {\n        seaf_warning (\"Failed to create connection pool for host %s.\\n\", task->host);\n        task->error = SYNC_ERROR_ID_NOT_ENOUGH_MEMORY;\n        seafile_unref (file);\n        return -1;\n    }\n\n    conn = connection_pool_get_connection (pool);\n    if (!conn) {\n        seaf_warning (\"Failed to get connection to host %s.\\n\", task->host);\n        task->error = SYNC_ERROR_ID_NOT_ENOUGH_MEMORY;\n        seafile_unref (file);\n        return -1;\n    }\n\n    int i;\n    char *block_id;\n    int *pcnt;\n    for (i = 0; i < file->n_blocks; ++i) {\n        block_id = file->blk_sha1s[i];\n        pthread_mutex_lock (&task->ref_cnt_lock);\n        if (seaf_block_manager_block_exists (seaf->block_mgr,\n                                             task->repo_id, task->repo_version,\n                                             block_id)) {\n            BlockMetadata *bmd;\n            bmd = seaf_block_manager_stat_block (seaf->block_mgr,\n                                                 task->repo_id, task->repo_version,\n                                                 block_id);\n            if (bmd) {\n                pcnt = g_hash_table_lookup (task->blk_ref_cnts, block_id);\n                if (!pcnt) {\n                    pcnt = g_new0(int, 1);\n                    g_hash_table_insert (task->blk_ref_cnts, g_strdup(block_id), pcnt);\n                }\n                *pcnt += 1;\n                pthread_mutex_unlock (&task->ref_cnt_lock);\n                task->done_download += bmd->size;\n                g_free (bmd);\n                continue;\n            }\n        }\n        pthread_mutex_unlock (&task->ref_cnt_lock);\n\n        ret = get_block (task, conn, block_id);\n        if (ret < 0 || task->state == HTTP_TASK_STATE_CANCELED)\n            break;\n    }\n\n    connection_pool_return_connection (pool, conn);\n\n    seafile_unref (file);\n\n    return ret;\n}\n*/\n\nint\nhttp_tx_manager_get_block (HttpTxManager *manager,\n                           const char *repo_id,\n                           const char *block_id,\n                           const char *host,\n                           const char *token,\n                           gboolean use_fileserver_port,\n                           int *error_id,\n                           HttpRecvCallback get_blk_cb,\n                           void *user_data)\n{\n    HttpTxPriv *priv = seaf->http_tx_mgr->priv;\n    ConnectionPool *pool;\n    Connection *conn;\n    CURL *curl;\n    char *url;\n    int status;\n    int ret = 0;\n\n    pool = find_connection_pool (priv, host);\n    if (!pool) {\n        *error_id = SYNC_ERROR_ID_NOT_ENOUGH_MEMORY;\n        seaf_warning (\"Failed to create connection pool for host %s.\\n\", host);\n        return -1;\n    }\n\n    conn = connection_pool_get_connection (pool);\n    if (!conn) {\n        *error_id = SYNC_ERROR_ID_NOT_ENOUGH_MEMORY;\n        seaf_warning (\"Failed to get connection to host %s.\\n\", host);\n        return -1;\n    }\n\n    curl = conn->curl;\n\n    if (!use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/%s/block/%s\",\n                               host, repo_id, block_id);\n    else\n        url = g_strdup_printf (\"%s/repo/%s/block/%s\",\n                               host, repo_id, block_id);\n\n    int curl_error;\n    if (http_get (curl, url, token, &status, NULL, NULL,\n                  get_blk_cb, user_data, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {\n        *error_id = curl_error_to_http_task_error (curl_error);\n        conn->release = TRUE;\n        ret = -1;\n        goto out;\n    }\n\n    if (status != HTTP_OK) {\n        *error_id = curl_error_to_http_task_error (curl_error);\n        seaf_warning (\"Bad response code for GET %s: %d.\\n\", url, status);\n        ret = -1;\n        goto out;\n    }\n\nout:\n    g_free (url);\n    connection_pool_return_connection (pool, conn);\n    return ret;\n}\n\nstatic int\nparse_block_map (const char *rsp_content, gint64 rsp_size,\n                 gint64 **pblock_map, int *n_blocks)\n{\n    json_t *array, *element;\n    json_error_t jerror;\n    size_t n, i;\n    gint64 *block_map = NULL;\n    int ret = 0;\n\n    array = json_loadb (rsp_content, rsp_size, 0, &jerror);\n    if (!array) {\n        seaf_warning (\"Failed to load json: %s\\n\", jerror.text);\n        return -1;\n    }\n\n    if (json_typeof (array) != JSON_ARRAY) {\n        seaf_warning (\"Response is not a json array.\\n\");\n        ret = -1;\n        goto out;\n    }\n\n    n = json_array_size (array);\n    block_map = g_new0 (gint64, n);\n\n    for (i = 0; i < n; ++i) {\n        element = json_array_get (array, i);\n        if (json_typeof (element) != JSON_INTEGER) {\n            seaf_warning (\"Block map element not an integer.\\n\");\n            ret = -1;\n            goto out;\n        }\n        block_map[i] = (gint64)json_integer_value (element);\n    }\n\nout:\n    json_decref (array);\n    if (ret < 0) {\n        g_free (block_map);\n        *pblock_map = NULL;\n    } else {\n        *pblock_map = block_map;\n        *n_blocks = (int)n;\n    }\n    return ret;\n}\n\nint\nhttp_tx_manager_get_file_block_map (HttpTxManager *manager,\n                                    const char *repo_id,\n                                    const char *file_id,\n                                    const char *host,\n                                    const char *token,\n                                    gboolean use_fileserver_port,\n                                    gint64 **pblock_map,\n                                    int *n_blocks)\n{\n    HttpTxPriv *priv = seaf->http_tx_mgr->priv;\n    ConnectionPool *pool;\n    Connection *conn;\n    CURL *curl;\n    char *url;\n    int status;\n    char *rsp_content = NULL;\n    gint64 rsp_size;\n    int ret = 0;\n\n    pool = find_connection_pool (priv, host);\n    if (!pool) {\n        seaf_warning (\"Failed to create connection pool for host %s.\\n\", host);\n        return -1;\n    }\n\n    conn = connection_pool_get_connection (pool);\n    if (!conn) {\n        seaf_warning (\"Failed to get connection to host %s.\\n\", host);\n        return -1;\n    }\n\n    curl = conn->curl;\n\n    if (!use_fileserver_port)\n        url = g_strdup_printf (\"%s/seafhttp/repo/%s/block-map/%s\",\n                               host, repo_id, file_id);\n    else\n        url = g_strdup_printf (\"%s/repo/%s/block-map/%s\",\n                               host, repo_id, file_id);\n\n    int curl_error;\n    if (http_get (curl, url, token, &status, &rsp_content, &rsp_size,\n                  NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {\n        conn->release = TRUE;\n        ret = -1;\n        goto out;\n    }\n\n    if (status != HTTP_OK) {\n        seaf_warning (\"Bad response code for GET %s: %d.\\n\", url, status);\n        ret = -1;\n        goto out;\n    }\n\n    if (parse_block_map (rsp_content, rsp_size, pblock_map, n_blocks) < 0) {\n        ret = -1;\n        goto out;\n    }\n\nout:\n    g_free (url);\n    g_free (rsp_content);\n    connection_pool_return_connection (pool, conn);\n    return ret;\n}\n\nstatic int\nupdate_local_repo (HttpTxTask *task)\n{\n    SeafRepo *repo;\n    SeafCommit *new_head;\n    SeafBranch *branch;\n    int ret = 0;\n\n    new_head = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                               task->repo_id,\n                                               task->repo_version,\n                                               task->head);\n    if (!new_head) {\n        seaf_warning (\"Failed to get commit %s:%s.\\n\", task->repo_id, task->head);\n        task->error = SYNC_ERROR_ID_LOCAL_DATA_CORRUPT;\n        return -1;\n    }\n\n    /* If repo doesn't exist, create it.\n     * Note that branch doesn't exist either in this case.\n     */\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, new_head->repo_id);\n    if (task->is_clone) {\n        gboolean empty_enc_key = FALSE;\n        char *value = seaf_repo_manager_get_repo_property (seaf->repo_mgr, new_head->repo_id, REPO_PROP_EMPTY_ENC_KEY);\n        if (g_strcmp0(value, \"true\") == 0)\n            empty_enc_key = TRUE;\n        g_free (value);\n\n        if (repo != NULL) {\n            repo->empty_enc_key = empty_enc_key;\n            goto out;\n        }\n\n        repo = seaf_repo_new (new_head->repo_id, NULL, NULL);\n        if (repo == NULL) {\n            /* create repo failed */\n            task->error = SYNC_ERROR_ID_NOT_ENOUGH_MEMORY;\n            ret = -1;\n            goto out;\n        }\n        repo->empty_enc_key = empty_enc_key;\n\n        seaf_repo_from_commit (repo, new_head);\n\n        seaf_repo_manager_add_repo (seaf->repo_mgr, repo);\n\n        /* If it's a new repo, create 'local' and 'master' branch */\n        branch = seaf_branch_new (\"local\", task->repo_id, task->head);\n        seaf_branch_manager_add_branch (seaf->branch_mgr, branch);\n        seaf_branch_unref (branch);\n\n        branch = seaf_branch_new (\"master\", task->repo_id, task->head);\n        seaf_branch_manager_add_branch (seaf->branch_mgr, branch);\n        seaf_branch_unref (branch);\n    } else {\n        if (!repo) {\n            task->error = SYNC_ERROR_ID_LOCAL_DATA_CORRUPT;\n            ret = -1;\n            goto out;\n        }\n\n        branch = seaf_branch_manager_get_branch (seaf->branch_mgr, \n                                                 task->repo_id,\n                                                 \"master\");\n        if (!branch) {\n            seaf_warning (\"Branch master not found for repo %.8s.\\n\", task->repo_id);\n            task->error = SYNC_ERROR_ID_LOCAL_DATA_CORRUPT;\n            ret = -1;\n            goto out;\n        }\n        seaf_branch_set_commit (branch, new_head->commit_id);\n        seaf_branch_manager_update_branch (seaf->branch_mgr, branch);\n        seaf_branch_unref (branch);\n\n        /* Update repo head branch. */\n        seaf_branch_set_commit (repo->head, new_head->commit_id);\n        seaf_branch_manager_update_branch (seaf->branch_mgr, repo->head);\n\n        if (g_strcmp0 (repo->name, new_head->repo_name) != 0)\n            seaf_repo_set_name (repo, new_head->repo_name);\n    }\n\nout:\n    seaf_commit_unref (new_head);\n    return ret;\n}\n\nstatic void *\nhttp_download_thread (void *vdata)\n{\n    HttpTxTask *task = vdata;\n    HttpTxPriv *priv = seaf->http_tx_mgr->priv;\n    ConnectionPool *pool;\n    Connection *conn = NULL;\n    GList *fs_id_list = NULL;\n\n    pool = find_connection_pool (priv, task->host);\n    if (!pool) {\n        seaf_warning (\"Failed to create connection pool for host %s.\\n\", task->host);\n        task->error = SYNC_ERROR_ID_NOT_ENOUGH_MEMORY;\n        goto out;\n    }\n\n    conn = connection_pool_get_connection (pool);\n    if (!conn) {\n        seaf_warning (\"Failed to get connection to host %s.\\n\", task->host);\n        task->error = SYNC_ERROR_ID_NOT_ENOUGH_MEMORY;\n        goto out;\n    }\n\n    /* seaf_message (\"Download with HTTP sync protocol version %d.\\n\", */\n    /*               task->protocol_version); */\n\n    transition_state (task, task->state, HTTP_TASK_RT_STATE_CHECK);\n\n    if (check_permission (task, conn) < 0) {\n        seaf_warning (\"Download permission denied for repo %.8s on server %s.\\n\",\n                      task->repo_id, task->host);\n        goto out;\n    }\n\n    if (task->state == HTTP_TASK_STATE_CANCELED)\n        goto out;\n\n    transition_state (task, task->state, HTTP_TASK_RT_STATE_COMMIT);\n\n    if (get_commit_object (task, conn) < 0) {\n        seaf_warning (\"Failed to get server head commit for repo %.8s on server %s.\\n\",\n                      task->repo_id, task->host);\n        goto out;\n    }\n\n    if (task->state == HTTP_TASK_STATE_CANCELED)\n        goto out;\n\n    transition_state (task, task->state, HTTP_TASK_RT_STATE_FS);\n\n    if (get_needed_fs_id_list (task, conn, &fs_id_list) < 0) {\n        seaf_warning (\"Failed to get fs id list for repo %.8s on server %s.\\n\",\n                      task->repo_id, task->host);\n        goto out;\n    }\n\n    if (task->state == HTTP_TASK_STATE_CANCELED)\n        goto out;\n\n    while (fs_id_list != NULL) {\n        if (get_fs_objects (task, conn, &fs_id_list) < 0) {\n            seaf_warning (\"Failed to get fs objects for repo %.8s on server %s.\\n\",\n                          task->repo_id, task->host);\n            goto out;\n        }\n\n        if (task->state == HTTP_TASK_STATE_CANCELED)\n            goto out;\n    }\n\n    transition_state (task, task->state, HTTP_TASK_RT_STATE_BLOCK);\n\n    /* Record download head commit id, so that we can resume download\n     * if this download is interrupted.\n     */\n    seaf_repo_manager_set_repo_property (seaf->repo_mgr,\n                                         task->repo_id,\n                                         REPO_PROP_DOWNLOAD_HEAD,\n                                         task->head);\n\n    int rc = seaf_repo_fetch_and_checkout (task, task->head);\n    switch (rc) {\n    case FETCH_CHECKOUT_SUCCESS:\n        break;\n    case FETCH_CHECKOUT_CANCELED:\n        goto out;\n    case FETCH_CHECKOUT_FAILED:\n        task->error = SYNC_ERROR_ID_WRITE_LOCAL_DATA;\n        goto out;\n    case FETCH_CHECKOUT_TRANSFER_ERROR:\n        goto out;\n    case FETCH_CHECKOUT_LOCKED:\n        task->error = SYNC_ERROR_ID_FILE_LOCKED_BY_APP;\n        goto out;\n    }\n\n    update_local_repo (task);\n\nout:\n    connection_pool_return_connection (pool, conn);\n    string_list_free (fs_id_list);\n    return vdata;\n}\n\nstatic void\nhttp_download_done (void *vdata)\n{\n    HttpTxTask *task = vdata;\n\n    if (task->error != SYNC_ERROR_ID_NO_ERROR)\n        transition_state (task, HTTP_TASK_STATE_ERROR, HTTP_TASK_RT_STATE_FINISHED);\n    else if (task->state == HTTP_TASK_STATE_CANCELED)\n        transition_state (task, task->state, HTTP_TASK_RT_STATE_FINISHED);\n    else\n        transition_state (task, HTTP_TASK_STATE_FINISHED, HTTP_TASK_RT_STATE_FINISHED);\n}\n\nGList*\nhttp_tx_manager_get_upload_tasks (HttpTxManager *manager)\n{\n    return g_hash_table_get_values (manager->priv->upload_tasks);\n}\n\nGList*\nhttp_tx_manager_get_download_tasks (HttpTxManager *manager)\n{\n    return g_hash_table_get_values (manager->priv->download_tasks);\n}\n\nHttpTxTask *\nhttp_tx_manager_find_task (HttpTxManager *manager, const char *repo_id)\n{\n    HttpTxTask *task = NULL;\n\n    task = g_hash_table_lookup (manager->priv->upload_tasks, repo_id);\n    if (task)\n        return task;\n\n    task = g_hash_table_lookup (manager->priv->download_tasks, repo_id);\n    return task;\n}\n\nvoid\nhttp_tx_manager_cancel_task (HttpTxManager *manager,\n                             const char *repo_id,\n                             int task_type)\n{\n    HttpTxTask *task = NULL;\n\n    if (task_type == HTTP_TASK_TYPE_DOWNLOAD)\n        task = g_hash_table_lookup (manager->priv->download_tasks, repo_id);\n    else\n        task = g_hash_table_lookup (manager->priv->upload_tasks, repo_id);\n\n    if (!task)\n        return;\n\n    if (task->state != HTTP_TASK_STATE_NORMAL) {\n        seaf_warning (\"Cannot cancel task not in NORMAL state.\\n\");\n        return;\n    }\n\n    if (task->runtime_state == HTTP_TASK_RT_STATE_INIT) {\n        transition_state (task, HTTP_TASK_STATE_CANCELED, HTTP_TASK_RT_STATE_FINISHED);\n        return;\n    }\n\n    /* Only change state. runtime_state will be changed in worker thread. */\n    transition_state (task, HTTP_TASK_STATE_CANCELED, task->runtime_state);\n}\n\nint\nhttp_tx_task_get_rate (HttpTxTask *task)\n{\n    return task->last_tx_bytes;\n}\n\nconst char *\nhttp_task_state_to_str (int state)\n{\n    if (state < 0 || state >= N_HTTP_TASK_STATE)\n        return \"unknown\";\n\n    return http_task_state_str[state];\n}\n\nconst char *\nhttp_task_rt_state_to_str (int rt_state)\n{\n    if (rt_state < 0 || rt_state >= N_HTTP_TASK_RT_STATE)\n        return \"unknown\";\n\n    return http_task_rt_state_str[rt_state];\n}\n"
  },
  {
    "path": "daemon/http-tx-mgr.h",
    "content": "#ifndef HTTP_TX_MGR_H\n#define HTTP_TX_MGR_H\n\n#include <pthread.h>\n\nenum {\n    HTTP_TASK_TYPE_DOWNLOAD = 0,\n    HTTP_TASK_TYPE_UPLOAD,\n};\n\n\n/**\n * The state that can be set by user.\n *\n * A task in NORMAL state can be canceled;\n * A task in RT_STATE_FINISHED can be removed.\n */\nenum HttpTaskState {\n    HTTP_TASK_STATE_NORMAL = 0,\n    HTTP_TASK_STATE_CANCELED,\n    HTTP_TASK_STATE_FINISHED,\n    HTTP_TASK_STATE_ERROR,\n    N_HTTP_TASK_STATE,\n};\n\nenum HttpTaskRuntimeState {\n    HTTP_TASK_RT_STATE_INIT = 0,\n    HTTP_TASK_RT_STATE_CHECK,\n    HTTP_TASK_RT_STATE_COMMIT,\n    HTTP_TASK_RT_STATE_FS,\n    HTTP_TASK_RT_STATE_BLOCK,         /* Only used in upload. */\n    HTTP_TASK_RT_STATE_UPDATE_BRANCH, /* Only used in upload. */\n    HTTP_TASK_RT_STATE_FINISHED,\n    N_HTTP_TASK_RT_STATE,\n};\n\nstruct _SeafileSession;\nstruct _HttpTxPriv;\n\nstruct _HttpTxManager {\n    struct _SeafileSession   *seaf;\n\n    struct _HttpTxPriv *priv;\n};\n\ntypedef struct _HttpTxManager HttpTxManager;\n\nstruct _HttpTxTask {\n    HttpTxManager *manager;\n\n    char repo_id[37];\n    int repo_version;\n    char *repo_name;\n    char *token;\n    int protocol_version;\n    int type;\n    char *host;\n    gboolean is_clone;\n    char *email;\n    char *username;\n    gboolean use_fileserver_port;\n\n    char head[41];\n\n    char *passwd;\n    char *worktree;\n\n    int state;\n    int runtime_state;\n    int error;\n    /* Used to signify stop transfer for all threads. */\n    gboolean all_stop;\n\n    /* When downloading with multi-thread, a block may be shared by\n     * multiple files. We can't remove a block before all *fetched* files with\n     * this block have been checked out.\n     * block_id -> ref_count.\n     */\n    GHashTable *blk_ref_cnts;\n    pthread_mutex_t ref_cnt_lock;\n\n    /* For clone fs object progress */\n    int n_fs_objs;\n    int done_fs_objs;\n\n    /* For upload progress */\n    int n_blocks;\n    int done_blocks;\n    /* For download progress */\n    gint64 total_download;\n    gint64 done_download;\n\n    gint tx_bytes;              /* bytes transferred in this second. */\n    gint last_tx_bytes;         /* bytes transferred in the last second. */\n};\ntypedef struct _HttpTxTask HttpTxTask;\n\nHttpTxTask *\nhttp_tx_task_new (HttpTxManager *mgr,\n                  const char *repo_id,\n                  int repo_version,\n                  int type,\n                  gboolean is_clone,\n                  const char *host,\n                  const char *token,\n                  const char *passwd,\n                  const char *worktree);\n\nvoid\nhttp_tx_task_free (HttpTxTask *task);\n\nHttpTxManager *\nhttp_tx_manager_new (struct _SeafileSession *seaf);\n\nint\nhttp_tx_manager_start (HttpTxManager *mgr);\n\nint\nhttp_tx_manager_add_download (HttpTxManager *manager,\n                              const char *repo_id,\n                              int repo_version,\n                              const char *host,\n                              const char *token,\n                              const char *server_head_id,\n                              gboolean is_clone,\n                              const char *passwd,\n                              const char *worktree,\n                              int protocol_version,\n                              const char *email,\n                              const char *username,\n                              gboolean use_fileserver_port,\n                              const char *repo_name,\n                              GError **error);\n\nint\nhttp_tx_manager_add_upload (HttpTxManager *manager,\n                            const char *repo_id,\n                            int repo_version,\n                            const char *host,\n                            const char *token,\n                            int protocol_version,\n                            gboolean use_fileserver_port,\n                            GError **error);\n\nstruct _HttpProtocolVersion {\n    gboolean check_success;     /* TRUE if we get response from the server. */\n    gboolean not_supported;\n    int version;\n    int error_code;\n};\ntypedef struct _HttpProtocolVersion HttpProtocolVersion;\n\ntypedef void (*HttpProtocolVersionCallback) (HttpProtocolVersion *result,\n                                             void *user_data);\n\n/* Asynchronous interface for getting protocol version from a server.\n * Also used to determine if the server support http sync.\n */\nint\nhttp_tx_manager_check_protocol_version (HttpTxManager *manager,\n                                        const char *host,\n                                        gboolean use_fileserver_port,\n                                        HttpProtocolVersionCallback callback,\n                                        void *user_data);\n\n\ntypedef void (*HttpNotifServerCallback) (gboolean is_alive,\n                                         void *user_data);\n\nint\nhttp_tx_manager_check_notif_server (HttpTxManager *manager,\n                                    const char *host,\n                                    gboolean use_fileserver_port,\n                                    HttpNotifServerCallback callback,\n                                    void *user_data);\n\nstruct _HttpHeadCommit {\n    gboolean check_success;\n    gboolean is_corrupt;\n    gboolean is_deleted;\n    char head_commit[41];\n    int error_code;\n};\ntypedef struct _HttpHeadCommit HttpHeadCommit;\n\ntypedef void (*HttpHeadCommitCallback) (HttpHeadCommit *result,\n                                        void *user_data);\n\n/* Asynchronous interface for getting head commit info from a server. */\nint\nhttp_tx_manager_check_head_commit (HttpTxManager *manager,\n                                   const char *repo_id,\n                                   int repo_version,\n                                   const char *host,\n                                   const char *token,\n                                   gboolean use_fileserver_port,\n                                   HttpHeadCommitCallback callback,\n                                   void *user_data);\n\ntypedef struct _HttpFolderPermReq {\n    char repo_id[37];\n    char *token;\n    gint64 timestamp;\n} HttpFolderPermReq;\n\ntypedef struct _HttpFolderPermRes {\n    char repo_id[37];\n    gint64 timestamp;\n    GList *user_perms;\n    GList *group_perms;\n} HttpFolderPermRes;\n\nvoid\nhttp_folder_perm_req_free (HttpFolderPermReq *req);\n\nvoid\nhttp_folder_perm_res_free (HttpFolderPermRes *res);\n\nstruct _HttpFolderPerms {\n    gboolean success;\n    GList *results;             /* List of HttpFolderPermRes */\n};\ntypedef struct _HttpFolderPerms HttpFolderPerms;\n\ntypedef void (*HttpGetFolderPermsCallback) (HttpFolderPerms *result,\n                                            void *user_data);\n\n/* Asynchronous interface for getting folder permissions for a repo. */\nint\nhttp_tx_manager_get_folder_perms (HttpTxManager *manager,\n                                  const char *host,\n                                  gboolean use_fileserver_port,\n                                  GList *folder_perm_requests, /* HttpFolderPermReq */\n                                  HttpGetFolderPermsCallback callback,\n                                  void *user_data);\n\ntypedef struct _HttpLockedFilesReq {\n    char repo_id[37];\n    char *token;\n    gint64 timestamp;\n} HttpLockedFilesReq;\n\ntypedef struct _HttpLockedFilesRes {\n    char repo_id[37];\n    gint64 timestamp;\n    GHashTable *locked_files;   /* path -> by_me */\n} HttpLockedFilesRes;\n\nvoid\nhttp_locked_files_req_free (HttpLockedFilesReq *req);\n\nvoid\nhttp_locked_files_res_free (HttpLockedFilesRes *res);\n\nstruct _HttpLockedFiles {\n    gboolean success;\n    GList *results;             /* List of HttpLockedFilesRes */\n};\ntypedef struct _HttpLockedFiles HttpLockedFiles;\n\ntypedef void (*HttpGetLockedFilesCallback) (HttpLockedFiles *result,\n                                            void *user_data);\n\n/* Asynchronous interface for getting locked files for a repo. */\nint\nhttp_tx_manager_get_locked_files (HttpTxManager *manager,\n                                  const char *host,\n                                  gboolean use_fileserver_port,\n                                  GList *locked_files_requests,\n                                  HttpGetLockedFilesCallback callback,\n                                  void *user_data);\n\n/* Synchronous interface for locking/unlocking a file on the server. */\nint\nhttp_tx_manager_lock_file (HttpTxManager *manager,\n                           const char *host,\n                           gboolean use_fileserver_port,\n                           const char *token,\n                           const char *repo_id,\n                           const char *path);\n\nint\nhttp_tx_manager_unlock_file (HttpTxManager *manager,\n                             const char *host,\n                             gboolean use_fileserver_port,\n                             const char *token,\n                             const char *repo_id,\n                             const char *path);\n\nstruct _HttpAPIGetResult {\n    gboolean success;\n    char *rsp_content;\n    int rsp_size;\n    int error_code;\n    int http_status;\n};\ntypedef struct _HttpAPIGetResult HttpAPIGetResult;\n\ntypedef void (*HttpAPIGetCallback) (HttpAPIGetResult *result,\n                                    void *user_data);\n\nint\nhttp_tx_manager_fileserver_api_get  (HttpTxManager *manager,\n                                     const char *host,\n                                     const char *url,\n                                     const char *token,\n                                     HttpAPIGetCallback callback,\n                                     void *user_data);\n\nGHashTable *\nhttp_tx_manager_get_head_commit_ids (HttpTxManager *manager,\n                                     const char *host,\n                                     gboolean use_fileserver_port,\n                                     GList *repo_id_list,\n                                     int *ret_status);\n\n/*\nint\nhttp_tx_task_download_file_blocks (HttpTxTask *task, const char *file_id);\n*/\n\ntypedef size_t (*HttpRecvCallback) (void *, size_t, size_t, void *);\n\nint\nhttp_tx_manager_get_block (HttpTxManager *manager,\n                           const char *repo_id,\n                           const char *block_id,\n                           const char *host,\n                           const char *token,\n                           gboolean use_fileserver_port,\n                           int *error_id,\n                           HttpRecvCallback get_blk_cb,\n                           void *user_data);\n\nint\nhttp_tx_manager_get_file_block_map (HttpTxManager *manager,\n                                    const char *repo_id,\n                                    const char *file_id,\n                                    const char *host,\n                                    const char *token,\n                                    gboolean use_fileserver_port,\n                                    gint64 **pblock_map,\n                                    int *n_blocks);\n\nGList*\nhttp_tx_manager_get_upload_tasks (HttpTxManager *manager);\n\nGList*\nhttp_tx_manager_get_download_tasks (HttpTxManager *manager);\n\nHttpTxTask *\nhttp_tx_manager_find_task (HttpTxManager *manager, const char *repo_id);\n\nvoid\nhttp_tx_manager_cancel_task (HttpTxManager *manager,\n                             const char *repo_id,\n                             int task_type);\n\nint\nhttp_tx_task_get_rate (HttpTxTask *task);\n\nconst char *\nhttp_task_state_to_str (int state);\n\nconst char *\nhttp_task_rt_state_to_str (int rt_state);\n\n#endif\n"
  },
  {
    "path": "daemon/job-mgr.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)\n#include <event2/event.h>\n#include <event2/event_compat.h>\n#else\n#include <event.h>\n#endif\n\n#include <glib.h>\n\n#include <string.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <errno.h>\n\n#include \"utils.h\"\n#include \"log.h\"\n\n#include \"seafile-session.h\"\n#include \"job-mgr.h\"\n\nstruct _SeafJobManager {\n    SeafileSession  *session;\n    GThreadPool     *thread_pool;\n    int              next_job_id;\n};\n\nstruct _SeafJob {\n    SeafJobManager *manager;\n\n    int             id;\n    seaf_pipe_t    pipefd[2];\n\n    JobThreadFunc   thread_func;\n    JobDoneCallback done_func;  /* called when the thread is done */\n    void           *data;\n\n    /* the done callback should only access this field */\n    void           *result;\n};\ntypedef struct _SeafJob SeafJob;\n\nSeafJob *\nseaf_job_new ()\n{\n    SeafJob *job;\n\n    job = g_new0 (SeafJob, 1);\n    return job;\n}\n\nvoid\nseaf_job_free (SeafJob *job)\n{\n    g_free (job);\n}\n\nstatic void\njob_thread_wrapper (void *vdata, void *unused)\n{\n    SeafJob *job = vdata;\n   \n    job->result = job->thread_func (job->data);\n    if (seaf_pipe_writen (job->pipefd[1], \"a\", 1) != 1) {\n        seaf_warning (\"[Job Manager] write to pipe error: %s\\n\", strerror(errno));\n    }\n}\n\nstatic void\njob_done_cb (evutil_socket_t fd, short event, void *vdata)\n{\n    SeafJob *job = vdata;\n    char buf[1];\n\n    if (seaf_pipe_readn (job->pipefd[0], buf, 1) != 1) {\n        seaf_warning (\"[Job Manager] read pipe error: %s\\n\", strerror(errno));\n    }\n    seaf_pipe_close (job->pipefd[0]);\n    seaf_pipe_close (job->pipefd[1]);\n    if (job->done_func) {\n        job->done_func (job->result);\n    }\n\n    seaf_job_free (job);\n}\n\nint\njob_thread_create (SeafJob *job)\n{\n    SeafileSession *session = job->manager->session;\n\n    if (seaf_pipe (job->pipefd) < 0) {\n        seaf_warning (\"[Job Manager] pipe error: %s\\n\", strerror(errno));\n        return -1;\n    }\n\n    g_thread_pool_push (job->manager->thread_pool, job, NULL);\n\n    event_base_once (session->ev_base, job->pipefd[0], EV_READ, job_done_cb, job, NULL);\n\n    return 0;\n}\n\nSeafJobManager *\nseaf_job_manager_new (SeafileSession *session, int max_threads)\n{\n    SeafJobManager *mgr;\n\n    mgr = g_new0 (SeafJobManager, 1);\n    mgr->session = session;\n    mgr->thread_pool = g_thread_pool_new (job_thread_wrapper,\n                                          NULL,\n                                          max_threads,\n                                          FALSE,\n                                          NULL);\n\n    return mgr;\n}\n\nvoid\nseaf_job_manager_free (SeafJobManager *mgr)\n{\n    g_thread_pool_free (mgr->thread_pool, TRUE, FALSE);\n    g_free (mgr);\n}\n\nint\nseaf_job_manager_schedule_job (SeafJobManager *mgr,\n                               JobThreadFunc func,\n                               JobDoneCallback done_func,\n                               void *data)\n{\n    SeafJob *job = seaf_job_new ();\n    job->id = mgr->next_job_id++;\n    job->manager = mgr;\n    job->thread_func = func;\n    job->done_func = done_func;\n    job->data = data;\n    \n    if (job_thread_create (job) < 0) {\n        seaf_job_free (job);\n        return -1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "daemon/job-mgr.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n/**\n * Job Manager manages long term jobs. These jobs are run in their\n * own threads.\n */\n\n#ifndef SEAF_JOB_MGR_H\n#define SEAF_JOB_MGR_H\n\nstruct _SeafJobManager;\ntypedef struct _SeafJobManager SeafJobManager;\n\nstruct _SeafileSession;\n\n/*\n  The thread func should return the result back by\n     return (void *)result;\n  The result will be passed to JobDoneCallback.\n */\ntypedef void* (*JobThreadFunc)(void *data);\ntypedef void (*JobDoneCallback)(void *result);\n\nSeafJobManager *\nseaf_job_manager_new (struct _SeafileSession *session, int max_threads);\n\nvoid\nseaf_job_manager_free (struct _SeafJobManager *mgr);\n\nint\nseaf_job_manager_schedule_job (struct _SeafJobManager *mgr,\n                               JobThreadFunc func,\n                               JobDoneCallback done_func,\n                               void *data);\n\n#endif\n"
  },
  {
    "path": "daemon/notif-mgr.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n#include <libwebsockets.h>\n#include <string.h>\n#include <glib.h>\n\n#include \"seafile-session.h\"\n#include \"notif-mgr.h\"\n#include \"sync-mgr.h\"\n\n#define DEBUG_FLAG SEAFILE_DEBUG_NOTIFICATION\n#include \"log.h\"\n\n#define NOTIF_PORT 8083\n\n#define RECONNECT_INTERVAL 60 /* 60s */\n\n#define STATUS_DISCONNECTED 0\n#define STATUS_CONNECTED    1\n#define STATUS_ERROR        2\n#define STATUS_CANCELLED    3\n\ntypedef struct NotifServer {\n    struct lws_context *context;\n    struct lws_client_connect_info i;\n    struct lws\t\t*wsi;\n\n    // status of the notification server.\n    int      status;\n    // whether to close the connection to the server.\n    gboolean close;\n\n    GHashTable *subscriptions;\n    pthread_mutex_t sub_lock;\n    GAsyncQueue *messages;\n\n    gboolean use_ssl;\n    char    *server_url;\n    char    *addr;\n    char    *path;\n    int     port;\n\n    gint    refcnt;\n} NotifServer;\n\nstruct _SeafNotifManagerPriv {\n    pthread_mutex_t server_lock;\n    GHashTable *servers;\n};\n\n// The Message structure is used to send messages to the server.\ntypedef struct Message {\n    void    *payload;\n    size_t  len;\n    int     type;\n} Message;\n\nstatic Message*\nnotif_message_new (const char *str, int type)\n{\n    int len, n;\n\n    len = strlen(str) + 1;\n    Message *msg = g_new0 (Message, 1);\n    msg->payload = malloc((unsigned int)(LWS_PRE + len));\n    if (!msg->payload) {\n        g_free (msg);\n        return NULL;\n    }\n\n    // The libwebsockets library requires the message to be sent with a LWS_PRE header.\n    n = lws_snprintf((char *)msg->payload + LWS_PRE, (unsigned int)len, \"%s\", str);\n    msg->len = (unsigned int)n;\n    msg->type = type;\n\n    return msg;\n}\n\nstatic void\nnotif_message_free (Message *msg)\n{\n    if (!msg)\n        return;\n    g_free (msg->payload);\n    g_free (msg);\n}\n\nSeafNotifManager *\nseaf_notif_manager_new (SeafileSession *seaf)\n{\n    SeafNotifManager *mgr = g_new0 (SeafNotifManager, 1);\n    mgr->seaf = seaf;\n\n    mgr->priv = g_new0 (SeafNotifManagerPriv, 1);    \n    pthread_mutex_init (&mgr->priv->server_lock, NULL);\n    mgr->priv->servers = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                                g_free, NULL);\n\n    return mgr;\n}\n\ntypedef struct URI {\n    char *scheme;\n    char *host;\n    int  port;\n} URI;\n\n// Assume that the input url format is http[s]://host:port.\nstatic URI*\nparse_notification_url (const char *url)\n{\n    const char *server = url;\n    char *colon;\n    char *url_no_port;\n    char *scheme = NULL;\n    URI *uri = NULL;\n    int port;\n    \n\n    if (strncmp(url, \"https://\", 8) == 0) {\n        scheme = g_strdup (\"https\");\n        server = url + 8;\n        port = 443;\n    } else if (strncmp (url, \"http://\", 7) == 0) {\n        scheme = g_strdup (\"http\");\n        server = url + 7;\n        port = 80;\n    }\n    \n    if (!server) {\n        return NULL;\n    }\n\n    uri = g_new0 (URI, 1);\n    uri->scheme = scheme;\n\n    colon = strrchr (server, ':');\n    if (colon) {\n        url_no_port = g_strndup(server, colon - server);\n        uri->host = g_strdup (url_no_port);\n        if (colon + 1)\n            port = atoi (colon + 1);\n\n        uri->host = url_no_port;\n        uri->port = port;\n\n        return uri;\n    } \n\n    uri->host = g_strdup (server);\n    uri->port = port;\n\n    return uri;\n}\n\nstatic void\nnotif_server_ref (NotifServer *server);\n\nstatic struct lws_context *\nlws_context_new (int port);\n\nstatic NotifServer*\nnotif_new_server (const char *server_url, gboolean use_notif_server_port)\n{\n    NotifServer *server = NULL;\n    static struct lws_context *context;\n    URI *uri = NULL;\n    int port = NOTIF_PORT;\n    gboolean use_ssl = FALSE;\n\n    uri = parse_notification_url (server_url);\n    if (!uri) {\n        seaf_warning (\"failed to parse notification url from %s\\n\", server_url);\n        return NULL;\n    }\n\n    // If use_notif_server_port is FALSE, the server should be deployed behind Nginx.\n    // In this case we'll use the same port as Seafile server.\n    if (!use_notif_server_port) {\n        port = uri->port;\n    }\n\n    if (strncmp(server_url, \"https\", 5) == 0) {\n        use_ssl = TRUE;\n    }\n    \n\n    context = lws_context_new (use_ssl);\n    if (!context) {\n        g_free (uri->scheme);\n        g_free (uri->host);\n        g_free (uri);\n        seaf_warning (\"failed to create libwebsockets context\\n\");\n        return NULL;\n    }\n\n    server = g_new0 (NotifServer, 1);\n\n    server->messages = g_async_queue_new ();\n\n    server->context = context;\n    server->server_url = g_strdup (server_url);\n    server->addr = g_strdup (uri->host);\n    server->use_ssl = use_ssl;\n    if (use_notif_server_port)\n        server->path = g_strdup (\"/\");\n    else\n        server->path = g_strdup (\"/notification\");\n    server->port = port;\n\n    pthread_mutex_init (&server->sub_lock, NULL);\n    server->subscriptions = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                                   g_free, NULL);\n    notif_server_ref (server);\n\n    g_free (uri->scheme);\n    g_free (uri->host);\n    g_free (uri);\n    return server;\n}\n\nNotifServer*\nget_notif_server (SeafNotifManager *mgr, const char *url)\n{\n    NotifServer *server = NULL;\n\n    if (!url) {\n        return NULL;\n    }\n\n    pthread_mutex_lock (&mgr->priv->server_lock);\n    server = g_hash_table_lookup (mgr->priv->servers, url);\n    if (!server) {\n        pthread_mutex_unlock (&mgr->priv->server_lock);\n        return NULL;\n    }\n    notif_server_ref (server);\n    pthread_mutex_unlock (&mgr->priv->server_lock);\n\n    return server;\n}\n\nstatic void\ndelete_subscribed_repos (NotifServer *server);\n\nstatic void\ndelete_unsent_messages (NotifServer *server);\n\nstatic void\nnotif_server_free (NotifServer *server)\n{\n    if (!server)\n        return;\n    if (server->context)\n        lws_context_destroy(server->context);\n    g_free (server->server_url);\n    g_free (server->addr);\n    g_free (server->path);\n    if (server->subscriptions)\n        g_hash_table_destroy (server->subscriptions);\n\n    delete_unsent_messages (server);\n    g_async_queue_unref (server->messages);\n\n    g_free (server);\n}\n\nstatic void\nnotif_server_ref (NotifServer *server)\n{\n    g_atomic_int_inc (&server->refcnt);\n}\n\nstatic void\nnotif_server_unref (NotifServer *server)\n{\n    if (!server)\n        return;\n    if (g_atomic_int_dec_and_test (&server->refcnt))\n        notif_server_free (server);\n}\n\nstatic void\ninit_client_connect_info (NotifServer *server);\n\nstatic void *\nnotification_worker (void *vdata);\n\n// This function will check whether the notification server has been created,\n// if not, it will create a new one, otherwise it will return directly.\n// The host is the server's url and use_notif_server_port is used to check whether the server has nginx deployed.\nvoid\nseaf_notif_manager_connect_server (SeafNotifManager *mgr, const char *host, gboolean use_notif_server_port)\n{\n    pthread_t tid;\n    int rc;\n    NotifServer *existing_server = NULL;\n    NotifServer *server = NULL;\n\n    // Don't connect a connected server.\n    existing_server = get_notif_server (mgr, host);\n    if (existing_server) {\n        notif_server_unref (existing_server);\n        return;\n    }\n\n    server = notif_new_server (host, use_notif_server_port);\n    if (!server) {\n        return;\n    }\n\n    init_client_connect_info (server);\n\n    rc = pthread_create (&tid, NULL, notification_worker, server);\n    if (rc != 0) {\n        seaf_warning (\"Failed to create event notification new thread: %s.\\n\", strerror(rc));\n        notif_server_unref (server);\n        return;\n    }\n\n    pthread_mutex_lock (&mgr->priv->server_lock);\n    g_hash_table_insert (mgr->priv->servers, g_strdup (host), server);\n    pthread_mutex_unlock (&mgr->priv->server_lock);\n\n    return;\n}\n\nstatic void\ndisconnect_server (NotifServer *server)\n{\n    // lws_cancel_service will produce a cancel event to break out of lws_service loop.\n    lws_cancel_service (server->context);\n    server->close = TRUE;\n}\n\n// This policy will send a ping packet to the server per second.\n// If we don't receive pong messages within 5 seconds, it is considered that the connection is unavailable.\n// We will exit the event loop, and reconnect to the notification server.\nstatic const lws_retry_bo_t ping_policy = {\n\t.secs_since_valid_ping\t\t= 1,\n\t.secs_since_valid_hangup\t= 5,\n};\n\nstatic void\ninit_client_connect_info (NotifServer *server)\n{\n    struct lws_client_connect_info *i = &server->i;\n    memset(i, 0, sizeof(server->i));\n\n    i->context = server->context;\n    i->port = server->port;\n    i->address = server->addr;\n    i->path = server->path;\n    i->host = i->address;\n    i->origin = i->address;\n    if (server->use_ssl) {\n        i->ssl_connection = LCCSCF_USE_SSL | LCCSCF_ALLOW_SELFSIGNED;\n    }\n    i->protocol = \"notification.seafile.com\";\n    i->local_protocol_name = \"notification.seafile.com\";\n    i->pwsi = &server->wsi;\n    i->retry_and_idle_policy = &ping_policy;\n    i->userdata = server;\n}\n\nstatic void\nhandle_messages (const char *msg, size_t len);\n\nstatic char *ua = \"Seafile/\"SEAFILE_CLIENT_VERSION\" (\"USER_AGENT_OS\")\";\n\n// success:0\nstatic int\nevent_callback (struct lws *wsi, enum lws_callback_reasons reason,\n                void *user, void *in, size_t len)\n{\n    NotifServer *server = (NotifServer *)user;\n    Message *msg = NULL;\n    int m;\n    int ret = 0;\n    if (!server) {\n        return ret;\n    }\n\n    seaf_debug (\"Notification event: %d\\n\", reason);\n\n    switch (reason) {\n    case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:\n    {\n        unsigned char **p = (unsigned char **)in, *end = (*p) + len;\n\n        if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_USER_AGENT,\n            (unsigned char *)ua, (int)strlen(ua), p, end))\n            return -1;\n        break;\n    }\n    case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:\n        server->status = STATUS_ERROR;\n        seaf_debug (\"websocket connection error: %s\\n\",\n            in ? (char *)in : \"(null)\");\n        ret = -1;\n        break;\n    case LWS_CALLBACK_CLIENT_RECEIVE:\n        handle_messages (in, len);\n        break;\n    case LWS_CALLBACK_CLIENT_WRITEABLE:\n        msg = g_async_queue_try_pop (server->messages);\n        if (!msg) {\n            break;\n        }\n\n        /* notice we allowed for LWS_PRE in the payload already */\n        m = lws_write(wsi, ((unsigned char *)msg->payload) + LWS_PRE,\n                  msg->len, msg->type);\n        if (m < (int)msg->len) {\n            notif_message_free (msg);\n            seaf_warning (\"Failed to write message to websocket\\n\");\n            server->status = STATUS_ERROR;\n            return -1;\n        }\n\n        notif_message_free (msg);\n        break;\n    case LWS_CALLBACK_CLIENT_ESTABLISHED:\n        seaf_sync_manager_check_locks_and_folder_perms (seaf->sync_mgr, server->server_url);\n        server->status = STATUS_CONNECTED;\n        seaf_debug (\"Successfully connected to the server: %s\\n\", server->server_url);\n        break;\n    case LWS_CALLBACK_CLIENT_CLOSED:\n        ret = -1;\n        server->status = STATUS_ERROR;\n        // When the client is closed, cancel the context to prevent the socket from blocking in poll after the operating system wakes from sleep.\n        // After calling lws_cancel_service, a LWS_CALLBACK_EVENT_WAIT_CANCELLED callback is sent to every protocol on every vhost,\n        // notification_worker will exit loop.\n        lws_cancel_service (server->context);\n        break;\n    case LWS_CALLBACK_EVENT_WAIT_CANCELLED:\n        ret = -1;\n        server->status = STATUS_CANCELLED;\n        break;\n    default:\n        break;\n    }\n\n    return ret;\n}\n\nstatic int\nhandle_repo_update (json_t *content)\n{\n    json_t *member;\n    const char *repo_id;\n    const char *commit_id;\n    SeafRepo *repo = NULL;\n\n    member = json_object_get (content, \"repo_id\");\n    if (!member) {\n        seaf_warning (\"Invalid repo update notification: no repo_id.\\n\");\n        return -1;\n    }\n    repo_id = json_string_value (member);\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo) {\n        return -1;\n    }\n\n    if (!seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) {\n        return -1;\n    }\n\n    member = json_object_get (content, \"commit_id\");\n    if (!member) {\n        seaf_warning (\"Invalid repo update notification: no commit_id.\\n\");\n        return -1;\n    }\n    commit_id = json_string_value (member);\n    if (!commit_id) {\n        seaf_warning (\"Invalid repo update notification: commit_id is null.\\n\");\n        return -1;\n    }\n\n    seaf_sync_manager_update_repo (seaf->sync_mgr, repo, commit_id);\n\n    return 0;\n}\n\nstatic int\nhandle_file_lock (json_t *content)\n{\n    json_t *member;\n    const char *repo_id;\n    const char *change_event;\n    const char *path;\n    const char *lock_user;\n    SeafRepo *repo = NULL;\n\n    member = json_object_get (content, \"repo_id\");\n    if (!member) {\n        seaf_warning (\"Invalid file lock notification: no repo_id.\\n\");\n        return -1;\n    }\n    repo_id = json_string_value (member);\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo) {\n        return -1;\n    }\n\n    if (!seaf_repo_manager_server_is_pro (seaf->repo_mgr, repo->server_url))\n        return -1;\n\n    if (!seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) {\n        return -1;\n    }\n\n    member = json_object_get (content, \"path\");\n    if (!member) {\n        seaf_warning (\"Invalid file lock notification: no path.\\n\");\n        return -1;\n    }\n    path = json_string_value (member);\n    if (!path) {\n        seaf_warning (\"Invalid repo file lock notification: path is null.\\n\");\n        return -1;\n    }\n\n    member = json_object_get (content, \"change_event\");\n    if (!member) {\n        seaf_warning (\"Invalid file lock notification: no change_event.\\n\");\n        return -1;\n    }\n    change_event = json_string_value (member);\n\n    if (g_strcmp0 (change_event, \"locked\") == 0) {\n        member = json_object_get (content, \"lock_user\");\n        if (!member) {\n            seaf_warning (\"Invalid file lock notification: no lock_user.\\n\");\n            return -1;\n        }\n        lock_user = json_string_value (member);\n        // don't need to lock file when file has beed locked.\\n\n        if (seaf_filelock_manager_get_lock_status (seaf->filelock_mgr, repo_id, path) != FILE_NOT_LOCKED) {\n            return 0;\n        }\n\n        FileLockType type = LOCKED_OTHERS;\n        if (g_strcmp0 (lock_user, repo->email) == 0)\n            type = LOCKED_MANUAL;\n\n        seaf_filelock_manager_lock_file (seaf->filelock_mgr, repo_id, path, type);\n    } else if (g_strcmp0 (change_event, \"unlocked\") == 0) {\n        if (seaf_filelock_manager_get_lock_status (seaf->filelock_mgr, repo_id, path) == FILE_NOT_LOCKED) {\n            return 0;\n        }\n        seaf_filelock_manager_unlock_file (seaf->filelock_mgr, repo_id, path);\n    }\n\n    return 0;\n}\n\nstatic int\nhandle_folder_perm (json_t *content)\n{\n    json_t *member;\n    const char *repo_id;\n    const char *change_event;\n    const char *type;\n    const char *path;\n    const char *permission;\n    SeafRepo *repo = NULL;\n\n    member = json_object_get (content, \"repo_id\");\n    if (!member) {\n        seaf_warning (\"Invalid folder perm notification: no repo_id.\\n\");\n        return -1;\n    }\n    repo_id = json_string_value (member);\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo) {\n        return -1;\n    }\n\n    if (!seaf_repo_manager_server_is_pro (seaf->repo_mgr, repo->server_url))\n        return -1;\n\n    if (!seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) {\n        return -1;\n    }\n\n    member = json_object_get (content, \"path\");\n    if (!member) {\n        seaf_warning (\"Invalid folder perm notification: no path.\\n\");\n        return -1;\n    }\n    path = json_string_value (member);\n    if (!path) {\n        seaf_warning (\"Invalid repo folder perm notification: path is null.\\n\");\n        return -1;\n    }\n\n    member = json_object_get (content, \"type\");\n    if (!member) {\n        seaf_warning (\"Invalid folder perm notification: no type.\\n\");\n        return -1;\n    }\n    type = json_string_value (member);\n\n    member = json_object_get (content, \"change_event\");\n    if (!member) {\n        seaf_warning (\"Invalid folder perm notification: no change_event.\\n\");\n        return -1;\n    }\n    change_event = json_string_value (member);\n\n    member = json_object_get (content, \"perm\");\n    if (!member) {\n        seaf_warning (\"Invalid folder perm notification: no perm.\\n\");\n        return -1;\n    }\n    permission = json_string_value (member);\n\n    FolderPerm *perm = g_new0 (FolderPerm, 1);\n    perm->path = g_strdup (path);\n    perm->permission = g_strdup (permission);\n\n    if (g_strcmp0 (type, \"user\") == 0) {\n        if (g_strcmp0 (change_event, \"add\") == 0 || g_strcmp0 (change_event, \"modify\") == 0)\n            seaf_repo_manager_update_folder_perm (seaf->repo_mgr, repo_id,\n                                                  FOLDER_PERM_TYPE_USER,\n                                                  perm);\n        else if (g_strcmp0 (change_event, \"del\") == 0)\n            seaf_repo_manager_delete_folder_perm (seaf->repo_mgr, repo_id,\n                                                  FOLDER_PERM_TYPE_USER,\n                                                  perm);\n    } else if (g_strcmp0 (type, \"group\") == 0) {\n        if (g_strcmp0 (change_event, \"add\") == 0 || g_strcmp0 (change_event, \"modify\") == 0)\n            seaf_repo_manager_update_folder_perm (seaf->repo_mgr, repo_id,\n                                                  FOLDER_PERM_TYPE_GROUP,\n                                                  perm);\n        else if (g_strcmp0 (change_event, \"del\") == 0)\n            seaf_repo_manager_delete_folder_perm (seaf->repo_mgr, repo_id,\n                                                  FOLDER_PERM_TYPE_GROUP,\n                                                  perm);\n    }\n    g_free (perm);\n\n    return 0;\n}\n\nstatic int\nhandle_jwt_expired (json_t *content)\n{\n    NotifServer *server = NULL;\n    json_t *member;\n    const char *repo_id;\n    SeafRepo *repo = NULL;\n    int ret = 0;\n\n    member = json_object_get (content, \"repo_id\");\n    if (!member) {\n        seaf_warning (\"Invalid jwt expired notification: no repo_id.\\n\");\n        ret = -1;\n        goto out;\n    }\n    repo_id = json_string_value (member);\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo) {\n        ret = -1;\n        goto out;\n    }\n\n    if (!seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) {\n        ret = -1;\n        goto out;\n    }\n\n    server = get_notif_server (seaf->notif_mgr, repo->server_url);\n    if (!server || server->status != STATUS_CONNECTED) {\n        ret = -1;\n        goto out;\n    }\n\n    pthread_mutex_lock (&server->sub_lock);\n    g_hash_table_remove (server->subscriptions, repo->id);\n    pthread_mutex_unlock (&server->sub_lock);\n\n    // Set last_check_jwt_token to 0 to allow the repo to re-acquire a jwt token.\n    repo->last_check_jwt_token = 0;\nout:\n    notif_server_unref (server);\n\n    return ret;\n}\n\nstatic void\nhandle_messages (const char *msg, size_t len)\n{\n    json_t *object, *content, *member;\n    json_error_t jerror;\n    const char *type;\n\n    seaf_debug (\"Receive repo notification: %s\\n\", msg);\n\n    object = json_loadb (msg, len, 0, &jerror);\n    if (!object) {\n        seaf_warning (\"Failed to parse notification: %s.\\n\", jerror.text);\n        return;\n    }\n\n    member = json_object_get (object, \"type\");\n    if (!member) {\n        seaf_warning (\"Invalid notification info: no type.\\n\");\n        goto out;\n    }\n\n    type = json_string_value (member);\n\n    content = json_object_get (object, \"content\");\n    if (!content) {\n        seaf_warning (\"Invalid notification info: no content.\\n\");\n        goto out;\n    }\n\n    if (g_strcmp0 (type, \"repo-update\") == 0) {\n        if (handle_repo_update (content) < 0) {\n            goto out;\n        }\n    } else if (g_strcmp0 (type, \"file-lock-changed\") == 0) {\n        if (handle_file_lock (content) < 0) {\n            goto out;\n        }\n    } else if (g_strcmp0 (type, \"folder-perm-changed\") == 0) {\n        if (handle_folder_perm (content) < 0) {\n            goto out;\n        }\n    } else if (g_strcmp0 (type, \"jwt-expired\") == 0) {\n        if (handle_jwt_expired (content) < 0) {\n            goto out;\n        }\n    }\n\nout:\n    if (object)\n        json_decref (object);\n}\n\nstatic const struct lws_protocols protocols[] = {\n    { \"notification.seafile.com\", event_callback, 0, 0, 0, NULL, 0 },\n    {NULL, NULL, 0, 0, 0, NULL, 0} \n};\n\nstatic struct lws_context *\nlws_context_new (gboolean use_ssl)\n{\n    struct lws_context_creation_info info;\n    struct lws_context *context = NULL;\n\n    memset(&info, 0, sizeof info);\n    info.port = CONTEXT_PORT_NO_LISTEN;\n    info.protocols = protocols;\n    // Since we know this lws context is only ever going to be used with\n    // one client wsis / fds / sockets at a time, let lws know it doesn't\n    // have to use the default allocations for fd tables up to ulimit -n.\n    // It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we will use.\n    info.fd_limit_per_thread = 1 + 1 + 1;\n    char *ca_path = g_build_filename (seaf->seaf_dir, \"ca-bundle.pem\", NULL);\n    if (use_ssl) {\n         info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;\n         info.client_ssl_ca_filepath = ca_path;\n    }\n\n    context = lws_create_context(&info);\n    if (!context) {\n        g_free (ca_path);\n        seaf_warning (\"failed to create libwebsockets context\\n\");\n        return NULL;\n    }\n\n    g_free (ca_path);\n    return context;\n}\n\nstatic void\ndelete_subscribed_repos (NotifServer *server)\n{\n    GHashTableIter iter;\n    gpointer key, value;\n\n    if (!server->subscriptions)\n        return;\n\n    pthread_mutex_lock (&server->sub_lock);\n    g_hash_table_iter_init (&iter, server->subscriptions);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        g_hash_table_iter_remove (&iter);\n    }\n    pthread_mutex_unlock (&server->sub_lock);\n}\n\nstatic void\ndelete_unsent_messages (NotifServer *server)\n{\n    Message *msg = NULL;\n\n    if (!server->messages)\n        return;\n\n    while (1) {\n        msg = g_async_queue_try_pop (server->messages);\n        if (!msg) {\n            break;\n        }\n        notif_message_free (msg);\n    }\n\n    return;\n}\n\nstatic void *\nnotification_worker (void *vdata)\n{\n    NotifServer *server = (NotifServer *)vdata;\n\n    if (!server) {\n        return 0;\n    }\n\n    struct lws_client_connect_info *i = &server->i;\n    int n = 0;\n\n    while (!server->close) {\n        // We don't need to check the return value of this function, the connection will be processed in the event loop.\n        lws_client_connect_via_info(i);\n\n        while (n >= 0 && !server->close &&\n               server->status != STATUS_ERROR &&\n               server->status != STATUS_CANCELLED) {\n            n = lws_service(server->context, 0);\n        }\n\n        delete_subscribed_repos (server);\n        delete_unsent_messages (server);\n\n        if (server->status == STATUS_CANCELLED)\n            break;\n\n        // Wait a minute to reconnect to the notification server.\n        g_usleep (RECONNECT_INTERVAL * G_USEC_PER_SEC);\n        n = 0;\n        server->status = STATUS_DISCONNECTED;\n    }\n\n    seaf_message (\"Notification worker for server %s exiting.\\n\", server->server_url);\n    pthread_mutex_lock (&seaf->notif_mgr->priv->server_lock);\n    g_hash_table_remove (seaf->notif_mgr->priv->servers, server->server_url);\n    pthread_mutex_unlock (&seaf->notif_mgr->priv->server_lock);\n    notif_server_unref (server);\n\n    return 0;\n}\n\nvoid\nseaf_notif_manager_subscribe_repo (SeafNotifManager *mgr, SeafRepo *repo)\n{\n    NotifServer *server = NULL;\n    json_t *json_msg = NULL;\n    json_t *content = NULL;\n    char *str = NULL;\n    char *sub_id = NULL;\n    json_t *array, *obj;\n    char *repo_id = repo->id;\n\n    server = get_notif_server (mgr, repo->server_url);\n    if (!server || server->status != STATUS_CONNECTED)\n        goto out;\n\n    json_msg = json_object ();\n    json_object_set_new (json_msg, \"type\", json_string(\"subscribe\"));\n\n    content = json_object ();\n\n    array = json_array ();\n\n    obj = json_object ();\n    json_object_set_new (obj, \"id\", json_string(repo_id));\n    json_object_set_new (obj, \"jwt_token\", json_string(repo->jwt_token));\n    json_array_append_new (array, obj);\n\n    json_object_set_new (content, \"repos\", array);\n\n    json_object_set_new (json_msg, \"content\", content);\n\n    str = json_dumps (json_msg, JSON_COMPACT);\n    if (!str)\n        goto out;\n\n    Message *msg = notif_message_new (str, LWS_WRITE_TEXT);\n    if (!msg)\n        goto out;\n\n    g_async_queue_push (server->messages, msg);\n\n    sub_id = g_strdup (repo_id);\n\n    pthread_mutex_lock (&server->sub_lock);\n    g_hash_table_insert (server->subscriptions, sub_id, sub_id);\n    pthread_mutex_unlock (&server->sub_lock);\n\n    seaf_debug (\"Successfully subscribe repo %s\\n\", repo_id);\n\nout:\n    g_free (str);\n    json_decref (json_msg);\n    notif_server_unref (server);\n}\n\nvoid\nseaf_notif_manager_unsubscribe_repo (SeafNotifManager *mgr, SeafRepo *repo)\n{\n    NotifServer *server = NULL;\n    json_t *json_msg = NULL;\n    json_t *content = NULL;\n    char *str = NULL;\n    json_t *array, *obj;\n    char *repo_id = repo->id;\n\n    server = get_notif_server (mgr, repo->server_url);\n    if (!server || server->status != STATUS_CONNECTED) {\n        goto out;\n    }\n\n    json_msg = json_object ();\n    json_object_set_new (json_msg, \"type\", json_string(\"unsubscribe\"));\n\n    content = json_object ();\n\n    array = json_array ();\n\n    obj = json_object ();\n    json_object_set_new (obj, \"id\", json_string(repo_id));\n    json_array_append_new (array, obj);\n\n    json_object_set_new (content, \"repos\", array);\n\n    json_object_set_new (json_msg, \"content\", content);\n\n    str = json_dumps (json_msg, JSON_COMPACT);\n    if (!str)\n        goto out;\n\n    Message *msg = notif_message_new (str, LWS_WRITE_TEXT);\n    if (!msg)\n        goto out;\n\n    g_async_queue_push (server->messages, msg);\n\n    pthread_mutex_lock (&server->sub_lock);\n    g_hash_table_remove (server->subscriptions, repo_id);\n    pthread_mutex_unlock (&server->sub_lock);\n\n    seaf_debug (\"Successfully unsubscribe repo %s\\n\", repo_id);\n\nout:\n    g_free (str);\n    json_decref (json_msg);\n    notif_server_unref (server);\n}\n\ngboolean\nseaf_notif_manager_is_repo_subscribed (SeafNotifManager *mgr, SeafRepo *repo)\n{\n    NotifServer *server = NULL;\n    gboolean subscribed = FALSE;\n\n    if (!repo->server_url) {\n        goto out;\n    }\n\n    server = get_notif_server (mgr, repo->server_url);\n    if (!server || server->status != STATUS_CONNECTED) {\n        goto out;\n    }\n\n    pthread_mutex_lock (&server->sub_lock);\n    if (g_hash_table_lookup (server->subscriptions, repo->id)) {\n        pthread_mutex_unlock (&server->sub_lock);\n        subscribed = TRUE;\n        goto out;\n    }\n    pthread_mutex_unlock (&server->sub_lock);\n\nout:\n    notif_server_unref (server);\n    return subscribed;\n}\n"
  },
  {
    "path": "daemon/notif-mgr.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef NOTIF_MGR_H\n#define NOTIF_MGR_H\ntypedef struct _SeafNotifManager SeafNotifManager;\ntypedef struct _SeafNotifManagerPriv SeafNotifManagerPriv;\n\nstruct _SeafileSession;\n\nstruct _SeafNotifManager {\n    struct _SeafileSession   *seaf;\n\n    SeafNotifManagerPriv *priv;\n};\n\nSeafNotifManager *\nseaf_notif_manager_new (struct _SeafileSession *seaf);\n\nvoid\nseaf_notif_manager_connect_server (SeafNotifManager *mgr, const char *host,\n                                   gboolean use_notif_server_port);\n\nvoid\nseaf_notif_manager_subscribe_repo (SeafNotifManager *mgr, SeafRepo *repo);\n\nvoid\nseaf_notif_manager_unsubscribe_repo (SeafNotifManager *mgr, SeafRepo *repo);\n\ngboolean\nseaf_notif_manager_is_repo_subscribed (SeafNotifManager *mgr, SeafRepo *repo);\n\n#endif\n"
  },
  {
    "path": "daemon/repo-mgr.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n#include <glib/gstdio.h>\n\n#ifdef WIN32\n#include <windows.h>\n#include <shlobj.h>\n#endif\n\n#include <pthread.h>\n\n#include \"utils.h\"\n#define DEBUG_FLAG SEAFILE_DEBUG_SYNC\n#include \"log.h\"\n\n#include \"vc-utils.h\"\n\n#include \"seafile-session.h\"\n#include \"seafile-config.h\"\n#include \"commit-mgr.h\"\n#include \"branch-mgr.h\"\n#include \"repo-mgr.h\"\n#include \"fs-mgr.h\"\n#include \"seafile-error.h\"\n#include \"seafile-crypt.h\"\n#include \"index/index.h\"\n#include \"index/cache-tree.h\"\n#include \"diff-simple.h\"\n#include \"change-set.h\"\n\n#include \"db.h\"\n\n#include \"seafile-object.h\"\n\n#define INDEX_DIR \"index\"\n#define IGNORE_FILE \"seafile-ignore.txt\"\n\n#ifdef HAVE_KEYSTORAGE_GK\n#include \"repokey/seafile-gnome-keyring.h\"\n#endif // HAVE_KEYSTORAGE_GK\n\n#ifndef SEAFILE_CLIENT_VERSION\n#define SEAFILE_CLIENT_VERSION PACKAGE_VERSION\n#endif\n\nstruct _SeafRepoManagerPriv {\n    GHashTable *repo_hash;\n    sqlite3    *db;\n    pthread_mutex_t db_lock;\n    GHashTable *checkout_tasks_hash;\n    pthread_rwlock_t lock;\n\n    GHashTable *user_perms;     /* repo_id -> folder user perms */\n    GHashTable *group_perms;    /* repo_id -> folder group perms */\n    pthread_mutex_t perm_lock;\n\n    GAsyncQueue *lock_office_job_queue;\n\n    // sync_errors is used to record sync errors for which notifications have been sent to avoid repeated notifications of the same error.\n    GList *sync_errors;\n    pthread_mutex_t errors_lock;\n\n    GHashTable* block_map_cache_table;\n    pthread_rwlock_t block_map_lock;\n};\n\nstatic const char *ignore_table[] = {\n    /* tmp files under Linux */\n    \"*~\",\n    /* Seafile's backup file */\n    \"*.sbak\",\n    /* Emacs tmp files */\n    \"#*#\",\n    /* windows image cache */\n    \"Thumbs.db\",\n    /* For Mac */\n    \".DS_Store\",\n    \"._*\",\n    NULL,\n};\n\n#define CONFLICT_PATTERN \" \\\\(SFConflict .+\\\\)\"\n\n#define OFFICE_LOCK_PATTERN \"~\\\\$(.+)$\"\n#define LIBRE_OFFICE_LOCK_PATTERN \"\\\\.~lock\\\\.(.+)#$\"\n#define WPS_LOCK_PATTERN \"\\\\.~(.+)$\"\n\nstatic GPatternSpec** ignore_patterns;\nstatic GPatternSpec* office_temp_ignore_patterns[5];\nstatic GRegex *conflict_pattern = NULL;\nstatic GRegex *office_lock_pattern = NULL;\nstatic GRegex *libre_office_lock_pattern = NULL;\nstatic GRegex *wps_lock_pattern = NULL;\n\nstatic SeafRepo *\nload_repo (SeafRepoManager *manager, const char *repo_id);\n\nstatic void load_repos (SeafRepoManager *manager, const char *seaf_dir);\nstatic void seaf_repo_manager_del_repo_property (SeafRepoManager *manager,\n                                                 const char *repo_id);\n\nstatic int save_branch_repo_map (SeafRepoManager *manager, SeafBranch *branch);\nstatic void save_repo_property (SeafRepoManager *manager,\n                                const char *repo_id,\n                                const char *key, const char *value);\n\nstatic void\nlocked_file_free (LockedFile *file)\n{\n    if (!file)\n        return;\n    g_free (file->operation);\n    g_free (file);\n}\n\nstatic gboolean\nload_locked_file (sqlite3_stmt *stmt, void *data)\n{\n    GHashTable *ret = data;\n    LockedFile *file;\n    const char *path, *operation, *file_id;\n    gint64 old_mtime;\n\n    path = (const char *)sqlite3_column_text (stmt, 0);\n    operation = (const char *)sqlite3_column_text (stmt, 1);\n    old_mtime = sqlite3_column_int64 (stmt, 2);\n    file_id = (const char *)sqlite3_column_text (stmt, 3);\n\n    file = g_new0 (LockedFile, 1);\n    file->operation = g_strdup(operation);\n    file->old_mtime = old_mtime;\n    if (file_id)\n        memcpy (file->file_id, file_id, 40);\n\n    g_hash_table_insert (ret, g_strdup(path), file);\n\n    return TRUE;\n}\n\nLockedFileSet *\nseaf_repo_manager_get_locked_file_set (SeafRepoManager *mgr, const char *repo_id)\n{\n    GHashTable *locked_files = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                                      g_free,\n                                                      (GDestroyNotify)locked_file_free);\n    char sql[256];\n\n    sqlite3_snprintf (sizeof(sql), sql,\n                      \"SELECT path, operation, old_mtime, file_id FROM LockedFiles \"\n                      \"WHERE repo_id = '%q'\",\n                      repo_id);\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    /* Ingore database error. We return an empty set on error. */\n    sqlite_foreach_selected_row (mgr->priv->db, sql,\n                                 load_locked_file, locked_files);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    LockedFileSet *ret = g_new0 (LockedFileSet, 1);\n    ret->mgr = mgr;\n    memcpy (ret->repo_id, repo_id, 36);\n    ret->locked_files = locked_files;\n\n    return ret;\n}\n\nvoid\nlocked_file_set_free (LockedFileSet *fset)\n{\n    if (!fset)\n        return;\n    g_hash_table_destroy (fset->locked_files);\n    g_free (fset);\n}\n\nint\nlocked_file_set_add_update (LockedFileSet *fset,\n                            const char *path,\n                            const char *operation,\n                            gint64 old_mtime,\n                            const char *file_id)\n{\n    SeafRepoManager *mgr = fset->mgr;\n    char *sql;\n    sqlite3_stmt *stmt;\n    LockedFile *file;\n    gboolean exists;\n\n    exists = (g_hash_table_lookup (fset->locked_files, path) != NULL);\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    if (!exists) {\n        seaf_debug (\"New locked file record %.8s, %s, %s, %\"\n                    G_GINT64_FORMAT\".\\n\",\n                    fset->repo_id, path, operation, old_mtime);\n\n        sql = \"INSERT INTO LockedFiles VALUES (?, ?, ?, ?, ?, NULL)\";\n        stmt = sqlite_query_prepare (mgr->priv->db, sql);\n        sqlite3_bind_text (stmt, 1, fset->repo_id, -1, SQLITE_TRANSIENT);\n        sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT);\n        sqlite3_bind_text (stmt, 3, operation, -1, SQLITE_TRANSIENT);\n        sqlite3_bind_int64 (stmt, 4, old_mtime);\n        sqlite3_bind_text (stmt, 5, file_id, -1, SQLITE_TRANSIENT);\n        if (sqlite3_step (stmt) != SQLITE_DONE) {\n            seaf_warning (\"Failed to insert locked file %s to db: %s.\\n\",\n                          path, sqlite3_errmsg (mgr->priv->db));\n            sqlite3_finalize (stmt);\n            pthread_mutex_unlock (&mgr->priv->db_lock);\n            return -1;\n        }\n        sqlite3_finalize (stmt);\n\n        file = g_new0 (LockedFile, 1);\n        file->operation = g_strdup(operation);\n        file->old_mtime = old_mtime;\n        if (file_id)\n            memcpy (file->file_id, file_id, 40);\n\n        g_hash_table_insert (fset->locked_files, g_strdup(path), file);\n    } else {\n        seaf_debug (\"Update locked file record %.8s, %s, %s.\\n\",\n                    fset->repo_id, path, operation);\n\n        /* If a UPDATE record exists, don't update the old_mtime.\n         * We need to keep the old mtime when the locked file was first detected.\n         */\n\n        sql = \"UPDATE LockedFiles SET operation = ?, file_id = ? \"\n            \"WHERE repo_id = ? AND path = ?\";\n        stmt = sqlite_query_prepare (mgr->priv->db, sql);\n        sqlite3_bind_text (stmt, 1, operation, -1, SQLITE_TRANSIENT);\n        sqlite3_bind_text (stmt, 2, file_id, -1, SQLITE_TRANSIENT);\n        sqlite3_bind_text (stmt, 3, fset->repo_id, -1, SQLITE_TRANSIENT);\n        sqlite3_bind_text (stmt, 4, path, -1, SQLITE_TRANSIENT);\n        if (sqlite3_step (stmt) != SQLITE_DONE) {\n            seaf_warning (\"Failed to update locked file %s to db: %s.\\n\",\n                          path, sqlite3_errmsg (mgr->priv->db));\n            sqlite3_finalize (stmt);\n            pthread_mutex_unlock (&mgr->priv->db_lock);\n            return -1;\n        }\n        sqlite3_finalize (stmt);\n\n        file = g_hash_table_lookup (fset->locked_files, path);\n        g_free (file->operation);\n        file->operation = g_strdup(operation);\n        if (file_id)\n            memcpy (file->file_id, file_id, 40);\n    }\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    return 0;\n}\n\nint\nlocked_file_set_remove (LockedFileSet *fset, const char *path, gboolean db_only)\n{\n    SeafRepoManager *mgr = fset->mgr;\n    char *sql;\n    sqlite3_stmt *stmt;\n\n    if (g_hash_table_lookup (fset->locked_files, path) == NULL)\n        return 0;\n\n    seaf_debug (\"Remove locked file record %.8s, %s.\\n\",\n                fset->repo_id, path);\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    sql = \"DELETE FROM LockedFiles WHERE repo_id = ? AND path = ?\";\n    stmt = sqlite_query_prepare (mgr->priv->db, sql);\n    sqlite3_bind_text (stmt, 1, fset->repo_id, -1, SQLITE_TRANSIENT);\n    sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT);\n    if (sqlite3_step (stmt) != SQLITE_DONE) {\n        seaf_warning (\"Failed to remove locked file %s from db: %s.\\n\",\n                      path, sqlite3_errmsg (mgr->priv->db));\n        sqlite3_finalize (stmt);\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        return -1;\n    }\n    sqlite3_finalize (stmt);\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    if (!db_only)\n        g_hash_table_remove (fset->locked_files, path);\n\n    return 0;\n}\n\nLockedFile *\nlocked_file_set_lookup (LockedFileSet *fset, const char *path)\n{\n    return (LockedFile *) g_hash_table_lookup (fset->locked_files, path);\n}\n\n/* Folder permissions. */\n\nFolderPerm *\nfolder_perm_new (const char *path, const char *permission)\n{\n    FolderPerm *perm = g_new0 (FolderPerm, 1);\n\n    perm->path = g_strdup(path);\n    perm->permission = g_strdup(permission);\n\n    return perm;\n}\n\nvoid\nfolder_perm_free (FolderPerm *perm)\n{\n    if (!perm)\n        return;\n\n    g_free (perm->path);\n    g_free (perm->permission);\n    g_free (perm);\n}\n\nstatic GList *\nfolder_perm_list_copy (GList *perms)\n{\n    GList *ret = NULL, *ptr;\n    FolderPerm *perm, *new_perm;\n\n    for (ptr = perms; ptr; ptr = ptr->next) {\n        perm = ptr->data;\n        new_perm = folder_perm_new (perm->path, perm->permission);\n        ret = g_list_append (ret, new_perm);\n    }\n\n    return ret;\n}\n\nstatic gint\ncomp_folder_perms (gconstpointer a, gconstpointer b)\n{\n    const FolderPerm *perm_a = a, *perm_b = b;\n\n    return (strcmp (perm_b->path, perm_a->path));\n}\n\nstatic void\ndelete_folder_perm (SeafRepoManager *mgr, const char *repo_id, FolderPermType type, FolderPerm *perm)\n{\n    GList *folder_perms = NULL;\n    if (type == FOLDER_PERM_TYPE_USER) {\n        folder_perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id);\n        if (!folder_perms)\n            return;\n\n        if (g_strcmp0 (perm->path, \"\") == 0) {\n            g_list_free_full (folder_perms, (GDestroyNotify)folder_perm_free);\n            g_hash_table_remove (mgr->priv->user_perms, repo_id);\n            return;\n        }\n\n        GList *existing = g_list_find_custom (folder_perms,\n                                              perm,\n                                              comp_folder_perms);\n        if (existing) {\n            FolderPerm *old_perm = existing->data;\n            folder_perms = g_list_remove (folder_perms, old_perm);\n            folder_perm_free (old_perm);\n            g_hash_table_insert (mgr->priv->user_perms, g_strdup(repo_id), folder_perms);\n        }\n    } else if (type == FOLDER_PERM_TYPE_GROUP) {\n        folder_perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id);\n        if (!folder_perms)\n            return;\n\n        if (g_strcmp0 (perm->path, \"\") == 0) {\n            g_list_free_full (folder_perms, (GDestroyNotify)folder_perm_free);\n            g_hash_table_remove (mgr->priv->group_perms, repo_id);\n            return;\n        }\n\n        GList *existing = g_list_find_custom (folder_perms,\n                                              perm,\n                                              comp_folder_perms);\n        if (existing) {\n            FolderPerm *old_perm = existing->data;\n            folder_perms = g_list_remove (folder_perms, old_perm);\n            folder_perm_free (old_perm);\n            g_hash_table_insert (mgr->priv->group_perms, g_strdup(repo_id), folder_perms);\n        }\n    }\n}\n\nint\nseaf_repo_manager_delete_folder_perm (SeafRepoManager *mgr,\n                                      const char *repo_id,\n                                      FolderPermType type,\n                                      FolderPerm *perm)\n{\n    char *sql;\n    sqlite3_stmt *stmt;\n\n    g_return_val_if_fail ((type == FOLDER_PERM_TYPE_USER ||\n                           type == FOLDER_PERM_TYPE_GROUP),\n                          -1);\n\n    if (!perm) {\n        return 0;\n    }\n\n    /* Update db. */\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    if (g_strcmp0 (perm->path, \"\") == 0) {\n        if (type == FOLDER_PERM_TYPE_USER)\n            sql = \"DELETE FROM FolderUserPerms WHERE repo_id = ?\";\n        else\n            sql = \"DELETE FROM FolderGroupPerms WHERE repo_id = ?\";\n    } else {\n        if (type == FOLDER_PERM_TYPE_USER)\n            sql = \"DELETE FROM FolderUserPerms WHERE repo_id = ? and path = ?\";\n        else\n            sql = \"DELETE FROM FolderGroupPerms WHERE repo_id = ? and path = ?\";\n    }\n\n    stmt = sqlite_query_prepare (mgr->priv->db, sql);\n    sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);\n    if (g_strcmp0 (perm->path, \"\") != 0)\n        sqlite3_bind_text (stmt, 2, perm->path, -1, SQLITE_TRANSIENT);\n    if (sqlite3_step (stmt) != SQLITE_DONE) {\n        seaf_warning (\"Failed to remove folder perm for %.8s: %s.\\n\",\n                      repo_id, sqlite3_errmsg (mgr->priv->db));\n        sqlite3_finalize (stmt);\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        return -1;\n    }\n    sqlite3_finalize (stmt);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    /* Update in memory */\n    pthread_mutex_lock (&mgr->priv->perm_lock);\n    delete_folder_perm (mgr, repo_id, type, perm);\n    pthread_mutex_unlock (&mgr->priv->perm_lock);\n\n    return 0;\n}\n\nint\nseaf_repo_manager_update_folder_perm (SeafRepoManager *mgr,\n                                      const char *repo_id,\n                                      FolderPermType type,\n                                      FolderPerm *perm)\n{\n    char *sql;\n    sqlite3_stmt *stmt;\n\n    g_return_val_if_fail ((type == FOLDER_PERM_TYPE_USER ||\n                           type == FOLDER_PERM_TYPE_GROUP),\n                          -1);\n\n    if (!perm) {\n        return 0;\n    }\n\n    /* Update db. */\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    if (type == FOLDER_PERM_TYPE_USER)\n        sql = \"DELETE FROM FolderUserPerms WHERE repo_id = ? and path = ?\";\n    else\n        sql = \"DELETE FROM FolderGroupPerms WHERE repo_id = ? and path = ?\";\n    stmt = sqlite_query_prepare (mgr->priv->db, sql);\n    sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);\n    sqlite3_bind_text (stmt, 2, perm->path, -1, SQLITE_TRANSIENT);\n    if (sqlite3_step (stmt) != SQLITE_DONE) {\n        seaf_warning (\"Failed to remove folder perm for %.8s(%s): %s.\\n\",\n                      repo_id, perm->path, sqlite3_errmsg (mgr->priv->db));\n        sqlite3_finalize (stmt);\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        return -1;\n    }\n    sqlite3_finalize (stmt);\n\n    if (type == FOLDER_PERM_TYPE_USER)\n        sql = \"INSERT INTO FolderUserPerms VALUES (?, ?, ?)\";\n    else\n        sql = \"INSERT INTO FolderGroupPerms VALUES (?, ?, ?)\";\n    stmt = sqlite_query_prepare (mgr->priv->db, sql);\n\n    sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);\n    sqlite3_bind_text (stmt, 2, perm->path, -1, SQLITE_TRANSIENT);\n    sqlite3_bind_text (stmt, 3, perm->permission, -1, SQLITE_TRANSIENT);\n\n    if (sqlite3_step (stmt) != SQLITE_DONE) {\n        seaf_warning (\"Failed to insert folder perm for %.8s(%s): %s.\\n\",\n                      repo_id, perm->path, sqlite3_errmsg (mgr->priv->db));\n        sqlite3_finalize (stmt);\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        return -1;\n    }\n\n    sqlite3_finalize (stmt);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    /* Update in memory */\n    GList *folder_perms;\n\n    pthread_mutex_lock (&mgr->priv->perm_lock);\n    if (type == FOLDER_PERM_TYPE_USER) {\n        folder_perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id);\n        if (folder_perms) {\n            GList *existing = g_list_find_custom (folder_perms,\n                                                  perm,\n                                                  comp_folder_perms);\n            if (existing) {\n                FolderPerm *old_perm = existing->data;\n                g_free (old_perm->permission);\n                old_perm->permission = g_strdup (perm->permission);\n            } else {\n                FolderPerm *new_perm = folder_perm_new (perm->path, perm->permission);\n                folder_perms = g_list_insert_sorted (folder_perms, new_perm,\n                                                     comp_folder_perms);\n            }\n        } else {\n                FolderPerm *new_perm = folder_perm_new (perm->path, perm->permission);\n                folder_perms = g_list_insert_sorted (folder_perms, new_perm,\n                                                     comp_folder_perms);\n        }\n        g_hash_table_insert (mgr->priv->user_perms, g_strdup(repo_id), folder_perms);\n    } else if (type == FOLDER_PERM_TYPE_GROUP) {\n        folder_perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id);\n        if (folder_perms) {\n            GList *existing = g_list_find_custom (folder_perms,\n                                                  perm,\n                                                  comp_folder_perms);\n            if (existing) {\n                FolderPerm *old_perm = existing->data;\n                g_free (old_perm->permission);\n                old_perm->permission = g_strdup (perm->permission);\n            } else {\n                FolderPerm *new_perm = folder_perm_new (perm->path, perm->permission);\n                folder_perms = g_list_insert_sorted (folder_perms, new_perm,\n                                                     comp_folder_perms);\n            }\n        } else {\n                FolderPerm *new_perm = folder_perm_new (perm->path, perm->permission);\n                folder_perms = g_list_insert_sorted (folder_perms, new_perm,\n                                                     comp_folder_perms);\n        }\n        g_hash_table_insert (mgr->priv->group_perms, g_strdup(repo_id), folder_perms);\n    }\n    pthread_mutex_unlock (&mgr->priv->perm_lock);\n\n    return 0;\n}\n\nint\nseaf_repo_manager_update_folder_perms (SeafRepoManager *mgr,\n                                       const char *repo_id,\n                                       FolderPermType type,\n                                       GList *folder_perms)\n{\n    char *sql;\n    sqlite3_stmt *stmt;\n    GList *ptr;\n    FolderPerm *perm;\n\n    g_return_val_if_fail ((type == FOLDER_PERM_TYPE_USER ||\n                           type == FOLDER_PERM_TYPE_GROUP),\n                          -1);\n\n    /* Update db. */\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    if (type == FOLDER_PERM_TYPE_USER)\n        sql = \"DELETE FROM FolderUserPerms WHERE repo_id = ?\";\n    else\n        sql = \"DELETE FROM FolderGroupPerms WHERE repo_id = ?\";\n    stmt = sqlite_query_prepare (mgr->priv->db, sql);\n    sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);\n    if (sqlite3_step (stmt) != SQLITE_DONE) {\n        seaf_warning (\"Failed to remove folder perms for %.8s: %s.\\n\",\n                      repo_id, sqlite3_errmsg (mgr->priv->db));\n        sqlite3_finalize (stmt);\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        return -1;\n    }\n    sqlite3_finalize (stmt);\n\n    if (!folder_perms) {\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        return 0;\n    }\n\n    if (type == FOLDER_PERM_TYPE_USER)\n        sql = \"INSERT INTO FolderUserPerms VALUES (?, ?, ?)\";\n    else\n        sql = \"INSERT INTO FolderGroupPerms VALUES (?, ?, ?)\";\n    stmt = sqlite_query_prepare (mgr->priv->db, sql);\n\n    for (ptr = folder_perms; ptr; ptr = ptr->next) {\n        perm = ptr->data;\n\n        sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);\n        sqlite3_bind_text (stmt, 2, perm->path, -1, SQLITE_TRANSIENT);\n        sqlite3_bind_text (stmt, 3, perm->permission, -1, SQLITE_TRANSIENT);\n\n        if (sqlite3_step (stmt) != SQLITE_DONE) {\n            seaf_warning (\"Failed to insert folder perms for %.8s: %s.\\n\",\n                          repo_id, sqlite3_errmsg (mgr->priv->db));\n            sqlite3_finalize (stmt);\n            pthread_mutex_unlock (&mgr->priv->db_lock);\n            return -1;\n        }\n\n        sqlite3_reset (stmt);\n        sqlite3_clear_bindings (stmt);\n    }\n\n    sqlite3_finalize (stmt);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    /* Update in memory */\n    GList *new, *old;\n    new = folder_perm_list_copy (folder_perms);\n    new = g_list_sort (new, comp_folder_perms);\n\n    pthread_mutex_lock (&mgr->priv->perm_lock);\n    if (type == FOLDER_PERM_TYPE_USER) {\n        old = g_hash_table_lookup (mgr->priv->user_perms, repo_id);\n        if (old)\n            g_list_free_full (old, (GDestroyNotify)folder_perm_free);\n        g_hash_table_insert (mgr->priv->user_perms, g_strdup(repo_id), new);\n    } else if (type == FOLDER_PERM_TYPE_GROUP) {\n        old = g_hash_table_lookup (mgr->priv->group_perms, repo_id);\n        if (old)\n            g_list_free_full (old, (GDestroyNotify)folder_perm_free);\n        g_hash_table_insert (mgr->priv->group_perms, g_strdup(repo_id), new);\n    }\n    pthread_mutex_unlock (&mgr->priv->perm_lock);\n\n    return 0;\n}\n\nstatic gboolean\nload_folder_perm (sqlite3_stmt *stmt, void *data)\n{\n    GList **p_perms = data;\n    const char *path, *permission;\n\n    path = (const char *)sqlite3_column_text (stmt, 0);\n    permission = (const char *)sqlite3_column_text (stmt, 1);\n\n    FolderPerm *perm = folder_perm_new (path, permission);\n    *p_perms = g_list_prepend (*p_perms, perm);\n\n    return TRUE;\n}\n\nstatic GList *\nload_folder_perms_for_repo (SeafRepoManager *mgr,\n                            const char *repo_id,\n                            FolderPermType type)\n{\n    GList *perms = NULL;\n    char sql[256];\n\n    g_return_val_if_fail ((type == FOLDER_PERM_TYPE_USER ||\n                           type == FOLDER_PERM_TYPE_GROUP),\n                          NULL);\n\n    if (type == FOLDER_PERM_TYPE_USER)\n        sqlite3_snprintf (sizeof(sql), sql,\n                          \"SELECT path, permission FROM FolderUserPerms \"\n                          \"WHERE repo_id = '%q'\",\n                          repo_id);\n    else\n        sqlite3_snprintf (sizeof(sql), sql,\n                          \"SELECT path, permission FROM FolderGroupPerms \"\n                          \"WHERE repo_id = '%q'\",\n                          repo_id);\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    if (sqlite_foreach_selected_row (mgr->priv->db, sql,\n                                     load_folder_perm, &perms) < 0) {\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        GList *ptr;\n        for (ptr = perms; ptr; ptr = ptr->next)\n            folder_perm_free ((FolderPerm *)ptr->data);\n        g_list_free (perms);\n        return NULL;\n    }\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    /* Sort list in descending order by perm->path (longer path first). */\n    perms = g_list_sort (perms, comp_folder_perms);\n\n    return perms;\n}\n\nstatic void\ninit_folder_perms (SeafRepoManager *mgr)\n{\n    SeafRepoManagerPriv *priv = mgr->priv;\n    GList *repo_ids = g_hash_table_get_keys (priv->repo_hash);\n    GList *ptr;\n    GList *perms;\n    char *repo_id;\n\n    for (ptr = repo_ids; ptr; ptr = ptr->next) {\n        repo_id = ptr->data;\n        perms = load_folder_perms_for_repo (mgr, repo_id, FOLDER_PERM_TYPE_USER);\n        if (perms) {\n            pthread_mutex_lock (&priv->perm_lock);\n            g_hash_table_insert (priv->user_perms, g_strdup(repo_id), perms);\n            pthread_mutex_unlock (&priv->perm_lock);\n        }\n        perms = load_folder_perms_for_repo (mgr, repo_id, FOLDER_PERM_TYPE_GROUP);\n        if (perms) {\n            pthread_mutex_lock (&priv->perm_lock);\n            g_hash_table_insert (priv->group_perms, g_strdup(repo_id), perms);\n            pthread_mutex_unlock (&priv->perm_lock);\n        }\n    }\n\n    g_list_free (repo_ids);\n}\n\nstatic void\nremove_folder_perms (SeafRepoManager *mgr, const char *repo_id)\n{\n    GList *perms = NULL;\n\n    pthread_mutex_lock (&mgr->priv->perm_lock);\n\n    perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id);\n    if (perms) {\n        g_list_free_full (perms, (GDestroyNotify)folder_perm_free);\n        g_hash_table_remove (mgr->priv->user_perms, repo_id);\n    }\n\n    perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id);\n    if (perms) {\n        g_list_free_full (perms, (GDestroyNotify)folder_perm_free);\n        g_hash_table_remove (mgr->priv->group_perms, repo_id);\n    }\n\n    pthread_mutex_unlock (&mgr->priv->perm_lock);\n}\n\nint\nseaf_repo_manager_update_folder_perm_timestamp (SeafRepoManager *mgr,\n                                                const char *repo_id,\n                                                gint64 timestamp)\n{\n    char sql[256];\n    int ret;\n\n    snprintf (sql, sizeof(sql),\n              \"REPLACE INTO FolderPermTimestamp VALUES ('%s', %\"G_GINT64_FORMAT\")\",\n              repo_id, timestamp);\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    ret = sqlite_query_exec (mgr->priv->db, sql);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    return ret;\n}\n\ngint64\nseaf_repo_manager_get_folder_perm_timestamp (SeafRepoManager *mgr,\n                                             const char *repo_id)\n{\n    char sql[256];\n    gint64 ret;\n\n    sqlite3_snprintf (sizeof(sql), sql,\n                      \"SELECT timestamp FROM FolderPermTimestamp WHERE repo_id = '%q'\",\n                      repo_id);\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    ret = sqlite_get_int64 (mgr->priv->db, sql);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    return ret;\n}\n\nstatic char *\nlookup_folder_perm (GList *perms, const char *path)\n{\n    GList *ptr;\n    FolderPerm *perm;\n    char *folder;\n    int len;\n    char *permission = NULL;\n\n    for (ptr = perms; ptr; ptr = ptr->next) {\n        perm = ptr->data;\n\n        if (strcmp (perm->path, \"/\") != 0)\n            folder = g_strconcat (perm->path, \"/\", NULL);\n        else\n            folder = g_strdup(perm->path);\n\n        len = strlen(folder);\n        if (strcmp (perm->path, path) == 0 || strncmp(folder, path, len) == 0) {\n            permission = perm->permission;\n            g_free (folder);\n            break;\n        }\n        g_free (folder);\n    }\n\n    return permission;\n}\n\nstatic gboolean\nis_path_writable (const char *repo_id,\n                  gboolean is_repo_readonly,\n                  const char *path)\n{\n    SeafRepoManager *mgr = seaf->repo_mgr;\n    GList *user_perms = NULL, *group_perms = NULL;\n    char *permission = NULL;\n    char *abs_path = NULL;\n\n    pthread_mutex_lock (&mgr->priv->perm_lock);\n\n    user_perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id);\n    group_perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id);\n\n    if (user_perms || group_perms)\n        abs_path = g_strconcat (\"/\", path, NULL);\n\n    if (user_perms)\n        permission = lookup_folder_perm (user_perms, abs_path);\n    if (!permission && group_perms)\n        permission = lookup_folder_perm (group_perms, abs_path);\n\n    pthread_mutex_unlock (&mgr->priv->perm_lock);\n\n    g_free (abs_path);\n\n    if (!permission)\n        return !is_repo_readonly;\n\n    if (strcmp (permission, \"rw\") == 0)\n        return TRUE;\n    else\n        return FALSE;\n}\n\ngboolean\nseaf_repo_manager_is_path_writable (SeafRepoManager *mgr,\n                                    const char *repo_id,\n                                    const char *path)\n{\n    SeafRepo *repo = seaf_repo_manager_get_repo (mgr, repo_id);\n    if (!repo) {\n        seaf_warning (\"Failed to get repo %s.\\n\", repo_id);\n        return FALSE;\n    }\n\n    return is_path_writable (repo_id, repo->is_readonly, path);\n}\n\ngboolean\nis_repo_id_valid (const char *id)\n{\n    if (!id)\n        return FALSE;\n\n    return is_uuid_valid (id);\n}\n\n/*\n * Sync error related. These functions should belong to the sync-mgr module.\n * But since we have to store the errors in repo database, we have to put the code here.\n */\n\nint\nseaf_repo_manager_record_sync_error (const char *repo_id,\n                                     const char *repo_name,\n                                     const char *path,\n                                     int error_id)\n{\n    char *sql;\n    int ret;\n\n    pthread_mutex_lock (&seaf->repo_mgr->priv->db_lock);\n\n    if (path != NULL)\n        sql = sqlite3_mprintf (\"DELETE FROM FileSyncError WHERE repo_id='%q' AND path='%q'\",\n                               repo_id, path);\n    else\n        sql = sqlite3_mprintf (\"DELETE FROM FileSyncError WHERE repo_id='%q' AND path IS NULL\",\n                               repo_id);\n    ret = sqlite_query_exec (seaf->repo_mgr->priv->db, sql);\n    sqlite3_free (sql);\n    if (ret < 0)\n        goto out;\n\n    /* REPLACE INTO will update the primary key id automatically.\n     * So new errors are always on top.\n     */\n    if (path != NULL)\n        sql = sqlite3_mprintf (\"INSERT INTO FileSyncError \"\n                               \"(repo_id, repo_name, path, err_id, timestamp) \"\n                               \"VALUES ('%q', '%q', '%q', %d, %lld)\",\n                               repo_id, repo_name, path, error_id, (gint64)time(NULL));\n    else\n        sql = sqlite3_mprintf (\"INSERT INTO FileSyncError \"\n                               \"(repo_id, repo_name, err_id, timestamp) \"\n                               \"VALUES ('%q', '%q', %d, %lld)\",\n                               repo_id, repo_name, error_id, (gint64)time(NULL));\n        \n    ret = sqlite_query_exec (seaf->repo_mgr->priv->db, sql);\n    sqlite3_free (sql);\n\nout:\n    pthread_mutex_unlock (&seaf->repo_mgr->priv->db_lock);\n    return ret;\n}\n\nstatic gboolean\ncollect_file_sync_errors (sqlite3_stmt *stmt, void *data)\n{\n    GList **pret = data;\n    const char *repo_id, *repo_name, *path;\n    int id, err_id;\n    gint64 timestamp;\n    SeafileFileSyncError *error;\n\n    id = sqlite3_column_int (stmt, 0);\n    repo_id = (const char *)sqlite3_column_text (stmt, 1);\n    repo_name = (const char *)sqlite3_column_text (stmt, 2);\n    path = (const char *)sqlite3_column_text (stmt, 3);\n    err_id = sqlite3_column_int (stmt, 4);\n    timestamp = sqlite3_column_int64 (stmt, 5);\n\n    error = g_object_new (SEAFILE_TYPE_FILE_SYNC_ERROR,\n                          \"id\", id,\n                          \"repo_id\", repo_id,\n                          \"repo_name\", repo_name,\n                          \"path\", path,\n                          \"err_id\", err_id,\n                          \"timestamp\", timestamp,\n                          NULL);\n    *pret = g_list_prepend (*pret, error);\n\n    return TRUE;\n}\n\nint\nseaf_repo_manager_del_file_sync_error_by_id (SeafRepoManager *mgr, int id)\n{\n    int ret = 0;    \n    char *sql = NULL;\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    sql = sqlite3_mprintf (\"DELETE FROM FileSyncError WHERE id=%d\",\n                           id);\n    ret = sqlite_query_exec (mgr->priv->db, sql);\n    sqlite3_free (sql);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    return ret;\n}\n\nGList *\nseaf_repo_manager_get_file_sync_errors (SeafRepoManager *mgr, int offset, int limit)\n{\n    GList *ret = NULL;\n    char *sql;\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    sql = sqlite3_mprintf (\"SELECT id, repo_id, repo_name, path, err_id, timestamp FROM \"\n                           \"FileSyncError ORDER BY id DESC LIMIT %d OFFSET %d\",\n                           limit, offset);\n    sqlite_foreach_selected_row (mgr->priv->db, sql,\n                                 collect_file_sync_errors, &ret);\n    sqlite3_free (sql);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    ret = g_list_reverse (ret);\n\n    return ret;\n}\n\n/*\n * Record file-level sync errors and send system notification.\n */\n\nstruct _SyncError {\n    char *repo_id;\n    char *repo_name;\n    char *path;\n    int err_id;\n    gint64 timestamp;\n};\ntypedef struct _SyncError SyncError;\n\nvoid\nsend_sync_error_notification (const char *repo_id,\n                              const char *repo_name,\n                              const char *path,\n                              int err_id)\n{\n    json_t *object;\n    char *str;\n\n    object = json_object ();\n    json_object_set_new (object, \"repo_id\", json_string(repo_id));\n    json_object_set_new (object, \"repo_name\", json_string(repo_name));\n    json_object_set_new (object, \"path\", json_string(path));\n    json_object_set_new (object, \"err_id\", json_integer(err_id));\n\n    str = json_dumps (object, 0);\n\n    seaf_mq_manager_publish_notification (seaf->mq_mgr,\n                                          \"sync.error\",\n                                          str);\n\n    free (str);\n    json_decref (object);\n\n}\n\nstatic void\ncheck_and_send_notification (SeafRepoManager *mgr,\n                             const char *repo_id,\n                             const char *repo_name,\n                             const char *path,\n                             int err_id)\n{\n    GList *errors = mgr->priv->sync_errors, *ptr;\n    SyncError *err, *new_err;\n    gboolean found = FALSE;\n\n    pthread_mutex_lock (&mgr->priv->errors_lock);\n\n    for (ptr = errors; ptr; ptr = ptr->next) {\n        err = ptr->data;\n        if (g_strcmp0 (err->repo_id, repo_id) == 0 &&\n            g_strcmp0 (err->path, path) == 0) {\n            found = TRUE;\n            if (err->err_id != err_id) {\n                err->err_id = err_id;\n                send_sync_error_notification (repo_id, repo_name, path, err_id);\n            }\n            err->timestamp = (gint64)time(NULL);\n            break;\n        }\n    }\n\n    if (!found) {\n        new_err = g_new0 (SyncError, 1);\n        new_err->repo_id = g_strdup(repo_id);\n        new_err->repo_name = g_strdup(repo_name);\n        new_err->path = g_strdup(path);\n        new_err->err_id = err_id;\n        new_err->timestamp = (gint64)time(NULL);\n        mgr->priv->sync_errors = g_list_prepend (mgr->priv->sync_errors, new_err);\n\n        send_sync_error_notification (repo_id, repo_name, path, err_id);\n    }\n\n    pthread_mutex_unlock (&mgr->priv->errors_lock);\n}\n\nvoid\nsend_file_sync_error_notification (const char *repo_id,\n                                   const char *repo_name,\n                                   const char *path,\n                                   int err_id)\n{\n    if (!repo_name) {\n        SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n        if (!repo)\n            return;\n        repo_name = repo->name;\n    }\n\n    seaf_repo_manager_record_sync_error (repo_id, repo_name, path, err_id);\n\n    seaf_sync_manager_set_task_error_code (seaf->sync_mgr, repo_id, err_id);\n\n    check_and_send_notification (seaf->repo_mgr, repo_id, repo_name, path, err_id);\n}\n\nSeafRepo*\nseaf_repo_new (const char *id, const char *name, const char *desc)\n{\n    SeafRepo* repo;\n\n    /* valid check */\n  \n    \n    repo = g_new0 (SeafRepo, 1);\n    memcpy (repo->id, id, 36);\n    repo->id[36] = '\\0';\n\n    repo->name = g_strdup(name);\n    repo->desc = g_strdup(desc);\n\n    repo->worktree_invalid = TRUE;\n    repo->auto_sync = 1;\n    pthread_mutex_init (&repo->lock, NULL);\n\n    return repo;\n}\n\nint\nseaf_repo_check_worktree (SeafRepo *repo)\n{\n    SeafStat st;\n\n    if (repo->worktree == NULL) {\n        seaf_warning (\"Worktree for repo '%s'(%.8s) is not set.\\n\",\n                      repo->name, repo->id);\n        return -1;\n    }\n\n    /* check repo worktree */\n    if (g_access(repo->worktree, F_OK) < 0) {\n        if (!repo->worktree_invalid) {\n            seaf_warning (\"Failed to access worktree %s for repo '%s'(%.8s)\\n\",\n                          repo->worktree, repo->name, repo->id);\n        }\n\n        return -1;\n    }\n\n    if (seaf_stat(repo->worktree, &st) < 0) {\n        seaf_warning (\"Failed to stat worktree %s for repo '%s'(%.8s)\\n\",\n                      repo->worktree, repo->name, repo->id);\n        return -1;\n    }\n    if (!S_ISDIR(st.st_mode)) {\n        seaf_warning (\"Worktree %s for repo '%s'(%.8s) is not a directory.\\n\",\n                      repo->worktree, repo->name, repo->id);\n        return -1;\n    }\n\n    return 0;\n}\n\n\nstatic gboolean\ncheck_worktree_common (SeafRepo *repo)\n{\n    if (!repo->head) {\n        seaf_warning (\"Head for repo '%s'(%.8s) is not set.\\n\",\n                      repo->name, repo->id);\n        return FALSE;\n    }\n\n    if (seaf_repo_check_worktree (repo) < 0) {\n        return FALSE;\n    }\n\n    return TRUE;\n}\n\nvoid\nseaf_repo_free (SeafRepo *repo)\n{\n    if (repo->head) seaf_branch_unref (repo->head);\n\n    g_free (repo->name);\n    g_free (repo->desc);\n    g_free (repo->category);\n    g_free (repo->worktree);\n    g_free (repo->relay_id);\n    g_free (repo->email);\n    g_free (repo->username);\n    g_free (repo->token);\n    g_free (repo->pwd_hash_algo);\n    g_free (repo->pwd_hash_params);\n    g_free (repo);\n}\n\nstatic void\nset_head_common (SeafRepo *repo, SeafBranch *branch)\n{\n    if (repo->head)\n        seaf_branch_unref (repo->head);\n    repo->head = branch;\n    seaf_branch_ref(branch);\n}\n\nint\nseaf_repo_set_head (SeafRepo *repo, SeafBranch *branch)\n{\n    if (save_branch_repo_map (repo->manager, branch) < 0)\n        return -1;\n    set_head_common (repo, branch);\n    return 0;\n}\n\nSeafCommit *\nseaf_repo_get_head_commit (const char *repo_id)\n{\n    SeafRepo *repo;\n    SeafCommit *head;\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo) {\n        seaf_warning (\"Failed to get repo %s.\\n\", repo_id);\n        return NULL;\n    }\n\n    head = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                           repo_id, repo->version,\n                                           repo->head->commit_id);\n    if (!head) {\n        seaf_warning (\"Failed to get head for repo %s.\\n\", repo_id);\n        return NULL;\n    }\n\n    return head;\n}\n\nvoid\nseaf_repo_from_commit (SeafRepo *repo, SeafCommit *commit)\n{\n    repo->name = g_strdup (commit->repo_name);\n    repo->desc = g_strdup (commit->repo_desc);\n    repo->encrypted = commit->encrypted;\n    repo->last_modify = commit->ctime;\n    memcpy (repo->root_id, commit->root_id, 40);\n    if (repo->encrypted) {\n        repo->enc_version = commit->enc_version;\n        if (repo->enc_version == 1 && !commit->pwd_hash_algo)\n            memcpy (repo->magic, commit->magic, 32);\n        else if (repo->enc_version == 2) {\n            memcpy (repo->random_key, commit->random_key, 96);\n        }\n        else if (repo->enc_version == 3) {\n            memcpy (repo->random_key, commit->random_key, 96);\n            memcpy (repo->salt, commit->salt, 64);\n        }\n        else if (repo->enc_version == 4) {\n            memcpy (repo->random_key, commit->random_key, 96);\n            memcpy (repo->salt, commit->salt, 64);\n        }\n        if (repo->enc_version >= 2 && !commit->pwd_hash_algo) {\n            memcpy (repo->magic, commit->magic, 64);\n        }\n        if (commit->pwd_hash_algo) {\n            memcpy (repo->pwd_hash, commit->pwd_hash, 64);\n            repo->pwd_hash_algo = g_strdup (commit->pwd_hash_algo);\n            repo->pwd_hash_params = g_strdup (commit->pwd_hash_params);\n        }\n    }\n    repo->no_local_history = commit->no_local_history;\n    repo->version = commit->version;\n}\n\nvoid\nseaf_repo_to_commit (SeafRepo *repo, SeafCommit *commit)\n{\n    commit->repo_name = g_strdup (repo->name);\n    commit->repo_desc = g_strdup (repo->desc);\n    commit->encrypted = repo->encrypted;\n    if (commit->encrypted) {\n        commit->enc_version = repo->enc_version;\n        if (commit->enc_version == 1 && !repo->pwd_hash_algo)\n            commit->magic = g_strdup (repo->magic);\n        else if (commit->enc_version == 2) {\n            commit->random_key = g_strdup (repo->random_key);\n        }\n        else if (commit->enc_version == 3) {\n            commit->random_key = g_strdup (repo->random_key);\n            commit->salt = g_strdup (repo->salt);\n        }\n        else if (commit->enc_version == 4) {\n            commit->random_key = g_strdup (repo->random_key);\n            commit->salt = g_strdup (repo->salt);\n        }\n        if (commit->enc_version >= 2 && !repo->pwd_hash_algo) {\n            commit->magic = g_strdup (repo->magic);\n        }\n        if (repo->pwd_hash_algo) {\n            commit->pwd_hash = g_strdup (repo->pwd_hash);\n            commit->pwd_hash_algo = g_strdup (repo->pwd_hash_algo);\n            commit->pwd_hash_params = g_strdup (repo->pwd_hash_params);\n        }\n    }\n    commit->no_local_history = repo->no_local_history;\n    commit->version = repo->version;\n}\n\nstatic gboolean\nneed_to_sync_worktree_name (const char *repo_id)\n{\n    char *need_sync_wt_name = seaf_repo_manager_get_repo_property (seaf->repo_mgr,\n                                                                   repo_id,\n                                                                   REPO_SYNC_WORKTREE_NAME);\n    gboolean ret = (g_strcmp0(need_sync_wt_name, \"true\") == 0);\n    g_free (need_sync_wt_name);\n    return ret;\n}\n\nstatic void\nupdate_repo_worktree_name (SeafRepo *repo, const char *new_name, gboolean rewatch)\n{\n    char *dirname = NULL, *basename = NULL;\n    char *new_worktree = NULL;\n\n    seaf_message (\"Update worktree folder name of repo %s to %s.\\n\",\n                  repo->id, new_name);\n\n    dirname = g_path_get_dirname (repo->worktree);\n    if (g_strcmp0 (dirname, \".\") == 0)\n        return;\n    basename = g_path_get_basename (repo->worktree);\n\n    new_worktree = g_build_filename (dirname, new_name, NULL);\n\n    /* This can possibly fail on Windows if some files are opened under the worktree.\n     * The rename operation will be retried on next restart.\n     */\n    if (seaf_util_rename (repo->worktree, new_worktree) < 0) {\n        seaf_warning (\"Failed to rename worktree from %s to %s: %s.\\n\",\n                      repo->worktree, new_worktree, strerror(errno));\n        goto out;\n    }\n\n    if (seaf_repo_manager_set_repo_worktree (seaf->repo_mgr, repo, new_worktree) < 0) {\n        goto out;\n    }\n\n    if (rewatch) {\n        if (seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id) < 0) {\n            seaf_warning (\"Failed to unwatch repo %s old worktree.\\n\", repo->id);\n            goto out;\n        }\n\n        if (seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id, repo->worktree) < 0) {\n            seaf_warning (\"Failed to watch repo %s new worktree.\\n\", repo->id);\n        }\n    }\n\nout:\n    g_free (dirname);\n    g_free (basename);\n    g_free (new_worktree);\n}\n\nvoid\nseaf_repo_set_name (SeafRepo *repo, const char *new_name)\n{\n    char *old_name = repo->name;\n    repo->name = g_strdup(new_name);\n    g_free (old_name);\n\n    if (need_to_sync_worktree_name (repo->id))\n        update_repo_worktree_name (repo, new_name, TRUE);\n}\n\nstatic gboolean\ncollect_commit (SeafCommit *commit, void *vlist, gboolean *stop)\n{\n    GList **commits = vlist;\n\n    /* The traverse function will unref the commit, so we need to ref it.\n     */\n    seaf_commit_ref (commit);\n    *commits = g_list_prepend (*commits, commit);\n    return TRUE;\n}\n\nGList *\nseaf_repo_get_commits (SeafRepo *repo)\n{\n    GList *branches;\n    GList *ptr;\n    SeafBranch *branch;\n    GList *commits = NULL;\n\n    branches = seaf_branch_manager_get_branch_list (seaf->branch_mgr, repo->id);\n    if (branches == NULL) {\n        seaf_warning (\"Failed to get branch list of repo %s.\\n\", repo->id);\n        return NULL;\n    }\n\n    for (ptr = branches; ptr != NULL; ptr = ptr->next) {\n        branch = ptr->data;\n        gboolean res = seaf_commit_manager_traverse_commit_tree (seaf->commit_mgr,\n                                                                 repo->id,\n                                                                 repo->version,\n                                                                 branch->commit_id,\n                                                                 collect_commit,\n                                                                 &commits, FALSE);\n        if (!res) {\n            for (ptr = commits; ptr != NULL; ptr = ptr->next)\n                seaf_commit_unref ((SeafCommit *)(ptr->data));\n            g_list_free (commits);\n            goto out;\n        }\n    }\n\n    commits = g_list_reverse (commits);\n\nout:\n    for (ptr = branches; ptr != NULL; ptr = ptr->next) {\n        seaf_branch_unref ((SeafBranch *)ptr->data);\n    }\n    return commits;\n}\n\nvoid\nseaf_repo_set_readonly (SeafRepo *repo)\n{\n    repo->is_readonly = TRUE;\n    save_repo_property (repo->manager, repo->id, REPO_PROP_IS_READONLY, \"true\");\n}\n\nvoid\nseaf_repo_unset_readonly (SeafRepo *repo)\n{\n    repo->is_readonly = FALSE;\n    save_repo_property (repo->manager, repo->id, REPO_PROP_IS_READONLY, \"false\");\n}\n\ngboolean\nseaf_repo_manager_is_ignored_hidden_file (const char *filename)\n{\n    GPatternSpec **spec = ignore_patterns;\n\n    while (*spec) {\n        if (g_pattern_match_string(*spec, filename))\n            return TRUE;\n        spec++;\n    }\n\n    return FALSE;\n}\n\nstatic gboolean\nshould_ignore(const char *basepath, const char *filename, void *data)\n{\n    GPatternSpec **spec = ignore_patterns;\n    GList *ignore_list = (GList *)data;\n\n    if (!g_utf8_validate (filename, -1, NULL)) {\n        seaf_warning (\"File name %s contains non-UTF8 characters, skip.\\n\", filename);\n        return TRUE;\n    }\n\n    /* Ignore file/dir if its name is too long. */\n    if (strlen(filename) >= SEAF_DIR_NAME_LEN) {\n        seaf_warning (\"File name %s is too long, skip.\\n\", filename);\n        return TRUE;\n    }\n\n    if (strchr (filename, '/'))\n        return TRUE;\n\n    while (*spec) {\n        if (g_pattern_match_string(*spec, filename))\n            return TRUE;\n        spec++;\n    }\n\n    if (!seaf->sync_extra_temp_file) {\n        spec = office_temp_ignore_patterns;\n        while (*spec) {\n            if (g_pattern_match_string(*spec, filename))\n                return TRUE;\n            spec++;\n        }\n    }\n\n    if (basepath) {\n        char *fullpath = g_build_path (\"/\", basepath, filename, NULL);\n        if (seaf_repo_check_ignore_file (ignore_list, fullpath)) {\n            g_free (fullpath);\n            return TRUE;\n        }\n        g_free (fullpath);\n    }\n\n    return FALSE;\n}\n\n#ifndef WIN32\nstatic inline gboolean\nhas_trailing_space_or_period (const char *path)\n{\n    int len = strlen(path);\n    if (path[len - 1] == ' ' || path[len - 1] == '.') {\n        return TRUE;\n    }\n\n    return FALSE;\n}\n\nstatic gboolean\ncheck_path_ignore_on_windows (const char *file_path)\n{\n    gboolean ret = FALSE;\n    static char illegals[] = {'\\\\', ':', '*', '?', '\"', '<', '>', '|', '\\b', '\\t'};\n    char **components = g_strsplit (file_path, \"/\", -1);\n    int n_comps = g_strv_length (components);\n    int j = 0;\n    char *file_name;\n    int i;\n    char c;\n\n    for (; j < n_comps; ++j) {\n        file_name = components[j];\n\n        if (has_trailing_space_or_period (file_name)) {\n            /* Ignore files/dir whose path has trailing spaces. It would cause\n             * problem on windows. */\n            /* g_debug (\"ignore '%s' which contains trailing space in path\\n\", path); */\n            ret = TRUE;\n            goto out;\n        }\n\n        for (i = 0; i < G_N_ELEMENTS(illegals); i++) {\n            if (strchr (file_name, illegals[i])) {\n                ret = TRUE;\n                goto out;\n            }\n        }\n\n        for (c = 1; c <= 31; c++) {\n            if (strchr (file_name, c)) {\n                ret = TRUE;\n                goto out;\n            }\n        }\n    }\n\nout:\n    g_strfreev (components);\n\n    return ret;\n}\n#endif\n\nstatic int\nindex_cb (const char *repo_id,\n          int version,\n          const char *path,\n          unsigned char sha1[],\n          SeafileCrypt *crypt,\n          gboolean write_data)\n{\n    gint64 size;\n\n    /* Check in blocks and get object ID. */\n    if (seaf_fs_manager_index_blocks (seaf->fs_mgr, repo_id, version,\n                                      path, sha1, &size, crypt, write_data, !seaf->disable_block_hash) < 0) {\n        seaf_warning (\"Failed to index file %s.\\n\", path);\n        return -1;\n    }\n    return 0;\n}\n\nstatic gboolean\nis_symlink (const char *full_path)\n{\n#ifndef WIN32\n    SeafStat st;\n\n    if (lstat (full_path, &st) == 0 && S_ISLNK(st.st_mode)) {\n        return TRUE;\n    }\n    return FALSE;\n#else\n    return FALSE;\n#endif\n}\n\n#define MAX_COMMIT_SIZE 100 * (1 << 20) /* 100MB */\n\ntypedef struct _AddOptions {\n    LockedFileSet *fset;\n    ChangeSet *changeset;\n    gboolean is_repo_ro;\n    gboolean startup_scan;\n} AddOptions;\n\nstatic int\nadd_file (const char *repo_id,\n          int version,\n          const char *modifier,\n          struct index_state *istate, \n          const char *path,\n          const char *full_path,\n          SeafStat *st,\n          SeafileCrypt *crypt,\n          gint64 *total_size,\n          GQueue **remain_files,\n          AddOptions *options)\n{\n    gboolean added = FALSE;\n    int ret = 0;\n    gboolean is_writable = TRUE, is_locked = FALSE;\n    struct cache_entry *ce;\n    char *base_name = NULL;\n\n    if (seaf->ignore_symlinks && is_symlink(full_path)) {\n        return ret;\n    }\n\n    if (options)\n        is_writable = is_path_writable(repo_id,\n                                       options->is_repo_ro, path);\n\n    is_locked = seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,\n                                                      repo_id, path);\n    if (is_locked && options && !(options->startup_scan)) {\n        /* send_sync_error_notification (repo_id, NULL, path, */\n        /*                               SYNC_ERROR_ID_FILE_LOCKED); */\n    }\n\n    if (options && options->startup_scan) {\n        SyncStatus status;\n\n        ce = index_name_exists (istate, path, strlen(path), 0);\n        if (!ce || ie_match_stat(ce, st, 0) != 0)\n            status = SYNC_STATUS_SYNCING;\n        else\n            status = SYNC_STATUS_SYNCED;\n\n        /* Don't set \"syncing\" status for read-only path. */\n        if (status == SYNC_STATUS_SYNCED || (is_writable && !is_locked))\n            seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                                  repo_id,\n                                                  path,\n                                                  S_IFREG,\n                                                  status,\n                                                  FALSE);\n        /* send an error notification for read-only repo when modifying a file. */\n        if (status == SYNC_STATUS_SYNCING && !is_writable)\n            send_file_sync_error_notification (repo_id, NULL, path,\n                                               SYNC_ERROR_ID_UPDATE_TO_READ_ONLY_REPO);\n    }\n\n    if (!is_writable || is_locked)\n        return ret;\n\n#if defined WIN32 || defined __APPLE__\n    if (options && options->fset) {\n        LockedFile *file = locked_file_set_lookup (options->fset, path);\n        if (file) {\n            if (strcmp (file->operation, LOCKED_OP_DELETE) == 0) {\n                /* Only remove the lock record if the file is changed. */\n                if (st->st_mtime == file->old_mtime) {\n                    return ret;\n                }\n                locked_file_set_remove (options->fset, path, FALSE);\n            } else if (strcmp (file->operation, LOCKED_OP_UPDATE) == 0) {\n                return ret;\n            }\n        }\n    }\n#endif\n\n#ifndef WIN32\n    base_name = g_path_get_basename(full_path);\n    if (!seaf->hide_windows_incompatible_path_notification &&\n        check_path_ignore_on_windows (base_name)) {\n\n        ce = index_name_exists (istate, path, strlen(path), 0);\n        if (!ce)\n            send_file_sync_error_notification (repo_id, NULL, path,\n                                               SYNC_ERROR_ID_INVALID_PATH_ON_WINDOWS);\n    }\n    g_free (base_name);\n#endif\n\n    if (!remain_files) {\n        ret = add_to_index (repo_id, version, istate, path, full_path,\n                            st, 0, crypt, index_cb, modifier, &added);\n        if (!added) {\n            /* If the contents of the file doesn't change, move it to\n               synced status.\n            */\n            seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                                  repo_id,\n                                                  path,\n                                                  S_IFREG,\n                                                  SYNC_STATUS_SYNCED,\n                                                  FALSE);\n        } else {\n            if (total_size)\n                *total_size += (gint64)(st->st_size);\n            if (options && options->changeset) {\n                /* ce may be updated. */\n                ce = index_name_exists (istate, path, strlen(path), 0);\n                add_to_changeset (options->changeset,\n                                  DIFF_STATUS_ADDED,\n                                  ce->sha1,\n                                  st,\n                                  modifier,\n                                  path,\n                                  NULL);\n            }\n        }\n    } else if (*remain_files == NULL) {\n        ret = add_to_index (repo_id, version, istate, path, full_path,\n                            st, 0, crypt, index_cb, modifier, &added);\n        if (added) {\n            *total_size += (gint64)(st->st_size);\n            if (*total_size >= MAX_COMMIT_SIZE)\n                *remain_files = g_queue_new ();\n        } else {\n            seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                                  repo_id,\n                                                  path,\n                                                  S_IFREG,\n                                                  SYNC_STATUS_SYNCED,\n                                                  FALSE);\n        }\n        if (added && options && options->changeset) {\n            /* ce may be updated. */\n            ce = index_name_exists (istate, path, strlen(path), 0);\n            add_to_changeset (options->changeset,\n                              DIFF_STATUS_ADDED,\n                              ce->sha1,\n                              st,\n                              modifier,\n                              path,\n                              NULL);\n        }\n    } else {\n        *total_size += (gint64)(st->st_size);\n        g_queue_push_tail (*remain_files, g_strdup(path));\n    }\n\n    if (ret < 0) {\n        seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                              repo_id,\n                                              path,\n                                              S_IFREG,\n                                              SYNC_STATUS_ERROR,\n                                              TRUE);\n        // Only record index error when the file exists.\n        if (seaf_util_exists (full_path)) {\n            send_file_sync_error_notification (repo_id, NULL, path,\n                                               SYNC_ERROR_ID_INDEX_ERROR);\n        }\n    }\n\n    return ret;\n}\n\ntypedef struct AddParams {\n    const char *repo_id;\n    int version;\n    const char *modifier;\n    struct index_state *istate;\n    const char *worktree;\n    SeafileCrypt *crypt;\n    gboolean ignore_empty_dir;\n    GList *ignore_list;\n    gint64 *total_size;\n    GQueue **remain_files;\n    AddOptions *options;\n} AddParams;\n\n#ifndef WIN32\n\nstatic int\nadd_dir_recursive (const char *path, const char *full_path, SeafStat *st,\n                   AddParams *params, gboolean ignored)\n{\n    AddOptions *options = params->options;\n    GDir *dir;\n    const char *dname;\n    char *subpath, *full_subpath;\n    int n, total;\n    gboolean is_writable = TRUE;\n    struct stat sub_st;\n    char *base_name = NULL;\n\n    if (seaf->ignore_symlinks && is_symlink(full_path)) {\n        return 0;\n    }\n\n    dir = g_dir_open (full_path, 0, NULL);\n    if (!dir) {\n        seaf_warning (\"Failed to open dir %s: %s.\\n\", full_path, strerror(errno));\n\n        seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                              params->repo_id,\n                                              path,\n                                              S_IFDIR,\n                                              SYNC_STATUS_ERROR,\n                                              TRUE);\n        if (!ignored) {\n            send_file_sync_error_notification (params->repo_id, NULL, path,\n                                               SYNC_ERROR_ID_INDEX_ERROR);\n        }\n        return 0;\n    }\n\n    base_name = g_path_get_basename(full_path);\n    if (!seaf->hide_windows_incompatible_path_notification &&\n        check_path_ignore_on_windows (base_name)) {\n\n        struct cache_entry *ce = index_name_exists (params->istate, path,\n                                                    strlen(path), 0);\n        if (!ce)\n            send_file_sync_error_notification (params->repo_id, NULL, path,\n                                               SYNC_ERROR_ID_INVALID_PATH_ON_WINDOWS);\n    }\n    g_free (base_name);\n\n    n = 0;\n    total = 0;\n    while ((dname = g_dir_read_name(dir)) != NULL) {\n        ++total;\n\n#ifdef __APPLE__\n        char *norm_dname = g_utf8_normalize (dname, -1, G_NORMALIZE_NFC);\n        subpath = g_build_path (PATH_SEPERATOR, path, norm_dname, NULL);\n        g_free (norm_dname);\n#else\n        subpath = g_build_path (PATH_SEPERATOR, path, dname, NULL);\n#endif\n        full_subpath = g_build_filename (params->worktree, subpath, NULL);\n\n        if (stat (full_subpath, &sub_st) < 0) {\n            seaf_warning (\"Failed to stat %s: %s.\\n\", full_subpath, strerror(errno));\n            g_free (subpath);\n            g_free (full_subpath);\n            continue;\n        }\n\n        if (ignored || should_ignore(full_path, dname, params->ignore_list)) {\n            if (options && options->startup_scan) {\n                if (S_ISDIR(sub_st.st_mode))\n                    add_dir_recursive (subpath, full_subpath, &sub_st, params, TRUE);\n                else\n                    seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                                          params->repo_id,\n                                                          subpath,\n                                                          S_IFREG,\n                                                          SYNC_STATUS_IGNORED,\n                                                          TRUE);\n            }\n            g_free (subpath);\n            g_free (full_subpath);\n            continue;\n        }\n\n        ++n;\n\n        if (S_ISDIR(sub_st.st_mode))\n            add_dir_recursive (subpath, full_subpath, &sub_st, params, FALSE);\n        else if (S_ISREG(sub_st.st_mode))\n            add_file (params->repo_id,\n                      params->version,\n                      params->modifier,\n                      params->istate,\n                      subpath,\n                      full_subpath,\n                      &sub_st,\n                      params->crypt,\n                      params->total_size,\n                      params->remain_files,\n                      params->options);\n\n        g_free (subpath);\n        g_free (full_subpath);\n    }\n    g_dir_close (dir);\n\n    if (ignored) {\n        seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                              params->repo_id,\n                                              path,\n                                              S_IFDIR,\n                                              SYNC_STATUS_IGNORED,\n                                              TRUE);\n        return 0;\n    }\n\n    if (options)\n        is_writable = is_path_writable(params->repo_id,\n                                       options->is_repo_ro, path);\n\n    /* Update active path status for empty dir */\n    if (options && options->startup_scan && total == 0) {\n        SyncStatus status;\n        struct cache_entry *ce = index_name_exists (params->istate, path,\n                                                    strlen(path), 0);\n        if (!ce)\n            status = SYNC_STATUS_SYNCING;\n        else\n            status = SYNC_STATUS_SYNCED;\n\n\n        if (status == SYNC_STATUS_SYNCED || is_writable)\n            seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                                  params->repo_id,\n                                                  path,\n                                                  S_IFDIR,\n                                                  status,\n                                                  FALSE);\n    }\n\n    if (n == 0 && path[0] != 0 && is_writable) {\n        if (!params->remain_files || *(params->remain_files) == NULL) {\n            int rc = add_empty_dir_to_index (params->istate, path, st);\n            if (rc == 1 && options && options->changeset) {\n                unsigned char allzero[20] = {0};\n                add_to_changeset (options->changeset,\n                                  DIFF_STATUS_DIR_ADDED,\n                                  allzero,\n                                  st,\n                                  NULL,\n                                  path,\n                                  NULL);\n            }\n        } else\n            g_queue_push_tail (*(params->remain_files), g_strdup(path));\n    }\n\n    return 0;\n}\n\n/*\n * @remain_files: returns the files haven't been added under this path.\n *                If it's set to NULL, no partial commit will be created.\n */\nstatic int\nadd_recursive (const char *repo_id,\n               int version,\n               const char *modifier,\n               struct index_state *istate, \n               const char *worktree,\n               const char *path,\n               SeafileCrypt *crypt,\n               gboolean ignore_empty_dir,\n               GList *ignore_list,\n               gint64 *total_size,\n               GQueue **remain_files,\n               AddOptions *options)\n{\n    char *full_path;\n    SeafStat st;\n\n    full_path = g_build_path (PATH_SEPERATOR, worktree, path, NULL);\n    if (seaf_stat (full_path, &st) < 0) {\n        /* Ignore broken symlinks on Linux and Mac OS X */\n        if (lstat (full_path, &st) == 0 && S_ISLNK(st.st_mode)) {\n            g_free (full_path);\n            return 0;\n        }\n        seaf_warning (\"Failed to stat %s.\\n\", full_path);\n        g_free (full_path);\n        /* Ignore error. */\n\n        seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                              repo_id,\n                                              path,\n                                              0,\n                                              SYNC_STATUS_ERROR,\n                                              TRUE);\n\n        return 0;\n    }\n\n    if (S_ISREG(st.st_mode)) {\n        add_file (repo_id,\n                  version,\n                  modifier,\n                  istate,\n                  path,\n                  full_path,\n                  &st,\n                  crypt,\n                  total_size,\n                  remain_files,\n                  options);\n    } else if (S_ISDIR(st.st_mode)) {\n        AddParams params = {\n            .repo_id = repo_id,\n            .version = version,\n            .modifier = modifier,\n            .istate = istate,\n            .worktree = worktree,\n            .crypt = crypt,\n            .ignore_empty_dir = ignore_empty_dir,\n            .ignore_list = ignore_list,\n            .total_size = total_size,\n            .remain_files = remain_files,\n            .options = options,\n        };\n\n        add_dir_recursive (path, full_path, &st, &params, FALSE);\n    }\n\n    g_free (full_path);\n    return 0;\n}\n\nstatic gboolean\nis_empty_dir (const char *path, GList *ignore_list)\n{\n    GDir *dir;\n    const char *dname;\n    gboolean ret = TRUE;\n\n    dir = g_dir_open (path, 0, NULL);\n    if (!dir) {\n        return FALSE;\n    }\n\n    while ((dname = g_dir_read_name(dir)) != NULL) {\n        if (!should_ignore(path, dname, ignore_list)) {\n            ret = FALSE;\n            break;\n        }\n    }\n    g_dir_close (dir);\n\n    return ret;\n}\n\n#else\n\ntypedef struct IterCBData {\n    AddParams *add_params;\n    const char *parent;\n    const char *full_parent;\n    int n;\n\n    /* If parent dir is ignored, all children are ignored too. */\n    gboolean ignored;\n} IterCBData;\n\nstatic int\nadd_dir_recursive (const char *path, const char *full_path, SeafStat *st,\n                   AddParams *params, gboolean ignored);\n\nstatic int\niter_dir_cb (wchar_t *full_parent_w,\n             WIN32_FIND_DATAW *fdata,\n             void *user_data,\n             gboolean *stop)\n{\n    IterCBData *data = user_data;\n    AddParams *params = data->add_params;\n    AddOptions *options = params->options;\n    char *dname = NULL, *path = NULL, *full_path = NULL;\n    SeafStat st;\n    int ret = 0;\n\n    dname = g_utf16_to_utf8 (fdata->cFileName, -1, NULL, NULL, NULL);\n    if (!dname) {\n        goto out;\n    }\n\n    path = g_build_path (\"/\", data->parent, dname, NULL);\n    full_path = g_build_path (\"/\", params->worktree, path, NULL);\n\n    seaf_stat_from_find_data (fdata, &st);\n\n    if (data->ignored ||\n        should_ignore(data->full_parent, dname, params->ignore_list)) {\n        if (options && options->startup_scan) {\n            if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)\n                add_dir_recursive (path, full_path, &st, params, TRUE);\n            else\n                seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                                      params->repo_id,\n                                                      path,\n                                                      S_IFREG,\n                                                      SYNC_STATUS_IGNORED,\n                                                      TRUE);\n        }\n        goto out;\n    }\n\n    if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)\n        ret = add_dir_recursive (path, full_path, &st, params, FALSE);\n    else\n        ret = add_file (params->repo_id,\n                        params->version,\n                        params->modifier,\n                        params->istate,\n                        path,\n                        full_path,\n                        &st,\n                        params->crypt,\n                        params->total_size,\n                        params->remain_files,\n                        params->options);\n\n    ++(data->n);\n\nout:\n    g_free (dname);\n    g_free (path);\n    g_free (full_path);\n\n    return 0;\n}\n\nstatic int\nadd_dir_recursive (const char *path, const char *full_path, SeafStat *st,\n                   AddParams *params, gboolean ignored)\n{\n    AddOptions *options = params->options;\n    IterCBData data;\n    wchar_t *full_path_w;\n    int ret = 0;\n    gboolean is_writable = TRUE;\n\n    memset (&data, 0, sizeof(data));\n    data.add_params = params;\n    data.parent = path;\n    data.full_parent = full_path;\n    data.ignored = ignored;\n\n    full_path_w = win32_long_path (full_path);\n    ret = traverse_directory_win32 (full_path_w, iter_dir_cb, &data);\n    g_free (full_path_w);\n\n    /* Ignore traverse dir error. */\n    if (ret < 0) {\n        seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                              params->repo_id,\n                                              path,\n                                              S_IFDIR,\n                                              SYNC_STATUS_ERROR,\n                                              TRUE);\n        // The directory under Documents may be protected by the system on Windows and cannot be accessed, so the indexing errors for these folders are skipped.\n        char *parent_dir = g_path_get_dirname (full_path);\n        char *dir_name = NULL;\n        if (parent_dir) {\n            dir_name = g_path_get_basename (parent_dir);\n        }\n        if (!ignored && g_strcmp0 (dir_name, \"Documents\") != 0) {\n            send_file_sync_error_notification (params->repo_id, NULL, path,\n                                               SYNC_ERROR_ID_INDEX_ERROR);\n        }\n        g_free (parent_dir);\n        g_free (dir_name);\n        return 0;\n    }\n\n    if (ignored) {\n        seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                              params->repo_id,\n                                              path,\n                                              S_IFDIR,\n                                              SYNC_STATUS_IGNORED,\n                                              TRUE);\n        return 0;\n    }\n\n    if (options)\n        is_writable = is_path_writable(params->repo_id,\n                                        options->is_repo_ro, path);\n\n    /* Update active path status for empty dir */\n    if (options && options->startup_scan && ret == 0) {\n        SyncStatus status;\n        struct cache_entry *ce = index_name_exists (params->istate, path,\n                                                    strlen(path), 0);\n        if (!ce)\n            status = SYNC_STATUS_SYNCING;\n        else\n            status = SYNC_STATUS_SYNCED;\n\n\n        if (status == SYNC_STATUS_SYNCED || is_writable)\n            seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                                  params->repo_id,\n                                                  path,\n                                                  S_IFDIR,\n                                                  status,\n                                                  FALSE);\n    }\n\n    if (data.n == 0 && path[0] != 0 && !params->ignore_empty_dir && is_writable) {\n        if (!params->remain_files || *(params->remain_files) == NULL) {\n            int rc = add_empty_dir_to_index (params->istate, path, st);\n            if (rc == 1 && options && options->changeset) {\n                unsigned char allzero[20] = {0};\n                add_to_changeset (options->changeset,\n                                  DIFF_STATUS_DIR_ADDED,\n                                  allzero,\n                                  st,\n                                  NULL,\n                                  path,\n                                  NULL);\n            }\n        } else\n            g_queue_push_tail (*(params->remain_files), g_strdup(path));\n    }\n\n    return ret;\n}\n\nstatic int\nadd_recursive (const char *repo_id,\n               int version,\n               const char *modifier,\n               struct index_state *istate, \n               const char *worktree,\n               const char *path,\n               SeafileCrypt *crypt,\n               gboolean ignore_empty_dir,\n               GList *ignore_list,\n               gint64 *total_size,\n               GQueue **remain_files,\n               AddOptions *options)\n{\n    char *full_path;\n    SeafStat st;\n    int ret = 0;\n\n    full_path = g_build_path (PATH_SEPERATOR, worktree, path, NULL);\n    if (seaf_stat (full_path, &st) < 0) {\n        seaf_warning (\"Failed to stat %s.\\n\", full_path);\n        g_free (full_path);\n        seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                              repo_id,\n                                              path,\n                                              0,\n                                              SYNC_STATUS_ERROR,\n                                              TRUE);\n        /* Ignore error */\n        return 0;\n    }\n\n    if (S_ISREG(st.st_mode)) {\n        ret = add_file (repo_id,\n                        version,\n                        modifier,\n                        istate, \n                        path,\n                        full_path,\n                        &st,\n                        crypt,\n                        total_size,\n                        remain_files,\n                        options);\n    } else if (S_ISDIR(st.st_mode)) {\n        AddParams params = {\n            .repo_id = repo_id,\n            .version = version,\n            .modifier = modifier,\n            .istate = istate,\n            .worktree = worktree,\n            .crypt = crypt,\n            .ignore_empty_dir = ignore_empty_dir,\n            .ignore_list = ignore_list,\n            .total_size = total_size,\n            .remain_files = remain_files,\n            .options = options,\n        };\n\n        ret = add_dir_recursive (path, full_path, &st, &params, FALSE);\n    }\n\n    g_free (full_path);\n    return ret;\n}\n\nstatic gboolean\nis_empty_dir (const char *path, GList *ignore_list)\n{\n    WIN32_FIND_DATAW fdata;\n    HANDLE handle;\n    wchar_t *pattern;\n    wchar_t *path_w;\n    char *dname;\n    int path_len_w;\n    DWORD error;\n    gboolean ret = TRUE;\n\n    path_w = win32_long_path (path);\n\n    path_len_w = wcslen(path_w);\n\n    pattern = g_new0 (wchar_t, (path_len_w + 3));\n    wcscpy (pattern, path_w);\n    wcscat (pattern, L\"\\\\*\");\n\n    handle = FindFirstFileW (pattern, &fdata);\n    if (handle == INVALID_HANDLE_VALUE) {\n        seaf_warning (\"FindFirstFile failed %s: %lu.\\n\",\n                      path, GetLastError());\n        ret = FALSE;\n        goto out;\n    }\n\n    do {\n        if (wcscmp (fdata.cFileName, L\".\") == 0 ||\n            wcscmp (fdata.cFileName, L\"..\") == 0)\n            continue;\n\n        dname = g_utf16_to_utf8 (fdata.cFileName, -1, NULL, NULL, NULL);\n        if (!dname || !should_ignore (path, dname, ignore_list)) {\n            ret = FALSE;\n            g_free (dname);\n            FindClose (handle);\n            goto out;\n        }\n        g_free (dname);\n    } while (FindNextFileW (handle, &fdata) != 0);\n\n    error = GetLastError();\n    if (error != ERROR_NO_MORE_FILES) {\n        seaf_warning (\"FindNextFile failed %s: %lu.\\n\",\n                      path, error);\n    }\n\n    FindClose (handle);\n\nout:\n    g_free (path_w);\n    g_free (pattern);\n    return ret;\n}\n\n#endif  /* WIN32 */\n\n/* Returns whether the file should be removed from index. */\nstatic gboolean\ncheck_locked_file_before_remove (LockedFileSet *fset, const char *path)\n{\n#if defined WIN32 || defined __APPLE__\n    if (!fset)\n        return TRUE;\n\n    LockedFile *file = locked_file_set_lookup (fset, path);\n    gboolean ret = TRUE;\n\n    if (file)\n        ret = FALSE;\n\n    return ret;\n#else\n    return TRUE;\n#endif\n}\n\nstatic void\nremove_deleted (struct index_state *istate, const char *worktree, const char *prefix,\n                GList *ignore_list, LockedFileSet *fset,\n                const char *repo_id, gboolean is_repo_ro,\n                ChangeSet *changeset)\n{\n    struct cache_entry **ce_array = istate->cache;\n    struct cache_entry *ce;\n    char path[SEAF_PATH_MAX];\n    unsigned int i;\n    SeafStat st;\n    int ret;\n    gboolean not_exist;\n\n    char *full_prefix = g_strconcat (prefix, \"/\", NULL);\n    int len = strlen(full_prefix);\n\n    for (i = 0; i < istate->cache_nr; ++i) {\n        ce = ce_array[i];\n\n        if (!is_path_writable (repo_id, is_repo_ro, ce->name))\n            continue;\n\n        if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,\n                                                  repo_id, ce->name)) {\n            seaf_debug (\"Remove deleted: %s is locked on server, ignore.\\n\", ce->name);\n            continue;\n        }\n\n        if (prefix[0] != 0 && strcmp (ce->name, prefix) != 0 &&\n            strncmp (ce->name, full_prefix, len) != 0)\n            continue;\n\n        snprintf (path, SEAF_PATH_MAX, \"%s/%s\", worktree, ce->name);\n        not_exist = FALSE;\n        ret = seaf_stat (path, &st);\n        if (ret < 0 && errno == ENOENT)\n            not_exist = TRUE;\n\n        if (S_ISDIR (ce->ce_mode)) {\n            if (ce->ce_ctime.sec != 0 || ce_stage(ce) != 0) {\n                if (not_exist || (ret == 0 && !S_ISDIR (st.st_mode))) {\n                    /* Add to changeset only if dir is removed. */\n                    ce->ce_flags |= CE_REMOVE;\n                    if (changeset)\n                        /* Remove the parent dir from change set if it becomes\n                         * empty. If in the work tree the empty dir still exist,\n                         * we'll add it back to changeset in add_recursive() later.\n                         */\n                        remove_from_changeset (changeset,\n                                               DIFF_STATUS_DIR_DELETED,\n                                               ce->name,\n                                               TRUE,\n                                               prefix);\n                } else if (!is_empty_dir (path, ignore_list)) {\n                    /* Don't add to changeset if empty dir became non-empty. */\n                    ce->ce_flags |= CE_REMOVE;\n                }\n            }\n        } else {\n            /* If ce->ctime is 0 and stage is 0, it was not successfully checked out.\n             * In this case we don't want to mistakenly remove the file\n             * from the repo.\n             */\n            if ((not_exist || (ret == 0 && !S_ISREG (st.st_mode))) &&\n                (ce->ce_ctime.sec != 0 || ce_stage(ce) != 0) &&\n                check_locked_file_before_remove (fset, ce->name))\n            {\n                ce_array[i]->ce_flags |= CE_REMOVE;\n                if (changeset)\n                    remove_from_changeset (changeset,\n                                           DIFF_STATUS_DELETED,\n                                           ce->name,\n                                           TRUE,\n                                           prefix);\n            }\n        }\n    }\n\n    remove_marked_cache_entries (istate);\n\n    g_free (full_prefix);\n}\n\nstatic int\nscan_worktree_for_changes (struct index_state *istate, SeafRepo *repo,\n                           SeafileCrypt *crypt, GList *ignore_list,\n                           LockedFileSet *fset)\n{\n    remove_deleted (istate, repo->worktree, \"\", ignore_list, fset,\n                    repo->id, repo->is_readonly, repo->changeset);\n\n    AddOptions options;\n    memset (&options, 0, sizeof(options));\n    options.fset = fset;\n    options.is_repo_ro = repo->is_readonly;\n    options.changeset = repo->changeset;\n\n    if (add_recursive (repo->id, repo->version, repo->email,\n                       istate, repo->worktree, \"\", crypt, FALSE, ignore_list,\n                       NULL, NULL, &options) < 0)\n        return -1;\n\n    return 0;\n}\n\nstatic gboolean\ncheck_full_path_ignore (const char *worktree, const char *path, GList *ignore_list)\n{\n    char **tokens;\n    guint i;\n    guint n;\n    gboolean ret = FALSE;\n\n    tokens = g_strsplit (path, \"/\", 0);\n    n = g_strv_length (tokens);\n    for (i = 0; i < n; ++i) {\n        /* don't check ignore_list */\n        if (should_ignore (NULL, tokens[i], ignore_list)) {\n            ret = TRUE;\n            goto out;\n        }\n    }\n\n    char *full_path = g_build_path (\"/\", worktree, path, NULL);\n    if (seaf_repo_check_ignore_file (ignore_list, full_path))\n        ret = TRUE;\n    g_free (full_path);\n\nout:\n    g_strfreev (tokens);\n    return ret;\n}\n\nstatic int\nadd_path_to_index (SeafRepo *repo, struct index_state *istate,\n                   SeafileCrypt *crypt, const char *path, GList *ignore_list,\n                   GList **scanned_dirs, gint64 *total_size, GQueue **remain_files,\n                   LockedFileSet *fset)\n{\n    char *full_path;\n    SeafStat st;\n    AddOptions options;\n\n    /* When a repo is initially added, a SCAN_DIR event will be created\n     * for the worktree root \"\".\n     */\n    if (path[0] == 0) {\n        remove_deleted (istate, repo->worktree, \"\", ignore_list, fset,\n                        repo->id, repo->is_readonly, repo->changeset);\n\n        memset (&options, 0, sizeof(options));\n        options.fset = fset;\n        options.is_repo_ro = repo->is_readonly;\n        options.startup_scan = TRUE;\n        options.changeset = repo->changeset;\n\n        add_recursive (repo->id, repo->version, repo->email, istate,\n                       repo->worktree, path,\n                       crypt, FALSE, ignore_list,\n                       total_size, remain_files, &options);\n\n        return 0;\n    }\n\n    /* If we've recursively scanned the parent directory, don't need to scan\n     * any files under it any more.\n     */\n    GList *ptr;\n    char *dir, *full_dir;\n    for (ptr = *scanned_dirs; ptr; ptr = ptr->next) {\n        dir = ptr->data;\n        /* exact match */\n        if (strcmp (dir, path) == 0) {\n            seaf_debug (\"%s has been scanned before, skip adding.\\n\", path);\n            return 0;\n        }\n\n        /* prefix match. */\n        full_dir = g_strconcat (dir, \"/\", NULL);\n        if (strncmp (full_dir, path, strlen(full_dir)) == 0) {\n            g_free (full_dir);\n            seaf_debug (\"%s has been scanned before, skip adding.\\n\", path);\n            return 0;\n        }\n        g_free (full_dir);\n    }\n\n    if (check_full_path_ignore (repo->worktree, path, ignore_list))\n        return 0;\n\n    full_path = g_build_filename (repo->worktree, path, NULL);\n\n    if (seaf_stat (full_path, &st) < 0) {\n        if (errno != ENOENT)\n            send_file_sync_error_notification (repo->id, repo->name, path,\n                                               SYNC_ERROR_ID_INDEX_ERROR);\n        seaf_warning (\"Failed to stat %s: %s.\\n\", path, strerror(errno));\n        g_free (full_path);\n        return -1;\n    }\n\n    if (S_ISDIR(st.st_mode))\n        *scanned_dirs = g_list_prepend (*scanned_dirs, g_strdup(path));\n\n    memset (&options, 0, sizeof(options));\n    options.fset = fset;\n    options.is_repo_ro = repo->is_readonly;\n    options.changeset = repo->changeset;\n\n    /* Add is always recursive */\n    add_recursive (repo->id, repo->version, repo->email, istate, repo->worktree, path,\n                   crypt, FALSE, ignore_list, total_size, remain_files, &options);\n\n    g_free (full_path);\n    return 0;\n}\n\n#if 0\n\nstatic int\nadd_path_to_index (SeafRepo *repo, struct index_state *istate,\n                   SeafileCrypt *crypt, const char *path, GList *ignore_list,\n                   GList **scanned_dirs, gint64 *total_size, GQueue **remain_files,\n                   LockedFileSet *fset)\n{\n    /* If we've recursively scanned the parent directory, don't need to scan\n     * any files under it any more.\n     */\n    GList *ptr;\n    char *dir, *full_dir;\n    for (ptr = *scanned_dirs; ptr; ptr = ptr->next) {\n        dir = ptr->data;\n\n        /* Have scanned from root directory. */\n        if (dir[0] == 0) {\n            seaf_debug (\"%s has been scanned before, skip adding.\\n\", path);\n            return 0;\n        }\n\n        /* exact match */\n        if (strcmp (dir, path) == 0) {\n            seaf_debug (\"%s has been scanned before, skip adding.\\n\", path);\n            return 0;\n        }\n\n        /* prefix match. */\n        full_dir = g_strconcat (dir, \"/\", NULL);\n        if (strncmp (full_dir, path, strlen(full_dir)) == 0) {\n            g_free (full_dir);\n            seaf_debug (\"%s has been scanned before, skip adding.\\n\", path);\n            return 0;\n        }\n        g_free (full_dir);\n    }\n\n    if (path[0] != 0 && check_full_path_ignore (repo->worktree, path, ignore_list))\n        return 0;\n\n    remove_deleted (istate, repo->worktree, path, ignore_list, NULL,\n                    repo->id, repo->is_readonly, repo->changeset);\n\n    *scanned_dirs = g_list_prepend (*scanned_dirs, g_strdup(path));\n\n    AddOptions options;\n    memset (&options, 0, sizeof(options));\n    options.fset = fset;\n    options.is_repo_ro = repo->is_readonly;\n    options.changeset = repo->changeset;\n    /* When something is changed in the root directory, update active path\n     * sync status when scanning the worktree. This is inaccurate. This will\n     * be changed after we process fs events on Mac more precisely.\n     */\n    if (path[0] == 0)\n        options.startup_scan = TRUE;\n\n    /* Add is always recursive */\n    add_recursive (repo->id, repo->version, repo->email, istate, repo->worktree, path,\n                   crypt, FALSE, ignore_list, total_size, remain_files, &options);\n\n    return 0;\n}\n\n#endif  /* __APPLE__ */\n\nstatic int\nadd_remain_files (SeafRepo *repo, struct index_state *istate,\n                  SeafileCrypt *crypt, GQueue *remain_files,\n                  GList *ignore_list, gint64 *total_size)\n{\n    char *path;\n    char *full_path;\n    SeafStat st;\n    struct cache_entry *ce;\n\n    while ((path = g_queue_pop_head (remain_files)) != NULL) {\n        full_path = g_build_filename (repo->worktree, path, NULL);\n        if (seaf_stat (full_path, &st) < 0) {\n            seaf_warning (\"Failed to stat %s: %s.\\n\", full_path, strerror(errno));\n            g_free (path);\n            g_free (full_path);\n            continue;\n        }\n\n#ifndef WIN32\n    char *base_name = g_path_get_basename(full_path);\n        if (!seaf->hide_windows_incompatible_path_notification &&\n            check_path_ignore_on_windows (base_name)) {\n\n            send_file_sync_error_notification (repo->id, repo->name, path,\n                                               SYNC_ERROR_ID_INVALID_PATH_ON_WINDOWS);\n        }\n    g_free (base_name);\n#endif\n\n        if (S_ISREG(st.st_mode)) {\n            gboolean added = FALSE;\n            int ret = 0;\n            ret = add_to_index (repo->id, repo->version, istate, path, full_path,\n                                &st, 0, crypt, index_cb, repo->email, &added);\n            if (added) {\n                ce = index_name_exists (istate, path, strlen(path), 0);\n                add_to_changeset (repo->changeset,\n                                  DIFF_STATUS_ADDED,\n                                  ce->sha1,\n                                  &st,\n                                  repo->email,\n                                  path,\n                                  NULL);\n\n                *total_size += (gint64)(st.st_size);\n                if (*total_size >= MAX_COMMIT_SIZE) {\n                    g_free (path);\n                    g_free (full_path);\n                    break;\n                }\n            } else {\n                seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                                      repo->id,\n                                                      path,\n                                                      S_IFREG,\n                                                      SYNC_STATUS_SYNCED,\n                                                      TRUE);\n            }\n            if (ret < 0) {\n                seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                                      repo->id,\n                                                      path,\n                                                      S_IFREG,\n                                                      SYNC_STATUS_ERROR,\n                                                      TRUE);\n                if (seaf_util_exists (full_path)) {\n                    send_file_sync_error_notification (repo->id, NULL, path,\n                                                       SYNC_ERROR_ID_INDEX_ERROR);\n                }\n            }\n        } else if (S_ISDIR(st.st_mode)) {\n            if (is_empty_dir (full_path, ignore_list)) {\n                int rc = add_empty_dir_to_index (istate, path, &st);\n                if (rc == 1) {\n                    unsigned char allzero[20] = {0};\n                    add_to_changeset (repo->changeset,\n                                      DIFF_STATUS_DIR_ADDED,\n                                      allzero,\n                                      &st,\n                                      NULL,\n                                      path,\n                                      NULL);\n                }\n            }\n        }\n        g_free (path);\n        g_free (full_path);\n    }\n\n    return 0;\n}\n\nstatic void\ntry_add_empty_parent_dir_entry (const char *worktree,\n                                struct index_state *istate,\n                                const char *path)\n{\n    if (index_name_exists (istate, path, strlen(path), 0) != NULL)\n        return;\n\n    char *parent_dir = g_path_get_dirname (path);\n\n    /* Parent dir is the worktree dir. */\n    if (strcmp (parent_dir, \".\") == 0) {\n        g_free (parent_dir);\n        return;\n    }\n\n    char *full_dir = g_build_filename (worktree, parent_dir, NULL);\n    SeafStat st;\n    if (seaf_stat (full_dir, &st) < 0) {\n        goto out;\n    }\n\n    add_empty_dir_to_index_with_check (istate, parent_dir, &st);\n\nout:\n    g_free (parent_dir);\n    g_free (full_dir);\n}\n\nstatic void\ntry_add_empty_parent_dir_entry_from_wt (const char *worktree,\n                                        struct index_state *istate,\n                                        GList *ignore_list,\n                                        const char *path)\n{\n    if (index_name_exists (istate, path, strlen(path), 0) != NULL)\n        return;\n\n    char *parent_dir = g_path_get_dirname (path);\n\n    /* Parent dir is the worktree dir. */\n    if (strcmp (parent_dir, \".\") == 0) {\n        g_free (parent_dir);\n        return;\n    }\n\n    char *full_dir = g_build_filename (worktree, parent_dir, NULL);\n    SeafStat st;\n    if (seaf_stat (full_dir, &st) < 0) {\n        goto out;\n    }\n\n    if (is_empty_dir (full_dir, ignore_list)) {\n#ifdef WIN32\n        wchar_t *parent_dir_w = g_utf8_to_utf16 (parent_dir, -1, NULL, NULL, NULL);\n        wchar_t *pw;\n        for (pw = parent_dir_w; *pw != L'\\0'; ++pw)\n            if (*pw == L'/')\n                *pw = L'\\\\';\n\n        wchar_t *long_path = win32_83_path_to_long_path (worktree,\n                                                         parent_dir_w,\n                                                         wcslen(parent_dir_w));\n        g_free (parent_dir_w);\n        if (!long_path) {\n            seaf_warning (\"Convert %s to long path failed.\\n\", parent_dir);\n            goto out;\n        }\n\n        char *utf8_path = g_utf16_to_utf8 (long_path, -1, NULL, NULL, NULL);\n        if (!utf8_path) {\n            g_free (long_path);\n            goto out;\n        }\n\n        char *p;\n        for (p = utf8_path; *p != 0; ++p)\n            if (*p == '\\\\')\n                *p = '/';\n        g_free (long_path);\n\n        add_empty_dir_to_index (istate, utf8_path, &st);\n#else\n        add_empty_dir_to_index (istate, parent_dir, &st);\n#endif\n    }\n\nout:\n    g_free (parent_dir);\n    g_free (full_dir);\n}\n\nstatic void\nupdate_attributes (SeafRepo *repo,\n                   struct index_state *istate,\n                   const char *worktree,\n                   const char *path)\n{\n    ChangeSet *changeset = repo->changeset;\n    char *full_path;\n    struct cache_entry *ce;\n    SeafStat st;\n\n    ce = index_name_exists (istate, path, strlen(path), 0);\n    if (!ce)\n        return;\n\n    full_path = g_build_filename (worktree, path, NULL);\n    if (seaf_stat (full_path, &st) < 0) {\n        seaf_warning (\"Failed to stat %s: %s.\\n\", full_path, strerror(errno));\n        g_free (full_path);\n        return;\n    }\n\n    unsigned int new_mode = create_ce_mode (st.st_mode);\n    if (new_mode != ce->ce_mode || st.st_mtime != ce->ce_mtime.sec) {\n        ce->ce_mode = new_mode;\n        ce->ce_mtime.sec = st.st_mtime;\n        istate->cache_changed = 1;\n        add_to_changeset (changeset,\n                          DIFF_STATUS_MODIFIED,\n                          ce->sha1,\n                          &st,\n                          repo->email,\n                          path,\n                          NULL);\n    }\n    g_free (full_path);\n}\n\n#ifdef WIN32\nstatic void\nscan_subtree_for_deletion (const char *repo_id,\n                           struct index_state *istate,\n                           const char *worktree,\n                           const char *path,\n                           GList *ignore_list,\n                           LockedFileSet *fset,\n                           gboolean is_readonly,\n                           GList **scanned_dirs,\n                           ChangeSet *changeset)\n{\n    wchar_t *path_w = NULL;\n    wchar_t *dir_w = NULL;\n    wchar_t *p;\n    char *dir = NULL;\n    char *p2;\n\n    /* In most file systems, like NTFS, 8.3 format path should contain ~.\n     * Also note that *~ files are ignored.\n     */\n    if (!strchr (path, '~') || path[strlen(path)-1] == '~')\n        return;\n\n    path_w = g_utf8_to_utf16 (path, -1, NULL, NULL, NULL);\n\n    for (p = path_w; *p != L'\\0'; ++p)\n        if (*p == L'/')\n            *p = L'\\\\';\n\n    while (1) {\n        p = wcsrchr (path_w, L'\\\\');\n        if (p)\n            *p = L'\\0';\n        else\n            break;\n\n        dir_w = win32_83_path_to_long_path (worktree, path_w, wcslen(path_w));\n        if (dir_w)\n            break;\n    }\n\n    if (!dir_w)\n        dir_w = wcsdup(L\"\");\n\n    dir = g_utf16_to_utf8 (dir_w, -1, NULL, NULL, NULL);\n    if (!dir)\n        goto out;\n\n    for (p2 = dir; *p2 != 0; ++p2)\n        if (*p2 == '\\\\')\n            *p2 = '/';\n\n    /* If we've recursively scanned the parent directory, don't need to scan\n     * any files under it any more.\n     */\n    GList *ptr;\n    char *s, *full_s;\n    for (ptr = *scanned_dirs; ptr; ptr = ptr->next) {\n        s = ptr->data;\n\n        /* Have scanned from root directory. */\n        if (s[0] == 0) {\n            goto out;\n        }\n\n        /* exact match */\n        if (strcmp (s, path) == 0) {\n            goto out;\n        }\n\n        /* prefix match. */\n        full_s = g_strconcat (s, \"/\", NULL);\n        if (strncmp (full_s, dir, strlen(full_s)) == 0) {\n            g_free (full_s);\n            goto out;\n        }\n        g_free (full_s);\n    }\n\n    *scanned_dirs = g_list_prepend (*scanned_dirs, g_strdup(dir));\n\n    remove_deleted (istate, worktree, dir, ignore_list, fset,\n                    repo_id, is_readonly, changeset);\n\n    /* After remove_deleted(), empty dirs are left not removed in changeset.\n     * This can be fixed by removing the accurate deleted path. In most cases,\n     * basename doesn't contain ~, so we can always get the accurate path.\n     */\n    /* if (!convertion_failed) { */\n    /*     char *basename = strrchr (path, '/'); */\n    /*     char *deleted_path = NULL; */\n    /*     if (basename) { */\n    /*         deleted_path = g_build_path (\"/\", dir, basename, NULL); */\n    /*         add_to_changeset (changeset, */\n    /*                           DIFF_STATUS_DELETED, */\n    /*                           NULL, */\n    /*                           NULL, */\n    /*                           NULL, */\n    /*                           deleted_path, */\n    /*                           NULL, */\n    /*                           FALSE); */\n    /*         g_free (deleted_path); */\n    /*     } */\n    /* } */\n\nout:\n    g_free (path_w);\n    g_free (dir_w);\n    g_free (dir);\n}\n#else\nstatic void\nscan_subtree_for_deletion (const char *repo_id,\n                           struct index_state *istate,\n                           const char *worktree,\n                           const char *path,\n                           GList *ignore_list,\n                           LockedFileSet *fset,\n                           gboolean is_readonly,\n                           GList **scanned_dirs,\n                           ChangeSet *changeset)\n{\n}\n#endif\n\n/* Return TRUE if the caller should stop processing next event. */\nstatic gboolean\nhandle_add_files (SeafRepo *repo, struct index_state *istate,\n                  SeafileCrypt *crypt, GList *ignore_list,\n                  LockedFileSet *fset,\n                  WTStatus *status, WTEvent *event,\n                  GList **scanned_dirs, gint64 *total_size)\n{\n    SyncInfo *info;\n\n    if (!repo->create_partial_commit) {\n        /* XXX: We now use remain_files = NULL to signify not creating\n         * partial commits. It's better to use total_size = NULL for\n         * that purpose.\n         */\n        add_path_to_index (repo, istate, crypt, event->path,\n                           ignore_list, scanned_dirs,\n                           total_size, NULL, NULL);\n    } else if (!event->remain_files) {\n        GQueue *remain_files = NULL;\n        add_path_to_index (repo, istate, crypt, event->path,\n                           ignore_list, scanned_dirs,\n                           total_size, &remain_files, fset);\n        if (*total_size >= MAX_COMMIT_SIZE) {\n            seaf_message (\"Creating partial commit after adding %s.\\n\",\n                          event->path);\n\n            status->partial_commit = TRUE;\n\n            /* An event for a new folder may contain many files.\n             * If the total_size become larger than 100MB after adding\n             * some of these files, the remaining file paths will be\n             * cached in remain files. This way we don't need to scan\n             * the folder again next time.\n             */\n            if (remain_files) {\n                if (g_queue_get_length (remain_files) == 0) {\n                    g_queue_free (remain_files);\n                    return TRUE;\n                }\n\n                seaf_message (\"Remain files for %s.\\n\", event->path);\n\n                /* Cache remaining files in the event structure. */\n                event->remain_files = remain_files;\n\n                pthread_mutex_lock (&status->q_lock);\n                g_queue_push_head (status->event_q, event);\n                pthread_mutex_unlock (&status->q_lock);\n\n                info = seaf_sync_manager_get_sync_info (seaf->sync_mgr, repo->id);\n                if (!info->multipart_upload) {\n                    info->multipart_upload = TRUE;\n                    info->total_bytes = *total_size;\n                }\n            }\n\n            return TRUE;\n        }\n    } else {\n        seaf_message (\"Adding remaining files for %s.\\n\", event->path);\n\n        add_remain_files (repo, istate, crypt, event->remain_files,\n                          ignore_list, total_size);\n        if (g_queue_get_length (event->remain_files) != 0) {\n            pthread_mutex_lock (&status->q_lock);\n            g_queue_push_head (status->event_q, event);\n            pthread_mutex_unlock (&status->q_lock);\n            return TRUE;\n        } else {\n            info = seaf_sync_manager_get_sync_info (seaf->sync_mgr, repo->id);\n            info->end_multipart_upload = TRUE;\n            return TRUE;\n        }\n        if (*total_size >= MAX_COMMIT_SIZE)\n            return TRUE;\n    }\n\n    return FALSE;\n}\n\n#ifdef __APPLE__\n\n/* struct _WTDirent { */\n/*     char *dname; */\n/*     struct stat st; */\n/* }; */\n/* typedef struct _WTDirent WTDirent; */\n\n/* static gint */\n/* compare_wt_dirents (gconstpointer a, gconstpointer b) */\n/* { */\n/*     const WTDirent *dent_a = a, *dent_b = b; */\n\n/*     return (strcmp (dent_a->dname, dent_b->dname)); */\n/* } */\n\n/* static GList * */\n/* get_sorted_wt_dirents (const char *dir_path, const char *full_dir_path, */\n/*                        gboolean *error) */\n/* { */\n/*     GDir *dir; */\n/*     GError *err = NULL; */\n/*     const char *name; */\n/*     char *dname; */\n/*     char *full_sub_path, *sub_path; */\n/*     WTDirent *dent; */\n/*     GList *ret = NULL; */\n\n/*     dir = g_dir_open (full_dir_path, 0, &err); */\n/*     if (!dir) { */\n/*         seaf_warning (\"Failed to open dir %s: %s.\\n\", full_dir_path, err->message); */\n/*         *error = TRUE; */\n/*         return NULL; */\n/*     } */\n\n/*     while ((name = g_dir_read_name(dir)) != NULL) { */\n/*         dname = g_utf8_normalize (name, -1, G_NORMALIZE_NFC); */\n/*         sub_path = g_strconcat (dir_path, \"/\", dname, NULL); */\n/*         full_sub_path = g_strconcat (full_dir_path, \"/\", dname, NULL); */\n\n/*         dent = g_new0 (WTDirent, 1); */\n/*         dent->dname = dname; */\n\n/*         if (stat (full_sub_path, &dent->st) < 0) { */\n/*             seaf_warning (\"Failed to stat %s: %s.\\n\", full_sub_path, strerror(errno)); */\n/*             g_free (dname); */\n/*             g_free (sub_path); */\n/*             g_free (full_sub_path); */\n/*             g_free (dent); */\n/*             continue; */\n/*         } */\n\n/*         ret = g_list_prepend (ret, dent); */\n\n/*         g_free (sub_path); */\n/*         g_free (full_sub_path); */\n/*     } */\n\n/*     g_dir_close (dir); */\n\n/*     ret = g_list_sort (ret, compare_wt_dirents); */\n/*     return ret; */\n/* } */\n\n/* static void */\n/* wt_dirent_free (WTDirent *dent) */\n/* { */\n/*     if (!dent) */\n/*         return; */\n/*     g_free (dent->dname); */\n/*     g_free (dent); */\n/* } */\n\n/* inline static char * */\n/* concat_sub_path (const char *dir, const char *dname) */\n/* { */\n/*     if (dir[0] != 0) */\n/*         return g_strconcat(dir, \"/\", dname, NULL); */\n/*     else */\n/*         return g_strdup(dname); */\n/* } */\n\n/* static int */\n/* get_changed_paths_in_folder (SeafRepo *repo, struct index_state *istate, */\n/*                              const char *dir_path, */\n/*                              GList **add, GList **mod, GList **del) */\n/* { */\n/*     char *full_dir_path; */\n/*     GList *wt_dents = NULL, *index_dents = NULL; */\n/*     gboolean error = FALSE; */\n\n/*     full_dir_path = g_build_filename(repo->worktree, dir_path, NULL); */\n\n/*     wt_dents = get_sorted_wt_dirents (dir_path, full_dir_path, &error); */\n/*     if (error) { */\n/*         g_free (full_dir_path); */\n/*         return -1; */\n/*     } */\n\n/*     index_dents = list_dirents_from_index (istate, dir_path); */\n\n/*     GList *p; */\n/*     IndexDirent *dent; */\n/*     for (p = index_dents; p; p = p->next) { */\n/*         dent = p->data; */\n/*     } */\n\n/*     GList *p1 = wt_dents, *p2 = index_dents; */\n/*     WTDirent *dent1; */\n/*     IndexDirent *dent2; */\n\n/*     while (p1 && p2) { */\n/*         dent1 = p1->data; */\n/*         dent2 = p2->data; */\n\n/*         int rc = strcmp (dent1->dname, dent2->dname); */\n/*         if (rc == 0) { */\n/*             if (S_ISREG(dent1->st.st_mode) && !dent2->is_dir) { */\n/*                 if (dent1->st.st_mtime != dent2->ce->ce_mtime.sec) */\n/*                     *mod = g_list_prepend (*mod, concat_sub_path(dir_path, dent1->dname)); */\n/*             } else if ((S_ISREG(dent1->st.st_mode) && dent2->is_dir) || */\n/*                        (S_ISDIR(dent1->st.st_mode) && !dent2->is_dir)) { */\n/*                 *add = g_list_prepend (*add, concat_sub_path(dir_path, dent1->dname)); */\n/*                 *del = g_list_prepend (*del, concat_sub_path(dir_path, dent1->dname)); */\n/*             } */\n/*             p1 = p1->next; */\n/*             p2 = p2->next; */\n/*         } else if (rc < 0) { */\n/*             *add = g_list_prepend (*add, concat_sub_path(dir_path, dent1->dname)); */\n/*             p1 = p1->next; */\n/*         } else { */\n/*             *del = g_list_prepend (*del, concat_sub_path(dir_path, dent2->dname)); */\n/*             p2 = p2->next; */\n/*         } */\n/*     } */\n\n/*     while (p1) { */\n/*         dent1 = p1->data; */\n/*         *add = g_list_prepend (*add, concat_sub_path(dir_path, dent1->dname)); */\n/*         p1 = p1->next; */\n/*     } */\n\n/*     while (p2) { */\n/*         dent2 = p2->data; */\n/*         *del = g_list_prepend (*del, concat_sub_path(dir_path, dent2->dname)); */\n/*         p2 = p2->next; */\n/*     } */\n\n/*     g_free (full_dir_path); */\n/*     g_list_free_full (wt_dents, (GDestroyNotify)wt_dirent_free); */\n/*     g_list_free_full (index_dents, (GDestroyNotify)index_dirent_free); */\n/*     return 0; */\n/* } */\n\n#endif  /* __APPLE__ */\n\nstatic void\nupdate_active_file (SeafRepo *repo,\n                    const char *path,\n                    SeafStat *st,\n                    struct index_state *istate,\n                    gboolean ignored)\n{\n    if (ignored) {\n        seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                              repo->id,\n                                              path,\n                                              S_IFREG,\n                                              SYNC_STATUS_IGNORED,\n                                              TRUE);\n    } else {\n        SyncStatus status;\n        gboolean is_writable;\n\n        struct cache_entry *ce = index_name_exists(istate, path, strlen(path), 0);\n        if (!ce || ie_match_stat(ce, st, 0) != 0)\n            status = SYNC_STATUS_SYNCING;\n        else\n            status = SYNC_STATUS_SYNCED;\n\n        is_writable = is_path_writable (repo->id, repo->is_readonly, path);\n\n        if (!is_writable && status == SYNC_STATUS_SYNCING)\n            seaf_sync_manager_delete_active_path (seaf->sync_mgr,\n                                                  repo->id,\n                                                  path);\n        else\n            seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                                  repo->id,\n                                                  path,\n                                                  S_IFREG,\n                                                  status,\n                                                  FALSE);\n    }\n}\n\n#ifdef WIN32\n\ntypedef struct _UpdatePathData {\n    SeafRepo *repo;\n    struct index_state *istate;\n    GList *ignore_list;\n\n    const char *parent;\n    const char *full_parent;\n    gboolean ignored;\n} UpdatePathData;\n\nstatic void\nupdate_active_path_recursive (SeafRepo *repo,\n                              const char *path,\n                              struct index_state *istate,\n                              GList *ignore_list,\n                              gboolean ignored);\n\nstatic int\nupdate_active_path_cb (wchar_t *full_parent_w,\n                       WIN32_FIND_DATAW *fdata,\n                       void *user_data,\n                       gboolean *stop)\n{\n    UpdatePathData *upd_data = user_data;\n    char *dname;\n    char *path;\n    gboolean ignored = FALSE;\n    SeafStat st;\n\n    dname = g_utf16_to_utf8 (fdata->cFileName, -1, NULL, NULL, NULL);\n    if (!dname)\n        return 0;\n\n    path = g_build_path (\"/\", upd_data->parent, dname, NULL);\n\n    if (upd_data->ignored || should_ignore (upd_data->full_parent, dname, upd_data->ignore_list))\n        ignored = TRUE;\n\n    seaf_stat_from_find_data (fdata, &st);\n\n    if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {\n        update_active_path_recursive (upd_data->repo,\n                                      path,\n                                      upd_data->istate,\n                                      upd_data->ignore_list,\n                                      ignored);\n    } else {\n        update_active_file (upd_data->repo,\n                            path,\n                            &st,\n                            upd_data->istate,\n                            ignored);\n    }\n\n    g_free (dname);\n    g_free (path);\n\n    return 0;\n}\n\nstatic void\nupdate_active_path_recursive (SeafRepo *repo,\n                              const char *path,\n                              struct index_state *istate,\n                              GList *ignore_list,\n                              gboolean ignored)\n{\n    char *full_path;\n    wchar_t *full_path_w;\n    int ret = 0;\n    UpdatePathData upd_data;\n\n    full_path = g_build_filename (repo->worktree, path, NULL);\n\n    memset (&upd_data, 0, sizeof(upd_data));\n    upd_data.repo = repo;\n    upd_data.istate = istate;\n    upd_data.ignore_list = ignore_list;\n    upd_data.parent = path;\n    upd_data.full_parent = full_path;\n    upd_data.ignored = ignored;\n\n    full_path_w = win32_long_path (full_path);\n    ret = traverse_directory_win32 (full_path_w, update_active_path_cb, &upd_data);\n    g_free (full_path_w);\n    g_free (full_path);\n\n    if (ret < 0)\n        return;\n\n    /* Don't set sync status for read-only paths, since changes to read-only\n     * files are ignored.\n     */\n    if (!is_path_writable (repo->id, repo->is_readonly, path))\n        return;\n\n    /* traverse_directory_win32() returns number of entries in the directory. */\n    if (ret == 0 && path[0] != 0) {\n        if (ignored) {\n            seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                                  repo->id,\n                                                  path,\n                                                  S_IFDIR,\n                                                  SYNC_STATUS_IGNORED,\n                                                  FALSE);\n        } else {\n            /* There is no need to update an empty dir. */\n            SyncStatus status;\n            struct cache_entry *ce = index_name_exists(istate, path, strlen(path), 0);\n            if (!ce)\n                status = SYNC_STATUS_SYNCING;\n            else\n                status = SYNC_STATUS_SYNCED;\n            seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                                  repo->id,\n                                                  path,\n                                                  S_IFDIR,\n                                                  status,\n                                                  FALSE);\n        }\n    }\n}\n\n#else\n\nstatic void\nupdate_active_path_recursive (SeafRepo *repo,\n                              const char *path,\n                              struct index_state *istate,\n                              GList *ignore_list,\n                              gboolean ignored)\n{\n    GDir *dir;\n    GError *error = NULL;\n    const char *name;\n    char *dname;\n    char *full_path, *full_sub_path, *sub_path;\n    struct stat st;\n    gboolean ignore_sub;\n\n    full_path = g_build_filename(repo->worktree, path, NULL);\n\n    dir = g_dir_open (full_path, 0, &error);\n    if (!dir) {\n        seaf_warning (\"Failed to open dir %s: %s.\\n\", full_path, error->message);\n        g_free (full_path);\n        return;\n    }\n\n    int n = 0;\n    while ((name = g_dir_read_name(dir)) != NULL) {\n        ++n;\n\n        dname = g_utf8_normalize (name, -1, G_NORMALIZE_NFC);\n        sub_path = g_strconcat (path, \"/\", dname, NULL);\n        full_sub_path = g_strconcat (full_path, \"/\", dname, NULL);\n\n        ignore_sub = FALSE;\n        if (ignored || should_ignore(full_path, dname, ignore_list))\n            ignore_sub = TRUE;\n\n        if (seaf->ignore_symlinks && is_symlink(full_sub_path)) {\n            g_free (dname);\n            g_free (sub_path);\n            g_free (full_sub_path);\n            continue;\n        }\n\n        if (stat (full_sub_path, &st) < 0) {\n            seaf_warning (\"Failed to stat %s: %s.\\n\", full_sub_path, strerror(errno));\n            g_free (dname);\n            g_free (sub_path);\n            g_free (full_sub_path);\n            continue;\n        }\n\n        if (S_ISDIR(st.st_mode)) {\n            update_active_path_recursive (repo, sub_path, istate, ignore_list,\n                                          ignore_sub);\n        } else if (S_ISREG(st.st_mode)) {\n            update_active_file (repo, sub_path, &st, istate,\n                                ignore_sub);\n        }\n\n        g_free (dname);\n        g_free (sub_path);\n        g_free (full_sub_path);\n    }\n\n    g_dir_close (dir);\n\n    g_free (full_path);\n\n    /* Don't set sync status for read-only paths, since changes to read-only\n     * files are ignored.\n     */\n    if (!is_path_writable (repo->id, repo->is_readonly, path))\n        return;\n\n    if (n == 0 && path[0] != 0) {\n        if (ignored) {\n            seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                                  repo->id,\n                                                  path,\n                                                  S_IFDIR,\n                                                  SYNC_STATUS_IGNORED,\n                                                  TRUE);\n        } else {\n            /* There is no need to update an empty dir. */\n            SyncStatus status;\n            struct cache_entry *ce = index_name_exists(istate, path, strlen(path), 0);\n            if (!ce)\n                status = SYNC_STATUS_SYNCING;\n            else\n                status = SYNC_STATUS_SYNCED;\n            seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                                  repo->id,\n                                                  path,\n                                                  S_IFDIR,\n                                                  status,\n                                                  TRUE);\n        }\n    }\n}\n\n#endif  /* WIN32 */\n\nstatic void\nprocess_active_path (SeafRepo *repo, const char *path,\n                     struct index_state *istate, GList *ignore_list)\n{\n    SeafStat st;\n    gboolean ignored = FALSE;\n\n    char *fullpath = g_build_filename (repo->worktree, path, NULL);\n    if (seaf_stat (fullpath, &st) < 0) {\n        g_free (fullpath);\n        return;\n    }\n\n    if (check_full_path_ignore (repo->worktree, path, ignore_list))\n        ignored = TRUE;\n\n    if (S_ISREG(st.st_mode)) {\n        if (!seaf_filelock_manager_is_file_locked(seaf->filelock_mgr,\n                                                  repo->id, path)) {\n            update_active_file (repo, path, &st, istate, ignored);\n        }\n    } else {\n        update_active_path_recursive (repo, path, istate, ignore_list, ignored);\n    }\n\n    g_free (fullpath);\n}\n\n#ifdef __APPLE__\n\n/* static void */\n/* process_active_folder (SeafRepo *repo, const char *dir, */\n/*                        struct index_state *istate, GList *ignore_list) */\n/* { */\n/*     GList *add = NULL, *mod = NULL, *del = NULL; */\n/*     GList *p; */\n/*     char *path; */\n\n/*     /\\* Delete event will be triggered on the deleted dir too. *\\/ */\n/*     if (!g_file_test (dir, G_FILE_TEST_IS_DIR)) */\n/*         return; */\n\n/*     if (get_changed_paths_in_folder (repo, istate, dir, &add, &mod, &del) < 0) { */\n/*         seaf_warning (\"Failed to get changed paths under %s.\\n\", dir); */\n/*         return; */\n/*     } */\n\n/*     for (p = add; p; p = p->next) { */\n/*         path = p->data; */\n/*         process_active_path (repo, path, istate, ignore_list); */\n/*     } */\n\n/*     for (p = mod; p; p = p->next) { */\n/*         path = p->data; */\n/*         process_active_path (repo, path, istate, ignore_list); */\n/*     } */\n\n/*     g_list_free_full (add, g_free); */\n/*     g_list_free_full (mod, g_free); */\n/*     g_list_free_full (del, g_free); */\n/* } */\n\n#endif  /* __APPLE__ */\n\nstatic void\nupdate_path_sync_status (SeafRepo *repo, WTStatus *status,\n                         struct index_state *istate, GList *ignore_list)\n{\n    char *path;\n\n    while (1) {\n        pthread_mutex_lock (&status->ap_q_lock);\n        path = g_queue_pop_head (status->active_paths);\n        pthread_mutex_unlock (&status->ap_q_lock);\n\n        if (!path)\n            break;\n\n/* #ifdef __APPLE__ */\n/*         process_active_folder (repo, path, istate, ignore_list); */\n/* #else */\n        process_active_path (repo, path, istate, ignore_list);\n/* #endif */\n\n        g_free (path);\n    }\n}\n\n/* Excel first writes update to a temporary file and then rename the file to\n * xlsx. Unfortunately the temp file dosen't have specific pattern.\n * We can only ignore renaming from non xlsx file to xlsx file.\n */\nstatic gboolean\nignore_xlsx_update (const char *src_path, const char *dst_path)\n{\n    GPatternSpec *pattern = g_pattern_spec_new (\"*.xlsx\");\n    int ret = FALSE;\n\n    if (!g_pattern_match_string(pattern, src_path) &&\n        g_pattern_match_string(pattern, dst_path))\n        ret = TRUE;\n\n    g_pattern_spec_free (pattern);\n    return ret;\n}\n\nstatic gboolean\nis_seafile_backup_file (const char *path)\n{\n    GPatternSpec *pattern = g_pattern_spec_new (\"*.sbak\");\n    int ret = FALSE;\n\n    if (g_pattern_match_string(pattern, path))\n        ret = TRUE;\n\n    g_pattern_spec_free (pattern);\n    return ret;\n}\n\nstatic void\nhandle_rename (SeafRepo *repo, struct index_state *istate,\n               SeafileCrypt *crypt, GList *ignore_list,\n               LockedFileSet *fset,\n               WTEvent *event, GList **scanned_del_dirs,\n               gint64 *total_size)\n{\n    char *fullpath = NULL;\n    gboolean not_found, src_ignored, dst_ignored;\n\n    seaf_sync_manager_delete_active_path (seaf->sync_mgr, repo->id, event->path);\n\n    fullpath = g_build_path (\"/\", repo->worktree, event->new_path, NULL);\n    // Check whether the renamed file is a symbolic link.\n    if (seaf->ignore_symlinks && is_symlink(fullpath)) {\n        g_free (fullpath);\n        return;\n    }\n    g_free (fullpath);\n\n    if (!is_path_writable(repo->id,\n                          repo->is_readonly, event->path) ||\n        !is_path_writable(repo->id,\n                          repo->is_readonly, event->new_path)) {\n        seaf_debug (\"Rename: %s or %s is not writable, ignore.\\n\",\n                    event->path, event->new_path);\n        return;\n    }\n\n    if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,\n                                              repo->id, event->path)) {\n        seaf_debug (\"Rename: %s is locked on server, ignore.\\n\", event->path);\n        /* send_sync_error_notification (repo->id, NULL, event->path, */\n        /*                               SYNC_ERROR_ID_FILE_LOCKED); */\n        return;\n    }\n\n    if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,\n                                              repo->id, event->new_path)) {\n        seaf_debug (\"Rename: %s is locked on server, ignore.\\n\", event->new_path);\n        /* send_sync_error_notification (repo->id, NULL, event->new_path, */\n        /*                               SYNC_ERROR_ID_FILE_LOCKED); */\n        return;\n    }\n\n    src_ignored = check_full_path_ignore(repo->worktree, event->path, ignore_list);\n    dst_ignored = check_full_path_ignore(repo->worktree, event->new_path, ignore_list);\n\n    /* If the destination path is ignored, just remove the source path. */\n    if (dst_ignored) {\n        if (!src_ignored &&\n            !is_seafile_backup_file (event->new_path) &&\n            check_locked_file_before_remove (fset, event->path)) {\n            not_found = FALSE;\n            remove_from_index_with_prefix (istate, event->path, &not_found);\n            if (not_found)\n                scan_subtree_for_deletion (repo->id,\n                                           istate,\n                                           repo->worktree, event->path,\n                                           ignore_list, fset,\n                                           repo->is_readonly,\n                                           scanned_del_dirs,\n                                           repo->changeset);\n\n            remove_from_changeset (repo->changeset,\n                                   DIFF_STATUS_DELETED,\n                                   event->path,\n                                   FALSE,\n                                   NULL);\n        }\n        return;\n    }\n\n    /* Now the destination path is not ignored. */\n\n    if (!src_ignored && !ignore_xlsx_update (event->path, event->new_path) &&\n        check_locked_file_before_remove (fset, event->path)) {\n        not_found = FALSE;\n        rename_index_entries (istate, event->path, event->new_path, &not_found,\n                              NULL, NULL);\n        if (not_found)\n            scan_subtree_for_deletion (repo->id,\n                                       istate,\n                                       repo->worktree, event->path,\n                                       ignore_list, fset,\n                                       repo->is_readonly,\n                                       scanned_del_dirs,\n                                       repo->changeset);\n\n        /* Moving files out of a dir may make it empty. */\n        try_add_empty_parent_dir_entry_from_wt (repo->worktree,\n                                                istate,\n                                                ignore_list,\n                                                event->path);\n\n        add_to_changeset (repo->changeset,\n                          DIFF_STATUS_RENAMED,\n                          NULL,\n                          NULL,\n                          NULL,\n                          event->path,\n                          event->new_path);\n    }\n\n    AddOptions options;\n    memset (&options, 0, sizeof(options));\n    options.fset = fset;\n    options.is_repo_ro = repo->is_readonly;\n    options.changeset = repo->changeset;\n\n    /* We should always scan the destination to compare with the renamed\n     * index entries. For example, in the following case:\n     * 1. file a.txt is updated;\n     * 2. a.txt is moved to test/a.txt;\n     * If the two operations are executed in a batch, the updated content\n     * of a.txt won't be committed if we don't scan the destination, because\n     * when we process the update event, a.txt is already not in its original\n     * place.\n     */\n    add_recursive (repo->id, repo->version, repo->email,\n                   istate, repo->worktree, event->new_path,\n                   crypt, FALSE, ignore_list,\n                   total_size, NULL, &options);\n}\n\n#ifdef WIN32\n\ntypedef struct FindOfficeData {\n    const char *lock_file_name;\n    char *office_file_name;\n} FindOfficeData;\n\nstatic int\nfind_office_file_cb (wchar_t *parent,\n                     WIN32_FIND_DATAW *fdata,\n                     void *user_data,\n                     gboolean *stop)\n{\n    FindOfficeData *data = user_data;\n    const wchar_t *dname_w = fdata->cFileName;\n    wchar_t *lock_name_w = NULL;\n\n    if (wcslen(dname_w) < 2)\n        return 0;\n    if (wcsncmp (dname_w, L\"~$\", 2) == 0)\n        return 0;\n\n    lock_name_w = g_utf8_to_utf16 (data->lock_file_name,\n                                   -1, NULL, NULL, NULL);\n    /* Skip \"~$\" at the beginning. */\n    if (wcscmp (dname_w + 2, lock_name_w) == 0) {\n        data->office_file_name = g_utf16_to_utf8 (dname_w, -1, NULL, NULL, NULL);\n        *stop = TRUE;\n    }\n    g_free (lock_name_w);\n\n    return 0;\n}\n\nstatic gboolean\nfind_office_file_path (const char *worktree,\n                       const char *parent_dir,\n                       const char *lock_file_name,\n                       char **office_path)\n{\n    char *fullpath = NULL;\n    wchar_t *fullpath_w = NULL;\n    FindOfficeData data;\n    gboolean ret = FALSE;\n\n    fullpath = g_build_path (\"/\", worktree, parent_dir, NULL);\n    fullpath_w = win32_long_path (fullpath);\n\n    data.lock_file_name = lock_file_name;\n    data.office_file_name = NULL;\n\n    if (traverse_directory_win32 (fullpath_w, find_office_file_cb, &data) < 0) {\n        goto out;\n    }\n\n    if (data.office_file_name != NULL) {\n        *office_path = g_build_path (\"/\", parent_dir, data.office_file_name, NULL);\n        ret = TRUE;\n    }\n\nout:\n    g_free (fullpath);\n    g_free (fullpath_w);\n    return ret;\n}\n\n#elif defined __APPLE__\n\nstatic gboolean\nfind_office_file_path (const char *worktree,\n                       const char *parent_dir,\n                       const char *lock_file_name,\n                       char **office_path)\n{\n    GDir *dir = NULL;\n    GError *error = NULL;\n    char *fullpath = NULL;\n    const char *dname;\n    char *dname_nfc = NULL;\n    char *dname_skip_head = NULL;\n    gboolean ret = FALSE;\n\n    fullpath = g_build_path (\"/\", worktree, parent_dir, NULL);\n    dir = g_dir_open (fullpath, 0, &error);\n    if (error) {\n        seaf_warning (\"Failed to open dir %s: %s.\\n\", fullpath, error->message);\n        g_clear_error (&error);\n        g_free (fullpath);\n        return ret;\n    }\n\n    while ((dname = g_dir_read_name (dir)) != NULL) {\n        dname_nfc = g_utf8_normalize (dname, -1, G_NORMALIZE_NFC);\n        if (!dname_nfc)\n            continue;\n\n        if (g_utf8_strlen(dname_nfc, -1) < 2 || strncmp (dname_nfc, \"~$\", 2) == 0) {\n            g_free (dname_nfc);\n            continue;\n        }\n\n        dname_skip_head = g_utf8_find_next_char(g_utf8_find_next_char(dname_nfc, NULL), NULL);\n\n        if (g_strcmp0 (dname_skip_head, lock_file_name) == 0) {\n            *office_path = g_build_path (\"/\", parent_dir, dname_nfc, NULL);\n            ret = TRUE;\n            g_free (dname_nfc);\n            break;\n        }\n\n        g_free (dname_nfc);\n    }\n\n    g_free (fullpath);\n    g_dir_close (dir);\n    return ret;\n}\n\n#else\n\nstatic gboolean\nfind_office_file_path (const char *worktree,\n                       const char *parent_dir,\n                       const char *lock_file_name,\n                       gboolean is_wps,\n                       char **office_path)\n{\n    GDir *dir = NULL;\n    GError *error = NULL;\n    char *fullpath = NULL;\n    const char *dname;\n    char *dname_nfc = NULL;\n    char *dname_skip_head = NULL;\n    gboolean ret = FALSE;\n\n    fullpath = g_build_path (\"/\", worktree, parent_dir, NULL);\n    dir = g_dir_open (fullpath, 0, &error);\n    if (error) {\n        seaf_warning (\"Failed to open dir %s: %s.\\n\", fullpath, error->message);\n        g_clear_error (&error);\n        g_free (fullpath);\n        return ret;\n    }\n\n    while ((dname = g_dir_read_name (dir)) != NULL) {\n        dname_nfc = g_utf8_normalize (dname, -1, G_NORMALIZE_NFC);\n        if (!dname_nfc)\n            continue;\n\n        if (g_utf8_strlen(dname_nfc, -1) < 2 || strncmp (dname_nfc, \"~$\", 2) == 0 || strncmp(dname_nfc, \".~\", 2) == 0) {\n            g_free (dname_nfc);\n            continue;\n        }\n\n        if (is_wps) {\n            dname_skip_head = g_utf8_find_next_char(dname_nfc, NULL);\n            // WPS may replace the first one or two characters of the filename with \".～\" based on the length of the filename.\n            if (strcmp (dname_skip_head, lock_file_name) == 0) {\n                *office_path = g_build_path(\"/\", parent_dir, dname_nfc, NULL);\n                ret = TRUE;\n                g_free (dname_nfc);\n                break;\n            }\n            dname_skip_head = g_utf8_find_next_char(dname_skip_head, NULL);\n        } else {\n            dname_skip_head = g_utf8_find_next_char(g_utf8_find_next_char(dname_nfc, NULL), NULL);\n        }\n\n        if (g_strcmp0 (dname_skip_head, lock_file_name) == 0) {\n            *office_path = g_build_path (\"/\", parent_dir, dname_nfc, NULL);\n            ret = TRUE;\n            g_free (dname_nfc);\n            break;\n        }\n\n        g_free (dname_nfc);\n    }\n\n    g_free (fullpath);\n    g_dir_close (dir);\n    return ret;\n}\n\n#endif\n\n#if defined WIN32 || defined __APPLE__\n\nstatic gboolean\nis_office_lock_file (const char *worktree,\n                     const char *path,\n                     char **office_path)\n{\n    gboolean ret;\n\n    if (!g_regex_match (office_lock_pattern, path, 0, NULL))\n        return FALSE;\n\n    /* Replace ~$abc.docx with abc.docx */\n    *office_path = g_regex_replace (office_lock_pattern,\n                                    path, -1, 0,\n                                    \"\\\\1\", 0, NULL);\n\n    /* When the filename is long, sometimes the first two characters\n       in the filename will be directly replaced with ~$.\n       So if the office_path file doesn't exist, we have to match\n       against all filenames in this directory, to find the office\n       file's name.\n    */\n    char *fullpath = g_build_path (\"/\", worktree, *office_path, NULL);\n    if (seaf_util_exists (fullpath)) {\n        g_free (fullpath);\n        return TRUE;\n    }\n    g_free (fullpath);\n\n    char *lock_file_name = g_path_get_basename(*office_path);\n    char *parent_dir = g_path_get_dirname(*office_path);\n    if (strcmp(parent_dir, \".\") == 0) {\n        g_free (parent_dir);\n        parent_dir = g_strdup(\"\");\n    }\n    g_free (*office_path);\n    *office_path = NULL;\n\n    ret = find_office_file_path (worktree, parent_dir, lock_file_name,\n                                 office_path);\n\n    g_free (lock_file_name);\n    g_free (parent_dir);\n    return ret;\n}\n\n#else\n\nstatic gboolean\nis_office_lock_file (const char *worktree,\n                     const char *path,\n                     char **office_path)\n{\n    gboolean ret;\n    gboolean is_wps = FALSE;\n\n    if (!office_lock_pattern || !libre_office_lock_pattern || !wps_lock_pattern)\n        return FALSE;\n\n    if (g_regex_match (office_lock_pattern, path, 0, NULL)) {\n        /* Replace ~$abc.docx with abc.docx */\n        *office_path = g_regex_replace (office_lock_pattern,\n                                        path, -1, 0,\n                                        \"\\\\1\", 0, NULL);\n    } else if (g_regex_match (libre_office_lock_pattern, path, 0, NULL)) {\n        /* Replace .~lock.abc.docx# with abc.docx */\n        *office_path = g_regex_replace (libre_office_lock_pattern,\n                                        path, -1, 0,\n                                        \"\\\\1\", 0, NULL);\n    } else if (g_regex_match (wps_lock_pattern, path, 0, NULL)) {\n        /* Replace .~abc.docx with abc.docx */\n        *office_path = g_regex_replace (wps_lock_pattern,\n                                        path, -1, 0,\n                                        \"\\\\1\", 0, NULL);\n        is_wps = TRUE;\n    } else\n        return FALSE;\n\n    /* When the filename is long, sometimes the first two characters\n       in the filename will be directly replaced with ~$.\n       So if the office_path file doesn't exist, we have to match\n       against all filenames in this directory, to find the office\n       file's name.\n    */\n    char *fullpath = g_build_path (\"/\", worktree, *office_path, NULL);\n    if (seaf_util_exists (fullpath)) {\n        g_free (fullpath);\n        return TRUE;\n    }\n    g_free (fullpath);\n\n    char *lock_file_name = g_path_get_basename(*office_path);\n    char *parent_dir = g_path_get_dirname(*office_path);\n    if (strcmp(parent_dir, \".\") == 0) {\n        g_free (parent_dir);\n        parent_dir = g_strdup(\"\");\n    }\n    g_free (*office_path);\n    *office_path = NULL;\n\n    ret = find_office_file_path (worktree, parent_dir, lock_file_name,\n                                 is_wps, office_path);\n\n    g_free (lock_file_name);\n    g_free (parent_dir);\n    return ret;\n}\n\n#endif\n\ntypedef struct LockOfficeJob {\n    char repo_id[37];\n    char *path;\n    gboolean lock;              /* False if unlock */\n} LockOfficeJob;\n\nstatic void\nlock_office_job_free (LockOfficeJob *job)\n{\n    if (!job)\n        return;\n    g_free (job->path);\n    g_free (job);\n}\n\nstatic void\ndo_lock_office_file (LockOfficeJob *job)\n{\n    SeafRepo *repo;\n    char *fullpath = NULL;\n    SeafStat st;\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, job->repo_id);\n    if (!repo)\n        return;\n\n    fullpath = g_build_path (\"/\", repo->worktree, job->path, NULL);\n    if (seaf_stat (fullpath, &st) < 0 || !S_ISREG(st.st_mode)) {\n        g_free (fullpath);\n        return;\n    }\n    g_free (fullpath);\n\n    seaf_message (\"Auto lock file %s/%s\\n\", repo->name, job->path);\n\n    int status = seaf_filelock_manager_get_lock_status (seaf->filelock_mgr,\n                                                        repo->id, job->path);\n    if (status != FILE_NOT_LOCKED) {\n        return;\n    }\n\n    if (http_tx_manager_lock_file (seaf->http_tx_mgr,\n                                   repo->effective_host,\n                                   repo->use_fileserver_port,\n                                   repo->token,\n                                   repo->id,\n                                   job->path) < 0) {\n        seaf_warning (\"Failed to lock %s in repo %.8s on server.\\n\",\n                      job->path, repo->id);\n        return;\n    }\n\n    /* Mark file as locked locally so that the user can see the effect immediately. */\n    seaf_filelock_manager_mark_file_locked (seaf->filelock_mgr, repo->id, job->path, LOCKED_AUTO);\n}\n\nstatic void\ndo_unlock_office_file (LockOfficeJob *job)\n{\n    SeafRepo *repo;\n    char *fullpath = NULL;\n    SeafStat st;\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, job->repo_id);\n    if (!repo)\n        return;\n\n    fullpath = g_build_path (\"/\", repo->worktree, job->path, NULL);\n    if (seaf_stat (fullpath, &st) < 0 || !S_ISREG(st.st_mode)) {\n        g_free (fullpath);\n        return;\n    }\n    g_free (fullpath);\n\n    seaf_message (\"Auto unlock file %s/%s\\n\", repo->name, job->path);\n\n    int status = seaf_filelock_manager_get_lock_status (seaf->filelock_mgr,\n                                                        repo->id, job->path);\n    if (status != FILE_LOCKED_BY_ME_AUTO) {\n        return;\n    }\n\n    if (http_tx_manager_unlock_file (seaf->http_tx_mgr,\n                                     repo->effective_host,\n                                     repo->use_fileserver_port,\n                                     repo->token,\n                                     repo->id,\n                                     job->path) < 0) {\n        seaf_warning (\"Failed to unlock %s in repo %.8s on server.\\n\",\n                      job->path, repo->id);\n        return;\n    }\n\n    /* Mark file as unlocked locally so that the user can see the effect immediately. */\n    seaf_filelock_manager_mark_file_unlocked (seaf->filelock_mgr, repo->id, job->path);\n}\n\n#if 0\nstatic void\nunlock_closed_office_files ()\n{\n    GList *locked_files, *ptr;\n    SeafRepo *repo;\n    FileLockInfo *info;\n    LockOfficeJob *job;\n\n    locked_files = seaf_filelock_manager_get_auto_locked_files (seaf->filelock_mgr);\n    for (ptr = locked_files; ptr; ptr = ptr->next) {\n        info = ptr->data;\n\n        seaf_message (\"%s %s.\\n\", info->repo_id, info->path);\n\n        repo = seaf_repo_manager_get_repo (seaf->repo_mgr, info->repo_id);\n        if (!repo)\n            continue;\n\n        seaf_message (\"1\\n\");\n\n        if (!do_check_file_locked (info->path, repo->worktree, FALSE)) {\n            seaf_message (\"2\\n\");\n\n            job = g_new0 (LockOfficeJob, 1);\n            memcpy (job->repo_id, info->repo_id, 36);\n            job->path = g_strdup(info->path);\n            do_unlock_office_file (job);\n            lock_office_job_free (job);\n        }\n    }\n\n    g_list_free_full (locked_files, (GDestroyNotify)file_lock_info_free);\n}\n#endif\n\nstatic void *\nlock_office_file_worker (void *vdata)\n{\n    GAsyncQueue *queue = (GAsyncQueue *)vdata;\n    LockOfficeJob *job;\n\n    /* unlock_closed_office_files (); */\n\n    while (1) {\n        job = g_async_queue_pop (queue);\n        if (!job)\n            break;\n\n        if (job->lock)\n            do_lock_office_file (job);\n        else\n            do_unlock_office_file (job);\n\n        lock_office_job_free (job);\n    }\n\n    return NULL;\n}\n\nstatic void\nlock_office_file_on_server (SeafRepo *repo, const char *path)\n{\n    LockOfficeJob *job;\n    GAsyncQueue *queue = seaf->repo_mgr->priv->lock_office_job_queue;\n\n    if (!seaf_repo_manager_server_is_pro (seaf->repo_mgr, repo->server_url))\n        return;\n\n    job = g_new0 (LockOfficeJob, 1);\n    memcpy (job->repo_id, repo->id, 36);\n    job->path = g_strdup(path);\n    job->lock = TRUE;\n\n    g_async_queue_push (queue, job);\n}\n\nstatic void\nunlock_office_file_on_server (SeafRepo *repo, const char *path)\n{\n    LockOfficeJob *job;\n    GAsyncQueue *queue = seaf->repo_mgr->priv->lock_office_job_queue;\n\n    if (!seaf_repo_manager_server_is_pro (seaf->repo_mgr, repo->server_url))\n        return;\n\n    job = g_new0 (LockOfficeJob, 1);\n    memcpy (job->repo_id, repo->id, 36);\n    job->path = g_strdup(path);\n    job->lock = FALSE;\n\n    g_async_queue_push (queue, job);\n}\n\nstatic int\napply_worktree_changes_to_index (SeafRepo *repo, struct index_state *istate,\n                                 SeafileCrypt *crypt, GList *ignore_list,\n                                 LockedFileSet *fset, GList **event_list)\n{\n    WTStatus *status;\n    WTEvent *event, *next_event;\n    gboolean not_found;\n    char *office_path = NULL;\n\n    status = seaf_wt_monitor_get_worktree_status (seaf->wt_monitor, repo->id);\n    if (!status) {\n        seaf_warning (\"Can't find worktree status for repo %s(%.8s).\\n\",\n                      repo->name, repo->id);\n        return -1;\n    }\n\n    update_path_sync_status (repo, status, istate, ignore_list);\n\n    GList *scanned_dirs = NULL, *scanned_del_dirs = NULL;\n\n    WTEvent *last_event;\n\n    pthread_mutex_lock (&status->q_lock);\n    last_event = g_queue_peek_tail (status->event_q);\n    pthread_mutex_unlock (&status->q_lock);\n\n    if (!last_event) {\n        seaf_message (\"All events are processed for repo %s.\\n\", repo->id);\n        status->partial_commit = FALSE;\n        goto out;\n    }\n\n    gint64 total_size = 0;\n\n    while (1) {\n        pthread_mutex_lock (&status->q_lock);\n        event = g_queue_pop_head (status->event_q);\n        next_event = g_queue_peek_head (status->event_q);\n        pthread_mutex_unlock (&status->q_lock);\n        if (!event)\n            break;\n\n        WTEvent *copy = wt_event_new (event->ev_type, event->path, event->new_path);\n        *event_list = g_list_prepend (*event_list, copy);\n\n#ifdef WIN32\n        // If a file or dir is moved, REMOVED and ADDED event will be emitted by the kernel.\n        // When the kernel first emits the REMOVED event of the file or dir, and then emits the ADDED event of the file or dir,\n        // it indicates that this is a move event.\n        if (next_event && event->ev_type == WT_EVENT_DELETE) {\n            char *event_base_name = g_path_get_basename (event->path);\n            char *next_event_base_name  = g_path_get_basename (next_event->path);\n            if (next_event->ev_type == WT_EVENT_CREATE_OR_UPDATE && g_strcmp0(event_base_name, next_event_base_name) == 0) {\n                WTEvent *new_event =  wt_event_new (WT_EVENT_RENAME, event->path, next_event->path);\n\n                pthread_mutex_lock (&status->q_lock);\n                next_event = g_queue_pop_head (status->event_q);\n                pthread_mutex_unlock (&status->q_lock);\n\n                wt_event_free (event);\n                wt_event_free (next_event);\n\n                event = new_event;\n            }\n            g_free (event_base_name);\n            g_free (next_event_base_name);\n        }\n#endif\n\n        /* Scanned dirs list is used to avoid redundant scan of consecutive\n           CREATE_OR_UPDATE events. When we see other events, we should\n           clear the list. Otherwise in some cases we'll get wrong result.\n           For example, the following sequence (run with a script):\n           1. Add a dir with files\n           2. Delete the dir with files\n           3. Add back the same dir again.\n        */\n        if (event->ev_type != WT_EVENT_CREATE_OR_UPDATE) {\n            g_list_free_full (scanned_dirs, g_free);\n            scanned_dirs = NULL;\n        }\n\n        switch (event->ev_type) {\n        case WT_EVENT_CREATE_OR_UPDATE:\n            /* If consecutive CREATE_OR_UPDATE events present\n               in the event queue, only process the last one.\n            */\n            if (next_event &&\n                next_event->ev_type == event->ev_type &&\n                strcmp (next_event->path, event->path) == 0)\n                break;\n\n            /* CREATE_OR_UPDATE event tells us the exact path of changed file/dir.\n             * If the event path is not writable, we don't need to check the paths\n             * under the event path.\n             */\n            if (!is_path_writable(repo->id,\n                                  repo->is_readonly, event->path)) {\n                char *filename = g_path_get_basename (event->path);\n                if (seaf_repo_manager_is_ignored_hidden_file(filename)) {\n                    g_free (filename);\n                    break;\n                }\n                g_free (filename);\n\n                char *fullpath = g_build_path(PATH_SEPERATOR, repo->worktree, event->path, NULL);                \n                struct cache_entry *ce = index_name_exists(istate, event->path, strlen(event->path), 0);\n                SeafStat st;\n                if (ce != NULL &&\n                    seaf_stat (fullpath, &st) == 0 &&\n                    ce->ce_mtime.sec == st.st_mtime &&\n                    ce->ce_size == st.st_size) {\n                    g_free (fullpath);\n                    break;\n                }\n\n                send_file_sync_error_notification (repo->id, repo->name, event->path,\n                                                   SYNC_ERROR_ID_UPDATE_TO_READ_ONLY_REPO);\n                seaf_debug (\"%s is not writable, ignore.\\n\", event->path);\n\n                g_free (fullpath);\n                break;\n            }\n\n            office_path = NULL;\n            if (is_office_lock_file (repo->worktree, event->path, &office_path))\n                lock_office_file_on_server (repo, office_path);\n            g_free (office_path);\n\n            if (handle_add_files (repo, istate, crypt, ignore_list,\n                                  fset,\n                                  status, event,\n                                  &scanned_dirs, &total_size))\n                goto out;\n\n            break;\n        case WT_EVENT_SCAN_DIR:\n            if (handle_add_files (repo, istate, crypt, ignore_list,\n                                  fset,\n                                  status, event,\n                                  &scanned_dirs, &total_size))\n                goto out;\n\n            break;\n        case WT_EVENT_DELETE:\n            seaf_sync_manager_delete_active_path (seaf->sync_mgr,\n                                                  repo->id,\n                                                  event->path);\n\n            office_path = NULL;\n            if (is_office_lock_file (repo->worktree, event->path, &office_path))\n                unlock_office_file_on_server (repo, office_path);\n            g_free (office_path);\n\n            if (check_full_path_ignore(repo->worktree, event->path, ignore_list))\n                break;\n\n            if (!is_path_writable(repo->id,\n                                  repo->is_readonly, event->path)) {\n                seaf_debug (\"%s is not writable, ignore.\\n\", event->path);\n                break;\n            }\n\n            if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,\n                                                      repo->id, event->path)) {\n                seaf_debug (\"Delete: %s is locked on server, ignore.\\n\", event->path);\n                /* send_sync_error_notification (repo->id, NULL, event->path, */\n                /*                               SYNC_ERROR_ID_FILE_LOCKED); */\n                break;\n            }\n\n            if (check_locked_file_before_remove (fset, event->path)) {\n                not_found = FALSE;\n                remove_from_index_with_prefix (istate, event->path, &not_found);\n                if (not_found)\n                    scan_subtree_for_deletion (repo->id,\n                                               istate,\n                                               repo->worktree, event->path,\n                                               ignore_list, fset,\n                                               repo->is_readonly,\n                                               &scanned_del_dirs,\n                                               repo->changeset);\n\n                remove_from_changeset (repo->changeset,\n                                       DIFF_STATUS_DELETED,\n                                       event->path,\n                                       FALSE,\n                                       NULL);\n\n                try_add_empty_parent_dir_entry_from_wt (repo->worktree,\n                                                        istate,\n                                                        ignore_list,\n                                                        event->path);\n            }\n            break;\n        case WT_EVENT_RENAME:\n            handle_rename (repo, istate, crypt, ignore_list, fset, event, &scanned_del_dirs, &total_size);\n            break;\n        case WT_EVENT_ATTRIB:\n            if (!is_path_writable(repo->id,\n                                  repo->is_readonly, event->path)) {\n                seaf_debug (\"%s is not writable, ignore.\\n\", event->path);\n                break;\n            }\n            update_attributes (repo, istate, repo->worktree, event->path);\n            break;\n        case WT_EVENT_OVERFLOW:\n            seaf_warning (\"Kernel event queue overflowed, fall back to scan.\\n\");\n            scan_worktree_for_changes (istate, repo, crypt, ignore_list, fset);\n            break;\n        }\n\n        if (event == last_event) {\n            wt_event_free (event);\n            seaf_message (\"All events are processed for repo %s.\\n\", repo->id);\n            status->partial_commit = FALSE;\n            break;\n        } else\n            wt_event_free (event);\n    }\n\nout:\n    wt_status_unref (status);\n    string_list_free (scanned_dirs);\n    string_list_free (scanned_del_dirs);\n\n    return 0;\n}\n\nstatic int\nindex_add (SeafRepo *repo, struct index_state *istate,\n           gboolean is_force_commit, GList **event_list)\n{\n    SeafileCrypt *crypt = NULL;\n    LockedFileSet *fset = NULL;\n    GList *ignore_list = NULL;\n    int ret = 0;\n\n    if (repo->encrypted) {\n        crypt = seafile_crypt_new (repo->enc_version, repo->enc_key, repo->enc_iv);\n    }\n\n#if defined WIN32 || defined __APPLE__\n    if (repo->version > 0)\n        fset = seaf_repo_manager_get_locked_file_set (seaf->repo_mgr, repo->id);\n#endif\n\n    ignore_list = seaf_repo_load_ignore_files (repo->worktree);\n\n    if (!is_force_commit) {\n        if (apply_worktree_changes_to_index (repo, istate, crypt, ignore_list, fset, event_list) < 0) {\n            seaf_warning (\"Failed to apply worktree changes to index.\\n\");\n            ret = -1;\n        }\n    } else if (scan_worktree_for_changes (istate, repo, crypt, ignore_list, fset) < 0) {\n        seaf_warning (\"Failed to scan worktree for changes.\\n\");\n        ret = -1;\n    }\n\n    seaf_repo_free_ignore_files (ignore_list);\n\n#if defined WIN32 || defined __APPLE__\n    locked_file_set_free (fset);\n#endif\n\n    g_free (crypt);\n\n    return ret;\n}\n\nstatic int\ncommit_tree (SeafRepo *repo, const char *root_id,\n             const char *desc, char commit_id[])\n{\n    SeafCommit *commit;\n\n    commit = seaf_commit_new (NULL, repo->id, root_id,\n                              repo->email ? repo->email\n                              : \"unknown\",\n                              seaf->client_id,\n                              desc, 0);\n\n    commit->parent_id = g_strdup (repo->head->commit_id);\n\n    /* Add this computer's name to commit. */\n    commit->device_name = g_strdup(seaf->client_name);\n    commit->client_version = g_strdup (SEAFILE_CLIENT_VERSION);\n\n    seaf_repo_to_commit (repo, commit);\n\n    if (seaf_commit_manager_add_commit (seaf->commit_mgr, commit) < 0)\n        return -1;\n\n    seaf_branch_set_commit (repo->head, commit->commit_id);\n    seaf_branch_manager_update_branch (seaf->branch_mgr, repo->head);\n\n    strcpy (commit_id, commit->commit_id);\n    seaf_commit_unref (commit);\n\n    return 0;\n}\n\nstatic gboolean\ncompare_index_changeset (struct index_state *istate, ChangeSet *changeset)\n{\n    struct cache_entry *ce;\n    int i;\n    gboolean ret = TRUE;\n\n    for (i = 0; i < istate->cache_nr; ++i) {\n        ce = istate->cache[i];\n\n        if (!(ce->ce_flags & CE_ADDED))\n            continue;\n\n        seaf_message (\"checking %s in changeset.\\n\", ce->name);\n\n        if (!changeset_check_path (changeset, ce->name,\n                                   ce->sha1, ce->ce_mode, ce->ce_mtime.sec))\n            ret = FALSE;\n    }\n\n    return ret;\n}\n\n#if 0\nstatic int \nprint_index (struct index_state *istate)\n{\n    int i;\n    struct cache_entry *ce;\n    char id[41];\n    seaf_message (\"Totally %u entries in index, version %u.\\n\",\n                  istate->cache_nr, istate->version);\n    for (i = 0; i < istate->cache_nr; ++i) {\n        ce = istate->cache[i];\n        rawdata_to_hex (ce->sha1, id, 20);\n        seaf_message (\"%s, %s, %o, %\"G_GINT64_FORMAT\", %s, %\"G_GINT64_FORMAT\", %d\\n\",\n                      ce->name, id, ce->ce_mode, \n                      ce->ce_mtime.sec, ce->modifier, ce->ce_size, ce_stage(ce));\n    }\n\n    return 0;\n}\n#endif\n\nstatic void\nprint_event_log (const char *repo_name, const char *repo_id,\n                 const char *commit_id, GList *event_list)\n{\n    GList *ptr;\n    WTEvent *event;\n    char *name;\n    int i = 0;\n    GString *msg = g_string_new (\"\");\n\n    g_string_append_printf (msg, \"%s %s %s\\n\", repo_name, repo_id, commit_id);\n\n    for (ptr = event_list; ptr; ptr = ptr->next) {\n        event = ptr->data;\n        i++;\n        switch (event->ev_type) {\n        case WT_EVENT_CREATE_OR_UPDATE:\n            name = \"create/update\";\n            break;\n        case WT_EVENT_SCAN_DIR:\n            name = \"scan dir\";\n            break;\n        case WT_EVENT_DELETE:\n            name = \"delete\";\n            break;\n        case WT_EVENT_RENAME:\n            name = \"rename\";\n            break;\n        case WT_EVENT_OVERFLOW:\n            name = \"overflow\";\n            break;\n        default:\n            name = \"unknown\";\n        }\n        g_string_append_printf (msg, \"[event %d] %s, %s %s\\n\", i, name, event->path, event->new_path?event->new_path:\"\");\n    }\n\n    seafile_event_message (msg->str);\n\n    g_string_free (msg, TRUE);\n}\n\nchar *\nseaf_repo_index_commit (SeafRepo *repo,\n                        gboolean is_force_commit,\n                        gboolean is_initial_commit,\n                        GError **error)\n{\n    SeafRepoManager *mgr = repo->manager;\n    struct index_state istate;\n    char index_path[SEAF_PATH_MAX];\n    SeafCommit *head = NULL;\n    char *new_root_id = NULL;\n    char commit_id[41];\n    ChangeSet *changeset = NULL;\n    GList *diff_results = NULL;\n    char *desc = NULL;\n    char *ret = NULL;\n    GList *event_list = NULL;\n\n    if (!check_worktree_common (repo))\n        return NULL;\n\n    memset (&istate, 0, sizeof(istate));\n    snprintf (index_path, SEAF_PATH_MAX, \"%s/%s\", mgr->index_dir, repo->id);\n    if (read_index_from (&istate, index_path, repo->version) < 0) {\n        seaf_warning (\"Failed to load index.\\n\");\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, \"Internal data structure error\");\n        return NULL;\n    }\n\n    changeset = changeset_new (repo->id);\n    if (!changeset) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, \"Internal data structure error\");\n        goto out;\n    }\n\n    repo->changeset = changeset;\n\n    if (index_add (repo, &istate, is_force_commit, &event_list) < 0) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, \"Failed to add\");\n        goto out;\n    }\n\n    if (!istate.cache_changed)\n        goto out;\n\n    new_root_id = commit_tree_from_changeset (changeset);\n    if (!new_root_id) {\n        seaf_warning (\"Create commit tree failed for repo %s\\n\", repo->id);\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,\n                     \"Failed to generate commit\");\n        goto out;\n    }\n\n    head = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                           repo->id, repo->version,\n                                           repo->head->commit_id);\n    if (!head) {\n        seaf_warning (\"Head commit %s for repo %s not found\\n\",\n                      repo->head->commit_id, repo->id);\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, \"Data corrupt\");\n        goto out;\n    }\n\n    if (strcmp (head->root_id, new_root_id) == 0) {\n        seaf_message (\"No change to the fs tree of repo %s\\n\", repo->id);\n        /* If no file modification and addition are missing, and the new root\n         * id is the same as the old one, skip commiting.\n         */\n        if (!is_initial_commit && !is_force_commit)\n            compare_index_changeset (&istate, changeset);\n\n        update_index (&istate, index_path);\n        goto out;\n    }\n\n    diff_commit_roots (repo->id, repo->version, head->root_id, new_root_id, &diff_results, TRUE);\n    desc = diff_results_to_description (diff_results);\n    if (!desc)\n        desc = g_strdup(\"\");\n\n    if (commit_tree (repo, new_root_id, desc, commit_id) < 0) {\n        seaf_warning (\"Failed to save commit file\");\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, \"Internal error\");\n        goto out;\n    }\n\n    if (event_list) {\n        event_list = g_list_reverse (event_list);\n        print_event_log (repo->name, repo->id, commit_id, event_list);\n    }\n\n    if (update_index (&istate, index_path) < 0) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, \"Internal error\");\n        goto out;\n    }\n\n    g_signal_emit_by_name (seaf, \"repo-committed\", repo);\n\n    ret = g_strdup(commit_id);\n\nout:\n    if (event_list) {\n        g_list_free_full (event_list, (GDestroyNotify)wt_event_free);\n    }\n    g_free (desc);\n    seaf_commit_unref (head);\n    g_free (new_root_id);\n    changeset_free (changeset);\n    g_list_free_full (diff_results, (GDestroyNotify)diff_entry_free);\n    discard_index (&istate);\n    return ret;\n}\n\n#ifdef DEBUG_UNPACK_TREES\nstatic void\nprint_unpack_result (struct index_state *result)\n{\n\tint i;\n\tstruct cache_entry *ce;\n\n\tfor (i = 0; i < result->cache_nr; ++i) {\n\t\tce = result->cache[i];\n\t\tprintf (\"%s\\t\", ce->name);\n\t\tif (ce->ce_flags & CE_UPDATE)\n\t\t\tprintf (\"update/add\\n\");\n\t\telse if (ce->ce_flags & CE_WT_REMOVE)\n\t\t\tprintf (\"remove\\n\");\n\t\telse\n\t\t\tprintf (\"unchange\\n\");\n\t}\n}\n\nstatic int \nprint_index (struct index_state *istate)\n{\n    printf (\"Index timestamp: %d\\n\", istate->timestamp.sec);\n\n    int i;\n    struct cache_entry *ce;\n    char id[41];\n    printf (\"Totally %u entries in index.\\n\", istate->cache_nr);\n    for (i = 0; i < istate->cache_nr; ++i) {\n        ce = istate->cache[i];\n        rawdata_to_hex (ce->sha1, id, 20);\n        printf (\"%s\\t%s\\t%o\\t%d\\t%d\\n\", ce->name, id, ce->ce_mode, \n                ce->ce_ctime.sec, ce->ce_mtime.sec);\n    }\n\n    return 0;\n}\n#endif  /* DEBUG_UNPACK_TREES */\n\nGList *\nseaf_repo_diff (SeafRepo *repo, const char *old, const char *new, int fold_dir_diff, char **error)\n{\n    SeafCommit *c1 = NULL, *c2 = NULL;\n    int ret = 0;\n    GList *diff_entries = NULL;\n\n    c2 = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                         repo->id, repo->version,\n                                         new);\n    if (!c2) {\n        *error = g_strdup(\"Can't find new commit\");\n        return NULL;\n    }\n\n    if (old == NULL || old[0] == '\\0') {\n        if (c2->parent_id && c2->second_parent_id) {\n            ret = diff_merge (c2, &diff_entries, fold_dir_diff);\n            seaf_commit_unref (c2);\n            if (ret < 0) {\n                *error = g_strdup(\"Failed to do diff\");\n                g_list_free_full (diff_entries, (GDestroyNotify)diff_entry_free);\n                return NULL;\n            }\n            return diff_entries;\n        }\n\n        if (!c2->parent_id) {\n            seaf_commit_unref (c2);\n            return NULL;\n        }\n        c1 = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                             repo->id, repo->version,\n                                             c2->parent_id);\n    } else {\n        c1 = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                             repo->id, repo->version, old);\n    }\n\n    if (!c1) {\n        *error = g_strdup(\"Can't find old commit\");\n        seaf_commit_unref (c2);\n        return NULL;\n    }\n\n    /* do diff */\n    ret = diff_commits (c1, c2, &diff_entries, fold_dir_diff);\n    if (ret < 0) {\n        g_list_free_full (diff_entries, (GDestroyNotify)diff_entry_free);\n        diff_entries = NULL;\n        *error = g_strdup(\"Failed to do diff\");\n    }\n\n    seaf_commit_unref (c1);\n    seaf_commit_unref (c2);\n\n    return diff_entries;\n}\n\nint\ncheckout_empty_dir (const char *worktree,\n                    const char *name,\n                    gint64 mtime,\n                    struct cache_entry *ce)\n{\n    char *path;\n\n    path = build_checkout_path (worktree, name, strlen(name));\n\n    if (!path)\n        return FETCH_CHECKOUT_FAILED;\n\n    if (!seaf_util_exists (path) && seaf_util_mkdir (path, 0777) < 0) {\n        seaf_warning (\"Failed to create empty dir %s in checkout.\\n\", path);\n        g_free (path);\n        return FETCH_CHECKOUT_FAILED;\n    }\n\n    if (mtime != 0 && seaf_set_file_time (path, mtime) < 0) {\n        seaf_warning (\"Failed to set mtime for %s.\\n\", path);\n    }\n\n    SeafStat st;\n    seaf_stat (path, &st);\n    fill_stat_cache_info (ce, &st);\n\n    g_free (path);\n    return FETCH_CHECKOUT_SUCCESS;\n}\n\nstatic struct cache_entry *\ncache_entry_from_diff_entry (DiffEntry *de)\n{\n    int size, namelen;\n    struct cache_entry *ce;\n\n    namelen = strlen(de->name);\n    size = cache_entry_size(namelen);\n    ce = calloc(1, size);\n    memcpy(ce->name, de->name, namelen);\n    ce->ce_flags = namelen;\n\n    memcpy (ce->sha1, de->sha1, 20);\n    ce->modifier = g_strdup(de->modifier);\n    ce->ce_size = de->size;\n    ce->ce_mtime.sec = de->mtime;\n\n    if (S_ISREG(de->mode))\n        ce->ce_mode = create_ce_mode (de->mode);\n    else\n        ce->ce_mode = S_IFDIR;\n\n    return ce;\n}\n\n#define UPDATE_CACHE_SIZE_LIMIT 100 * (1 << 20) /* 100MB */\n\ntypedef struct FileTxData {\n    char repo_id[37];\n    int repo_version;\n    SeafileCrypt *crypt;\n    HttpTxTask *http_task;\n    char conflict_head_id[41];\n    GAsyncQueue *finished_tasks;\n\n    const char *worktree;\n    LockedFileSet *fset;\n} FileTxData;\n\ntypedef struct FileTxTask {\n    char *path;\n    struct cache_entry *ce;\n    DiffEntry *de;\n    gboolean new_ce;\n    gboolean skip_fetch;\n\n    int result;\n    gboolean no_checkout;\n    gboolean force_conflict;\n} FileTxTask;\n\nstatic void\nfile_tx_task_free (FileTxTask *task)\n{\n    if (!task)\n        return;\n\n    g_free (task->path);\n    g_free (task);\n}\n\nstruct _UpdateAux {\n    int fd;\n    SeafileCrypt *crypt;\n    // The older version client might fail to load the repo key, resulting in blocks being encrypted with an all-zero enc key.\n    // When decryption fails, an additional attempt is made to decrypt using the all-zero key.\n    SeafileCrypt *zero_crypt;\n    char *repo_id;\n    char *content;\n    int size;\n    void *user_data;\n};\ntypedef struct _UpdateAux UpdateAux;\n\nstatic int\nfill_block (void *contents, size_t realsize, void *userp)\n{\n    UpdateAux *aux = userp;\n    int rc = 0;\n    int ret = realsize;\n    char *dec_out = NULL;\n    int dec_out_len = -1;\n\n    if (aux->crypt) {\n        rc = seafile_decrypt (&dec_out, &dec_out_len, contents, realsize, aux->crypt);\n        if (rc != 0) {\n            rc = seafile_decrypt(&dec_out, &dec_out_len, contents, realsize, aux->zero_crypt);\n            if (rc != 0) {\n                seaf_warning (\"Decrypt block failed.\\n\");\n                return -1;\n            } else {\n                save_repo_property (seaf->repo_mgr, aux->repo_id, REPO_PROP_EMPTY_ENC_KEY, \"true\");\n            }\n        }\n\n        ret = writen (aux->fd, dec_out, dec_out_len);\n    } else {\n        ret = writen (aux->fd, contents, realsize);\n    }\n\n    g_free (dec_out);\n\n    return ret;\n}\n\nstatic size_t\nupdate_block_cb (void *contents, size_t size, size_t nmemb, void *userp)\n{\n    size_t realsize = size * nmemb;\n    UpdateAux *aux = userp;\n    HttpTxTask *task = aux->user_data;\n    int ret = realsize;\n\n    if (task) {\n        if (task->state == HTTP_TASK_STATE_CANCELED || task->all_stop) {\n            return 0;\n        }\n    }\n\n    aux->size += realsize;\n    if (fill_block (contents, realsize, aux) < 0) {\n        return 0;\n    }\n\n    /* Update global transferred bytes. */\n    g_atomic_int_add (&(seaf->sync_mgr->recv_bytes), realsize);\n\n    /* Update transferred bytes for this task */\n    if (task)\n        g_atomic_int_add (&task->tx_bytes, realsize);\n\n    /* If uploaded bytes exceeds the limit, wait until the counter\n     * is reset. We check the counter every 100 milliseconds, so we\n     * can waste up to 100 milliseconds without sending data after\n     * the counter is reset.\n     */\n    while (1) {\n        gint sent = g_atomic_int_get(&(seaf->sync_mgr->recv_bytes));\n        if (seaf->sync_mgr->download_limit > 0 &&\n            sent > seaf->sync_mgr->download_limit)\n            /* 100 milliseconds */\n            g_usleep (100000);\n        else\n            break;\n    }\n\n    return ret;\n}\n\nstatic size_t\nupdate_enc_block_cb (void *contents, size_t size, size_t nmemb, void *userp)\n{\n    size_t realsize = size * nmemb;\n    UpdateAux *aux = userp;\n    HttpTxTask *task = aux->user_data;\n    int ret = realsize;\n\n    if (task) {\n        if (task->state == HTTP_TASK_STATE_CANCELED || task->all_stop) {\n            return 0;\n        }\n    }\n\n    aux->content = g_realloc (aux->content, aux->size + realsize);\n    if (!aux->content) {\n        seaf_warning (\"Not enough memory.\\n\");\n        return 0;\n    }\n    memcpy (aux->content + aux->size, contents, realsize);\n    aux->size += realsize;\n\n    /* Update global transferred bytes. */\n    g_atomic_int_add (&(seaf->sync_mgr->recv_bytes), realsize);\n\n    /* Update transferred bytes for this task */\n    if (task)\n        g_atomic_int_add (&task->tx_bytes, realsize);\n\n    /* If uploaded bytes exceeds the limit, wait until the counter\n     * is reset. We check the counter every 100 milliseconds, so we\n     * can waste up to 100 milliseconds without sending data after\n     * the counter is reset.\n     */\n    while (1) {\n        gint sent = g_atomic_int_get(&(seaf->sync_mgr->recv_bytes));\n        if (seaf->sync_mgr->download_limit > 0 &&\n            sent > seaf->sync_mgr->download_limit)\n            /* 100 milliseconds */\n            g_usleep (100000);\n        else\n            break;\n    }\n\n    return ret;\n}\n\nstatic int\ncheckout_block_cb (const char *repo_id, const char *block_id, int fd, SeafileCrypt *crypt, CheckoutBlockAux *user_data)\n{\n    HttpTxTask *task = user_data->task;\n    int ret = 0;\n    int error_id = SYNC_ERROR_ID_NO_ERROR;\n    SeafileCrypt *zero_crypt = NULL;\n    UpdateAux aux = {0};\n    aux.fd = fd;\n    aux.crypt = crypt;\n    aux.repo_id = repo_id;\n    aux.user_data = task;\n    if (crypt) {\n        unsigned char enc_key[32], enc_iv[16];\n        memset (enc_key, 0, sizeof(enc_key)); \n        memset (enc_iv, 0, sizeof(enc_iv)); \n        zero_crypt = seafile_crypt_new (crypt->version, enc_key, enc_iv);\n        aux.zero_crypt = zero_crypt;\n        if (http_tx_manager_get_block(seaf->http_tx_mgr, user_data->repo_id,\n                                      block_id, user_data->host,\n                                      user_data->token, user_data->use_fileserver_port,\n                                      &error_id,\n                                      update_enc_block_cb, &aux) < 0) {\n            if (task->state == HTTP_TASK_STATE_CANCELED) {\n                ret = -1;\n                goto out;\n            }\n            if (task->error == SYNC_ERROR_ID_NO_ERROR) {\n                task->error = error_id;\n            }\n            ret = -1;\n            seaf_warning (\"Failed to get block %s from server.\\n\",\n                          block_id);\n            goto out;\n        }\n        if (fill_block(aux.content, aux.size, &aux) < 0) {\n            ret = -1;\n            seaf_warning (\"Failed to fill block %s.\\n\",\n                          block_id);\n            goto out;\n        }\n    } else {\n        if (http_tx_manager_get_block(seaf->http_tx_mgr, user_data->repo_id,\n                                      block_id, user_data->host,\n                                      user_data->token, user_data->use_fileserver_port,\n                                      &error_id,\n                                      update_block_cb, &aux) < 0) {\n            if (task->state == HTTP_TASK_STATE_CANCELED) {\n                ret = -1;\n                goto out;\n            }\n            if (task->error == SYNC_ERROR_ID_NO_ERROR) {\n                task->error = error_id;\n            }\n            ret = -1;\n            seaf_warning (\"Failed to get block %s from server.\\n\",\n                          block_id);\n            goto out;\n        }\n    }\n    if (task)\n        task->done_download += aux.size;\nout:\n    if (zero_crypt)\n        g_free (zero_crypt);\n    g_free (aux.content);\n    return ret;\n}\n\nstatic gboolean\ncheck_path_conflict (const char *path, char **orig_path)\n{\n    gboolean is_conflict = FALSE;\n    GError *error = NULL;\n\n    is_conflict = g_regex_match (conflict_pattern, path, 0, NULL);\n    if (is_conflict) {\n        *orig_path = g_regex_replace_literal (conflict_pattern, path, -1,\n                                              0, \"\", 0, &error);\n        if (!*orig_path)\n            is_conflict = FALSE;\n    }\n\n    return is_conflict;\n}\n\n/*\nstatic void\ncleanup_file_blocks_http (HttpTxTask *task, const char *file_id)\n{\n    Seafile *file;\n    int i;\n    char *block_id;\n    int *pcnt;\n\n    file = seaf_fs_manager_get_seafile (seaf->fs_mgr,\n                                        task->repo_id, task->repo_version,\n                                        file_id);\n    if (!file) {\n        seaf_warning (\"Failed to load seafile object %s:%s\\n\",\n                      task->repo_id, file_id);\n        return;\n    }\n\n    for (i = 0; i < file->n_blocks; ++i) {\n        block_id = file->blk_sha1s[i];\n\n        pthread_mutex_lock (&task->ref_cnt_lock);\n\n        pcnt = g_hash_table_lookup (task->blk_ref_cnts, block_id);\n        if (pcnt) {\n            --(*pcnt);\n            if (*pcnt > 0) {\n                pthread_mutex_unlock (&task->ref_cnt_lock);\n                continue;\n            }\n        }\n\n        seaf_block_manager_remove_block (seaf->block_mgr,\n                                         task->repo_id, task->repo_version,\n                                         block_id);\n        g_hash_table_remove (task->blk_ref_cnts, block_id);\n\n        pthread_mutex_unlock (&task->ref_cnt_lock);\n    }\n\n    seafile_unref (file);\n}\n*/\n\n// just skip all downloaded blocks.\nstatic void\ncalculate_block_offset(Seafile *file, gint64 *block_map, gint64 *skip_buffer_size, int *block_offset, gint64 requested_offset)\n{\n    gint64 offset = 0;\n    int i = 0;\n    *skip_buffer_size = 0;\n    for (; i < file->n_blocks ; ++i) {\n        offset += block_map[i];\n        if (offset > requested_offset)\n            break;\n        *skip_buffer_size = offset;\n    }\n    *block_offset = i;\n}\n\n#define CACHE_BLOCK_MAP_THRESHOLD (2 << 23) /* 8MB */\n\nstatic void\ncheck_and_get_block_offset (SeafRepoManager *mgr, CheckoutBlockAux *aux,\n                            const char *repo_id, int version,\n                            const char *file_id, const char *file_path,\n                            gint64 *skip_buffer_size, int *block_offset)\n{\n    Seafile *seafile = NULL;\n    SeafStat st;\n    int n_blocks = 0;\n    gint64 *block_map = NULL;\n    char *tmp_path = NULL;\n    gboolean path_exists = FALSE;\n\n    seafile = seaf_fs_manager_get_seafile (seaf->fs_mgr, repo_id, version, file_id);\n    if (!seafile) {\n        return;\n    }\n\n    if (seafile->file_size <= CACHE_BLOCK_MAP_THRESHOLD) {\n        goto out;\n    }\n\n    tmp_path = g_strconcat (file_path, SEAF_TMP_EXT, NULL);\n\n    path_exists = (seaf_stat (tmp_path, &st) == 0);\n    if (!path_exists) {\n        goto out;\n    }\n\n    char file_id_attr[41];\n    ssize_t len;\n\n    len = seaf_getxattr (tmp_path, SEAFILE_FILE_ID_ATTR,\n                         file_id_attr, sizeof(file_id_attr));\n    if (len < 0) {\n        goto out;\n    }\n    // File has been changed on server.\n    if (g_strcmp0 (file_id, file_id_attr) != 0) {\n        goto out;\n    }\n\n    pthread_rwlock_rdlock (&mgr->priv->block_map_lock);\n    block_map = g_hash_table_lookup (mgr->priv->block_map_cache_table, file_id);\n    pthread_rwlock_unlock (&mgr->priv->block_map_lock);\n    if (!block_map) {\n        if (http_tx_manager_get_file_block_map (seaf->http_tx_mgr,\n                                                repo_id,\n                                                file_id,\n                                                aux->host,\n                                                aux->token,\n                                                aux->use_fileserver_port,\n                                                &block_map,\n                                                &n_blocks) == 0) {\n            if (n_blocks != seafile->n_blocks) {\n                seaf_warning (\"Block number return from server does not match\"\n                              \"seafile object. File-id is %s.\"\n                              \"Returned %d, expect %d\\n\",\n                              seafile->file_id, n_blocks, seafile->n_blocks);\n            } else {\n                pthread_rwlock_wrlock (&mgr->priv->block_map_lock);\n                g_hash_table_replace (mgr->priv->block_map_cache_table, g_strdup(file_id), block_map);\n                pthread_rwlock_unlock (&mgr->priv->block_map_lock);\n            }\n        }\n\n    }\n    if (block_map) {\n        calculate_block_offset(seafile, block_map, skip_buffer_size, block_offset, st.st_size);\n    }\nout:\n    g_free (tmp_path);\n    seafile_unref (seafile);\n}\n\nint\nseaf_repo_manager_checkout_file (SeafRepo *repo,\n                                 const char *file_id,\n                                 const char *file_path,\n                                 guint32 mode,\n                                 guint64 mtime,\n                                 SeafileCrypt *crypt,\n                                 const char *in_repo_path,\n                                 const char *conflict_head_id,\n                                 gboolean force_conflict,\n                                 gboolean *conflicted,\n                                 const char *email,\n                                 CheckoutBlockAux *aux)\n{\n    int ret;\n    gint64 skip_buffer_size = 0;\n    int block_offset = 0;\n\n    if (!crypt)\n        check_and_get_block_offset (seaf->repo_mgr, aux, repo->id, repo->version,\n                                    file_id, file_path, &skip_buffer_size, &block_offset);\n\n    FileCheckoutData file_data = {0}; \n    int error_id = FETCH_CHECKOUT_SUCCESS;\n    file_data.repo_id = repo->id;\n    file_data.version = repo->version;\n    file_data.file_id = file_id; \n    file_data.file_path = file_path;\n    file_data.mode = mode;\n    file_data.mtime = mtime;\n    file_data.crypt = crypt;\n    file_data.in_repo_path = in_repo_path;\n    file_data.conflict_head_id = conflict_head_id;\n    file_data.force_conflict = force_conflict;\n    file_data.conflicted = conflicted;\n    file_data.email = email;\n    file_data.skip_buffer_size = skip_buffer_size;\n    file_data.block_offset = block_offset;\n\n    ret = seaf_fs_manager_checkout_file (seaf->fs_mgr,\n                                         &file_data,\n                                         &error_id,\n                                         checkout_block_cb,\n                                         aux);\n    return ret;\n}\n\nstatic int\ncheckout_file_http (FileTxData *data,\n                    FileTxTask *file_task)\n{\n    char *repo_id = data->repo_id;\n    int repo_version = data->repo_version;\n    struct cache_entry *ce = file_task->ce;\n    DiffEntry *de = file_task->de;\n    SeafileCrypt *crypt = data->crypt;\n    gboolean no_checkout = file_task->no_checkout;\n    gboolean force_conflict = file_task->force_conflict;\n    const char *worktree = data->worktree;\n    const char *conflict_head_id = data->conflict_head_id;\n    LockedFileSet *fset = data->fset;\n    HttpTxTask *http_task = data->http_task;\n    SeafStat st;\n    char file_id[41];\n    gboolean locked_on_server = FALSE;\n\n    if (no_checkout)\n        return FETCH_CHECKOUT_SUCCESS;\n\n    if (should_ignore_on_checkout (de->name, NULL))\n        return FETCH_CHECKOUT_SUCCESS;\n\n    rawdata_to_hex (de->sha1, file_id, 20);\n\n    locked_on_server = seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,\n                                                             repo_id, de->name);\n\n#if defined WIN32 || defined __APPLE__\n    if (do_check_file_locked (de->name, worktree, locked_on_server)) {\n        if (!locked_file_set_lookup (fset, de->name))\n            send_file_sync_error_notification (repo_id, NULL, de->name,\n                                               SYNC_ERROR_ID_FILE_LOCKED_BY_APP);\n\n        locked_file_set_add_update (fset, de->name, LOCKED_OP_UPDATE,\n                                    ce->ce_mtime.sec, file_id);\n        /* Stay in syncing status if the file is locked. */\n\n        return FETCH_CHECKOUT_SUCCESS;\n    }\n#endif\n\n    /* Temporarily unlock the file if it's locked on server, so that the client\n     * itself can write to it. \n     */\n    if (locked_on_server)\n        seaf_filelock_manager_unlock_wt_file (seaf->filelock_mgr,\n                                              repo_id, de->name);\n\n    /* then checkout the file. */\n    gboolean conflicted = FALSE;\n\n    CheckoutBlockAux *aux = g_new0 (CheckoutBlockAux, 1);\n    aux->repo_id = g_strdup (repo_id);\n    aux->host = g_strdup (http_task->host);\n    aux->token = g_strdup (http_task->token);\n    aux->use_fileserver_port = http_task->use_fileserver_port;\n    aux->task = http_task;\n\n    gint64 skip_buffer_size = 0;\n    int block_offset = 0;\n    if (!crypt)\n        check_and_get_block_offset (seaf->repo_mgr, aux, repo_id, repo_version,\n                                    file_id, file_task->path, &skip_buffer_size, &block_offset);\n    if (skip_buffer_size > 0)\n        http_task->done_download += skip_buffer_size;\n\n    FileCheckoutData file_data = {0}; \n    int error_id = FETCH_CHECKOUT_SUCCESS;\n    file_data.repo_id = repo_id;\n    file_data.version = repo_version;\n    file_data.file_id = file_id; \n    file_data.file_path = file_task->path;\n    file_data.mode = de->mode;\n    file_data.mtime = de->mtime;\n    file_data.crypt = crypt;\n    file_data.in_repo_path = de->name;\n    file_data.conflict_head_id = conflict_head_id;\n    file_data.force_conflict = force_conflict;\n    file_data.conflicted = &conflicted;\n    if (http_task->username)\n        file_data.email = http_task->username;\n    else\n        file_data.email = http_task->email;\n    file_data.skip_buffer_size = skip_buffer_size;\n    file_data.block_offset = block_offset;\n\n    if (seaf_fs_manager_checkout_file (seaf->fs_mgr,\n                                       &file_data,\n                                       &error_id,\n                                       checkout_block_cb,\n                                       aux) < 0) {\n        seaf_warning (\"Failed to checkout file %s.\\n\", file_task->path);\n\n        if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,\n                                                  repo_id, de->name))\n            seaf_filelock_manager_lock_wt_file (seaf->filelock_mgr,\n                                                repo_id, de->name);\n\n        free_checkout_block_aux (aux);\n        return error_id;\n    }\n    free_checkout_block_aux (aux);\n\n    if (locked_on_server)\n        seaf_filelock_manager_lock_wt_file (seaf->filelock_mgr,\n                                            repo_id, de->name);\n\n    // cleanup_file_blocks_http (http_task, file_id);\n\n    if (conflicted) {\n        send_file_sync_error_notification (repo_id, NULL, de->name, SYNC_ERROR_ID_CONFLICT);\n    } else if (!http_task->is_clone) {\n        char *orig_path = NULL;\n        if (check_path_conflict (de->name, &orig_path))\n            send_file_sync_error_notification (repo_id, NULL, orig_path, SYNC_ERROR_ID_CONFLICT);\n        g_free (orig_path);\n    }\n\n    /* finally fill cache_entry info */\n    /* Only update index if we checked out the file without any error\n     * or conflicts. The ctime of the entry will remain 0 if error.\n     */\n    seaf_stat (file_task->path, &st);\n    fill_stat_cache_info (ce, &st);\n\n    return FETCH_CHECKOUT_SUCCESS;\n}\n\nstatic void\nfetch_file_thread_func (gpointer data, gpointer user_data)\n{\n    FileTxTask *task = data;\n    FileTxData *tx_data = user_data;\n    GAsyncQueue *finished_tasks = tx_data->finished_tasks;\n    DiffEntry *de = task->de;\n    char *repo_id = tx_data->repo_id;\n    char file_id[41];\n    gboolean is_clone = tx_data->http_task->is_clone;\n    int repo_version = tx_data->repo_version;\n    struct cache_entry *ce = task->ce;\n    SeafileCrypt *crypt = tx_data->crypt;\n    char *path = task->path;\n    HttpTxTask *http_task = tx_data->http_task;\n    SeafStat st;\n    gboolean path_exists = FALSE;\n    int rc = FETCH_CHECKOUT_SUCCESS;\n\n    if (task->skip_fetch)\n        goto out;\n\n    rawdata_to_hex (de->sha1, file_id, 20);\n\n    path_exists = (seaf_stat (path, &st) == 0);\n\n    /* seaf_message (\"Download file %s for repo %s\\n\", de->name, repo_id); */\n\n    if (!is_clone)\n        seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                              repo_id,\n                                              de->name,\n                                              de->mode,\n                                              SYNC_STATUS_SYNCING,\n                                              TRUE);\n\n    if (path_exists && S_ISREG(st.st_mode)) {\n        if (st.st_mtime == ce->ce_mtime.sec) {\n            /* Worktree and index are consistent. */\n            if (memcmp (de->sha1, ce->sha1, 20) == 0) {\n                seaf_debug (\"wt and index are consistent. no need to checkout.\\n\");\n                task->no_checkout = TRUE;\n\n                /* Update mode if necessary. */\n                if (de->mode != ce->ce_mode) {\n#ifndef WIN32\n                    chmod (path, de->mode & ~S_IFMT);\n                    ce->ce_mode = de->mode;\n#endif\n                }\n\n                /* Update mtime if necessary. */\n                if (de->mtime != ce->ce_mtime.sec) {\n                    seaf_set_file_time (path, de->mtime);\n                    ce->ce_mtime.sec = de->mtime;\n                }\n\n                fill_stat_cache_info (ce, &st);\n\n                goto out;\n            }\n            /* otherwise we have to checkout the file. */\n        } else {\n            if (compare_file_content (path, &st, de->sha1, crypt, repo_version) == 0) {\n                /* This happens after the worktree file was updated,\n                 * but the index was not. Just need to update the index.\n                 */\n                seaf_debug (\"update index only.\\n\");\n                task->no_checkout = TRUE;\n                fill_stat_cache_info (ce, &st);\n                goto out;\n            } else {\n                /* Conflict. The worktree file was updated by the user. */\n                seaf_message (\"File %s is updated by user. \"\n                              \"Will checkout to conflict file later.\\n\", path);\n                task->force_conflict = TRUE;\n            }\n        }\n    }\n\n    /* Download the blocks of this file. */\n    rc = checkout_file_http (tx_data, task);\n    if (http_task->state == HTTP_TASK_STATE_CANCELED) {\n        rc = FETCH_CHECKOUT_CANCELED;\n        seaf_debug (\"Transfer canceled.\\n\");\n    } else if (rc == FETCH_CHECKOUT_TRANSFER_ERROR) {\n        rc = FETCH_CHECKOUT_TRANSFER_ERROR;\n        seaf_warning (\"Transfer failed.\\n\");\n    }\n\nout:\n    task->result = rc;\n    g_async_queue_push (finished_tasks, task);\n}\n\nstatic gboolean\ncheck_case_conflict (const char *path1, const char *path2, char **conflict_path)\n{\n    if (!path1 || !path2 || g_strcmp0 (path1, \".\") == 0 || g_strcmp0 (path2, \".\") == 0) {\n        return FALSE;\n    }\n    // no case conflict\n    if (strcasecmp (path1, path2) != 0) {\n        return FALSE;\n    }\n\n    char *base_name1 = g_path_get_basename (path1);\n    char *base_name2 = g_path_get_basename (path2);\n    char *parent_dir1 = g_path_get_dirname (path1);\n    char *parent_dir2 = g_path_get_dirname (path2);\n    gboolean ret = FALSE;\n\n    // case conflict\n    if (strcmp (base_name1, base_name2) != 0) {\n        *conflict_path = g_strdup (path2); \n        ret = TRUE;\n        goto out;\n    }\n    // find conflict path\n    ret = check_case_conflict (parent_dir1, parent_dir2, conflict_path);\nout:\n    g_free (base_name1);\n    g_free (base_name2);\n    g_free (parent_dir1);\n    g_free (parent_dir2);\n    return ret;\n}\n\n// Since file creation is asynchronous, the file may not have been created locally at the time of checking for case conflicts, \n// so an additional check for the name of the file being created is required.\nstatic gboolean\nis_adding_files_case_conflict (GList **adding_files, const char *name, char **conflict_path,\n                               GHashTable *no_case_conflict_hash)\n{\n    if (g_hash_table_lookup (no_case_conflict_hash, name)) {\n        return FALSE;\n    }\n#if defined WIN32 || defined __APPLE__\n    GList *ptr;\n    SeafStat st;\n\n    ptr = *adding_files;\n\n    char *path;\n    for (; ptr; ptr = ptr->next) {\n        path = ptr->data;\n        if (check_case_conflict (path, name, conflict_path)){\n            return TRUE;\n        }\n    }\n\n    return FALSE;\n#else\n    return FALSE;\n#endif\n}\n\nstatic int\nschedule_file_fetch (GThreadPool *tpool,\n                     const char *repo_id,\n                     const char *repo_name,\n                     const char *worktree,\n                     struct index_state *istate,\n                     DiffEntry *de,\n                     const char *conflict_head_id,\n                     LockedFileSet *fset,\n                     GHashTable *pending_tasks,\n                     GHashTable *case_conflict_hash,\n                     GHashTable *no_case_conflict_hash,\n                     GList **adding_files)\n{\n    struct cache_entry *ce;\n    gboolean new_ce = FALSE;\n    gboolean skip_fetch = FALSE;\n    char *path = NULL;\n    FileTxTask *file_task;\n    gboolean no_checkout = FALSE;\n    char *conflict_path = NULL;\n\n    ce = index_name_exists (istate, de->name, strlen(de->name), 0);\n    if (!ce) {\n        ce = cache_entry_from_diff_entry (de);\n        new_ce = TRUE;\n    }\n\n    IgnoreReason reason;\n    if (should_ignore_on_checkout (de->name, &reason)) {\n        seaf_message (\"Path %s is invalid on Windows, skip checkout\\n\",\n                      de->name);\n        if (reason == IGNORE_REASON_END_SPACE_PERIOD)\n            send_file_sync_error_notification (repo_id, repo_name, de->name,\n                                               SYNC_ERROR_ID_PATH_END_SPACE_PERIOD);\n        else if (reason == IGNORE_REASON_INVALID_CHARACTER)\n            send_file_sync_error_notification (repo_id, repo_name, de->name,\n                                               SYNC_ERROR_ID_PATH_INVALID_CHARACTER);\n        skip_fetch = TRUE;\n    }\n\n    if (!skip_fetch && (is_path_case_conflict (worktree, de->name, &conflict_path, no_case_conflict_hash) ||\n        is_adding_files_case_conflict(adding_files, de->name, &conflict_path, no_case_conflict_hash))) {\n        if (conflict_path && !g_hash_table_lookup(case_conflict_hash, conflict_path)) {\n            seaf_message (\"Path %s is case conflict, skip checkout\\n\", conflict_path);\n            send_file_sync_error_notification (repo_id, repo_name, conflict_path,\n                                               SYNC_ERROR_ID_CASE_CONFLICT);\n            g_hash_table_insert (case_conflict_hash, conflict_path, conflict_path);\n        } else if (conflict_path) {\n            g_free (conflict_path);\n        }\n        skip_fetch = TRUE;\n        no_checkout = TRUE;\n    }\n\n    if (!skip_fetch) {\n        path = build_checkout_path (worktree, de->name, strlen(de->name));\n        if (!path) {\n            if (new_ce)\n                cache_entry_free (ce);\n            return FETCH_CHECKOUT_FAILED;\n        }\n    }\n\n    char *de_name = g_strdup(de->name);\n    *adding_files = g_list_prepend (*adding_files, de_name);\n\n    file_task = g_new0 (FileTxTask, 1);\n    file_task->de = de;\n    file_task->ce = ce;\n    file_task->path = path;\n    file_task->new_ce = new_ce;\n    file_task->skip_fetch = skip_fetch;\n    file_task->no_checkout = no_checkout;\n\n    if (!g_hash_table_lookup (pending_tasks, de->name)) {\n        g_hash_table_insert (pending_tasks, g_strdup(de->name), file_task);\n        g_thread_pool_push (tpool, file_task, NULL);\n    } else {\n        file_tx_task_free (file_task);\n    }\n\n    return FETCH_CHECKOUT_SUCCESS;\n}\n\nstatic void\nhandle_dir_added_de (const char *repo_id,\n                     const char *repo_name,\n                     const char *worktree,\n                     struct index_state *istate,\n                     DiffEntry *de,\n                     GHashTable *no_case_conflict_hash)\n{\n    seaf_debug (\"Checkout empty dir %s.\\n\", de->name);\n\n    struct cache_entry *ce;\n    gboolean add_ce = FALSE;\n\n    ce = index_name_exists (istate, de->name, strlen(de->name), 0);\n    if (!ce) {\n        ce = cache_entry_from_diff_entry (de);\n        add_ce = TRUE;\n    }\n\n    IgnoreReason reason;\n    if (should_ignore_on_checkout (de->name, &reason)) {\n        seaf_message (\"Path %s is invalid on Windows, skip checkout\\n\",\n                      de->name);\n        if (reason == IGNORE_REASON_END_SPACE_PERIOD)\n            send_file_sync_error_notification (repo_id, repo_name, de->name,\n                                               SYNC_ERROR_ID_PATH_END_SPACE_PERIOD);\n        else if (reason == IGNORE_REASON_INVALID_CHARACTER)\n            send_file_sync_error_notification (repo_id, repo_name, de->name,\n                                               SYNC_ERROR_ID_PATH_INVALID_CHARACTER);\n        goto update_index;\n    }\n\n    if (is_path_case_conflict(worktree, de->name, NULL, no_case_conflict_hash)) {\n        seaf_message (\"Path %s is case conflict, skip checkout\\n\", de->name);\n        send_file_sync_error_notification (repo_id, repo_name, de->name,\n                                           SYNC_ERROR_ID_CASE_CONFLICT);\n        goto update_index;\n    }\n\n    checkout_empty_dir (worktree,\n                        de->name,\n                        de->mtime,\n                        ce);\n\n    seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                          repo_id,\n                                          de->name,\n                                          de->mode,\n                                          SYNC_STATUS_SYNCED,\n                                          TRUE);\n\nupdate_index:\n    if (add_ce) {\n        if (!(ce->ce_flags & CE_REMOVE)) {\n            add_index_entry (istate, ce,\n                             (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE));\n        }\n    } else\n        ce->ce_mtime.sec = de->mtime;\n}\n\n#define DEFAULT_DOWNLOAD_THREADS 3\n\nstatic int\ndownload_files_http (const char *repo_id,\n                     int repo_version,\n                     const char *worktree,\n                     struct index_state *istate,\n                     const char *index_path,\n                     SeafileCrypt *crypt,\n                     HttpTxTask *http_task,\n                     GList *results,\n                     const char *conflict_head_id,\n                     LockedFileSet *fset,\n                     GHashTable *no_case_conflict_hash)\n{\n    struct cache_entry *ce;\n    DiffEntry *de;\n    gint64 checkout_size = 0;\n    GThreadPool *tpool;\n    GAsyncQueue *finished_tasks;\n    GHashTable *pending_tasks;\n    GHashTable *case_conflict_hash;\n    GList *adding_files = NULL;\n    GList *ptr;\n    FileTxTask *task;\n    int ret = FETCH_CHECKOUT_SUCCESS;\n    gboolean checkout_file_failed = FALSE;\n\n    finished_tasks = g_async_queue_new ();\n\n    FileTxData data;\n    memset (&data, 0, sizeof(data));\n    memcpy (data.repo_id, repo_id, 36);\n    data.repo_version = repo_version;\n    data.crypt = crypt;\n    data.http_task = http_task;\n    memcpy (data.conflict_head_id, conflict_head_id, 40);\n    data.finished_tasks = finished_tasks;\n    data.worktree = worktree; \n    data.fset = fset;\n\n    tpool = g_thread_pool_new (fetch_file_thread_func, &data,\n                               DEFAULT_DOWNLOAD_THREADS, FALSE, NULL);\n\n    pending_tasks = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                           g_free, (GDestroyNotify)file_tx_task_free);\n\n    case_conflict_hash = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                            g_free, NULL);\n\n    for (ptr = results; ptr != NULL; ptr = ptr->next) {\n        de = ptr->data;\n\n        if (de->status == DIFF_STATUS_DIR_ADDED) {\n            handle_dir_added_de (repo_id, http_task->repo_name, worktree, istate, de, no_case_conflict_hash);\n        } else if (de->status == DIFF_STATUS_ADDED ||\n                   de->status == DIFF_STATUS_MODIFIED) {\n            if (FETCH_CHECKOUT_FAILED == schedule_file_fetch (tpool,\n                                                              repo_id,\n                                                              http_task->repo_name,\n                                                              worktree,\n                                                              istate,\n                                                              de,\n                                                              conflict_head_id,\n                                                              fset,\n                                                              pending_tasks,\n                                                              case_conflict_hash,\n                                                              no_case_conflict_hash,\n                                                              &adding_files))\n                continue;\n        }\n    }\n\n    /* If there is no file need to be downloaded, return immediately. */\n    if (g_hash_table_size(pending_tasks) == 0) {\n        if (results != NULL)\n            update_index (istate, index_path);\n        goto out;\n    }\n\n    char file_id[41];\n    while ((task = g_async_queue_pop (finished_tasks)) != NULL) {\n        ce = task->ce;\n        de = task->de;\n\n        rawdata_to_hex (de->sha1, file_id, 20);\n        /* seaf_message (\"Finished downloading file %s for repo %s\\n\", */\n        /*               de->name, repo_id); */\n\n        if (task->result == FETCH_CHECKOUT_CANCELED ||\n            task->result == FETCH_CHECKOUT_TRANSFER_ERROR) {\n            ret = task->result;\n            if (task->new_ce)\n                cache_entry_free (task->ce);\n            http_task->all_stop = TRUE;\n            goto out;\n        }\n\n        int rc = task->result;\n\n        // Record a file-level sync error when failed to checkout file.\n        if (rc == FETCH_CHECKOUT_FAILED) {\n            if (checkout_file_failed) {\n                seaf_repo_manager_record_sync_error (repo_id, http_task->repo_name, de->name,\n                                                     SYNC_ERROR_ID_CHECKOUT_FILE);\n            } else {\n                checkout_file_failed = TRUE;\n                send_file_sync_error_notification (repo_id, http_task->repo_name, de->name,\n                                                   SYNC_ERROR_ID_CHECKOUT_FILE);\n            }\n        }\n\n        if (!http_task->is_clone) {\n            SyncStatus status;\n            if (rc == FETCH_CHECKOUT_FAILED)\n                status = SYNC_STATUS_ERROR;\n            else\n                status = SYNC_STATUS_SYNCED;\n            seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                                  repo_id,\n                                                  de->name,\n                                                  de->mode,\n                                                  status,\n                                                  TRUE);\n        }\n\n        if (task->new_ce) {\n            if (!(ce->ce_flags & CE_REMOVE)) {\n                add_index_entry (istate, task->ce,\n                                 (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE));\n            }\n        } else {\n            ce->ce_mtime.sec = de->mtime;\n            ce->ce_size = de->size;\n            memcpy (ce->sha1, de->sha1, 20);\n            if (ce->modifier) g_free (ce->modifier);\n            ce->modifier = g_strdup(de->modifier);\n            ce->ce_mode = create_ce_mode (de->mode);\n        }\n\n        g_hash_table_remove (pending_tasks, de->name);\n\n        if (g_hash_table_size (pending_tasks) == 0)\n            break;\n\n        /* Save index file to disk after checking out some size of files.\n         * This way we don't need to re-compare too many files if this\n         * checkout is interrupted.\n         */\n        checkout_size += ce->ce_size;\n        if (checkout_size >= UPDATE_CACHE_SIZE_LIMIT) {\n            update_index (istate, index_path);\n            checkout_size = 0;\n        }\n    }\n\n    update_index (istate, index_path);\n\nout:\n    /* Wait until all threads exit.\n     * This is necessary when the download is canceled or encountered error.\n     */\n    g_thread_pool_free (tpool, TRUE, TRUE);\n\n    /* Free all pending file task structs. */\n    g_hash_table_destroy (pending_tasks);\n\n    g_hash_table_destroy (case_conflict_hash);\n\n    if (adding_files)\n        string_list_free (adding_files);\n\n    g_async_queue_unref (finished_tasks);\n\n    return ret;\n}\n\nstatic gboolean\nexpand_dir_added_cb (SeafFSManager *mgr,\n                     const char *path,\n                     SeafDirent *dent,\n                     void *user_data,\n                     gboolean *stop)\n{\n    GList **expanded = user_data;\n    DiffEntry *de = NULL;\n    unsigned char sha1[20];\n\n    hex_to_rawdata (dent->id, sha1, 20);\n\n    if (S_ISDIR(dent->mode) && strcmp(dent->id, EMPTY_SHA1) == 0)\n        de = diff_entry_new (DIFF_TYPE_COMMITS, DIFF_STATUS_DIR_ADDED, sha1, path);\n    else if (S_ISREG(dent->mode))\n        de = diff_entry_new (DIFF_TYPE_COMMITS, DIFF_STATUS_ADDED, sha1, path);\n\n    if (de) {\n        de->mtime = dent->mtime;\n        de->mode = dent->mode;\n        de->modifier = g_strdup(dent->modifier);\n        de->size = dent->size;\n        *expanded = g_list_prepend (*expanded, de);\n    }\n\n    return TRUE;\n}\n\n/*\n * Expand DIR_ADDED results into multiple ADDED results.\n */\nstatic int\nexpand_diff_results (const char *repo_id, int version,\n                     const char *remote_root, const char *local_root,\n                     GList **results)\n{\n    GList *ptr, *next;\n    DiffEntry *de;\n    char obj_id[41];\n    GList *expanded = NULL;\n\n    ptr = *results;\n    while (ptr) {\n        de = ptr->data;\n\n        next = ptr->next;\n\n        if (de->status == DIFF_STATUS_DIR_ADDED) {\n            *results = g_list_delete_link (*results, ptr);\n\n            rawdata_to_hex (de->sha1, obj_id, 20);\n            if (seaf_fs_manager_traverse_path (seaf->fs_mgr,\n                                               repo_id, version,\n                                               remote_root,\n                                               de->name,\n                                               expand_dir_added_cb,\n                                               &expanded) < 0) {\n                diff_entry_free (de);\n                goto error;\n            }\n            diff_entry_free (de);\n        }\n\n        ptr = next;\n    }\n\n    expanded = g_list_reverse (expanded);\n    *results = g_list_concat (*results, expanded);\n\n    return 0;\n\nerror:\n    g_list_free_full (expanded, (GDestroyNotify)diff_entry_free);\n    return -1;\n}\n\nstatic int\ndo_rename_in_worktree (DiffEntry *de, const char *worktree)\n{\n    char *old_path, *new_path;\n    int ret = 0;\n\n    old_path = g_build_filename (worktree, de->name, NULL);\n\n    if (seaf_util_exists (old_path)) {\n        new_path = build_checkout_path (worktree, de->new_name, strlen(de->new_name));\n        if (!new_path) {\n            ret = -1;\n            goto out;\n        }\n\n        if (seaf_util_rename (old_path, new_path) < 0) {\n            seaf_warning (\"Failed to rename %s to %s: %s.\\n\",\n                          old_path, new_path, strerror(errno));\n            ret = -1;\n        }\n\n        g_free (new_path);\n    }\n\nout:\n    g_free (old_path);\n    return ret;\n}\n\nstatic gboolean\nis_built_in_ignored_file (const char *filename)\n{\n    GPatternSpec **spec = ignore_patterns;\n\n    while (*spec) {\n        if (g_pattern_match_string(*spec, filename))\n            return TRUE;\n        spec++;\n    }\n\n    if (!seaf->sync_extra_temp_file) {\n        spec = office_temp_ignore_patterns;\n        while (*spec) {\n            if (g_pattern_match_string(*spec, filename))\n                return TRUE;\n            spec++;\n        }\n    }\n\n    return FALSE;\n}\n\n#ifdef WIN32\n\n/*\n * @path: path relative to the worktree, utf-8 encoded\n * @path_w: absolute path include worktree, utf-16 encoded.\n * Return 0 when successfully deleted the folder; otherwise -1.\n */\nstatic int\ndelete_worktree_dir_recursive_win32 (struct index_state *istate,\n                                     const char *path,\n                                     const wchar_t *path_w)\n{\n    WIN32_FIND_DATAW fdata;\n    HANDLE handle;\n    wchar_t *pattern;\n    wchar_t *sub_path_w;\n    char *sub_path, *dname;\n    int path_len_w;\n    DWORD error;\n    int ret = 0;\n    guint64 mtime;\n    gboolean builtin_ignored = FALSE;\n\n    path_len_w = wcslen(path_w);\n\n    pattern = g_new0 (wchar_t, (path_len_w + 3));\n    wcscpy (pattern, path_w);\n    wcscat (pattern, L\"\\\\*\");\n\n    handle = FindFirstFileW (pattern, &fdata);\n    g_free (pattern);\n\n    if (handle == INVALID_HANDLE_VALUE) {\n        seaf_warning (\"FindFirstFile failed %s: %lu.\\n\",\n                      path, GetLastError());\n        return -1;\n    }\n\n    do {\n        if (wcscmp (fdata.cFileName, L\".\") == 0 ||\n            wcscmp (fdata.cFileName, L\"..\") == 0)\n            continue;\n\n        dname = g_utf16_to_utf8 (fdata.cFileName, -1, NULL, NULL, NULL);\n        if (!dname)\n            continue;\n\n        sub_path_w = g_new0 (wchar_t, path_len_w + wcslen(fdata.cFileName) + 2);\n        wcscpy (sub_path_w, path_w);\n        wcscat (sub_path_w, L\"\\\\\");\n        wcscat (sub_path_w, fdata.cFileName);\n\n        sub_path = g_strconcat (path, \"/\", dname, NULL);\n        builtin_ignored = is_built_in_ignored_file(dname);\n        g_free (dname);\n\n        if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {\n            if (delete_worktree_dir_recursive_win32 (istate, sub_path, sub_path_w) < 0) {\n                ret = -1;\n            }\n        } else {\n            struct cache_entry *ce;\n            /* Files like .DS_Store and Thumbs.db should be deleted any way. */\n            if (!builtin_ignored) {\n                mtime = (guint64)file_time_to_unix_time (&fdata.ftLastWriteTime);\n                ce = index_name_exists (istate, sub_path, strlen(sub_path), 0);\n                if (!ce || (!is_eml_file (dname) && ce->ce_mtime.sec != mtime)) {\n                    seaf_message (\"File %s is changed, skip deleting it.\\n\", sub_path);\n                    g_free (sub_path_w);\n                    g_free (sub_path);\n                    ret = -1;\n                    continue;\n                }\n            }\n\n            if (!DeleteFileW (sub_path_w)) {\n                error = GetLastError();\n                seaf_warning (\"Failed to delete file %s: %lu.\\n\",\n                              sub_path, error);\n                ret = -1;\n            }\n        }\n\n        g_free (sub_path_w);\n        g_free (sub_path);\n    } while (FindNextFileW (handle, &fdata) != 0);\n\n    error = GetLastError();\n    if (error != ERROR_NO_MORE_FILES) {\n        seaf_warning (\"FindNextFile failed %s: %lu.\\n\",\n                      path, error);\n        ret = -1;\n    }\n\n    FindClose (handle);\n\n    if (ret < 0)\n        return ret;\n\n    int n = 0;\n    while (!RemoveDirectoryW (path_w)) {\n        error = GetLastError();\n        seaf_warning (\"Failed to remove dir %s: %lu.\\n\",\n                      path, error);\n        if (error != ERROR_DIR_NOT_EMPTY) {\n            ret = -1;\n            break;\n        }\n        if (++n >= 3) {\n            ret = -1;\n            break;\n        }\n        /* Sleep 100ms and retry. */\n        g_usleep (100000);\n        seaf_warning (\"Retry remove dir %s.\\n\", path);\n    }\n\n    return ret;\n}\n\n#else\n\nstatic int\ndelete_worktree_dir_recursive (struct index_state *istate,\n                               const char *path,\n                               const char *full_path)\n{\n    GDir *dir;\n    const char *dname;\n    char *dname_nfc;\n    GError *error = NULL;\n    char *sub_path, *full_sub_path;\n    SeafStat st;\n    int ret = 0;\n    gboolean builtin_ignored = FALSE;\n\n    dir = g_dir_open (full_path, 0, &error);\n    if (!dir) {\n        seaf_warning (\"Failed to open dir %s: %s.\\n\", full_path, error->message);\n        return -1;\n    }\n\n    while ((dname = g_dir_read_name (dir)) != NULL) {\n        dname_nfc = g_utf8_normalize (dname, -1, G_NORMALIZE_NFC);\n        sub_path = g_build_path (\"/\", path, dname_nfc, NULL);\n        full_sub_path = g_build_path (\"/\", full_path, dname_nfc, NULL);\n        builtin_ignored = is_built_in_ignored_file (dname_nfc);\n        g_free (dname_nfc);\n\n        if (lstat (full_sub_path, &st) < 0) {\n            seaf_warning (\"Failed to stat %s.\\n\", full_sub_path);\n            g_free (sub_path);\n            g_free (full_sub_path);\n            ret = -1;\n            continue;\n        }\n\n        if (S_ISDIR(st.st_mode)) {\n            if (delete_worktree_dir_recursive (istate, sub_path, full_sub_path) < 0)\n                ret = -1;\n        } else {\n            struct cache_entry *ce;\n            /* Files like .DS_Store and Thumbs.db should be deleted any way. */\n            if (!builtin_ignored) {\n                ce = index_name_exists (istate, sub_path, strlen(sub_path), 0);\n                if (!ce || ce->ce_mtime.sec != st.st_mtime) {\n                    seaf_message (\"File %s is changed, skip deleting it.\\n\", full_sub_path);\n                    g_free (sub_path);\n                    g_free (full_sub_path);\n                    ret = -1;\n                    continue;\n                }\n            }\n\n            /* Delete all other file types. */\n            if (seaf_util_unlink (full_sub_path) < 0) {\n                seaf_warning (\"Failed to delete file %s: %s.\\n\",\n                              full_sub_path, strerror(errno));\n                ret = -1;\n            }\n        }\n\n        g_free (sub_path);\n        g_free (full_sub_path);\n    }\n\n    g_dir_close (dir);\n\n    if (ret < 0)\n        return ret;\n\n    if (g_rmdir (full_path) < 0) {\n        seaf_warning (\"Failed to delete dir %s: %s.\\n\", full_path, strerror(errno));\n        ret = -1;\n    }\n\n    return ret;\n}\n\n#endif  /* WIN32 */\n\n#define SEAFILE_RECYCLE_BIN_FOLDER \"recycle-bin\"\n\nstatic int\nmove_dir_to_recycle_bin (const char *dir_path)\n{\n    char *trash_folder = g_build_path (\"/\", seaf->worktree_dir, SEAFILE_RECYCLE_BIN_FOLDER, NULL);\n    if (checkdir_with_mkdir (trash_folder) < 0) {\n        seaf_warning (\"Seafile trash folder %s doesn't exist and cannot be created.\\n\",\n                      trash_folder);\n        g_free (trash_folder);\n        return -1;\n    }\n    g_free (trash_folder);\n\n    char *basename = g_path_get_basename (dir_path);\n    char *dst_path = g_build_path (\"/\", seaf->worktree_dir, SEAFILE_RECYCLE_BIN_FOLDER, basename, NULL);\n    int ret = 0;\n\n    int n;\n    char *tmp_path;\n    for (n = 1; n < 10; ++n) {\n        if (g_file_test (dst_path, G_FILE_TEST_EXISTS)) {\n            tmp_path = g_strdup_printf (\"%s(%d)\", dst_path, n);\n            g_free (dst_path);\n            dst_path = tmp_path;\n            continue;\n        }\n        break;\n    }\n\n    if (seaf_util_rename (dir_path, dst_path) < 0) {\n        seaf_warning (\"Failed to move %s to Seafile recycle bin %s: %s\\n\",\n                      dir_path, dst_path, strerror(errno));\n        ret = -1;\n        goto out;\n    }\n\n    seaf_message (\"Moved folder %s to Seafile recycle bin %s.\\n\",\n                  dir_path, dst_path);\n\nout:\n    g_free (basename);\n    g_free (dst_path);\n    return ret;\n}\n\nstatic void\ndelete_worktree_dir (const char *repo_id,\n                     const char *repo_name,\n                     struct index_state *istate,\n                     const char *worktree,\n                     const char *path)\n{\n    char *full_path = g_build_path (\"/\", worktree, path, NULL);\n\n#ifdef WIN32\n    wchar_t *full_path_w = win32_long_path (full_path);\n    delete_worktree_dir_recursive_win32 (istate, path, full_path_w);\n    g_free (full_path_w);\n#else\n    delete_worktree_dir_recursive(istate, path, full_path);\n#endif\n\n    /* If for some reason the dir cannot be removed, try to move it to a trash folder\n     * under Seafile folder. Otherwise the removed folder will be created agian on the\n     * server, which will confuse the users.\n     */\n    if (g_file_test (full_path, G_FILE_TEST_EXISTS)) {\n        if (move_dir_to_recycle_bin (full_path) == 0)\n            send_file_sync_error_notification (repo_id, repo_name, path,\n                                               SYNC_ERROR_ID_REMOVE_UNCOMMITTED_FOLDER);\n    }\n\n    g_free (full_path);\n}\n\nstatic void\nupdate_sync_status (struct cache_entry *ce, void *user_data)\n{\n    char *repo_id = user_data;\n\n    seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                          repo_id,\n                                          ce->name,\n                                          ce->ce_mode,\n                                          SYNC_STATUS_SYNCED,\n                                          TRUE);\n}\n\nstatic int\nconvert_rename_to_checkout (const char *repo_id,\n                            int repo_version,\n                            const char *root_id,\n                            DiffEntry *de,\n                            GList **entries)\n{\n    if (de->status == DIFF_STATUS_RENAMED) {\n        char file_id[41];\n        SeafDirent *dent = NULL;\n        DiffEntry *new_de = NULL;\n\n        rawdata_to_hex (de->sha1, file_id, 20);\n        dent = seaf_fs_manager_get_dirent_by_path (seaf->fs_mgr,\n                                                   repo_id,\n                                                   repo_version,\n                                                   root_id,\n                                                   de->new_name,\n                                                   NULL);\n        if (!dent) {\n            seaf_warning (\"Failed to find %s in repo %s\\n\",\n                          de->new_name, repo_id);\n            return -1;\n        }\n\n        new_de = diff_entry_new (DIFF_TYPE_COMMITS, DIFF_STATUS_ADDED,\n                                 de->sha1, de->new_name);\n        if (new_de) {\n            new_de->mtime = dent->mtime;\n            new_de->mode = dent->mode;\n            new_de->modifier = g_strdup(dent->modifier);\n            new_de->size = dent->size;\n            *entries = g_list_prepend (*entries, new_de);\n        }\n\n        seaf_dirent_free (dent);\n    } else if (de->status == DIFF_STATUS_DIR_RENAMED) {\n        GList *expanded = NULL;\n\n        if (seaf_fs_manager_traverse_path (seaf->fs_mgr,\n                                           repo_id, repo_version,\n                                           root_id,\n                                           de->new_name,\n                                           expand_dir_added_cb,\n                                           &expanded) < 0) {\n            g_list_free_full (expanded, (GDestroyNotify)diff_entry_free);\n            return -1;\n        }\n\n        *entries = g_list_concat (*entries, expanded);\n    }\n\n    return 0;\n}\n\nstatic gboolean\nload_enc_keys_cb (sqlite3_stmt *stmt, void *vcrypt)\n{\n    SeafileCrypt *crypt = vcrypt;\n    const char *key, *iv;\n\n    key = (const char *)sqlite3_column_text(stmt, 0);\n    iv = (const char *)sqlite3_column_text(stmt, 1);\n\n    if (crypt->version == 1) {\n        hex_to_rawdata (key, crypt->key, 16);\n        hex_to_rawdata (iv, crypt->iv, 16);\n    } else if (crypt->version >= 2) {\n        hex_to_rawdata (key, crypt->key, 32);\n        hex_to_rawdata (iv, crypt->iv, 16);\n    }\n\n    return FALSE;\n}\n\nstatic int\nload_crypt_from_enc_info (SeafRepoManager *manager, const char *repo_id, SeafileCrypt *crypt)\n{\n    sqlite3 *db = manager->priv->db;\n    char sql[256];\n    int n;\n\n    pthread_mutex_lock (&manager->priv->db_lock);\n\n    snprintf (sql, sizeof(sql), \n              \"SELECT key, iv FROM RepoKeys WHERE repo_id='%s'\",\n              repo_id);\n    n = sqlite_foreach_selected_row (db, sql, load_enc_keys_cb, crypt);\n    if (n < 0) {\n        pthread_mutex_unlock (&manager->priv->db_lock);\n        return -1;\n    }\n\n    pthread_mutex_unlock (&manager->priv->db_lock);\n\n    return 0;\n}\n\nstatic gboolean\nhandle_de_rename (HttpTxTask *http_task, const char *worktree, DiffEntry *de,\n                  struct index_state *istate, GHashTable *no_case_conflict_hash)\n{\n    char *repo_id = http_task->repo_id;\n    int repo_version = http_task->repo_version;\n    gboolean is_clone = http_task->is_clone;\n    struct cache_entry *ce;\n\n#if defined WIN32 || defined __APPLE__\n    gboolean old_path_conflict = is_path_case_conflict(worktree, de->name, NULL, no_case_conflict_hash);\n    gboolean new_path_conflict = is_path_case_conflict(worktree, de->new_name, NULL, no_case_conflict_hash);\n\n    if (g_strcasecmp (de->name, de->new_name) == 0) {\n        if (!old_path_conflict && !new_path_conflict) {\n            return TRUE;\n        } else if (old_path_conflict && !new_path_conflict) {\n            seaf_message (\"Path %s is renamed to %s, which has case conflict and will not be checked out.\\n\", de->name, de->new_name);\n            send_file_sync_error_notification (repo_id, NULL, de->new_name,\n                           SYNC_ERROR_ID_CASE_CONFLICT);\n            return FALSE;\n        } else if (!old_path_conflict && new_path_conflict) {\n            do_rename_in_worktree (de, worktree);\n        } else {\n            seaf_message (\"Path %s is renamed to %s, which has case conflict and will not be checked out.\\n\", de->name, de->new_name);\n            send_file_sync_error_notification (repo_id, NULL, de->new_name,\n                           SYNC_ERROR_ID_CASE_CONFLICT);\n            return FALSE;\n        }\n    } else {\n        if (!old_path_conflict && !new_path_conflict) {\n            do_rename_in_worktree (de, worktree);\n        } else if (old_path_conflict && !new_path_conflict) {\n            seaf_message (\"Case conflict path %s is renamed to %s without case conflict, check it out\\n\", de->name, de->new_name);\n            return TRUE;\n        } else if (!old_path_conflict && new_path_conflict) {\n            // check if file has been changed and delete old path.\n            if (de->status == DIFF_STATUS_DIR_RENAMED) {\n                seaf_message (\"Path %s is renamed to %s, which has case conflict and will not be checked out. Delete it\\n\", de->name, de->new_name);\n                send_file_sync_error_notification (repo_id, NULL, de->new_name,\n                               SYNC_ERROR_ID_CASE_CONFLICT);\n                delete_worktree_dir (repo_id, http_task->repo_name, istate, worktree, de->name);\n            } else {\n                ce = index_name_exists (istate, de->name, strlen(de->name), 0);\n                if (ce) {\n                    seaf_message (\"Path %s is renamed to %s, which has case conflict and will not be checked out. Delete it\\n\", de->name, de->new_name);\n                    send_file_sync_error_notification (repo_id, NULL, de->new_name,\n                                   SYNC_ERROR_ID_CASE_CONFLICT);\n                    delete_path (worktree, de->name, de->mode, ce->ce_mtime.sec);\n                }\n            }\n        } else {\n            seaf_message (\"Path %s is renamed to %s, which has case conflict and will not be checked out.\\n\", de->name, de->new_name);\n            send_file_sync_error_notification (repo_id, NULL, de->new_name,\n                           SYNC_ERROR_ID_CASE_CONFLICT);\n            return FALSE;\n        }\n    }\n#else\n    do_rename_in_worktree (de, worktree);\n#endif\n\n    return FALSE;\n}\n\nint\nseaf_repo_fetch_and_checkout (HttpTxTask *http_task, const char *remote_head_id)\n{\n    char *repo_id;\n    int repo_version;\n    gboolean is_clone;\n    char *worktree;\n    char *passwd;\n\n    SeafRepo *repo = NULL;\n    SeafBranch *master = NULL;\n    SeafCommit *remote_head = NULL, *master_head = NULL;\n    char index_path[SEAF_PATH_MAX];\n    struct index_state istate;\n    int ret = FETCH_CHECKOUT_SUCCESS;\n    GList *results = NULL;\n    SeafileCrypt *crypt = NULL;\n    GList *ignore_list = NULL;\n    LockedFileSet *fset = NULL;\n    GHashTable *no_case_conflict_hash = NULL;\n\n    repo_id = http_task->repo_id;\n    repo_version = http_task->repo_version;\n    is_clone = http_task->is_clone;\n    worktree = http_task->worktree;\n    passwd = http_task->passwd;\n\n    memset (&istate, 0, sizeof(istate));\n    snprintf (index_path, SEAF_PATH_MAX, \"%s/%s\",\n              seaf->repo_mgr->index_dir, repo_id);\n    if (read_index_from (&istate, index_path, repo_version) < 0) {\n        seaf_warning (\"Failed to load index.\\n\");\n        return FETCH_CHECKOUT_FAILED;\n    }\n\n    no_case_conflict_hash = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                            g_free, NULL);\n\n    if (!is_clone) {\n        repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n        if (!repo) {\n            seaf_warning (\"Failed to get repo %.8s.\\n\", repo_id);\n            goto out;\n        }\n\n        master = seaf_branch_manager_get_branch (seaf->branch_mgr,\n                                                 repo_id, \"master\");\n        if (!master) {\n            seaf_warning (\"Failed to get master branch for repo %.8s.\\n\",\n                          repo_id);\n            ret = FETCH_CHECKOUT_FAILED;\n            goto out;\n        }\n\n        master_head = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                                      repo_id,\n                                                      repo_version,\n                                                      master->commit_id);\n        if (!master_head) {\n            seaf_warning (\"Failed to get master head %s of repo %.8s.\\n\",\n                          repo_id, master->commit_id);\n            ret = FETCH_CHECKOUT_FAILED;\n            goto out;\n        }\n    }\n\n    if (!is_clone)\n        worktree = repo->worktree;\n\n    remote_head = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                                  repo_id,\n                                                  repo_version,\n                                                  remote_head_id);\n    if (!remote_head) {\n        seaf_warning (\"Failed to get remote head %s of repo %.8s.\\n\",\n                      repo_id, remote_head_id);\n        ret = FETCH_CHECKOUT_FAILED;\n        goto out;\n    }\n\n    if (diff_commit_roots (repo_id, repo_version,\n                           master_head ? master_head->root_id : EMPTY_SHA1,\n                           remote_head->root_id,\n                           &results, TRUE) < 0) {\n        seaf_warning (\"Failed to diff for repo %.8s.\\n\", repo_id);\n        ret = FETCH_CHECKOUT_FAILED;\n        goto out;\n    }\n\n    GList *ptr;\n    DiffEntry *de;\n\n    /* Expand DIR_ADDED diff entries. */\n    if (expand_diff_results (repo_id, repo_version,\n                             remote_head->root_id,\n                             master_head ? master_head->root_id : EMPTY_SHA1,\n                             &results) < 0) {\n        ret = FETCH_CHECKOUT_FAILED;\n        goto out;\n    }\n\n#ifdef WIN32\n    for (ptr = results; ptr; ptr = ptr->next) {\n        de = ptr->data;\n        if (de->status == DIFF_STATUS_DIR_RENAMED ||\n            de->status == DIFF_STATUS_DIR_DELETED) {\n            if (do_check_dir_locked (de->name, worktree)) {\n                seaf_message (\"File(s) in dir %s are locked by other program, \"\n                              \"skip rename/delete.\\n\", de->name);\n                send_file_sync_error_notification (repo_id, NULL, de->name,\n                                                   SYNC_ERROR_ID_FOLDER_LOCKED_BY_APP);\n                ret = FETCH_CHECKOUT_LOCKED;\n                goto out;\n            }\n        } else if (de->status == DIFF_STATUS_RENAMED) {\n            gboolean locked_on_server = seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,\n                                                                              repo_id,\n                                                                              de->name);\n\n            if (do_check_file_locked (de->name, worktree, locked_on_server)) {\n                seaf_message (\"File %s is locked by other program, skip rename.\\n\",\n                              de->name);\n                send_file_sync_error_notification (repo_id, NULL, de->name,\n                                                   SYNC_ERROR_ID_FILE_LOCKED_BY_APP);\n                ret = FETCH_CHECKOUT_LOCKED;\n                goto out;\n            }\n        }\n    }\n#endif\n\n    if (remote_head->encrypted) {\n        if (!is_clone) {\n            crypt = seafile_crypt_new (repo->enc_version,\n                                       repo->enc_key,\n                                       repo->enc_iv);\n        } else if (passwd){\n            unsigned char enc_key[32], enc_iv[16];\n            if (seafile_decrypt_repo_enc_key (remote_head->enc_version,\n                                          passwd,\n                                          remote_head->random_key,\n                                          remote_head->salt,\n                                          enc_key, enc_iv) < 0) {\n                seaf_warning (\"Failed to decrypt repo enc key: %s\\n\", repo_id);\n                ret = FETCH_CHECKOUT_FAILED;\n                goto out;\n            }\n            crypt = seafile_crypt_new (remote_head->enc_version,\n                                       enc_key, enc_iv);\n        } else {\n            // resync an encrypted repo, get key and iv from db. \n            crypt = g_new0 (SeafileCrypt, 1);\n            crypt->version = remote_head->enc_version;\n            load_crypt_from_enc_info (seaf->repo_mgr, repo_id, crypt);\n        }\n    }\n\n    ignore_list = seaf_repo_load_ignore_files (worktree);\n\n    struct cache_entry *ce;\n\n#if defined WIN32 || defined __APPLE__\n    fset = seaf_repo_manager_get_locked_file_set (seaf->repo_mgr, repo_id);\n#endif\n\n    for (ptr = results; ptr; ptr = ptr->next) {\n        de = ptr->data;\n        if (de->status == DIFF_STATUS_DELETED) {\n            seaf_debug (\"Delete file %s.\\n\", de->name);\n\n            ce = index_name_exists (&istate, de->name, strlen(de->name), 0);\n            if (!ce)\n                continue;\n\n            if (should_ignore_on_checkout (de->name, NULL)) {\n                remove_from_index_with_prefix (&istate, de->name, NULL);\n                try_add_empty_parent_dir_entry (worktree, &istate, de->name);\n                continue;\n            }\n\n            gboolean locked_on_server = seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,\n                                                                              repo_id,\n                                                                              de->name);\n            if (locked_on_server)\n                seaf_filelock_manager_unlock_wt_file (seaf->filelock_mgr,\n                                                      repo_id, de->name);\n\n#if defined WIN32 || defined __APPLE__\n            if (!do_check_file_locked (de->name, worktree, locked_on_server)) {\n                locked_file_set_remove (fset, de->name, FALSE);\n                if (!is_path_case_conflict (worktree, de->name, NULL, no_case_conflict_hash)) {\n                    delete_path (worktree, de->name, de->mode, ce->ce_mtime.sec);\n                } else {\n                    seaf_message (\"Path %s is case conflict, skip delete\\n\", de->name);\n                    send_file_sync_error_notification (repo_id, NULL, de->name,\n                                                       SYNC_ERROR_ID_CASE_CONFLICT);\n                }\n            } else {\n                if (!locked_file_set_lookup (fset, de->name))\n                    send_file_sync_error_notification (repo_id, http_task->repo_name, de->name,\n                                                       SYNC_ERROR_ID_FILE_LOCKED_BY_APP);\n\n                locked_file_set_add_update (fset, de->name, LOCKED_OP_DELETE,\n                                            ce->ce_mtime.sec, NULL);\n            }\n#else\n            delete_path (worktree, de->name, de->mode, ce->ce_mtime.sec);\n#endif\n\n            /* No need to lock wt file again since it's deleted. */\n\n            remove_from_index_with_prefix (&istate, de->name, NULL);\n            try_add_empty_parent_dir_entry (worktree, &istate, de->name);\n        } else if (de->status == DIFF_STATUS_DIR_DELETED) {\n            seaf_debug (\"Delete dir %s.\\n\", de->name);\n\n            /* Nothing to delete. */\n            if (!master_head || strcmp(master_head->root_id, EMPTY_SHA1) == 0)\n                continue;\n\n            if (should_ignore_on_checkout (de->name, NULL)) {\n                seaf_message (\"Path %s is invalid on Windows, skip delete.\\n\",\n                              de->name);\n                remove_from_index_with_prefix (&istate, de->name, NULL);\n\n                try_add_empty_parent_dir_entry (worktree, &istate, de->name);\n                continue;\n            }\n\n            if (!is_path_case_conflict (worktree, de->name, NULL, no_case_conflict_hash)) {\n                delete_worktree_dir (repo_id, http_task->repo_name, &istate, worktree, de->name);\n            } else {\n                seaf_message (\"Path %s is case conflict, skip delete\\n\", de->name);\n                send_file_sync_error_notification (repo_id, NULL, de->name,\n                                                   SYNC_ERROR_ID_CASE_CONFLICT);\n            }\n\n            /* Remove all index entries under this directory */\n            remove_from_index_with_prefix (&istate, de->name, NULL);\n\n            try_add_empty_parent_dir_entry (worktree, &istate, de->name);\n        }\n    }\n\n    for (ptr = results; ptr; ptr = ptr->next) {\n        de = ptr->data;\n        if (de->status == DIFF_STATUS_RENAMED ||\n            de->status == DIFF_STATUS_DIR_RENAMED) {\n            seaf_debug (\"Rename %s to %s.\\n\", de->name, de->new_name);\n\n#ifdef WIN32\n            IgnoreReason reason;\n            if (should_ignore_on_checkout (de->new_name, &reason)) {\n                seaf_message (\"Path %s is invalid on Windows, skip rename.\\n\", de->new_name);\n\n                if (reason == IGNORE_REASON_END_SPACE_PERIOD)\n                    send_file_sync_error_notification (repo_id, http_task->repo_name,\n                                                       de->new_name,\n                                                       SYNC_ERROR_ID_PATH_END_SPACE_PERIOD);\n                else if (reason == IGNORE_REASON_INVALID_CHARACTER)\n                    send_file_sync_error_notification (repo_id, http_task->repo_name,\n                                                       de->new_name,\n                                                       SYNC_ERROR_ID_PATH_INVALID_CHARACTER);\n                continue;\n            } else if (should_ignore_on_checkout (de->name, NULL)) {\n                /* If the server renames an invalid path to a valid path,\n                 * directly checkout the valid path. The checkout will merge\n                 * with any existing files.\n                 */\n                convert_rename_to_checkout (repo_id, repo_version,\n                                            remote_head->root_id,\n                                            de, &results);\n                continue;\n            }\n#endif\n\n            if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,\n                                                      repo_id, de->name))\n                seaf_filelock_manager_unlock_wt_file (seaf->filelock_mgr,\n                                                      repo_id, de->name);\n\n            gboolean checkout_directly = handle_de_rename (http_task, worktree, de, &istate, no_case_conflict_hash);\n            if (checkout_directly) {\n                convert_rename_to_checkout (repo_id, repo_version,\n                                            remote_head->root_id,\n                                            de, &results);\n            } else {\n                /* update_sync_status updates the sync status for each renamed path.\n                 * The renamed file/folder becomes \"synced\" immediately after rename.\n                 */\n                if (!is_clone)\n                    rename_index_entries (&istate, de->name, de->new_name, NULL,\n                                          update_sync_status, repo_id);\n                else\n                    rename_index_entries (&istate, de->name, de->new_name, NULL,\n                                          NULL, NULL);\n\n                /* Moving files out of a dir may make it empty. */\n                try_add_empty_parent_dir_entry (worktree, &istate, de->name);\n            }\n        }\n    }\n\n    if (istate.cache_changed)\n        update_index (&istate, index_path);\n\n    for (ptr = results; ptr; ptr = ptr->next) {\n        de = ptr->data;\n        if (de->status == DIFF_STATUS_ADDED || de->status == DIFF_STATUS_MODIFIED) {\n            http_task->total_download += de->size;\n        }\n    }\n\n    ret = download_files_http (repo_id,\n                               repo_version,\n                               worktree,\n                               &istate,\n                               index_path,\n                               crypt,\n                               http_task,\n                               results,\n                               remote_head_id,\n                               fset,\n                               no_case_conflict_hash);\n\nout:\n    discard_index (&istate);\n\n    seaf_branch_unref (master);\n    seaf_commit_unref (master_head);\n    seaf_commit_unref (remote_head);\n\n    g_list_free_full (results, (GDestroyNotify)diff_entry_free);\n\n    g_free (crypt);\n\n    if (ignore_list)\n        seaf_repo_free_ignore_files (ignore_list);\n\n#if defined WIN32 || defined __APPLE__\n    locked_file_set_free (fset);\n#endif\n\n    g_hash_table_destroy (no_case_conflict_hash);\n\n    return ret;\n}\n\nint\nseaf_repo_manager_set_repo_worktree (SeafRepoManager *mgr,\n                                     SeafRepo *repo,\n                                     const char *worktree)\n{\n    if (g_access(worktree, F_OK) != 0)\n        return -1;\n\n    if (repo->worktree)\n        g_free (repo->worktree);\n    repo->worktree = g_strdup(worktree);\n\n    if (seaf_repo_manager_set_repo_property (mgr, repo->id,\n                                             \"worktree\",\n                                             repo->worktree) < 0)\n        return -1;\n\n    repo->worktree_invalid = FALSE;\n\n    return 0;\n}\n\nvoid\nseaf_repo_manager_invalidate_repo_worktree (SeafRepoManager *mgr,\n                                            SeafRepo *repo)\n{\n    if (repo->worktree_invalid)\n        return;\n\n    repo->worktree_invalid = TRUE;\n\n    if (repo->auto_sync && (repo->sync_interval == 0)) {\n        if (seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id) < 0) {\n            seaf_warning (\"failed to unwatch repo %s.\\n\", repo->id);\n        }\n    }\n}\n\nvoid\nseaf_repo_manager_validate_repo_worktree (SeafRepoManager *mgr,\n                                          SeafRepo *repo)\n{\n    if (!repo->worktree_invalid)\n        return;\n\n    repo->worktree_invalid = FALSE;\n\n    if (repo->auto_sync && (repo->sync_interval == 0)) {\n        if (seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id, repo->worktree) < 0) {\n            seaf_warning (\"failed to watch repo %s.\\n\", repo->id);\n        }\n    }\n}\n\nSeafRepoManager*\nseaf_repo_manager_new (SeafileSession *seaf)\n{\n    SeafRepoManager *mgr = g_new0 (SeafRepoManager, 1);\n\n    mgr->priv = g_new0 (SeafRepoManagerPriv, 1);\n    mgr->seaf = seaf;\n    mgr->index_dir = g_build_path (PATH_SEPERATOR, seaf->seaf_dir, INDEX_DIR, NULL);\n\n    pthread_mutex_init (&mgr->priv->db_lock, NULL);\n\n    mgr->priv->checkout_tasks_hash = g_hash_table_new_full\n        (g_str_hash, g_str_equal, g_free, g_free);\n\n    ignore_patterns = g_new0 (GPatternSpec*, G_N_ELEMENTS(ignore_table));\n    int i;\n    for (i = 0; ignore_table[i] != NULL; i++) {\n        ignore_patterns[i] = g_pattern_spec_new (ignore_table[i]);\n    }\n\n    office_temp_ignore_patterns[0] = g_pattern_spec_new(\"~$*\");\n    /* for files like ~WRL0001.tmp for docx and *.tmp for xlsx and pptx */\n    office_temp_ignore_patterns[1] = g_pattern_spec_new(\"*.tmp\");\n    office_temp_ignore_patterns[2] = g_pattern_spec_new(\".~lock*#\");\n    /* for temporary files of WPS Office */\n    office_temp_ignore_patterns[3] = g_pattern_spec_new(\".~*\");\n    office_temp_ignore_patterns[4] = NULL;\n\n    GError *error = NULL;\n    conflict_pattern = g_regex_new (CONFLICT_PATTERN, 0, 0, &error);\n    if (error) {\n        seaf_warning (\"Failed to create regex '%s': %s\\n\",\n                      CONFLICT_PATTERN, error->message);\n        g_clear_error (&error);\n    }\n\n    office_lock_pattern = g_regex_new (OFFICE_LOCK_PATTERN, 0, 0, &error);\n    if (error) {\n        seaf_warning (\"Failed to create regex '%s': %s\\n\",\n                      OFFICE_LOCK_PATTERN, error->message);\n        g_clear_error (&error);\n    }\n    libre_office_lock_pattern = g_regex_new (LIBRE_OFFICE_LOCK_PATTERN, 0, 0, &error);\n    if (error) {\n        seaf_warning (\"Failed to create libre office regex '%s': %s\\n\",\n                      LIBRE_OFFICE_LOCK_PATTERN, error->message);\n        g_clear_error (&error);\n    }\n    wps_lock_pattern = g_regex_new (WPS_LOCK_PATTERN, 0, 0, &error);\n    if (error) {\n        seaf_warning (\"Failed to create wps regex '%s': %s\\n\",\n                      WPS_LOCK_PATTERN, error->message);\n        g_clear_error (&error);\n    }\n\n    mgr->priv->repo_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);\n\n    pthread_rwlock_init (&mgr->priv->lock, NULL);\n\n    mgr->priv->lock_office_job_queue = g_async_queue_new ();\n\n    pthread_mutex_init (&mgr->priv->errors_lock, NULL);\n\n    mgr->priv->block_map_cache_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);\n    pthread_rwlock_init (&mgr->priv->block_map_lock, NULL);\n\n    mgr->priv->user_perms = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);\n    mgr->priv->group_perms = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);\n    pthread_mutex_init (&mgr->priv->perm_lock, NULL);\n\n    return mgr;\n}\n\nint\nseaf_repo_manager_init (SeafRepoManager *mgr)\n{\n    if (checkdir_with_mkdir (mgr->index_dir) < 0) {\n        seaf_warning (\"Index dir %s does not exist and is unable to create\\n\",\n                   mgr->index_dir);\n        return -1;\n    }\n\n    /* Load all the repos into memory on the client side. */\n    load_repos (mgr, mgr->seaf->seaf_dir);\n\n    /* Load folder permissions from db. */\n    init_folder_perms (mgr);\n\n    return 0;\n}\n\nstatic void\nwatch_repos (SeafRepoManager *mgr)\n{\n    GHashTableIter iter;\n    SeafRepo *repo;\n    gpointer key, value;\n\n    g_hash_table_iter_init (&iter, mgr->priv->repo_hash);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        repo = value;\n        if (repo->auto_sync && !repo->worktree_invalid && (repo->sync_interval == 0)) {\n            if (seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id, repo->worktree) < 0) {\n                seaf_warning (\"failed to watch repo %s.\\n\", repo->id);\n                /* If we fail to add watch at the beginning, sync manager\n                 * will periodically check repo status and retry.\n                 */\n            }\n        }\n    }\n}\n\n#define REMOVE_OBJECTS_BATCH 1000\n\nstatic int\nremove_store (const char *top_store_dir, const char *store_id, int *count)\n{\n    char *obj_dir = NULL;\n    GDir *dir1, *dir2;\n    const char *dname1, *dname2;\n    char *path1, *path2;\n\n    obj_dir = g_build_filename (top_store_dir, store_id, NULL);\n\n    dir1 = g_dir_open (obj_dir, 0, NULL);\n    if (!dir1) {\n        g_free (obj_dir);\n        return 0;\n    }\n\n    seaf_message (\"Removing store %s\\n\", obj_dir);\n\n    while ((dname1 = g_dir_read_name(dir1)) != NULL) {\n        path1 = g_build_filename (obj_dir, dname1, NULL);\n\n        dir2 = g_dir_open (path1, 0, NULL);\n        if (!dir2) {\n            seaf_warning (\"Failed to open obj dir %s.\\n\", path1);\n            g_dir_close (dir1);\n            g_free (path1);\n            g_free (obj_dir);\n            return -1;\n        }\n\n        while ((dname2 = g_dir_read_name(dir2)) != NULL) {\n            path2 = g_build_filename (path1, dname2, NULL);\n            g_unlink (path2);\n\n            /* To prevent using too much IO, only remove 1000 objects per 5 seconds.\n             */\n            if (++(*count) > REMOVE_OBJECTS_BATCH) {\n                g_usleep (5 * G_USEC_PER_SEC);\n                *count = 0;\n            }\n\n            g_free (path2);\n        }\n        g_dir_close (dir2);\n\n        g_rmdir (path1);\n        g_free (path1);\n    }\n\n    g_dir_close (dir1);\n    g_rmdir (obj_dir);\n    g_free (obj_dir);\n\n    return 0;\n}\n\nstatic void\ncleanup_deleted_stores_by_type (const char *type)\n{\n    char *top_store_dir;\n    const char *repo_id;\n\n    top_store_dir = g_build_filename (seaf->seaf_dir, \"deleted_store\", type, NULL);\n\n    GError *error = NULL;\n    GDir *dir = g_dir_open (top_store_dir, 0, &error);\n    if (!dir) {\n        seaf_warning (\"Failed to open store dir %s: %s.\\n\",\n                      top_store_dir, error->message);\n        g_free (top_store_dir);\n        return;\n    }\n\n    int count = 0;\n    while ((repo_id = g_dir_read_name(dir)) != NULL) {\n        remove_store (top_store_dir, repo_id, &count);\n    }\n\n    g_free (top_store_dir);\n    g_dir_close (dir);\n}\n\nstatic void *\ncleanup_deleted_stores (void *vdata)\n{\n    while (1) {\n        cleanup_deleted_stores_by_type (\"commits\");\n        cleanup_deleted_stores_by_type (\"fs\");\n        cleanup_deleted_stores_by_type (\"blocks\");\n        g_usleep (60 * G_USEC_PER_SEC);\n    }\n    return NULL;\n}\n\nstruct _GetBlockAux {\n    char *content;\n    int size;\n};\ntypedef struct _GetBlockAux GetBlockAux;\n\nstatic size_t\nget_enc_block_cb (void *contents, size_t size, size_t nmemb, void *userp)\n{\n    size_t realsize = size * nmemb;\n    GetBlockAux *aux = userp;\n    int ret = realsize;\n\n    aux->content = g_realloc (aux->content, aux->size + realsize);\n    if (!aux->content) {\n        seaf_warning (\"Not enough memory.\\n\");\n        return 0;\n    }\n    memcpy (aux->content + aux->size, contents, realsize);\n    aux->size += realsize;\n\n    return ret;\n}\n\nstatic int\ndecrypt_block (SeafRepo *repo, SeafileCrypt *crypt, SeafileCrypt *zero_crypt, const char *block_id)\n{\n    int ret = 0;\n    char *dec_out = NULL;\n    int dec_out_len = -1;\n    GetBlockAux aux;\n    memset (&aux, 0, sizeof(aux));\n\n    int error_id;\n    if (http_tx_manager_get_block(seaf->http_tx_mgr, repo->id,\n                                  block_id, repo->effective_host,\n                                  repo->token, repo->use_fileserver_port,\n                                  &error_id,\n                                  get_enc_block_cb, &aux) < 0) {\n        seaf_warning (\"Failed to get block %s from server.\\n\",\n                      block_id);\n        goto out;\n    }\n\n    int rc = seafile_decrypt (&dec_out, &dec_out_len, aux.content, aux.size, crypt);\n    if (rc != 0) {\n        rc = seafile_decrypt(&dec_out, &dec_out_len, aux.content, aux.size, zero_crypt);\n        if (rc == 0) {\n            ret = -1;\n            goto out;\n        }\n    }\n\n\nout:\n    g_free (dec_out);\n    g_free (aux.content);\n    return ret;\n}\n\nstatic int\ncheck_repo_corrupted_blocks (SeafRepo *repo)\n{\n    SeafCommit *head = NULL;\n    SeafileCrypt *crypt = NULL;\n    SeafileCrypt *zero_crypt = NULL;\n    char path[SEAF_PATH_MAX];\n    char index_path[SEAF_PATH_MAX];\n    struct index_state istate;\n    gboolean not_exist;\n    int ret = 0;\n\n    memset (&istate, 0, sizeof(istate));\n    snprintf (index_path, SEAF_PATH_MAX, \"%s/%s\", repo->manager->index_dir, repo->id);\n\n    if (read_index_from (&istate, index_path, repo->version) < 0) {\n        seaf_warning (\"Failed to load repo index: %s.\\n\", repo->id);\n        return -1;\n    }\n\n    head = seaf_commit_manager_get_commit (seaf->commit_mgr,\n                                           repo->id, repo->version,\n                                           repo->head->commit_id);\n    if (!head) {\n        ret = -1;\n        seaf_warning (\"Failed to get head for repo %s.\\n\", repo->id);\n        goto out;\n    }\n\n    crypt = seafile_crypt_new (repo->enc_version, repo->enc_key, repo->enc_iv);\n    unsigned char enc_key[32], enc_iv[16];\n    memset (enc_key, 0, sizeof(enc_key)); \n    memset (enc_iv, 0, sizeof(enc_iv)); \n    zero_crypt = seafile_crypt_new (repo->enc_version, enc_key, enc_iv);\n\n    struct cache_entry **ce_array = istate.cache;\n    struct cache_entry *ce;\n    SeafStat st;\n    unsigned char sha1[20];\n    int i;\n\n    for (i = 0; i < istate.cache_nr; ++i) {\n        ce = ce_array[i];\n        if (S_ISDIR (ce->ce_mode)) {\n            continue;\n        }\n\n        snprintf (path, SEAF_PATH_MAX, \"%s/%s\", repo->worktree, ce->name);\n        not_exist = FALSE;\n        int rc = seaf_stat (path, &st);\n        if (rc < 0 && errno == ENOENT) {\n            not_exist = TRUE;\n        } else if (rc == 0 && st.st_size == 0) {\n            continue;\n        }\n\n        GError *error = NULL;\n        char *file_id = seaf_fs_manager_get_seafile_id_by_path (seaf->fs_mgr, repo->id, repo->version, head->root_id, ce->name, &error);\n        if (error) {\n            g_clear_error (&error);\n        }\n        if (!file_id) {\n            seaf_warning (\"Failed to get seafile id by path: %s\\n\", path);\n            continue;\n        }\n        // When a local file exists, the client checks whether the file is correctly encrypted by reindexing it and comparing the fileID.\n        // Since files can be uploaded either via the web or the client, if the fileID generated using the CDC algorithm does not match, the client will perform a second check using a non-CDC algorithm.\n        // If the fileID still does not match, the file is considered to have corrupted encryption.\n\n        // If the local file does not exist, it may have failed to checkout due to decryption errors. In this case, the client attempts to retrieve the file blocks from the server and decrypt them.       // If the file can be decrypted using an all-zero key, it is considered to have corrupted encryption.\n        if (!not_exist) {\n            char new_file_id[41];\n            gint64 size;\n            // Reindex file blocks using cdc.\n            if (seaf_fs_manager_index_blocks (seaf->fs_mgr, repo->id, repo->version, path, sha1, &size, crypt, TRUE, TRUE) < 0) {\n                g_free (file_id);\n                seaf_warning (\"Failed to index file: %s\\n\", path);\n                continue;\n            }\n            rawdata_to_hex (sha1, new_file_id, 20);\n            if (g_strcmp0 (file_id, new_file_id) == 0) {\n                g_free (file_id);\n                continue;\n            }\n\n            // Reindex file block without using cdc.\n            if (seaf_fs_manager_index_blocks (seaf->fs_mgr, repo->id, repo->version, path, sha1, &size, crypt, TRUE, FALSE) < 0) {\n                g_free (file_id);\n                seaf_warning (\"Failed to index file: %s\\n\", path);\n                continue;\n            }\n            rawdata_to_hex (sha1, new_file_id, 20);\n            if (g_strcmp0 (file_id, new_file_id) == 0) {\n                g_free (file_id);\n                continue;\n            }\n            g_free (file_id);\n            if (ce->ce_mtime.sec != st.st_mtime) {\n                continue;\n            }\n            seaf_warning (\"Failed to compare file id for path %s, blocks are corrupted.\\n\", path);\n            repo->empty_enc_key = TRUE;\n            save_repo_property (seaf->repo_mgr, repo->id, REPO_PROP_EMPTY_ENC_KEY, \"true\");\n            break;\n        } else if (ret == 0 && (ce->ce_ctime.sec == 0 && ce_stage(ce) == 0)) {\n            // If ce->ctime is 0 and stage is 0, it was not successfully checked out.\n            Seafile *seafile = seaf_fs_manager_get_seafile (seaf->fs_mgr, repo->id, repo->version, file_id);\n            if (!seafile) {\n                g_free (file_id);\n                seaf_warning (\"Failed to find file %s in repo %s\\n\", file_id, repo->id);\n                continue;\n            }\n            if (seafile->n_blocks == 0) {\n                g_free (file_id);\n                continue;\n            }\n            char *block_id = seafile->blk_sha1s[0];\n            if (decrypt_block (repo, crypt, zero_crypt, block_id) < 0) {\n                g_free (file_id);\n                seaf_warning (\"Failed to compare block_id %s, block is corrupted.\\n\", block_id);\n                repo->empty_enc_key = TRUE;\n                save_repo_property (seaf->repo_mgr, repo->id, REPO_PROP_EMPTY_ENC_KEY, \"true\");\n                break;\n\n            }\n            g_free (file_id);\n        }\n    }\n\n\nout:\n    discard_index (&istate);\n    seaf_commit_unref (head);\n    g_free (crypt);\n    g_free (zero_crypt);\n    return ret;\n}\n\nstatic char *\nload_repo_property (SeafRepoManager *manager,\n                    const char *repo_id,\n                    const char *key);\n\n// The older versions of the client may fail to import the encryption key due to missing records in RepoKeys or database errors.\n// As a result, some files might be encrypted using an all-zero key.\n// This check will reindex the local files to determine whether any of files were encrypted with an incorrect key.\nstatic void *\ncheck_corrupted_enc_blocks (void *vdata)\n{\n    // This feature is disabled by default. Comment out this check to enable it..\n    if (!seaf->check_enc_blocks) {\n        return NULL;\n    }\n\n    // Wait for a while, and auto sync will set the repo's effective_host property.\n    g_usleep (60  * G_USEC_PER_SEC);\n\n    GList *ptr, *repos = seaf_repo_manager_get_repo_list(seaf->repo_mgr, -1, -1);\n    if (!repos) {\n        return NULL;\n    }\n\n    for (ptr = repos; ptr; ptr = ptr->next) {\n        SeafRepo *repo = (SeafRepo*)ptr->data;\n        char *value = load_repo_property (seaf->repo_mgr, repo->id, REPO_PROP_CHECK_BLOCKS);\n        if (g_strcmp0 (value, \"true\") == 0) {\n            g_free (value);\n            continue;\n        }\n        g_free (value);\n\n        if (!repo->encrypted || repo->enc_key[0] == '\\0') {\n            continue;\n        }\n        if (!repo->effective_host) {\n            continue;\n        }\n        seaf_message (\"Start to check enc repo blocks for repo %s.\\n\", repo->id);\n        if (check_repo_corrupted_blocks(repo) < 0) {\n            continue;\n        }\n        seaf_message (\"Finish to check enc repo blocks for repo %s.\\n\", repo->id);\n        save_repo_property (seaf->repo_mgr, repo->id, REPO_PROP_CHECK_BLOCKS, \"true\");\n    }\n\n    g_list_free (repos);\n    return NULL;\n}\n\nint\nseaf_repo_manager_start (SeafRepoManager *mgr)\n{\n    pthread_t tid;\n    pthread_attr_t attr;\n    pthread_attr_init(&attr);\n    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);\n    int rc;\n\n    watch_repos (mgr);\n\n    rc = pthread_create (&tid, &attr, cleanup_deleted_stores, NULL);\n    if (rc != 0) {\n        seaf_warning (\"Failed to start cleanup thread: %s\\n\", strerror(rc));\n    }\n\n    rc = pthread_create (&tid, &attr, lock_office_file_worker,\n                         mgr->priv->lock_office_job_queue);\n    if (rc != 0) {\n        seaf_warning (\"Failed to start lock office file thread: %s\\n\", strerror(rc));\n    }\n\n    rc = pthread_create (&tid, &attr, check_corrupted_enc_blocks, NULL);\n    if (rc != 0) {\n        seaf_warning (\"Failed to start check enc blocks thread: %s\\n\", strerror(rc));\n    }\n\n    return 0;\n}\n\nSeafRepo*\nseaf_repo_manager_create_new_repo (SeafRepoManager *mgr,\n                                   const char *name,\n                                   const char *desc)\n{\n    SeafRepo *repo;\n    char *repo_id;\n    \n    repo_id = gen_uuid ();\n    repo = seaf_repo_new (repo_id, name, desc);\n    if (!repo) {\n        g_free (repo_id);\n        return NULL;\n    }\n    g_free (repo_id);\n\n    /* we directly create dir because it shouldn't exist */\n    /* if (seaf_repo_mkdir (repo, base) < 0) { */\n    /*     seaf_repo_free (repo); */\n    /*     goto out; */\n    /* } */\n\n    seaf_repo_manager_add_repo (mgr, repo);\n    return repo;\n}\n\nint\nseaf_repo_manager_add_repo (SeafRepoManager *manager,\n                            SeafRepo *repo)\n{\n    char sql[256];\n    sqlite3 *db = manager->priv->db;\n\n    pthread_mutex_lock (&manager->priv->db_lock);\n\n    snprintf (sql, sizeof(sql), \"REPLACE INTO Repo VALUES ('%s');\", repo->id);\n    sqlite_query_exec (db, sql);\n\n    pthread_mutex_unlock (&manager->priv->db_lock);\n\n    /* There may be a \"deletion record\" for this repo when it was deleted\n     * last time.\n     */\n    seaf_repo_manager_remove_garbage_repo (manager, repo->id);\n\n    repo->manager = manager;\n\n    if (pthread_rwlock_wrlock (&manager->priv->lock) < 0) {\n        seaf_warning (\"[repo mgr] failed to lock repo cache.\\n\");\n        return -1;\n    }\n\n    g_hash_table_insert (manager->priv->repo_hash, g_strdup(repo->id), repo);\n\n    pthread_rwlock_unlock (&manager->priv->lock);\n\n    return 0;\n}\n\nint\nseaf_repo_manager_mark_repo_deleted (SeafRepoManager *mgr, SeafRepo *repo)\n{\n    char sql[256];\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    snprintf (sql, sizeof(sql), \"INSERT INTO DeletedRepo VALUES ('%s')\",\n              repo->id);\n    if (sqlite_query_exec (mgr->priv->db, sql) < 0) {\n        pthread_mutex_unlock (&mgr->priv->db_lock);\n        return -1;\n    }\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    repo->delete_pending = TRUE;\n\n    return 0;\n}\n\nstatic gboolean\nget_garbage_repo_id (sqlite3_stmt *stmt, void *vid_list)\n{\n    GList **ret = vid_list;\n    char *repo_id;\n\n    repo_id = g_strdup((const char *)sqlite3_column_text (stmt, 0));\n    *ret = g_list_prepend (*ret, repo_id);\n\n    return TRUE;\n}\n\nGList *\nseaf_repo_manager_list_garbage_repos (SeafRepoManager *mgr)\n{\n    GList *repo_ids = NULL;\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    sqlite_foreach_selected_row (mgr->priv->db,\n                                 \"SELECT repo_id FROM GarbageRepos\",\n                                 get_garbage_repo_id, &repo_ids);\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    return repo_ids;\n}\n\nvoid\nseaf_repo_manager_remove_garbage_repo (SeafRepoManager *mgr, const char *repo_id)\n{\n    char sql[256];\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    snprintf (sql, sizeof(sql), \"DELETE FROM GarbageRepos WHERE repo_id='%s'\",\n              repo_id);\n    sqlite_query_exec (mgr->priv->db, sql);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n}\n\nvoid\nseaf_repo_manager_remove_repo_ondisk (SeafRepoManager *mgr,\n                                      const char *repo_id,\n                                      gboolean add_deleted_record)\n{\n    char sql[256];\n\n    /* We don't need to care about I/O errors here, since we can\n     * GC any unreferenced repo data later.\n     */\n\n    if (add_deleted_record) {\n        snprintf (sql, sizeof(sql), \"REPLACE INTO GarbageRepos VALUES ('%s')\",\n                  repo_id);\n        if (sqlite_query_exec (mgr->priv->db, sql) < 0)\n            goto out;\n    }\n\n    /* Once the item in Repo table is deleted, the repo is gone.\n     * This is the \"commit point\".\n     */\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    snprintf (sql, sizeof(sql), \"DELETE FROM Repo WHERE repo_id = '%s'\", repo_id);\n    if (sqlite_query_exec (mgr->priv->db, sql) < 0)\n        goto out;\n\n    snprintf (sql, sizeof(sql), \n              \"DELETE FROM DeletedRepo WHERE repo_id = '%s'\", repo_id);\n    sqlite_query_exec (mgr->priv->db, sql);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    /* remove index */\n    char path[SEAF_PATH_MAX];\n    snprintf (path, SEAF_PATH_MAX, \"%s/%s\", mgr->index_dir, repo_id);\n    seaf_util_unlink (path);\n\n    /* remove branch */\n    GList *p;\n    GList *branch_list = \n        seaf_branch_manager_get_branch_list (seaf->branch_mgr, repo_id);\n    for (p = branch_list; p; p = p->next) {\n        SeafBranch *b = (SeafBranch *)p->data;\n        seaf_repo_manager_branch_repo_unmap (mgr, b);\n        seaf_branch_manager_del_branch (seaf->branch_mgr, repo_id, b->name);\n    }\n    seaf_branch_list_free (branch_list);\n\n    /* delete repo property firstly */\n    seaf_repo_manager_del_repo_property (mgr, repo_id);\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    snprintf (sql, sizeof(sql), \"DELETE FROM RepoPasswd WHERE repo_id = '%s'\", \n              repo_id);\n    sqlite_query_exec (mgr->priv->db, sql);\n    snprintf (sql, sizeof(sql), \"DELETE FROM RepoKeys WHERE repo_id = '%s'\", \n              repo_id);\n    sqlite_query_exec (mgr->priv->db, sql);\n\n    snprintf (sql, sizeof(sql), \"DELETE FROM MergeInfo WHERE repo_id = '%s'\", \n              repo_id);\n    sqlite_query_exec (mgr->priv->db, sql);\n\n    snprintf (sql, sizeof(sql), \"DELETE FROM LockedFiles WHERE repo_id = '%s'\",\n              repo_id);\n    sqlite_query_exec (mgr->priv->db, sql);\n\n    snprintf (sql, sizeof(sql), \"DELETE FROM FolderUserPerms WHERE repo_id = '%s'\", \n              repo_id);\n    sqlite_query_exec (mgr->priv->db, sql);\n\n    snprintf (sql, sizeof(sql), \"DELETE FROM FolderGroupPerms WHERE repo_id = '%s'\", \n              repo_id);\n    sqlite_query_exec (mgr->priv->db, sql);\n\n    snprintf (sql, sizeof(sql), \"DELETE FROM FolderPermTimestamp WHERE repo_id = '%s'\", \n              repo_id);\n    sqlite_query_exec (mgr->priv->db, sql);\n\n    seaf_filelock_manager_remove (seaf->filelock_mgr, repo_id);\n\nout:\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n}\n\nstatic char *\ngen_deleted_store_path (const char *type, const char *repo_id)\n{\n    int n = 1;\n    char *path = NULL;\n    char *name = NULL;\n\n    path = g_build_filename (seaf->deleted_store, type, repo_id, NULL);\n    while (g_access(path, F_OK) == 0 && n < 10) {\n        g_free (path);\n        name = g_strdup_printf (\"%s(%d)\", repo_id, n);\n        path = g_build_filename (seaf->deleted_store, type, name, NULL);\n        g_free (name);\n        ++n;\n    }\n\n    if (n == 10) {\n        g_free (path);\n        return NULL;\n    }\n\n    return path;\n}\n\nvoid\nseaf_repo_manager_move_repo_store (SeafRepoManager *mgr,\n                                   const char *type,\n                                   const char *repo_id)\n{\n    char *src = NULL;\n    char *dst = NULL;\n\n    src = g_build_filename (seaf->seaf_dir, \"storage\", type, repo_id, NULL);\n    dst = gen_deleted_store_path (type, repo_id);\n    if (dst) {\n        g_rename (src, dst);\n    }\n    g_free (src);\n    g_free (dst);\n}\n\n/* Move commits, fs stores into \"deleted_store\" directory. */\nstatic void\nmove_repo_stores (SeafRepoManager *mgr, SeafRepo *repo)\n{\n    seaf_repo_manager_move_repo_store (mgr, \"commits\", repo->id);\n    seaf_repo_manager_move_repo_store (mgr, \"fs\", repo->id);\n}\n\nint\nseaf_repo_manager_del_repo (SeafRepoManager *mgr,\n                            SeafRepo *repo)\n{\n    seaf_repo_manager_remove_repo_ondisk (mgr, repo->id,\n                                          (repo->version > 0) ? TRUE : FALSE);\n\n    seaf_sync_manager_remove_active_path_info (seaf->sync_mgr, repo->id);\n\n    remove_folder_perms (mgr, repo->id);\n\n    move_repo_stores (mgr, repo);\n\n    if (pthread_rwlock_wrlock (&mgr->priv->lock) < 0) {\n        seaf_warning (\"[repo mgr] failed to lock repo cache.\\n\");\n        return -1;\n    }\n\n    g_hash_table_remove (mgr->priv->repo_hash, repo->id);\n\n    pthread_rwlock_unlock (&mgr->priv->lock);\n\n#if defined WIN32 || defined __APPLE__ || defined COMPILE_LINUX_WS\n    seaf_notif_manager_unsubscribe_repo (seaf->notif_mgr, repo);\n#endif\n\n    seaf_repo_free (repo);\n\n    return 0;\n}\n\n/*\n  Return the internal Repo in hashtable. The caller should not free the returned Repo.\n */\nSeafRepo*\nseaf_repo_manager_get_repo (SeafRepoManager *manager, const gchar *id)\n{\n    SeafRepo *res;\n\n    if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) {\n        seaf_warning (\"[repo mgr] failed to lock repo cache.\\n\");\n        return NULL;\n    }\n\n    res = g_hash_table_lookup (manager->priv->repo_hash, id);\n\n    pthread_rwlock_unlock (&manager->priv->lock);\n\n    if (res && !res->delete_pending)\n        return res;\n\n    return NULL;\n}\n\ngboolean\nseaf_repo_manager_repo_exists (SeafRepoManager *manager, const gchar *id)\n{\n    SeafRepo *res;\n\n    if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) {\n        seaf_warning (\"[repo mgr] failed to lock repo cache.\\n\");\n        return FALSE;\n    }\n\n    res = g_hash_table_lookup (manager->priv->repo_hash, id);\n\n    pthread_rwlock_unlock (&manager->priv->lock);\n\n    if (res && !res->delete_pending)\n        return TRUE;\n    \n    return FALSE;\n}\n\nstatic int\nsave_branch_repo_map (SeafRepoManager *manager, SeafBranch *branch)\n{\n    char *sql;\n    sqlite3 *db = manager->priv->db;\n\n    pthread_mutex_lock (&manager->priv->db_lock);\n\n    sql = sqlite3_mprintf (\"REPLACE INTO RepoBranch VALUES (%Q, %Q)\",\n                           branch->repo_id, branch->name);\n    sqlite_query_exec (db, sql);\n    sqlite3_free (sql);\n\n    pthread_mutex_unlock (&manager->priv->db_lock);\n\n    return 0;\n}\n\nint\nseaf_repo_manager_branch_repo_unmap (SeafRepoManager *manager, SeafBranch *branch)\n{\n    char *sql;\n    sqlite3 *db = manager->priv->db;\n\n    pthread_mutex_lock (&manager->priv->db_lock);\n\n    sql = sqlite3_mprintf (\"DELETE FROM RepoBranch WHERE branch_name = %Q\"\n                           \" AND repo_id = %Q\",\n                           branch->name, branch->repo_id);\n    if (sqlite_query_exec (db, sql) < 0) {\n        seaf_warning (\"Unmap branch repo failed\\n\");\n        pthread_mutex_unlock (&manager->priv->db_lock);\n        sqlite3_free (sql);\n        return -1;\n    }\n\n    sqlite3_free (sql);\n    pthread_mutex_unlock (&manager->priv->db_lock);\n\n    return 0;\n}\n\nstatic void\nload_repo_commit (SeafRepoManager *manager,\n                  SeafRepo *repo,\n                  SeafBranch *branch)\n{\n    SeafCommit *commit;\n\n    commit = seaf_commit_manager_get_commit_compatible (manager->seaf->commit_mgr,\n                                                        repo->id,\n                                                        branch->commit_id);\n    if (!commit) {\n        seaf_warning (\"Commit %s is missing\\n\", branch->commit_id);\n        repo->is_corrupted = TRUE;\n        return;\n    }\n\n    set_head_common (repo, branch);\n    seaf_repo_from_commit (repo, commit);\n\n    seaf_commit_unref (commit);\n}\n\nstatic gboolean\nload_keys_cb (sqlite3_stmt *stmt, void *vrepo)\n{\n    SeafRepo *repo = vrepo;\n    const char *key, *iv;\n\n    key = (const char *)sqlite3_column_text(stmt, 0);\n    iv = (const char *)sqlite3_column_text(stmt, 1);\n\n    if (repo->enc_version == 1) {\n        hex_to_rawdata (key, repo->enc_key, 16);\n        hex_to_rawdata (iv, repo->enc_iv, 16);\n    } else if (repo->enc_version >= 2) {\n        hex_to_rawdata (key, repo->enc_key, 32);\n        hex_to_rawdata (iv, repo->enc_iv, 16);\n    }\n\n    return FALSE;\n}\n\nstatic int\nload_repo_passwd (SeafRepoManager *manager, SeafRepo *repo)\n{\n    sqlite3 *db = manager->priv->db;\n    char sql[256];\n    int n;\n\n    pthread_mutex_lock (&manager->priv->db_lock);\n\n    snprintf (sql, sizeof(sql), \n              \"SELECT key, iv FROM RepoKeys WHERE repo_id='%s'\",\n              repo->id);\n    n = sqlite_foreach_selected_row (db, sql, load_keys_cb, repo);\n    if (n < 0) {\n        pthread_mutex_unlock (&manager->priv->db_lock);\n        return -1;\n    }\n\n    pthread_mutex_unlock (&manager->priv->db_lock);\n\n    return 0;\n    \n}\n\nstatic gboolean\nload_property_cb (sqlite3_stmt *stmt, void *pvalue)\n{\n    char **value = pvalue;\n\n    char *v = (char *) sqlite3_column_text (stmt, 0);\n    *value = g_strdup (v);\n\n    /* Only one result. */\n    return FALSE;\n}\n\nstatic char *\nload_repo_property (SeafRepoManager *manager,\n                    const char *repo_id,\n                    const char *key)\n{\n    sqlite3 *db = manager->priv->db;\n    char sql[256];\n    char *value = NULL;\n\n    pthread_mutex_lock (&manager->priv->db_lock);\n\n    snprintf(sql, 256, \"SELECT value FROM RepoProperty WHERE \"\n             \"repo_id='%s' and key='%s'\", repo_id, key);\n    if (sqlite_foreach_selected_row (db, sql, load_property_cb, &value) < 0) {\n        seaf_warning (\"Error read property %s for repo %s.\\n\", key, repo_id);\n        pthread_mutex_unlock (&manager->priv->db_lock);\n        return NULL;\n    }\n\n    pthread_mutex_unlock (&manager->priv->db_lock);\n\n    return value;\n}\n\nstatic gboolean\nload_branch_cb (sqlite3_stmt *stmt, void *vrepo)\n{\n    SeafRepo *repo = vrepo;\n    SeafRepoManager *manager = repo->manager;\n\n    char *branch_name = (char *) sqlite3_column_text (stmt, 0);\n    SeafBranch *branch =\n        seaf_branch_manager_get_branch (manager->seaf->branch_mgr,\n                                        repo->id, branch_name);\n    if (branch == NULL) {\n        seaf_warning (\"Broken branch name for repo %s\\n\", repo->id); \n        repo->is_corrupted = TRUE;\n        return FALSE;\n    }\n    load_repo_commit (manager, repo, branch);\n    seaf_branch_unref (branch);\n\n    /* Only one result. */\n    return FALSE;\n}\n\nstatic gboolean\nis_wt_repo_name_same (const char *worktree, const char *repo_name)\n{\n    char *basename = g_path_get_basename (worktree);\n    gboolean ret = FALSE;\n    ret = (g_strcmp0 (basename, repo_name) == 0);\n    g_free (basename);\n    return ret;\n}\n\nstatic SeafRepo *\nload_repo (SeafRepoManager *manager, const char *repo_id)\n{\n    char sql[256];\n\n    SeafRepo *repo = seaf_repo_new(repo_id, NULL, NULL);\n    if (!repo) {\n        seaf_warning (\"[repo mgr] failed to alloc repo.\\n\");\n        return NULL;\n    }\n\n    repo->manager = manager;\n\n    snprintf(sql, 256, \"SELECT branch_name FROM RepoBranch WHERE repo_id='%s'\",\n             repo->id);\n    if (sqlite_foreach_selected_row (manager->priv->db, sql, \n                                     load_branch_cb, repo) < 0) {\n        seaf_warning (\"Error read branch for repo %s.\\n\", repo->id);\n        seaf_repo_free (repo);\n        return NULL;\n    }\n\n    /* If repo head is set but failed to load branch or commit. */\n    if (repo->is_corrupted) {\n        seaf_repo_free (repo);\n        /* remove_repo_ondisk (manager, repo_id); */\n        return NULL;\n    }\n\n    /* Repo head may be not set if it's just cloned but not checked out yet. */\n    if (repo->head == NULL) {\n        /* the repo do not have a head branch, try to load 'master' branch */\n        SeafBranch *branch =\n            seaf_branch_manager_get_branch (manager->seaf->branch_mgr,\n                                            repo->id, \"master\");\n        if (branch != NULL) {\n             SeafCommit *commit;\n\n             commit =\n                 seaf_commit_manager_get_commit_compatible (manager->seaf->commit_mgr,\n                                                            repo->id,\n                                                            branch->commit_id);\n             if (commit) {\n                 seaf_repo_from_commit (repo, commit);\n                 seaf_commit_unref (commit);\n             } else {\n                 seaf_warning (\"[repo-mgr] Can not find commit %s\\n\",\n                            branch->commit_id);\n                 repo->is_corrupted = TRUE;\n             }\n\n             seaf_branch_unref (branch);\n        } else {\n            seaf_warning (\"[repo-mgr] Failed to get branch master\");\n            repo->is_corrupted = TRUE;\n        }\n    }\n\n    if (repo->is_corrupted) {\n        seaf_repo_free (repo);\n        /* remove_repo_ondisk (manager, repo_id); */\n        return NULL;\n    }\n\n    load_repo_passwd (manager, repo);\n\n    char *value;\n\n    value = load_repo_property (manager, repo->id, REPO_AUTO_SYNC);\n    if (g_strcmp0(value, \"false\") == 0) {\n        repo->auto_sync = 0;\n    }\n    g_free (value);\n\n    repo->worktree = load_repo_property (manager, repo->id, \"worktree\");\n    if (repo->worktree)\n        repo->worktree_invalid = FALSE;\n\n    repo->email = load_repo_property (manager, repo->id, REPO_PROP_EMAIL);\n    repo->username = load_repo_property (manager, repo->id, REPO_PROP_USERNAME);\n    repo->token = load_repo_property (manager, repo->id, REPO_PROP_TOKEN);\n\n    /* May be NULL if this property is not set in db. */\n    repo->server_url = load_repo_property (manager, repo->id, REPO_PROP_SERVER_URL);\n\n    if (repo->head != NULL && seaf_repo_check_worktree (repo) < 0) {\n        if (seafile_session_config_get_allow_invalid_worktree(seaf)) {\n            seaf_warning (\"Worktree for repo \\\"%s\\\" is invalid, but still keep it.\\n\",\n                          repo->name);\n            repo->worktree_invalid = TRUE;\n        } else {\n            seaf_message (\"Worktree for repo \\\"%s\\\" is invalid, delete it.\\n\",\n                          repo->name);\n            seaf_repo_manager_del_repo (manager, repo);\n            return NULL;\n        }\n    }\n\n    /* load readonly property */\n    value = load_repo_property (manager, repo->id, REPO_PROP_IS_READONLY);\n    if (g_strcmp0(value, \"true\") == 0)\n        repo->is_readonly = TRUE;\n    else\n        repo->is_readonly = FALSE;\n    g_free (value);\n\n    /* load sync period property */\n    value = load_repo_property (manager, repo->id, REPO_PROP_SYNC_INTERVAL);\n    if (value) {\n        int interval = atoi(value);\n        if (interval > 0)\n            repo->sync_interval = interval;\n    }\n    g_free (value);\n\n    if (repo->worktree) {\n        gboolean wt_repo_name_same = is_wt_repo_name_same (repo->worktree, repo->name);\n        value = load_repo_property (manager, repo->id, REPO_SYNC_WORKTREE_NAME);\n        if (g_strcmp0 (value, \"true\") == 0) {\n            /* If need to sync worktree name with library name, update worktree folder name. */\n            if (!wt_repo_name_same)\n                update_repo_worktree_name (repo, repo->name, FALSE);\n        } else {\n            /* If an existing repo's worktree folder name is the same as repo name, but\n             * sync_worktree_name property is not set, set it.\n             */\n            if (wt_repo_name_same)\n                save_repo_property (manager, repo->id, REPO_SYNC_WORKTREE_NAME, \"true\");\n        }\n        g_free (value);\n    }\n\n    /* load empty enc key property */\n    value = load_repo_property (manager, repo->id, REPO_PROP_EMPTY_ENC_KEY);\n    if (g_strcmp0(value, \"true\") == 0)\n        repo->empty_enc_key = TRUE;\n    else\n        repo->empty_enc_key = FALSE;\n    g_free (value);\n\n    g_hash_table_insert (manager->priv->repo_hash, g_strdup(repo->id), repo);\n\n    return repo;\n}\n\nstatic sqlite3*\nopen_db (SeafRepoManager *manager, const char *seaf_dir)\n{\n    sqlite3 *db;\n    char *db_path;\n\n    db_path = g_build_filename (seaf_dir, \"repo.db\", NULL);\n    if (sqlite_open_db (db_path, &db) < 0)\n        return NULL;\n    g_free (db_path);\n    manager->priv->db = db;\n\n    char *sql = \"CREATE TABLE IF NOT EXISTS Repo (repo_id TEXT PRIMARY KEY);\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE TABLE IF NOT EXISTS DeletedRepo (repo_id TEXT PRIMARY KEY);\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE TABLE IF NOT EXISTS RepoBranch (\"\n        \"repo_id TEXT PRIMARY KEY, branch_name TEXT);\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE TABLE IF NOT EXISTS RepoLanToken (\"\n        \"repo_id TEXT PRIMARY KEY, token TEXT);\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE TABLE IF NOT EXISTS RepoTmpToken (\"\n        \"repo_id TEXT, peer_id TEXT, token TEXT, timestamp INTEGER, \"\n        \"PRIMARY KEY (repo_id, peer_id));\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE TABLE IF NOT EXISTS RepoPasswd \"\n        \"(repo_id TEXT PRIMARY KEY, passwd TEXT NOT NULL);\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE TABLE IF NOT EXISTS RepoKeys \"\n        \"(repo_id TEXT PRIMARY KEY, key TEXT NOT NULL, iv TEXT NOT NULL);\";\n    sqlite_query_exec (db, sql);\n    \n    sql = \"CREATE TABLE IF NOT EXISTS RepoProperty (\"\n        \"repo_id TEXT, key TEXT, value TEXT);\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE INDEX IF NOT EXISTS RepoIndex ON RepoProperty (repo_id);\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE TABLE IF NOT EXISTS MergeInfo (\"\n        \"repo_id TEXT PRIMARY KEY, in_merge INTEGER, branch TEXT);\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE TABLE IF NOT EXISTS CommonAncestor (\"\n        \"repo_id TEXT PRIMARY KEY, ca_id TEXT, head_id TEXT);\";\n    sqlite_query_exec (db, sql);\n\n    /* Version 1 repos will be added to this table after deletion.\n     * GC will scan this table and remove the objects and blocks for the repos.\n     */\n    sql = \"CREATE TABLE IF NOT EXISTS GarbageRepos (repo_id TEXT PRIMARY KEY);\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE TABLE IF NOT EXISTS LockedFiles (repo_id TEXT, path TEXT, \"\n        \"operation TEXT, old_mtime INTEGER, file_id TEXT, new_path TEXT, \"\n        \"PRIMARY KEY (repo_id, path));\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE TABLE IF NOT EXISTS FolderUserPerms (\"\n        \"repo_id TEXT, path TEXT, permission TEXT);\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE INDEX IF NOT EXISTS folder_user_perms_repo_id_idx \"\n        \"ON FolderUserPerms (repo_id);\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE TABLE IF NOT EXISTS FolderGroupPerms (\"\n        \"repo_id TEXT, path TEXT, permission TEXT);\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE INDEX IF NOT EXISTS folder_group_perms_repo_id_idx \"\n        \"ON FolderGroupPerms (repo_id);\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE TABLE IF NOT EXISTS FolderPermTimestamp (\"\n        \"repo_id TEXT, timestamp INTEGER, PRIMARY KEY (repo_id));\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE TABLE IF NOT EXISTS ServerProperty (\"\n        \"server_url TEXT, key TEXT, value TEXT, PRIMARY KEY (server_url, key));\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE INDEX IF NOT EXISTS ServerIndex ON ServerProperty (server_url);\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE TABLE IF NOT EXISTS FileSyncError (\"\n        \"id INTEGER PRIMARY KEY AUTOINCREMENT, repo_id TEXT, repo_name TEXT, \"\n        \"path TEXT, err_id INTEGER, timestamp INTEGER);\";\n    sqlite_query_exec (db, sql);\n\n    sql = \"CREATE INDEX IF NOT EXISTS FileSyncErrorIndex ON FileSyncError (repo_id, path)\";\n    sqlite_query_exec (db, sql);\n\n    return db;\n}\n\nstatic gboolean\nload_repo_cb (sqlite3_stmt *stmt, void *vmanager)\n{\n    SeafRepoManager *manager = vmanager;\n    const char *repo_id;\n\n    repo_id = (const char *) sqlite3_column_text (stmt, 0);\n\n    load_repo (manager, repo_id);\n\n    return TRUE;\n}\n\nstatic gboolean\nremove_deleted_repo (sqlite3_stmt *stmt, void *vmanager)\n{\n    SeafRepoManager *manager = vmanager;\n    const char *repo_id;\n\n    repo_id = (const char *) sqlite3_column_text (stmt, 0);\n\n    seaf_repo_manager_remove_repo_ondisk (manager, repo_id, TRUE);\n\n    return TRUE;\n}\n\nstatic void\nload_repos (SeafRepoManager *manager, const char *seaf_dir)\n{\n    sqlite3 *db = open_db(manager, seaf_dir);\n    if (!db) return;\n\n    char *sql;\n\n    sql = \"SELECT repo_id FROM DeletedRepo\";\n    if (sqlite_foreach_selected_row (db, sql, remove_deleted_repo, manager) < 0) {\n        seaf_warning (\"Error removing deleted repos.\\n\");\n        return;\n    }\n\n    sql = \"SELECT repo_id FROM Repo;\";\n    if (sqlite_foreach_selected_row (db, sql, load_repo_cb, manager) < 0) {\n        seaf_warning (\"Error read repo db.\\n\");\n        return;\n    }\n}\n\nstatic void\nsave_repo_property (SeafRepoManager *manager,\n                    const char *repo_id,\n                    const char *key, const char *value)\n{\n    char *sql;\n    sqlite3 *db = manager->priv->db;\n\n    pthread_mutex_lock (&manager->priv->db_lock);\n\n    sql = sqlite3_mprintf (\"SELECT repo_id FROM RepoProperty WHERE repo_id=%Q AND key=%Q\",\n                           repo_id, key);\n    if (sqlite_check_for_existence(db, sql)) {\n        sqlite3_free (sql);\n        sql = sqlite3_mprintf (\"UPDATE RepoProperty SET value=%Q\"\n                               \"WHERE repo_id=%Q and key=%Q\",\n                               value, repo_id, key);\n        sqlite_query_exec (db, sql);\n        sqlite3_free (sql);\n    } else {\n        sqlite3_free (sql);\n        sql = sqlite3_mprintf (\"INSERT INTO RepoProperty VALUES (%Q, %Q, %Q)\",\n                               repo_id, key, value);\n        sqlite_query_exec (db, sql);\n        sqlite3_free (sql);\n    }\n\n    pthread_mutex_unlock (&manager->priv->db_lock);\n}\n\nint\nseaf_repo_manager_set_repo_property (SeafRepoManager *manager, \n                                     const char *repo_id,\n                                     const char *key,\n                                     const char *value)\n{\n    SeafRepo *repo;\n\n    repo = seaf_repo_manager_get_repo (manager, repo_id);\n    if (!repo)\n        return -1;\n\n    if (strcmp(key, REPO_AUTO_SYNC) == 0) {\n        if (!seaf->started) {\n            seaf_message (\"System not started, skip setting auto sync value.\\n\");\n            return 0;\n        }\n\n        if (g_strcmp0(value, \"true\") == 0) {\n            repo->auto_sync = 1;\n            if (repo->sync_interval == 0)\n                seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id,\n                                            repo->worktree);\n            repo->last_sync_time = 0;\n        } else {\n            repo->auto_sync = 0;\n            if (repo->sync_interval == 0)\n                seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id);\n            /* Cancel current sync task if any. */\n            seaf_sync_manager_cancel_sync_task (seaf->sync_mgr, repo->id);\n            seaf_sync_manager_remove_active_path_info (seaf->sync_mgr, repo->id);\n        }\n    }\n\n    if (strcmp(key, REPO_PROP_SYNC_INTERVAL) == 0) {\n        if (!seaf->started) {\n            seaf_message (\"System not started, skip setting auto sync value.\\n\");\n            return 0;\n        }\n\n        int interval = atoi(value);\n\n        if (interval > 0) {\n            repo->sync_interval = interval;\n            if (repo->auto_sync)\n                seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id);\n        } else {\n            repo->sync_interval = 0;\n            if (repo->auto_sync)\n                seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id,\n                                            repo->worktree);\n        }\n    }\n\n    if (strcmp (key, REPO_PROP_SERVER_URL) == 0) {\n        char *url = canonical_server_url (value);\n\n        if (!repo->server_url) {\n            /* Called from clone-mgr. */\n            repo->server_url = url;\n        } else {\n            g_free (repo->server_url);\n            repo->server_url = url;\n\n            g_free (repo->effective_host);\n            repo->effective_host = NULL;\n        }\n\n        save_repo_property (manager, repo_id, key, url);\n        return 0;\n    }\n\n    if (strcmp (key, REPO_PROP_IS_READONLY) == 0) {\n       if (g_strcmp0 (value, \"true\") == 0)\n           repo->is_readonly = TRUE;\n       else\n           repo->is_readonly = FALSE;\n    }\n\n    if (strcmp (key, REPO_PROP_USERNAME) == 0) {\n        if (!repo->username) {\n            /* Called from clone-mgr. */\n            repo->username = g_strdup (value);\n        } else {\n            g_free (repo->username);\n            repo->username = g_strdup(value);\n        }\n\n        save_repo_property (manager, repo_id, key, value);\n        return 0;\n    }\n\n    save_repo_property (manager, repo_id, key, value);\n    return 0;\n}\n\nchar *\nseaf_repo_manager_get_repo_property (SeafRepoManager *manager, \n                                     const char *repo_id,\n                                     const char *key)\n{\n    return load_repo_property (manager, repo_id, key);\n}\n\nstatic void\nseaf_repo_manager_del_repo_property (SeafRepoManager *manager, \n                                     const char *repo_id)\n{\n    char *sql;\n    sqlite3 *db = manager->priv->db;\n\n    pthread_mutex_lock (&manager->priv->db_lock);\n\n    sql = sqlite3_mprintf (\"DELETE FROM RepoProperty WHERE repo_id = %Q\", repo_id);\n    sqlite_query_exec (db, sql);\n    sqlite3_free (sql);\n\n    pthread_mutex_unlock (&manager->priv->db_lock);\n}\n\nstatic void\nseaf_repo_manager_del_repo_property_by_key (SeafRepoManager *manager,\n                                            const char *repo_id,\n                                            const char *key)\n{\n    char *sql;\n    sqlite3 *db = manager->priv->db;\n\n    pthread_mutex_lock (&manager->priv->db_lock);\n\n    sql = sqlite3_mprintf (\"DELETE FROM RepoProperty \"\n                           \"WHERE repo_id = %Q \"\n                           \"  AND key = %Q\", repo_id, key);\n    sqlite_query_exec (db, sql);\n    sqlite3_free (sql);\n\n    pthread_mutex_unlock (&manager->priv->db_lock);\n}\n\nstatic int\nsave_repo_enc_info (SeafRepoManager *manager,\n                    SeafRepo *repo)\n{\n    sqlite3 *db = manager->priv->db;\n    char sql[512];\n    char key[65], iv[33];\n\n    if (repo->enc_version == 1) {\n        rawdata_to_hex (repo->enc_key, key, 16);\n        rawdata_to_hex (repo->enc_iv, iv, 16);\n    } else if (repo->enc_version >= 2) {\n        rawdata_to_hex (repo->enc_key, key, 32);\n        rawdata_to_hex (repo->enc_iv, iv, 16);\n    }\n\n    snprintf (sql, sizeof(sql), \"REPLACE INTO RepoKeys VALUES ('%s', '%s', '%s')\",\n              repo->id, key, iv);\n    if (sqlite_query_exec (db, sql) < 0)\n        return -1;\n\n    return 0;\n}\n\nint \nseaf_repo_manager_set_repo_passwd (SeafRepoManager *manager,\n                                   SeafRepo *repo,\n                                   const char *passwd)\n{\n    int ret;\n\n    if (seafile_decrypt_repo_enc_key (repo->enc_version, passwd, repo->random_key,\n                                      repo->salt,\n                                      repo->enc_key, repo->enc_iv) < 0)\n        return -1;\n\n    pthread_mutex_lock (&manager->priv->db_lock);\n\n    ret = save_repo_enc_info (manager, repo);\n\n    pthread_mutex_unlock (&manager->priv->db_lock);\n\n    return ret;\n}\n\nint \nseaf_repo_manager_save_repo_enc_info (SeafRepoManager *manager,\n                                      const char *repo_id,\n                                      const char *key,\n                                      const char *iv)\n{\n    sqlite3 *db = manager->priv->db;\n    char sql[512];\n\n    snprintf (sql, sizeof(sql), \"REPLACE INTO RepoKeys VALUES ('%s', '%s', '%s')\",\n              repo_id, key, iv);\n    if (sqlite_query_exec (db, sql) < 0)\n        return -1;\n\n    return 0;\n}\n\nint \nseaf_repo_manager_load_repo_enc_info (SeafRepoManager *manager,\n                                      SeafRepo *repo)\n{\n\n    return load_repo_passwd (manager, repo);\n}\n\nGList*\nseaf_repo_manager_get_repo_list (SeafRepoManager *manager, int start, int limit)\n{\n    GList *repo_list = NULL;\n    GHashTableIter iter;\n    SeafRepo *repo;\n    gpointer key, value;\n\n    if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) {\n        seaf_warning (\"[repo mgr] failed to lock repo cache.\\n\");\n        return NULL;\n    }\n    g_hash_table_iter_init (&iter, manager->priv->repo_hash);\n\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        repo = value;\n        if (!repo->delete_pending)\n            repo_list = g_list_prepend (repo_list, repo);\n    }\n\n    pthread_rwlock_unlock (&manager->priv->lock);\n\n    return repo_list;\n}\n\nGList *\nseaf_repo_manager_get_repo_id_list_by_server (SeafRepoManager *manager, const char *server_url)\n{\n    GList *repo_id_list = NULL;\n    GHashTableIter iter;\n    SeafRepo *repo;\n    gpointer key, value;\n\n    if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) {\n        seaf_warning (\"[repo mgr] failed to lock repo cache.\\n\");\n        return NULL;\n    }\n    g_hash_table_iter_init (&iter, manager->priv->repo_hash);\n\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        repo = value;\n        if (!repo->delete_pending && g_strcmp0 (repo->server_url, server_url) == 0)\n            repo_id_list = g_list_prepend (repo_id_list, g_strdup(repo->id));\n    }\n\n    pthread_rwlock_unlock (&manager->priv->lock);\n\n    return repo_id_list;\n}\n\nint\nseaf_repo_manager_set_repo_email (SeafRepoManager *mgr,\n                                  SeafRepo *repo,\n                                  const char *email)\n{\n    g_free (repo->email);\n    repo->email = g_strdup(email);\n\n    save_repo_property (mgr, repo->id, REPO_PROP_EMAIL, email);\n    return 0;\n}\n\nint\nseaf_repo_manager_set_repo_token (SeafRepoManager *manager, \n                                  SeafRepo *repo,\n                                  const char *token)\n{\n    g_free (repo->token);\n    repo->token = g_strdup(token);\n\n    save_repo_property (manager, repo->id, REPO_PROP_TOKEN, token);\n    return 0;\n}\n\n\nint\nseaf_repo_manager_remove_repo_token (SeafRepoManager *manager,\n                                     SeafRepo *repo)\n{\n    g_free (repo->token);\n    repo->token = NULL;\n    seaf_repo_manager_del_repo_property_by_key(manager, repo->id, REPO_PROP_TOKEN);\n    return 0;\n}\n\nint\nseaf_repo_manager_set_repo_relay_info (SeafRepoManager *mgr,\n                                       const char *repo_id,\n                                       const char *relay_addr,\n                                       const char *relay_port)\n{\n    save_repo_property (mgr, repo_id, REPO_PROP_RELAY_ADDR, relay_addr);\n    save_repo_property (mgr, repo_id, REPO_PROP_RELAY_PORT, relay_port);\n    return 0;\n}\n\nvoid\nseaf_repo_manager_get_repo_relay_info (SeafRepoManager *mgr,\n                                       const char *repo_id,\n                                       char **relay_addr,\n                                       char **relay_port)\n{\n    char *addr, *port;\n\n    addr = load_repo_property (mgr, repo_id, REPO_PROP_RELAY_ADDR);\n    port = load_repo_property (mgr, repo_id, REPO_PROP_RELAY_PORT);\n\n    if (relay_addr && addr)\n        *relay_addr = addr;\n    if (relay_port && port)\n        *relay_port = port;\n}\n\nstatic void\nupdate_server_properties (SeafRepoManager *mgr,\n                          const char *repo_id,\n                          const char *new_server_url)\n{\n    char *old_server_url = NULL;\n    char *sql = NULL;\n\n    old_server_url = seaf_repo_manager_get_repo_property (mgr, repo_id,\n                                                          REPO_PROP_SERVER_URL);\n    if (!old_server_url)\n        return;\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    sql = sqlite3_mprintf (\"UPDATE ServerProperty SET server_url=%Q WHERE \"\n                           \"server_url=%Q;\", new_server_url, old_server_url);\n    sqlite_query_exec (mgr->priv->db, sql);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    sqlite3_free (sql);\n    g_free (old_server_url);\n}\n\nint\nseaf_repo_manager_update_repos_server_host (SeafRepoManager *mgr,\n                                            const char *old_server_url,\n                                            const char *new_server_url)\n{\n    GList *ptr, *repos = seaf_repo_manager_get_repo_list (seaf->repo_mgr, 0, -1);\n    SeafRepo *r;\n    char *canon_old_server_url = canonical_server_url(old_server_url);    \n    char *canon_new_server_url = canonical_server_url(new_server_url);\n\n    for (ptr = repos; ptr; ptr = ptr->next) {\n        r = ptr->data;\n        \n        char *server_url = seaf_repo_manager_get_repo_property (seaf->repo_mgr,\n                                                                r->id,\n                                                                REPO_PROP_SERVER_URL);\n        \n        if (g_strcmp0(server_url, canon_old_server_url) == 0) {\n            /* Update server property before server_url is changed. */\n            update_server_properties (mgr, r->id, canon_new_server_url);\n\n            seaf_repo_manager_set_repo_property (\n                seaf->repo_mgr, r->id, REPO_PROP_SERVER_URL, canon_new_server_url);\n        }\n        g_free (server_url);\n\n    }\n\n    g_list_free (repos);\n    g_free (canon_old_server_url);\n    g_free (canon_new_server_url);\n\n    return 0;\n}\n\nchar *\nseaf_repo_manager_get_server_property (SeafRepoManager *mgr,\n                                       const char *server_url,\n                                       const char *key)\n{\n    char *sql = sqlite3_mprintf (\"SELECT value FROM ServerProperty WHERE \"\n                                 \"server_url=%Q AND key=%Q;\",\n                                 server_url, key);\n    char *value;\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    value = sqlite_get_string (mgr->priv->db, sql);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    sqlite3_free (sql);\n    return value;\n}\n\nint\nseaf_repo_manager_set_server_property (SeafRepoManager *mgr,\n                                       const char *server_url,\n                                       const char *key,\n                                       const char *value)\n{\n    char *sql;\n    int ret;\n    char *canon_server_url = canonical_server_url(server_url);\n\n    pthread_mutex_lock (&mgr->priv->db_lock);\n\n    sql = sqlite3_mprintf (\"REPLACE INTO ServerProperty VALUES (%Q, %Q, %Q);\",\n                           canon_server_url, key, value);\n    ret = sqlite_query_exec (mgr->priv->db, sql);\n\n    pthread_mutex_unlock (&mgr->priv->db_lock);\n\n    sqlite3_free (sql);\n    g_free (canon_server_url);\n    return ret;\n}\n\ngboolean\nseaf_repo_manager_server_is_pro (SeafRepoManager *mgr,\n                                 const char *server_url)\n{\n    gboolean ret = FALSE;\n\n    char *is_pro = seaf_repo_manager_get_server_property (seaf->repo_mgr,\n                                                          server_url,\n                                                          SERVER_PROP_IS_PRO);\n    if (is_pro != NULL && strcasecmp (is_pro, \"true\") == 0)\n        ret = TRUE;\n\n    g_free (is_pro);\n    return ret;\n}\n\n/*\n * Read ignored files from ignore.txt\n */\nGList *seaf_repo_load_ignore_files (const char *worktree)\n{\n    GList *list = NULL;\n    SeafStat st;\n    FILE *fp;\n    char *full_path, *pattern;\n    char path[SEAF_PATH_MAX];\n\n    full_path = g_build_path (PATH_SEPERATOR, worktree,\n                              IGNORE_FILE, NULL);\n    if (seaf_stat (full_path, &st) < 0)\n        goto error;\n    if (!S_ISREG(st.st_mode))\n        goto error;\n    fp = g_fopen(full_path, \"r\");\n    if (fp == NULL)\n        goto error;\n\n    while (fgets(path, SEAF_PATH_MAX, fp) != NULL) {\n        /* remove leading and trailing whitespace, including \\n \\r. */\n        g_strstrip (path);\n\n        /* ignore comment and blank line */\n        if (path[0] == '#' || path[0] == '\\0')\n            continue;\n\n        /* Change 'foo/' to 'foo/ *'. */\n        if (path[strlen(path)-1] == '/')\n            pattern = g_strdup_printf(\"%s/%s*\", worktree, path);\n        else\n            pattern = g_strdup_printf(\"%s/%s\", worktree, path);\n\n        list = g_list_prepend(list, pattern);\n    }\n\n    fclose(fp);\n    g_free (full_path);\n    return list;\n\nerror:\n    g_free (full_path);\n    return NULL;\n}\n\ngboolean\nseaf_repo_check_ignore_file (GList *ignore_list, const char *fullpath)\n{\n    char *str;\n    SeafStat st;\n    GPatternSpec *ignore_spec;\n    GList *p;\n\n    str = g_strdup(fullpath);\n\n    int rc = seaf_stat(str, &st);\n    if (rc == 0 && S_ISDIR(st.st_mode)) {\n        g_free (str);\n        str = g_strconcat (fullpath, \"/\", NULL);\n    }\n\n    for (p = ignore_list; p != NULL; p = p->next) {\n        char *pattern = (char *)p->data;\n\n        ignore_spec = g_pattern_spec_new(pattern);\n        if (g_pattern_match_string(ignore_spec, str)) {\n            g_free (str);\n            g_pattern_spec_free(ignore_spec);\n            return TRUE;\n        }\n        g_pattern_spec_free(ignore_spec);\n    }\n\n    g_free (str);\n    return FALSE;\n}\n\n/*\n * Free ignored file list\n */\nvoid seaf_repo_free_ignore_files (GList *ignore_list)\n{\n    GList *p;\n\n    if (ignore_list == NULL)\n        return;\n\n    for (p = ignore_list; p != NULL; p = p->next)\n        free(p->data);\n\n    g_list_free (ignore_list);\n}\n"
  },
  {
    "path": "daemon/repo-mgr.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef SEAF_REPO_MGR_H\n#define SEAF_REPO_MGR_H\n\n#include \"common.h\"\n\n#include <pthread.h>\n\n#include \"seafile-object.h\"\n#include \"commit-mgr.h\"\n#include \"branch-mgr.h\"\n#include \"http-tx-mgr.h\"\n\n#define REPO_AUTO_SYNC        \"auto-sync\"\n#define REPO_RELAY_ID         \"relay-id\"\n#define REPO_REMOTE_HEAD      \"remote-head\"\n#define REPO_LOCAL_HEAD       \"local-head\"\n#define REPO_PROP_EMAIL       \"email\"\n#define REPO_PROP_TOKEN       \"token\"\n#define REPO_PROP_RELAY_ADDR  \"relay-address\"\n#define REPO_PROP_RELAY_PORT  \"relay-port\"\n#define REPO_PROP_DOWNLOAD_HEAD \"download-head\"\n#define REPO_PROP_IS_READONLY \"is-readonly\"\n#define REPO_PROP_SERVER_URL  \"server-url\"\n#define REPO_PROP_SYNC_INTERVAL \"sync-interval\"\n#define REPO_SYNC_WORKTREE_NAME \"sync-worktree-name\"\n#define REPO_PROP_USERNAME \"username\"\n#define REPO_PROP_EMPTY_ENC_KEY \"empty-enc-key\"\n#define REPO_PROP_CHECK_BLOCKS \"check-blocks\"\n\nstruct _SeafRepoManager;\ntypedef struct _SeafRepo SeafRepo;\n\nstruct _ChangeSet;\n\n/* The caller can use the properties directly. But the caller should\n * always write on repos via the API. \n */\nstruct _SeafRepo {\n    struct _SeafRepoManager *manager;\n\n    gchar       id[37];\n    gchar      *name;\n    gchar      *desc;\n    gchar      *category;       /* not used yet */\n    gboolean    encrypted;\n    int         enc_version;\n    gchar       salt[65];\n    gchar       magic[65];       /* hash(repo_id + passwd), key stretched. */\n    gchar       pwd_hash[65];       /* hash(repo_id + passwd), key stretched. */\n    gchar       *pwd_hash_algo;\n    gchar       *pwd_hash_params;\n    gchar       random_key[97];  /* key length is 48 after encryption */\n    gboolean    no_local_history;\n    gint64 last_modify;\n\n    SeafBranch *head;\n    gchar root_id[41];\n\n    gboolean    is_corrupted;\n    gboolean    delete_pending;\n\n    gchar      *relay_id;\n    gchar      *worktree;\n    gboolean    wt_changed;\n    int         wt_check_time;\n    int         last_sync_time;\n\n    /* Last time check locked files. */\n    int         last_check_locked_time;\n    gboolean    checking_locked_files;\n\n    unsigned char enc_key[32];   /* 256-bit encryption key */\n    unsigned char enc_iv[16];\n\n    gchar      *email;          /* email of the user on the relay */\n    gchar      *username;\n    gchar      *token;          /* token for access this repo on server */\n\n    gchar      *jwt_token;\n    gint64     last_check_jwt_token;\n\n    pthread_mutex_t lock;\n\n    gboolean      worktree_invalid; /* true if worktree moved or deleted */\n    gboolean      index_corrupted;\n    gboolean      is_readonly;\n\n    unsigned int  auto_sync : 1;\n    unsigned int  quota_full_notified : 1;\n    unsigned int  access_denied_notified : 1;\n\n    int version;\n\n    gboolean create_partial_commit;\n\n    /* Used for http sync. */\n    char *server_url;\n    /* Can be server_url or server_url:8082, depends on which one works. */\n    char *effective_host;\n    gboolean use_fileserver_port;\n\n    /* Detected file change set during indexing.\n     * Added to here to avoid passing additional arguments. */\n    struct _ChangeSet *changeset;\n\n    /* Non-zero if periodic sync is set for this repo. */\n    int sync_interval;\n\n    // empty_enc_key will be set to true when the currently loadded enc key is empty, or when blocks can be decrypted using an empty enc key.\n    gboolean empty_enc_key;\n};\n\n\ngboolean is_repo_id_valid (const char *id);\n\nSeafRepo* \nseaf_repo_new (const char *id, const char *name, const char *desc);\n\nvoid\nseaf_repo_free (SeafRepo *repo);\n\nint\nseaf_repo_set_head (SeafRepo *repo, SeafBranch *branch);\n\nSeafCommit *\nseaf_repo_get_head_commit (const char *repo_id);\n\nvoid\nseaf_repo_set_readonly (SeafRepo *repo);\n\nvoid\nseaf_repo_unset_readonly (SeafRepo *repo);\n\nint\nseaf_repo_checkdir (SeafRepo *repo);\n\n/* Update repo name, desc, magic etc from commit.\n */\nvoid\nseaf_repo_from_commit (SeafRepo *repo, SeafCommit *commit);\n\n/* Update repo-related fields to commit. \n */\nvoid\nseaf_repo_to_commit (SeafRepo *repo, SeafCommit *commit);\n\nvoid\nseaf_repo_set_name (SeafRepo *repo, const char *new_name);\n\n/*\n * Returns a list of all commits belongs to the repo.\n * The commits in the repos are all unique.\n */\nGList *\nseaf_repo_get_commits (SeafRepo *repo);\n\nchar *\nseaf_repo_index_commit (SeafRepo *repo,\n                        gboolean is_force_commit,\n                        gboolean is_initial_commit,\n                        GError **error);\n\nGList *\nseaf_repo_diff (SeafRepo *repo, const char *old, const char *new, int fold_dir_diff, char **error);\n\ntypedef struct _SeafRepoManager SeafRepoManager;\ntypedef struct _SeafRepoManagerPriv SeafRepoManagerPriv;\n\nstruct _SeafRepoManager {\n    struct _SeafileSession *seaf;\n\n    char *index_dir;\n\n    SeafRepoManagerPriv *priv;\n};\n\nSeafRepoManager* \nseaf_repo_manager_new (struct _SeafileSession *seaf);\n\nint\nseaf_repo_manager_init (SeafRepoManager *mgr);\n\nint\nseaf_repo_manager_start (SeafRepoManager *mgr);\n\nint\nseaf_repo_manager_add_repo (SeafRepoManager *mgr, SeafRepo *repo);\n\nint\nseaf_repo_manager_mark_repo_deleted (SeafRepoManager *mgr, SeafRepo *repo);\n\nint\nseaf_repo_manager_del_repo (SeafRepoManager *mgr, SeafRepo *repo);\n\nvoid\nseaf_repo_manager_move_repo_store (SeafRepoManager *mgr,\n                                   const char *type,\n                                   const char *repo_id);\n\nvoid\nseaf_repo_manager_remove_repo_ondisk (SeafRepoManager *mgr, const char *repo_id,\n                                      gboolean add_deleted_record);\n\nSeafRepo* \nseaf_repo_manager_create_new_repo (SeafRepoManager *mgr,\n                                   const char *name,\n                                   const char *desc);\n\nSeafRepo* \nseaf_repo_manager_get_repo (SeafRepoManager *manager, const gchar *id);\n\ngboolean\nseaf_repo_manager_repo_exists (SeafRepoManager *manager, const gchar *id);\n\nGList* \nseaf_repo_manager_get_repo_list (SeafRepoManager *mgr, int start, int limit);\n\nGList *\nseaf_repo_manager_get_repo_id_list_by_server (SeafRepoManager *mgr, const char *server_url);\n\nGList *\nseaf_repo_manager_list_garbage_repos (SeafRepoManager *mgr);\n\nvoid\nseaf_repo_manager_remove_garbage_repo (SeafRepoManager *mgr, const char *repo_id);\n\n#define MAX_REPO_TOKEN 64\n#define DEFAULT_REPO_TOKEN \"default\"\n\n\nint\nseaf_repo_manager_set_repo_token (SeafRepoManager *manager, \n                                  SeafRepo *repo,\n                                  const char *token);\n\nint\nseaf_repo_manager_remove_repo_token (SeafRepoManager *manager,\n                                     SeafRepo *repo);\n\nint\nseaf_repo_manager_set_repo_email (SeafRepoManager *manager, \n                                  SeafRepo *repo,\n                                  const char *email);\n\nint\nseaf_repo_manager_set_repo_relay_info (SeafRepoManager *manager, \n                                       const char *repo_id,\n                                       const char *relay_addr,\n                                       const char *relay_port);\nvoid\nseaf_repo_manager_get_repo_relay_info (SeafRepoManager *mgr,\n                                       const char *repo_id,\n                                       char **relay_addr,\n                                       char **relay_port);\n\nint\nseaf_repo_manager_branch_repo_unmap (SeafRepoManager *manager, SeafBranch *branch);\n\nint\nseaf_repo_manager_set_repo_property (SeafRepoManager *manager,\n                                     const char *repo_id,\n                                     const char *key,\n                                     const char *value);\n\nchar *\nseaf_repo_manager_get_repo_property (SeafRepoManager *manager,\n                                     const char *repo_id,\n                                     const char *key);\n\nvoid\nseaf_repo_mamager_del_repo_property (SeafRepoManager *manager, SeafRepo *repo);\n\nint\nseaf_repo_check_worktree (SeafRepo *repo);\n\nint\nseaf_repo_manager_set_repo_worktree (SeafRepoManager *mgr,\n                                     SeafRepo *repo,\n                                     const char *worktree);\n\nvoid\nseaf_repo_manager_invalidate_repo_worktree (SeafRepoManager *mgr,\n                                            SeafRepo *repo);\n\nvoid\nseaf_repo_manager_validate_repo_worktree (SeafRepoManager *mgr,\n                                          SeafRepo *repo);\n\nint\nseaf_repo_manager_set_repo_passwd (SeafRepoManager *manager,\n                                   SeafRepo *repo,\n                                   const char *passwd);\n\nint \nseaf_repo_manager_save_repo_enc_info (SeafRepoManager *manager,\n                                      const char *repo_id,\n                                      const char *key,\n                                      const char *iv);\n\nint \nseaf_repo_manager_load_repo_enc_info (SeafRepoManager *manager,\n                                     SeafRepo *repo);\n\nint\nseaf_repo_manager_update_repos_server_host (SeafRepoManager *mgr,\n                                            const char *old_server_url,\n                                            const char *new_server_url);\n\n#define SERVER_PROP_IS_PRO \"is_pro\"\n\nchar *\nseaf_repo_manager_get_server_property (SeafRepoManager *mgr,\n                                       const char *server_url,\n                                       const char *key);\n\nint\nseaf_repo_manager_set_server_property (SeafRepoManager *mgr,\n                                       const char *server_url,\n                                       const char *key,\n                                       const char *value);\n\ngboolean\nseaf_repo_manager_server_is_pro (SeafRepoManager *mgr,\n                                 const char *server_url);\n\nGList *\nseaf_repo_load_ignore_files (const char *worktree);\n\ngboolean\nseaf_repo_check_ignore_file (GList *ignore_list, const char *fullpath);\n\nvoid\nseaf_repo_free_ignore_files (GList *ignore_list);\n\nenum {\n    FETCH_CHECKOUT_SUCCESS = 0,\n    FETCH_CHECKOUT_CANCELED,\n    FETCH_CHECKOUT_FAILED,\n    FETCH_CHECKOUT_TRANSFER_ERROR,\n    FETCH_CHECKOUT_LOCKED,\n};\n\nstruct _TransferTask;\nstruct _HttpTxTask;\n\nint\nseaf_repo_fetch_and_checkout (struct _HttpTxTask *http_task,\n                              const char *remote_head_id);\n\nint\nseaf_repo_manager_checkout_file (SeafRepo *repo,\n                                 const char *file_id,\n                                 const char *file_path,\n                                 guint32 mode,\n                                 guint64 mtime,\n                                 SeafileCrypt *crypt,\n                                 const char *in_repo_path,\n                                 const char *conflict_head_id,\n                                 gboolean force_conflict,\n                                 gboolean *conflicted,\n                                 const char *email,\n                                 CheckoutBlockAux *aux);\n\ngboolean\nseaf_repo_manager_is_ignored_hidden_file (const char *filename);\n\n/* Locked files. */\n\n#define LOCKED_OP_UPDATE \"update\"\n#define LOCKED_OP_DELETE \"delete\"\n\ntypedef struct _LockedFile {\n    char *operation;\n    gint64 old_mtime;\n    char file_id[41];\n} LockedFile;\n\ntypedef struct _LockedFileSet {\n    SeafRepoManager *mgr;\n    char repo_id[37];\n    GHashTable *locked_files;\n} LockedFileSet;\n\nLockedFileSet *\nseaf_repo_manager_get_locked_file_set (SeafRepoManager *mgr, const char *repo_id);\n\nvoid\nlocked_file_set_free (LockedFileSet *fset);\n\nint\nlocked_file_set_add_update (LockedFileSet *fset,\n                            const char *path,\n                            const char *operation,\n                            gint64 old_mtime,\n                            const char *file_id);\n\nint\nlocked_file_set_remove (LockedFileSet *fset, const char *path, gboolean db_only);\n\nLockedFile *\nlocked_file_set_lookup (LockedFileSet *fset, const char *path);\n\n/* Folder Permissions. */\n\ntypedef enum FolderPermType {\n    FOLDER_PERM_TYPE_USER = 0,\n    FOLDER_PERM_TYPE_GROUP,\n} FolderPermType;\n\ntypedef struct _FolderPerm {\n    char *path;\n    char *permission;\n} FolderPerm;\n\nvoid\nfolder_perm_free (FolderPerm *perm);\n\nint\nseaf_repo_manager_delete_folder_perm (SeafRepoManager *mgr,\n                                      const char *repo_id,\n                                      FolderPermType type,\n                                      FolderPerm *perm);\n\nint\nseaf_repo_manager_update_folder_perm (SeafRepoManager *mgr,\n                                      const char *repo_id,\n                                      FolderPermType type,\n                                      FolderPerm *perm);\n\nint\nseaf_repo_manager_update_folder_perms (SeafRepoManager *mgr,\n                                       const char *repo_id,\n                                       FolderPermType type,\n                                       GList *folder_perms);\n\nint\nseaf_repo_manager_update_folder_perm_timestamp (SeafRepoManager *mgr,\n                                                const char *repo_id,\n                                                gint64 timestamp);\n\ngint64\nseaf_repo_manager_get_folder_perm_timestamp (SeafRepoManager *mgr,\n                                             const char *repo_id);\n\ngboolean\nseaf_repo_manager_is_path_writable (SeafRepoManager *mgr,\n                                    const char *repo_id,\n                                    const char *path);\n\n/* Sync error related. */\n\nint\nseaf_repo_manager_record_sync_error (const char *repo_id,\n                                     const char *repo_name,\n                                     const char *path,\n                                     int error_id);\n\nGList *\nseaf_repo_manager_get_file_sync_errors (SeafRepoManager *mgr, int offset, int limit);\n\nint\nseaf_repo_manager_del_file_sync_error_by_id (SeafRepoManager *mgr, int id);\n\n/* Record sync error and send notification. */\nvoid\nsend_file_sync_error_notification (const char *repo_id,\n                                   const char *repo_name,\n                                   const char *path,\n                                   int err_id);\n\n#endif\n"
  },
  {
    "path": "daemon/seaf-daemon.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#ifdef WIN32\n#include <windows.h>\n#include <wincrypt.h>\n#include <shellapi.h>\n#endif\n\n#ifndef WIN32\n#include <unistd.h>\n#include <sys/file.h>\n#endif\n#include <stdio.h>\n#include <stdlib.h>\n#include <errno.h>\n#include <getopt.h>\n#include <signal.h>\n\n#include <glib.h>\n#include <glib-object.h>\n#include <curl/curl.h>\n#include <event2/thread.h>\n\n#ifdef HAVE_BREAKPAD_SUPPORT\n#include <c_bpwrapper.h>\n#endif // HAVE_BREAKPAD_SUPPORT\n\n#ifdef ENABLE_BREAKPAD\n#include \"c_bpwrapper.h\"\n#endif // ENABLE_BREAKPAD\n\n#include <searpc.h>\n#include <searpc-named-pipe-transport.h>\n\n#include \"seafile-session.h\"\n#include \"seafile-rpc.h\"\n#include \"log.h\"\n#include \"utils.h\"\n#include \"vc-utils.h\"\n#include \"seafile-config.h\"\n#ifndef USE_GPL_CRYPTO\n#include \"curl-init.h\"\n#endif\n\n#include \"cdc/cdc.h\"\n\n#ifndef SEAFILE_CLIENT_VERSION\n#define SEAFILE_CLIENT_VERSION PACKAGE_VERSION\n#endif\n\n\nSeafileSession *seaf;\n\nstatic const char *short_options = \"hvc:d:w:l:D:bg:G:\";\nstatic struct option long_options[] = {\n    { \"help\", no_argument, NULL, 'h', },\n    { \"version\", no_argument, NULL, 'v', },\n    { \"config-file\", required_argument, NULL, 'c' },\n    { \"seafdir\", required_argument, NULL, 'd' },\n    { \"daemon\", no_argument, NULL, 'b' },\n    { \"debug\", required_argument, NULL, 'D' },\n    { \"worktree\", required_argument, NULL, 'w' },\n    { \"log\", required_argument, NULL, 'l' },\n    { \"ccnet-debug-level\", required_argument, NULL, 'g' },\n    { \"seafile-debug-level\", required_argument, NULL, 'G' },\n    { NULL, 0, NULL, 0, },\n};\n\nstatic void usage ()\n{\n    fprintf (stderr, \"usage: seaf-daemon [-c config_dir] [-d seafile_dir] [-w worktree_dir] [--daemon]\\n\");\n}\n\n#include <searpc.h>\n#include \"searpc-signature.h\"\n#include \"searpc-marshal.h\"\n\n#define SEAFILE_SOCKET_NAME \"seafile.sock\"\n\nstatic void\nregister_rpc_service ()\n{\n    searpc_server_init (register_marshals);\n\n    searpc_create_service (\"seafile-rpcserver\");\n    searpc_create_service (\"seafile-threaded-rpcserver\");\n\n    /* seafile-rpcserver */\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_sync_error_id_to_str,\n                                     \"seafile_sync_error_id_to_str\",\n                                     searpc_signature_string__int());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_add_del_confirmation,\n                                     \"seafile_add_del_confirmation\",\n                                     searpc_signature_int__string_int());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_get_config,\n                                     \"seafile_get_config\",\n                                     searpc_signature_string__string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_set_config,\n                                     \"seafile_set_config\",\n                                     searpc_signature_int__string_string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_get_config_int,\n                                     \"seafile_get_config_int\",\n                                     searpc_signature_int__string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_set_config_int,\n                                     \"seafile_set_config_int\",\n                                     searpc_signature_int__string_int());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_set_upload_rate_limit,\n                                     \"seafile_set_upload_rate_limit\",\n                                     searpc_signature_int__int());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_set_download_rate_limit,\n                                     \"seafile_set_download_rate_limit\",\n                                     searpc_signature_int__int());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_unsync_repos_by_account,\n                                     \"seafile_unsync_repos_by_account\",\n                                     searpc_signature_int__string_string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_remove_repo_tokens_by_account,\n                                     \"seafile_remove_repo_tokens_by_account\",\n                                     searpc_signature_int__string_string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_set_repo_token,\n                                     \"seafile_set_repo_token\",\n                                     searpc_signature_int__string_string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_get_upload_rate,\n                                     \"seafile_get_upload_rate\",\n                                     searpc_signature_int__void());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_get_download_rate,\n                                     \"seafile_get_download_rate\",\n                                     searpc_signature_int__void());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_destroy_repo,\n                                     \"seafile_destroy_repo\",\n                                     searpc_signature_int__string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_set_repo_property,\n                                     \"seafile_set_repo_property\",\n                                     searpc_signature_int__string_string_string());\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_get_repo_property,\n                                     \"seafile_get_repo_property\",\n                                     searpc_signature_string__string_string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_update_repos_server_host,\n                                     \"seafile_update_repos_server_host\",\n                                     searpc_signature_int__string_string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_disable_auto_sync,\n                                     \"seafile_disable_auto_sync\",\n                                     searpc_signature_int__void());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_enable_auto_sync,\n                                     \"seafile_enable_auto_sync\",\n                                     searpc_signature_int__void());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_is_auto_sync_enabled,\n                                     \"seafile_is_auto_sync_enabled\",\n                                     searpc_signature_int__void());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_gen_default_worktree,\n                                     \"gen_default_worktree\",\n                                     searpc_signature_string__string_string());\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_check_path_for_clone,\n                                     \"seafile_check_path_for_clone\",\n                                     searpc_signature_int__string());\n\n    /* clone means sync with existing folder, download means sync to a new folder. */\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_clone,\n                                     \"seafile_clone\",\n        searpc_signature_string__string_int_string_string_string_string_string_string_string_int_string());\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_download,\n                                     \"seafile_download\",\n        searpc_signature_string__string_int_string_string_string_string_string_string_string_int_string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_cancel_clone_task,\n                                     \"seafile_cancel_clone_task\",\n                                     searpc_signature_int__string());\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_get_clone_tasks,\n                                     \"seafile_get_clone_tasks\",\n                                     searpc_signature_objlist__void());\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_sync,\n                                     \"seafile_sync\",\n                                     searpc_signature_int__string_string());\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_get_repo_list,\n                                     \"seafile_get_repo_list\",\n                                     searpc_signature_objlist__int_int());\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_get_repo,\n                                     \"seafile_get_repo\",\n                                     searpc_signature_object__string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_get_repo_sync_task,\n                                     \"seafile_get_repo_sync_task\",\n                                     searpc_signature_object__string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_find_transfer_task,\n                                     \"seafile_find_transfer_task\",\n                                     searpc_signature_object__string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_get_path_sync_status,\n                                     \"seafile_get_path_sync_status\",\n                                     searpc_signature_string__string_string_int());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_mark_file_locked,\n                                     \"seafile_mark_file_locked\",\n                                     searpc_signature_int__string_string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_mark_file_unlocked,\n                                     \"seafile_mark_file_unlocked\",\n                                     searpc_signature_int__string_string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_generate_magic_and_random_key,\n                                     \"seafile_generate_magic_and_random_key\",\n                                     searpc_signature_object__int_string_string_string_string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_get_server_property,\n                                     \"seafile_get_server_property\",\n                                     searpc_signature_string__string_string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_set_server_property,\n                                     \"seafile_set_server_property\",\n                                     searpc_signature_int__string_string_string());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_get_file_sync_errors,\n                                     \"seafile_get_file_sync_errors\",\n                                     searpc_signature_objlist__int_int());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_del_file_sync_error_by_id,\n                                     \"seafile_del_file_sync_error_by_id\",\n                                     searpc_signature_int__int());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_get_sync_notification,\n                                     \"seafile_get_sync_notification\",\n                                     searpc_signature_json__void());\n\n    searpc_server_register_function (\"seafile-rpcserver\",\n                                     seafile_shutdown,\n                                     \"seafile_shutdown\",\n                                     searpc_signature_int__void());\n\n    /* Need to run in a thread since diff may take long. */\n    searpc_server_register_function (\"seafile-threaded-rpcserver\",\n                                     seafile_diff,\n                                     \"seafile_diff\",\n                                     searpc_signature_objlist__string_string_string_int());\n}\n\n#ifdef WIN32\nchar *b64encode(const char *input)\n{\n    char buf[32767] = {0};\n    DWORD retlen = 32767;\n    CryptBinaryToStringA((BYTE*) input, strlen(input), CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, buf, &retlen);\n    return strdup(buf);\n}\n#endif\n\nstatic int\nstart_searpc_server ()\n{\n    register_rpc_service ();\n\n#ifdef WIN32\n    char userNameBuf[32767];\n    DWORD bufCharCount = sizeof(userNameBuf);\n    if (GetUserNameA(userNameBuf, &bufCharCount) == 0) {\n        seaf_warning (\"Failed to get user name, GLE=%lu, required size is %lu\\n\",\n                      GetLastError(), bufCharCount);\n        return -1;\n    }\n\n    char *path = g_strdup_printf(\"\\\\\\\\.\\\\pipe\\\\seafile_%s\", b64encode(userNameBuf));\n#else\n    char *path = g_build_filename (seaf->seaf_dir, SEAFILE_SOCKET_NAME, NULL);\n#endif\n\n    SearpcNamedPipeServer *server = searpc_create_named_pipe_server (path);\n    if (!server) {\n        seaf_warning (\"Failed to create named pipe server.\\n\");\n        g_free (path);\n        return -1;\n    }\n\n    seaf->rpc_socket_path = path;\n\n    return searpc_named_pipe_server_start (server);\n}\n\n\n#ifndef WIN32\nstatic void\nset_signal_handlers (SeafileSession *session)\n{\n    signal (SIGPIPE, SIG_IGN);\n}\n#endif\n\n#ifdef WIN32\n/* Get the commandline arguments in unicode, then convert them to utf8  */\nstatic char **\nget_argv_utf8 (int *argc)\n{\n    int i = 0;\n    char **argv = NULL;\n    const wchar_t *cmdline = NULL;\n    wchar_t **argv_w = NULL;\n\n    cmdline = GetCommandLineW();\n    argv_w = CommandLineToArgvW (cmdline, argc);\n    if (!argv_w) {\n        printf(\"failed to CommandLineToArgvW(), GLE=%lu\\n\", GetLastError());\n        return NULL;\n    }\n\n    argv = (char **)malloc (sizeof(char*) * (*argc));\n    for (i = 0; i < *argc; i++) {\n        argv[i] = wchar_to_utf8 (argv_w[i]);\n    }\n\n    return argv;\n}\n#endif\n\n#ifdef __linux__\nstatic int\nwrite_pidfile (char *pidfile)\n{\n    int fd = open (pidfile, O_WRONLY | O_CREAT, 0644);\n    if (fd < 0) {\n        seaf_warning (\"Failed to open pidfile %s: %s\\n\",\n                      pidfile, strerror(errno));\n        return -1;\n    }\n\n    if (flock (fd, LOCK_EX | LOCK_NB) < 0) {\n        seaf_warning (\"Failed to lock pidfile %s: %s\\n\",\n                      pidfile, strerror(errno));\n        close (fd);\n        return -1;\n    }\n\n    return 0;\n}\n#endif\n\nint\nmain (int argc, char **argv)\n{\n#if defined(HAVE_BREAKPAD_SUPPORT) || defined(ENABLE_BREAKPAD)\n#ifdef WIN32\n#define DUMPS_DIR \"~/ccnet/logs/dumps/\"\n#else\n#define DUMPS_DIR \"~/.ccnet/logs/dumps/\"\n#endif\n    const char *dump_dir = ccnet_expand_path(DUMPS_DIR);\n    checkdir_with_mkdir(dump_dir);\n    CBPWrapperExceptionHandler bp_exception_handler = newCBPWrapperExceptionHandler(dump_dir);\n#endif\n\n#ifdef WIN32\n#define DEFAULT_CONFIG_DIR \"~/ccnet\"\n#else\n#define DEFAULT_CONFIG_DIR \"~/.ccnet\"\n#endif\n\n    int c;\n    char *config_dir = DEFAULT_CONFIG_DIR;\n    char *seafile_dir = NULL;\n    char *worktree_dir = NULL;\n    char *logfile = NULL;\n    char *eventfile = NULL;\n    char *pidfile = NULL;\n    const char *debug_str = NULL;\n    int daemon_mode = 0;\n    char *ccnet_debug_level_str = \"info\";\n    char *seafile_debug_level_str = \"debug\";\n\n#ifdef WIN32\n    LoadLibraryA (\"exchndl.dll\");\n\n    argv = get_argv_utf8 (&argc);\n#endif\n\n    while ((c = getopt_long (argc, argv, short_options,\n                             long_options, NULL)) != EOF)\n    {\n        switch (c) {\n        case 'h':\n            usage();\n            exit (1);\n            break;\n        case 'v':\n            exit (1);\n            break;\n        case 'c':\n            config_dir = optarg;\n            break;\n        case 'd':\n            seafile_dir = g_strdup(optarg);\n            break;\n        case 'b':\n            daemon_mode = 1;\n            break;\n        case 'D':\n            debug_str = optarg;\n            break;\n        case 'w':\n            worktree_dir = g_strdup(optarg);\n            break;\n        case 'l':\n            logfile = g_strdup(optarg);\n            break;\n        case 'g':\n            ccnet_debug_level_str = optarg;\n            break;\n        case 'G':\n            seafile_debug_level_str = optarg;\n            break;\n        default:\n            usage ();\n            exit (1);\n        }\n    }\n\n    argc -= optind;\n    argv += optind;\n\n#ifndef WIN32\n    if (daemon_mode) {\n#ifndef __APPLE__\n        daemon (1, 0);\n#else   /* __APPLE */\n        /* daemon is deprecated under APPLE\n         * use fork() instead\n         * */\n        switch (fork ()) {\n          case -1:\n              seaf_warning (\"Failed to daemonize\");\n              exit (-1);\n              break;\n          case 0:\n              /* all good*/\n              break;\n          default:\n              /* kill origin process */\n              exit (0);\n        }\n#endif  /* __APPLE */\n    }\n#endif /* !WIN32 */\n\n    cdc_init ();\n\n    curl_global_init (CURL_GLOBAL_ALL);\n\n#if !GLIB_CHECK_VERSION(2, 35, 0)\n    g_type_init();\n#endif\n#if !GLIB_CHECK_VERSION(2, 31, 0)\n    g_thread_init(NULL);\n#endif\n\n#ifndef WIN32\n    /* init multithreading support for libevent.because struct event_base is not thread safe. */\n    evthread_use_pthreads();\n#endif\n\n    if (!debug_str)\n        debug_str = g_getenv(\"SEAFILE_DEBUG\");\n    seafile_debug_set_flags_string (debug_str);\n\n    if (logfile == NULL)\n        logfile = g_build_filename (config_dir, \"logs\", \"seafile.log\", NULL);\n    if (seafile_log_init (logfile, ccnet_debug_level_str,\n                          seafile_debug_level_str) < 0) {\n        seaf_warning (\"Failed to init log.\\n\");\n        exit (1);\n    }\n\n    eventfile = g_build_filename (config_dir, \"logs\", \"events.log\", NULL);\n    if (seafile_event_log_init (eventfile) < 0) {\n        seaf_warning (\"Failed to init event log.\\n\");\n        exit (1);\n    }\n\n    seafile_event_message(\"Starting record seafile events.\\n\");\n\n    seafile_log_rotate_start ();\n\n    /* init seafile */\n    if (seafile_dir == NULL)\n        seafile_dir = g_build_filename (config_dir, \"seafile-data\", NULL);\n    if (worktree_dir == NULL)\n        worktree_dir = g_build_filename (g_get_home_dir(), \"seafile\", NULL);\n\n    seaf = seafile_session_new (seafile_dir, worktree_dir, config_dir);\n    if (!seaf) {\n        seaf_warning (\"Failed to create seafile session.\\n\");\n        exit (1);\n    }\n\n\n    pidfile = g_build_filename (seafile_dir, \"seaf-daemon.pid\", NULL);\n#ifdef __linux__\n    if (write_pidfile (pidfile) < 0) {\n        seaf_message (\"The seafile data directory %s is already used by another Seafile client instance. Please use another configuration directory.\\n\", seafile_dir);\n        exit (1);\n    }\n#endif\n\n    seaf_message (\"starting seafile client \"SEAFILE_CLIENT_VERSION\"\\n\");\n#if defined(SEAFILE_SOURCE_COMMIT_ID)\n    seaf_message (\"seafile source code version \"SEAFILE_SOURCE_COMMIT_ID\"\\n\");\n#endif\n\n    g_free (seafile_dir);\n    g_free (worktree_dir);\n    g_free (logfile);\n    g_free (eventfile);\n    g_free (pidfile);\n\n#ifndef WIN32\n    set_signal_handlers (seaf);\n#else\n    WSADATA wsadata;\n    WSAStartup (0x0101, &wsadata);\n#endif\n\n#ifndef USE_GPL_CRYPTO\n    seafile_curl_init();\n#endif\n    seafile_session_prepare (seaf);\n    seafile_session_start (seaf);\n\n    if (start_searpc_server () < 0) {\n        seaf_warning (\"Failed to start searpc server.\\n\");\n        exit (1);\n    }\n\n    seaf_message (\"rpc server started.\\n\");\n\n\n    seafile_session_config_set_string (seaf, \"wktree\", seaf->worktree_dir);\n\n    event_base_loop (seaf->ev_base, 0);\n\n#ifndef USE_GPL_CRYPTO\n    seafile_curl_deinit();\n#endif\n\n    return 0;\n}\n"
  },
  {
    "path": "daemon/seafile-config.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n#include \"db.h\"\n\n#include \"seafile-config.h\"\n\ngboolean\nseafile_session_config_exists (SeafileSession *session, const char *key)\n{\n    char sql[256];\n\n    snprintf (sql, sizeof(sql),\n              \"SELECT 1 FROM Config WHERE key = '%s'\",\n              key);\n    return sqlite_check_for_existence (session->config_db, sql);\n}\n\nstatic gboolean\nget_value (sqlite3_stmt *stmt, void *data)\n{\n    char **p_value = data;\n\n    *p_value = g_strdup((char *) sqlite3_column_text (stmt, 0));\n    /* Only one result. */\n    return FALSE;\n}\n\nstatic char *\nconfig_get_string (sqlite3 *config_db, const char *key)\n{\n    char sql[256];\n    char *value = NULL;\n\n    snprintf (sql, sizeof(sql),\n              \"SELECT value FROM Config WHERE key='%s';\",\n              key);\n    if (sqlite_foreach_selected_row (config_db, sql,\n                                     get_value, &value) < 0)\n        return NULL;\n\n    return value;\n}\n\nchar *\nseafile_session_config_get_string (SeafileSession *session,\n                                   const char *key)\n{\n    return (config_get_string (session->config_db, key));\n}\n\nint\nseafile_session_config_get_int (SeafileSession *session,\n                                const char *key,\n                                gboolean *exists)\n{\n    char *value;\n    int ret;\n\n    value = config_get_string (session->config_db, key);\n    if (!value) {\n        if (exists)\n            *exists = FALSE;\n        return -1;\n    }\n\n    if (exists)\n        *exists = TRUE;\n    ret = atoi (value);\n    g_free (value);\n    return ret;\n}\n\ngboolean\nseafile_session_config_get_bool (SeafileSession *session,\n                                 const char *key)\n{\n    char *value;\n    gboolean ret = FALSE;\n\n    value = config_get_string (session->config_db, key);\n    if (g_strcmp0(value, \"true\") == 0)\n        ret = TRUE;\n\n    g_free (value);\n    return ret;\n}\n\nint\nseafile_session_config_set_string (SeafileSession *session,\n                                   const char *key,\n                                   const char *value)\n{\n    char sql[256];\n\n    sqlite3_snprintf (sizeof(sql), sql,\n                      \"REPLACE INTO Config VALUES ('%q', '%q');\",\n                      key, value);\n    if (sqlite_query_exec (session->config_db, sql) < 0)\n        return -1;\n\n    if (g_strcmp0 (key, KEY_CLIENT_NAME) == 0) {\n        g_free (session->client_name);\n        session->client_name = g_strdup(value);\n    }\n\n    if (g_strcmp0(key, KEY_SYNC_EXTRA_TEMP_FILE) == 0) {\n        if (g_strcmp0(value, \"true\") == 0)\n            session->sync_extra_temp_file = TRUE;\n        else\n            session->sync_extra_temp_file = FALSE;\n    }\n\n    if (g_strcmp0(key, KEY_DISABLE_VERIFY_CERTIFICATE) == 0) {\n        if (g_strcmp0(value, \"true\") == 0)\n            session->disable_verify_certificate = TRUE;\n        else\n            session->disable_verify_certificate = FALSE;\n    }\n\n    if (g_strcmp0(key, KEY_USE_PROXY) == 0) {\n        if (g_strcmp0(value, \"true\") == 0)\n            session->use_http_proxy = TRUE;\n        else\n            session->use_http_proxy = FALSE;\n    }\n\n    if (g_strcmp0(key, KEY_PROXY_TYPE) == 0) {\n        session->http_proxy_type =\n            g_strcmp0(value, \"none\") == 0 ? NULL : g_strdup(value);\n    }\n\n    if (g_strcmp0(key, KEY_PROXY_ADDR) == 0) {\n        session->http_proxy_addr = g_strdup(value);\n    }\n\n    if (g_strcmp0(key, KEY_PROXY_USERNAME) == 0) {\n        session->http_proxy_username = g_strdup(value);\n    }\n\n    if (g_strcmp0(key, KEY_PROXY_PASSWORD) == 0) {\n        session->http_proxy_password = g_strdup(value);\n    }\n\n    if (g_strcmp0(key, KEY_HIDE_WINDOWS_INCOMPATIBLE_PATH_NOTIFICATION) == 0) {\n        if (g_strcmp0(value, \"true\") == 0)\n            session->hide_windows_incompatible_path_notification = TRUE;\n        else\n            session->hide_windows_incompatible_path_notification = FALSE;\n    }\n\n    if (g_strcmp0(key, KEY_IGNORE_SYMLINKS) == 0) {\n        if (g_strcmp0(value, \"true\") == 0)\n            session->ignore_symlinks = TRUE;\n        else\n            session->ignore_symlinks = FALSE;\n    }\n\n    return 0;\n}\n\nint\nseafile_session_config_set_int (SeafileSession *session,\n                                const char *key,\n                                int value)\n{\n    char sql[256];\n\n    sqlite3_snprintf (sizeof(sql), sql,\n                      \"REPLACE INTO Config VALUES ('%q', %d);\",\n                      key, value);\n    if (sqlite_query_exec (session->config_db, sql) < 0)\n        return -1;\n\n    if (g_strcmp0(key, KEY_PROXY_PORT) == 0) {\n        session->http_proxy_port = value;\n    }\n    if (g_strcmp0(key, KEY_DELETE_CONFIRM_THRESHOLD) == 0) {\n        session->delete_confirm_threshold = value;\n    }\n\n    return 0;\n}\n\nsqlite3 *\nseafile_session_config_open_db (const char *db_path)\n{\n    sqlite3 *db;\n\n    if (sqlite_open_db (db_path, &db) < 0)\n        return NULL;\n\n    /*\n     * Values are stored in text. You should convert it\n     * back to integer if needed when you read it from\n     * db.\n     */\n    char *sql = \"CREATE TABLE IF NOT EXISTS Config (\"\n        \"key TEXT PRIMARY KEY, \"\n        \"value TEXT);\";\n    sqlite_query_exec (db, sql);\n\n    return db;\n}\n\nint\nseafile_session_config_set_allow_invalid_worktree(SeafileSession *session, gboolean val)\n{\n    return seafile_session_config_set_string(session, KEY_ALLOW_INVALID_WORKTREE,\n                                             val ? \"true\" : \"false\");\n}\n\ngboolean\nseafile_session_config_get_allow_invalid_worktree(SeafileSession *session)\n{\n    return seafile_session_config_get_bool (session, KEY_ALLOW_INVALID_WORKTREE);\n}\n\ngboolean\nseafile_session_config_get_allow_repo_not_found_on_server(SeafileSession *session)\n{\n    return seafile_session_config_get_bool (session,\n                                            KEY_ALLOW_REPO_NOT_FOUND_ON_SERVER);\n}\n"
  },
  {
    "path": "daemon/seafile-config.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef SEAFILE_CONFIG_H\n#define SEAFILE_CONFIG_H\n\n#include \"seafile-session.h\"\n#include \"db.h\"\n\n#define KEY_CLIENT_ID \"client_id\"\n#define KEY_CLIENT_NAME \"client_name\"\n\n#define KEY_MONITOR_ID  \"monitor_id\"\n#define KEY_CHECK_REPO_PERIOD \"check_repo_period\"\n#define KEY_DB_HOST \"db_host\"\n#define KEY_DB_USER \"db_user\"\n#define KEY_DB_PASSWD \"db_passwd\"\n#define KEY_DB_NAME \"db_name\"\n#define KEY_UPLOAD_LIMIT \"upload_limit\"\n#define KEY_DOWNLOAD_LIMIT \"download_limit\"\n#define KEY_CDC_AVERAGE_BLOCK_SIZE \"block_size\"\n#define KEY_ALLOW_INVALID_WORKTREE \"allow_invalid_worktree\"\n#define KEY_ALLOW_REPO_NOT_FOUND_ON_SERVER \"allow_repo_not_found_on_server\"\n#define KEY_SYNC_EXTRA_TEMP_FILE \"sync_extra_temp_file\"\n#define KEY_DISABLE_BLOCK_HASH \"disable_block_hash\"\n#define KEY_HIDE_WINDOWS_INCOMPATIBLE_PATH_NOTIFICATION \"hide_windows_incompatible_path_notification\"\n#define KEY_IGNORE_SYMLINKS \"ignore_symlinks\"\n\n/* Http sync settings. */\n#define KEY_ENABLE_HTTP_SYNC \"enable_http_sync\"\n#define KEY_DISABLE_VERIFY_CERTIFICATE \"disable_verify_certificate\"\n\n/* Http sync proxy settings. */\n#define KEY_USE_PROXY \"use_proxy\"\n#define KEY_PROXY_TYPE \"proxy_type\"\n#define KEY_PROXY_ADDR \"proxy_addr\"\n#define KEY_PROXY_PORT \"proxy_port\"\n#define KEY_PROXY_USERNAME \"proxy_username\"\n#define KEY_PROXY_PASSWORD \"proxy_password\"\n#define PROXY_TYPE_HTTP \"http\"\n#define PROXY_TYPE_SOCKS \"socks\"\n#define KEY_DELETE_CONFIRM_THRESHOLD \"delete_confirm_threshold\"\n\ngboolean\nseafile_session_config_exists (SeafileSession *session, const char *key);\n\n/*\n * Returns: config value in string. The string should be freed by caller. \n */\nchar *\nseafile_session_config_get_string (SeafileSession *session,\n                                   const char *key);\n\n/*\n * Returns:\n * If key exists, @exists will be set to TRUE and returns the value;\n * otherwise, @exists will be set to FALSE and returns -1.\n */\nint\nseafile_session_config_get_int (SeafileSession *session,\n                                const char *key,\n                                gboolean *exists);\n\n/*\n * Returns: config value in boolean. Return FALSE if the value is not configured. \n */\ngboolean\nseafile_session_config_get_bool (SeafileSession *session,\n                                 const char *key);\n\n\nint\nseafile_session_config_set_string (SeafileSession *session,\n                                   const char *key,\n                                   const char *value);\n\nint\nseafile_session_config_set_int (SeafileSession *session,\n                                const char *key,\n                                int value);\n\nint\nseafile_session_config_set_allow_invalid_worktree(SeafileSession *session, gboolean val);\n\ngboolean\nseafile_session_config_get_allow_invalid_worktree(SeafileSession *session);\n\ngboolean\nseafile_session_config_get_allow_repo_not_found_on_server(SeafileSession *session);\n\nsqlite3 *\nseafile_session_config_open_db (const char *db_path);\n\n\n#endif\n"
  },
  {
    "path": "daemon/seafile-error-impl.h",
    "content": "#ifndef SEAFILE_ERROR_IMPL_H\n#define SEAFILE_ERROR_IMPL_H\n\n#include \"seafile-error.h\"\n\nenum {\n    SYNC_ERROR_LEVEL_REPO,\n    SYNC_ERROR_LEVEL_FILE,\n    SYNC_ERROR_LEVEL_NETWORK,\n};\n\nconst char *\nsync_error_id_to_str (int error);\n\nint\nsync_error_level (int error);\n\n#endif\n"
  },
  {
    "path": "daemon/seafile-error.c",
    "content": "#include \"common.h\"\n\n#include \"seafile-error-impl.h\"\n\ntypedef struct SyncErrorInfo {\n    int error_id;\n    int error_level;\n    const char *err_str;\n} SyncErrorInfo;\n\nstatic SyncErrorInfo sync_error_info_tbl[] = {\n    {\n        SYNC_ERROR_ID_FILE_LOCKED_BY_APP,\n        SYNC_ERROR_LEVEL_FILE,\n        \"File is locked by another application\"\n    },\n    {\n        SYNC_ERROR_ID_FOLDER_LOCKED_BY_APP,\n        SYNC_ERROR_LEVEL_FILE,\n        \"Folder is locked by another application\"\n    },\n    {\n        SYNC_ERROR_ID_FILE_LOCKED,\n        SYNC_ERROR_LEVEL_FILE,\n        \"File is locked by another user\"\n    },\n    {\n        SYNC_ERROR_ID_INVALID_PATH,\n        SYNC_ERROR_LEVEL_FILE,\n        \"Path is invalid\"\n    },\n    {\n        SYNC_ERROR_ID_INDEX_ERROR,\n        SYNC_ERROR_LEVEL_FILE,\n        \"Error when indexing\"\n    },\n    {\n        SYNC_ERROR_ID_PATH_END_SPACE_PERIOD,\n        SYNC_ERROR_LEVEL_FILE,\n        \"Path ends with space or period character\"\n    },\n    {\n        SYNC_ERROR_ID_PATH_INVALID_CHARACTER,\n        SYNC_ERROR_LEVEL_FILE,\n        \"Path contains invalid characters like '|' or ':'\"\n    },\n    {\n        SYNC_ERROR_ID_FOLDER_PERM_DENIED,\n        SYNC_ERROR_LEVEL_FILE,\n        \"Update to file denied by folder permission setting\"\n    },\n    {\n        SYNC_ERROR_ID_PERM_NOT_SYNCABLE,\n        SYNC_ERROR_LEVEL_FILE,\n        \"No permission to sync this folder\"\n    },\n    {\n        SYNC_ERROR_ID_UPDATE_TO_READ_ONLY_REPO,\n        SYNC_ERROR_LEVEL_FILE,\n        \"Created or updated a file in a non-writable library or folder\"\n    },\n    {\n        SYNC_ERROR_ID_ACCESS_DENIED,\n        SYNC_ERROR_LEVEL_REPO,\n        \"Permission denied on server\"\n    },\n    {\n        SYNC_ERROR_ID_NO_WRITE_PERMISSION,\n        SYNC_ERROR_LEVEL_REPO,\n        \"Do not have write permission to the library\"\n    },\n    {\n        SYNC_ERROR_ID_QUOTA_FULL,\n        SYNC_ERROR_LEVEL_REPO,\n        \"Storage quota full\"\n    },\n    {\n        SYNC_ERROR_ID_NETWORK,\n        SYNC_ERROR_LEVEL_NETWORK,\n        \"Network error\",\n    },\n    {\n        SYNC_ERROR_ID_RESOLVE_PROXY,\n        SYNC_ERROR_LEVEL_NETWORK,\n        \"Cannot resolve proxy address\"\n    },\n    {\n        SYNC_ERROR_ID_RESOLVE_HOST,\n        SYNC_ERROR_LEVEL_NETWORK,\n        \"Cannot resolve server address\"\n    },\n    {\n        SYNC_ERROR_ID_CONNECT,\n        SYNC_ERROR_LEVEL_NETWORK,\n        \"Cannot connect to server\"\n    },\n    {\n        SYNC_ERROR_ID_SSL,\n        SYNC_ERROR_LEVEL_NETWORK,\n        \"Failed to establish secure connection. Please check server SSL certificate\"\n    },\n    {\n        SYNC_ERROR_ID_TX,\n        SYNC_ERROR_LEVEL_NETWORK,\n        \"Data transfer was interrupted. Please check network or firewall\"\n    },\n    {\n        SYNC_ERROR_ID_TX_TIMEOUT,\n        SYNC_ERROR_LEVEL_NETWORK,\n        \"Data transfer timed out. Please check network or firewall\"\n    },\n    {\n        SYNC_ERROR_ID_UNHANDLED_REDIRECT,\n        SYNC_ERROR_LEVEL_NETWORK,\n        \"Unhandled http redirect from server. Please check server cofiguration\"\n    },\n    {\n        SYNC_ERROR_ID_SERVER,\n        SYNC_ERROR_LEVEL_REPO,\n        \"Server error\"\n    },\n    {\n        SYNC_ERROR_ID_LOCAL_DATA_CORRUPT,\n        SYNC_ERROR_LEVEL_REPO,\n        \"Internal data corrupt on the client. Please try to resync the library\"\n    },\n    {\n        SYNC_ERROR_ID_WRITE_LOCAL_DATA,\n        SYNC_ERROR_LEVEL_REPO,\n        \"Failed to write data on the client. Please check disk space or folder permissions\"\n    },\n    {\n        SYNC_ERROR_ID_SERVER_REPO_DELETED,\n        SYNC_ERROR_LEVEL_REPO,\n        \"Library deleted on server\"\n    },\n    {\n        SYNC_ERROR_ID_SERVER_REPO_CORRUPT,\n        SYNC_ERROR_LEVEL_REPO,\n        \"Library damaged on server\"\n    },\n    {\n        SYNC_ERROR_ID_NOT_ENOUGH_MEMORY,\n        SYNC_ERROR_LEVEL_REPO,\n        \"Not enough memory\"\n    },\n    {\n        SYNC_ERROR_ID_CONFLICT,\n        SYNC_ERROR_LEVEL_FILE,\n        \"Concurrent updates to file. File is saved as conflict file\"\n    },\n    {\n        SYNC_ERROR_ID_GENERAL_ERROR,\n        SYNC_ERROR_LEVEL_REPO,\n        \"Unknown error\"\n    },\n    {\n        SYNC_ERROR_ID_NO_ERROR,\n        SYNC_ERROR_LEVEL_REPO,\n        \"No error\"\n    },\n    {\n        SYNC_ERROR_ID_REMOVE_UNCOMMITTED_FOLDER,\n        SYNC_ERROR_LEVEL_FILE,\n        \"A folder that may contain not-yet-uploaded files is moved to seafile-recycle-bin folder\"\n    },\n    {\n        SYNC_ERROR_ID_INVALID_PATH_ON_WINDOWS,\n        SYNC_ERROR_LEVEL_FILE,\n        \"File or directory is invalid on Windows\"\n    },\n    {\n        SYNC_ERROR_ID_LIBRARY_TOO_LARGE,\n        SYNC_ERROR_LEVEL_REPO,\n        \"Library cannot be synced since it has too many files\"\n    },\n    {\n        SYNC_ERROR_ID_DEL_CONFIRMATION_PENDING,\n        SYNC_ERROR_LEVEL_REPO,\n        \"Waiting for confirmation to delete files\"\n    },\n    {\n        SYNC_ERROR_ID_TOO_MANY_FILES,\n        SYNC_ERROR_LEVEL_REPO,\n        \"Files cannot be uploaded to this library due to file number limit settings\"\n    },\n    {\n        SYNC_ERROR_ID_CHECKOUT_FILE,\n        SYNC_ERROR_LEVEL_FILE,\n        \"Failed to checkout file on the client. Please check disk space or folder permissions\"\n    },\n    {\n        SYNC_ERROR_ID_BLOCK_MISSING,\n        SYNC_ERROR_LEVEL_REPO,\n        \"Failed to upload file blocks. Please check network or firewall\"\n    },\n    {\n        SYNC_ERROR_ID_CASE_CONFLICT,\n        SYNC_ERROR_LEVEL_FILE,\n        \"Path has character case conflict with existing file or folder. Will not be downloaded\"\n    },\n    {\n        SYNC_ERROR_ID_STOPPED_BY_LOGOUT,\n        SYNC_ERROR_LEVEL_REPO,\n        \"Syncing is stopped by logout. Please re-sync the library if needed\"\n    },\n    {\n        SYNC_ERROR_ID_CORRUPTED_ENC_KEY,\n        SYNC_ERROR_LEVEL_REPO,\n        \"Encryption key is corrupted. Please create a new library and upload files again\"\n    },\n};\n\nconst char *\nsync_error_id_to_str (int error)\n{\n    g_return_val_if_fail ((error >= 0 && error < N_SYNC_ERROR_ID), \"Unknown error\");\n\n    return sync_error_info_tbl[error].err_str;\n}\n\nint\nsync_error_level (int error)\n{\n    g_return_val_if_fail ((error >= 0 && error < N_SYNC_ERROR_ID), -1);\n\n    return sync_error_info_tbl[error].error_level;\n}\n"
  },
  {
    "path": "daemon/seafile-session.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#include <stdint.h>\n#ifndef WIN32\n#include <dirent.h>\n#include <unistd.h>\n#endif\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <errno.h>\n\n#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)\n#include <event2/event.h>\n#include <event2/event_compat.h>\n#include <event2/event_struct.h>\n#else\n#include <event.h>\n#endif\n\n#include <glib.h>\n\n#include \"utils.h\"\n\n#include \"seafile-session.h\"\n#include \"seafile-config.h\"\n#include \"vc-utils.h\"\n#include \"log.h\"\n\n#define MAX_THREADS 50\n\nenum {\n\tREPO_COMMITTED,\n    REPO_FETCHED,\n    REPO_UPLOADED,\n    REPO_HTTP_FETCHED,\n    REPO_HTTP_UPLOADED,\n    REPO_WORKTREE_CHECKED,\n\tLAST_SIGNAL\n};\n\nint signals[LAST_SIGNAL];\n\nG_DEFINE_TYPE (SeafileSession, seafile_session, G_TYPE_OBJECT);\n\n\nstatic void\nseafile_session_class_init (SeafileSessionClass *klass)\n{\n\n    signals[REPO_COMMITTED] =\n        g_signal_new (\"repo-committed\", SEAFILE_TYPE_SESSION,\n                      G_SIGNAL_RUN_LAST,\n                      0,        /* no class singal handler */\n                      NULL, NULL, /* no accumulator */\n                      g_cclosure_marshal_VOID__POINTER,\n                      G_TYPE_NONE, 1, G_TYPE_POINTER);\n\n    signals[REPO_FETCHED] =\n        g_signal_new (\"repo-fetched\", SEAFILE_TYPE_SESSION,\n                      G_SIGNAL_RUN_LAST,\n                      0,        /* no class singal handler */\n                      NULL, NULL, /* no accumulator */\n                      g_cclosure_marshal_VOID__POINTER,\n                      G_TYPE_NONE, 1, G_TYPE_POINTER);\n\n    signals[REPO_UPLOADED] =\n        g_signal_new (\"repo-uploaded\", SEAFILE_TYPE_SESSION,\n                      G_SIGNAL_RUN_LAST,\n                      0,        /* no class singal handler */\n                      NULL, NULL, /* no accumulator */\n                      g_cclosure_marshal_VOID__POINTER,\n                      G_TYPE_NONE, 1, G_TYPE_POINTER);\n    signals[REPO_HTTP_FETCHED] =\n        g_signal_new (\"repo-http-fetched\", SEAFILE_TYPE_SESSION,\n                      G_SIGNAL_RUN_LAST,\n                      0,        /* no class singal handler */\n                      NULL, NULL, /* no accumulator */\n                      g_cclosure_marshal_VOID__POINTER,\n                      G_TYPE_NONE, 1, G_TYPE_POINTER);\n\n    signals[REPO_HTTP_UPLOADED] =\n        g_signal_new (\"repo-http-uploaded\", SEAFILE_TYPE_SESSION,\n                      G_SIGNAL_RUN_LAST,\n                      0,        /* no class singal handler */\n                      NULL, NULL, /* no accumulator */\n                      g_cclosure_marshal_VOID__POINTER,\n                      G_TYPE_NONE, 1, G_TYPE_POINTER);\n}\n\nstatic int\ncreate_deleted_store_dirs (const char *deleted_store)\n{\n    char *commits = NULL, *fs = NULL, *blocks = NULL;\n    int ret = 0;\n\n    if (checkdir_with_mkdir (deleted_store) < 0) {\n        seaf_warning (\"Directory %s does not exist and is unable to create\\n\",\n                      deleted_store);\n        return -1;\n    }\n\n    commits = g_build_filename (deleted_store, \"commits\", NULL);\n    if (checkdir_with_mkdir (commits) < 0) {\n        seaf_warning (\"Directory %s does not exist and is unable to create\\n\",\n                      commits);\n        ret = -1;\n        goto out;\n    }\n\n    fs = g_build_filename (deleted_store, \"fs\", NULL);\n    if (checkdir_with_mkdir (fs) < 0) {\n        seaf_warning (\"Directory %s does not exist and is unable to create\\n\",\n                      fs);\n        ret = -1;\n        goto out;\n    }\n\n    blocks = g_build_filename (deleted_store, \"blocks\", NULL);\n    if (checkdir_with_mkdir (blocks) < 0) {\n        seaf_warning (\"Directory %s does not exist and is unable to create\\n\",\n                      blocks);\n        ret = -1;\n        goto out;\n    }\n\nout:\n    g_free (commits);\n    g_free (fs);\n    g_free (blocks);\n    return ret;\n}\n\nSeafileSession *\nseafile_session_new(const char *seafile_dir,\n                    const char *worktree_dir,\n                    const char *ccnet_dir)\n{\n    char *abs_seafile_dir;\n    char *abs_worktree_dir;\n    char *abs_ccnet_dir;\n    char *tmp_file_dir;\n    char *db_path;\n    char *deleted_store;\n    sqlite3 *config_db;\n    SeafileSession *session = NULL;\n\n    abs_worktree_dir = ccnet_expand_path (worktree_dir);\n    abs_seafile_dir = ccnet_expand_path (seafile_dir);\n    abs_ccnet_dir = ccnet_expand_path (ccnet_dir);\n    tmp_file_dir = g_build_filename (abs_seafile_dir, \"tmpfiles\", NULL);\n    db_path = g_build_filename (abs_seafile_dir, \"config.db\", NULL);\n    deleted_store = g_build_filename (abs_seafile_dir, \"deleted_store\", NULL);\n\n    if (checkdir_with_mkdir (abs_seafile_dir) < 0) {\n        seaf_warning (\"Config dir %s does not exist and is unable to create\\n\",\n                      abs_seafile_dir);\n        goto onerror;\n    }\n\n    if (checkdir_with_mkdir (abs_worktree_dir) < 0) {\n        seaf_warning (\"Worktree %s does not exist and is unable to create\\n\",\n                      abs_worktree_dir);\n        goto onerror;\n    }\n\n    if (checkdir_with_mkdir (tmp_file_dir) < 0) {\n        seaf_warning (\"Temp file dir %s does not exist and is unable to create\\n\",\n                      tmp_file_dir);\n        goto onerror;\n    }\n\n    if (create_deleted_store_dirs (deleted_store) < 0)\n        goto onerror;\n\n    config_db = seafile_session_config_open_db (db_path);\n    if (!config_db) {\n        seaf_warning (\"Failed to open config db.\\n\");\n        goto onerror;\n    }\n\n    session = g_object_new (SEAFILE_TYPE_SESSION, NULL);\n    session->ev_base = event_base_new ();\n    session->seaf_dir = abs_seafile_dir;\n    session->tmp_file_dir = tmp_file_dir;\n    session->worktree_dir = abs_worktree_dir;\n    session->ccnet_dir = abs_ccnet_dir;\n    session->config_db = config_db;\n    session->deleted_store = deleted_store;\n\n    session->fs_mgr = seaf_fs_manager_new (session, abs_seafile_dir);\n    if (!session->fs_mgr)\n        goto onerror;\n    session->block_mgr = seaf_block_manager_new (session, abs_seafile_dir);\n    if (!session->block_mgr)\n        goto onerror;\n    session->commit_mgr = seaf_commit_manager_new (session);\n    if (!session->commit_mgr)\n        goto onerror;\n    session->repo_mgr = seaf_repo_manager_new (session);\n    if (!session->repo_mgr)\n        goto onerror;\n    session->branch_mgr = seaf_branch_manager_new (session);\n    if (!session->branch_mgr)\n        goto onerror;\n\n    session->clone_mgr = seaf_clone_manager_new (session);\n    if (!session->clone_mgr)\n        goto onerror;\n    session->sync_mgr = seaf_sync_manager_new (session);\n    if (!session->sync_mgr)\n        goto onerror;\n    session->wt_monitor = seaf_wt_monitor_new (session);\n    if (!session->wt_monitor)\n        goto onerror;\n    session->http_tx_mgr = http_tx_manager_new (session);\n    if (!session->http_tx_mgr)\n        goto onerror;\n\n    session->filelock_mgr = seaf_filelock_manager_new (session);\n    if (!session->filelock_mgr)\n        goto onerror;\n\n    session->job_mgr = seaf_job_manager_new (session, MAX_THREADS);\n    session->ev_mgr = cevent_manager_new ();\n    if (!session->ev_mgr)\n        goto onerror;\n    \n    session->mq_mgr = seaf_mq_manager_new ();\n    if (!session->mq_mgr)\n        goto onerror;\n\n#if defined WIN32 || defined __APPLE__ || defined COMPILE_LINUX_WS\n    session->notif_mgr = seaf_notif_manager_new (session);\n#endif\n\n    return session;\n\nonerror:\n    free (abs_seafile_dir);\n    free (abs_worktree_dir);\n    free (abs_ccnet_dir);\n    g_free (tmp_file_dir);\n    g_free (db_path);\n    g_free (deleted_store);\n    g_free (session);\n    return NULL;    \n}\n\n\nstatic void\nseafile_session_init (SeafileSession *session)\n{\n}\n\nstatic void\nload_system_proxy (SeafileSession *session)\n{\n    char *system_proxy_txt = g_build_filename (seaf->seaf_dir, \"system-proxy.txt\", NULL);\n    json_t *json = NULL;\n    if (!g_file_test (system_proxy_txt, G_FILE_TEST_EXISTS)) {\n        seaf_warning (\"Can't load system proxy: file %s doesn't exist\\n\", system_proxy_txt);\n        goto out;\n    }\n\n    json_error_t jerror;\n    json = json_load_file(system_proxy_txt, 0, &jerror);\n    if (!json) {\n        if (strlen(jerror.text) > 0)\n            seaf_warning (\"Failed to load system proxy information: %s.\\n\", jerror.text);\n        else\n            seaf_warning (\"Failed to load system proxy information\\n\");\n        goto out;\n    }\n    const char *type;\n    type = json_object_get_string_member (json, \"type\");\n    if (!type) {\n        seaf_warning (\"Failed to load system proxy information: proxy type missing\\n\");\n        goto out;\n    }\n    if (strcmp(type, \"none\") != 0 && strcmp(type, \"socks\") != 0 && strcmp(type, \"http\") != 0) {\n        seaf_warning (\"Failed to load system proxy information: invalid proxy type %s\\n\", type);\n        goto out;\n    }\n    if (g_strcmp0(type, \"none\") == 0) {\n        goto out;\n    }\n    session->http_proxy_type = g_strdup(type);\n    session->http_proxy_addr = g_strdup(json_object_get_string_member (json, \"addr\"));\n    session->http_proxy_port = json_object_get_int_member (json, \"port\");\n    session->http_proxy_username = g_strdup(json_object_get_string_member (json, \"username\"));\n    session->http_proxy_password = g_strdup(json_object_get_string_member (json, \"password\"));\n\nout:\n    g_free (system_proxy_txt);\n    if (json)\n        json_decref(json);\n}\n\nstatic char *\ngenerate_client_id ()\n{\n    char *uuid = gen_uuid();\n    unsigned char buf[20];\n    char sha1[41];\n\n    calculate_sha1 (buf, uuid, 20);\n    rawdata_to_hex (buf, sha1, 20);\n\n    g_free (uuid);\n    return g_strdup(sha1);\n}\n\nstatic void\nread_ccnet_conf (const char *ccnet_dir, char **client_id, char **client_name)\n{\n    char *ccnet_conf_path = g_build_path (\"/\", ccnet_dir, \"ccnet.conf\", NULL);\n    GKeyFile *key_file = g_key_file_new ();\n    GError *error = NULL;\n\n    if (!g_file_test (ccnet_conf_path, G_FILE_TEST_IS_REGULAR))\n        goto out;\n\n    if (!g_key_file_load_from_file (key_file, ccnet_conf_path, 0, &error)) {\n        seaf_warning (\"Failed to read ccnet.conf: %s.\\n\", error->message);\n        g_clear_error (&error);\n        goto out;\n    }\n\n    *client_id = g_key_file_get_string (key_file, \"General\", \"ID\", &error);\n    if (error) {\n        seaf_warning (\"Failed to read client id from ccnet.conf: %s.\\n\", error->message);\n        g_clear_error (&error);\n        goto out;\n    }\n\n    *client_name = g_key_file_get_string (key_file, \"General\", \"NAME\", &error);\n    if (error) {\n        seaf_warning (\"Failed to read client name from ccnet.conf: %s.\\n\", error->message);\n        g_clear_error (&error);\n        goto out;\n    }\n\nout:\n    g_free (ccnet_conf_path);\n    g_key_file_free (key_file);\n}\n\n#define MAX_DELETED_FILES_NUM 500\n\nvoid\nseafile_session_prepare (SeafileSession *session)\n{\n    char *client_id = NULL, *client_name = NULL;\n\n    /* load config */\n\n    read_ccnet_conf (session->ccnet_dir, &client_id, &client_name);\n\n    session->client_id = seafile_session_config_get_string (session, KEY_CLIENT_ID);\n    if (!session->client_id) {\n        if (client_id) {\n            session->client_id = g_strdup (client_id);\n        } else {\n            session->client_id = generate_client_id();\n        }\n        seafile_session_config_set_string (session,\n                                           KEY_CLIENT_ID,\n                                           session->client_id);\n    }\n\n    session->client_name = seafile_session_config_get_string (session, KEY_CLIENT_NAME);\n    if (!session->client_name) {\n        if (client_name) {\n            session->client_name = g_strdup (client_name);\n            seafile_session_config_set_string (session,\n                                               KEY_CLIENT_NAME,\n                                               session->client_name);\n        } else {\n            session->client_name = g_strdup(\"unknown\");\n        }\n    }\n\n    seaf_warning (\"client id = %s, client_name = %s\\n\", session->client_id, session->client_name);\n    g_free (client_id);\n    g_free (client_name);\n\n    session->sync_extra_temp_file = seafile_session_config_get_bool\n        (session, KEY_SYNC_EXTRA_TEMP_FILE);\n\n    /* Enable http sync by default. */\n    session->enable_http_sync = TRUE;\n\n    session->disable_verify_certificate = seafile_session_config_get_bool\n        (session, KEY_DISABLE_VERIFY_CERTIFICATE);\n\n    session->use_http_proxy =\n        seafile_session_config_get_bool(session, KEY_USE_PROXY);\n\n    gboolean use_system_proxy =\n        seafile_session_config_get_bool(session, \"use_system_proxy\");\n\n    if (use_system_proxy) {\n        load_system_proxy(session);\n    } else {\n        session->http_proxy_type =\n            seafile_session_config_get_string(session, KEY_PROXY_TYPE);\n        session->http_proxy_addr =\n            seafile_session_config_get_string(session, KEY_PROXY_ADDR);\n        session->http_proxy_port =\n            seafile_session_config_get_int(session, KEY_PROXY_PORT, NULL);\n        session->http_proxy_username =\n            seafile_session_config_get_string(session, KEY_PROXY_USERNAME);\n        session->http_proxy_password =\n            seafile_session_config_get_string(session, KEY_PROXY_PASSWORD);\n    }\n\n    session->delete_confirm_threshold = seafile_session_config_get_int (session, KEY_DELETE_CONFIRM_THRESHOLD, NULL);\n    if (session->delete_confirm_threshold <= 0)\n        session->delete_confirm_threshold = MAX_DELETED_FILES_NUM;\n\n    int block_size = seafile_session_config_get_int(session, KEY_CDC_AVERAGE_BLOCK_SIZE, NULL);\n    if (block_size >= 1024) {\n        session->cdc_average_block_size = block_size;\n    } else if (block_size == -1) {\n        session->cdc_average_block_size = 0;\n    } else {\n        session->cdc_average_block_size = 0;\n        seaf_message (\"Block size less than 1KB. Use default block size(8MB).\\n\");\n    }\n\n    session->disable_block_hash =\n        seafile_session_config_get_bool (session, KEY_DISABLE_BLOCK_HASH);\n    \n    char *value = seafile_session_config_get_string(session, KEY_HIDE_WINDOWS_INCOMPATIBLE_PATH_NOTIFICATION);\n    if (g_strcmp0 (value, \"false\") == 0)\n        session->hide_windows_incompatible_path_notification = FALSE;\n    else \n        session->hide_windows_incompatible_path_notification = TRUE;\n    g_free (value);\n\n    value = seafile_session_config_get_string(session, KEY_IGNORE_SYMLINKS);\n    if (g_strcmp0 (value, \"true\") == 0)\n        session->ignore_symlinks = TRUE;\n    else \n        session->ignore_symlinks = FALSE;\n    g_free (value);\n    \n    /* Start mq manager earlier, so that we can send notifications\n     * when start repo manager. */\n    seaf_mq_manager_init (session->mq_mgr);\n    seaf_commit_manager_init (session->commit_mgr);\n    seaf_fs_manager_init (session->fs_mgr);\n    seaf_branch_manager_init (session->branch_mgr);\n    seaf_filelock_manager_init (session->filelock_mgr);\n    seaf_repo_manager_init (session->repo_mgr);\n    seaf_clone_manager_init (session->clone_mgr);\n#ifndef SEAF_TOOL    \n    seaf_sync_manager_init (session->sync_mgr);\n#endif\n}\n\n/* static void */\n/* recover_interrupted_merges () */\n/* { */\n/*     GList *repos, *ptr; */\n/*     SeafRepo *repo; */\n/*     SeafRepoMergeInfo info; */\n/*     char *err_msg = NULL; */\n/*     gboolean unused; */\n\n/*     repos = seaf_repo_manager_get_repo_list (seaf->repo_mgr, -1, -1); */\n/*     for (ptr = repos; ptr; ptr = ptr->next) { */\n/*         repo = ptr->data; */\n\n/*         if (seaf_repo_manager_get_merge_info (seaf->repo_mgr, repo->id, &info) < 0) { */\n/*             seaf_warning (\"Failed to get merge info for repo %s.\\n\", repo->id); */\n/*             continue; */\n/*         } */\n\n/*         if (info.in_merge) { */\n/*             seaf_message (\"Recovering merge for repo %.8s.\\n\", repo->id); */\n\n/*             /\\* No one else is holding the lock. *\\/ */\n/*             pthread_mutex_lock (&repo->lock); */\n/*             if (seaf_repo_merge (repo, \"master\", &err_msg, &unused) < 0) { */\n/*                 g_free (err_msg); */\n/*             } */\n/*             pthread_mutex_unlock (&repo->lock); */\n/*         } */\n/*     } */\n/*     g_list_free (repos); */\n/* } */\n\nstatic gboolean\nis_repo_store_in_use (const char *repo_id)\n{\n    if (seaf_repo_manager_repo_exists (seaf->repo_mgr, repo_id))\n        return TRUE;\n\n    char sql[256];\n    snprintf (sql, sizeof(sql), \"SELECT 1 FROM CloneTasks WHERE repo_id='%s'\",\n              repo_id);\n    if (sqlite_check_for_existence (seaf->clone_mgr->db, sql))\n        return TRUE;\n\n    return FALSE;\n}\n\nstatic void\ncleanup_unused_repo_stores (const char *type)\n{\n    char *top_store_dir;\n    const char *repo_id;\n\n    top_store_dir = g_build_filename (seaf->seaf_dir, \"storage\", type, NULL);\n\n    GError *error = NULL;\n    GDir *dir = g_dir_open (top_store_dir, 0, &error);\n    if (!dir) {\n        seaf_warning (\"Failed to open store dir %s: %s.\\n\",\n                      top_store_dir, error->message);\n        g_free (top_store_dir);\n        return;\n    }\n\n    while ((repo_id = g_dir_read_name(dir)) != NULL) {\n        if (!is_repo_store_in_use (repo_id)) {\n            seaf_message (\"Moving %s for deleted repo %s.\\n\", type, repo_id);\n            seaf_repo_manager_move_repo_store (seaf->repo_mgr, type, repo_id);\n        }\n    }\n\n    g_free (top_store_dir);\n    g_dir_close (dir);\n}\n\nstatic void *\non_start_cleanup_job (void *vdata)\n{\n    /* recover_interrupted_merges (); */\n\n    /* Ignore migration errors. If any blocks is not migrated successfully,\n     * there will be some sync error in run time. The user has to recover the\n     * error by resyncing.\n     */\n    /* migrate_client_v0_repos (); */\n\n    cleanup_unused_repo_stores (\"commits\");\n    cleanup_unused_repo_stores (\"fs\");\n    cleanup_unused_repo_stores (\"blocks\");\n\n    return vdata;\n}\n\nstatic void\ncleanup_job_done (void *vdata)\n{\n    SeafileSession *session = vdata;\n\n    if (cevent_manager_start (session->ev_mgr) < 0) {\n        g_error (\"Failed to start event manager.\\n\");\n        return;\n    }\n\n    if (http_tx_manager_start (session->http_tx_mgr) < 0) {\n        g_error (\"Failed to start http transfer manager.\\n\");\n        return;\n    }\n\n    if (seaf_sync_manager_start (session->sync_mgr) < 0) {\n        g_error (\"Failed to start sync manager.\\n\");\n        return;\n    }\n\n    if (seaf_wt_monitor_start (session->wt_monitor) < 0) {\n        g_error (\"Failed to start worktree monitor.\\n\");\n        return;\n    }\n\n    /* Must be after wt monitor, since we may add watch to repo worktree. */\n    if (seaf_repo_manager_start (session->repo_mgr) < 0) {\n        g_error (\"Failed to start repo manager.\\n\");\n        return;\n    }\n\n    if (seaf_clone_manager_start (session->clone_mgr) < 0) {\n        g_error (\"Failed to start clone manager.\\n\");\n        return;\n    }\n\n    if (seaf_filelock_manager_start (session->filelock_mgr) < 0) {\n        g_error (\"Failed to start filelock manager.\\n\");\n        return;\n    }\n\n    /* The system is up and running. */\n    session->started = TRUE;\n}\n\nstatic void\non_start_cleanup (SeafileSession *session)\n{\n    seaf_job_manager_schedule_job (seaf->job_mgr, \n                                   on_start_cleanup_job, \n                                   cleanup_job_done,\n                                   session);\n}\n\nvoid\nseafile_session_start (SeafileSession *session)\n{\n    /* Finish cleanup task before anything is run. */\n    on_start_cleanup (session);\n}\n\n"
  },
  {
    "path": "daemon/seafile-session.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef SEAFILE_SESSION_H\n#define SEAFILE_SESSION_H\n\n#include <glib-object.h>\n#include \"cevent.h\"\n#include \"job-mgr.h\"\n\n#include \"block-mgr.h\"\n#include \"fs-mgr.h\"\n#include \"commit-mgr.h\"\n#include \"branch-mgr.h\"\n#include \"repo-mgr.h\"\n#include \"clone-mgr.h\"\n#include \"db.h\"\n\n#include \"sync-mgr.h\"\n#include \"wt-monitor.h\"\n#include \"mq-mgr.h\"\n#include \"notif-mgr.h\"\n\n#include \"http-tx-mgr.h\"\n#include \"filelock-mgr.h\"\n\n\n#define SEAFILE_TYPE_SESSION                  (seafile_session_get_type ())\n#define SEAFILE_SESSION(obj)                  (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_SESSION, SeafileSession))\n#define SEAFILE_IS_SESSION(obj)               (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_SESSION))\n#define SEAFILE_SESSION_CLASS(klass)          (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_SESSION, SeafileSessionClass))\n#define SEAFILE_IS_SESSION_CLASS(klass)       (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_SESSION))\n#define SEAFILE_SESSION_GET_CLASS(obj)        (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_SESSION, SeafileSessionClass))\n\n\ntypedef struct _SeafileSession SeafileSession;\ntypedef struct _SeafileSessionClass SeafileSessionClass;\n\nstruct event_base;\n\nstruct _SeafileSession {\n    GObject         parent_instance;\n\n    struct event_base   *ev_base;\n\n    char                *client_id;\n    char                *client_name;\n\n    char                *seaf_dir;\n    char                *tmp_file_dir;\n    char                *worktree_dir; /* the default directory for\n                                        * storing worktrees  */\n    char                *ccnet_dir;\n    sqlite3             *config_db;\n    char                *deleted_store;\n    char                *rpc_socket_path;\n\n    uint32_t            cdc_average_block_size;\n    SeafBlockManager    *block_mgr;\n    SeafFSManager       *fs_mgr;\n    SeafCommitManager   *commit_mgr;\n    SeafBranchManager   *branch_mgr;\n    SeafRepoManager     *repo_mgr;\n    SeafCloneManager    *clone_mgr;\n    SeafSyncManager     *sync_mgr;\n    SeafWTMonitor       *wt_monitor;\n    SeafMqManager       *mq_mgr;\n\n    CEventManager       *ev_mgr;\n    SeafJobManager     *job_mgr;\n\n    HttpTxManager       *http_tx_mgr;\n\n    SeafFilelockManager *filelock_mgr;\n\n    SeafNotifManager    *notif_mgr;\n\n    /* Set after all components are up and running. */\n    gboolean             started;\n\n    gboolean             sync_extra_temp_file;\n    gboolean             enable_http_sync;\n    gboolean             disable_verify_certificate;\n\n    gboolean             disable_block_hash;\n    \n    gboolean             hide_windows_incompatible_path_notification;\n\n    gboolean             ignore_symlinks;\n\n    gboolean             use_http_proxy;\n    char                *http_proxy_type;\n    char                *http_proxy_addr;\n    int                  http_proxy_port;\n    char                *http_proxy_username;\n    char                *http_proxy_password;\n    int                 delete_confirm_threshold;\n\n    gboolean check_enc_blocks;\n};\n\nstruct _SeafileSessionClass\n{\n    GObjectClass    parent_class;\n};\n\n\nextern SeafileSession *seaf;\n\nSeafileSession *\nseafile_session_new(const char *seafile_dir,\n                    const char *worktree_dir,\n                    const char *config_dir);\nvoid\nseafile_session_prepare (SeafileSession *session);\n\nvoid\nseafile_session_start (SeafileSession *session);\n\nchar *\nseafile_session_get_tmp_file_path (SeafileSession *session,\n                                   const char *basename,\n                                   char path[]);\n#if 0\nvoid\nseafile_session_add_event (SeafileSession *session, \n                           const char *type,\n                           const char *first, ...);\n#endif\n\n#endif /* SEAFILE_H */\n"
  },
  {
    "path": "daemon/set-perm.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#include \"utils.h\"\n#define DEBUG_FLAG SEAFILE_DEBUG_SYNC\n#include \"log.h\"\n\n#include \"set-perm.h\"\n\n#ifdef WIN32\n\n#ifndef _WIN32_WINNT\n#define _WIN32_WINNT 0x501\n#endif\n\n#include <windows.h>\n#include <AccCtrl.h>\n#include <AclApi.h>\n\n#define WIN32_WRITE_ACCESS_MASK (FILE_WRITE_DATA | FILE_APPEND_DATA | \\\n                                 FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES | DELETE)\n\n// Remove explicit ACEs set by us.\nstatic int\nunset_permissions (PACL dacl)\n{\n    ACL_SIZE_INFORMATION size_info;\n\n    if (!dacl)\n        return 0;\n\n    if (!GetAclInformation (dacl, &size_info,\n                            sizeof(size_info), AclSizeInformation)) {\n        seaf_warning (\"GetAclInformation Error: %lu\\n\", GetLastError());\n        return -1;\n    }\n\n    DWORD i;\n    ACE_HEADER *ace;\n    ACCESS_DENIED_ACE *deny_ace;\n    ACCESS_ALLOWED_ACE *allowed_ace;\n    for (i = 0; i < size_info.AceCount; ++i) {\n        if (!GetAce(dacl, i, (void**)&ace)) {\n            seaf_warning (\"GetAce Error: %lu\\n\", GetLastError());\n            return -1;\n        }\n\n        // Skip inherited ACEs.\n        if (ace->AceFlags & INHERITED_ACE)\n            continue;\n\n        if (ace->AceType == ACCESS_DENIED_ACE_TYPE) {\n            deny_ace = (ACCESS_DENIED_ACE *)ace;\n            if (deny_ace->Mask == WIN32_WRITE_ACCESS_MASK) {\n                DeleteAce(dacl, i);\n                break;\n            }\n        } else if (ace->AceType == ACCESS_ALLOWED_ACE_TYPE) {\n            allowed_ace = (ACCESS_ALLOWED_ACE *)ace;\n            if (allowed_ace->Mask == WIN32_WRITE_ACCESS_MASK) {\n                DeleteAce(dacl, i);\n                break;\n            }\n        }\n    }\n\n    return 0;\n}\n\nint\nseaf_set_path_permission (const char *path, SeafPathPerm perm, gboolean recursive)\n{\n    wchar_t *wpath = NULL;\n    int ret = 0;\n    DWORD res = 0;\n    PACL old_dacl = NULL, new_dacl = NULL;\n    PSECURITY_DESCRIPTOR sd = NULL;\n    EXPLICIT_ACCESS ea;\n\n    g_return_val_if_fail (perm == SEAF_PATH_PERM_RO || perm == SEAF_PATH_PERM_RW, -1);\n\n    seaf_debug (\"set permission for %s, perm: %d, recursive: %d\\n\",\n                path, perm, recursive);\n\n    wpath = win32_long_path (path);\n    if (!wpath)\n        return -1;\n\n    res = GetNamedSecurityInfoW(wpath, SE_FILE_OBJECT, \n                                DACL_SECURITY_INFORMATION,\n                                NULL, NULL, &old_dacl, NULL, &sd);\n    if (ERROR_SUCCESS != res) {\n        seaf_warning( \"GetNamedSecurityInfo Error for path %s: %lu\\n\", path, res );\n        ret = -1;\n        goto cleanup;\n    }\n\n    unset_permissions (old_dacl);\n\n    // Initialize an EXPLICIT_ACCESS structure for the new ACE. \n\n    memset (&ea, 0, sizeof(EXPLICIT_ACCESS));\n    ea.grfAccessPermissions = WIN32_WRITE_ACCESS_MASK;\n    ea.grfAccessMode = ((perm == SEAF_PATH_PERM_RO)?DENY_ACCESS:GRANT_ACCESS);\n    ea.grfInheritance = (CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE);\n    ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;\n    ea.Trustee.TrusteeType = TRUSTEE_IS_USER;\n    ea.Trustee.ptstrName = L\"CURRENT_USER\";\n\n    // Create a new ACL that merges the new ACE\n    // into the existing DACL.\n\n    res = SetEntriesInAcl(1, &ea, old_dacl, &new_dacl);\n    if (ERROR_SUCCESS != res)  {\n        seaf_warning( \"SetEntriesInAcl Error %lu\\n\", res );\n        ret = -1;\n        goto cleanup;\n    }  \n\n    // Attach the new ACL as the object's DACL.\n\n    if (recursive) {\n        res = SetNamedSecurityInfoW(wpath, SE_FILE_OBJECT, \n                                    DACL_SECURITY_INFORMATION,\n                                    NULL, NULL, new_dacl, NULL);\n        if (ERROR_SUCCESS != res)  {\n            seaf_warning( \"SetNamedSecurityInfo Error %lu\\n\", res );\n            ret = -1;\n            goto cleanup;\n        }\n    } else {\n        SECURITY_DESCRIPTOR new_sd;\n\n        InitializeSecurityDescriptor (&new_sd, SECURITY_DESCRIPTOR_REVISION);\n        SetSecurityDescriptorDacl (&new_sd, TRUE, new_dacl, FALSE);\n\n        if (!SetFileSecurityW (wpath, DACL_SECURITY_INFORMATION, &new_sd)) {\n            seaf_warning (\"SetFileSecurity Error %lu\\n\", GetLastError());\n            ret = -1;\n            goto cleanup;\n        }\n    }\n\n cleanup:\n    g_free (wpath);\n    if(sd != NULL) \n        LocalFree((HLOCAL) sd);\n    if(new_dacl != NULL) \n        LocalFree((HLOCAL) new_dacl);\n    return ret;\n}\n\nint\nseaf_unset_path_permission (const char *path, gboolean recursive)\n{\n    wchar_t *wpath = NULL;\n    int ret = 0;\n    DWORD res = 0;\n    PACL old_dacl = NULL, new_dacl = NULL;\n    PSECURITY_DESCRIPTOR sd = NULL;\n\n    seaf_debug (\"unset permission for %s, recursive: %d\\n\",\n                path, recursive);\n\n    wpath = win32_long_path (path);\n    if (!wpath)\n        return -1;\n\n    res = GetNamedSecurityInfoW(wpath, SE_FILE_OBJECT, \n                                DACL_SECURITY_INFORMATION,\n                                NULL, NULL, &old_dacl, NULL, &sd);\n    if (ERROR_SUCCESS != res) {\n        seaf_warning( \"GetNamedSecurityInfo Error %lu\\n\", res );\n        ret = -1;\n        goto cleanup;\n    }\n\n    // Create a new copy of the old ACL\n\n    res = SetEntriesInAcl(0, NULL, old_dacl, &new_dacl);\n    if (ERROR_SUCCESS != res)  {\n        seaf_warning( \"SetEntriesInAcl Error %lu\\n\", res );\n        ret = -1;\n        goto cleanup;\n    }\n\n    if (!new_dacl) {\n        goto cleanup;\n    }\n\n    unset_permissions (new_dacl);\n\n    // Update path's ACL\n\n    if (recursive) {\n        res = SetNamedSecurityInfoW(wpath, SE_FILE_OBJECT, \n                                    DACL_SECURITY_INFORMATION,\n                                    NULL, NULL, new_dacl, NULL);\n        if (ERROR_SUCCESS != res)  {\n            seaf_warning( \"SetNamedSecurityInfo Error %lu\\n\", res );\n            ret = -1;\n            goto cleanup;\n        }\n    } else {\n        SECURITY_DESCRIPTOR new_sd;\n\n        InitializeSecurityDescriptor (&new_sd, SECURITY_DESCRIPTOR_REVISION);\n        SetSecurityDescriptorDacl (&new_sd, TRUE, new_dacl, FALSE);\n\n        if (!SetFileSecurityW (wpath, DACL_SECURITY_INFORMATION, &new_sd)) {\n            seaf_warning (\"SetFileSecurity Error %lu\\n\", GetLastError());\n            ret = -1;\n            goto cleanup;\n        }\n    }\n\n cleanup:\n    g_free (wpath);\n    if(sd != NULL) \n        LocalFree((HLOCAL) sd);\n    if(new_dacl != NULL) \n        LocalFree((HLOCAL) new_dacl);\n    return ret;\n}\n\nSeafPathPerm\nseaf_get_path_permission (const char *path)\n{\n    wchar_t *wpath = NULL;\n    SeafPathPerm ret = SEAF_PATH_PERM_UNKNOWN;\n    DWORD res = 0;\n    PACL dacl = NULL;\n    PSECURITY_DESCRIPTOR sd = NULL;\n\n    wpath = win32_long_path (path);\n    if (!wpath)\n        return ret;\n\n    res = GetNamedSecurityInfoW(wpath, SE_FILE_OBJECT, \n                                DACL_SECURITY_INFORMATION,\n                                NULL, NULL, &dacl, NULL, &sd);\n    if (ERROR_SUCCESS != res) {\n        seaf_warning( \"GetNamedSecurityInfo Error %lu\\n\", res );\n        goto cleanup;\n    }\n\n    ACL_SIZE_INFORMATION size_info;\n\n    if (!GetAclInformation (dacl, &size_info,\n                            sizeof(size_info), AclSizeInformation)) {\n        seaf_warning (\"GetAclInformation Error: %lu\\n\", GetLastError());\n        goto cleanup;\n    }\n\n    DWORD i;\n    ACE_HEADER *ace;\n    ACCESS_DENIED_ACE *deny_ace;\n    ACCESS_ALLOWED_ACE *allowed_ace;\n    for (i = 0; i < size_info.AceCount; ++i) {\n        if (!GetAce(dacl, i, (void**)&ace)) {\n            seaf_warning (\"GetAce Error: %lu\\n\", GetLastError());\n            goto cleanup;\n        }\n\n        // Skip inherited ACEs.\n        if (ace->AceFlags & INHERITED_ACE)\n            continue;\n\n        if (ace->AceType == ACCESS_DENIED_ACE_TYPE) {\n            deny_ace = (ACCESS_DENIED_ACE *)ace;\n            if (deny_ace->Mask == WIN32_WRITE_ACCESS_MASK) {\n                ret = SEAF_PATH_PERM_RO;\n                break;\n            }\n        } else if (ace->AceType == ACCESS_ALLOWED_ACE_TYPE) {\n            allowed_ace = (ACCESS_ALLOWED_ACE *)ace;\n            if (allowed_ace->Mask == WIN32_WRITE_ACCESS_MASK) {\n                ret = SEAF_PATH_PERM_RW;\n                break;\n            }\n        }\n    }\n\ncleanup:\n    g_free (wpath);\n    if(sd != NULL) \n        LocalFree((HLOCAL) sd);\n    return ret;\n}\n\n#else\n\n#include <sys/stat.h>\n\nint\nseaf_set_path_permission (const char *path, SeafPathPerm perm, gboolean recursive)\n{\n    struct stat st;\n    mode_t new_mode;\n\n    if (stat (path, &st) < 0) {\n        seaf_warning (\"Failed to stat %s: %s\\n\", path, strerror(errno));\n        return -1;\n    }\n\n    new_mode = st.st_mode;\n    if (perm == SEAF_PATH_PERM_RO)\n        new_mode &= ~(S_IWUSR);\n    else if (perm == SEAF_PATH_PERM_RW)\n        new_mode |= S_IWUSR;\n\n    if (chmod (path, new_mode) < 0) {\n        seaf_warning (\"Failed to chmod %s to %d: %s\\n\", path, new_mode, strerror(errno));\n        return -1;\n    }\n\n    return 0;\n}\n\nint\nseaf_unset_path_permission (const char *path, gboolean recursive)\n{\n    return 0;\n}\n\nSeafPathPerm\nseaf_get_path_permission (const char *path)\n{\n    return SEAF_PATH_PERM_UNKNOWN;\n}\n\n#endif  /* WIN32 */\n"
  },
  {
    "path": "daemon/set-perm.h",
    "content": "#ifndef SEAF_SET_PERM_H\n#define SEAF_SET_PERM_H\n\nenum SeafPathPerm {\n    SEAF_PATH_PERM_UNKNOWN = 0,\n    SEAF_PATH_PERM_RO,\n    SEAF_PATH_PERM_RW,\n};\ntypedef enum SeafPathPerm SeafPathPerm;\n\nint\nseaf_set_path_permission (const char *path, SeafPathPerm perm, gboolean recursive);\n\nint\nseaf_unset_path_permission (const char *path, gboolean recursive);\n\nSeafPathPerm\nseaf_get_path_permission (const char *path);\n\n#endif\n"
  },
  {
    "path": "daemon/sync-mgr.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n\n#include \"common.h\"\n\n#include <pthread.h>\n\n#include \"db.h\"\n#include \"seafile-session.h\"\n#include \"seafile-config.h\"\n#include \"sync-mgr.h\"\n#include \"seafile-error-impl.h\"\n#include \"mq-mgr.h\"\n#include \"utils.h\"\n#include \"vc-utils.h\"\n\n#include \"sync-status-tree.h\"\n#include \"diff-simple.h\"\n\n#ifdef WIN32\n#include <shlobj.h>\n#endif\n\n#define DEBUG_FLAG SEAFILE_DEBUG_SYNC\n#include \"log.h\"\n\n#include \"timer.h\"\n\n#define DEFAULT_SYNC_INTERVAL 30 /* 30s */\n#define CHECK_SYNC_INTERVAL  1000 /* 1s */\n#define UPDATE_TX_STATE_INTERVAL 1000 /* 1s */\n#define MAX_RUNNING_SYNC_TASKS 5\n#define CHECK_LOCKED_FILES_INTERVAL 10 /* 10s */\n#define CHECK_FOLDER_PERMS_INTERVAL 30 /* 30s */\n#define JWT_TOKEN_EXPIRE_TIME 3*24*3600 /* 3 days */\n\n#define SYNC_PERM_ERROR_RETRY_TIME 2\n\nstruct _HttpServerState {\n    int http_version;\n    gboolean checking;\n    gint64 last_http_check_time;\n    char *testing_host;\n    /* Can be server_url or server_url:8082, depends on which one works. */\n    char *effective_host;\n    gboolean use_fileserver_port;\n\n    gboolean notif_server_checked;\n    gboolean notif_server_alive;\n\n    gboolean server_disconnected;\n\n    gboolean folder_perms_not_supported;\n    gint64 last_check_perms_time;\n    gboolean checking_folder_perms;\n\n    gboolean locked_files_not_supported;\n    gint64 last_check_locked_files_time;\n    gboolean checking_locked_files;\n\n    gboolean immediate_check_folder_perms;\n    gboolean immediate_check_locked_files;\n\n    /*\n     * repo_id -> head commit id mapping.\n     * Caches the head commit ids of synced repos.\n     */\n    GHashTable *head_commit_map;\n    pthread_mutex_t head_commit_map_lock;\n    gboolean head_commit_map_init;\n    gint64 last_update_head_commit_map_time;\n\n    gint64 n_jwt_token_request;\n};\ntypedef struct _HttpServerState HttpServerState;\n\ntypedef struct DelConfirmationResult {\n    gboolean resync;\n} DelConfirmationResult;\n\nstruct _SeafSyncManagerPriv {\n    struct SeafTimer *check_sync_timer;\n    struct SeafTimer *update_tx_state_timer;\n    int    pulse_count;\n\n    /* When FALSE, auto sync is globally disabled */\n    gboolean   auto_sync_enabled;\n\n    GHashTable *active_paths;\n    pthread_mutex_t paths_lock;\n\n#ifdef WIN32\n    GAsyncQueue *refresh_paths;\n    struct SeafTimer *refresh_windows_timer;\n#endif\n\n    pthread_mutex_t del_confirmation_lock;\n    GHashTable *del_confirmation_tasks;\n};\n\nstruct _ActivePathsInfo {\n    GHashTable *paths;\n    struct SyncStatusTree *syncing_tree;\n    struct SyncStatusTree *synced_tree;\n};\ntypedef struct _ActivePathsInfo ActivePathsInfo;\n\nstatic int auto_sync_pulse (void *vmanager);\n\nstatic void on_repo_http_fetched (SeafileSession *seaf,\n                                  HttpTxTask *tx_task,\n                                  SeafSyncManager *manager);\nstatic void on_repo_http_uploaded (SeafileSession *seaf,\n                                   HttpTxTask *tx_task,\n                                   SeafSyncManager *manager);\n\nstatic inline void\ntransition_sync_state (SyncTask *task, int new_state);\n\nstatic void sync_task_free (SyncTask *task);\n\nstatic int\nsync_repo_v2 (SeafSyncManager *manager, SeafRepo *repo, gboolean is_manual_sync);\n\nstatic gboolean\ncheck_http_protocol (SeafSyncManager *mgr, SeafRepo *repo);\n\nstatic void\nactive_paths_info_free (ActivePathsInfo *info);\n\nstatic HttpServerState *\nhttp_server_state_new ()\n{\n    HttpServerState *state = g_new0 (HttpServerState, 1);\n    state->head_commit_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);\n    pthread_mutex_init (&state->head_commit_map_lock, NULL);\n    return state;\n}\n\nstatic void\nhttp_server_state_free (HttpServerState *state)\n{\n    if (!state)\n        return;\n    g_hash_table_destroy (state->head_commit_map);\n    pthread_mutex_destroy (&state->head_commit_map_lock);\n    g_free (state);\n}\n\nSeafSyncManager*\nseaf_sync_manager_new (SeafileSession *seaf)\n{\n    SeafSyncManager *mgr = g_new0 (SeafSyncManager, 1);\n    mgr->priv = g_new0 (SeafSyncManagerPriv, 1);    \n    mgr->priv->auto_sync_enabled = TRUE;\n    mgr->seaf = seaf;\n\n    mgr->sync_interval = DEFAULT_SYNC_INTERVAL;\n    mgr->sync_infos = g_hash_table_new (g_str_hash, g_str_equal);\n\n    mgr->http_server_states = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                                     g_free,\n                                                     (GDestroyNotify)http_server_state_free);\n\n    gboolean exists;\n    int download_limit = seafile_session_config_get_int (seaf,\n                                                         KEY_DOWNLOAD_LIMIT,\n                                                         &exists);\n    if (exists)\n        mgr->download_limit = download_limit;\n\n    int upload_limit = seafile_session_config_get_int (seaf,\n                                                       KEY_UPLOAD_LIMIT,\n                                                       &exists);\n    if (exists)\n        mgr->upload_limit = upload_limit;\n\n    mgr->priv->active_paths = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                                     g_free,\n                                                     (GDestroyNotify)active_paths_info_free);\n    pthread_mutex_init (&mgr->priv->paths_lock, NULL);\n\n#ifdef WIN32\n    mgr->priv->refresh_paths = g_async_queue_new ();\n#endif\n\n    mgr->priv->del_confirmation_tasks = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                                           g_free,\n                                                           g_free);\n    pthread_mutex_init (&mgr->priv->del_confirmation_lock, NULL);\n\n    return mgr;\n}\n\nstatic SyncInfo*\nget_sync_info (SeafSyncManager *manager, const char *repo_id)\n{\n    SyncInfo *info = g_hash_table_lookup (manager->sync_infos, repo_id);\n    if (info) return info;\n\n    info = g_new0 (SyncInfo, 1);\n    memcpy (info->repo_id, repo_id, 36);\n    g_hash_table_insert (manager->sync_infos, info->repo_id, info);\n    return info;\n}\n\nSyncInfo *\nseaf_sync_manager_get_sync_info (SeafSyncManager *mgr,\n                                 const char *repo_id)\n{\n    return g_hash_table_lookup (mgr->sync_infos, repo_id);\n}\n\nint\nseaf_sync_manager_init (SeafSyncManager *mgr)\n{\n    return 0;\n}\n\nstatic void \nformat_http_task_detail (HttpTxTask *task, GString *buf)\n{\n    if (task->state != HTTP_TASK_STATE_NORMAL ||\n        task->runtime_state == HTTP_TASK_RT_STATE_INIT ||\n        task->runtime_state == HTTP_TASK_RT_STATE_FINISHED)\n        return;\n\n    SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr,\n                                                 task->repo_id);\n    char *repo_name;\n    char *type;\n    \n    if (repo) {\n        repo_name = repo->name;\n        type = (task->type == HTTP_TASK_TYPE_UPLOAD) ? \"upload\" : \"download\";\n        \n    } else if (task->is_clone) {\n        CloneTask *ctask;\n        ctask = seaf_clone_manager_get_task (seaf->clone_mgr, task->repo_id);\n        repo_name = ctask->repo_name;\n        type = \"download\";\n        \n    } else {\n        return;\n    }\n    int rate = http_tx_task_get_rate(task);\n\n    g_string_append_printf (buf, \"%s\\t%d %s\\n\", type, rate, repo_name);\n}\n\n/*\n * Publish a notification message to report :\n *\n *      [uploading/downloading]\\t[transfer-rate] [repo-name]\\n\n */\nstatic int\nupdate_tx_state (void *vmanager)\n{\n    SeafSyncManager *mgr = vmanager;\n    GString *buf = g_string_new (NULL);\n    GList *tasks, *ptr;\n    HttpTxTask *http_task;\n\n    mgr->last_sent_bytes = g_atomic_int_get (&mgr->sent_bytes);\n    g_atomic_int_set (&mgr->sent_bytes, 0);\n    mgr->last_recv_bytes = g_atomic_int_get (&mgr->recv_bytes);\n    g_atomic_int_set (&mgr->recv_bytes, 0);\n\n    tasks = http_tx_manager_get_upload_tasks (seaf->http_tx_mgr);\n    for (ptr = tasks; ptr; ptr = ptr->next) {\n        http_task = ptr->data;\n        format_http_task_detail (http_task, buf);\n    }\n    g_list_free (tasks);\n\n    tasks = http_tx_manager_get_download_tasks (seaf->http_tx_mgr);\n    for (ptr = tasks; ptr; ptr = ptr->next) {\n        http_task = ptr->data;\n        format_http_task_detail (http_task, buf);\n    }\n    g_list_free (tasks);\n\n    if (buf->len != 0)\n        seaf_mq_manager_publish_notification (seaf->mq_mgr, \"transfer\",\n                                              buf->str);\n\n    g_string_free (buf, TRUE);\n\n    return TRUE;\n}\n\n#ifdef WIN32\nstatic void *\nrefresh_windows_explorer_thread (void *vdata);\n\n#define STARTUP_REFRESH_WINDOWS_DELAY 10000\n\nstatic int\nrefresh_all_windows_on_startup (void *vdata)\n{\n    /* This is a hack to tell Windows Explorer to refresh all open windows.\n     * On startup, if there is one big library, its events may dominate the\n     * explorer refresh queue. Other libraries don't get refreshed until\n     * the big library's events are consumed. So we refresh the open windows\n     * to reduce the delay.\n     */\n    SHChangeNotify (SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);\n\n    /* One time */\n    return 0;\n}\n#endif\n\nstatic void *update_cached_head_commit_ids (void *arg);\n\nint\nseaf_sync_manager_start (SeafSyncManager *mgr)\n{\n    mgr->priv->check_sync_timer = seaf_timer_new (\n        auto_sync_pulse, mgr, CHECK_SYNC_INTERVAL);\n\n    mgr->priv->update_tx_state_timer = seaf_timer_new (\n        update_tx_state, mgr, UPDATE_TX_STATE_INTERVAL);\n\n    g_signal_connect (seaf, \"repo-http-fetched\",\n                      (GCallback)on_repo_http_fetched, mgr);\n    g_signal_connect (seaf, \"repo-http-uploaded\",\n                      (GCallback)on_repo_http_uploaded, mgr);\n\n#ifdef WIN32\n    seaf_job_manager_schedule_job (seaf->job_mgr,\n                                   refresh_windows_explorer_thread,\n                                   NULL,\n                                   mgr->priv->refresh_paths);\n\n    mgr->priv->refresh_windows_timer = seaf_timer_new (\n        refresh_all_windows_on_startup, mgr, STARTUP_REFRESH_WINDOWS_DELAY);\n#endif\n\n    pthread_t tid;\n    pthread_attr_t attr;\n    pthread_attr_init(&attr);\n    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);\n    if (pthread_create (&tid, &attr, update_cached_head_commit_ids, mgr) < 0) {\n        seaf_warning (\"Failed to create update cached head commit id thread.\\n\");\n        return -1;\n    }\n\n    return 0;\n}\n\nint\nseaf_sync_manager_add_sync_task (SeafSyncManager *mgr,\n                                 const char *repo_id,\n                                 GError **error)\n{\n    if (!seaf->started) {\n        seaf_message (\"sync manager is not started, skip sync request.\\n\");\n        return -1;\n    }\n\n    SeafRepo *repo;\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo) {\n        seaf_warning (\"[sync mgr] cannot find repo %s.\\n\", repo_id);\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_REPO, \"Invalid repo\");\n        return -1;\n    }\n\n    if (seaf_repo_check_worktree (repo) < 0) {\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_NO_WORKTREE,\n                     \"Worktree doesn't exist\");\n        return -1;\n    }\n\n#ifdef USE_GPL_CRYPTO\n    if (repo->version == 0 || (repo->encrypted && repo->enc_version < 2)) {\n        seaf_warning (\"Don't support syncing old version libraries.\\n\");\n        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,\n                     \"Don't support syncing old version libraries\");\n        return -1;\n    }\n#endif\n\n    SyncInfo *info = get_sync_info (mgr, repo->id);\n\n    if (info->in_sync)\n        return 0;\n\n    if (repo->version > 0) {\n        if (check_http_protocol (mgr, repo)) {\n            sync_repo_v2 (mgr, repo, TRUE);\n            return 0;\n        }\n    } else {\n        seaf_warning (\"Repo %s(%s) is version 0 library. Syncing is no longer supported.\\n\",\n                      repo->name, repo->id);\n    }\n\n    return 0;\n}\n\nvoid\nseaf_sync_manager_cancel_sync_task (SeafSyncManager *mgr,\n                                    const char *repo_id)\n{\n    SyncInfo *info;\n    SyncTask *task;\n\n    if (!seaf->started) {\n        seaf_message (\"sync manager is not started, skip cancel request.\\n\");\n        return;\n    }\n\n    /* Cancel running task. */\n    info = g_hash_table_lookup (mgr->sync_infos, repo_id);\n\n    if (!info)\n        return;\n    else if (!info->in_sync) {\n        if (info->current_task && info->current_task->state == SYNC_STATE_ERROR) {\n            info->err_cnt = 0;\n            info->in_error = FALSE;\n            info->sync_perm_err_cnt = 0;\n        }\n        return;\n    }\n\n    g_return_if_fail (info->current_task != NULL);\n    task = info->current_task;\n\n    switch (task->state) {\n    case SYNC_STATE_FETCH:\n        http_tx_manager_cancel_task (seaf->http_tx_mgr,\n                                     repo_id,\n                                     HTTP_TASK_TYPE_DOWNLOAD);\n        transition_sync_state (task, SYNC_STATE_CANCEL_PENDING);\n        break;\n    case SYNC_STATE_UPLOAD:\n        http_tx_manager_cancel_task (seaf->http_tx_mgr,\n                                     repo_id,\n                                     HTTP_TASK_TYPE_UPLOAD);\n        transition_sync_state (task, SYNC_STATE_CANCEL_PENDING);\n        break;\n    case SYNC_STATE_COMMIT:\n    case SYNC_STATE_INIT:\n    case SYNC_STATE_MERGE:\n        transition_sync_state (task, SYNC_STATE_CANCEL_PENDING);\n        break;\n    case SYNC_STATE_CANCEL_PENDING:\n        break;\n    default:\n        g_return_if_reached ();\n    }\n}\n\n/* Check the notify setting by user.  */\nstatic gboolean\nneed_notify_sync (SeafRepo *repo)\n{\n    char *notify_setting = seafile_session_config_get_string(seaf, \"notify_sync\");\n    if (notify_setting == NULL) {\n        seafile_session_config_set_string(seaf, \"notify_sync\", \"on\");\n        return TRUE;\n    }\n\n    gboolean result = (g_strcmp0(notify_setting, \"on\") == 0);\n    g_free (notify_setting);\n    return result;\n}\n\nstatic const char *sync_state_str[] = {\n    \"synchronized\",\n    \"committing\",\n    \"initializing\",\n    \"downloading\",\n    \"merging\",\n    \"uploading\",\n    \"error\",\n    \"canceled\",\n    \"cancel pending\"\n};\n\nstatic gboolean\nfind_meaningful_commit (SeafCommit *commit, void *data, gboolean *stop)\n{\n    SeafCommit **p_head = data;\n\n    if (commit->second_parent_id && commit->new_merge && !commit->conflict)\n        return TRUE;\n\n    *stop = TRUE;\n    seaf_commit_ref (commit);\n    *p_head = commit;\n    return TRUE;\n}\n\nstatic void\nnotify_sync (SeafRepo *repo, gboolean is_multipart_upload)\n{\n    SeafCommit *head = NULL;\n\n    if (!seaf_commit_manager_traverse_commit_tree_truncated (seaf->commit_mgr,\n                                                   repo->id, repo->version,\n                                                   repo->head->commit_id,\n                                                   find_meaningful_commit,\n                                                   &head, FALSE)) {\n        seaf_warning (\"Failed to traverse commit tree of %.8s.\\n\", repo->id);\n        return;\n    }\n    if (!head)\n        return;\n\n    GString *buf = g_string_new (NULL);\n    g_string_append_printf (buf, \"%s\\t%s\\t%s\\t%s\\t%s\",\n                            repo->name,\n                            repo->id,\n                            head->commit_id,\n                            head->parent_id,\n                            head->desc);\n    if (!is_multipart_upload)\n        seaf_mq_manager_publish_notification (seaf->mq_mgr,\n                                              \"sync.done\",\n                                              buf->str);\n    else\n        seaf_mq_manager_publish_notification (seaf->mq_mgr,\n                                              \"sync.multipart_upload\",\n                                              buf->str);\n    g_string_free (buf, TRUE);\n    seaf_commit_unref (head);\n}\n\n#define IN_ERROR_THRESHOLD 3\n\nstatic gboolean\nis_perm_error (int error)\n{\n    return (error == SYNC_ERROR_ID_ACCESS_DENIED ||\n            error == SYNC_ERROR_ID_NO_WRITE_PERMISSION ||\n            error == SYNC_ERROR_ID_PERM_NOT_SYNCABLE ||\n            error == SYNC_ERROR_ID_FOLDER_PERM_DENIED ||\n            error == SYNC_ERROR_ID_TOO_MANY_FILES ||\n            error == SYNC_ERROR_ID_QUOTA_FULL);\n}\n\nstatic void\nupdate_sync_info_error_state (SyncTask *task, int new_state)\n{\n    SyncInfo *info = task->info;\n\n    if (new_state == SYNC_STATE_ERROR) {\n        info->err_cnt++;\n        if (info->err_cnt == IN_ERROR_THRESHOLD)\n            info->in_error = TRUE;\n        if (is_perm_error(task->error))\n            info->sync_perm_err_cnt++;\n    } else if (info->err_cnt > 0) {\n        info->err_cnt = 0;\n        info->in_error = FALSE;\n        info->sync_perm_err_cnt = 0;\n    }\n}\n\nstatic void commit_repo (SyncTask *task);\n\nstatic void\ntransition_sync_state (SyncTask *task, int new_state)\n{\n    g_return_if_fail (new_state >= 0 && new_state < SYNC_STATE_NUM);\n\n    SyncInfo *info = task->info;\n\n    if (task->state != new_state) {\n        if (((task->state == SYNC_STATE_INIT && task->uploaded) ||\n             task->state == SYNC_STATE_FETCH) &&\n            new_state == SYNC_STATE_DONE &&\n            need_notify_sync(task->repo))\n            notify_sync (task->repo, (info->multipart_upload && !info->end_multipart_upload));\n\n        /* If we're in the process of uploading large set of files, they'll be splitted\n         * into multiple batches for upload. We want to immediately start the next batch\n         * after previous one is done.\n         */\n        if (new_state == SYNC_STATE_DONE &&\n            info->multipart_upload &&\n            !info->end_multipart_upload) {\n            commit_repo (task);\n            return;\n        }\n\n        /* If file error levels occured during sync, the whole sync process can still finish\n         * with DONE state. But we need to notify the user about this error in the interface.\n         * Such file level errors are set with seaf_sync_manager_set_task_error_code().\n         */\n        if (new_state != SYNC_STATE_ERROR && task->error != SYNC_ERROR_ID_NO_ERROR) {\n            seaf_message (\"Repo '%s' sync is finished but with error: %s\\n\",\n                          task->repo->name,\n                          sync_error_id_to_str(task->error));\n        }\n\n        if (!(task->state == SYNC_STATE_DONE && new_state == SYNC_STATE_INIT) &&\n            !(task->state == SYNC_STATE_INIT && new_state == SYNC_STATE_DONE)) {\n            seaf_message (\"Repo '%s' sync state transition from '%s' to '%s'.\\n\",\n                          task->repo->name,\n                          sync_state_str[task->state],\n                          sync_state_str[new_state]);\n        }\n\n        task->state = new_state;\n        if (new_state == SYNC_STATE_DONE || \n            new_state == SYNC_STATE_CANCELED ||\n            new_state == SYNC_STATE_ERROR) {\n            info->in_sync = FALSE;\n            --(task->mgr->n_running_tasks);\n            update_sync_info_error_state (task, new_state);\n\n            /* Keep previous upload progress if sync task is canceled or failed. */\n            if (new_state == SYNC_STATE_DONE) {\n                info->multipart_upload = FALSE;\n                info->end_multipart_upload = FALSE;\n                info->total_bytes = 0;\n                info->uploaded_bytes = 0;\n            }\n        }\n\n#ifdef WIN32\n        seaf_sync_manager_add_refresh_path (seaf->sync_mgr, task->repo->worktree);\n#endif\n    }\n}\n\nstatic void\nset_task_error (SyncTask *task, int error)\n{\n    g_return_if_fail (error >= 0 && error < N_SYNC_ERROR_ID);\n\n    const char *err_str = sync_error_id_to_str(error);\n    int err_level = sync_error_level(error);\n\n    if (task->state != SYNC_STATE_ERROR) {\n        seaf_message (\"Repo '%s' sync state transition from %s to '%s': '%s'.\\n\",\n                      task->repo->name,\n                      sync_state_str[task->state],\n                      sync_state_str[SYNC_STATE_ERROR],\n                      err_str);\n        task->state = SYNC_STATE_ERROR;\n        task->error = error;\n        task->info->in_sync = FALSE;\n        --(task->mgr->n_running_tasks);\n        update_sync_info_error_state (task, SYNC_STATE_ERROR);\n\n        /* For repo-level errors, only need to record in database, but not send notifications.\n         * File-level errors are recorded and notified in the location they happens, not here.\n         */\n        if (err_level == SYNC_ERROR_LEVEL_REPO)\n            send_file_sync_error_notification (task->repo->id, task->repo->name, NULL, error);\n\n#ifdef WIN32\n        seaf_sync_manager_add_refresh_path (seaf->sync_mgr, task->repo->worktree);\n#endif\n\n    }\n}\n\nvoid\nseaf_sync_manager_set_task_error_code (SeafSyncManager *mgr,\n                                       const char *repo_id,\n                                       int error)\n{\n    SyncInfo *info = g_hash_table_lookup (mgr->sync_infos, repo_id);\n    if (!info)\n        return;\n\n    info->current_task->error = error;\n}\n\nstatic void\nsync_task_free (SyncTask *task)\n{\n    g_free (task->tx_id);\n    g_free (task->dest_id);\n    g_free (task->token);\n    g_free (task);\n}\n\nstatic void\nstart_upload_if_necessary (SyncTask *task)\n{\n    GError *error = NULL;\n    SeafRepo *repo = task->repo;\n\n    if (http_tx_manager_add_upload (seaf->http_tx_mgr,\n                                    repo->id,\n                                    repo->version,\n                                    repo->effective_host,\n                                    repo->token,\n                                    task->http_version,\n                                    repo->use_fileserver_port,\n                                    &error) < 0) {\n        seaf_warning (\"Failed to start http upload: %s\\n\", error->message);\n        set_task_error (task, SYNC_ERROR_ID_NOT_ENOUGH_MEMORY);\n        return;\n    }\n    task->tx_id = g_strdup(repo->id);\n\n    transition_sync_state (task, SYNC_STATE_UPLOAD);\n}\n\nstatic void\nstart_fetch_if_necessary (SyncTask *task, const char *remote_head)\n{\n    GError *error = NULL;\n    SeafRepo *repo = task->repo;\n\n    if (http_tx_manager_add_download (seaf->http_tx_mgr,\n                                      repo->id,\n                                      repo->version,\n                                      repo->effective_host,\n                                      repo->token,\n                                      remote_head,\n                                      FALSE,\n                                      NULL,\n                                      NULL,\n                                      task->http_version,\n                                      repo->email,\n                                      repo->username,\n                                      repo->use_fileserver_port,\n                                      repo->name,\n                                      &error) < 0) {\n        seaf_warning (\"Failed to start http download: %s.\\n\", error->message);\n        set_task_error (task, SYNC_ERROR_ID_NOT_ENOUGH_MEMORY);\n        return;\n    }\n    task->tx_id = g_strdup(repo->id);\n\n    transition_sync_state (task, SYNC_STATE_FETCH);\n}\n\nstatic gboolean\nrepo_block_store_exists (SeafRepo *repo)\n{\n    gboolean ret;\n    char *store_path = g_build_filename (seaf->seaf_dir, \"storage\", \"blocks\",\n                                         repo->id, NULL);\n    if (g_file_test (store_path, G_FILE_TEST_IS_DIR))\n        ret = TRUE;\n    else\n        ret = FALSE;\n    g_free (store_path);\n    return ret;\n}\n\n#if defined WIN32 || defined __APPLE__\n\nstatic GHashTable *\nload_locked_files_blocks (const char *repo_id)\n{\n    LockedFileSet *fset;\n    GHashTable *block_id_hash;\n    GHashTableIter iter;\n    gpointer key, value;\n    LockedFile *locked;\n    Seafile *file;\n    int i;\n    char *blk_id;\n\n    fset = seaf_repo_manager_get_locked_file_set (seaf->repo_mgr, repo_id);\n\n    block_id_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);\n\n    g_hash_table_iter_init (&iter, fset->locked_files);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        locked = value;\n\n        if (strcmp (locked->operation, LOCKED_OP_UPDATE) == 0) {\n            file = seaf_fs_manager_get_seafile (seaf->fs_mgr,\n                                                fset->repo_id, 1,\n                                                locked->file_id);\n            if (!file) {\n                seaf_warning (\"Failed to find file %s in repo %.8s.\\n\",\n                              locked->file_id, fset->repo_id);\n                continue;\n            }\n\n            for (i = 0; i < file->n_blocks; ++i) {\n                blk_id = g_strdup (file->blk_sha1s[i]);\n                g_hash_table_replace (block_id_hash, blk_id, blk_id);\n            }\n\n            seafile_unref (file);\n        }\n    }\n\n    locked_file_set_free (fset);\n\n    return block_id_hash;\n}\n\nstatic gboolean\nremove_block_cb (const char *store_id,\n                 int version,\n                 const char *block_id,\n                 void *user_data)\n{\n    GHashTable *block_hash = user_data;\n\n    if (!g_hash_table_lookup (block_hash, block_id))\n        seaf_block_manager_remove_block (seaf->block_mgr, store_id, version, block_id);\n\n    return TRUE;\n}\n\n#endif\n\nstatic void *\nremove_repo_blocks (void *vtask)\n{\n    SyncTask *task = vtask;\n\n#if defined WIN32 || defined __APPLE__\n    GHashTable *block_hash;\n\n    block_hash = load_locked_files_blocks (task->repo->id);\n    if (g_hash_table_size (block_hash) == 0) {\n        g_hash_table_destroy (block_hash);\n        seaf_block_manager_remove_store (seaf->block_mgr, task->repo->id);\n        return vtask;\n    }\n\n    seaf_block_manager_foreach_block (seaf->block_mgr,\n                                      task->repo->id,\n                                      task->repo->version,\n                                      remove_block_cb,\n                                      block_hash);\n\n    g_hash_table_destroy (block_hash);\n#else\n    seaf_block_manager_remove_store (seaf->block_mgr, task->repo->id);\n#endif\n\n    return vtask;\n}\n\nstatic void\nremove_blocks_done (void *vtask)\n{\n    SyncTask *task = vtask;\n\n    transition_sync_state (task, SYNC_STATE_DONE);\n}\n\nstatic void\non_repo_deleted_on_server (SyncTask *task, SeafRepo *repo)\n{\n    set_task_error (task, SYNC_ERROR_ID_SERVER_REPO_DELETED);\n\n    seaf_warning (\"repo %s(%.8s) not found on server\\n\",\n                  repo->name, repo->id);\n\n    if (!seafile_session_config_get_allow_repo_not_found_on_server(seaf)) {\n        seaf_message (\"remove repo %s(%.8s) since it's deleted on relay\\n\",\n                      repo->name, repo->id);\n        /* seaf_mq_manager_publish_notification (seaf->mq_mgr, */\n        /*                                       \"repo.deleted_on_relay\", */\n        /*                                       repo->name); */\n        seaf_repo_manager_del_repo (seaf->repo_mgr, repo);\n    }\n}\n\nstatic void\nupdate_sync_status_v2 (SyncTask *task)\n{\n    SyncInfo *info = task->info;\n    SeafRepo *repo = task->repo;\n    SeafBranch *master = NULL, *local = NULL;\n\n    local = seaf_branch_manager_get_branch (\n        seaf->branch_mgr, info->repo_id, \"local\");\n    if (!local) {\n        seaf_warning (\"[sync-mgr] Branch local not found for repo %s(%.8s).\\n\",\n                   repo->name, repo->id);\n        set_task_error (task, SYNC_ERROR_ID_LOCAL_DATA_CORRUPT);\n        return;\n    }\n\n    master = seaf_branch_manager_get_branch (\n        seaf->branch_mgr, info->repo_id, \"master\");\n    if (!master) {\n        seaf_warning (\"[sync-mgr] Branch master not found for repo %s(%.8s).\\n\",\n                   repo->name, repo->id);\n        set_task_error (task, SYNC_ERROR_ID_LOCAL_DATA_CORRUPT);\n        return;\n    }\n\n    if (info->repo_corrupted) {\n        set_task_error (task, SYNC_ERROR_ID_SERVER_REPO_CORRUPT);\n    } else if (info->deleted_on_relay) {\n        on_repo_deleted_on_server (task, repo);\n    } else {\n        /* If local head is the same as remote head, already in sync. */\n        if (strcmp (local->commit_id, info->head_commit) == 0) {\n            /* As long as the repo is synced with the server. All the local\n             * blocks are not useful any more.\n             */\n            if (repo_block_store_exists (repo)) {\n                seaf_message (\"Removing blocks for repo %s(%.8s).\\n\",\n                              repo->name, repo->id);\n                seaf_job_manager_schedule_job (seaf->job_mgr,\n                                               remove_repo_blocks,\n                                               remove_blocks_done,\n                                               task);\n            } else\n                transition_sync_state (task, SYNC_STATE_DONE);\n        } else\n            start_fetch_if_necessary (task, task->info->head_commit);\n    }\n\n    seaf_branch_unref (local);\n    seaf_branch_unref (master);\n}\n\nstatic void\ncheck_head_commit_done (HttpHeadCommit *result, void *user_data)\n{\n    SyncTask *task = user_data;\n    SyncInfo *info = task->info;\n\n    if (!result->check_success) {\n        set_task_error (task, result->error_code);\n        return;\n    }\n\n    info->deleted_on_relay = result->is_deleted;\n    info->repo_corrupted = result->is_corrupt;\n    memcpy (info->head_commit, result->head_commit, 40);\n\n    update_sync_status_v2 (task);\n}\n\nstatic int\ncheck_head_commit_http (SyncTask *task)\n{\n    SeafRepo *repo = task->repo;\n\n    int ret = http_tx_manager_check_head_commit (seaf->http_tx_mgr,\n                                                 repo->id, repo->version,\n                                                 repo->effective_host,\n                                                 repo->token,\n                                                 repo->use_fileserver_port,\n                                                 check_head_commit_done,\n                                                 task);\n    if (ret == 0)\n        transition_sync_state (task, SYNC_STATE_INIT);\n    else if (ret < 0)\n        set_task_error (task, SYNC_ERROR_ID_NOT_ENOUGH_MEMORY);\n\n    return ret;\n}\n\nstruct CommitResult {\n    SyncTask *task;\n    gboolean changed;\n    gboolean success;\n};\n\nstatic void *\ncommit_job (void *vtask)\n{\n    SyncTask *task = vtask;\n    SeafRepo *repo = task->repo;\n    struct CommitResult *res = g_new0 (struct CommitResult, 1);\n    GError *error = NULL;\n\n    res->task = task;\n\n    if (repo->delete_pending)\n        return res;\n\n    res->changed = TRUE;\n    res->success = TRUE;\n\n    char *commit_id = seaf_repo_index_commit (repo,\n                                              task->is_manual_sync,\n                                              task->is_initial_commit,\n                                              &error);\n    if (commit_id == NULL && error != NULL) {\n        seaf_warning (\"[Sync mgr] Failed to commit to repo %s(%.8s).\\n\",\n                      repo->name, repo->id);\n        res->success = FALSE;\n    } else if (commit_id == NULL) {\n        res->changed = FALSE;\n    }\n    g_free (commit_id);\n\n    return res;\n}\n\nstatic char *\nexceed_max_deleted_files (SeafRepo *repo);\nstatic void\nnotify_delete_confirmation (const char *repo_name, const char *desc, const char *confirmation_id);\n\nstatic void\ncommit_job_done (void *vres)\n{\n    struct CommitResult *res = vres;\n    SeafRepo *repo = res->task->repo;\n    SyncTask *task = res->task;\n\n    res->task->mgr->commit_job_running = FALSE;\n\n    if (repo->delete_pending) {\n        transition_sync_state (res->task, SYNC_STATE_CANCELED);\n        seaf_repo_manager_del_repo (seaf->repo_mgr, repo);\n        g_free (res);\n        return;\n    }\n\n    if (res->task->state == SYNC_STATE_CANCEL_PENDING) {\n        transition_sync_state (res->task, SYNC_STATE_CANCELED);\n        g_free (res);\n        return;\n    }\n\n    if (!res->success) {\n        set_task_error (res->task, SYNC_ERROR_ID_INDEX_ERROR);\n        g_free (res);\n        return;\n    }\n\n    if (res->changed) {\n        char *desc = NULL;\n        desc = exceed_max_deleted_files (repo);\n        if (desc) {\n            notify_delete_confirmation (repo->name, desc, repo->head->commit_id);\n            seaf_warning (\"Delete more than %d files, add delete confirmation.\\n\", seaf->delete_confirm_threshold);\n            task->info->del_confirmation_pending = TRUE;\n            set_task_error (res->task, SYNC_ERROR_ID_DEL_CONFIRMATION_PENDING);\n            g_free (desc);\n            g_free (res);\n            return;\n        }\n        start_upload_if_necessary (res->task);\n    }\n    else if (task->is_manual_sync || task->is_initial_commit)\n        check_head_commit_http (task);\n    else\n        transition_sync_state (task, SYNC_STATE_DONE);\n\n    g_free (res);\n}\n\nstatic int check_commit_state (void *data);\n\nstatic void\ncommit_repo (SyncTask *task)\n{\n    /* In order not to eat too much CPU power, only one commit job can be run\n     * at the same time. Other sync tasks have to check every 1 second.\n     */\n    if (task->mgr->commit_job_running) {\n        task->commit_timer = seaf_timer_new (check_commit_state, task, 1000);\n        return;\n    }\n\n    task->mgr->commit_job_running = TRUE;\n\n    transition_sync_state (task, SYNC_STATE_COMMIT);\n\n    if (seaf_job_manager_schedule_job (seaf->job_mgr, \n                                       commit_job, \n                                       commit_job_done,\n                                       task) < 0)\n        set_task_error (task, SYNC_ERROR_ID_NOT_ENOUGH_MEMORY);\n}\n\nstatic int\ncheck_commit_state (void *data)\n{\n    SyncTask *task = data;\n\n    if (!task->mgr->commit_job_running) {\n        seaf_timer_free (&task->commit_timer);\n        commit_repo (task);\n        return 0;\n    }\n\n    return 1;\n}\n\nstatic SyncTask *\ncreate_sync_task_v2 (SeafSyncManager *manager, SeafRepo *repo,\n                     gboolean is_manual_sync, gboolean is_initial_commit)\n{\n    SyncTask *task = g_new0 (SyncTask, 1);\n    SyncInfo *info;\n\n    info = get_sync_info (manager, repo->id);\n\n    task->info = info;\n    task->mgr = manager;\n\n    task->dest_id = g_strdup (repo->relay_id);\n    task->token = g_strdup(repo->token);\n    task->is_manual_sync = is_manual_sync;\n    task->is_initial_commit = is_initial_commit;\n    task->error = SYNC_ERROR_ID_NO_ERROR;\n\n    repo->last_sync_time = time(NULL);\n    ++(manager->n_running_tasks);\n\n    /* Free the last task when a new task is started.\n     * This way we can always get the state of the last task even\n     * after it's done.\n     */\n    if (task->info->current_task)\n        sync_task_free (task->info->current_task);\n    task->info->current_task = task;\n    task->info->in_sync = TRUE;\n    task->repo = repo;\n\n    if (repo->server_url) {\n        HttpServerState *state = g_hash_table_lookup (manager->http_server_states,\n                                                      repo->server_url);\n        if (state) {\n            task->http_version = state->http_version;\n        }\n    }\n\n    return task;\n}\n\nstatic gboolean\ncreate_commit_from_event_queue (SeafSyncManager *manager, SeafRepo *repo,\n                                gboolean is_manual_sync)\n{\n    WTStatus *status;\n    SyncTask *task;\n    gboolean ret = FALSE;\n    gint now = (gint)time(NULL);\n    gint last_changed;\n\n    status = seaf_wt_monitor_get_worktree_status (manager->seaf->wt_monitor,\n                                                  repo->id);\n    if (status) {\n        last_changed = g_atomic_int_get (&status->last_changed);\n        if (status->last_check == 0) {\n            /* Force commit and sync after a new repo is added. */\n            task = create_sync_task_v2 (manager, repo, is_manual_sync, TRUE);\n            repo->create_partial_commit = TRUE;\n            commit_repo (task);\n            status->last_check = now;\n            ret = TRUE;\n        } else if (status->partial_commit) {\n            task = create_sync_task_v2 (manager, repo, is_manual_sync, FALSE);\n            repo->create_partial_commit = TRUE;\n            commit_repo (task);\n            ret = TRUE;\n        } else if (last_changed != 0 && status->last_check <= last_changed) {\n            /* Commit and sync if the repo has been updated after the\n             * last check and is not updated for the last 2 seconds.\n             */\n            if (now - last_changed >= 2) {\n                task = create_sync_task_v2 (manager, repo, is_manual_sync, FALSE);\n                repo->create_partial_commit = TRUE;\n                commit_repo (task);\n                status->last_check = now;\n                ret = TRUE;\n            }\n        }\n        wt_status_unref (status);\n    }\n\n    return ret;\n}\n\nstatic gboolean\ncan_schedule_repo (SeafSyncManager *manager, SeafRepo *repo)\n{\n    int now = (int)time(NULL);\n\n    return ((repo->last_sync_time == 0 ||\n             repo->last_sync_time < now - manager->sync_interval) &&\n            manager->n_running_tasks < MAX_RUNNING_SYNC_TASKS);\n}\n\nstatic gboolean\nneed_check_on_server (SeafSyncManager *manager, SeafRepo *repo, const char *master_head_id)\n{\n#define HEAD_COMMIT_MAP_TTL 90\n    HttpServerState *state;\n    gboolean ret = FALSE;\n    SyncInfo *info;\n\n    /* If sync state is in error, always retry. */\n    info = get_sync_info (manager, repo->id);\n    if (info && info->current_task && info->current_task->state == SYNC_STATE_ERROR)\n        return TRUE;\n\n    state = g_hash_table_lookup (manager->http_server_states, repo->server_url);\n    if (!state)\n        return TRUE;\n\n    pthread_mutex_lock (&state->head_commit_map_lock);\n\n    if (!state->head_commit_map_init) {\n        ret = TRUE;\n        goto out;\n    }\n\n    gint64 now = (gint64)time(NULL);\n    if (now - state->last_update_head_commit_map_time >= HEAD_COMMIT_MAP_TTL) {\n        ret = TRUE;\n        goto out;\n    }\n\n    char *server_head = g_hash_table_lookup (state->head_commit_map, repo->id);\n    if (!server_head) {\n        /* Repo was removed on server. Just return \"changed on server\". */\n        ret = TRUE;\n        goto out;\n    }\n    if (g_strcmp0 (server_head, master_head_id) != 0)\n        ret = TRUE;\n\nout:\n    pthread_mutex_unlock (&state->head_commit_map_lock);\n    return ret;\n}\n\n#define MAX_DELETED_FILES_NUM 100\n\ninline static char *\nget_basename (char *path)\n{\n    char *slash;\n    slash = strrchr (path, '/');\n    if (!slash)\n        return path;\n    return (slash + 1);\n}\n\ntypedef struct _GetDeletedFilesAux {\n    char *basename;\n    int number;\n} GetDeletedFilesAux;\n\nstatic gboolean\nget_deleted_files_cb (SeafFSManager *mgr,\n                      const char *path,\n                      SeafDirent *dent,\n                      void *user_data,\n                      gboolean *stop)\n{\n    GetDeletedFilesAux *aux = user_data;\n\n    if (S_ISREG(dent->mode)) {\n        if (!aux->basename) {\n            char *basename = get_basename(dent->name);\n            aux->basename = g_strdup(basename);\n        }\n        aux->number++;\n    }\n    return TRUE;\n}\n\nstatic void\nget_number_of_deleted_files (SeafRepo *repo, const char *remote_root,\n                             DiffEntry *de, int *n_deleted, char **deleted_file)\n{\n    SeafDir *dir = NULL;\n    char dir_id[41];\n\n    rawdata_to_hex (de->sha1, dir_id, 20);\n    GetDeletedFilesAux *aux = g_new0 (GetDeletedFilesAux, 1);\n\n    if (seaf_fs_manager_traverse_path (seaf->fs_mgr,\n                                       repo->id, repo->version,\n                                       remote_root,\n                                       de->name,\n                                       get_deleted_files_cb,\n                                       aux) < 0) {\n        goto out;\n    }\n\n    if (*n_deleted == 0) {\n        *deleted_file = g_strdup (aux->basename);\n    }\n    *n_deleted += aux->number;\n\nout:\n    g_free (aux->basename);\n    g_free (aux);\n}\n\nstatic char *\nexceed_max_deleted_files (SeafRepo *repo)\n{\n    SeafBranch *master = NULL, *local = NULL;\n    SeafCommit *local_head = NULL, *master_head = NULL;\n    GList *diff_results = NULL;\n    char *deleted_file = NULL;\n    GString *desc = NULL;\n    char *ret = NULL;\n\n    local = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, \"local\");\n    if (!local) {\n        seaf_warning (\"No local branch found for repo %s(%.8s).\\n\",\n                      repo->name, repo->id);\n        goto out;\n    }\n\n    master = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, \"master\");\n    if (!master) {\n        seaf_warning (\"No master branch found for repo %s(%.8s).\\n\",\n                      repo->name, repo->id);\n        goto out;\n    }\n\n    local_head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version,\n                                                 local->commit_id);\n    if (!local_head) {\n        seaf_warning (\"Failed to get head of local branch for repo %s.\\n\", repo->id);\n        goto out;\n    }\n\n    master_head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version,\n                                                  master->commit_id);\n    if (!master_head) {\n        seaf_warning (\"Failed to get head of master branch for repo %s.\\n\", repo->id);\n        goto out;\n    }\n\n    diff_commit_roots (repo->id, repo->version, master_head->root_id, local_head->root_id, &diff_results, TRUE);\n    if (!diff_results) {\n        goto out;\n    }\n    GList *p;\n    DiffEntry *de;\n    int n_deleted = 0;\n\n    for (p = diff_results; p != NULL; p = p->next) {\n        de = p->data;\n        switch (de->status) {\n        case DIFF_STATUS_DELETED:\n            if (n_deleted == 0)\n                deleted_file = g_strdup(get_basename(de->name));\n            n_deleted++;\n            break;\n        case DIFF_STATUS_DIR_DELETED:\n            get_number_of_deleted_files (repo, master_head->root_id, de, &n_deleted, &deleted_file);\n            break;\n        }\n    }\n\n    if (n_deleted >= seaf->delete_confirm_threshold) {\n        desc = g_string_new (\"\");\n        g_string_append_printf (desc, \"Deleted \\\"%s\\\" and %d more files.\\n\",\n                                deleted_file, n_deleted - 1);\n        ret = g_string_free (desc, FALSE);\n    }\n\nout:\n    g_free (deleted_file);\n    seaf_branch_unref (local);\n    seaf_branch_unref (master);\n    seaf_commit_unref (local_head);\n    seaf_commit_unref (master_head);\n    g_list_free_full (diff_results, (GDestroyNotify)diff_entry_free);\n\n    return ret;\n}\n\nstatic void\nnotify_delete_confirmation (const char *repo_name, const char *desc, const char *confirmation_id)\n{\n    json_t *obj = json_object ();\n    json_object_set_string_member (obj, \"repo_name\", repo_name);\n    json_object_set_string_member (obj, \"delete_files\", desc);\n    json_object_set_string_member (obj, \"confirmation_id\", confirmation_id);\n\n    char *msg = json_dumps (obj, JSON_COMPACT);\n\n    seaf_mq_manager_publish_notification (seaf->mq_mgr, \"sync.del_confirmation\", msg);\n\n    json_decref (obj);\n    g_free (msg);\n}\n\nint\nseaf_sync_manager_add_del_confirmation (SeafSyncManager *mgr,\n                                        const char *confirmation_id,\n                                        gboolean resync)\n{\n    SeafSyncManagerPriv *priv = seaf->sync_mgr->priv;\n    DelConfirmationResult *result = NULL;\n\n    result = g_new0 (DelConfirmationResult, 1);\n    result->resync = resync;\n\n    pthread_mutex_lock (&priv->del_confirmation_lock);\n    g_hash_table_insert (priv->del_confirmation_tasks, g_strdup (confirmation_id), result);\n    pthread_mutex_unlock (&priv->del_confirmation_lock);\n\n    return 0;\n}\n\nstatic DelConfirmationResult *\nget_del_confirmation_result (const char *confirmation_id)\n{\n    SeafSyncManagerPriv *priv = seaf->sync_mgr->priv;\n    DelConfirmationResult *result, *copy = NULL;\n\n\n    pthread_mutex_lock (&priv->del_confirmation_lock);\n    result = g_hash_table_lookup (priv->del_confirmation_tasks, confirmation_id);\n    if (result) {\n        copy = g_new0 (DelConfirmationResult, 1);\n        copy->resync = result->resync;\n        g_hash_table_remove (priv->del_confirmation_tasks, confirmation_id);\n    }\n    pthread_mutex_unlock (&priv->del_confirmation_lock);\n\n    return copy;\n}\n\nstatic void\nresync_repo (SeafRepo *repo)\n{\n    GError *error = NULL;\n    char *repo_id = g_strdup (repo->id);\n    int repo_version = repo->version;\n    char *repo_name = g_strdup (repo->name);\n    char *token = g_strdup (repo->token);\n    char *magic = g_strdup (repo->magic);\n    int enc_version = repo->enc_version;\n    char *random_key = g_strdup (repo->random_key);\n    char *worktree = g_strdup (repo->worktree);\n    char *email = g_strdup (repo->email);\n    char *more_info = NULL;\n    gboolean is_encrypted = FALSE;\n    char key[65], iv[33];\n    json_t *obj = json_object ();\n\n    json_object_set_int_member (obj, \"is_readonly\", repo->is_readonly);\n    json_object_set_string_member (obj, \"repo_salt\", repo->salt);\n    json_object_set_string_member (obj, \"server_url\", repo->server_url);\n    if (repo->username)\n        json_object_set_string_member (obj, \"username\", repo->username);\n    if (repo->encrypted) {\n        is_encrypted = TRUE;\n        if (repo->enc_version == 1) {\n            rawdata_to_hex (repo->enc_key, key, 16);\n            rawdata_to_hex (repo->enc_iv, iv, 16);\n        } else if (repo->enc_version >= 2){\n            rawdata_to_hex (repo->enc_key, key, 32);\n            rawdata_to_hex (repo->enc_iv, iv, 16);\n        }\n        json_object_set_int_member (obj, \"resync_enc_repo\", TRUE);\n        if (repo->pwd_hash_algo) {\n            json_object_set_string_member (obj, \"pwd_hash_algo\", repo->pwd_hash_algo);\n            json_object_set_string_member (obj, \"pwd_hash_params\", repo->pwd_hash_params);\n            json_object_set_string_member (obj, \"pwd_hash\", repo->pwd_hash);\n        }\n    }\n\n    more_info = json_dumps (obj, 0);\n\n    if (repo->auto_sync && (repo->sync_interval == 0))\n        seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id);\n\n    seaf_repo_manager_del_repo (seaf->repo_mgr, repo);\n\n    if (is_encrypted) {\n        seaf_repo_manager_save_repo_enc_info (seaf->repo_mgr, repo_id, key, iv);\n    }\n\n    char *ret = seaf_clone_manager_add_task (seaf->clone_mgr, repo_id,\n                                             repo_version, repo_name,\n                                             token, NULL,\n                                             magic, enc_version,\n                                             random_key, worktree,\n                                             email, more_info, &error);\n    if (error) {\n        seaf_warning (\"Failed to clone repo %s: %s\\n\", repo_id, error->message);\n        g_clear_error (&error);\n    }\n    g_free (ret);\n    g_free (repo_id);\n    g_free (repo_name);\n    g_free (token);\n    g_free (magic);\n    g_free (random_key);\n    g_free (worktree);\n    g_free (email);\n    json_decref (obj);\n    g_free (more_info);\n}\n\nstatic int\nsync_repo_v2 (SeafSyncManager *manager, SeafRepo *repo, gboolean is_manual_sync)\n{\n    SeafBranch *master, *local;\n    SyncTask *task;\n    int ret = 0;\n    char *last_download = NULL;\n    SyncInfo *info = NULL;\n\n    master = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, \"master\");\n    if (!master) {\n        seaf_warning (\"No master branch found for repo %s(%.8s).\\n\",\n                      repo->name, repo->id);\n        return -1;\n    }\n    local = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, \"local\");\n    if (!local) {\n        seaf_warning (\"No local branch found for repo %s(%.8s).\\n\",\n                      repo->name, repo->id);\n        return -1;\n    }\n\n    /* If last download was interrupted in the fetch and download stage,\n     * need to resume it at exactly the same remote commit.\n     */\n    last_download = seaf_repo_manager_get_repo_property (seaf->repo_mgr,\n                                                         repo->id,\n                                                         REPO_PROP_DOWNLOAD_HEAD);\n    if (last_download && strcmp (last_download, EMPTY_SHA1) != 0) {\n        if (is_manual_sync || can_schedule_repo (manager, repo)) {\n            task = create_sync_task_v2 (manager, repo, is_manual_sync, FALSE);\n            start_fetch_if_necessary (task, last_download);\n        }\n        goto out;\n    }\n\n    info = get_sync_info (manager, repo->id);\n\n    if (strcmp (master->commit_id, local->commit_id) != 0) {\n        if (is_manual_sync || can_schedule_repo (manager, repo) || info->del_confirmation_pending) {\n            task = create_sync_task_v2 (manager, repo, is_manual_sync, FALSE);\n            if (!task->info->del_confirmation_pending) {\n                char *desc = NULL;\n                desc = exceed_max_deleted_files (repo);\n                if (desc) {\n                    notify_delete_confirmation (repo->name, desc, local->commit_id);\n                    seaf_warning (\"Delete more than %d files, add delete confirmation.\\n\", seaf->delete_confirm_threshold);\n                    task->info->del_confirmation_pending = TRUE;\n                    set_task_error (task, SYNC_ERROR_ID_DEL_CONFIRMATION_PENDING);\n                    g_free (desc);\n                    goto out;\n                }\n            } else {\n                DelConfirmationResult *result = get_del_confirmation_result (local->commit_id);\n                if (!result) {\n                    // User has not confirmed whether to continue syncing.\n                    set_task_error (task, SYNC_ERROR_ID_DEL_CONFIRMATION_PENDING);\n                    goto out;\n                } else if (result->resync) {\n                    // User chooses to resync.\n                    g_free (result);\n                    task->info->del_confirmation_pending = FALSE;\n                    set_task_error (task, SYNC_ERROR_ID_DEL_CONFIRMATION_PENDING);\n                    // Delete this repo and resync this repo by adding clone task.\n                    resync_repo (repo);\n                    ret = -1;\n                    goto out;\n                }\n                // User chooes to continue syncing.\n                g_free (result);\n                task->info->del_confirmation_pending = FALSE;\n            }\n            start_upload_if_necessary (task);\n        }\n        /* Do nothing if the client still has something to upload\n         * but it's before 30-second schedule.\n         */\n        goto out;\n    } else if (is_manual_sync) {\n        task = create_sync_task_v2 (manager, repo, is_manual_sync, FALSE);\n        commit_repo (task);\n        goto out;\n    } else if (create_commit_from_event_queue (manager, repo, is_manual_sync))\n        goto out;\n\n    if (is_manual_sync || can_schedule_repo (manager, repo)) {\n        /* If file syncing protocol version is higher than 2, we check for all head commit ids\n         * for synced repos regularly.\n         */\n        if (!is_manual_sync && !need_check_on_server (manager, repo, master->commit_id)) {\n            seaf_debug (\"Repo %s is not changed on server %s.\\n\", repo->name, repo->server_url);\n            repo->last_sync_time = time(NULL);\n            goto out;\n        }\n\n        task = create_sync_task_v2 (manager, repo, is_manual_sync, FALSE);\n        check_head_commit_http (task);\n    }\n\nout:\n    g_free (last_download);\n    seaf_branch_unref (master);\n    seaf_branch_unref (local);\n    return ret;\n}\n\nvoid\nseaf_sync_manager_update_repo (SeafSyncManager *manager, SeafRepo *repo,\n                               const char *head_commit)\n{\n    HttpServerState *state;\n    SeafBranch *master = NULL;\n\n    state = g_hash_table_lookup (manager->http_server_states, repo->server_url);\n    if (!state) {\n        return;\n    }\n\n    master = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, \"master\");\n    if (!master) {\n        return;\n    }\n\n    if (g_strcmp0(head_commit, master->commit_id) != 0) {\n        pthread_mutex_lock (&state->head_commit_map_lock);\n        g_hash_table_replace (state->head_commit_map, g_strdup (repo->id), g_strdup (head_commit));\n        pthread_mutex_unlock (&state->head_commit_map_lock);\n        // Set last_sync_time to 0 to allow the repo to be sync immediately.\n        // Otherwise it only gets synced after 30 seconds since the last sync.\n        repo->last_sync_time = 0;\n    }\n\n    seaf_branch_unref (master);\n    return;\n}\n\nvoid\nseaf_sync_manager_check_locks_and_folder_perms (SeafSyncManager *manager, const char *server_url)\n{\n    HttpServerState *state;\n\n    state = g_hash_table_lookup (manager->http_server_states, server_url);\n    if (!state) {\n        return;\n    }\n\n    if (!seaf_repo_manager_server_is_pro (seaf->repo_mgr, server_url))\n        return;\n\n    state->immediate_check_folder_perms = TRUE;\n    state->immediate_check_locked_files = TRUE;\n\n    return;\n}\n\n\nstatic void\nauto_delete_repo (SeafSyncManager *manager, SeafRepo *repo)\n{\n    SyncInfo *info = seaf_sync_manager_get_sync_info (manager, repo->id);\n    char *name = g_strdup (repo->name);\n\n    seaf_message (\"Auto deleted repo '%s'.\\n\", repo->name);\n\n    seaf_sync_manager_cancel_sync_task (seaf->sync_mgr, repo->id);\n\n    if (info != NULL && info->in_sync) {\n        seaf_repo_manager_mark_repo_deleted (seaf->repo_mgr, repo);\n    } else {\n        seaf_repo_manager_del_repo (seaf->repo_mgr, repo);\n    }\n\n    /* Publish a message, for applet to notify in the system tray */\n    seaf_mq_manager_publish_notification (seaf->mq_mgr,\n                                          \"repo.removed\",\n                                          name);\n    g_free (name);\n}\n\nstatic char *\nhttp_fileserver_url (const char *url)\n{\n    const char *host;\n    char *colon;\n    char *url_no_port;\n    char *ret = NULL;\n\n    /* Just return the url itself if it's invalid. */\n    if (strlen(url) <= strlen(\"http://\"))\n        return g_strdup(url);\n\n    /* Skip protocol schem. */\n    host = url + strlen(\"http://\");\n\n    colon = strrchr (host, ':');\n    if (colon) {\n        url_no_port = g_strndup(url, colon - url);\n        ret = g_strconcat(url_no_port, \":8082\", NULL);\n        g_free (url_no_port);\n    } else {\n        ret = g_strconcat(url, \":8082\", NULL);\n    }\n\n    return ret;\n}\n\nstatic void\ncheck_http_fileserver_protocol_done (HttpProtocolVersion *result, void *user_data)\n{\n    HttpServerState *state = user_data;\n\n    state->checking = FALSE;\n\n    if (result->check_success && !result->not_supported) {\n        state->http_version = MIN(result->version, CURRENT_SYNC_PROTO_VERSION);\n        state->effective_host = http_fileserver_url(state->testing_host);\n        state->use_fileserver_port = TRUE;\n        seaf_message (\"File syncing protocol version on server %s is %d. \"\n                      \"Client file syncing protocol version is %d. Use version %d.\\n\",\n                      state->effective_host, result->version, CURRENT_SYNC_PROTO_VERSION,\n                      state->http_version);\n    }\n}\n\nstatic void\ncheck_http_protocol_done (HttpProtocolVersion *result, void *user_data)\n{\n    HttpServerState *state = user_data;\n\n    if (result->check_success && !result->not_supported) {\n        state->http_version = MIN(result->version, CURRENT_SYNC_PROTO_VERSION);\n        state->effective_host = g_strdup(state->testing_host);\n        state->checking = FALSE;\n        seaf_message (\"File syncing protocol version on server %s is %d. \"\n                      \"Client file syncing protocol version is %d. Use version %d.\\n\",\n                      state->effective_host, result->version, CURRENT_SYNC_PROTO_VERSION,\n                      state->http_version);\n    } else if (strncmp(state->testing_host, \"https\", 5) != 0) {\n        char *host_fileserver = http_fileserver_url(state->testing_host);\n        if (http_tx_manager_check_protocol_version (seaf->http_tx_mgr,\n                                                    host_fileserver,\n                                                    TRUE,\n                                                    check_http_fileserver_protocol_done,\n                                                    state) < 0)\n            state->checking = FALSE;\n        g_free (host_fileserver);\n    } else {\n        state->checking = FALSE;\n    }\n}\n\n#define CHECK_HTTP_INTERVAL 10\n\n/*\n * Returns TRUE if we're ready to use http-sync; otherwise FALSE.\n */\nstatic gboolean\ncheck_http_protocol (SeafSyncManager *mgr, SeafRepo *repo)\n{\n    /* If a repo was cloned before 4.0, server-url is not set. */\n    if (!repo->server_url)\n        return FALSE;\n\n    HttpServerState *state = g_hash_table_lookup (mgr->http_server_states,\n                                                  repo->server_url);\n    if (!state) {\n        state = http_server_state_new ();\n        g_hash_table_insert (mgr->http_server_states,\n                             g_strdup(repo->server_url), state);\n    }\n\n    if (state->checking) {\n        return FALSE;\n    }\n\n    if (state->http_version > 0) {\n        if (!repo->effective_host) {\n            repo->effective_host = g_strdup(state->effective_host);\n            repo->use_fileserver_port = state->use_fileserver_port;\n        }\n        return TRUE;\n    }\n\n    /* If we haven't detected the server url successfully, retry every 10 seconds. */\n    gint64 now = time(NULL);\n    if (now - state->last_http_check_time < CHECK_HTTP_INTERVAL)\n        return FALSE;\n\n    /* First try repo->server_url.\n     * If it fails and https is not used, try server_url:8082 instead.\n     */\n    g_free (state->testing_host);\n    state->testing_host = g_strdup(repo->server_url);\n\n    state->last_http_check_time = (gint64)time(NULL);\n\n    if (http_tx_manager_check_protocol_version (seaf->http_tx_mgr,\n                                                repo->server_url,\n                                                FALSE,\n                                                check_http_protocol_done,\n                                                state) < 0)\n        return FALSE;\n\n    state->checking = TRUE;\n\n    return FALSE;\n}\n\nstatic void\ncheck_notif_server_done (gboolean is_alive, void *user_data)\n{\n    HttpServerState *state = user_data;\n    \n    if (is_alive) {\n        state->notif_server_alive = TRUE;\n        seaf_message (\"Notification server is enabled on the remote server %s.\\n\", state->effective_host);\n    }\n}\n\nstatic char *\nhttp_notification_url (const char *url)\n{\n    const char *host;\n    char *colon;\n    char *url_no_port;\n    char *ret = NULL;\n\n    /* Just return the url itself if it's invalid. */\n    if (strlen(url) <= strlen(\"http://\"))\n        return g_strdup(url);\n\n    /* Skip protocol schem. */\n    host = url + strlen(\"http://\");\n\n    colon = strrchr (host, ':');\n    if (colon) {\n        url_no_port = g_strndup(url, colon - url);\n        ret = g_strconcat(url_no_port, \":8083\", NULL);\n        g_free (url_no_port);\n    } else {\n        ret = g_strconcat(url, \":8083\", NULL);\n    }\n\n    return ret;\n}\n\n#if defined WIN32 || defined __APPLE__ || defined COMPILE_LINUX_WS\n// Returns TRUE if notification server is alive; otherwise FALSE.\n// We only check notification server once.\nstatic gboolean\ncheck_notif_server (SeafSyncManager *mgr, SeafRepo *repo)\n{\n    if (!repo->server_url)\n        return FALSE;\n\n    HttpServerState *state = g_hash_table_lookup (mgr->http_server_states,\n                                                  repo->server_url);\n    if (!state) {\n        return FALSE;\n    }\n\n    if (state->notif_server_alive) {\n        return TRUE;\n    }\n\n    if (state->notif_server_checked) {\n        return FALSE;\n    }\n\n    char *notif_url = NULL;\n    if (state->use_fileserver_port) {\n        notif_url = http_notification_url (repo->server_url);\n    } else {\n        notif_url = g_strdup (repo->server_url);\n    }\n\n    if (http_tx_manager_check_notif_server (seaf->http_tx_mgr,\n                                            notif_url,\n                                            state->use_fileserver_port,\n                                            check_notif_server_done,\n                                            state) < 0) {\n        g_free (notif_url);\n        return FALSE;\n    }\n\n    state->notif_server_checked = TRUE;\n\n    g_free (notif_url);\n    return FALSE;\n}\n#endif\n\ngint\ncmp_repos_by_sync_time (gconstpointer a, gconstpointer b, gpointer user_data)\n{\n    const SeafRepo *repo_a = a;\n    const SeafRepo *repo_b = b;\n\n    return (repo_a->last_sync_time - repo_b->last_sync_time);\n}\n\n#if defined WIN32 || defined __APPLE__\n\nstatic void\ncleanup_file_blocks (const char *repo_id, int version, const char *file_id)\n{\n    Seafile *file;\n    int i;\n\n    file = seaf_fs_manager_get_seafile (seaf->fs_mgr,\n                                        repo_id, version,\n                                        file_id);\n    for (i = 0; i < file->n_blocks; ++i)\n        seaf_block_manager_remove_block (seaf->block_mgr,\n                                         repo_id, version,\n                                         file->blk_sha1s[i]);\n\n    seafile_unref (file);\n}\n\nstatic gboolean\nhandle_locked_file_update (SeafRepo *repo, struct index_state *istate,\n                           LockedFileSet *fset, const char *path, LockedFile *locked,\n                           CheckoutBlockAux *aux)\n{\n    gboolean locked_on_server = FALSE;\n    struct cache_entry *ce;\n    char file_id[41];\n    char *fullpath = NULL;\n    SeafStat st;\n    gboolean file_exists = TRUE;\n    SeafileCrypt *crypt = NULL;\n    SeafBranch *master = NULL;\n    gboolean ret = TRUE;\n\n    locked_on_server = seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,\n                                                             repo->id,\n                                                             path);\n    /* File is still locked, do nothing. */\n    if (do_check_file_locked (path, repo->worktree, locked_on_server))\n        return FALSE;\n\n    seaf_debug (\"Update previously locked file %s in repo %.8s.\\n\",\n                path, repo->id);\n\n    /* If the file was locked on the last checkout, the worktree file was not\n     * updated, but the index has been updated. So the ce in the index should\n     * contain the information for the file to be updated.\n     */\n    ce = index_name_exists (istate, path, strlen(path), 0);\n    if (!ce) {\n        seaf_warning (\"Cache entry for %s in repo %s(%.8s) is not found \"\n                      \"when update locked file.\",\n                      path, repo->name, repo->id);\n        goto remove_from_db;\n    }\n\n    rawdata_to_hex (ce->sha1, file_id, 20);\n\n    fullpath = g_build_filename (repo->worktree, path, NULL);\n\n    file_exists = seaf_util_exists (fullpath);\n\n    if (file_exists && seaf_stat (fullpath, &st) < 0) {\n        seaf_warning (\"Failed to stat %s: %s.\\n\", fullpath, strerror(errno));\n        goto out;\n    }\n\n    if (repo->encrypted)\n        crypt = seafile_crypt_new (repo->enc_version,\n                                   repo->enc_key,\n                                   repo->enc_iv);\n\n    master = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, \"master\");\n    if (!master) {\n        seaf_warning (\"No master branch found for repo %s(%.8s).\\n\",\n                      repo->name, repo->id);\n        goto out;\n    }\n\n    gboolean conflicted;\n    gboolean force_conflict = (file_exists && st.st_mtime != locked->old_mtime);\n    char *username = repo->username;\n    if (!username) {\n        username = repo->email;\n    }\n    if (seaf_repo_manager_checkout_file (repo,\n                                         file_id, fullpath,\n                                         ce->ce_mode, ce->ce_mtime.sec,\n                                         crypt,\n                                         path,\n                                         master->commit_id,\n                                         force_conflict,\n                                         &conflicted,\n                                         username,\n                                         aux) < 0) {\n        seaf_warning (\"Failed to checkout previously locked file %s in repo \"\n                      \"%s(%.8s).\\n\",\n                      path, repo->name, repo->id);\n    }\n\n    seaf_sync_manager_update_active_path (seaf->sync_mgr,\n                                          repo->id,\n                                          path,\n                                          S_IFREG,\n                                          SYNC_STATUS_SYNCED,\n                                          TRUE);\n\n    /* In checkout, the file was overwritten by rename, so the file attributes\n       are gone. We have to set read-only state again.\n    */\n    if (locked_on_server)\n        seaf_filelock_manager_lock_wt_file (seaf->filelock_mgr,\n                                            repo->id,\n                                            path);\n\nout:\n    cleanup_file_blocks (repo->id, repo->version, file_id);\n\nremove_from_db:\n    /* Remove the locked file record from db. */\n    locked_file_set_remove (fset, path, TRUE);\n\n    g_free (fullpath);\n    g_free (crypt);\n    seaf_branch_unref (master);\n    return ret;\n}\n\nstatic gboolean\nhandle_locked_file_delete (SeafRepo *repo, struct index_state *istate,\n                           LockedFileSet *fset, const char *path, LockedFile *locked)\n{\n    gboolean locked_on_server = FALSE;\n    char *fullpath = NULL;\n    SeafStat st;\n    gboolean file_exists = TRUE;\n    gboolean ret = TRUE;\n\n    locked_on_server = seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,\n                                                             repo->id,\n                                                             path);\n\n    /* File is still locked, do nothing. */\n    if (do_check_file_locked (path, repo->worktree, locked_on_server))\n        return FALSE;\n\n    seaf_debug (\"Delete previously locked file %s in repo %.8s.\\n\",\n                path, repo->id);\n\n    fullpath = g_build_filename (repo->worktree, path, NULL);\n\n    file_exists = seaf_util_exists (fullpath);\n\n    if (file_exists && seaf_stat (fullpath, &st) < 0) {\n        seaf_warning (\"Failed to stat %s: %s.\\n\", fullpath, strerror(errno));\n        goto out;\n    }\n\n    if (file_exists && st.st_mtime == locked->old_mtime)\n        seaf_util_unlink (fullpath);\n\nout:\n    /* Remove the locked file record from db. */\n    locked_file_set_remove (fset, path, TRUE);\n\n    g_free (fullpath);\n    return ret;\n}\n\nstatic void *\ncheck_locked_files (void *vdata)\n{\n    SeafRepo *repo = vdata;\n    LockedFileSet *fset;\n    GHashTableIter iter;\n    gpointer key, value;\n    char *path;\n    LockedFile *locked;\n    char index_path[SEAF_PATH_MAX];\n    struct index_state istate;\n\n    fset = seaf_repo_manager_get_locked_file_set (seaf->repo_mgr, repo->id);\n\n    if (g_hash_table_size (fset->locked_files) == 0) {\n        locked_file_set_free (fset);\n        return vdata;\n    }\n\n    memset (&istate, 0, sizeof(istate));\n    snprintf (index_path, SEAF_PATH_MAX, \"%s/%s\", repo->manager->index_dir, repo->id);\n    if (read_index_from (&istate, index_path, repo->version) < 0) {\n        seaf_warning (\"Failed to load index.\\n\");\n        return vdata;\n    }\n\n    CheckoutBlockAux *aux = g_new0 (CheckoutBlockAux, 1);\n    aux->repo_id = g_strdup (repo->id);\n    aux->host = g_strdup (repo->effective_host);\n    aux->token = g_strdup (repo->token);\n    aux->use_fileserver_port = repo->use_fileserver_port;\n\n    gboolean success;\n    g_hash_table_iter_init (&iter, fset->locked_files);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        path = key;\n        locked = value;\n\n        success = FALSE;\n        if (strcmp (locked->operation, LOCKED_OP_UPDATE) == 0)\n            success = handle_locked_file_update (repo, &istate, fset, path, locked, aux);\n        else if (strcmp (locked->operation, LOCKED_OP_DELETE) == 0)\n            success = handle_locked_file_delete (repo, &istate, fset, path, locked);\n\n        if (success)\n            g_hash_table_iter_remove (&iter);\n    }\n    free_checkout_block_aux (aux);\n\n    discard_index (&istate);\n    locked_file_set_free (fset);\n\n    return vdata;\n}\n\nstatic void\ncheck_locked_files_done (void *vdata)\n{\n    SeafRepo *repo = vdata;\n    repo->checking_locked_files = FALSE;\n}\n\n#endif\n\nstatic void\ncheck_folder_perms_done (HttpFolderPerms *result, void *user_data)\n{\n    HttpServerState *server_state = user_data;\n    GList *ptr;\n    HttpFolderPermRes *res;\n    gint64 now = (gint64)time(NULL);\n\n    server_state->checking_folder_perms = FALSE;\n\n    if (!result->success) {\n        /* If on star-up we find that checking folder perms fails,\n         * we assume the server doesn't support it.\n         */\n        if (server_state->last_check_perms_time == 0)\n            server_state->folder_perms_not_supported = TRUE;\n        server_state->last_check_perms_time = now;\n        return;\n    }\n\n    SyncInfo *info;\n    for (ptr = result->results; ptr; ptr = ptr->next) {\n        res = ptr->data;\n\n        info = get_sync_info (seaf->sync_mgr, res->repo_id);\n        if (info->in_sync)\n            continue;\n\n        seaf_repo_manager_update_folder_perms (seaf->repo_mgr, res->repo_id,\n                                               FOLDER_PERM_TYPE_USER,\n                                               res->user_perms);\n        seaf_repo_manager_update_folder_perms (seaf->repo_mgr, res->repo_id,\n                                               FOLDER_PERM_TYPE_GROUP,\n                                               res->group_perms);\n        seaf_repo_manager_update_folder_perm_timestamp (seaf->repo_mgr,\n                                                        res->repo_id,\n                                                        res->timestamp);\n    }\n\n    server_state->last_check_perms_time = now;\n}\n\nstatic void\ncheck_folder_permissions_one_server_immediately (SeafSyncManager *mgr,\n                                                 const char *host,\n                                                 HttpServerState *server_state,\n                                                 GList *repos,\n                                                 gboolean force)\n{\n    GList *ptr;\n    SeafRepo *repo;\n    char *token;\n    gint64 timestamp;\n    HttpFolderPermReq *req;\n    GList *requests = NULL;\n\n    for (ptr = repos; ptr; ptr = ptr->next) {\n        repo = ptr->data;\n\n#if defined WIN32 || defined __APPLE__ || defined COMPILE_LINUX_WS\n        if (!force && seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo))\n            continue;\n#endif\n\n        if (!repo->head)\n            continue;\n\n        if (g_strcmp0 (host, repo->server_url) != 0)\n            continue;\n\n        token = seaf_repo_manager_get_repo_property (seaf->repo_mgr,\n                                                     repo->id, REPO_PROP_TOKEN);\n        if (!token)\n            continue;\n\n        timestamp = seaf_repo_manager_get_folder_perm_timestamp (seaf->repo_mgr,\n                                                                 repo->id);\n        if (timestamp < 0)\n            timestamp = 0;\n\n        req = g_new0 (HttpFolderPermReq, 1);\n        memcpy (req->repo_id, repo->id, 36);\n        req->token = g_strdup(token);\n        req->timestamp = timestamp;\n\n        requests = g_list_append (requests, req);\n\n        g_free (token);\n    }\n\n    if (!requests)\n        return;\n\n    server_state->checking_folder_perms = TRUE;\n\n    /* The requests list will be freed in http tx manager. */\n    if (http_tx_manager_get_folder_perms (seaf->http_tx_mgr,\n                                          server_state->effective_host,\n                                          server_state->use_fileserver_port,\n                                          requests,\n                                          check_folder_perms_done,\n                                          server_state) < 0) {\n        seaf_warning (\"Failed to schedule check folder permissions\\n\");\n        server_state->checking_folder_perms = FALSE;\n    }\n}\n\nstatic void\ncheck_folder_permissions_one_server (SeafSyncManager *mgr,\n                                     const char *host,\n                                     HttpServerState *server_state,\n                                     GList *repos)\n{\n    if (!seaf_repo_manager_server_is_pro (seaf->repo_mgr, host))\n        return;\n\n    gint64 now = (gint64)time(NULL);\n\n    if (server_state->http_version == 0 ||\n        server_state->folder_perms_not_supported ||\n        server_state->checking_folder_perms)\n        return;\n\n    if (server_state->immediate_check_folder_perms) {\n        server_state->immediate_check_folder_perms = FALSE;\n        check_folder_permissions_one_server_immediately (mgr, host, server_state, repos, TRUE);\n        return;\n    }\n\n    if (server_state->last_check_perms_time > 0 &&\n        now - server_state->last_check_perms_time < CHECK_FOLDER_PERMS_INTERVAL)\n        return;\n\n    check_folder_permissions_one_server_immediately (mgr, host, server_state, repos, FALSE);\n}\n\nstatic void\ncheck_folder_permissions (SeafSyncManager *mgr, GList *repos)\n{\n    GHashTableIter iter;\n    gpointer key, value;\n    char *host;\n    HttpServerState *state;\n\n    g_hash_table_iter_init (&iter, mgr->http_server_states);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        host = key;\n        state = value;\n        check_folder_permissions_one_server (mgr, host, state, repos);\n    }\n}\n\nstatic void\ncheck_server_locked_files_done (HttpLockedFiles *result, void *user_data)\n{\n    HttpServerState *server_state = user_data;\n    GList *ptr;\n    HttpLockedFilesRes *locked_res;\n    gint64 now = (gint64)time(NULL);\n\n    server_state->checking_locked_files = FALSE;\n\n    if (!result->success) {\n        /* If on star-up we find that checking locked files fails,\n         * we assume the server doesn't support it.\n         */\n        if (server_state->last_check_locked_files_time == 0)\n            server_state->locked_files_not_supported = TRUE;\n        server_state->last_check_locked_files_time = now;\n        return;\n    }\n\n    SyncInfo *info;\n    for (ptr = result->results; ptr; ptr = ptr->next) {\n        locked_res = ptr->data;\n\n        info = get_sync_info (seaf->sync_mgr, locked_res->repo_id);\n        if (info->in_sync)\n            continue;\n\n        seaf_filelock_manager_update (seaf->filelock_mgr,\n                                      locked_res->repo_id,\n                                      locked_res->locked_files);\n\n        seaf_filelock_manager_update_timestamp (seaf->filelock_mgr,\n                                                locked_res->repo_id,\n                                                locked_res->timestamp);\n    }\n\n    server_state->last_check_locked_files_time = now;\n}\n\nstatic void\ncheck_locked_files_one_server_immediately (SeafSyncManager *mgr,\n                                           const char *host,\n                                           HttpServerState *server_state,\n                                           GList *repos,\n                                           gboolean force)\n{\n    GList *ptr;\n    SeafRepo *repo;\n    char *token;\n    gint64 timestamp;\n    HttpLockedFilesReq *req;\n    GList *requests = NULL;\n\n    for (ptr = repos; ptr; ptr = ptr->next) {\n        repo = ptr->data;\n\n#if defined WIN32 || defined __APPLE__ || defined COMPILE_LINUX_WS\n        if (!force && seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo))\n            continue;\n#endif\n\n        if (!repo->head)\n            continue;\n\n        if (g_strcmp0 (host, repo->server_url) != 0)\n            continue;\n\n        token = seaf_repo_manager_get_repo_property (seaf->repo_mgr,\n                                                     repo->id, REPO_PROP_TOKEN);\n        if (!token)\n            continue;\n\n        timestamp = seaf_filelock_manager_get_timestamp (seaf->filelock_mgr,\n                                                         repo->id);\n        if (timestamp < 0)\n            timestamp = 0;\n\n        req = g_new0 (HttpLockedFilesReq, 1);\n        memcpy (req->repo_id, repo->id, 36);\n        req->token = g_strdup(token);\n        req->timestamp = timestamp;\n\n        requests = g_list_append (requests, req);\n\n        g_free (token);\n    }\n\n    if (!requests)\n        return;\n\n    server_state->checking_locked_files = TRUE;\n\n    /* The requests list will be freed in http tx manager. */\n    if (http_tx_manager_get_locked_files (seaf->http_tx_mgr,\n                                          server_state->effective_host,\n                                          server_state->use_fileserver_port,\n                                          requests,\n                                          check_server_locked_files_done,\n                                          server_state) < 0) {\n        seaf_warning (\"Failed to schedule check server locked files\\n\");\n        server_state->checking_locked_files = FALSE;\n    }\n}\n\nstatic void\ncheck_locked_files_one_server (SeafSyncManager *mgr,\n                               const char *host,\n                               HttpServerState *server_state,\n                               GList *repos)\n{\n    if (!seaf_repo_manager_server_is_pro (seaf->repo_mgr, host))\n        return;\n\n    gint64 now = (gint64)time(NULL);\n\n    if (server_state->http_version == 0 ||\n        server_state->locked_files_not_supported ||\n        server_state->checking_locked_files)\n        return;\n\n    if (server_state->immediate_check_locked_files) {\n        server_state->immediate_check_locked_files = FALSE;\n        check_locked_files_one_server_immediately (mgr, host, server_state, repos, TRUE);\n        return;\n    }\n\n    if (server_state->last_check_locked_files_time > 0 &&\n        now - server_state->last_check_locked_files_time < CHECK_FOLDER_PERMS_INTERVAL)\n        return;\n\n    check_locked_files_one_server_immediately (mgr, host, server_state, repos, FALSE);\n}\n\nstatic void\ncheck_server_locked_files (SeafSyncManager *mgr, GList *repos)\n{\n    GHashTableIter iter;\n    gpointer key, value;\n    char *host;\n    HttpServerState *state;\n\n    g_hash_table_iter_init (&iter, mgr->http_server_states);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        host = key;\n        state = value;\n        check_locked_files_one_server (mgr, host, state, repos);\n    }\n}\n\n#if 0\nstatic void\nprint_active_paths (SeafSyncManager *mgr)\n{\n    int n = seaf_sync_manager_active_paths_number(mgr);\n    seaf_message (\"%d active paths\\n\\n\", n);\n    if (n < 10) {\n        char *paths_json = seaf_sync_manager_list_active_paths_json (mgr);\n        seaf_message (\"%s\\n\", paths_json);\n        g_free (paths_json);\n    }\n}\n#endif\n\nstatic char *\nparse_jwt_token (const char *rsp_content, gint64 rsp_size)\n{\n    json_t *object = NULL;\n    json_error_t jerror;\n    const char *member = NULL;\n    char *jwt_token = NULL;\n\n    object = json_loadb (rsp_content, rsp_size, 0, &jerror);\n    if (!object) {\n        return NULL;\n    }\n\n    if (json_object_has_member (object, \"jwt_token\")) {\n        member = json_object_get_string_member (object, \"jwt_token\");\n        if (member)\n            jwt_token = g_strdup (member);\n    } else {\n        json_decref (object);\n        return NULL;\n    }\n\n    json_decref (object);\n    return jwt_token;\n}\n\n#define HTTP_FORBIDDEN 403\n#define HTTP_NOT_FOUND 404\n#define HTTP_SERVERR   500\n#define HTTP_SERVERR_BAD_GATEWAY    502\n#define HTTP_SERVERR_UNAVAILABLE    503\n#define HTTP_SERVERR_TIMEOUT        504\n\ntypedef struct _GetJwtTokenAux {\n    HttpServerState *state;\n    char *repo_id;\n} GetJwtTokenAux;\n\nstatic void\nfileserver_get_jwt_token_cb (HttpAPIGetResult *result, void *user_data)\n{\n    GetJwtTokenAux *aux = user_data;\n    HttpServerState *state = aux->state;\n    char *repo_id = aux->repo_id;\n    SeafRepo *repo = NULL;\n    char *jwt_token = NULL;\n\n    state->n_jwt_token_request--;\n\n    if (result->http_status == HTTP_NOT_FOUND ||\n        result->http_status == HTTP_FORBIDDEN ||\n        result->http_status == HTTP_SERVERR) {\n        goto out;\n    }\n\n    if (!result->success) {\n        goto out;\n    }\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo)\n        goto out;\n\n    jwt_token = parse_jwt_token (result->rsp_content,result->rsp_size);\n    if (!jwt_token) {\n        seaf_warning (\"Failed to parse jwt token for repo %s\\n\", repo->id);\n        goto out;\n    }\n    g_free (repo->jwt_token);\n    repo->jwt_token = jwt_token;\n\nout:\n    g_free (aux->repo_id);\n    g_free (aux);\n    return;\n}\n\ninline static gboolean\nperiodic_sync_due (SeafRepo *repo)\n{\n    int now = (int)time(NULL);\n    return (now > (repo->last_sync_time + repo->sync_interval));\n}\n\n#if defined WIN32 || defined __APPLE__ || defined COMPILE_LINUX_WS\nstatic int\ncheck_and_subscribe_repo (SeafSyncManager *mgr, SeafRepo *repo)\n{\n    char *url = NULL;\n    HttpServerState *state = g_hash_table_lookup (mgr->http_server_states,\n                                                  repo->server_url);\n    if (!state || !state->notif_server_alive) {\n        return 0;\n    }\n\n    if (state->n_jwt_token_request > 10) {\n        return 0;\n    }\n\n    gint64 now = (gint64)time(NULL);\n    if (now - repo->last_check_jwt_token > JWT_TOKEN_EXPIRE_TIME) {\n        repo->last_check_jwt_token = now;\n        if (!repo->use_fileserver_port)\n            url = g_strdup_printf (\"%s/seafhttp/repo/%s/jwt-token\", repo->effective_host, repo->id);\n        else\n            url = g_strdup_printf (\"%s/repo/%s/jwt-token\", repo->effective_host, repo->id);\n\n        state->n_jwt_token_request++;\n        GetJwtTokenAux *aux = g_new0 (GetJwtTokenAux, 1);\n        aux->repo_id = g_strdup (repo->id);\n        aux->state = state;\n        if (http_tx_manager_fileserver_api_get (seaf->http_tx_mgr,\n                                            repo->effective_host,\n                                            url,\n                                            repo->token,\n                                            fileserver_get_jwt_token_cb,\n                                            aux) < 0) {\n            g_free (aux->repo_id);\n            g_free (aux);\n            state->n_jwt_token_request--;\n        }\n        g_free (url);\n        return 0;\n    }\n    if (!seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) {\n        if (repo->jwt_token && repo->server_url)\n            seaf_notif_manager_subscribe_repo (seaf->notif_mgr, repo);\n    }\n\n    return 0;\n}\n#endif\n\nstatic int\nauto_sync_pulse (void *vmanager)\n{\n    SeafSyncManager *manager = vmanager;\n    GList *repos, *ptr;\n    SeafRepo *repo;\n\n    repos = seaf_repo_manager_get_repo_list (manager->seaf->repo_mgr, -1, -1);\n\n    check_folder_permissions (manager, repos);\n\n    check_server_locked_files (manager, repos);\n\n    /* Sort repos by last_sync_time, so that we don't \"starve\" any repo. */\n    repos = g_list_sort_with_data (repos, cmp_repos_by_sync_time, NULL);\n\n    for (ptr = repos; ptr != NULL; ptr = ptr->next) {\n        repo = ptr->data;\n\n        if (!manager->priv->auto_sync_enabled || !repo->auto_sync)\n            continue;\n\n        /* Every second, we'll check the worktree to see if it still exists.\n         * We'll invalidate worktree if it gets moved or deleted.\n         * But there is a hole here: If the user delete the worktree dir and\n         * recreate a dir with the same name within a second, we'll falsely\n         * see the worktree as valid. What's worse, the new worktree dir won't\n         * be monitored.\n         * This problem can only be solved by restart.\n         */\n        /* If repo has been checked out and the worktree doesn't exist,\n         * we'll delete the repo automatically.\n         */\n\n        if (repo->head != NULL) {\n            if (seaf_repo_check_worktree (repo) < 0) {\n                if (!repo->worktree_invalid) {\n                    // The repo worktree was valid, but now it's invalid\n                    seaf_repo_manager_invalidate_repo_worktree (seaf->repo_mgr, repo);\n                    if (!seafile_session_config_get_allow_invalid_worktree(seaf)) {\n                        auto_delete_repo (manager, repo);\n                    }\n                }\n                continue;\n            } else {\n                if (repo->worktree_invalid) {\n                    // The repo worktree was invalid, but now it's valid again,\n                    // so we start watch it\n                    seaf_repo_manager_validate_repo_worktree (seaf->repo_mgr, repo);\n                    continue;\n                }\n            }\n        }\n\n        repo->worktree_invalid = FALSE;\n\n#ifdef USE_GPL_CRYPTO\n        if (repo->version == 0 || (repo->encrypted && repo->enc_version < 2)) {\n            continue;\n        }\n#endif\n\n        if (!repo->token) {\n            /* If the user has logged out of the account, the repo token would\n             * be null */\n            seaf_debug (\"repo token of %s (%.8s) is null, would not sync it\\n\", repo->name, repo->id);\n            continue;\n        }\n\n        /* Don't sync repos not checked out yet. */\n        if (!repo->head)\n            continue;\n\n        gint64 now = (gint64)time(NULL);\n\n#if defined WIN32 || defined __APPLE__\n        if (repo->version > 0) {\n            if (repo->checking_locked_files)\n                continue;\n\n            // Since delayed file updates requires blocks from the server, we need to check the protocol information before updating.\n            if (check_http_protocol (manager, repo)) {\n                if (repo->last_check_locked_time == 0 ||\n                    now - repo->last_check_locked_time >= CHECK_LOCKED_FILES_INTERVAL)\n                {\n                    repo->checking_locked_files = TRUE;\n                    if (seaf_job_manager_schedule_job (seaf->job_mgr,\n                                                       check_locked_files,\n                                                       check_locked_files_done,\n                                                       repo) < 0) {\n                        seaf_warning (\"Failed to schedule check local locked files\\n\");\n                        repo->checking_locked_files = FALSE;\n                    } else {\n                        repo->last_check_locked_time = now;\n                    }\n\n                }\n            \n            }\n        }\n#endif\n\n        SyncInfo *info = get_sync_info (manager, repo->id);\n\n        if (info->in_sync)\n            continue;\n\n        if (info->sync_perm_err_cnt > SYNC_PERM_ERROR_RETRY_TIME)\n            continue;\n\n        if (repo->encrypted && repo->enc_key[0] == '\\0'){\n            repo->empty_enc_key = TRUE;\n            continue;\n        }\n\n        if (repo->version > 0) {\n            /* For repo version > 0, only use http sync. */\n            if (check_http_protocol (manager, repo)) {\n#if defined WIN32 || defined __APPLE__ || defined COMPILE_LINUX_WS\n                if (check_notif_server (manager, repo)) {\n                    seaf_notif_manager_connect_server (seaf->notif_mgr, repo->server_url, repo->use_fileserver_port);\n                }\n#endif\n\n                if (repo->sync_interval == 0) {\n                    if (sync_repo_v2 (manager, repo, FALSE) < 0) \n                        continue;\n#if defined WIN32 || defined __APPLE__ || defined COMPILE_LINUX_WS\n                    check_and_subscribe_repo (manager, repo);\n#endif\n                }\n                else if (periodic_sync_due (repo)) {\n                    if (sync_repo_v2 (manager, repo, TRUE) < 0)\n                        continue;\n#if defined WIN32 || defined __APPLE__ || defined COMPILE_LINUX_WS\n                    check_and_subscribe_repo (manager, repo);\n#endif\n                }\n            }\n        } else {\n            seaf_warning (\"Repo %s(%s) is version 0 library. Syncing is no longer supported.\\n\",\n                          repo->name, repo->id);\n        }\n    }\n\n    g_list_free (repos);\n    return TRUE;\n}\n\nstatic void\non_repo_http_fetched (SeafileSession *seaf,\n                      HttpTxTask *tx_task,\n                      SeafSyncManager *manager)\n{\n    SyncInfo *info = get_sync_info (manager, tx_task->repo_id);\n    SyncTask *task = info->current_task;\n\n    /* Clone tasks are handled by clone manager. */\n    if (tx_task->is_clone)\n        return;\n\n    if (task->repo->delete_pending) {\n        transition_sync_state (task, SYNC_STATE_CANCELED);\n        seaf_repo_manager_del_repo (seaf->repo_mgr, task->repo);\n        return;\n    }\n\n    if (tx_task->state == HTTP_TASK_STATE_FINISHED) {\n        memcpy (info->head_commit, tx_task->head, 41);\n        transition_sync_state (task, SYNC_STATE_DONE);\n    } else if (tx_task->state == HTTP_TASK_STATE_CANCELED) {\n        transition_sync_state (task, SYNC_STATE_CANCELED);\n    } else if (tx_task->state == HTTP_TASK_STATE_ERROR) {\n        if (tx_task->error == SYNC_ERROR_ID_SERVER_REPO_DELETED) {\n            on_repo_deleted_on_server (task, task->repo);\n        } else {\n            set_task_error (task, tx_task->error);\n        }\n    }\n}\n\nstatic void\non_repo_http_uploaded (SeafileSession *seaf,\n                       HttpTxTask *tx_task,\n                       SeafSyncManager *manager)\n{\n    SyncInfo *info = get_sync_info (manager, tx_task->repo_id);\n    SyncTask *task = info->current_task;\n\n    g_return_if_fail (task != NULL && info->in_sync);\n\n    if (task->repo->delete_pending) {\n        transition_sync_state (task, SYNC_STATE_CANCELED);\n        seaf_repo_manager_del_repo (seaf->repo_mgr, task->repo);\n        return;\n    }\n\n    if (tx_task->state == HTTP_TASK_STATE_FINISHED) {\n        memcpy (info->head_commit, tx_task->head, 41);\n\n        /* Save current head commit id for GC. */\n        seaf_repo_manager_set_repo_property (seaf->repo_mgr,\n                                             task->repo->id,\n                                             REPO_LOCAL_HEAD,\n                                             task->repo->head->commit_id);\n        task->uploaded = TRUE;\n        check_head_commit_http (task);\n    } else if (tx_task->state == HTTP_TASK_STATE_CANCELED) {\n        transition_sync_state (task, SYNC_STATE_CANCELED);\n    } else if (tx_task->state == HTTP_TASK_STATE_ERROR) {\n        if (tx_task->error == SYNC_ERROR_ID_SERVER_REPO_DELETED) {\n            on_repo_deleted_on_server (task, task->repo);\n        } else {\n            set_task_error (task, tx_task->error);\n        }\n    }\n}\n\nconst char *\nsync_state_to_str (int state)\n{\n    if (state < 0 || state >= SYNC_STATE_NUM) {\n        seaf_warning (\"illegal sync state: %d\\n\", state); \n        return NULL;\n    }\n\n    return sync_state_str[state];\n}\n\nstatic void\ndisable_auto_sync_for_repos (SeafSyncManager *mgr)\n{\n    GList *repos;\n    GList *ptr;\n    SeafRepo *repo;\n\n    repos = seaf_repo_manager_get_repo_list (seaf->repo_mgr, -1, -1);\n    for (ptr = repos; ptr; ptr = ptr->next) {\n        repo = ptr->data;\n        if (repo->sync_interval == 0)\n            seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id);\n        seaf_sync_manager_cancel_sync_task (mgr, repo->id);\n        seaf_sync_manager_remove_active_path_info (mgr, repo->id);\n    }\n\n    g_list_free (repos);\n}\n\nint\nseaf_sync_manager_disable_auto_sync (SeafSyncManager *mgr)\n{\n    if (!seaf->started) {\n        seaf_message (\"sync manager is not started, skip disable auto sync.\\n\");\n        return -1;\n    }\n\n    disable_auto_sync_for_repos (mgr);\n\n    mgr->priv->auto_sync_enabled = FALSE;\n    g_debug (\"[sync mgr] auto sync is disabled\\n\");\n    return 0;\n}\n\nstatic void\nenable_auto_sync_for_repos (SeafSyncManager *mgr)\n{\n    GList *repos;\n    GList *ptr;\n    SeafRepo *repo;\n\n    repos = seaf_repo_manager_get_repo_list (seaf->repo_mgr, -1, -1);\n    for (ptr = repos; ptr; ptr = ptr->next) {\n        repo = ptr->data;\n        if (repo->sync_interval == 0)\n            seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id, repo->worktree);\n    }\n\n    g_list_free (repos);\n}\n\nint\nseaf_sync_manager_enable_auto_sync (SeafSyncManager *mgr)\n{\n    if (!seaf->started) {\n        seaf_message (\"sync manager is not started, skip enable auto sync.\\n\");\n        return -1;\n    }\n\n    enable_auto_sync_for_repos (mgr);\n\n    mgr->priv->auto_sync_enabled = TRUE;\n    g_debug (\"[sync mgr] auto sync is enabled\\n\");\n    return 0;\n}\n\nint\nseaf_sync_manager_is_auto_sync_enabled (SeafSyncManager *mgr)\n{\n    if (mgr->priv->auto_sync_enabled)\n        return 1;\n    else\n        return 0;\n}\n\nstatic ActivePathsInfo *\nactive_paths_info_new (SeafRepo *repo)\n{\n    ActivePathsInfo *info = g_new0 (ActivePathsInfo, 1);\n\n    info->paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);\n    info->syncing_tree = sync_status_tree_new (repo->worktree);\n    info->synced_tree = sync_status_tree_new (repo->worktree);\n\n    return info;\n}\n\nstatic void\nactive_paths_info_free (ActivePathsInfo *info)\n{\n    if (!info)\n        return;\n    g_hash_table_destroy (info->paths);\n    sync_status_tree_free (info->syncing_tree);\n    sync_status_tree_free (info->synced_tree);\n    g_free (info);\n}\n\nvoid\nseaf_sync_manager_update_active_path (SeafSyncManager *mgr,\n                                      const char *repo_id,\n                                      const char *path,\n                                      int mode,\n                                      SyncStatus status,\n                                      gboolean refresh)\n{\n    ActivePathsInfo *info;\n    SeafRepo *repo;\n\n    if (!repo_id || !path) {\n        seaf_warning (\"BUG: empty repo_id or path.\\n\");\n        return;\n    }\n\n    if (status <= SYNC_STATUS_NONE || status >= N_SYNC_STATUS) {\n        seaf_warning (\"BUG: invalid sync status %d.\\n\", status);\n        return;\n    }\n\n    pthread_mutex_lock (&mgr->priv->paths_lock);\n\n    info = g_hash_table_lookup (mgr->priv->active_paths, repo_id);\n    if (!info) {\n        repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n        if (!repo) {\n            pthread_mutex_unlock (&mgr->priv->paths_lock);\n            return;\n        }\n        info = active_paths_info_new (repo);\n        g_hash_table_insert (mgr->priv->active_paths, g_strdup(repo_id), info);\n    }\n\n    SyncStatus existing = (SyncStatus) g_hash_table_lookup (info->paths, path);\n    if (!existing) {\n        g_hash_table_insert (info->paths, g_strdup(path), (void*)status);\n        if (status == SYNC_STATUS_SYNCING)\n            sync_status_tree_add (info->syncing_tree, path, mode, refresh);\n        else if (status == SYNC_STATUS_SYNCED)\n            sync_status_tree_add (info->synced_tree, path, mode, refresh);\n#ifdef WIN32\n        else if (refresh)\n            seaf_sync_manager_add_refresh_path (mgr, path);\n#endif\n    } else if (existing != status) {\n        g_hash_table_replace (info->paths, g_strdup(path), (void*)status);\n\n        if (existing == SYNC_STATUS_SYNCING)\n            sync_status_tree_del (info->syncing_tree, path);\n        else if (existing == SYNC_STATUS_SYNCED)\n            sync_status_tree_del (info->synced_tree, path);\n\n        if (status == SYNC_STATUS_SYNCING)\n            sync_status_tree_add (info->syncing_tree, path, mode, refresh);\n        else if (status == SYNC_STATUS_SYNCED)\n            sync_status_tree_add (info->synced_tree, path, mode, refresh);\n#ifdef WIN32\n        else if (refresh)\n            seaf_sync_manager_add_refresh_path (mgr, path);\n#endif\n    }\n\n    pthread_mutex_unlock (&mgr->priv->paths_lock);\n}\n\nvoid\nseaf_sync_manager_delete_active_path (SeafSyncManager *mgr,\n                                      const char *repo_id,\n                                      const char *path)\n{\n    ActivePathsInfo *info;\n\n    if (!repo_id || !path) {\n        seaf_warning (\"BUG: empty repo_id or path.\\n\");\n        return;\n    }\n\n    pthread_mutex_lock (&mgr->priv->paths_lock);\n\n    info = g_hash_table_lookup (mgr->priv->active_paths, repo_id);\n    if (!info) {\n        pthread_mutex_unlock (&mgr->priv->paths_lock);\n        return;\n    }\n\n    g_hash_table_remove (info->paths, path);\n    sync_status_tree_del (info->syncing_tree, path);\n    sync_status_tree_del (info->synced_tree, path);\n\n    pthread_mutex_unlock (&mgr->priv->paths_lock);\n}\n\nstatic char *path_status_tbl[] = {\n    \"none\",\n    \"syncing\",\n    \"error\",\n    \"ignored\",\n    \"synced\",\n    \"paused\",\n    \"readonly\",\n    \"locked\",\n    \"locked_by_me\",\n    NULL,\n};\n\nstatic char *\nget_repo_sync_status (SeafSyncManager *mgr, const char *repo_id)\n{\n    SyncInfo *info = get_sync_info (mgr, repo_id);\n    SeafRepo *repo;\n\n    if (info->in_error)\n        return g_strdup(path_status_tbl[SYNC_STATUS_ERROR]);\n\n    repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);\n    if (!repo)\n        return g_strdup(path_status_tbl[SYNC_STATUS_NONE]);\n\n    if (!repo->auto_sync || !mgr->priv->auto_sync_enabled)\n        return g_strdup(path_status_tbl[SYNC_STATUS_PAUSED]);\n\n    char allzeros[41] = {0};\n    if (!info->in_sync && memcmp(allzeros, info->head_commit, 41) == 0)\n        return g_strdup(path_status_tbl[SYNC_STATUS_NONE]);\n\n    if (info->in_sync &&\n        (info->current_task->state == SYNC_STATE_COMMIT ||\n         info->current_task->state == SYNC_STATE_FETCH ||\n         info->current_task->state == SYNC_STATE_UPLOAD ||\n         info->current_task->state == SYNC_STATE_MERGE))\n        return g_strdup(path_status_tbl[SYNC_STATUS_SYNCING]);\n    else if (!repo->is_readonly)\n        return g_strdup(path_status_tbl[SYNC_STATUS_SYNCED]);\n    else\n        return g_strdup(path_status_tbl[SYNC_STATUS_READONLY]);\n}\n\nchar *\nseaf_sync_manager_get_path_sync_status (SeafSyncManager *mgr,\n                                        const char *repo_id,\n                                        const char *path,\n                                        gboolean is_dir)\n{\n    ActivePathsInfo *info;\n    SyncInfo *sync_info;\n    SyncStatus ret = SYNC_STATUS_NONE;\n\n    if (!repo_id || !path) {\n        seaf_warning (\"BUG: empty repo_id or path.\\n\");\n        return NULL;\n    }\n\n    if (path[0] == 0) {\n        return get_repo_sync_status (mgr, repo_id);\n    }\n\n    /* If the repo is in error, all files in it should show no sync status. */\n    sync_info = get_sync_info (mgr, repo_id);\n    if (sync_info && sync_info->in_error) {\n        ret = SYNC_STATUS_NONE;\n        goto out;\n    }\n\n    pthread_mutex_lock (&mgr->priv->paths_lock);\n\n    info = g_hash_table_lookup (mgr->priv->active_paths, repo_id);\n    if (!info) {\n        pthread_mutex_unlock (&mgr->priv->paths_lock);\n        ret = SYNC_STATUS_NONE;\n        goto out;\n    }\n\n    ret = (SyncStatus) g_hash_table_lookup (info->paths, path);\n    if (is_dir && (ret == SYNC_STATUS_NONE)) {\n        /* If a dir is not in the syncing tree but in the synced tree,\n         * it's synced. Otherwise if it's in the syncing tree, some files\n         * under it must be syncing, so it should be in syncing status too.\n         */\n        if (sync_status_tree_exists (info->syncing_tree, path))\n            ret = SYNC_STATUS_SYNCING;\n        else if (sync_status_tree_exists (info->synced_tree, path))\n            ret = SYNC_STATUS_SYNCED;\n    }\n\n    pthread_mutex_unlock (&mgr->priv->paths_lock);\n\n    if (ret == SYNC_STATUS_SYNCED) {\n        if (!seaf_repo_manager_is_path_writable(seaf->repo_mgr, repo_id, path))\n            ret = SYNC_STATUS_READONLY;\n        else if (seaf_filelock_manager_is_file_locked_by_me (seaf->filelock_mgr,\n                                                             repo_id, path))\n            ret = SYNC_STATUS_LOCKED_BY_ME;\n        else if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,\n                                                       repo_id, path))\n            ret = SYNC_STATUS_LOCKED;\n    }\n\nout:\n    return g_strdup(path_status_tbl[ret]);\n}\n\nstatic json_t *\nactive_paths_to_json (GHashTable *paths)\n{\n    json_t *array = NULL, *obj = NULL;\n    GHashTableIter iter;\n    gpointer key, value;\n    char *path;\n    SyncStatus status;\n\n    array = json_array ();\n\n    g_hash_table_iter_init (&iter, paths);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        path = key;\n        status = (SyncStatus)value;\n\n        obj = json_object ();\n        json_object_set (obj, \"path\", json_string(path));\n        json_object_set (obj, \"status\", json_string(path_status_tbl[status]));\n\n        json_array_append (array, obj);\n    }\n\n    return array;\n}\n\nchar *\nseaf_sync_manager_list_active_paths_json (SeafSyncManager *mgr)\n{\n    json_t *array = NULL, *obj = NULL, *path_array = NULL;\n    GHashTableIter iter;\n    gpointer key, value;\n    char *repo_id;\n    ActivePathsInfo *info;\n    char *ret = NULL;\n\n    pthread_mutex_lock (&mgr->priv->paths_lock);\n\n    array = json_array ();\n\n    g_hash_table_iter_init (&iter, mgr->priv->active_paths);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        repo_id = key;\n        info = value;\n\n        obj = json_object();\n        path_array = active_paths_to_json (info->paths);\n        json_object_set (obj, \"repo_id\", json_string(repo_id));\n        json_object_set (obj, \"paths\", path_array);\n\n        json_array_append (array, obj);\n    }\n\n    pthread_mutex_unlock (&mgr->priv->paths_lock);\n\n    ret = json_dumps (array, JSON_INDENT(4));\n    if (!ret) {\n        seaf_warning (\"Failed to convert active paths to json\\n\");\n    }\n\n    json_decref (array);\n\n    return ret;\n}\n\nint\nseaf_sync_manager_active_paths_number (SeafSyncManager *mgr)\n{\n    GHashTableIter iter;\n    gpointer key, value;\n    ActivePathsInfo *info;\n    int ret = 0;\n\n    g_hash_table_iter_init (&iter, mgr->priv->active_paths);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        info = value;\n        ret += g_hash_table_size(info->paths);\n    }\n\n    return ret;\n}\n\nvoid\nseaf_sync_manager_remove_active_path_info (SeafSyncManager *mgr, const char *repo_id)\n{\n    pthread_mutex_lock (&mgr->priv->paths_lock);\n\n    g_hash_table_remove (mgr->priv->active_paths, repo_id);\n\n    pthread_mutex_unlock (&mgr->priv->paths_lock);\n\n#ifdef WIN32\n    /* This is a hack to tell Windows Explorer to refresh all open windows. */\n    SHChangeNotify (SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);\n#endif\n}\n\n#ifdef WIN32\n\nstatic wchar_t *\nwin_path (const char *path)\n{\n    char *ret = g_strdup(path);\n    wchar_t *ret_w;\n    char *p;\n\n    for (p = ret; *p != 0; ++p)\n        if (*p == '/')\n            *p = '\\\\';\n\n    ret_w = g_utf8_to_utf16 (ret, -1, NULL, NULL, NULL);\n\n    g_free (ret);\n    return ret_w;\n}\n\nstatic void *\nrefresh_windows_explorer_thread (void *vdata)\n{\n    GAsyncQueue *q = vdata;\n    char *path;\n    wchar_t *wpath;\n    int count = 0;\n\n    while (1) {\n        path = g_async_queue_pop (q);\n        wpath = win_path (path);\n\n        SHChangeNotify (SHCNE_ATTRIBUTES, SHCNF_PATHW, wpath, NULL);\n\n        g_free (path);\n        g_free (wpath);\n\n        if (++count >= 100) {\n            g_usleep (G_USEC_PER_SEC);\n            count = 0;\n        }\n    }\n\n    return NULL;\n}\n\nvoid\nseaf_sync_manager_add_refresh_path (SeafSyncManager *mgr, const char *path)\n{\n    g_async_queue_push (mgr->priv->refresh_paths, g_strdup(path));\n}\n\nvoid\nseaf_sync_manager_refresh_path (SeafSyncManager *mgr, const char *path)\n{\n    wchar_t *wpath;\n\n    wpath = win_path (path);\n    SHChangeNotify (SHCNE_ATTRIBUTES, SHCNF_PATHW, wpath, NULL);\n    g_free (wpath);\n}\n\n#endif\n\nstatic void\nupdate_head_commit_ids_for_server (gpointer key, gpointer value, gpointer user_data)\n{\n    char *server_url = key;\n    HttpServerState *state = value;\n    int status = 200;\n\n    /* Only get head commit ids from server if:\n     * 1. syncing protocol version has been checked, and\n     * 2. protocol version is at least 2.\n     */\n    if (state->http_version >= 2) {\n        seaf_debug (\"Updating repo head commit ids for server %s.\\n\", server_url);\n        GList *repo_id_list = seaf_repo_manager_get_repo_id_list_by_server (seaf->repo_mgr,\n                                                                            server_url);\n        if (!repo_id_list) {\n            return;\n        }\n\n        GHashTable *new_map = http_tx_manager_get_head_commit_ids (seaf->http_tx_mgr,\n                                                                   state->effective_host,\n                                                                   state->use_fileserver_port,\n                                                                   repo_id_list, &status);\n        if (new_map) {\n            //If the fileserver hangs up before sending events to the notification server,\n            // the client will not receive the updates, and some updates will be lost.\n            // Therefore, after the fileserver recovers, immediately checking locks and folder perms.\n            if (state->server_disconnected) {\n                seaf_sync_manager_check_locks_and_folder_perms (seaf->sync_mgr, server_url);\n            }\n            state->server_disconnected = FALSE;\n            pthread_mutex_lock (&state->head_commit_map_lock);\n            g_hash_table_destroy (state->head_commit_map);\n            state->head_commit_map = new_map;\n            if (!state->head_commit_map_init)\n                state->head_commit_map_init = TRUE;\n            state->last_update_head_commit_map_time = (gint64)time(NULL);\n            pthread_mutex_unlock (&state->head_commit_map_lock);\n        } else {\n            if (status == HTTP_SERVERR_BAD_GATEWAY ||\n                status == HTTP_SERVERR_UNAVAILABLE ||\n                status == HTTP_SERVERR_TIMEOUT) {\n                state->server_disconnected = TRUE;\n            }\n        }\n\n        g_list_free_full (repo_id_list, g_free);\n    }\n}\n\nstatic void *\nupdate_cached_head_commit_ids (void *arg)\n{\n    SeafSyncManager *mgr = (SeafSyncManager *)arg;\n\n    while (1) {\n        g_usleep (30 * G_USEC_PER_SEC);\n\n        g_hash_table_foreach (mgr->http_server_states, update_head_commit_ids_for_server, mgr);\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "daemon/sync-mgr.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef SYNC_MGR_H\n#define SYNC_MGR_H\n\ntypedef struct _SyncInfo SyncInfo;\ntypedef struct _SyncTask SyncTask;\n\ntypedef struct _SeafSyncManager SeafSyncManager;\ntypedef struct _SeafSyncManagerPriv SeafSyncManagerPriv;\n\nstruct SeafTimer;\n\nstruct _SyncInfo {\n    char       repo_id[41];     /* the repo */\n    char       head_commit[41]; /* head commit on relay */\n    SyncTask  *current_task;\n\n    gboolean   in_sync;         /* set to FALSE when sync state is DONE or ERROR */\n\n    gint       err_cnt;\n    gboolean   in_error;        /* set to TRUE if err_cnt >= 3 */\n\n    gboolean   deleted_on_relay;\n    gboolean   branch_deleted_on_relay;\n    gboolean   repo_corrupted;\n    gboolean   need_fetch;\n    gboolean   need_upload;\n    gboolean   need_merge;\n\n    /* Used by multipart upload. */\n    gboolean   multipart_upload;\n    gint64     total_bytes;\n    gint64     uploaded_bytes;\n    gboolean   end_multipart_upload;\n\n    gint       sync_perm_err_cnt;\n    gboolean   del_confirmation_pending;\n};\n\nenum {\n    SYNC_STATE_DONE,\n    SYNC_STATE_COMMIT,\n    SYNC_STATE_INIT,\n    SYNC_STATE_FETCH,\n    SYNC_STATE_MERGE,\n    SYNC_STATE_UPLOAD,\n    SYNC_STATE_ERROR,\n    SYNC_STATE_CANCELED,\n    SYNC_STATE_CANCEL_PENDING,\n    SYNC_STATE_NUM,\n};\n\nstruct _SyncTask {\n    SeafSyncManager *mgr;\n    SyncInfo        *info;\n    char            *dest_id;\n    gboolean         is_manual_sync;\n    gboolean         is_initial_commit;\n    int              state;\n    int              error;\n    char            *tx_id;\n    char            *token;\n    struct SeafTimer *commit_timer;\n\n    gboolean         uploaded;\n\n    int              http_version;\n\n    SeafRepo        *repo;  /* for convenience, only valid when in_sync. */\n};\n\nenum _SyncStatus {\n    SYNC_STATUS_NONE = 0,\n    SYNC_STATUS_SYNCING,\n    SYNC_STATUS_ERROR,\n    SYNC_STATUS_IGNORED,\n    SYNC_STATUS_SYNCED,\n    SYNC_STATUS_PAUSED,\n    SYNC_STATUS_READONLY,\n    SYNC_STATUS_LOCKED,\n    SYNC_STATUS_LOCKED_BY_ME,\n    N_SYNC_STATUS,\n};\ntypedef enum _SyncStatus SyncStatus;\n\nstruct _SeafileSession;\n\nstruct _SeafSyncManager {\n    struct _SeafileSession   *seaf;\n\n    GHashTable *sync_infos;\n    int         n_running_tasks;\n    gboolean    commit_job_running;\n    int         sync_interval;\n\n    GHashTable *http_server_states;\n\n    /* Sent/recv bytes from all transfer tasks in this second.\n     * Since we have http and non-http tasks, sync manager is\n     * the only reasonable place to put these variables.\n     */\n    gint             sent_bytes;\n    gint             recv_bytes;\n    gint             last_sent_bytes;\n    gint             last_recv_bytes;\n    /* Upload/download rate limits. */\n    gint             upload_limit;\n    gint             download_limit;\n\n    SeafSyncManagerPriv *priv;\n};\n\nSeafSyncManager* seaf_sync_manager_new (struct _SeafileSession *seaf);\n\nint seaf_sync_manager_init (SeafSyncManager *mgr);\nint seaf_sync_manager_start (SeafSyncManager *mgr);\n\nint\nseaf_sync_manager_add_sync_task (SeafSyncManager *mgr,\n                                 const char *repo_id,\n                                 GError **error);\n\nvoid\nseaf_sync_manager_cancel_sync_task (SeafSyncManager *mgr,\n                                    const char *repo_id);\n\n\nSyncInfo *\nseaf_sync_manager_get_sync_info (SeafSyncManager *mgr,\n                                 const char *repo_id);\n\nint\nseaf_sync_manager_add_del_confirmation (SeafSyncManager *mgr,\n                                        const char *confirmation_id,\n                                        gboolean resync);\n\nint\nseaf_sync_manager_disable_auto_sync (SeafSyncManager *mgr);\n\nint\nseaf_sync_manager_enable_auto_sync (SeafSyncManager *mgr);\n\nint\nseaf_sync_manager_is_auto_sync_enabled (SeafSyncManager *mgr);\n\nconst char *\nsync_state_to_str (int state);\n\nvoid\nseaf_sync_manager_update_active_path (SeafSyncManager *mgr,\n                                      const char *repo_id,\n                                      const char *path,\n                                      int mode,\n                                      SyncStatus status,\n                                      gboolean refresh);\n\nvoid\nseaf_sync_manager_delete_active_path (SeafSyncManager *mgr,\n                                      const char *repo_id,\n                                      const char *path);\n\nchar *\nseaf_sync_manager_get_path_sync_status (SeafSyncManager *mgr,\n                                        const char *repo_id,\n                                        const char *path,\n                                        gboolean is_dir);\n\nchar *\nseaf_sync_manager_list_active_paths_json (SeafSyncManager *mgr);\n\nint\nseaf_sync_manager_active_paths_number (SeafSyncManager *mgr);\n\nvoid\nseaf_sync_manager_remove_active_path_info (SeafSyncManager *mgr, const char *repo_id);\n\n#ifdef WIN32\n/* Add to refresh queue */\nvoid\nseaf_sync_manager_add_refresh_path (SeafSyncManager *mgr, const char *path);\n\n/* Refresh immediately. */\nvoid\nseaf_sync_manager_refresh_path (SeafSyncManager *mgr, const char *path);\n#endif\n\nvoid\nseaf_sync_manager_set_task_error_code (SeafSyncManager *mgr,\n                                       const char *repo_id,\n                                       int error);\n\nvoid\nseaf_sync_manager_update_repo (SeafSyncManager *manager, SeafRepo *repo,\n                               const char *commit_id);\n\nvoid\nseaf_sync_manager_check_locks_and_folder_perms (SeafSyncManager *manager,\n                                                const char *server_url);\n#endif\n"
  },
  {
    "path": "daemon/sync-status-tree.c",
    "content": "#include \"common.h\"\n\n#include \"seafile-session.h\"\n\n#include \"sync-status-tree.h\"\n\n#include \"log.h\"\n\nstruct _SyncStatusDir {\n    GHashTable *dirents;        /* name -> dirent. */\n};\ntypedef struct _SyncStatusDir SyncStatusDir;\n\nstruct _SyncStatusDirent {\n    char *name;\n    int mode;\n    /* Only used for directories. */\n    SyncStatusDir *subdir;\n};\ntypedef struct _SyncStatusDirent SyncStatusDirent;\n\nstruct SyncStatusTree {\n    SyncStatusDir *root;\n    char *worktree;\n};\ntypedef struct SyncStatusTree SyncStatusTree;\n\nstatic void\nsync_status_dirent_free (SyncStatusDirent *dirent);\n\nstatic SyncStatusDir *\nsync_status_dir_new ()\n{\n    SyncStatusDir *dir = g_new0 (SyncStatusDir, 1);\n    dir->dirents = g_hash_table_new_full (g_str_hash, g_str_equal,\n                                          g_free,\n                                          (GDestroyNotify)sync_status_dirent_free);\n    return dir;\n}\n\nstatic void\nsync_status_dir_free (SyncStatusDir *dir)\n{\n    if (!dir)\n        return;\n    g_hash_table_destroy (dir->dirents);\n    g_free (dir);\n}\n\nstatic SyncStatusDirent *\nsync_status_dirent_new (const char *name, int mode)\n{\n    SyncStatusDirent *dirent = g_new0(SyncStatusDirent, 1);\n    dirent->name = g_strdup(name);\n    dirent->mode = mode;\n\n    if (S_ISDIR(mode))\n        dirent->subdir = sync_status_dir_new ();\n\n    return dirent;\n}\n\nstatic void\nsync_status_dirent_free (SyncStatusDirent *dirent)\n{\n    if (!dirent)\n        return;\n    g_free (dirent->name);\n    sync_status_dir_free (dirent->subdir);\n    g_free (dirent);\n}\n\nSyncStatusTree *\nsync_status_tree_new (const char *worktree)\n{\n    SyncStatusTree *tree = g_new0(SyncStatusTree, 1);\n    tree->root = sync_status_dir_new ();\n    tree->worktree = g_strdup(worktree);\n    return tree;\n}\n\n#if 0\n#ifdef WIN32\nstatic void\nrefresh_recursive (const char *basedir, SyncStatusDir *dir)\n{\n    GHashTableIter iter;\n    gpointer key, value;\n    char *dname, *path;\n    SyncStatusDirent *dirent;\n\n    g_hash_table_iter_init (&iter, dir->dirents);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        dname = key;\n        dirent = value;\n\n        path = g_strconcat(basedir, \"/\", dname, NULL);\n        seaf_sync_manager_add_refresh_path (seaf->sync_mgr, path);\n\n        if (S_ISDIR(dirent->mode))\n            refresh_recursive (path, dirent->subdir);\n\n        g_free (path);\n    }\n}\n#endif\n#endif\t/* 0 */\n\nvoid\nsync_status_tree_free (struct SyncStatusTree *tree)\n{\n    if (!tree)\n        return;\n\n#ifdef WIN32\n    /* refresh_recursive (tree->worktree, tree->root); */\n#endif\n    /* Free the tree recursively. */\n    sync_status_dir_free (tree->root);\n\n    g_free (tree->worktree);\n    g_free (tree);\n}\n\nvoid\nsync_status_tree_add (SyncStatusTree *tree,\n                      const char *path,\n                      int mode,\n                      gboolean refresh)\n{\n    char **dnames = NULL;\n    guint n, i;\n    char *dname;\n    SyncStatusDir *dir = tree->root;\n    SyncStatusDirent *dirent;\n    GString *buf;\n\n    dnames = g_strsplit (path, \"/\", 0);\n    if (!dnames)\n        return;\n    n = g_strv_length (dnames);\n\n    buf = g_string_new (\"\");\n    g_string_append (buf, tree->worktree);\n\n    for (i = 0; i < n; i++) {\n        dname = dnames[i];\n        dirent = g_hash_table_lookup (dir->dirents, dname);\n        g_string_append (buf, \"/\");\n        g_string_append (buf, dname);\n        if (dirent) {\n            if (S_ISDIR(dirent->mode)) {\n                if (i == (n-1)) {\n                    goto out;\n                } else {\n                    dir = dirent->subdir;\n                }\n            } else {\n                goto out;\n            }\n        } else {\n            if (i == (n-1)) {\n                dirent = sync_status_dirent_new (dname, mode);\n                g_hash_table_insert (dir->dirents, g_strdup(dname), dirent);\n            } else {\n                dirent = sync_status_dirent_new (dname, S_IFDIR);\n                g_hash_table_insert (dir->dirents, g_strdup(dname), dirent);\n                dir = dirent->subdir;\n            }\n#ifdef WIN32\n            if (refresh)\n                seaf_sync_manager_add_refresh_path (seaf->sync_mgr, buf->str);\n#endif\n        }\n    }\n\nout:\n    g_string_free (buf, TRUE);\n    g_strfreev (dnames);\n}\n\ninline static gboolean\nis_empty_dir (SyncStatusDirent *dirent)\n{\n    return (g_hash_table_size(dirent->subdir->dirents) == 0);\n}\n\nstatic void\nremove_item (SyncStatusDir *dir, const char *dname, const char *fullpath)\n{\n    g_hash_table_remove (dir->dirents, dname);\n}\n\nstatic void\ndelete_recursive (SyncStatusDir *dir, char **dnames, guint n, guint i,\n                  const char *base)\n{\n    char *dname;\n    SyncStatusDirent *dirent;\n    char *fullpath = NULL;\n\n    dname = dnames[i];\n    fullpath = g_strconcat (base, \"/\", dname, NULL);\n\n    dirent = g_hash_table_lookup (dir->dirents, dname);\n    if (dirent) {\n        if (S_ISDIR(dirent->mode)) {\n            if (i == (n-1)) {\n                if (is_empty_dir(dirent))\n                    remove_item (dir, dname, fullpath);\n            } else {\n                delete_recursive (dirent->subdir, dnames, n, ++i, fullpath);\n                /* If this dir becomes empty after deleting the entry below,\n                 * remove the dir itself too.\n                 */\n                if (is_empty_dir(dirent))\n                    remove_item (dir, dname, fullpath);\n            }\n        } else if (i == (n-1)) {\n            remove_item (dir, dname, fullpath);\n        }\n    }\n\n    g_free (fullpath);\n}\n\nvoid\nsync_status_tree_del (SyncStatusTree *tree,\n                      const char *path)\n{\n    char **dnames = NULL;\n    guint n;\n    SyncStatusDir *dir = tree->root;\n\n    dnames = g_strsplit (path, \"/\", 0);\n    if (!dnames)\n        return;\n    n = g_strv_length (dnames);\n\n    delete_recursive (dir, dnames, n, 0, tree->worktree);\n\n    g_strfreev (dnames);\n}\n\nint\nsync_status_tree_exists (SyncStatusTree *tree,\n                         const char *path)\n{\n    char **dnames = NULL;\n    guint n, i;\n    char *dname;\n    SyncStatusDir *dir = tree->root;\n    SyncStatusDirent *dirent;\n    int ret = 0;\n\n    dnames = g_strsplit (path, \"/\", 0);\n    if (!dnames)\n        return ret;\n    n = g_strv_length (dnames);\n\n    for (i = 0; i < n; i++) {\n        dname = dnames[i];\n        dirent = g_hash_table_lookup (dir->dirents, dname);\n        if (dirent) {\n            if (S_ISDIR(dirent->mode)) {\n                if (i == (n-1)) {\n                    ret = 1;\n                    goto out;\n                } else {\n                    dir = dirent->subdir;\n                }\n            } else {\n                if (i == (n-1)) {\n                    ret = 1;\n                    goto out;\n                } else {\n                    goto out;\n                }\n            }\n        } else {\n            goto out;\n        }\n    }\n\nout:\n    g_strfreev (dnames);\n    return ret;\n}\n"
  },
  {
    "path": "daemon/sync-status-tree.h",
    "content": "#ifndef SYNC_STATUS_TREE_H\n#define SYNC_STATUS_TREE_H\n\nstruct SyncStatusTree;\n\nstruct SyncStatusTree *\nsync_status_tree_new (const char *worktree);\n\nvoid\nsync_status_tree_free (struct SyncStatusTree *tree);\n\n/*\n * Add a @path into the @tree. If any directory along the path is missing,\n * it will be created. If the path already exists, it won't be overwritten.\n */\nvoid\nsync_status_tree_add (struct SyncStatusTree *tree,\n                      const char *path,\n                      int mode,\n                      gboolean refresh);\n\n/*\n * Delete a path from the tree. If directory becomes empty after the deletion,\n * it will be deleted too. All empty direcotries along the path will be deleted.\n */\nvoid\nsync_status_tree_del (struct SyncStatusTree *tree,\n                      const char *path);\n\nint\nsync_status_tree_exists (struct SyncStatusTree *tree,\n                         const char *path);\n\n#endif\n"
  },
  {
    "path": "daemon/test-sync-tree.c",
    "content": "#include <stdio.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n\n#include \"sync-status-tree.h\"\n\nint main (int argc, char **argv)\n{\n    struct SyncStatusTree *tree;\n    int val;\n\n    tree = sync_status_tree_new ();\n\n    sync_status_tree_add (tree, \"a/b/c.txt\", S_IFREG);\n    sync_status_tree_add (tree, \"a/b/c/d\", S_IFDIR);\n    sync_status_tree_add (tree, \"a/xxx.txt\", S_IFREG);\n\n    printf (\"test after add\\n\");\n\n    val = sync_status_tree_exists (tree, \"a/b/c.txt\");\n    printf (\"a/b/c.txt: %d\\n\", val);\n\n    val = sync_status_tree_exists (tree, \"a/b/c/d\");\n    printf (\"a/b/c/d: %d\\n\", val);\n\n    val = sync_status_tree_exists (tree, \"a/d/f.foo\");\n    printf (\"a/d/f.foo: %d\\n\", val);\n\n    val = sync_status_tree_exists (tree, \"a/b\");\n    printf (\"a/b: %d\\n\", val);\n\n    sync_status_tree_del (tree, \"a/b/c.txt\");\n    sync_status_tree_del (tree, \"a/b/c/d\");\n    sync_status_tree_del (tree, \"a/xxx.txt\");\n    sync_status_tree_del (tree, \"a/c.pdf\");\n\n    printf (\"test after del\\n\");\n\n    val = sync_status_tree_exists (tree, \"a/b/c.txt\");\n    printf (\"a/b/c.txt: %d\\n\", val);\n\n    val = sync_status_tree_exists (tree, \"a/b/c/d\");\n    printf (\"a/b/c/d: %d\\n\", val);\n\n    val = sync_status_tree_exists (tree, \"a/b\");\n    printf (\"a/b: %d\\n\", val);\n\n    val = sync_status_tree_exists (tree, \"a\");\n    printf (\"a: %d\\n\", val);\n}\n"
  },
  {
    "path": "daemon/timer.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)\n#include <event2/event.h>\n#include <event2/event_compat.h>\n#include <event2/event_struct.h>\n#else\n#include <event.h>\n#endif\n\n#ifndef WIN32\n#include <sys/time.h>\n#endif\n\n#include \"seafile-session.h\"\n\n#include \"utils.h\"\n\n#include \"timer.h\"\n\nstruct SeafTimer\n{\n    struct event   *event;\n    struct timeval tv;\n    TimerCB        func;\n    void          *user_data;\n    uint8_t        in_callback;\n};\n\nstatic void\ntimer_callback (evutil_socket_t fd, short event, void *vtimer)\n{\n    int more;\n    struct SeafTimer *timer = vtimer;\n\n    timer->in_callback = 1;\n    more = (*timer->func) (timer->user_data);\n    timer->in_callback = 0;\n\n    if (more)\n        evtimer_add (timer->event, &timer->tv);\n    else\n        seaf_timer_free (&timer);\n}\n\nvoid\nseaf_timer_free (SeafTimer **ptimer)\n{\n    SeafTimer *timer;\n\n    /* zero out the argument passed in */\n    g_return_if_fail (ptimer);\n\n    timer = *ptimer;\n    *ptimer = NULL;\n\n    /* destroy the timer directly or via the command queue */\n    if (timer && !timer->in_callback)\n    {\n        event_del (timer->event);\n        event_free (timer->event);\n        g_free (timer);\n    }\n}\n\nstruct timeval\ntimeval_from_msec (uint64_t milliseconds)\n{\n    struct timeval ret;\n    const uint64_t microseconds = milliseconds * 1000;\n    ret.tv_sec  = microseconds / 1000000;\n    ret.tv_usec = microseconds % 1000000;\n    return ret;\n}\n\nSeafTimer*\nseaf_timer_new (TimerCB         func,\n                void           *user_data,\n                uint64_t        interval_milliseconds)\n{\n    SeafTimer *timer = g_new0 (SeafTimer, 1);\n\n    timer->tv = timeval_from_msec (interval_milliseconds);\n    timer->func = func;\n    timer->user_data = user_data;\n\n    timer->event = evtimer_new (seaf->ev_base, timer_callback, timer);\n    if (timer->event == NULL) {\n        return NULL;\n    }\n    evtimer_add (timer->event, &timer->tv);\n\n    return timer;\n}\n"
  },
  {
    "path": "daemon/timer.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef SEAF_TIMER_H\n#define SEAF_TIMER_H\n\n/* return TRUE to reschedule the timer, return FALSE to cancle the timer */\ntypedef int (*TimerCB) (void *data);\n\nstruct SeafTimer;\n\ntypedef struct SeafTimer SeafTimer;\n\n/**\n * Calls timer_func(user_data) after the specified interval.\n * The timer is freed if timer_func returns zero.\n * Otherwise, it's called again after the same interval.\n */\nSeafTimer* seaf_timer_new (TimerCB           func,\n                           void             *user_data,\n                           uint64_t          timeout_milliseconds);\n\n/**\n * Frees a timer and sets the timer pointer to NULL.\n */\nvoid seaf_timer_free (SeafTimer **timer);\n\n\n#endif\n"
  },
  {
    "path": "daemon/vc-utils.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n#define DEBUG_FLAG SEAFILE_DEBUG_MERGE\n#include \"log.h\"\n#include \"seafile-error.h\"\n\n#include <stdlib.h>\n#include <sys/stat.h>\n#include <glib/gstdio.h>\n\n#include \"seafile-session.h\"\n\n#include \"utils.h\"\n#include \"fs-mgr.h\"\n#include \"vc-utils.h\"\n#include \"vc-common.h\"\n#include \"index/index.h\"\n\nstatic gint\ncompare_dirents (gconstpointer a, gconstpointer b)\n{\n    const SeafDirent *denta = a, *dentb = b;\n\n    return strcmp (dentb->name, denta->name);\n}\n\nint\ncommit_trees_cb (const char *repo_id, int version,\n                 const char *worktree,\n                 struct cache_tree *it, struct cache_entry **cache,\n                 int entries, const char *base, int baselen)\n{\n    SeafDir *seaf_dir;\n    GList *dirents = NULL, *ptr;\n    int i;\n\n    for (i = 0; i < entries; i++) {\n        SeafDirent *seaf_dent;\n        char *name;\n        struct cache_entry *ce = cache[i];\n        struct cache_tree_sub *sub;\n        const char *path, *slash;\n        int pathlen, entlen;\n        const unsigned char *sha1;\n        char hex[41];\n        unsigned mode;\n        guint64 mtime;\n        gint64 size;\n        char *modifier;\n\n        if (ce->ce_flags & CE_REMOVE)\n            continue; /* entry being removed */\n\n        path = ce->name;\n        pathlen = ce_namelen(ce);\n        if (pathlen <= baselen || memcmp(base, path, baselen))\n            break; /* at the end of this level */\n\n        slash = strchr(path + baselen, '/');\n        if (slash) {\n            entlen = slash - (path + baselen);\n            sub = cache_tree_find_subtree(it, path + baselen, entlen, 0);\n            g_return_val_if_fail (sub != NULL, -1);\n            /* Skip cache entries in the sub level. */\n            i += sub->cache_tree->entry_count - 1;\n\n            sha1 = sub->cache_tree->sha1;\n            mtime = sub->cache_tree->mtime;\n            mode = S_IFDIR;\n            name = g_strndup(path + baselen, entlen);\n\n            rawdata_to_hex (sha1, hex, 20);\n            seaf_dent = seaf_dirent_new (dir_version_from_repo_version(version),\n                                         hex, mode, name, mtime, NULL, -1);\n            g_free(name);\n\n            dirents = g_list_prepend (dirents, seaf_dent);\n        } else {\n            sha1 = ce->sha1;\n            mode = ce->ce_mode;\n            mtime = ce->ce_mtime.sec;\n            size = ce->ce_size;\n            modifier = ce->modifier;\n            entlen = pathlen - baselen;\n            name = g_strndup(path + baselen, entlen);\n            rawdata_to_hex (sha1, hex, 20);\n\n            if (version > 0) {\n                seaf_dent =\n                    seaf_dirent_new (dir_version_from_repo_version(version),\n                                     hex, mode, name, mtime, modifier, size);\n            } else {\n                seaf_dent = seaf_dirent_new (0, hex, mode, name, 0, NULL, -1);\n            }\n\n            g_free(name);\n\n            dirents = g_list_prepend (dirents, seaf_dent);\n        }\n\n#if DEBUG\n        fprintf(stderr, \"cache-tree update-one %o %.*s\\n\",\n                mode, entlen, path + baselen);\n#endif\n    }\n\n    /* Sort dirents in descending order. */\n    dirents = g_list_sort (dirents, compare_dirents);\n\n    seaf_dir = seaf_dir_new (NULL, dirents, dir_version_from_repo_version(version));\n    hex_to_rawdata (seaf_dir->dir_id, it->sha1, 20);\n\n    /* Dir's mtime is the latest of all dir entires. */\n    guint64 dir_mtime = 0;\n    SeafDirent *dent;\n    for (ptr = dirents; ptr; ptr = ptr->next) {\n        dent = ptr->data;\n        if (dent->mtime > dir_mtime)\n            dir_mtime = dent->mtime;\n    }\n    it->mtime = dir_mtime;\n\n    if (!seaf_fs_manager_object_exists (seaf->fs_mgr,\n                                        repo_id, version,\n                                        seaf_dir->dir_id))\n        seaf_dir_save (seaf->fs_mgr, repo_id, version, seaf_dir);\n\n#if DEBUG\n    for (p = dirents; p; p = p->next) {\n        SeafDirent *tmp = (SeafDirent *)p->data;\n        fprintf(stderr, \"dump dirent name %s id %s\\n\", tmp->name, tmp->id);\n    }\n#endif\n\n    seaf_dir_free (seaf_dir);\n    return 0;\n}\n\nint\nupdate_index (struct index_state *istate, const char *index_path)\n{\n    char index_shadow[SEAF_PATH_MAX];\n    int index_fd;\n    int ret = 0;\n\n    snprintf (index_shadow, SEAF_PATH_MAX, \"%s.shadow\", index_path);\n    index_fd = seaf_util_create (index_shadow, O_RDWR | O_CREAT | O_TRUNC | O_BINARY,\n                                 0666);\n    if (index_fd < 0) {\n        seaf_warning (\"Failed to open shadow index: %s.\\n\", strerror(errno));\n        return -1;\n    }\n\n    if (write_index (istate, index_fd) < 0) {\n        seaf_warning (\"Failed to write shadow index: %s.\\n\", strerror(errno));\n        close (index_fd);\n        return -1;\n    }\n    close (index_fd);\n\n    ret = seaf_util_rename (index_shadow, index_path);\n    if (ret < 0) {\n        seaf_warning (\"Failed to update index errno=%d %s\\n\", errno, strerror(errno));\n        return -1;\n    }\n    return 0;\n}\n\n#ifndef WIN32\n\nint\nseaf_remove_empty_dir (const char *path)\n{\n    SeafStat st;\n    GDir *dir;\n    const char *dname;\n    char *full_path;\n    GError *error = NULL;\n\n    if (seaf_stat (path, &st) < 0 || !S_ISDIR(st.st_mode))\n        return 0;\n\n    if (seaf_util_rmdir (path) < 0) {\n        dir = g_dir_open (path, 0, &error);\n        if (!dir) {\n            seaf_warning (\"Failed to open dir %s: %s.\\n\", path, error->message);\n            return -1;\n        }\n\n        /* Remove all ignored hidden files. */\n        while ((dname = g_dir_read_name (dir)) != NULL) {\n            if (seaf_repo_manager_is_ignored_hidden_file(dname)) {\n                full_path = g_build_path (\"/\", path, dname, NULL);\n                if (seaf_util_unlink (full_path) < 0)\n                    seaf_warning (\"Failed to remove file %s: %s.\\n\",\n                                  full_path, strerror(errno));\n                g_free (full_path);\n            }\n        }\n\n        g_dir_close (dir);\n\n        if (seaf_util_rmdir (path) < 0) {\n            seaf_warning (\"Failed to remove dir %s: %s.\\n\", path, strerror(errno));\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n#else\n\nstatic int\nremove_hidden_file (wchar_t *parent, WIN32_FIND_DATAW *fdata,\n                    void *user_data, gboolean *stop)\n{\n    char *dname = NULL;\n    wchar_t *subpath_w = NULL;\n    char *subpath = NULL;\n\n    dname = g_utf16_to_utf8 (fdata->cFileName, -1, NULL, NULL, NULL);\n\n    if (!(fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&\n        (!dname || seaf_repo_manager_is_ignored_hidden_file(dname))) {\n        subpath_w = g_new0 (wchar_t, wcslen(parent) + wcslen(fdata->cFileName) + 2);\n        wcscpy (subpath_w, parent);\n        wcscat (subpath_w, L\"\\\\\");\n        wcscat (subpath_w, fdata->cFileName);\n\n        if (!DeleteFileW (subpath_w)) {\n            subpath = g_utf16_to_utf8 (subpath_w, -1, NULL, NULL, NULL);\n            seaf_warning (\"Failed to remove file %s: %lu.\\n\", subpath, GetLastError());\n            g_free (subpath);\n        }\n\n        g_free (subpath_w);\n    }\n\n    g_free (dname);\n    return 0;\n}\n\nint\nseaf_remove_empty_dir (const char *path)\n{\n    wchar_t *path_w = NULL;\n    WIN32_FILE_ATTRIBUTE_DATA attrs;\n    int ret = 0;\n\n    path_w = win32_long_path (path);\n\n    if (!GetFileAttributesExW (path_w, GetFileExInfoStandard, &attrs)) {\n        goto out;\n    }\n\n    if (!(attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {\n        goto out;\n    }\n\n    if (RemoveDirectoryW (path_w))\n        goto out;\n\n    if (GetLastError() == ERROR_DIR_NOT_EMPTY) {\n        traverse_directory_win32 (path_w, remove_hidden_file, NULL);\n        if (!RemoveDirectoryW (path_w)) {\n            seaf_warning (\"Failed to remove dir %s: %lu.\\n\", path, GetLastError());\n            ret = -1;\n        }\n    } else {\n        seaf_warning (\"Failed to remove dir %s: %lu.\\n\", path, GetLastError());\n        ret = -1;\n    }\n\nout:\n    g_free (path_w);\n    return ret;\n}\n\n#endif  /* WIN32 */\n\nstatic int\ncompute_file_id_with_cdc (const char *path, SeafStat *st,\n                          SeafileCrypt *crypt, int repo_version,\n                          uint32_t blk_avg_size, uint32_t blk_min_size, uint32_t blk_max_size,\n                          unsigned char sha1[])\n{\n    CDCFileDescriptor cdc;\n\n    memset (&cdc, 0, sizeof(cdc));\n    cdc.block_sz = blk_avg_size;\n    cdc.block_min_sz = blk_min_size;\n    cdc.block_max_sz = blk_max_size;\n    cdc.write_block = seafile_write_chunk;\n    if (filename_chunk_cdc (path, &cdc, crypt, FALSE) < 0) {\n        seaf_warning (\"Failed to chunk file.\\n\");\n        return -1;\n    }\n\n    if (repo_version > 0)\n        seaf_fs_manager_calculate_seafile_id_json (repo_version, &cdc, sha1);\n    else\n        memcpy (sha1, cdc.file_sum, 20);\n\n    if (cdc.blk_sha1s)\n        free (cdc.blk_sha1s);\n\n    return 0;\n}\n\nint\ncompare_file_content (const char *path, SeafStat *st, const unsigned char *ce_sha1,\n                      SeafileCrypt *crypt, int repo_version)\n{\n    unsigned char sha1[20];\n\n    if (st->st_size == 0) {\n        memset (sha1, 0, 20);\n        return hashcmp (sha1, ce_sha1);\n    } else {\n        if (seaf->cdc_average_block_size == 0) {\n            if (compute_file_id_with_cdc (path, st, crypt, repo_version,\n                                          CDC_AVERAGE_BLOCK_SIZE,\n                                          CDC_MIN_BLOCK_SIZE,\n                                          CDC_MAX_BLOCK_SIZE,\n                                          sha1) < 0) {\n                return -1;\n            }\n        } else {\n            if (compute_file_id_with_cdc (path, st, crypt, repo_version,\n                                          seaf->cdc_average_block_size,\n                                          seaf->cdc_average_block_size >> 1,\n                                          seaf->cdc_average_block_size << 1,\n                                          sha1) < 0) {\n                return -1;\n            }            \n        }\n        if (hashcmp (sha1, ce_sha1) == 0)\n            return 0;\n\n        /* Compare with old cdc block size. */\n        uint32_t block_size = calculate_chunk_size (st->st_size);\n        if (compute_file_id_with_cdc (path, st, crypt, repo_version,\n                                      block_size,\n                                      block_size >> 2,\n                                      block_size << 2,\n                                      sha1) < 0) {\n            return -1;\n        }\n        return hashcmp (sha1, ce_sha1);\n    }\n}\n\nchar *\nbuild_checkout_path (const char *worktree, const char *ce_name, int len)\n{\n    int base_len = strlen(worktree);\n    int full_len;\n    int offset;\n    SeafStat st;\n\n    if (!len) {\n        seaf_warning (\"entry name should not be empty.\\n\");\n        return NULL;\n    }\n\n    GString *path = g_string_new (\"\");\n\n    g_string_append_printf (path, \"%s/\", worktree);\n\n    /* first create all leading directories. */\n    full_len = base_len + len + 1;\n    offset = base_len + 1;\n    while (offset < full_len) {\n        do {\n            g_string_append_c (path, ce_name[offset-base_len-1]);\n            offset++;\n        } while (offset < full_len && ce_name[offset-base_len-1] != '/');\n        if (offset >= full_len)\n            break;\n\n        if (seaf_stat (path->str, &st) == 0 && S_ISDIR(st.st_mode))\n            continue;\n        \n        if (seaf_util_mkdir (path->str, 0777) < 0) {\n            seaf_warning (\"Failed to create directory %s.\\n\", path->str);\n            g_string_free (path, TRUE);\n            return NULL;\n        }\n    }\n\n    return g_string_free (path, FALSE);\n}\n\nint\ndelete_path (const char *worktree, const char *name,\n             unsigned int mode, gint64 old_mtime)\n{\n    char path[SEAF_PATH_MAX];\n    SeafStat st;\n    int len = strlen(name);\n\n    if (!len) {\n        seaf_warning (\"entry name should not be empty.\\n\");\n        return -1;\n    }\n\n    snprintf (path, SEAF_PATH_MAX, \"%s/%s\", worktree, name);\n\n    if (!S_ISDIR(mode)) {\n        /* file doesn't exist in work tree */\n        if (seaf_stat (path, &st) < 0 || !S_ISREG(st.st_mode)) {\n            return 0;\n        }\n\n        /* file has been changed. */\n        if (!is_eml_file (name) && (old_mtime != st.st_mtime)) {\n            seaf_warning (\"File %s is changed. Skip removing the file.\\n\", path);\n            return -1;\n        }\n\n        /* first unlink the file. */\n        if (seaf_util_unlink (path) < 0) {\n            seaf_warning (\"Failed to remove %s: %s.\\n\", path, strerror(errno));\n            return -1;\n        }\n    } else {\n        if (seaf_remove_empty_dir (path) < 0) {\n            seaf_warning (\"Failed to remove dir %s: %s.\\n\", path, strerror(errno));\n            return -1;\n        }\n    }\n\n    /* then remove all empty directories upwards. */\n    /* offset = base_len + len; */\n    /* do { */\n    /*     if (path[offset] == '/') { */\n    /*         path[offset] = '\\0'; */\n    /*         int ret = seaf_remove_empty_dir (path); */\n    /*         if (ret < 0) { */\n    /*             break; */\n    /*         } */\n    /*     } */\n    /* } while (--offset > base_len); */\n\n    return 0;\n}\n\n#ifdef WIN32\n\nstatic gboolean\ncheck_file_locked (const wchar_t *path_w, gboolean locked_on_server)\n{\n    HANDLE handle;\n    /* If the file is locked on server, its local access right has been set to\n     * read-only. So trying to test GENERIC_WRITE access will certainly return\n     * ACCESS_DENIED. In this case, we can only test for GENERIC_READ.\n     * MS Office seems to gain exclusive read/write access to the file. So even\n     * trying read access can return a SHARING_VIOLATION error.\n     */\n    DWORD access_mode = locked_on_server ? GENERIC_READ : GENERIC_WRITE;\n\n    handle = CreateFileW (path_w,\n                          access_mode,\n                          0,\n                          NULL,\n                          OPEN_EXISTING,\n                          0,\n                          NULL);\n    if (handle != INVALID_HANDLE_VALUE) {\n        CloseHandle (handle);\n    } else if (GetLastError() == ERROR_SHARING_VIOLATION) {\n        return TRUE;\n    }\n\n    return FALSE;\n}\n\ngboolean\ndo_check_file_locked (const char *path, const char *worktree, gboolean locked_on_server)\n{\n    char *real_path;\n    wchar_t *real_path_w;\n    gboolean ret;\n    real_path = g_build_path(PATH_SEPERATOR, worktree, path, NULL);\n    real_path_w = win32_long_path (real_path);\n    ret = check_file_locked (real_path_w, locked_on_server);\n    g_free (real_path);\n    g_free (real_path_w);\n    return ret;\n}\n\nstatic gboolean\ncheck_dir_locked (const wchar_t *path_w)\n{\n    HANDLE handle;\n\n    handle = CreateFileW (path_w,\n                          GENERIC_WRITE,\n                          0,\n                          NULL,\n                          OPEN_EXISTING,\n                          FILE_FLAG_BACKUP_SEMANTICS,\n                          NULL);\n    if (handle != INVALID_HANDLE_VALUE) {\n        CloseHandle (handle);\n    } else if (GetLastError() == ERROR_SHARING_VIOLATION) {\n        return TRUE;\n    }\n\n    return FALSE;\n}\n\nstatic gboolean\ncheck_dir_locked_recursive (const wchar_t *path_w)\n{\n    WIN32_FIND_DATAW fdata;\n    HANDLE handle;\n    wchar_t *pattern;\n    wchar_t *sub_path_w;\n    char *path;\n    int path_len_w;\n    DWORD error;\n    gboolean ret = FALSE;\n\n    if (check_dir_locked (path_w))\n        return TRUE;\n\n    path = g_utf16_to_utf8 (path_w, -1, NULL, NULL, NULL);\n    if (!path)\n        return FALSE;\n\n    path_len_w = wcslen(path_w);\n\n    pattern = g_new0 (wchar_t, (path_len_w + 3));\n    wcscpy (pattern, path_w);\n    wcscat (pattern, L\"\\\\*\");\n\n    handle = FindFirstFileW (pattern, &fdata);\n    if (handle == INVALID_HANDLE_VALUE) {\n        seaf_warning (\"FindFirstFile failed %s: %lu.\\n\",\n                      path, GetLastError());\n        goto out;\n    }\n\n    do {\n        if (wcscmp (fdata.cFileName, L\".\") == 0 ||\n            wcscmp (fdata.cFileName, L\"..\") == 0)\n            continue;\n\n        sub_path_w = g_new0 (wchar_t, path_len_w + wcslen(fdata.cFileName) + 2);\n        wcscpy (sub_path_w, path_w);\n        wcscat (sub_path_w, L\"\\\\\");\n        wcscat (sub_path_w, fdata.cFileName);\n\n        if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {\n            if (check_dir_locked_recursive (sub_path_w)) {\n                ret = TRUE;\n                g_free (sub_path_w);\n                goto out;\n            }\n        } else {\n            if (check_file_locked (sub_path_w, FALSE)) {\n                ret = TRUE;\n                g_free (sub_path_w);\n                goto out;\n            }\n        }\n\n        g_free (sub_path_w);\n    } while (FindNextFileW (handle, &fdata) != 0);\n\n    error = GetLastError();\n    if (error != ERROR_NO_MORE_FILES) {\n        seaf_warning (\"FindNextFile failed %s: %lu.\\n\",\n                      path, error);\n    }\n\nout:\n    FindClose (handle);\n    g_free (path);\n    g_free (pattern);\n    return ret;\n}\n\ngboolean\ndo_check_dir_locked (const char *path, const char *worktree)\n{\n    char *real_path = g_build_path (PATH_SEPERATOR, worktree, path, NULL);\n    wchar_t *real_path_w = win32_long_path (real_path);\n    gboolean ret = check_dir_locked_recursive (real_path_w);\n    g_free (real_path);\n    g_free (real_path_w);\n    return ret;\n}\n\ngboolean\nfiles_locked_on_windows (struct index_state *index, const char *worktree)\n{\n    gboolean ret = FALSE;\n    int i, entries;\n    struct cache_entry *ce;\n\n    entries = index->cache_nr;\n    for (i = 0; i < entries; ++i) {\n        ce = index->cache[i];\n        if (ce_stage(ce)) {\n            int mask = 0;\n\n            mask |= 1 << (ce_stage(ce) - 1);\n            while (i < entries) {\n                struct cache_entry *nce = index->cache[i];\n\n                if (strcmp(ce->name, nce->name))\n                    break;\n\n                mask |= 1 << (ce_stage(nce) - 1);\n                i++;\n            }\n            i--;\n\n            /* Check unmerged cases that can potentially\n               update or remove current files in the worktree.\n            */\n            if (mask == 7 ||    /* both changed */\n                mask == 6 ||    /* both added */\n                mask == 3)      /* others removed */\n            {\n                if (do_check_file_locked (ce->name, worktree, FALSE)) {\n                    ret = TRUE;\n                    break;\n                }\n            }\n        } else if (ce->ce_flags & CE_UPDATE ||\n                   ce->ce_flags & CE_WT_REMOVE) {\n            if (do_check_file_locked (ce->name, worktree, FALSE)) {\n                ret = TRUE;\n                break;\n            }\n        }\n    }\n\n    return ret;\n}\n\n#endif  /* WIN32 */\n\n#ifdef __APPLE__\n\n/* On Mac, we only check whether an office file is opened and locked.\n * The detection is done by checking ~$* tmp file.\n */\ngboolean\ndo_check_file_locked (const char *path, const char *worktree, gboolean locked_on_server)\n{\n#define OFFICE_FILE_PATTERN \".*\\\\.(docx|xlsx|pptx|doc|xls|ppt|vsdx)\"\n    char *dir_name = NULL, *file_name = NULL;\n    char *fullpath = NULL;\n    char *tmp_name = NULL;\n    int ret = FALSE;\n\n    if (!g_regex_match_simple (OFFICE_FILE_PATTERN, path, 0, 0))\n        return FALSE;\n\n    dir_name = g_path_get_dirname (path);\n    if (strcmp (dir_name, \".\") == 0) {\n        g_free (dir_name);\n        dir_name = g_strdup(\"\");\n    }\n    file_name = g_path_get_basename (path);\n\n    tmp_name = g_strconcat (\"~$\", file_name, NULL);\n    fullpath = g_build_path (\"/\", worktree, dir_name, tmp_name, NULL);\n    if (g_file_test (fullpath, G_FILE_TEST_IS_REGULAR)) {\n        ret = TRUE;\n        goto out;\n    }\n\n    /* Sometimes the first two characters are replaced by ~$. */\n\n    if (g_utf8_strlen(file_name, -1) < 2)\n        goto out;\n\n    char *ptr = g_utf8_find_next_char(g_utf8_find_next_char (file_name, NULL), NULL);\n    g_free (tmp_name);\n    g_free (fullpath);\n    tmp_name = g_strconcat (\"~$\", ptr, NULL);\n    fullpath = g_build_path (\"/\", worktree, dir_name, tmp_name, NULL);\n    if (g_file_test (fullpath, G_FILE_TEST_IS_REGULAR)) {\n        ret = TRUE;\n        goto out;\n    }\n\nout:\n    g_free (fullpath);\n    g_free (tmp_name);\n    g_free (dir_name);\n    g_free (file_name);\n    return ret;\n}\n\n#endif\n"
  },
  {
    "path": "daemon/vc-utils.h",
    "content": "#ifndef VC_UTILS_H\n#define VC_UTILS_H\n\n#include <glib.h>\n\n#include \"index/index.h\"\n#include \"index/cache-tree.h\"\n#include \"fs-mgr.h\"\n\n#define PATH_SEPERATOR \"/\"\n\nstruct SeafileCrypt;\n\n#ifdef WIN32\n\nstatic inline int readlink(const char *path, char *buf, size_t bufsiz)\n{ errno = ENOSYS; return -1; }\n\n#endif\n\nint\ncommit_trees_cb (const char *repo_id, int version,\n                 const char *modifier,\n                 struct cache_tree *it, struct cache_entry **cache,\n                 int entries, const char *base, int baselen);\n\nint\nupdate_index (struct index_state *istate, const char *index_path);\n\nint\nseaf_remove_empty_dir (const char *path);\n\nchar *\nbuild_checkout_path (const char *worktree, const char *ce_name, int len);\n\nint\ndelete_path (const char *worktree, const char *name,\n             unsigned int mode, gint64 old_mtime);\n\ngboolean\ndo_check_file_locked (const char *path, const char *worktree, gboolean locked_on_server);\n\ngboolean\ndo_check_dir_locked (const char *path, const char *worktree);\n\ngboolean\nfiles_locked_on_windows (struct index_state *index, const char *worktree);\n\nint\ncompare_file_content (const char *path, SeafStat *st, \n                      const unsigned char *ce_sha1,\n                      struct SeafileCrypt *crypt,\n                      int repo_version);\n\n#endif\n"
  },
  {
    "path": "daemon/wt-monitor-linux.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n#include \"common.h\"\n\n#include <sys/select.h>\n#include <sys/inotify.h>\n#include <sys/ioctl.h>\n#include <dirent.h>\n\n#include <sys/time.h>\n#include <sys/types.h>\n#include <unistd.h>\n\n#include \"job-mgr.h\"\n#include \"seafile-session.h\"\n#include \"utils.h\"\n#include \"wt-monitor.h\"\n#define DEBUG_FLAG SEAFILE_DEBUG_WATCH\n#include \"log.h\"\n\ntypedef struct WatchPathMapping {\n    GHashTable *wd_to_path;     /* watch descriptor -> path */\n} WatchPathMapping;\n\ntypedef struct RenameInfo {\n    uint32_t last_cookie;\n    char *old_path;\n    gboolean processing;        /* Are we processing a rename event? */\n} RenameInfo;\n\ntypedef struct EventInfo {\n    int wd;\n    uint32_t mask;\n    uint32_t cookie;\n    char name[NAME_MAX];\n} EventInfo;\n\ntypedef struct RepoWatchInfo {\n    WTStatus *status;\n    WatchPathMapping *mapping;\n    RenameInfo *rename_info;\n    EventInfo last_event;\n    char *worktree;\n} RepoWatchInfo;\n\n#define WATCH_MASK IN_MODIFY | IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_CLOSE_WRITE | IN_ATTRIB\n\nstruct SeafWTMonitorPriv {\n    pthread_mutex_t hash_lock;\n    GHashTable *handle_hash;        /* repo_id -> inotify_fd */\n    GHashTable *info_hash;          /* inotify_fd -> RepoWatchInfo */\n    fd_set read_fds;\n    int maxfd;\n};\n\nstatic void *wt_monitor_job_linux (void *vmonitor);\n\nstatic void handle_watch_command (SeafWTMonitor *monitor, WatchCommand *cmd);\n\nstatic int\nadd_watch_recursive (RepoWatchInfo *info, int in_fd,\n                     const char *worktree, const char *path,\n                     gboolean add_events);\n\n/* WatchPathMapping */\n\nstatic WatchPathMapping *create_mapping ()\n{\n    WatchPathMapping *mapping;\n\n    mapping = g_new0 (WatchPathMapping, 1);\n    mapping->wd_to_path = g_hash_table_new_full (g_direct_hash, g_direct_equal,\n                                                 NULL, g_free);\n\n    return mapping;\n}\n\nstatic void free_mapping (WatchPathMapping *mapping)\n{\n    g_hash_table_destroy (mapping->wd_to_path);\n    g_free (mapping);\n}\n\nstatic void add_mapping (WatchPathMapping *mapping,\n                         const char *path,\n                         int wd)\n{\n    g_hash_table_insert (mapping->wd_to_path, (gpointer)(long)wd, g_strdup(path));\n}\n\n/* RenameInfo */\n\nstatic RenameInfo *create_rename_info ()\n{\n    RenameInfo *info = g_new0 (RenameInfo, 1);\n\n    return info;\n}\n\nstatic void free_rename_info (RenameInfo *info)\n{\n    g_free (info->old_path);\n    g_free (info);\n}\n\ninline static void\nset_rename_processing_state (RenameInfo *info, uint32_t cookie, const char *path)\n{\n    info->last_cookie = cookie;\n    info->old_path = g_strdup(path);\n    info->processing = TRUE;\n}\n\ninline static void\nunset_rename_processing_state (RenameInfo *info)\n{\n    info->last_cookie = 0;\n    g_free (info->old_path);\n    info->old_path = NULL;\n    info->processing = FALSE;\n}\n\n/* RepoWatchInfo */\n\nstatic RepoWatchInfo *\ncreate_repo_watch_info (const char *repo_id, const char *worktree)\n{\n    WTStatus *status = create_wt_status (repo_id);\n    WatchPathMapping *mapping = create_mapping ();\n    RenameInfo *rename_info = create_rename_info ();\n\n    RepoWatchInfo *info = g_new0 (RepoWatchInfo, 1);\n    info->status = status;\n    info->mapping = mapping;\n    info->rename_info = rename_info;\n    info->worktree = g_strdup(worktree);\n\n    return info;\n}\n\nstatic void\nfree_repo_watch_info (RepoWatchInfo *info)\n{\n    wt_status_unref (info->status);\n    free_mapping (info->mapping);\n    free_rename_info (info->rename_info);\n    g_free (info->worktree);\n    g_free (info);\n}\n\nstatic void\nadd_event_to_queue (WTStatus *status,\n                    int type, const char *path, const char *new_path)\n{\n    WTEvent *event = wt_event_new (type, path, new_path);\n\n    char *name;\n    switch (type) {\n    case WT_EVENT_CREATE_OR_UPDATE:\n        name = \"create/update\";\n        break;\n    case WT_EVENT_SCAN_DIR:\n        name = \"scan dir\";\n        break;\n    case WT_EVENT_DELETE:\n        name = \"delete\";\n        break;\n    case WT_EVENT_RENAME:\n        name = \"rename\";\n        break;\n    case WT_EVENT_OVERFLOW:\n        name = \"overflow\";\n        break;\n    case WT_EVENT_ATTRIB:\n        name = \"attribute change\";\n        break;\n    default:\n        name = \"unknown\";\n    }\n\n    seaf_debug (\"Adding event: %s, %s %s\\n\", name, path, new_path?new_path:\"\");\n\n    pthread_mutex_lock (&status->q_lock);\n    g_queue_push_tail (status->event_q, event);\n    pthread_mutex_unlock (&status->q_lock);\n\n    if (type == WT_EVENT_CREATE_OR_UPDATE) {\n        pthread_mutex_lock (&status->ap_q_lock);\n\n        char *last = g_queue_peek_tail (status->active_paths);\n        if (!last || strcmp(last, path) != 0)\n            g_queue_push_tail (status->active_paths, g_strdup(path));\n\n        pthread_mutex_unlock (&status->ap_q_lock);\n    }\n}\n\n/*\n * We only recognize two consecutive \"moved\" events with the same cookie as\n * a rename pair. The processing logic is:\n * 1. Receive a MOVED_FROM event, set last_cookie and old_path, set processing to TRUE\n * 2. If the next event is MOVED_TO, and with the same cookie, then add an\n *    WT_EVENT_RENAME event to the queue.\n * 3. Otherwise, recognize them as one delete event followed by one\n *    create event\n *\n * This is a two-state state machine. The states are 'not processing rename' and\n * 'processing rename'.\n */\nstatic void\nhandle_rename (int in_fd,\n               RepoWatchInfo *info,\n               struct inotify_event *event,\n               const char *worktree,\n               const char *filename,\n               gboolean last_event)\n{\n    WTStatus *status = info->status;\n    RenameInfo *rename_info = info->rename_info;\n\n    if (event->mask & IN_MOVED_FROM)\n        seaf_debug (\"(%d) Move %s ->\\n\", event->cookie, event->name);\n    else if (event->mask & IN_MOVED_TO)\n        seaf_debug (\"(%d) Move -> %s.\\n\", event->cookie, event->name);\n\n    if (!rename_info->processing) {\n        if (event->mask & IN_MOVED_FROM) {\n            if (!last_event) {\n                set_rename_processing_state (rename_info, event->cookie, filename);\n            } else {\n                /* Rename event pair should be in one batch of events.\n                 * If a MOVED_FROM event is the last event in a batch,\n                 * the path should be moved out of the repo.\n                 */\n                add_event_to_queue (status, WT_EVENT_DELETE, filename, NULL);\n            }\n        } else if (event->mask & IN_MOVED_TO) {\n            /* A file/dir was moved into this repo. */\n            /* Add watch and produce events. */\n            add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL);\n            add_watch_recursive (info, in_fd, worktree, filename, FALSE);\n        }\n    } else {\n        if (event->mask & IN_MOVED_FROM) {\n            /* A file/dir was moved out of this repo.\n             * Output the last MOVED_FROM event as DELETE event\n             */\n            add_event_to_queue (status, WT_EVENT_DELETE, rename_info->old_path, NULL);\n\n            if (!last_event) {\n                /* Stay in processing state. */\n                rename_info->last_cookie = event->cookie;\n                g_free (rename_info->old_path);\n                rename_info->old_path = g_strdup(filename);\n            } else {\n                /* Another file/dir was moved out of this repo. */\n                add_event_to_queue (status, WT_EVENT_DELETE, filename, NULL);\n                unset_rename_processing_state (rename_info);\n            }\n        } else if (event->mask & IN_MOVED_TO) {\n            if (event->cookie == rename_info->last_cookie) {\n                /* Rename pair detected. */\n                add_event_to_queue (status, WT_EVENT_RENAME,\n                                    rename_info->old_path, filename);\n            } else {\n                /* A file/dir was moved out of the repo, followed by\n                 * aother file/dir was moved into this repo.\n                 */\n                add_event_to_queue (status, WT_EVENT_DELETE,\n                                    rename_info->old_path, NULL);\n                add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE,\n                                    filename, NULL);\n            }\n            /* Need to update wd -> path mapping. */\n            add_watch_recursive (info, in_fd, worktree, filename, FALSE);\n            unset_rename_processing_state (rename_info);\n        } else {\n            /* A file/dir was moved out of this repo, followed by another\n             * file operations.\n             */\n            add_event_to_queue (status, WT_EVENT_DELETE, rename_info->old_path, NULL);\n            unset_rename_processing_state (rename_info);\n        }\n    }\n}\n\ninline static gboolean\nis_modify_close_write (EventInfo *e1, struct inotify_event *e2)\n{\n    return ((e1->mask & IN_MODIFY) && (e2->mask & IN_CLOSE_WRITE));\n}\n\n#if 0\nstatic gboolean\nhandle_consecutive_duplicate_event (RepoWatchInfo *info, struct inotify_event *event)\n{\n    gboolean duplicate;\n\n    /* Initially last_event is zero so it's not duplicate with any real events. */\n    duplicate = (info->last_event.wd == event->wd &&\n                 (info->last_event.mask == event->mask ||\n                  is_modify_close_write(&info->last_event, event)) &&\n                 info->last_event.cookie == event->cookie &&\n                 strcmp (info->last_event.name, event->name) == 0);\n\n    info->last_event.wd = event->wd;\n    info->last_event.mask = event->mask;\n    info->last_event.cookie = event->cookie;\n    memcpy (info->last_event.name, event->name, event->len);\n    info->last_event.name[event->len] = 0;\n\n    return duplicate;\n}\n#endif\n\nstatic void\nprocess_one_event (int in_fd,\n                   RepoWatchInfo *info,\n                   const char *worktree,\n                   const char *parent,\n                   struct inotify_event *event,\n                   gboolean last_event)\n{\n    WTStatus *status = info->status;\n    char *filename;\n    gboolean update_last_changed = TRUE;\n    gboolean add_to_queue = TRUE;\n\n    /* An inotfy watch has been removed, we don't care about this for now. */\n    if ((event->mask & IN_IGNORED) || (event->mask & IN_UNMOUNT))\n        return;\n\n    /* Kernel event queue was overflowed, some events may lost. */\n    if (event->mask & IN_Q_OVERFLOW) {\n        add_event_to_queue (status, WT_EVENT_OVERFLOW, NULL, NULL);\n        return;\n    }\n\n    /* if (handle_consecutive_duplicate_event (info, event)) */\n    /*     add_to_queue = FALSE; */\n\n    if (event->len == 0) {\n        filename = g_strdup (parent);\n    } else {\n        filename = g_build_filename (parent, event->name, NULL);\n    }\n\n    handle_rename (in_fd, info, event, worktree, filename, last_event);\n\n    if (event->mask & IN_MODIFY) {\n        seaf_debug (\"Modified %s.\\n\", filename);\n        if (add_to_queue)\n            add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL);\n    } else if (event->mask & IN_CREATE) {\n        seaf_debug (\"Created %s.\\n\", filename);\n\n        /* Nautilus's file copy operation doesn't trigger write events.\n         * If the user copy a large file into the repo, only a create\n         * event and a close_write event will be received. If we process\n         * the create event, we'll certainly try to index a file when it's\n         * still being copied. So we'll ignore create event for files.\n         * Since write and close_write events will always be triggered,\n         * we don't need to worry about missing this file.\n         */\n        char *fullpath = g_build_filename (worktree, filename, NULL);\n        struct stat st;\n        if (lstat (fullpath, &st) < 0 ||\n            (!S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode))) {\n            g_free (fullpath);\n            update_last_changed = FALSE;\n            goto out;\n        }\n        g_free (fullpath);\n\n        /* We now know it's a directory or a symlink. */\n\n        /* Files or dirs could have been added under this dir before we\n         * watch it. So it's safer to scan this dir. At most time we don't\n         * have to scan recursively and very few new files will be found.\n         */\n        add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL);\n        add_watch_recursive (info, in_fd, worktree, filename, FALSE);\n    } else if (event->mask & IN_DELETE) {\n        seaf_debug (\"Deleted %s.\\n\", filename);\n        add_event_to_queue (status, WT_EVENT_DELETE, filename, NULL);\n    } else if (event->mask & IN_CLOSE_WRITE) {\n        seaf_debug (\"Close write %s.\\n\", filename);\n        if (add_to_queue)\n            add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL);\n    } else if (event->mask & IN_ATTRIB) {\n        seaf_debug (\"Attribute changed %s.\\n\", filename);\n        add_event_to_queue (status, WT_EVENT_ATTRIB, filename, NULL);\n    }\n\nout:\n    g_free (filename);\n    if (update_last_changed)\n        g_atomic_int_set (&info->status->last_changed, (gint)time(NULL));\n}\n\nstatic gboolean\nprocess_events (SeafWTMonitorPriv *priv, const char *repo_id, int in_fd)\n{\n    char *event_buf = NULL;\n    unsigned int buf_size;\n    struct inotify_event *event;\n    RepoWatchInfo *info;\n    int rc, n;\n    char *dir;\n    gboolean ret = FALSE;\n\n    rc = ioctl (in_fd, FIONREAD, &buf_size);\n    if (rc < 0) {\n        seaf_warning (\"Cannot get inotify event buf size: %s.\\n\", strerror(errno));\n        return FALSE;\n    }\n    event_buf = g_new (char, buf_size);\n\n    n = readn (in_fd, event_buf, buf_size);\n    if (n < 0) {\n        seaf_warning (\"Failed to read inotify fd: %s.\\n\", strerror(errno));\n        goto out;\n    } else if (n != buf_size) {\n        seaf_warning (\"Read incomplete inotify event struct.\\n\");\n        goto out;\n    }\n\n    info = g_hash_table_lookup (priv->info_hash, (gpointer)(long)in_fd);\n    if (!info) {\n        seaf_warning (\"Repo watch info not found.\\n\");\n        goto out;\n    }\n\n    int offset = 0;\n    while (offset < buf_size) {\n        event = (struct inotify_event *)&event_buf[offset];\n        offset += sizeof(struct inotify_event) + event->len;\n\n        dir = g_hash_table_lookup (info->mapping->wd_to_path,\n                                   (gpointer)(long)event->wd);\n        if (!dir) {\n            seaf_warning (\"Cannot find path from wd.\\n\");\n            goto out;\n        }\n\n        process_one_event (in_fd, info, info->worktree, dir,\n                           event, (offset >= buf_size));\n    }\n\n    ret = TRUE;\n\nout:\n    g_free (event_buf);\n    return ret;\n}\n\nstatic void *\nwt_monitor_job_linux (void *vmonitor)\n{\n    SeafWTMonitor *monitor = vmonitor;\n    SeafWTMonitorPriv *priv = monitor->priv;\n\n    WatchCommand cmd;\n    int n;\n    int rc;\n    fd_set fds;\n    int inotify_fd;\n    char *repo_id;\n    gpointer key, value;\n    GHashTableIter iter;\n\n    FD_SET (monitor->cmd_pipe[0], &priv->read_fds);\n    priv->maxfd = monitor->cmd_pipe[0];\n\n    while (1) {\n        fds = priv->read_fds;\n\n        rc = select (priv->maxfd + 1, &fds, NULL, NULL, NULL);\n        if (rc < 0 && errno == EINTR) {\n            continue;\n        } else if (rc < 0) {\n            seaf_warning (\"[wt mon] select error: %s.\\n\", strerror(errno));\n            break;\n        }\n\n        if (FD_ISSET (monitor->cmd_pipe[0], &fds)) {\n            n = seaf_pipe_readn (monitor->cmd_pipe[0], &cmd, sizeof(cmd));\n            if (n != sizeof(cmd)) {\n                seaf_warning (\"[wt mon] failed to read command.\\n\");\n                continue;\n            }\n            handle_watch_command (monitor, &cmd);\n        }\n\n        g_hash_table_iter_init (&iter, priv->handle_hash);\n        while (g_hash_table_iter_next (&iter, &key, &value)) {\n            repo_id = key;\n            inotify_fd = (int)(long)value;\n            if (FD_ISSET (inotify_fd, &fds))\n                process_events (priv, repo_id, inotify_fd);\n        }\n    }\n\n    return NULL;\n}\n\n/* Ignore errors so that we can still monitor other dirs\n * when one dir is bad.\n *\n * If @add_events is TRUE, add events for each dir and entries under that dir.\n * Note that only adding events for files is not enough, because repo-mgr will\n * need to add empty dirs to index.\n */\nstatic int\nadd_watch_recursive (RepoWatchInfo *info,\n                     int in_fd,\n                     const char *worktree,\n                     const char *path,\n                     gboolean add_events)\n{\n    char *full_path;\n    SeafStat st;\n    DIR *dir;\n    struct dirent *dent;\n    int wd;\n\n    full_path = g_build_filename (worktree, path, NULL);\n\n    if (stat (full_path, &st) < 0) {\n        seaf_warning (\"[wt mon] fail to stat %s: %s\\n\", full_path, strerror(errno));\n        goto out;\n    }\n\n    if (add_events && path[0] != 0)\n        add_event_to_queue (info->status, WT_EVENT_CREATE_OR_UPDATE,\n                            path, NULL);\n\n    if (S_ISDIR (st.st_mode)) {\n        seaf_debug (\"Watching %s.\\n\", full_path);\n\n        wd = inotify_add_watch (in_fd, full_path, (uint32_t)WATCH_MASK);\n        if (wd < 0) {\n            seaf_warning (\"[wt mon] fail to add watch to %s: %s.\\n\",\n                          full_path, strerror(errno));\n            goto out;\n        }\n\n        add_mapping (info->mapping, path, wd);\n\n        dir = opendir (full_path);\n        if (!dir) {\n            seaf_warning (\"[wt mon] fail to open dir %s: %s.\\n\",\n                          full_path, strerror(errno));\n            goto out;\n        }\n\n        while (1) {\n            dent = readdir (dir);\n            if (!dent)\n                break;\n            if (strcmp (dent->d_name, \".\") == 0 ||\n                strcmp (dent->d_name, \"..\") == 0)\n                continue;\n\n            char *sub_path = g_build_filename (path, dent->d_name, NULL);\n\n            /* Check d_type to avoid stating every files under this dir.\n             * Note that d_type may not be supported in some file systems,\n             * in this case DT_UNKNOWN is returned.\n             */\n            if (dent->d_type == DT_DIR || dent->d_type == DT_LNK ||\n                dent->d_type == DT_UNKNOWN)\n                add_watch_recursive (info, in_fd, worktree, sub_path, add_events);\n\n            if (dent->d_type == DT_REG && add_events)\n                add_event_to_queue (info->status, WT_EVENT_CREATE_OR_UPDATE,\n                                    sub_path, NULL);\n            g_free (sub_path);\n        }\n\n        closedir (dir);\n    }\n\nout:\n    g_free (full_path);\n    return 0;\n}\n\nstatic int\nadd_watch (SeafWTMonitorPriv *priv, const char *repo_id, const char *worktree)\n{\n    int inotify_fd;\n    RepoWatchInfo *info;\n\n    inotify_fd = inotify_init ();\n    if (inotify_fd < 0) {\n        seaf_warning (\"[wt mon] inotify_init failed: %s.\\n\", strerror(errno));\n        return -1;\n    }\n\n    pthread_mutex_lock (&priv->hash_lock);\n    g_hash_table_insert (priv->handle_hash,\n                         g_strdup(repo_id), (gpointer)(long)inotify_fd);\n\n    info = create_repo_watch_info (repo_id, worktree);\n    g_hash_table_insert (priv->info_hash, (gpointer)(long)inotify_fd, info);\n    pthread_mutex_unlock (&priv->hash_lock);\n\n    if (add_watch_recursive (info, inotify_fd, worktree, \"\", FALSE) < 0) {\n        close (inotify_fd);\n        pthread_mutex_lock (&priv->hash_lock);\n        g_hash_table_remove (priv->handle_hash, repo_id);\n        g_hash_table_remove (priv->info_hash, (gpointer)(long)inotify_fd);\n        pthread_mutex_unlock (&priv->hash_lock);\n        return -1;\n    }\n\n    /* A special event indicates repo-mgr to scan the whole worktree. */\n    add_event_to_queue (info->status, WT_EVENT_SCAN_DIR, \"\", NULL);\n\n    return inotify_fd;\n}\n\nstatic int handle_add_repo (SeafWTMonitorPriv *priv,\n                            const char *repo_id,\n                            const char *worktree)\n{\n    int inotify_fd;\n\n    inotify_fd = add_watch (priv, repo_id, worktree);\n    if (inotify_fd < 0) {\n        return -1;\n    }\n\n    FD_SET (inotify_fd, &priv->read_fds);\n    priv->maxfd = MAX (inotify_fd, priv->maxfd);\n    return 0;\n}\n\nstatic void\nupdate_maxfd (SeafWTMonitor *monitor)\n{\n    SeafWTMonitorPriv *priv = monitor->priv;\n    GHashTableIter iter;\n    gpointer key, value;\n    int fd, maxfd = monitor->cmd_pipe[0];\n\n    g_hash_table_iter_init (&iter, priv->info_hash);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        fd = (int) (long)key;\n        if (fd > maxfd)\n            maxfd = fd;\n    }\n\n    priv->maxfd = maxfd;\n}\n\nstatic int handle_rm_repo (SeafWTMonitor *monitor,\n                           const char *repo_id,\n                           gpointer handle)\n{\n    SeafWTMonitorPriv *priv = monitor->priv;\n    int inotify_fd = (int)(long)handle;\n\n    close (inotify_fd);\n    FD_CLR (inotify_fd, &priv->read_fds);\n    update_maxfd (monitor);\n\n    pthread_mutex_lock (&priv->hash_lock);\n    g_hash_table_remove (priv->handle_hash, repo_id);\n    g_hash_table_remove (priv->info_hash, (gpointer)(long)inotify_fd);\n    pthread_mutex_unlock (&priv->hash_lock);\n\n    return 0;\n}\n\nstatic int handle_refresh_repo (SeafWTMonitorPriv *priv, const char *repo_id)\n{\n    return 0;\n}\n\nstatic void\nreply_watch_command (SeafWTMonitor *monitor, int result)\n{\n    int n;\n\n    n = seaf_pipe_writen (monitor->res_pipe[1], &result, sizeof(int));\n    if (n != sizeof(int))\n        seaf_warning (\"[wt mon] fail to write command result.\\n\");\n}\n\nstatic void\nhandle_watch_command (SeafWTMonitor *monitor, WatchCommand *cmd)\n{\n    SeafWTMonitorPriv *priv = monitor->priv;\n\n    if (cmd->type == CMD_ADD_WATCH) {\n        if (g_hash_table_lookup_extended (priv->handle_hash, cmd->repo_id,\n                                          NULL, NULL)) {\n            reply_watch_command (monitor, 0);\n            return;\n        }\n\n        if (handle_add_repo(priv, cmd->repo_id, cmd->worktree) < 0) {\n            seaf_warning (\"[wt mon] failed to watch worktree of repo %s.\\n\",\n                          cmd->repo_id);\n            reply_watch_command (monitor, -1);\n            return;\n        }\n\n        seaf_debug (\"[wt mon] add watch for repo %s\\n\", cmd->repo_id);\n        reply_watch_command (monitor, 0);\n    } else if (cmd->type == CMD_DELETE_WATCH) {\n        gpointer key, value;\n        if (!g_hash_table_lookup_extended (priv->handle_hash, cmd->repo_id,\n                                           &key, &value)) {\n            reply_watch_command (monitor, 0);\n            return;\n        }\n\n        handle_rm_repo (monitor, cmd->repo_id, value);\n        reply_watch_command (monitor, 0);\n    } else if (cmd->type ==  CMD_REFRESH_WATCH) {\n        if (handle_refresh_repo (priv, cmd->repo_id) < 0) {\n            seaf_warning (\"[wt mon] failed to refresh watch of repo %s.\\n\",\n                          cmd->repo_id);\n            reply_watch_command (monitor, -1);\n            return;\n        }\n        reply_watch_command (monitor, 0);\n    }\n}\n\n/* Public interface functions. */\n\nSeafWTMonitor *\nseaf_wt_monitor_new (SeafileSession *seaf)\n{\n    SeafWTMonitor *monitor = g_new0 (SeafWTMonitor, 1);\n    SeafWTMonitorPriv *priv = g_new0 (SeafWTMonitorPriv, 1);\n\n    pthread_mutex_init (&priv->hash_lock, NULL);\n\n    priv->handle_hash = g_hash_table_new_full\n        (g_str_hash, g_str_equal, g_free, NULL);\n\n    priv->info_hash = g_hash_table_new_full\n        (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)free_repo_watch_info);\n\n    monitor->priv = priv;\n    monitor->seaf = seaf;\n\n    monitor->job_func = wt_monitor_job_linux;\n\n    return monitor;\n}\n\nWTStatus *\nseaf_wt_monitor_get_worktree_status (SeafWTMonitor *monitor,\n                                     const char *repo_id)\n{\n    SeafWTMonitorPriv *priv = monitor->priv;\n    gpointer key, value;\n    RepoWatchInfo *info;\n\n    pthread_mutex_lock (&priv->hash_lock);\n\n    if (!g_hash_table_lookup_extended (priv->handle_hash, repo_id,\n                                       &key, &value)) {\n        pthread_mutex_unlock (&priv->hash_lock);\n        return NULL;\n    }\n\n    info = g_hash_table_lookup(priv->info_hash, value);\n    wt_status_ref (info->status);\n\n    pthread_mutex_unlock (&priv->hash_lock);\n\n    return info->status;\n}\n"
  },
  {
    "path": "daemon/wt-monitor-macos.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n#include \"common.h\"\n\n#include <CoreServices/CoreServices.h>\n#include <sys/event.h>\n\n#include <sys/time.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\n#include \"job-mgr.h\"\n#include \"seafile-session.h\"\n#include \"utils.h\"\n#include \"wt-monitor.h\"\n#define DEBUG_FLAG SEAFILE_DEBUG_WATCH\n#include \"log.h\"\n\n#define RENAME_EXPIRE_TIME 10\n\ntypedef struct RenameInfo {\n    char *old_path;\n    char *old_event_path;\n    gboolean processing;\n    gint64 expire;\n} RenameInfo;\n\ntypedef struct RepoWatchInfo {\n    WTStatus *status;\n    char *worktree;\n    RenameInfo *rename_info;\n} RepoWatchInfo;\n\nstruct SeafWTMonitorPriv {\n    pthread_mutex_t hash_lock;\n    GHashTable *handle_hash;        /* repo_id -> inotify_fd (or handle) */\n    GHashTable *info_hash;          /* inotify_fd(or handle in deeed) -> RepoWatchInfo */\n};\n\nstatic void\nadd_event_to_queue (WTStatus *status,\n                    int type, const char *path, const char *new_path);\n\nstatic void handle_watch_command (SeafWTMonitor *monitor, WatchCommand *cmd);\n\ninline static void\nset_rename_processing_state (RenameInfo *info, const char *path, const char *event_path)\n{\n    gint64 now = (gint64)time(NULL);\n    info->old_path = g_strdup(path);\n    info->old_event_path = g_strdup(event_path);\n    info->processing = TRUE;\n    info->expire = now + RENAME_EXPIRE_TIME;\n}\n\ninline static void\nunset_rename_processing_state (RenameInfo *info)\n{\n    g_free (info->old_path);\n    g_free (info->old_event_path);\n    info->old_path = NULL;\n    info->old_event_path = NULL;\n    info->processing = FALSE;\n    info->expire = 0;\n}\n\n/* RepoWatchInfo */\n\nstatic RepoWatchInfo *\ncreate_repo_watch_info (const char *repo_id, const char *worktree)\n{\n    WTStatus *status = create_wt_status (repo_id);\n\n    RepoWatchInfo *info = g_new0 (RepoWatchInfo, 1);\n    info->status = status;\n    info->worktree = g_strdup(worktree);\n    info->rename_info = g_new0 (RenameInfo, 1);\n\n    return info;\n}\n\nstatic void\nfree_repo_watch_info (RepoWatchInfo *info)\n{\n    wt_status_unref (info->status);\n    g_free (info->worktree);\n    g_free (info->rename_info->old_path);\n    g_free (info->rename_info);\n    g_free (info);\n}\n\n// Since macOS does not distinguish filename case, we need to perform an additional check during rename operations to verify that the filename matches the actual name exactly.\nstatic gboolean\nis_filename_case_conflict (const char *eventPath)\n{\n    gboolean ret = FALSE;\n    int fd = -1;\n\n    fd = open (eventPath, O_RDONLY);\n    if (fd < 0) {\n        goto out;\n    }\n\n    char buffer[SEAF_PATH_MAX];\n    if (fcntl (fd, F_GETPATH, buffer) < 0) {\n        goto out;\n    }\n\n    if (strcasecmp (buffer, eventPath) == 0 &&\n        strcmp (buffer, eventPath) != 0) {\n        ret = TRUE;\n    }\n\nout:\n    if (fd >= 0)\n        close (fd);\n    return ret;\n}\n\nstatic void\ncheck_and_handle_rename (WTStatus *status, RenameInfo *rename_info,\n                        const char *eventPath, const char *filename)\n{\n    struct stat st;\n    gboolean old_path_exists = TRUE;\n    gboolean new_path_exists = TRUE;\n\n    if ((stat (rename_info->old_event_path, &st) < 0 && errno == ENOENT) ||\n         is_filename_case_conflict (rename_info->old_event_path)) {\n        old_path_exists = FALSE;\n    }\n    if ((stat (eventPath, &st) < 0 && errno == ENOENT) ||\n         is_filename_case_conflict (eventPath)) {\n        new_path_exists = FALSE;\n    }\n\n    if (!old_path_exists && new_path_exists) {\n        seaf_debug (\"Rename dir %s to %s\\n\", rename_info->old_path, filename);\n        add_event_to_queue (status, WT_EVENT_RENAME, rename_info->old_path, filename);\n    } else {\n        if (old_path_exists) {\n            seaf_debug (\"Rename: old path exist, add create event for %s\\n\", rename_info->old_path);\n            add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, rename_info->old_path, NULL);\n        } else {\n            seaf_debug (\"Rename: old path doesn't exist, add delete event for %s\\n\", rename_info->old_path);\n            add_event_to_queue (status, WT_EVENT_DELETE, rename_info->old_path, NULL);\n        }\n        if (new_path_exists) {\n            seaf_debug (\"Rename: new path exist, add create event for %s\\n\", filename);\n            add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL);\n        } else {\n            seaf_debug (\"Rename: new path doesn't exist, add delete event for %s\\n\", filename);\n            add_event_to_queue (status, WT_EVENT_DELETE, filename, NULL);\n        }\n    }\n\n    unset_rename_processing_state (rename_info);\n}\n\nstatic void\nhandle_rename (RepoWatchInfo *info,\n               const FSEventStreamEventFlags eventFlags,\n               const char *eventPath,\n               const char *filename)\n{\n    WTStatus *status = info->status;\n    RenameInfo *rename_info = info->rename_info;\n    gboolean is_dir = FALSE;\n    gint64 now = (gint64)time(NULL);\n\n    // If processing is set for more than 10 seconds, the system may not generate paired renaming events.\n    // In this case, we add creation event or deletion event based on whether the file exists.\n    if (rename_info->expire > 0 && now >= rename_info->expire) {\n        struct stat st;\n        if ((stat (rename_info->old_event_path, &st) < 0 && errno == ENOENT) || is_filename_case_conflict (eventPath)) {\n            seaf_debug (\"Rename info expired, delete renamed dir %s\\n\", rename_info->old_path);\n            add_event_to_queue (status, WT_EVENT_DELETE, rename_info->old_path, NULL);\n        } else {\n            seaf_debug (\"Rename info expired, create renamed dir %s\\n\", rename_info->old_path);\n            add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, rename_info->old_path, NULL);\n        }\n        unset_rename_processing_state (rename_info);\n    }\n\n    if (!(eventFlags & kFSEventStreamEventFlagItemRenamed)) {\n        return;\n    }\n\n    seaf_debug (\"Rename flag set for %s \\n\", filename);\n\n    if (eventFlags & kFSEventStreamEventFlagItemIsDir) {\n        is_dir = TRUE;\n    }\n\n    // It is not always possible to reliably determine if a file has been renamed on  Mac, as system events do not guarantee that renamed files appear consecutively.\n    // However, we can detect directory renaming, and handling directory renaming is generally not overly complex.\n    if (is_dir) {\n        if (!rename_info->processing) {\n            seaf_debug (\"Move %s ->\\n\", filename);\n            set_rename_processing_state (rename_info, filename, eventPath);\n        } else {\n            seaf_debug (\"Move -> %s.\\n\", filename);\n            // If a dir is renamed, the old path should not exist, and the new path should exist.\n            // If this condition is not met, split the renaming event into deletion event or creation event based on whether the dir exists.\n            check_and_handle_rename (status, rename_info, eventPath, filename);\n        }\n    } else {\n        struct stat st;\n        if ((stat (eventPath, &st) < 0 && errno == ENOENT) || is_filename_case_conflict (eventPath)) {\n            seaf_debug (\"Delete renamed file %s\\n\", filename);\n            add_event_to_queue (status, WT_EVENT_DELETE, filename, NULL);\n        } else {\n            seaf_debug (\"Create renamed file %s\\n\", filename);\n            add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL);\n        }\n    }\n}\n\nstatic void\nadd_event_to_queue (WTStatus *status,\n                    int type, const char *path, const char *new_path)\n{\n    char *nfc_path = NULL, *nfc_new_path = NULL;\n\n    if (path)\n        nfc_path = g_utf8_normalize (path, -1, G_NORMALIZE_NFC);\n    if (new_path)\n        nfc_new_path = g_utf8_normalize (new_path, -1, G_NORMALIZE_NFC);\n\n    WTEvent *event = wt_event_new (type, nfc_path, nfc_new_path);\n\n    g_free (nfc_path);\n    g_free (nfc_new_path);\n\n    char *name;\n    switch (type) {\n    case WT_EVENT_CREATE_OR_UPDATE:\n        name = \"create/update\";\n        break;\n    case WT_EVENT_SCAN_DIR:\n        name = \"scan dir\";\n        break;\n    case WT_EVENT_DELETE:\n        name = \"delete\";\n        break;\n    case WT_EVENT_RENAME:\n        name = \"rename\";\n        break;\n    case WT_EVENT_OVERFLOW:\n        name = \"overflow\";\n        break;\n    case WT_EVENT_ATTRIB:\n        name = \"attribute change\";\n        break;\n    default:\n        name = \"unknown\";\n    }\n\n    seaf_debug (\"Adding event: %s, %s %s\\n\", name, path, new_path?new_path:\"\");\n\n    pthread_mutex_lock (&status->q_lock);\n    g_queue_push_tail (status->event_q, event);\n    pthread_mutex_unlock (&status->q_lock);\n\n    if (type == WT_EVENT_CREATE_OR_UPDATE) {\n        pthread_mutex_lock (&status->ap_q_lock);\n\n        char *last = g_queue_peek_tail (status->active_paths);\n        if (!last || strcmp(last, path) != 0)\n            g_queue_push_tail (status->active_paths, g_strdup(path));\n\n        pthread_mutex_unlock (&status->ap_q_lock);\n    }\n}\n\nstatic void\nprocess_one_event (const char* eventPath,\n                   RepoWatchInfo *info,\n                   const char *worktree,\n                   const FSEventStreamEventId eventId,\n                   const FSEventStreamEventFlags eventFlags,\n                   gboolean last_event)\n{\n    WTStatus *status = info->status;\n    char *filename;\n    char *event_path_nfc;\n    const char *tmp;\n    struct stat buf;\n\n    event_path_nfc = g_utf8_normalize (eventPath, -1, G_NORMALIZE_NFC);\n\n    tmp = event_path_nfc + strlen(worktree);\n    if (*tmp == '/')\n        tmp++;\n    filename = g_strdup(tmp);\n    g_free (event_path_nfc);\n\n    /* Path for folder returned from system may contain a '/' at the end. */\n    int len = strlen(filename);\n    if (len > 0 && filename[len - 1] == '/')\n        filename[len - 1] = 0;\n\n    handle_rename (info, eventFlags, eventPath, filename);\n\n    if (eventFlags & kFSEventStreamEventFlagItemRemoved) {\n        seaf_debug (\"Deleted flag set for %s.\\n\", filename);\n        if (stat (eventPath, &buf) < 0) {\n            add_event_to_queue (status, WT_EVENT_DELETE, filename, NULL);\n        }\n    }\n\n    if (eventFlags & kFSEventStreamEventFlagItemModified) {\n        seaf_debug (\"Modified flag set for %s.\\n\", filename);\n        if (stat (eventPath, &buf) == 0) {\n            add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL);\n        }\n    }\n\n    if (eventFlags & kFSEventStreamEventFlagItemCreated) {\n        seaf_debug (\"Created flag set for %s.\\n\", filename);\n         /**\n          * no need to rechecking recursively in FSEventStream\n          *\n          * these flags are useful if necessary:\n          * kFSEventStreamEventFlagItemIsFile\n          * kFSEventStreamEventFlagItemIsDir\n          * kFSEventStreamEventFlagItemIsSymlink\n          */\n        if (stat (eventPath, &buf) == 0) {\n            add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL);\n        }\n    }\n\n    // If this is the last event, check rename_info and split the renaming event into deletion event or creation event.\n    RenameInfo *rename_info = info->rename_info;\n    if (last_event && rename_info->processing) {\n        if (stat (rename_info->old_event_path, &buf) < 0 && errno == ENOENT) {\n            seaf_debug (\"Delete renamed file %s\\n\", rename_info->old_path);\n            add_event_to_queue (status, WT_EVENT_DELETE, rename_info->old_path, NULL);\n        } else {\n            seaf_debug (\"Create renamed file %s\\n\", rename_info->old_path);\n            add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, rename_info->old_path, NULL);\n        }\n        unset_rename_processing_state (rename_info);\n    }\n\n    g_free (filename);\n    g_atomic_int_set (&info->status->last_changed, (gint)time(NULL));\n}\n\n#if 0\nstatic void\nprocess_one_event (const char* eventPath,\n                   RepoWatchInfo *info,\n                   const char *worktree,\n                   const FSEventStreamEventId eventId,\n                   const FSEventStreamEventFlags eventFlags)\n{\n    WTStatus *status = info->status;\n    char *dirname;\n    char *event_path_nfc;\n    const char *tmp;\n\n    event_path_nfc = g_utf8_normalize (eventPath, -1, G_NORMALIZE_NFC);\n\n    tmp = event_path_nfc + strlen(worktree);\n    if (*tmp == '/')\n        tmp++;\n    dirname = g_strdup(tmp);\n    g_free (event_path_nfc);\n\n    /* Path for folder returned from system may contain a '/' at the end. */\n    int len = strlen(dirname);\n    if (len > 0 && dirname[len - 1] == '/')\n        dirname[len - 1] = 0;\n\n    if (eventFlags & kFSEventStreamEventFlagItemRenamed) {\n        seaf_debug (\"Rename event in dir: %s \\n\", dirname);\n    } else if (eventFlags & kFSEventStreamEventFlagItemModified) {\n        seaf_debug (\"Modified event in dir %s.\\n\", dirname);\n    } else if (eventFlags & kFSEventStreamEventFlagItemCreated) {\n        seaf_debug (\"Created event in dir %s.\\n\", dirname);\n    } else if (eventFlags & kFSEventStreamEventFlagItemRemoved) {\n        seaf_debug (\"Deleted event in dir %s.\\n\", dirname);\n    } else if (eventFlags & kFSEventStreamEventFlagItemXattrMod) {\n        seaf_debug (\"XattrMod event in dir %s.\\n\", dirname);\n    } else {\n        seaf_debug (\"Unhandled event with flags %x.\\n\", eventFlags);\n    }\n\n    add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, dirname, NULL);\n\n    g_free (dirname);\n    g_atomic_int_set (&info->status->last_changed, (gint)time(NULL));\n}\n#endif\n\nstatic void\nstream_callback (ConstFSEventStreamRef streamRef,\n                      void *clientCallBackInfo,\n                      size_t numEvents,\n                      void *eventPaths,\n                      const FSEventStreamEventFlags eventFlags[],\n                      const FSEventStreamEventId eventIds[])\n{\n    RepoWatchInfo *info;\n    SeafWTMonitor *monitor = (SeafWTMonitor *)clientCallBackInfo;\n    SeafWTMonitorPriv *priv = monitor->priv;\n    char **paths = (char **)eventPaths;\n\n    info = g_hash_table_lookup (priv->info_hash, (gpointer)(long)streamRef);\n    if (!info) {\n        seaf_warning (\"Repo watch info not found.\\n\");\n        return;\n    }\n\n    int i;\n    for (i = 0; i < numEvents; i++) {\n        seaf_debug(\"%ld Change %llu in %s, flags %x\\n\", (long)CFRunLoopGetCurrent(),\n                   eventIds[i], paths[i], eventFlags[i]);\n        process_one_event (paths[i], info, info->worktree,\n                           eventIds[i], eventFlags[i], i==(numEvents - 1));\n    }\n}\n\nstatic FSEventStreamRef\nadd_watch (SeafWTMonitor *monitor, const char* repo_id, const char* worktree)\n{\n    SeafWTMonitorPriv *priv = monitor->priv;\n    RepoWatchInfo *info;\n    double latency = 0.25; /* unit: second */\n\n    char *worktree_nfd = g_utf8_normalize (worktree, -1, G_NORMALIZE_NFD);\n\n    CFStringRef mypaths[1];\n    mypaths[0] = CFStringCreateWithCString (kCFAllocatorDefault,\n                                            worktree_nfd, kCFStringEncodingUTF8);\n    g_free (worktree_nfd);\n    CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)mypaths, 1, NULL);\n    FSEventStreamRef stream;\n\n    /* Create the stream, passing in a callback */\n    // kFSEventStreamCreateFlagFileEvents does not work for libraries with name\n    // containing accent characters.\n    struct FSEventStreamContext ctx = {0, monitor, NULL, NULL, NULL};\n    stream = FSEventStreamCreate(kCFAllocatorDefault,\n                                 stream_callback,\n                                 &ctx,\n                                 pathsToWatch,\n                                 kFSEventStreamEventIdSinceNow,\n                                 latency,\n                                 kFSEventStreamCreateFlagFileEvents\n                                 );\n\n    CFRelease (mypaths[0]);\n    CFRelease (pathsToWatch);\n\n    if (!stream) {\n        seaf_warning (\"[wt] Failed to create event stream.\\n\");\n        return stream;\n    }\n\n    FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);\n    FSEventStreamStart (stream);\n    /* FSEventStreamShow (stream); */\n    seaf_debug (\"[wt mon] Add repo %s watch success: %s.\\n\", repo_id, worktree);\n\n    pthread_mutex_lock (&priv->hash_lock);\n    g_hash_table_insert (priv->handle_hash,\n                         g_strdup(repo_id), (gpointer)(long)stream);\n\n    info = create_repo_watch_info (repo_id, worktree);\n    g_hash_table_insert (priv->info_hash, (gpointer)(long)stream, info);\n    pthread_mutex_unlock (&priv->hash_lock);\n\n    /* A special event indicates repo-mgr to scan the whole worktree. */\n    add_event_to_queue (info->status, WT_EVENT_SCAN_DIR, \"\", NULL);\n    return stream;\n}\n\nstatic void\ncommand_read_cb (CFFileDescriptorRef fdref,\n                             CFOptionFlags callBackTypes,\n                             void *info)\n{\n    SeafWTMonitor *monitor = (SeafWTMonitor *)info;\n    WatchCommand cmd;\n    int n;\n\n    n = seaf_pipe_readn (monitor->cmd_pipe[0], &cmd, sizeof(cmd));\n    if (n != sizeof(cmd)) {\n        seaf_warning (\"[wt mon] failed to read command.\\n\");\n        CFFileDescriptorEnableCallBacks (fdref, kCFFileDescriptorReadCallBack);\n        return;\n    }\n\n    seaf_debug (\"[wt mon] %ld receive command type=%d, repo=%s\\n\",\n                (long)CFRunLoopGetCurrent(), cmd.type, cmd.repo_id);\n    handle_watch_command (monitor, &cmd);\n    CFFileDescriptorEnableCallBacks (fdref, kCFFileDescriptorReadCallBack);\n}\n\nstatic int\nadd_command_pipe (SeafWTMonitor *monitor)\n{\n    CFFileDescriptorContext ctx = {0, monitor, NULL, NULL, NULL};\n    CFFileDescriptorRef fdref = CFFileDescriptorCreate(NULL,\n                                                       monitor->cmd_pipe[0], true,\n                                                       command_read_cb, &ctx);\n    if (fdref == NULL) {\n        return -1;\n    }\n\n    CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);\n    CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0);\n    CFRunLoopAddSource (CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);\n    CFRelease(source);\n    return 0;\n}\n\nstatic void *\nwt_monitor_job_darwin (void *vmonitor)\n{\n    SeafWTMonitor *monitor = (SeafWTMonitor *)vmonitor;\n\n    add_command_pipe (monitor);\n    while (1) {\n        CFRunLoopRun();\n    }\n    return NULL;\n}\n\nstatic int\nhandle_add_repo (SeafWTMonitor *monitor, const char *repo_id, const char *worktree)\n{\n    FSEventStreamRef stream = add_watch (monitor, repo_id, worktree);\n    if (!stream)\n        return -1;\n    return 0;\n}\n\nstatic int\nhandle_rm_repo (SeafWTMonitor *monitor, const char *repo_id, gpointer handle)\n{\n    SeafWTMonitorPriv *priv = monitor->priv;\n    FSEventStreamRef stream = (FSEventStreamRef)handle;\n    FSEventStreamStop (stream);\n    FSEventStreamInvalidate (stream);\n    FSEventStreamRelease (stream);\n\n    pthread_mutex_lock (&priv->hash_lock);\n    g_hash_table_remove (priv->handle_hash, repo_id);\n    g_hash_table_remove (priv->info_hash, (gpointer)(long)stream);\n    pthread_mutex_unlock (&priv->hash_lock);\n    return 0;\n}\n\nstatic int\nhandle_refresh_repo (SeafWTMonitor *monitor, const char *repo_id)\n{\n    return 0;\n}\n\nstatic void\nreply_watch_command (SeafWTMonitor *monitor, int result)\n{\n    int n;\n\n    n = seaf_pipe_writen (monitor->res_pipe[1], &result, sizeof(int));\n    if (n != sizeof(int))\n        seaf_warning (\"[wt mon] fail to write command result.\\n\");\n}\n\nstatic void\nhandle_watch_command (SeafWTMonitor *monitor, WatchCommand *cmd)\n{\n    SeafWTMonitorPriv *priv = monitor->priv;\n\n    if (cmd->type == CMD_ADD_WATCH) {\n        if (g_hash_table_lookup_extended (priv->handle_hash, cmd->repo_id,\n                                          NULL, NULL)) {\n            reply_watch_command (monitor, 0);\n            return;\n        }\n\n        if (handle_add_repo(monitor, cmd->repo_id, cmd->worktree) < 0) {\n            seaf_warning (\"[wt mon] failed to watch worktree of repo %s.\\n\",\n                          cmd->repo_id);\n            reply_watch_command (monitor, -1);\n            return;\n        }\n\n        seaf_debug (\"[wt mon] add watch for repo %s\\n\", cmd->repo_id);\n        reply_watch_command (monitor, 0);\n    } else if (cmd->type == CMD_DELETE_WATCH) {\n        gpointer key, value;\n        if (!g_hash_table_lookup_extended (priv->handle_hash, cmd->repo_id,\n                                           &key, &value)) {\n            reply_watch_command (monitor, 0);\n            return;\n        }\n\n        handle_rm_repo (monitor, cmd->repo_id, value);\n        reply_watch_command (monitor, 0);\n    } else if (cmd->type ==  CMD_REFRESH_WATCH) {\n        if (handle_refresh_repo (monitor, cmd->repo_id) < 0) {\n            seaf_warning (\"[wt mon] failed to refresh watch of repo %s.\\n\",\n                          cmd->repo_id);\n            reply_watch_command (monitor, -1);\n            return;\n        }\n        reply_watch_command (monitor, 0);\n    }\n}\n\nSeafWTMonitor *\nseaf_wt_monitor_new (SeafileSession *seaf)\n{\n    SeafWTMonitor *monitor = g_new0 (SeafWTMonitor, 1);\n    SeafWTMonitorPriv *priv = g_new0 (SeafWTMonitorPriv, 1);\n\n    pthread_mutex_init (&priv->hash_lock, NULL);\n\n    priv->handle_hash = g_hash_table_new_full\n        (g_str_hash, g_str_equal, g_free, NULL);\n\n    priv->info_hash = g_hash_table_new_full\n        (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)free_repo_watch_info);\n\n    monitor->priv = priv;\n    monitor->seaf = seaf;\n\n    monitor->job_func = wt_monitor_job_darwin;\n\n    return monitor;\n}\n\nWTStatus *\nseaf_wt_monitor_get_worktree_status (SeafWTMonitor *monitor,\n                                     const char *repo_id)\n{\n    SeafWTMonitorPriv *priv = monitor->priv;\n    gpointer key, value;\n    RepoWatchInfo *info;\n\n    pthread_mutex_lock (&priv->hash_lock);\n\n    if (!g_hash_table_lookup_extended (priv->handle_hash, repo_id,\n                                       &key, &value)) {\n        pthread_mutex_unlock (&priv->hash_lock);\n        return NULL;\n    }\n\n    info = g_hash_table_lookup(priv->info_hash, value);\n    wt_status_ref (info->status);\n\n    pthread_mutex_unlock (&priv->hash_lock);\n\n    return info->status;\n}\n"
  },
  {
    "path": "daemon/wt-monitor-structs.c",
    "content": "#include <string.h>\n\n#include \"wt-monitor-structs.h\"\n\n/* WTEvent */\n\nWTEvent *wt_event_new (int type, const char *path, const char *new_path)\n{\n    WTEvent *event = g_new0 (WTEvent, 1);\n\n    event->ev_type = type;\n    if (path)\n        event->path = g_strdup (path);\n    if (new_path)\n        event->new_path = g_strdup(new_path);\n\n    return event;\n}\n\nstatic void free_path (gpointer data, gpointer user_data)\n{\n    g_free (data);\n}\n\nvoid wt_event_free (WTEvent *event)\n{\n    g_free (event->path);\n    g_free (event->new_path);\n    if (event->remain_files) {\n        g_queue_foreach (event->remain_files, free_path, NULL);\n        g_queue_free (event->remain_files);\n    }\n    g_free (event);\n}\n\n/* WTStatus */\n\nWTStatus *create_wt_status (const char *repo_id)\n{\n    WTStatus *status = g_new0 (WTStatus, 1);\n\n    memcpy (status->repo_id, repo_id, 36);\n    status->event_q = g_queue_new ();\n    pthread_mutex_init (&status->q_lock, NULL);\n\n    status->active_paths = g_queue_new ();\n    pthread_mutex_init (&status->ap_q_lock, NULL);\n\n    /* The monitor thread always holds a reference to this status\n     * until it's unwatched\n     */\n    status->ref_count = 1;\n\n    return status;\n}\n\nstatic void free_event_cb (gpointer data, gpointer user_data)\n{\n    WTEvent *event = data;\n    wt_event_free (event);\n}\n\nstatic void free_wt_status (WTStatus *status)\n{\n    if (status->event_q) {\n        g_queue_foreach (status->event_q, free_event_cb, NULL);\n        g_queue_free (status->event_q);\n    }\n    pthread_mutex_destroy (&status->q_lock);\n    g_free (status);\n}\n\nvoid\nwt_status_ref (WTStatus *status)\n{\n    ++(status->ref_count);\n}\n\nvoid\nwt_status_unref (WTStatus *status)\n{\n    if (!status) return;\n\n    if (--(status->ref_count) <= 0)\n        free_wt_status (status);\n}\n"
  },
  {
    "path": "daemon/wt-monitor-structs.h",
    "content": "#ifndef WT_MONITOR_STRUCTS_H\n#define WT_MONITOR_STRUCTS_H\n\n#include <glib.h>\n#include <pthread.h>\n\nenum {\n    WT_EVENT_CREATE_OR_UPDATE = 0,\n    WT_EVENT_DELETE,\n    WT_EVENT_RENAME,\n    WT_EVENT_ATTRIB,\n    WT_EVENT_OVERFLOW,\n    WT_EVENT_SCAN_DIR,\n};\n\ntypedef struct WTEvent {\n    int ev_type;\n    char *path;\n    char *new_path;             /* only used by rename event */\n\n    /* For CREATE_OR_UPDATE events, if a partial commit was created when\n     * adding files recursively, the remaining files will be cached in\n     * this queue so that we don't have to rescan the dir from beginning.\n     */\n    GQueue *remain_files;\n} WTEvent;\n\nWTEvent *wt_event_new (int type, const char *path, const char *new_path);\n\nvoid wt_event_free (WTEvent *event);\n\ntypedef struct WTStatus {\n    int         ref_count;\n\n    char        repo_id[37];\n    gint        last_check;\n    gint        last_changed;\n\n    /* If partial_commit is TRUE, the last commit is partial.\n     * We need to produce another commit from the remaining events.\n     */\n    gboolean    partial_commit;\n\n    pthread_mutex_t q_lock;\n    GQueue *event_q;\n\n    /* Paths that're updated. They corresponds to CREATE_OR_UPDATE events.\n     * Use a separate queue since we need to process them simultaneously with\n     * the event queue. And this queue is usually shorter and consumed faster,\n     * because we don't need to process them in multiple batches.\n     */\n    pthread_mutex_t ap_q_lock;\n    GQueue *active_paths;\n} WTStatus;\n\nWTStatus *create_wt_status (const char *repo_id);\n\nvoid wt_status_ref (WTStatus *status);\n\nvoid wt_status_unref (WTStatus *status);\n\n#endif\n"
  },
  {
    "path": "daemon/wt-monitor-win32.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n#include \"common.h\"\n\n#ifndef _WIN32_WINNT\n#define _WIN32_WINNT 0x500\n#endif\n\n#include <windows.h>\n\n#ifndef WIN32\n#include <unistd.h>\n#include <sys/time.h>\n#endif\n#include <sys/types.h>\n\n#include \"job-mgr.h\"\n#include \"seafile-session.h\"\n#include \"utils.h\"\n#include \"wt-monitor.h\"\n#define DEBUG_FLAG SEAFILE_DEBUG_WATCH\n#include \"log.h\"\n\n#define DIR_WATCH_MASK                                            \\\n    FILE_NOTIFY_CHANGE_FILE_NAME |  FILE_NOTIFY_CHANGE_LAST_WRITE \\\n    | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE \n\n/* Use large buffer to prevent events overflow. */\n#define DIR_WATCH_BUFSIZE 1 << 20 /* 1MB */\n\n/* Hold the OVERLAPPED struct for asynchronous ReadDirectoryChangesW(), and\n   the buf to receive dir change info. */\ntypedef struct DirWatchAux {\n    OVERLAPPED ol;\n    char buf[DIR_WATCH_BUFSIZE];\n    gboolean unused;\n} DirWatchAux;\n\ntypedef struct RenameInfo {\n    char *old_path;\n    gboolean processing;        /* Are we processing a rename event? */\n} RenameInfo;\n\ntypedef struct EventInfo {\n    DWORD action;\n    DWORD name_len;\n    char name[SEAF_PATH_MAX];\n} EventInfo;\n\ntypedef struct RepoWatchInfo {\n    WTStatus *status;\n    RenameInfo *rename_info;\n    EventInfo last_event;\n    char *worktree;\n} RepoWatchInfo;\n\nstruct SeafWTMonitorPriv {\n    pthread_mutex_t hash_lock;\n    GHashTable *handle_hash;    /* repo_id -> dir handle */\n    GHashTable *info_hash;      /* handle -> RepoWatchInfo  */\n    GHashTable *buf_hash;       /* handle -> aux buf */\n\n    HANDLE iocp_handle;\n\n    int cmd_bytes_read;\n    WatchCommand cmd;\n};\n\nstatic void *wt_monitor_job_win32 (void *vmonitor);\n\nstatic void handle_watch_command (SeafWTMonitor *monitor, WatchCommand *cmd);\n\n/* RenameInfo */\n\nstatic RenameInfo *create_rename_info ()\n{\n    RenameInfo *info = g_new0 (RenameInfo, 1);\n\n    return info;\n}\n\nstatic void free_rename_info (RenameInfo *info)\n{\n    g_free (info->old_path);\n    g_free (info);\n}\n\ninline static void\nset_rename_processing_state (RenameInfo *info, const char *path)\n{\n    info->old_path = g_strdup(path);\n    info->processing = TRUE;\n}\n\ninline static void\nunset_rename_processing_state (RenameInfo *info)\n{\n    g_free (info->old_path);\n    info->old_path = NULL;\n    info->processing = FALSE;\n}\n\n/* RepoWatchInfo */\n\nstatic RepoWatchInfo *\ncreate_repo_watch_info (const char *repo_id, const char *worktree)\n{\n    WTStatus *status = create_wt_status (repo_id);\n    RenameInfo *rename_info = create_rename_info ();\n\n    RepoWatchInfo *info = g_new0 (RepoWatchInfo, 1);\n    info->status = status;\n    info->rename_info = rename_info;\n    info->worktree = g_strdup(worktree);\n\n    return info;\n}\n\nstatic void\nfree_repo_watch_info (RepoWatchInfo *info)\n{\n    wt_status_unref (info->status);\n    free_rename_info (info->rename_info);\n    g_free (info->worktree);\n    g_free (info);\n}\n\nstatic inline void\ninit_overlapped(OVERLAPPED *ol)\n{\n    ol->Offset = ol->OffsetHigh = 0;\n}\n\n\nstatic inline void\nreset_overlapped(OVERLAPPED *ol)\n{\n    ol->Offset = ol->OffsetHigh = 0;\n}\n\nstatic void\nadd_event_to_queue (WTStatus *status,\n                    int type, const char *path, const char *new_path)\n{\n    WTEvent *event = wt_event_new (type, path, new_path);\n\n    char *name;\n    switch (type) {\n    case WT_EVENT_CREATE_OR_UPDATE:\n        name = \"create/update\";\n        break;\n    case WT_EVENT_SCAN_DIR:\n        name = \"scan dir\";\n        break;\n    case WT_EVENT_DELETE:\n        name = \"delete\";\n        break;\n    case WT_EVENT_RENAME:\n        name = \"rename\";\n        break;\n    case WT_EVENT_OVERFLOW:\n        name = \"overflow\";\n        break;\n    default:\n        name = \"unknown\";\n    }\n\n    seaf_debug (\"Adding event: %s, %s %s\\n\", name, path, new_path?new_path:\"\");\n\n    pthread_mutex_lock (&status->q_lock);\n    g_queue_push_tail (status->event_q, event);\n    pthread_mutex_unlock (&status->q_lock);\n\n    if (type == WT_EVENT_CREATE_OR_UPDATE) {\n        pthread_mutex_lock (&status->ap_q_lock);\n\n        char *last = g_queue_peek_tail (status->active_paths);\n        if (!last || strcmp(last, path) != 0)\n            g_queue_push_tail (status->active_paths, g_strdup(path));\n\n        pthread_mutex_unlock (&status->ap_q_lock);\n    }\n}\n\n/* Every time after a read event is processed, we should call\n * ReadDirectoryChangesW() on the dir handle asynchronously for the IOCP to\n * detect the change of the workthree.\n */\nstatic BOOL\nstart_watch_dir_change(SeafWTMonitorPriv *priv, HANDLE dir_handle)\n{\n    if (!dir_handle)\n        return FALSE;\n\n    BOOL first_alloc = FALSE;\n    DirWatchAux *aux = g_hash_table_lookup (priv->buf_hash, dir_handle);\n\n    /* allocate aux buffer at the first watch, it would be freed if the repo\n       is removed\n    */\n    if (!aux) {\n        first_alloc = TRUE;\n        aux = g_new0(DirWatchAux, 1);\n        init_overlapped(&aux->ol);\n    }\n\n    /* The ending W of this function indicates that the info recevied about\n       the change would be in Unicode(specifically, the name of the file that\n       is changed would be encoded in wide char).\n    */\n    BOOL ret;\n    DWORD code;\n    RepoWatchInfo *info;\nretry:\n    ret = ReadDirectoryChangesW\n        (dir_handle,            /* dir handle */\n         aux->buf,              /* buf to hold change info */\n         DIR_WATCH_BUFSIZE,     /* buf size */\n         TRUE,                  /* watch subtree */\n         DIR_WATCH_MASK,        /* notify filter */\n         NULL,                  /* bytes returned */\n         &aux->ol,              /* pointer to overlapped */\n         NULL);                 /* completion routine */\n\n    if (!ret) {\n        code = GetLastError();\n        seaf_warning(\"Failed to ReadDirectoryChangesW, \"\n                     \"error code %lu\", code);\n\n        if (first_alloc)\n            /* if failed at the first watch, free the aux buffer */\n            g_free(aux);\n        else if (code == ERROR_NOTIFY_ENUM_DIR) {\n            /* If buffer overflowed after the last call,\n             * add an overflow event and retry watch.\n             */\n            info = g_hash_table_lookup (priv->info_hash, dir_handle);\n            add_event_to_queue (info->status, WT_EVENT_OVERFLOW, NULL, NULL);\n            goto retry;\n        }\n    } else {\n        if (first_alloc)\n            /* insert the aux buffer into hash table at the first watch */\n            g_hash_table_insert (priv->buf_hash,\n                                 (gpointer)dir_handle, (gpointer)aux);\n    }\n\n    return ret;\n}\n\n\n/* Every time after a read event is processed, we should call ReadFile() on\n * the pipe handle asynchronously for the IOCP to detect when it's readable.\n */\nstatic BOOL\nstart_watch_cmd_pipe (SeafWTMonitor *monitor, OVERLAPPED *ol_in)\n{\n    SeafWTMonitorPriv *priv = monitor->priv;\n    OVERLAPPED *ol = ol_in; \n\n    if (!ol) {\n        ol = g_new0(OVERLAPPED, 1);\n        init_overlapped(ol);\n    }\n\n    HANDLE hPipe = (HANDLE)monitor->cmd_pipe[0];\n\n    void *p = (char *)(&priv->cmd) + priv->cmd_bytes_read;\n    int to_read = sizeof(WatchCommand) - priv->cmd_bytes_read;\n\n    BOOL sts = ReadFile\n        (hPipe,                 /* file handle */\n         p,            /* buffer */\n         to_read,  /* bytes to read */\n         NULL,                  /* bytes read */\n         ol);                   /* overlapped */\n\n    if (!sts && (GetLastError() != ERROR_IO_PENDING)) {\n        seaf_warning (\"failed to ReadFile, error code %lu\\n\",\n                      GetLastError());\n        if (!ol_in)\n            /* free the overlapped struct if failed at the first watch */\n            g_free(ol);\n\n        return FALSE;\n    }\n\n    return TRUE;\n}\n\n\n/* Add a specific HANDLE to an I/O Completion Port. If it's the cmd pipe\n * handle, call ReadFile() on it; If it's a dir handle, call\n * ReadDirectoryChangesW() on it.\n */\nstatic BOOL\nadd_handle_to_iocp (SeafWTMonitor *monitor, HANDLE hAdd)\n{\n    SeafWTMonitorPriv *priv = monitor->priv;\n    \n    if (!priv || !hAdd)\n        return FALSE;\n\n    /* CreateIoCompletionPort() will add the handle to an I/O Completion Port\n      if the iocp handle is not NULL. Otherwise it will create a new IOCP\n      handle.\n\n      The `key' parameter is used by th IOCP to tell us which handle watched\n      by the I/O Completion Port triggeed a return of the\n      GetQueuedCompletionStatus() function.\n\n      Here we use the value of the handle itself as the key for this handle\n      in the I/O Completion Port.\n    */\n    priv->iocp_handle = CreateIoCompletionPort\n        (hAdd,                  /* handle to add */\n         priv->iocp_handle,     /* iocp handle */\n         (ULONG_PTR)hAdd,       /* key for this handle */\n         1);                    /* Num of concurrent threads */\n\n    if (!priv->iocp_handle) {\n        seaf_warning (\"failed to create/add iocp, error code %lu\",\n                      GetLastError());\n        return FALSE;\n    }\n\n    if (hAdd == (HANDLE)monitor->cmd_pipe[0]) {\n        /* HANDLE is cmd_pipe */\n        return start_watch_cmd_pipe (monitor, NULL);\n    } else {\n        /* HANDLE is a dir handle */\n        return start_watch_dir_change (priv, hAdd);\n    }\n\n}\n\n/* Add the pipe handle and all repo wt handles to IO Completion Port. */\nstatic BOOL\nadd_all_to_iocp (SeafWTMonitor *monitor)\n{\n    SeafWTMonitorPriv *priv = monitor->priv;\n\n    if (!add_handle_to_iocp(monitor, (HANDLE)monitor->cmd_pipe[0])) {\n\n        seaf_warning(\"Failed to add cmd_pipe to iocp, \"\n                     \"error code %lu\", GetLastError());\n        return FALSE;\n    }\n\n    GHashTableIter iter;\n    gpointer value = NULL;\n    gpointer key = NULL;\n\n    g_hash_table_iter_init (&iter, priv->handle_hash);\n    while (g_hash_table_iter_next (&iter, &key, &value)) {\n        if (!add_handle_to_iocp(monitor, (HANDLE)value)) {\n            seaf_warning(\"Failed to add dir handle to iocp, \"\n                         \"repo %s, error code %lu\", (char *)key,\n                         GetLastError());\n            continue;\n        }\n    }\n\n    seaf_debug(\"Done: add_all_to_iocp\\n\");\n    return TRUE;\n}\n\n/*\n * On Windows, RENAMED_OLD_NAME and RENAMED_NEW_NAME always comes in pairs.\n * If a file or dir is moved in/out of the worktree, ADDED or REMOVED event\n * will be emitted by the kernel.\n * \n * This is a two-state state machine. The states are 'not processing rename' and\n * 'processing rename'.\n */\nstatic void\nhandle_rename (RepoWatchInfo *info,\n               PFILE_NOTIFY_INFORMATION event,\n               const char *worktree,\n               const char *filename,\n               gboolean last_event)\n{\n    WTStatus *status = info->status;\n    RenameInfo *rename_info = info->rename_info;\n\n    if (event->Action == FILE_ACTION_RENAMED_OLD_NAME)\n        seaf_debug (\"Move %s ->\\n\", filename);\n    else if (event->Action == FILE_ACTION_RENAMED_NEW_NAME)\n        seaf_debug (\"Move -> %s.\\n\", filename);\n\n    if (!rename_info->processing) {\n        if (event->Action == FILE_ACTION_RENAMED_OLD_NAME) {\n            if (!last_event) {\n                set_rename_processing_state (rename_info, filename);\n            } else {\n                /* RENAMED_OLD_NAME should not be the last event,\n                   just ignore it.\n                */\n            }\n        }\n    } else {\n        if (event->Action == FILE_ACTION_RENAMED_NEW_NAME) {\n            /* Rename pair detected. */\n            add_event_to_queue (status, WT_EVENT_RENAME,\n                                rename_info->old_path, filename);\n            unset_rename_processing_state (rename_info);\n        }\n    }\n}\n\n#if 0\nstatic gboolean\nhandle_consecutive_duplicate_event (RepoWatchInfo *info,\n                                    PFILE_NOTIFY_INFORMATION event)\n{\n    gboolean duplicate;\n\n    /* Initially last_event is zero so it's not duplicate with any real events. */\n    duplicate = (info->last_event.action == event->Action &&\n                 info->last_event.name_len == event->FileNameLength &&\n                 memcmp (info->last_event.name, event->FileName, event->FileNameLength) == 0);\n\n    info->last_event.action = event->Action;\n    info->last_event.name_len = event->FileNameLength;\n    memcpy (info->last_event.name, event->FileName, event->FileNameLength);\n\n    return duplicate;\n}\n#endif\n\nstatic char *\nconvert_to_unix_path (const char *worktree, const wchar_t *path, int path_len,\n                      gboolean convert_long_path)\n{\n    char *utf8_path = NULL;\n\n    if (convert_long_path) {\n        wchar_t *long_path = win32_83_path_to_long_path (worktree,\n                                                         path,\n                                                         path_len/sizeof(wchar_t));\n        if (long_path) {\n            utf8_path = g_utf16_to_utf8 (long_path, -1, NULL, NULL, NULL);\n            g_free (long_path);\n        } else\n            utf8_path = g_utf16_to_utf8 (path, path_len/sizeof(wchar_t),\n                                         NULL, NULL, NULL);\n    } else\n        utf8_path = g_utf16_to_utf8 (path, path_len/sizeof(wchar_t), NULL, NULL, NULL);\n\n    if (!utf8_path)\n        return NULL;\n\n    char *p;\n    for (p = utf8_path; *p != 0; ++p)\n        if (*p == '\\\\')\n            *p = '/';\n\n    return utf8_path;\n}\n\nstatic void\nprocess_one_event (RepoWatchInfo *info,\n                   const char *worktree,\n                   PFILE_NOTIFY_INFORMATION event,\n                   gboolean last_event)\n{\n    WTStatus *status = info->status;\n    char *filename;\n    gboolean add_to_queue = TRUE;\n\n#if 0\n    if (handle_consecutive_duplicate_event (info, event))\n        add_to_queue = FALSE;\n#endif\n\n    gboolean convert_long_path = !(event->Action == FILE_ACTION_RENAMED_OLD_NAME ||\n                                   event->Action == FILE_ACTION_REMOVED);\n    filename = convert_to_unix_path (worktree, event->FileName, event->FileNameLength,\n                                     convert_long_path);\n    if (!filename)\n        goto out;\n\n    handle_rename (info, event, worktree, filename, last_event);\n\n    if (event->Action == FILE_ACTION_MODIFIED) {\n        seaf_debug (\"Modified %s.\\n\", filename);\n\n        /* Ignore modified event for directories. */\n        char *full_path = g_build_filename (worktree, filename, NULL);\n        SeafStat st;\n        int rc = seaf_stat (full_path, &st);\n        if (rc < 0 || S_ISDIR(st.st_mode)) {\n            g_free (full_path);\n            goto out;\n        }\n        g_free (full_path);\n\n        if (add_to_queue)\n            add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL);\n    } else if (event->Action == FILE_ACTION_ADDED) {\n        seaf_debug (\"Created %s.\\n\", filename);\n        add_event_to_queue (status, WT_EVENT_CREATE_OR_UPDATE, filename, NULL);\n    } else if (event->Action == FILE_ACTION_REMOVED) {\n        seaf_debug (\"Deleted %s.\\n\", filename);\n        add_event_to_queue (status, WT_EVENT_DELETE, filename, NULL);\n    }\n\nout:\n    g_free (filename);\n    g_atomic_int_set (&info->status->last_changed, (gint)time(NULL));\n\n}\n\nstatic gboolean\nprocess_events (const char *repo_id, RepoWatchInfo *info,\n                char *event_buf, unsigned int buf_size)\n{\n    PFILE_NOTIFY_INFORMATION event;\n\n    int offset = 0;\n    while (1) {\n        event = (PFILE_NOTIFY_INFORMATION)&event_buf[offset];\n        offset += event->NextEntryOffset;\n\n        process_one_event (info, info->worktree,\n                           event, (event->NextEntryOffset == 0));\n\n        if (!event->NextEntryOffset)\n            break;\n    }\n\n    return TRUE;\n}\n\nstatic void *\nwt_monitor_job_win32 (void *vmonitor)\n{\n    SeafWTMonitor *monitor = vmonitor;\n    SeafWTMonitorPriv *priv = monitor->priv;\n    /* 2 * sizeof(inotify_event) + 256, should be large enough for one event.*/\n    RepoWatchInfo *info;\n\n\n    DWORD bytesRead = 0;\n    ULONG_PTR key = 0;\n    OVERLAPPED *ol = NULL;\n\n    /* Use I/O Completion Port to watch asynchronous events on:\n\n     * 1) dir watch handles(events created by ReadDirectoryChangesW)\n     * 2) the cmd pipe (which is a socket indeed)\n     \n     */\n\n    if (!add_all_to_iocp(monitor)) {\n        seaf_warning(\"Failed to add all to iocp\\n\");\n        return NULL;\n    }\n    \n    while (1) {\n\n        BOOL ret = GetQueuedCompletionStatus\n            (priv->iocp_handle,           /* iocp handle */\n             &bytesRead,                  /* length of info */\n             &key,                        /* completion key */\n             &ol,                         /* OVERLAPPED */\n             INFINITE);                   /* timeout */\n\n        static int retry;\n\n        if (!ret) {\n            seaf_warning (\"GetQueuedCompletionStatus failed, \"\n                          \"error code %lu\", GetLastError());\n\n            if (retry++ < 3)\n                continue;\n            else\n                break;\n        } else {\n            /* clear the retry counter on success */\n            retry = 0;\n        }\n\n        if (key == (ULONG_PTR)monitor->cmd_pipe[0]) {     \n            /* Triggered by a cmd pipe event */\n\n            priv->cmd_bytes_read += (int)bytesRead;\n            if (priv->cmd_bytes_read != sizeof(WatchCommand)) {\n                reset_overlapped(ol);\n                start_watch_cmd_pipe (monitor, ol);\n                continue;\n            } else\n                priv->cmd_bytes_read = 0;\n\n            seaf_debug (\"recevied a pipe cmd, type %d for repo %s\\n\",\n                        priv->cmd.type, priv->cmd.repo_id);\n\n            handle_watch_command (monitor, &priv->cmd);\n\n            reset_overlapped(ol);\n            start_watch_cmd_pipe (monitor, ol);\n\n        } else {\n            /* Trigger by one of the dir watch handles */\n\n            HANDLE hTriggered = (HANDLE)key;\n            info = (RepoWatchInfo *)g_hash_table_lookup\n                (priv->info_hash, (gconstpointer)hTriggered); \n\n            if (info) {\n                DirWatchAux *aux = g_hash_table_lookup (priv->buf_hash,\n                                                        (gconstpointer)hTriggered);\n\n                process_events (info->status->repo_id, info, aux->buf, bytesRead);\n\n                reset_overlapped(ol);\n                if (!start_watch_dir_change(priv, hTriggered)) {\n\n                    seaf_warning (\"start_watch_dir_change failed\"\n                                  \"for repo %s, error code %lu\\n\",\n                                  info->status->repo_id, GetLastError());\n                }\n            } else {\n                /* A previously unwatched dir_handle's DirWatchAux buf was\n                   scheduled to be freed. */\n                g_hash_table_remove (priv->buf_hash, (gconstpointer)hTriggered);\n            }\n        }\n    }\n    return NULL;\n}\n\n/* Get the HANDLE of a repo directory, for latter use in\n * ReadDirectoryChangesW(). This handle should be closed when the repo is\n * unwatched.\n */\nstatic HANDLE\nget_handle_of_path(const wchar_t *path)\n{\n    HANDLE dir_handle = NULL;\n\n    dir_handle = CreateFileW\n        (path,                  /* file name */\n         FILE_LIST_DIRECTORY,   /* desired access */\n         FILE_SHARE_DELETE | FILE_SHARE_READ\n         | FILE_SHARE_WRITE,    /* share mode */\n         NULL,                  /* securitry attr */\n         OPEN_EXISTING,         /* open options */\n         FILE_FLAG_BACKUP_SEMANTICS |\n         FILE_FLAG_OVERLAPPED,  /* flags needed for asynchronous IO*/\n         NULL);                 /* template file */\n\n    if (dir_handle == INVALID_HANDLE_VALUE) {\n        char *path_utf8 = g_utf16_to_utf8 (path, -1, NULL, NULL, NULL);\n        seaf_warning(\"failed to create dir handle for path %s, \"\n                     \"error code %lu\", path_utf8, GetLastError());\n        g_free (path_utf8);\n        return NULL;\n    }\n\n    return dir_handle;\n}\n\nstatic HANDLE add_watch (SeafWTMonitorPriv *priv,\n                         const char *repo_id,\n                         const char *worktree)\n{\n    HANDLE dir_handle = NULL;\n    wchar_t *path = NULL;\n    RepoWatchInfo *info;\n\n    /* worktree is in utf8, need to convert to wchar in win32 */\n    path = wchar_from_utf8 (worktree);\n\n    dir_handle = get_handle_of_path (path);\n    if (!dir_handle) {\n        seaf_warning (\"failed to open handle for worktree \"\n                      \"of repo  %s\\n\", repo_id);\n        g_free (path);\n        return NULL;\n    }\n    g_free (path);\n\n    pthread_mutex_lock (&priv->hash_lock);\n    g_hash_table_insert (priv->handle_hash,\n                         g_strdup(repo_id), (gpointer)dir_handle);\n\n    info = create_repo_watch_info (repo_id, worktree);\n    g_hash_table_insert (priv->info_hash, (gpointer)dir_handle, info);\n    pthread_mutex_unlock (&priv->hash_lock);\n\n    add_event_to_queue (info->status, WT_EVENT_SCAN_DIR, \"\", NULL);\n\n    return dir_handle;\n}\n\nstatic int handle_add_repo (SeafWTMonitor *monitor,\n                            const char *repo_id,\n                            const char *worktree)\n{\n    HANDLE handle;\n\n    handle = add_watch (monitor->priv, repo_id, worktree);\n    if (handle == NULL ||\n        !add_handle_to_iocp(monitor, handle)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int handle_rm_repo (SeafWTMonitorPriv *priv, const char *repo_id, gpointer handle)\n{\n    pthread_mutex_lock (&priv->hash_lock);\n    g_hash_table_remove (priv->handle_hash, repo_id);\n    g_hash_table_remove (priv->info_hash, handle);\n    pthread_mutex_unlock (&priv->hash_lock);\n\n    /* `aux' can't be freed here. Once we we close the dir_handle, its\n     *  outstanding io would cause GetQueuedCompletionStatus() to return some\n     *  information in aux->buf. If we free it here, it would cause seg fault.\n     *  It will be freed in the completion code of GetQueuedCompletionStatus().\n     */\n    CloseHandle (handle);\n\n    return 0;\n}\n\nstatic int handle_refresh_repo (SeafWTMonitorPriv *priv, const char *repo_id)\n{\n    return 0;\n}\n\nstatic void\nreply_watch_command (SeafWTMonitor *monitor, int result)\n{\n    int n;\n\n    n = seaf_pipe_writen (monitor->res_pipe[1], &result, sizeof(int));\n    if (n != sizeof(int))\n        seaf_warning (\"[wt mon] fail to write command result.\\n\");\n}\n\nstatic void\nhandle_watch_command (SeafWTMonitor *monitor, WatchCommand *cmd)\n{\n    SeafWTMonitorPriv *priv = monitor->priv;\n\n    if (cmd->type == CMD_ADD_WATCH) {\n        if (g_hash_table_lookup_extended (priv->handle_hash, cmd->repo_id,\n                                          NULL, NULL)) {\n            reply_watch_command (monitor, 0);\n            return;\n        }\n\n        if (handle_add_repo(monitor, cmd->repo_id, cmd->worktree) < 0) {\n            seaf_warning (\"[wt mon] failed to watch worktree of repo %s.\\n\",\n                          cmd->repo_id);\n            reply_watch_command (monitor, -1);\n            return;\n        }\n\n        seaf_debug (\"[wt mon] add watch for repo %s\\n\", cmd->repo_id);\n        reply_watch_command (monitor, 0);\n    } else if (cmd->type == CMD_DELETE_WATCH) {\n        gpointer key, value;\n        if (!g_hash_table_lookup_extended (priv->handle_hash, cmd->repo_id,\n                                           &key, &value)) {\n            reply_watch_command (monitor, 0);\n            return;\n        }\n\n        handle_rm_repo (priv, cmd->repo_id, value);\n        reply_watch_command (monitor, 0);\n    } else if (cmd->type ==  CMD_REFRESH_WATCH) {\n        if (handle_refresh_repo (priv, cmd->repo_id) < 0) {\n            seaf_warning (\"[wt mon] failed to refresh watch of repo %s.\\n\",\n                          cmd->repo_id);\n            reply_watch_command (monitor, -1);\n            return;\n        }\n        reply_watch_command (monitor, 0);\n    }\n}\n\n/* Public interface functions. */\n\nSeafWTMonitor *\nseaf_wt_monitor_new (SeafileSession *seaf)\n{\n    SeafWTMonitor *monitor = g_new0 (SeafWTMonitor, 1);\n    SeafWTMonitorPriv *priv = g_new0 (SeafWTMonitorPriv, 1);\n\n    pthread_mutex_init (&priv->hash_lock, NULL);\n\n    priv->handle_hash = g_hash_table_new_full\n        (g_str_hash, g_str_equal, g_free, NULL);\n\n    priv->info_hash = g_hash_table_new_full\n        (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)free_repo_watch_info);\n\n    priv->buf_hash = g_hash_table_new_full\n        (g_direct_hash, g_direct_equal, NULL, g_free);\n\n    monitor->priv = priv;\n    monitor->seaf = seaf;\n\n    monitor->job_func = wt_monitor_job_win32;\n\n    return monitor;\n}\n\nWTStatus *\nseaf_wt_monitor_get_worktree_status (SeafWTMonitor *monitor,\n                                     const char *repo_id)\n{\n    SeafWTMonitorPriv *priv = monitor->priv;\n    gpointer key, value;\n    RepoWatchInfo *info;\n\n    pthread_mutex_lock (&priv->hash_lock);\n\n    if (!g_hash_table_lookup_extended (priv->handle_hash, repo_id,\n                                       &key, &value)) {\n        pthread_mutex_unlock (&priv->hash_lock);\n        return NULL;\n    }\n\n    info = g_hash_table_lookup(priv->info_hash, value);\n    wt_status_ref (info->status);\n\n    pthread_mutex_unlock (&priv->hash_lock);\n\n    return info->status;\n}\n"
  },
  {
    "path": "daemon/wt-monitor.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#include \"seafile-session.h\"\n\n#include \"utils.h\"\n#include \"wt-monitor.h\"\n#define DEBUG_FLAG SEAFILE_DEBUG_WATCH\n#include \"log.h\"\n\n#include \"job-mgr.h\"\n\nint\nseaf_wt_monitor_start (SeafWTMonitor *monitor)\n{\n    if (seaf_pipe (monitor->cmd_pipe) < 0) {\n        seaf_warning (\"[wt mon] failed to create command pipe: %s.\\n\",\n                      strerror(errno));\n        return -1;\n    }\n\n    if (seaf_pipe (monitor->res_pipe) < 0) {\n        seaf_warning (\"[wt mon] failed to create result pipe: %s.\\n\",\n                      strerror(errno));\n        return -1;\n    }\n\n    if (seaf_job_manager_schedule_job (monitor->seaf->job_mgr,\n                                       monitor->job_func,\n                                       NULL, monitor) < 0) {\n        seaf_warning (\"[wt mon] failed to start monitor thread.\\n\");\n        return -1;\n    }\n\n    return 0;\n}\n\nint\nseaf_wt_monitor_watch_repo (SeafWTMonitor *monitor,\n                            const char *repo_id,\n                            const char *worktree)\n{\n    WatchCommand cmd;\n    int res;\n\n    memset (&cmd, 0, sizeof(cmd));\n    memcpy (cmd.repo_id, repo_id, 37);\n    cmd.type = CMD_ADD_WATCH;\n    g_strlcpy (cmd.worktree, worktree, SEAF_PATH_MAX);\n\n    int n = seaf_pipe_writen (monitor->cmd_pipe[1], &cmd, sizeof(cmd));\n    \n    if (n != sizeof(cmd)) {\n        seaf_warning (\"[wt mon] fail to write command pipe.\\n\");\n        return -1;\n    }\n\n    seaf_debug (\"send a watch command, repo %s\\n\", repo_id);\n\n    n = seaf_pipe_readn (monitor->res_pipe[0], &res, sizeof(int));\n    if (n != sizeof(int)) {\n        seaf_warning (\"[wt mon] fail to read result pipe.\\n\");\n        return -1;\n    }\n\n    return res;\n}\n\nint\nseaf_wt_monitor_unwatch_repo (SeafWTMonitor *monitor, const char *repo_id)\n{\n    WatchCommand cmd;\n    int res;\n\n    memset (&cmd, 0, sizeof(cmd));\n    memcpy (cmd.repo_id, repo_id, 37);\n    cmd.type = CMD_DELETE_WATCH;\n\n    int n = seaf_pipe_writen (monitor->cmd_pipe[1], &cmd, sizeof(cmd));\n\n    if (n != sizeof(cmd)) {\n        seaf_warning (\"[wt mon] fail to write command pipe.\\n\");\n        return -1;\n    }\n\n    seaf_debug (\"send an unwatch command, repo %s\\n\", repo_id);\n\n    n = seaf_pipe_readn (monitor->res_pipe[0], &res, sizeof(int));\n    if (n != sizeof(int)) {\n        seaf_warning (\"[wt mon] fail to read result pipe.\\n\");\n        return -1;\n    }\n\n    return res;\n}\n\nint\nseaf_wt_monitor_refresh_repo (SeafWTMonitor *monitor, const char *repo_id)\n{\n    WatchCommand cmd;\n    int res;\n\n    memset (&cmd, 0, sizeof(cmd));\n    memcpy (cmd.repo_id, repo_id, 37);\n    cmd.type = CMD_REFRESH_WATCH;\n\n    int n = seaf_pipe_writen (monitor->cmd_pipe[1], &cmd, sizeof(cmd));\n\n    if (n != sizeof(cmd)) {\n        seaf_warning (\"[wt mon] fail to write command pipe.\\n\");\n        return -1;\n    }\n\n    seaf_debug (\"send a refresh command, repo %s\\n\", repo_id);\n\n    n = seaf_pipe_readn (monitor->res_pipe[0], &res, sizeof(int));\n    if (n != sizeof(int)) {\n        seaf_warning (\"[wt mon] fail to read result pipe.\\n\");\n        return -1;\n    }\n\n    return res;\n}\n"
  },
  {
    "path": "daemon/wt-monitor.h",
    "content": "#ifndef SEAF_WT_MONITOR_H\n#define SEAF_WT_MONITOR_H\n\n#include \"wt-monitor-structs.h\"\n#include \"utils.h\"\n\ntypedef struct SeafWTMonitorPriv SeafWTMonitorPriv;\n\nstruct _SeafileSession;\n\ntypedef enum CommandType {\n    CMD_ADD_WATCH,\n    CMD_DELETE_WATCH,\n    CMD_REFRESH_WATCH,\n    N_CMD_TYPES,\n} CommandType;\n\ntypedef struct WatchCommand {\n    CommandType type;\n    char repo_id[37];\n    char worktree[SEAF_PATH_MAX];\n} WatchCommand;\n\ntypedef struct SeafWTMonitor {\n    struct _SeafileSession      *seaf;\n    SeafWTMonitorPriv   *priv;\n\n    seaf_pipe_t cmd_pipe[2];\n    seaf_pipe_t res_pipe[2];\n\n    /* platform dependent virtual functions */\n    void* (*job_func) (void *);\n} SeafWTMonitor;\n\nSeafWTMonitor *\nseaf_wt_monitor_new (struct _SeafileSession *seaf);\n\nint\nseaf_wt_monitor_start (SeafWTMonitor *monitor);\n\nint\nseaf_wt_monitor_watch_repo (SeafWTMonitor *monitor,\n                            const char *repo_id,\n                            const char *worktree);\n\nint\nseaf_wt_monitor_unwatch_repo (SeafWTMonitor *monitor, const char *repo_id);\n\nint\nseaf_wt_monitor_refresh_repo (SeafWTMonitor *monitor, const char *repo_id);\n\nWTStatus *\nseaf_wt_monitor_get_worktree_status (SeafWTMonitor *monitor,\n                                     const char *repo_id);\n\n#endif\n"
  },
  {
    "path": "debian/README.Debian",
    "content": "Seafile\n-------\n\nFor more information about Seafile, please visit http://seafile.com\n\n -- plt <freeplant@gmail.com>  Fri, 30 March 2012 16:43:10 +0800\n"
  },
  {
    "path": "debian/changelog",
    "content": "seafile-daemon (9.0.16) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Wed, 07 Jan 2026 10:21:41 +0800\nseafile-daemon (9.0.15) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Wed, 06 Aug 2025 13:48:26 +0800\nseafile-daemon (9.0.14) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Mon, 09 Jun 2025 10:54:39 +0800\nseafile-daemon (9.0.13) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Fri, 28 Mar 2025 17:20:19 +0800\nseafile-daemon (9.0.12) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Wed, 12 Feb 2025 10:53:45 +0800\nseafile-daemon (9.0.11) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Wed, 13 Nov 2024 14:54:33 +0800\nseafile-daemon (9.0.10) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Sat, 09 Nov 2024 14:23:19 +0800\nseafile-daemon (9.0.9) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Thu, 17 Oct 2024 10:18:59 +0800\nseafile-daemon (9.0.8) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Fri, 09 Aug 2024 10:36:03 +0800\nseafile-daemon (9.0.7) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Mon, 15 Jul 2024 14:16:20 +0800\nseafile-daemon (9.0.6) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Mon, 20 May 2024 14:04:58 +0800\nseafile-daemon (9.0.5) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Mon, 19 Feb 2024 13:16:46 +0800\nseafile-daemon (9.0.4) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Fri, 08 Sep  2023 09:48:04 +0800\nseafile-daemon (9.0.3) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 27 Jun  2023 17:29:54 +0800\nseafile-daemon (9.0.2-1) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Thur, 18 May  2023 11:55:23 +0800\nseafile-daemon (9.0.2) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 25 Apr  2023 11:55:23 +0800\nseafile-daemon (9.0.1) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 21 Mar  2023 15:08:01 +0800\nseafile-daemon (9.0.0) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Fri, 03 Mar  2023 13:02:45 +0800\nseafile-daemon (8.0.10) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Sat, 24 Dec  2022 14:53:19 +0800\nseafile-daemon (8.0.9) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Thu, 27 Oct  2022 11:35:08 +0800\nseafile-daemon (8.0.8) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Thu, 30 Jun  2022 10:31:41 +0800\nseafile-daemon (8.0.7) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Sun, 24 Apr  2022 10:34:42 +0800\nseafile-daemon (8.0.6) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Wed, 23 Feb  2022 17:21:27 +0800\nseafile-daemon (8.0.5) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Mon, 15 Nov  2021 17:36:48 +0800\nseafile-daemon (8.0.4) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Mon, 13 Sep  2021 15:58:21 +0800\nseafile-daemon (8.0.3) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 24 Jun  2021 11:26:24 +0800\nseafile-daemon (8.0.2) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Fri, 19 Feb  2021 13:32:13 +0800\nseafile-daemon (8.0.1) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Wed, 9 Dec  2020 13:35:13 +0800\nseafile-daemon (8.0.0) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Wed, 25 Nov  2020 15:26:48 +0800\nseafile-daemon (7.0.9) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Wed, 29 Jul  2020 10:28:58 +0800\nseafile-daemon (7.0.8) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 2 Jun  2020 16:54:38 +0800\nseafile-daemon (7.0.7) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Wed, 1 Apr  2020 09:57:00 +0800\nseafile-daemon (7.0.6) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Thur, 13 Feb  2020 12:33:34 +0800\nseafile-daemon (7.0.5) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Sat, 11 Jan  2020 10:55:29 +0800\nseafile-daemon (7.0.4) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 19 Nov  2019 12:02:10 +0800\nseafile-daemon (7.0.3) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Fri, 18 Oct  2019 15:02:10 +0800\nseafile-daemon (7.0.2) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Wed, 7 Aug  2019 11:16:10 +0800\nseafile-daemon (7.0.1) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 9 Jul  2019 18:22:10 +0800\nseafile-daemon (7.0.0) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 28 May  2019 10:33:10 +0800\nseafile-daemon (6.2.11) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Thur, 17 Jan  2019 15:44:10 +0800\nseafile-daemon (6.2.10) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 15 Jan  2019 16:25:10 +0800\nseafile-daemon (6.2.9) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Sat, 8 Dec  2018 11:12:10 +0800\nseafile-daemon (6.2.8) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 4 Dec  2018 11:44:10 +0800\nseafile-daemon (6.2.7) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 20 Nov  2018 15:58:10 +0800\nseafile-daemon (6.2.5) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 11 Sep  2018 17:43:10 +0800\nseafile-daemon (6.2.4) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Fri, 3 Aug  2018 14:43:10 +0800\nseafile-daemon (6.2.3) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Fri, 27 Jul 2018 14:43:10 +0800\nseafile-daemon (6.2.2) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Thur, 12 Jul 2018 14:35:10 +0800\nseafile-daemon (6.2.1) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Fri, 6 Jul 2018 17:04:10 +0800\nseafile-daemon (6.2.0) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 26 June 2018 17:04:10 +0800\nseafile-daemon (6.1.8) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 8 May 2018 17:16:21 +0800\n\nseafile-daemon (6.1.7) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Thur, 29 Mar 2018 15:16:21 +0800\n\nseafile-daemon (6.1.6) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 13 Mar 2018 12:16:21 +0800\n\nseafile-daemon (6.1.5) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Fri, 2 Feb 2018 13:50:34 +0800\n\nseafile-daemon (6.1.4) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 19 Dec 2017 17:09:34 +0800\n\nseafile-daemon (6.1.3) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Fri, 3 Nov 2017 13:58:10 +0800\n\nseafile-daemon (6.1.2) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Wed, 25 Oct 2017 13:58:10 +0800\n\n\nseafile-daemon (6.1.1) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 19 Sep 2017 18:08:10 +0800\n\nseafile-daemon (6.1.0) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Wed, 2 Aug 2017 16:37:10 +0800\nseafile-daemon (6.0.7) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 20 Jun 2017 16:37:10 +0800\nseafile-daemon (6.0.6) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Fri, 28 Apr 2017 16:37:10 +0800\n\nseafile-daemon (6.0.4.1) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Wed, 15 Mar 2017 16:37:10 +0800\n\nseafile-daemon (6.0.4) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Tue, 21 Feb 2017 13:58:10 +0800\n\nseafile-daemon (6.0.3) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Fri, 10 Feb 2017 15:27:16 +0800\n\nseafile-daemon (6.0.2) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Thu, 5 Jan 2017 14:06:16 +0800\n\nseafile-daemon (6.0.1) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Mon, 12 Dec 2016 14:47:32 +0800\n\nseafile-daemon (6.0.0) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Fri, 14 Oct 2016 13:49:07 +0800\n\nseafile-daemon (5.1.4) unstable; urgency=low\n\n  * new upstream release\n\n -- Jonathan Xu <jonathan.xu@seafile.com>  Fri, 29 Jul 2016 13:49:07 +0800\n\nseafile-daemon (5.1.3-1ubuntu1) UNRELEASED; urgency=medium\n\n  * new upstream release\n\n -- m.eik michalke <meik.michalke@hhu.de>  Thu, 30 Jun 2016 23:59:07 +0200\n\nseafile-daemon (5.1.2-5ubuntu2) unstable; urgency=medium\n\n  * fixed dependencies for seafile-daemon and seafile-cli\n\n -- m.eik michalke <meik.michalke@hhu.de>  Mon, 27 Jun 2016 11:16:33 +0200\n\nseafile-daemon (5.1.2-5ubuntu1) unstable; urgency=medium\n\n  * repackaging with cleaned up orig.tar.xz archives\n  * added symbols file\n\n -- m.eik michalke <meik.michalke@hhu.de>  Fri, 17 Jun 2016 19:04:41 +0200\n\nseafile-daemon (5.1.2-4) unstable; urgency=medium\n\n  * split package into seafile-daemon, libseafile0, libseafile-dev and seafile-cli\n  * updated the debian/copyright notice so people know who's responisble for the packaging\n  * rewrote the rules file, much simpler now\n  * prep for release on github\n\n -- m.eik michalke <meik.michalke@hhu.de>  Wed, 15 Jun 2016 22:38:24 +0200\n"
  },
  {
    "path": "debian/compat",
    "content": "7\n"
  },
  {
    "path": "debian/control",
    "content": "Source: seafile-daemon\nSection: net\nPriority: extra\nMaintainer: m.eik michalke <meik.michalke@hhu.de>\nBuild-Depends:\n    debhelper (>= 7),\n    dh-python,\n    autotools-dev,\n    libssl-dev,\n    libsqlite3-dev,\n    intltool,\n    libglib2.0-dev,\n    libevent-dev,\n    uuid-dev,\n    libtool,\n    libcurl4-openssl-dev,\n    valac,\n    libjansson-dev,\n    python3 (>= 3.5),\n    libsearpc-dev (>= 3.1.0)\n    libwebsockets-dev (>= 4.0.20)\nStandards-Version: 3.9.5\nHomepage: http://seafile.com\n\nPackage: seafile-daemon\nSection: net\nArchitecture: any\nDepends:\n    ${shlibs:Depends},\n    ${misc:Depends},\n    ${python3:Depends},\nConflicts: seafile\nSuggests: seafile-gui, seafile-cli\nDescription: Seafile daemon\n File syncing and sharing software with file encryption and group\n sharing, emphasis on reliability and high performance.\n .\n This package contains the Seafile daemon.\n\nPackage: libseafile0\nSection: libs\nArchitecture: any\nDepends:\n    ${shlibs:Depends},\n    ${misc:Depends},\n    ${python3:Depends}\nConflicts: seafile\nDescription: Shared libraries for Seafile\n This package contains the shared libraries for the Seafile daemon.\n\nPackage: seafile-cli\nSection: utils\nArchitecture: any\nDepends:\n    ${shlibs:Depends},\n    ${misc:Depends},\n    ${python3:Depends},\n    libseafile0 (>= ${binary:Version}),\n    seafile-daemon (>= ${binary:Version}),\n    python-searpc (>= 3.1.0)\nConflicts: seafile\nDescription: Seafile command line interface.\n This package contains the command line interface to manage Seafile libraries.\n\nPackage: libseafile-dev\nSection: libdevel\nArchitecture: any\nDepends:\n    ${misc:Depends},\n    libseafile0 (= ${binary:Version})\nConflicts: seafile\nDescription: Development files for package libseafile0.\n This package contains the development files for the libseafile0 package.\n\nPackage: seafile-daemon-dbg\nSection: debug\nArchitecture: any\nDepends:\n    seafile-daemon (= ${binary:Version}),\n    ${misc:Depends},\nDescription: Debugging symbols for the seafile-daemon package.\n This package contains the debugging symbols for the seafile-daemon package.\n\nPackage: libseafile-dbg\nSection: debug\nArchitecture: any\nDepends:\n    libseafile0 (= ${binary:Version}),\n    ${misc:Depends},\nDescription: Debugging symbols for the libseafile0 package.\n This package contains the debugging symbols for the libseafile0 package.\n"
  },
  {
    "path": "debian/copyright",
    "content": "Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nUpstream-Name: seafile\nUpstream-Contact: Lingtao Pan <freeplant@gmail.com>\nSource: https://github.com/haiwen/seafile\n\nFiles: *\nCopyright: 2012 plt\nLicense: GPL-2 with OpenSSL exception\n  This program is released under GPL-2, with the following addition\n  permission to link with OpenSSL library.\n  .\n  If you modify this program, or any covered work, by linking or\n  combining it with the OpenSSL project's OpenSSL library (or a\n  modified version of that library), containing parts covered by the\n  terms of the OpenSSL or SSLeay licenses, Seafile Ltd.\n  grants you additional permission to convey the resulting work.\n  Corresponding Source for a non-source form of such a combination\n  shall include the source code for the parts of OpenSSL used as well\n  as that of the covered work.\n  .\n  This software is distributed in the hope that it will be useful, but\n  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n  or FITNESS FOR A PARTICULAR PURPOSE.\n  .\n  You should have received a copy of the license with your Debian system,\n  in the file /usr/share/common-licenses/GPL-2, or with the\n  source package as the file COPYING or LICENSE.\n\nFiles: debian/*\nCopyright: 2016 m.eik michalke\nLicense: GPL-2\n  This program is free software; you can redistribute it\n  and/or modify it under the terms of the GNU General Public\n  License as published by the Free Software Foundation; either\n  version 2 of the License, or (at your option) any later\n  version.\n  .\n  This software is distributed in the hope that it will be useful, but\n  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n  or FITNESS FOR A PARTICULAR PURPOSE.\n  .\n  You should have received a copy of the license with your Debian system,\n  in the file /usr/share/common-licenses/GPL-2, or with the\n  source package as the file COPYING or LICENSE.\n"
  },
  {
    "path": "debian/dirs",
    "content": "usr/bin"
  },
  {
    "path": "debian/docs",
    "content": ""
  },
  {
    "path": "debian/libseafile-dev.install",
    "content": "usr/include/seafile\nusr/lib/pkgconfig\nusr/lib/libseafile.a\nusr/lib/libseafile.la\nusr/lib/libseafile.so\n"
  },
  {
    "path": "debian/libseafile0.install",
    "content": "usr/lib/*.so.*\nusr/lib/python3*/site-packages/seafile/*.py\n"
  },
  {
    "path": "debian/patches/fix-pkgconfig-paths.patch",
    "content": "Fixing wrong pkgconfig paths.\n--- a/lib/libseafile.pc.in\n+++ b/lib/libseafile.pc.in\n@@ -1,4 +1,4 @@\n-prefix=(DESTDIR)@prefix@\n+prefix=@prefix@\n exec_prefix=@exec_prefix@\n libdir=@libdir@\n includedir=@includedir@\n--- a/lib/Makefile.am\n+++ b/lib/Makefile.am\n@@ -85,10 +85,3 @@\n \trm -f rpc_table.stamp\n \trm -f rpc_table.tmp\n \trm -f vala.tmp vala.stamp ${valac_gen}\n-\n-install-data-local:\n-if MACOS\n-\tsed -i '' -e \"s|(DESTDIR)|${DESTDIR}|g\" $(pcfiles)\n-else\n-\t${SED} -i \"s|(DESTDIR)|${DESTDIR}|g\" $(pcfiles)\n-endif\n"
  },
  {
    "path": "debian/patches/series",
    "content": "fix-pkgconfig-paths.patch\n"
  },
  {
    "path": "debian/rules",
    "content": "#!/usr/bin/make -f\n# -*- makefile -*-\n\n%:\n\tdh $@ --with python3 --with autotools_dev\n\noverride_dh_auto_configure:\n\t./autogen.sh\n\tdh_auto_configure -- --disable-fuse --with-python3\n\noverride_dh_auto_test:\n# make check seems to be broken\n\noverride_dh_strip:\n\t# emptying the dependency_libs field in .la files\n\tsed -i \"/dependency_libs/ s/'.*'/''/\" `find debian/ -name '*.la'`\n\tdh_strip -pseafile-daemon --dbg-package=seafile-daemon-dbg\n\tdh_strip -plibseafile0 --dbg-package=libseafile-dbg\n"
  },
  {
    "path": "debian/seafile-cli.install",
    "content": "usr/bin/seaf-cli\nusr/share/man/man1/seaf-cli.1\n"
  },
  {
    "path": "debian/seafile-daemon.install",
    "content": "usr/bin/seaf-daemon\nusr/share/man/man1/seaf-daemon.1\n"
  },
  {
    "path": "debian/source/format",
    "content": "3.0 (quilt)\n"
  },
  {
    "path": "dmg/seafileLayout/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>content</key>\n\t<dict>\n\t\t<key>identifier</key>\n\t\t<string>Layout.B0B66CEF-E35C-48F3-8D66-172544C96704</string>\n\t\t<key>options</key>\n\t\t<dict>\n\t\t\t<key>backgroundImageName</key>\n\t\t\t<string>dmg_background.jpg</string>\n\t\t\t<key>fontSize</key>\n\t\t\t<integer>12</integer>\n\t\t\t<key>iconSize</key>\n\t\t\t<integer>128</integer>\n\t\t\t<key>layoutItems</key>\n\t\t\t<array>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>identifier</key>\n\t\t\t\t\t<string>LayoutItem.7DCEB33C-3B26-463C-9D52-D2E99ADD0AD0</string>\n\t\t\t\t\t<key>name</key>\n\t\t\t\t\t<string>Applications</string>\n\t\t\t\t\t<key>position</key>\n\t\t\t\t\t<string>{426, 190}</string>\n\t\t\t\t\t<key>type</key>\n\t\t\t\t\t<string>file</string>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>identifier</key>\n\t\t\t\t\t<string>LayoutItem.042D24CE-36D9-4328-A97F-BCF3DC524509</string>\n\t\t\t\t\t<key>name</key>\n\t\t\t\t\t<string>Seafile Client.app</string>\n\t\t\t\t\t<key>position</key>\n\t\t\t\t\t<string>{219, 190}</string>\n\t\t\t\t\t<key>type</key>\n\t\t\t\t\t<string>file</string>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>identifier</key>\n\t\t\t\t\t<string>LayoutItem.CFA83A1B-E783-4354-88A9-255F0F623398</string>\n\t\t\t\t\t<key>name</key>\n\t\t\t\t\t<string>.Trashes</string>\n\t\t\t\t\t<key>position</key>\n\t\t\t\t\t<string>{128, 426}</string>\n\t\t\t\t\t<key>type</key>\n\t\t\t\t\t<string>file</string>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>identifier</key>\n\t\t\t\t\t<string>LayoutItem.830478CF-49D6-4772-AC6C-3A56A329536F</string>\n\t\t\t\t\t<key>name</key>\n\t\t\t\t\t<string>.fseventsd</string>\n\t\t\t\t\t<key>position</key>\n\t\t\t\t\t<string>{336, 426}</string>\n\t\t\t\t\t<key>type</key>\n\t\t\t\t\t<string>file</string>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>identifier</key>\n\t\t\t\t\t<string>LayoutItem.E417B39C-5B93-4D7D-AE5A-FC3B0CDFD9D7</string>\n\t\t\t\t\t<key>name</key>\n\t\t\t\t\t<string>.DropDMGBackground</string>\n\t\t\t\t\t<key>position</key>\n\t\t\t\t\t<string>{400, 426}</string>\n\t\t\t\t\t<key>type</key>\n\t\t\t\t\t<string>file</string>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>identifier</key>\n\t\t\t\t\t<string>LayoutItem.209D5BB3-143C-4DB8-8FB9-F1175B8184F2</string>\n\t\t\t\t\t<key>name</key>\n\t\t\t\t\t<string>.DS_Store</string>\n\t\t\t\t\t<key>position</key>\n\t\t\t\t\t<string>{64, 426}</string>\n\t\t\t\t\t<key>type</key>\n\t\t\t\t\t<string>file</string>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>identifier</key>\n\t\t\t\t\t<string>LayoutItem.D20D1139-FE25-445E-BBE3-E5374F90A06E</string>\n\t\t\t\t\t<key>position</key>\n\t\t\t\t\t<string>{340, 388}</string>\n\t\t\t\t\t<key>rtf</key>\n\t\t\t\t\t<string>{\\rtf1\\ansi\\ansicpg1252\\cocoartf1138\\cocoasubrtf470\n{\\fonttbl\\f0\\fswiss\\fcharset0 Helvetica;}\n{\\colortbl;\\red255\\green255\\blue255;}\n\\pard\\tx560\\tx1120\\tx1680\\tx2240\\tx2800\\tx3360\\tx3920\\tx4480\\tx5040\\tx5600\\tx6160\\tx6720\\pardirnatural\n\n\\f0\\fs24 \\cf0 Drag Seafile Client to the Applications folder to install}</string>\n\t\t\t\t\t<key>size</key>\n\t\t\t\t\t<string>{282, 30}</string>\n\t\t\t\t\t<key>type</key>\n\t\t\t\t\t<string>text</string>\n\t\t\t\t</dict>\n\t\t\t</array>\n\t\t\t<key>windowOriginTopLeft</key>\n\t\t\t<string>{100, 100}</string>\n\t\t</dict>\n\t</dict>\n\t<key>documentCreator</key>\n\t<string>DropDMG 3.1.1</string>\n\t<key>documentType</key>\n\t<string>com.c-command.DropDMG.Layout</string>\n\t<key>formatVersion</key>\n\t<integer>1</integer>\n</dict>\n</plist>\n"
  },
  {
    "path": "doc/Makefile.am",
    "content": "CLIENT_MANUALS = seaf-daemon.1 seaf-cli.1\n\ndist_man1_MANS = $(CLIENT_MANUALS)\n\nEXTRA_DIST = cli-readme.txt\n"
  },
  {
    "path": "doc/cli-readme.txt",
    "content": "                         Seafile command line client\n                         ===========================\n\nFor the full manual about seafile CLI client, see [https://github.com/haiwen/seafile/wiki/Seafile-CLI-client]\n\nTable of Contents\n=================\n1 Requirement:\n2 Get Started\n    2.1 Initialize\n    2.2 Start seafile client\n    2.3 Download a library from a server\n    2.4 stop seafile client\n3 Uninstall\n\n\n1 Requirement:\n---------------\n\n  - python 2.6/2.7\n\n  - If you use python 2.6, you need to install python \"argparse\" module\n\n    see [https://pypi.python.org/pypi/argparse]\n\n\n2 Get Started\n--------------\n\n\n2.1 Initialize\n===============\n\n  mkdir ~/seafile-client\n  ./seaf-cli init -d ~/seafile-client\n\n\n2.2 Start seafile client\n=========================\n\n\n\n  ./seaf-cli start\n\n\n\n2.3 Download a library from a server\n=====================================\n\n   First retrieve the library id by browsing on the server -> it's in the url after \"/repo/\"\n\n   Then:\n\n   seaf-cli download -l \"the id of the library\" -s  \"the url + port of server\" -d \"the folder where the library folder will be downloaded\" -u \"username on server\" [-p \"password\"]\n\n   seaf-cli status  # check status of ongoing downloads\n\n   # Name  Status  Progress\n   # Apps    downloading     9984/10367, 9216.1KB/s\n\n\n2.4 stop seafile client\n========================\n\n  ./seaf-cli stop\n\n3 Uninstall\n------------\n\n  First stop the client:\n\n  seaf-cli stop\n\n  Then remove the data:\n\n\n  rm -rf ~/.seafile-client\n\n  rm -rf ~/.ccnet   # note this should not be erased if you run the server on the same host\n\n  rm -rf seafile-cli-1.5.3\n\n"
  },
  {
    "path": "doc/seaf-cli.1",
    "content": ".\\\" Manpage for seafile-client\n.\\\" Contact freeplant@gmail.com to correct errors or typos.\n.TH seafile 1 \"31 Jan 2013\" \"Linux\" \"seafile client man page\"\n.SH NAME\nseaf-cli \\- the command line interface for seafile client\n.SH SYNOPSIS\nseaf-cli [OPTIONS]\n.SH DESCRIPTION\n.BR seaf-cli\nis the command line interface for seafile client.\n.SH SEE ALSO\nccnet(1), seafile-applet(1), seaf-daemon(1), seafile-web(1)\n.SH AUTHOR\nLingtao Pan (freeplant@gmail.com)\n.SH WEBSTIE\nhttp://www.seafile.com\n.LP\nhttps://github.com/haiwen/seafile/\n"
  },
  {
    "path": "doc/seaf-daemon.1",
    "content": ".\\\" Manpage for seafile-client\n.\\\" Contact freeplant@gmail.com to correct errors or typos.\n.TH seafile 1 \"31 Jan 2013\" \"Linux\" \"seafile client man page\"\n.SH NAME\nseaf-daemon \\- the daemon of seafile client\n.SH SYNOPSIS\nseaf-daemon [OPTIONS]\n.SH DESCRIPTION\n.BR seafile-daemon\nis the daemon of seafile client.\nIt's started by seafile-applet(1).\n.SH SEE ALSO\nccnet(1), seaf-web(1), seafile-applet(1), seaf-cli(1)\n.SH AUTHOR\nLingtao Pan (freeplant@gmail.com)\n.SH WEBSTIE\nhttp://www.seafile.com\n.LP\nhttps://github.com/haiwen/seafile/\n"
  },
  {
    "path": "include/Makefile.am",
    "content": "\nseafiledir = $(includedir)/seafile\n\nseafile_HEADERS = seafile-rpc.h seafile.h seafile-error.h\n"
  },
  {
    "path": "include/seafile-error.h",
    "content": "#ifndef SEAFILE_ERROR_H\n#define SEAFILE_ERROR_H\n\n/* Error codes used in RPC. */\n\n#define SEAF_ERR_GENERAL        500\n#define SEAF_ERR_BAD_REPO       501\n#define SEAF_ERR_BAD_COMMIT     502\n#define SEAF_ERR_BAD_ARGS       503\n#define SEAF_ERR_INTERNAL       504\n#define SEAF_ERR_BAD_FILE       505\n#define SEAF_ERR_BAD_RELAY      506\n#define SEAF_ERR_LIST_COMMITS   507\n#define SEAF_ERR_REPO_AUTH      508\n#define SEAF_ERR_GC_NOT_STARTED 509\n#define SEAF_ERR_MONITOR_NOT_CONNECTED 510\n#define SEAF_ERR_BAD_DIR_ID     511\n#define SEAF_ERR_NO_WORKTREE    512\n#define SEAF_ERR_BAD_PEER_ID    513\n#define SEAF_ERR_REPO_LOCKED    514\n#define SEAF_ERR_DIR_MISSING    515\n#define SEAF_ERR_PATH_NO_EXIST  516 /* the dir or file pointed by this path not exists */\n\n/* Sync errors. */\n\n#define SYNC_ERROR_ID_FILE_LOCKED_BY_APP        0\n#define SYNC_ERROR_ID_FOLDER_LOCKED_BY_APP      1\n/* When file is locked on server. Returned in update-branch. */\n#define SYNC_ERROR_ID_FILE_LOCKED               2\n#define SYNC_ERROR_ID_INVALID_PATH              3\n#define SYNC_ERROR_ID_INDEX_ERROR               4\n#define SYNC_ERROR_ID_PATH_END_SPACE_PERIOD     5\n#define SYNC_ERROR_ID_PATH_INVALID_CHARACTER    6\n/* Returned in update-branch */\n#define SYNC_ERROR_ID_FOLDER_PERM_DENIED        7\n/* When there is no sync permission to library */\n#define SYNC_ERROR_ID_PERM_NOT_SYNCABLE         8\n/* Local error when updating a file in readonly library. */\n#define SYNC_ERROR_ID_UPDATE_TO_READ_ONLY_REPO  9\n/* When there is no read access to library. */\n#define SYNC_ERROR_ID_ACCESS_DENIED             10\n/* When there is no write access to library */\n#define SYNC_ERROR_ID_NO_WRITE_PERMISSION       11\n#define SYNC_ERROR_ID_QUOTA_FULL                12\n#define SYNC_ERROR_ID_NETWORK                   13\n#define SYNC_ERROR_ID_RESOLVE_PROXY             14\n#define SYNC_ERROR_ID_RESOLVE_HOST              15\n#define SYNC_ERROR_ID_CONNECT                   16\n#define SYNC_ERROR_ID_SSL                       17\n#define SYNC_ERROR_ID_TX                        18\n#define SYNC_ERROR_ID_TX_TIMEOUT                19\n#define SYNC_ERROR_ID_UNHANDLED_REDIRECT        20\n#define SYNC_ERROR_ID_SERVER                    21\n#define SYNC_ERROR_ID_LOCAL_DATA_CORRUPT        22\n#define SYNC_ERROR_ID_WRITE_LOCAL_DATA          23\n#define SYNC_ERROR_ID_SERVER_REPO_DELETED       24\n#define SYNC_ERROR_ID_SERVER_REPO_CORRUPT       25\n#define SYNC_ERROR_ID_NOT_ENOUGH_MEMORY         26\n#define SYNC_ERROR_ID_CONFLICT                  27\n#define SYNC_ERROR_ID_GENERAL_ERROR             28\n#define SYNC_ERROR_ID_NO_ERROR                  29\n#define SYNC_ERROR_ID_REMOVE_UNCOMMITTED_FOLDER 30\n#define SYNC_ERROR_ID_INVALID_PATH_ON_WINDOWS   31\n#define SYNC_ERROR_ID_LIBRARY_TOO_LARGE         32\n#define SYNC_ERROR_ID_DEL_CONFIRMATION_PENDING  33\n#define SYNC_ERROR_ID_TOO_MANY_FILES            34\n#define SYNC_ERROR_ID_CHECKOUT_FILE             35\n#define SYNC_ERROR_ID_BLOCK_MISSING             36\n#define SYNC_ERROR_ID_CASE_CONFLICT             37\n#define SYNC_ERROR_ID_STOPPED_BY_LOGOUT         38\n#define SYNC_ERROR_ID_CORRUPTED_ENC_KEY         39\n#define N_SYNC_ERROR_ID                         40\n\n#endif\n"
  },
  {
    "path": "include/seafile-rpc.h",
    "content": "\n#ifndef _SEAFILE_RPC_H\n#define _SEAFILE_RPC_H\n\n#include <jansson.h>\n#include \"seafile-object.h\"\n\n/**\n * seafile_get_repo_list:\n *\n * Returns repository list.\n */\nGList* seafile_get_repo_list (int start, int limit, GError **error);\n\n/**\n * seafile_get_repo:\n *\n * Returns: repo\n */\nGObject* seafile_get_repo (const gchar* id, GError **error);\n\nGObject *\nseafile_get_repo_sync_task (const char *repo_id, GError **error);\n\n/* [seafile_get_config] returns the value of the config entry whose name is\n * [key] in config.db\n */\nchar *seafile_get_config (const char *key, GError **error);\n\n/* [seafile_set_config] set the value of config key in config.db; old value\n * would be overwritten. */\nint seafile_set_config (const char *key, const char *value, GError **error);\n\nint\nseafile_set_config_int (const char *key, int value, GError **error);\n\nint\nseafile_get_config_int (const char *key, GError **error);\n\nint\nseafile_set_upload_rate_limit (int limit, GError **error);\n\nint\nseafile_set_download_rate_limit (int limit, GError **error);\n\n/**\n * seafile_destroy_repo:\n * @repo_id: repository id.\n */\nint seafile_destroy_repo (const gchar *repo_id, GError **error);\n\nint\nseafile_unsync_repos_by_account (const char *server_url, const char *email, GError **error);\n\nint\nseafile_remove_repo_tokens_by_account (const char *server_url, const char *email, GError **error);\n\nint\nseafile_set_repo_token (const char *repo_id, const char *token, GError **error);\n\nint\nseafile_get_download_rate(GError **error);\n\nint\nseafile_get_upload_rate(GError **error);\n\nint\nseafile_set_repo_property (const char *repo_id,\n                           const char *key,\n                           const char *value,\n                           GError **error);\n\ngchar *\nseafile_get_repo_property (const char *repo_id,\n                           const char *key,\n                           GError **error);\n\nint\nseafile_update_repos_server_host (const char *old_server_url,\n                                  const char *new_server_url,\n                                  GError **error);\n\nint seafile_disable_auto_sync (GError **error);\n\nint seafile_enable_auto_sync (GError **error);\n\nint seafile_is_auto_sync_enabled (GError **error);\n\nchar *\nseafile_get_path_sync_status (const char *repo_id,\n                              const char *path,\n                              int is_dir,\n                              GError **error);\n\nint\nseafile_mark_file_locked (const char *repo_id, const char *path, GError **error);\n\nint\nseafile_mark_file_unlocked (const char *repo_id, const char *path, GError **error);\n\nchar *\nseafile_get_server_property (const char *server_url, const char *key, GError **error);\n\nint\nseafile_set_server_property (const char *server_url,\n                             const char *key,\n                             const char *value,\n                             GError **error);\n\nGList *\nseafile_get_file_sync_errors (int offset, int limit, GError **error);\n\nint\nseafile_del_file_sync_error_by_id (int id, GError **error);\n\nchar *\nseafile_gen_default_worktree (const char *worktree_parent,\n                              const char *repo_name,\n                              GError **error);\nint\nseafile_check_path_for_clone(const char *path, GError **error);\n\n/**\n * seafile_clone:\n *\n * Fetch a new repo and then check it out.\n */\nchar *\nseafile_clone (const char *repo_id, \n               int repo_version,\n               const char *repo_name,\n               const char *worktree,\n               const char *token,\n               const char *passwd,\n               const char *magic,\n               const char *email,\n               const char *random_key,\n               int enc_version,\n               const char *more_info,\n               GError **error);\n\nchar *\nseafile_download (const char *repo_id, \n                  int repo_version,\n                  const char *repo_name,\n                  const char *wt_parent,\n                  const char *token,\n                  const char *passwd,\n                  const char *magic,\n                  const char *email,\n                  const char *random_key,\n                  int enc_version,\n                  const char *more_info,\n                  GError **error);\n\nint\nseafile_cancel_clone_task (const char *repo_id, GError **error);\n\n/**\n * seafile_get_clone_tasks:\n *\n * Get a list of clone tasks.\n */\nGList *\nseafile_get_clone_tasks (GError **error);\n\n/**\n * seafile_sync:\n *\n * Sync a repo with relay.\n */\nint seafile_sync (const char *repo_id, const char *peer_id, GError **error);\n\n/* -----------------  Task Related --------------  */\n\n/**\n * seafile_find_transfer:\n *\n * Find a non finished task of a repo\n */\nGObject *\nseafile_find_transfer_task (const char *repo_id, GError *error);\n\n\nint seafile_cancel_task (const gchar *task_id, int task_type, GError **error);\n\n/**\n * Remove finished upload task\n */\nint seafile_remove_task (const char *task_id, int task_type, GError **error);\n\n\n/* ------------------ Relay specific RPC calls. ------------ */\n\n/**\n * seafile_diff:\n *\n * Show the difference between @old commit and @new commit. If @old is NULL, then\n * show the difference between @new commit and its parent.\n *\n * @old and @new can also be branch name.\n */\nGList *\nseafile_diff (const char *repo_id, const char *old, const char *new,\n              int fold_dir_diff, GError **error);\n\nGObject *\nseafile_generate_magic_and_random_key(int enc_version,\n                                      const char* repo_id,\n                                      const char *passwd,\n                                      const char *pwd_hash_algo,\n                                      const char *pwd_hash_params,\n                                      GError **error);\njson_t * seafile_get_sync_notification (GError **error);\n\nint\nseafile_shutdown (GError **error);\n\nchar*\nseafile_sync_error_id_to_str (int error_id, GError **error);\n\nint\nseafile_add_del_confirmation (const char *confirmation_id, int resync, GError **error);\n#endif\n"
  },
  {
    "path": "include/seafile.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef SEAFILE_H\n#define SEAFILE_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nint seafile_destroy_repo (SearpcClient *client,\n                          const char *repo_id, GError **error);\n\nint seafile_set_repo_token (SearpcClient *client,\n                            const char *repo_id,\n                            const char *token,\n                            GError **error);\n\nchar *\nseafile_get_repo_token (SearpcClient *client,\n                        const char *repo_id,\n                        GError **error);\n\n\nint\nseafile_set_repo_property (SearpcClient *client,\n                           const char *repo_id,\n                           const char *key,\n                           const char *value,\n                           GError **error);\n\nGList *\nseafile_get_repo_list (SearpcClient *client,\n                       int offset,\n                       int limit, GError **error);\n\nGObject *\nseafile_get_repo (SearpcClient *client,\n                  const char *repo_id,\n                  GError **error);\n\n\nchar *seafile_get_config (SearpcClient *client, const char *key, GError **error);\n\nint seafile_calc_dir_size (SearpcClient *client, const char *path, GError **error);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "integration-tests/install-deps.sh",
    "content": "#!/bin/bash\n\nset -e -x\n\nsudo apt update\nsudo apt install -y  autoconf automake libtool libevent-dev libcurl4-openssl-dev \\\n     libgtk2.0-dev uuid-dev intltool libsqlite3-dev valac libjansson-dev cmake libssl-dev\n\ngit clone --depth=1 --branch=\"master\" git://github.com/haiwen/libsearpc.git deps/libsearpc\n\npushd deps/libsearpc\n./autogen.sh && ./configure --disable-fuse --disable-server --enable-client\nmake -j8 && sudo make install\npopd\n"
  },
  {
    "path": "lib/Makefile.am",
    "content": "pcfiles = libseafile.pc\npkgconfig_DATA = $(pcfiles)\npkgconfigdir = $(libdir)/pkgconfig\n\nAM_CFLAGS = @GLIB2_CFLAGS@ -I$(top_srcdir)/include \\\n\t-I$(top_srcdir)/lib \\\n\t-I$(top_srcdir)/common \\\n\t@SEARPC_CFLAGS@ \\\n\t@MSVC_CFLAGS@ \\\n\t-Wall\n\nif MACOS\nif COMPILE_UNIVERSAL\nAM_CFLAGS += -arch x86_64 -arch arm64\nendif\nendif\n\nBUILT_SOURCES = gensource\n\n## source file rules\nseafile_object_define = repo.vala task.vala\n\nseafile_object_gen = $(seafile_object_define:.vala=.c)\n\nvalac_gen = ${seafile_object_gen} seafile-object.h\n\nEXTRA_DIST = ${seafile_object_define} rpc_table.py $(pcfiles) vala.stamp\n\nutils_headers = net.h utils.h db.h\n\nutils_srcs = $(utils_headers:.h=.c)\n\nnoinst_HEADERS = ${utils_headers} include.h \\\n\t\tsearpc-signature.h searpc-marshal.h\n\nseafiledir = $(includedir)/seafile\nseafile_HEADERS = seafile-object.h\n\nseafile-rpc-wrapper.c: seafile-object.h\n\nseafile-object.h: ${seafile_object_define}\n\trm -f $@\n\t@VALAC@ --pkg posix ${seafile_object_define} -C -H seafile-object.h\n\n\n## library rules\nlib_LTLIBRARIES = libseafile.la\n\nlibseafile_la_SOURCES = ${seafile_object_gen} seafile-rpc-wrapper.c\n\nlibseafile_la_LDFLAGS = -no-undefined\nlibseafile_la_LIBADD = @GLIB2_LIBS@  @GOBJECT_LIBS@ @SEARPC_LIBS@\n\nnoinst_LTLIBRARIES = libseafile_common.la\n\nlibseafile_common_la_SOURCES = ${seafile_object_gen} ${utils_srcs}\nlibseafile_common_la_LDFLAGS = -no-undefined\nlibseafile_common_la_LIBADD = @GLIB2_LIBS@  @GOBJECT_LIBS@ @LIB_GDI32@ \\\n\t\t\t\t     @LIB_UUID@ @LIB_WS32@ @LIB_PSAPI@ -lsqlite3 \\\n\t\t\t\t\t @LIBEVENT_LIBS@ @SEARPC_LIBS@ @LIB_SHELL32@ \\\n\t@ZLIB_LIBS@\n\ngensource: ${valac_gen}\n\nvala.stamp: ${seafile_object_define}\n\trm -f ${seafile_object_gen}\n\t@rm -f vala.tmp\n\t@touch vala.tmp\n\t@VALAC@ -C --pkg posix $^\n\t@mv -f vala.tmp $@\n\n${seafile_object_gen}: vala.stamp\n\nclean-local:\n\trm -f vala.tmp vala.stamp ${valac_gen}\n\ninstall-data-local:\nif MACOS\n\tsed -i '' -e \"s|(DESTDIR)|${DESTDIR}|g\" $(pcfiles)\nelse\n\t${SED} -i \"s|(DESTDIR)|${DESTDIR}|g\" $(pcfiles)\nendif\n"
  },
  {
    "path": "lib/db.c",
    "content": "\n#include <glib.h>\n#ifndef WIN32\n#include <unistd.h>\n#endif\n\n#include \"db.h\"\n\nint\nsqlite_open_db (const char *db_path, sqlite3 **db)\n{\n    int result;\n    const char *errmsg;\n\n    result = sqlite3_open (db_path, db);\n    if (result) {\n        errmsg = sqlite3_errmsg (*db);\n                                \n        g_warning (\"Couldn't open database:'%s', %s\\n\", \n                   db_path, errmsg ? errmsg : \"no error given\");\n\n        sqlite3_close (*db);\n        return -1;\n    }\n\n    return 0;\n}\n\nint sqlite_close_db (sqlite3 *db)\n{\n    return sqlite3_close (db);\n}\n\nsqlite3_stmt *\nsqlite_query_prepare (sqlite3 *db, const char *sql)\n{\n    sqlite3_stmt *stmt;\n    int result;\n\n    result = sqlite3_prepare_v2 (db, sql, -1, &stmt, NULL);\n\n    if (result != SQLITE_OK) {\n        const gchar *str = sqlite3_errmsg (db);\n\n        g_warning (\"Couldn't prepare query, error:%d->'%s'\\n\\t%s\\n\", \n                   result, str ? str : \"no error given\", sql);\n\n        return NULL;\n    }\n\n    return stmt;\n}\n\nint\nsqlite_query_exec (sqlite3 *db, const char *sql)\n{\n    char *errmsg = NULL;\n    int result;\n\n    result = sqlite3_exec (db, sql, NULL, NULL, &errmsg);\n\n    if (result != SQLITE_OK) {\n        if (errmsg != NULL) {\n            g_warning (\"SQL error: %d - %s\\n:\\t%s\\n\", result, errmsg, sql);\n            sqlite3_free (errmsg);\n        }\n        return -1;\n    }\n\n    return 0;\n}\n\nint\nsqlite_begin_transaction (sqlite3 *db)\n{\n    char *sql = \"BEGIN TRANSACTION;\";\n    return sqlite_query_exec (db, sql);\n}\n\nint\nsqlite_end_transaction (sqlite3 *db)\n{\n    char *sql = \"END TRANSACTION;\";\n    return sqlite_query_exec (db, sql);\n}\n\n\ngboolean\nsqlite_check_for_existence (sqlite3 *db, const char *sql)\n{\n    sqlite3_stmt *stmt;\n    int result;\n\n    stmt = sqlite_query_prepare (db, sql);\n    if (!stmt)\n        return FALSE;\n\n    result = sqlite3_step (stmt);\n    if (result == SQLITE_ERROR) {\n        const gchar *str = sqlite3_errmsg (db);\n\n        g_warning (\"Couldn't execute query, error: %d->'%s'\\n\", \n                   result, str ? str : \"no error given\");\n        sqlite3_finalize (stmt);\n        return FALSE;\n    }\n    sqlite3_finalize (stmt);\n\n    if (result == SQLITE_ROW)\n        return TRUE;\n    return FALSE;\n}\n\nint\nsqlite_foreach_selected_row (sqlite3 *db, const char *sql, \n                             SqliteRowFunc callback, void *data)\n{\n    sqlite3_stmt *stmt;\n    int result;\n    int n_rows = 0;\n\n    stmt = sqlite_query_prepare (db, sql);\n    if (!stmt) {\n        return -1;\n    }\n\n    while (1) {\n        result = sqlite3_step (stmt);\n        if (result != SQLITE_ROW)\n            break;\n        n_rows++;\n        if (!callback (stmt, data))\n            break;\n    }\n\n    if (result == SQLITE_ERROR) {\n        const gchar *s = sqlite3_errmsg (db);\n\n        g_warning (\"Couldn't execute query, error: %d->'%s'\\n\",\n                   result, s ? s : \"no error given\");\n        sqlite3_finalize (stmt);\n        return -1;\n    }\n\n    sqlite3_finalize (stmt);\n    return n_rows;\n}\n\nint sqlite_get_int (sqlite3 *db, const char *sql)\n{\n    int ret = -1;\n    int result;\n    sqlite3_stmt *stmt;\n\n    if ( !(stmt = sqlite_query_prepare(db, sql)) )\n        return 0;\n\n    result = sqlite3_step (stmt);\n    if (result == SQLITE_ROW) {\n        ret = sqlite3_column_int (stmt, 0);\n        sqlite3_finalize (stmt);\n        return ret;\n    }\n\n    if (result == SQLITE_ERROR) {\n        const gchar *str = sqlite3_errmsg (db);\n        g_warning (\"Couldn't execute query, error: %d->'%s'\\n\",\n                   result, str ? str : \"no error given\");\n        sqlite3_finalize (stmt);\n        return -1;\n    }\n\n    sqlite3_finalize(stmt);\n    return ret;\n}\n\ngint64 sqlite_get_int64 (sqlite3 *db, const char *sql)\n{\n    gint64 ret = -1;\n    int result;\n    sqlite3_stmt *stmt;\n\n    if ( !(stmt = sqlite_query_prepare(db, sql)) )\n        return 0;\n\n    result = sqlite3_step (stmt);\n    if (result == SQLITE_ROW) {\n        ret = sqlite3_column_int64 (stmt, 0);\n        sqlite3_finalize (stmt);\n        return ret;\n    }\n\n    if (result == SQLITE_ERROR) {\n        const gchar *str = sqlite3_errmsg (db);\n        g_warning (\"Couldn't execute query, error: %d->'%s'\\n\",\n                   result, str ? str : \"no error given\");\n        sqlite3_finalize (stmt);\n        return -1;\n    }\n\n    sqlite3_finalize(stmt);\n    return ret;\n}\n\nchar *sqlite_get_string (sqlite3 *db, const char *sql)\n{\n    const char *res = NULL;\n    int result;\n    sqlite3_stmt *stmt;\n    char *ret;\n\n    if ( !(stmt = sqlite_query_prepare(db, sql)) )\n        return NULL;\n\n    result = sqlite3_step (stmt);\n    if (result == SQLITE_ROW) {\n        res = (const char *)sqlite3_column_text (stmt, 0);\n        ret = g_strdup(res);\n        sqlite3_finalize (stmt);\n        return ret;\n    }\n\n    if (result == SQLITE_ERROR) {\n        const gchar *str = sqlite3_errmsg (db);\n        g_warning (\"Couldn't execute query, error: %d->'%s'\\n\",\n                   result, str ? str : \"no error given\");\n        sqlite3_finalize (stmt);\n        return NULL;\n    }\n\n    sqlite3_finalize(stmt);\n    return NULL;\n}\n"
  },
  {
    "path": "lib/db.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef DB_UTILS_H\n#define DB_UTILS_H\n\n#include <sqlite3.h>\n\nint sqlite_open_db (const char *db_path, sqlite3 **db);\n\nint sqlite_close_db (sqlite3 *db);\n\nsqlite3_stmt *sqlite_query_prepare (sqlite3 *db, const char *sql);\n\nint sqlite_query_exec (sqlite3 *db, const char *sql);\nint sqlite_begin_transaction (sqlite3 *db);\nint sqlite_end_transaction (sqlite3 *db);\n\ngboolean sqlite_check_for_existence (sqlite3 *db, const char *sql);\n\ntypedef gboolean (*SqliteRowFunc) (sqlite3_stmt *stmt, void *data);\n\nint\nsqlite_foreach_selected_row (sqlite3 *db, const char *sql, \n                             SqliteRowFunc callback, void *data);\n\nint sqlite_get_int (sqlite3 *db, const char *sql);\n\ngint64 sqlite_get_int64 (sqlite3 *db, const char *sql);\n\nchar *sqlite_get_string (sqlite3 *db, const char *sql);\n\n\n#endif\n"
  },
  {
    "path": "lib/include.h",
    "content": "\n#include <stdint.h>\n#ifndef WIN32\n#include <config.h>\n#include <unistd.h>\n#endif\n#include <stdlib.h>\n#include <string.h>\n#include <errno.h>\n\n#include <glib.h>\n\n#include \"utils.h\"\n\n#ifndef ccnet_warning\n  #define ccnet_warning(fmt, ...) g_warning( \"%s: \" fmt,  __func__ , ##__VA_ARGS__)\n#endif\n\n#ifndef ccnet_error\n  #define ccnet_error(fmt, ...)   g_error( \"%s: \" fmt,  __func__ , ##__VA_ARGS__)\n#endif\n\n#ifndef ccnet_message\n  #define ccnet_message(fmt, ...) g_message(fmt, ##__VA_ARGS__)\n#endif\n\n#ifndef ccnet_debug\n  #define ccnet_debug(fmt, ...) g_debug(fmt, ##__VA_ARGS__)\n#endif\n\n\n#ifndef ENABLE_DEBUG\n#undef g_debug\n#define g_debug(...)  \n#endif\n"
  },
  {
    "path": "lib/libseafile.pc.in",
    "content": "prefix=(DESTDIR)@prefix@\nexec_prefix=@exec_prefix@\nlibdir=@libdir@\nincludedir=@includedir@\n\nName: libseafile\nDescription: Client library for accessing seafile service.\nVersion: @VERSION@\nLibs: -L${libdir} -lseafile @SEARPC_LIBS@\nCflags: -I${includedir} @SEARPC_CFLAGS@\nRequires: gobject-2.0 glib-2.0\n"
  },
  {
    "path": "lib/net.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n#ifdef WIN32\n    #define WINVER 0x0501\n    #include <inttypes.h>\n    #include <winsock2.h>\n    #include <ctype.h>\n    #include <ws2tcpip.h>\n#endif\n#include \"include.h\"\n\n#ifndef WIN32\n#include <unistd.h>\n#endif\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <string.h>\n\n\n#ifdef WIN32\n    #define UNUSED \n#else\n    #include <sys/types.h>\n    #include <sys/socket.h>\n    #include <sys/ioctl.h>\n    #include <netinet/in.h>\n    #include <arpa/inet.h>\n    #include <netdb.h>\n    #include <sys/un.h>\n    #include <net/if.h>\n    #include <netinet/tcp.h>\n#endif\n\n#include <fcntl.h>\n\n#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)\n#include <event2/util.h>\n#else\n#include <evutil.h>\n#endif\n\n#include \"net.h\"\n\n\n#ifdef WIN32\n\n#ifndef inet_aton\nint inet_aton(const char *string, struct in_addr *addr)\n{\n    addr->s_addr = inet_addr(string);\n    if (addr->s_addr != -1 || strcmp(\"255.255.255.255\", string) == 0)\n        return 1;\n    return 0;\n}\n#endif\n\n#endif //WIN32\n\nint\nccnet_netSetTOS (evutil_socket_t s, int tos)\n{\n#ifdef IP_TOS\n    return setsockopt( s, IPPROTO_IP, IP_TOS, (char*)&tos, sizeof( tos ) );\n#else\n    return 0;\n#endif\n}\n\nstatic evutil_socket_t\nmakeSocketNonBlocking (evutil_socket_t fd)\n{\n    if (fd >= 0)\n    {\n        if (evutil_make_socket_nonblocking(fd))\n        {\n            ccnet_warning (\"Couldn't make socket nonblock: %s\",\n                           evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));\n            evutil_closesocket(fd);\n            fd = -1;\n        }\n    }\n    return fd;\n}\n\nstatic evutil_socket_t\ncreateSocket (int family, int nonblock)\n{\n    evutil_socket_t fd;\n    int ret;\n\n    fd = socket (family, SOCK_STREAM, 0);\n\n    if (fd < 0) {\n        ccnet_warning(\"create Socket failed %d\\n\", fd);\n    } else if (nonblock) {\n        int nodelay = 1;\n\n        fd = makeSocketNonBlocking( fd );\n\n        ret = setsockopt (fd, IPPROTO_TCP, TCP_NODELAY,\n                          (char *)&nodelay, sizeof(nodelay));\n        if (ret < 0) {\n            ccnet_warning(\"setsockopt failed\\n\");\n            evutil_closesocket(fd);\n            return -1;\n        }\n    }\n\n    return fd;\n}\n\nevutil_socket_t\nccnet_net_open_tcp (const struct sockaddr *sa, int nonblock)\n{\n    evutil_socket_t s;\n    int sa_len;\n\n    if( (s = createSocket(sa->sa_family, nonblock)) < 0 )\n        return -1;\n\n#ifndef WIN32\n    if (sa->sa_family == AF_INET)\n        sa_len = sizeof (struct sockaddr_in); \n    else\n        sa_len = sizeof (struct sockaddr_in6);\n#else\n    if (sa->sa_family == AF_INET)\n        sa_len = sizeof (struct sockaddr_in); \n    else\n        return -1;\n#endif\n\n\n    if( (connect(s, sa, sa_len) < 0)\n#ifdef WIN32\n        && (sockerrno != WSAEWOULDBLOCK)\n#endif\n        && (sockerrno != EINPROGRESS) )\n    {\n        evutil_closesocket(s);\n        s = -1;\n    }\n\n    return s;\n}\n\nevutil_socket_t\nccnet_net_bind_tcp (int port, int nonblock)\n{\n#ifndef WIN32\n    int sockfd, n;\n    struct addrinfo hints, *res, *ressave;\n    char buf[10];\n        \n    memset (&hints, 0,sizeof (struct addrinfo));\n    hints.ai_flags = AI_PASSIVE;\n    hints.ai_family = AF_UNSPEC;\n    hints.ai_socktype = SOCK_STREAM;\n\n    snprintf (buf, sizeof(buf), \"%d\", port);\n\n    if ( (n = getaddrinfo(NULL, buf, &hints, &res) ) != 0) {\n        ccnet_warning (\"getaddrinfo fails: %s\\n\", gai_strerror(n));\n        return -1;\n    }\n\n    ressave = res;\n    \n    do {\n        int on = 1;\n\n        sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);\n        if (sockfd < 0)\n            continue;       /* error - try next one */\n\n\t\tif (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {\n\t\t\tccnet_warning (\"setsockopt of SO_REUSEADDR error\\n\");\n            continue;\n        }\n\n        if (nonblock)\n            sockfd = makeSocketNonBlocking (sockfd);\n        if (sockfd < 0)\n            continue;       /* error - try next one */\n\n        if (bind(sockfd, res->ai_addr, res->ai_addrlen) == 0)\n            break;          /* success */\n\n        close(sockfd);      /* bind error - close and try next one */\n    } while ( (res = res->ai_next) != NULL);\n\n    freeaddrinfo (ressave);\n\n    if (res == NULL) {\n        ccnet_warning (\"bind fails: %s\\n\", strerror(errno));\n        return -1;\n    }\n\n    return sockfd;\n#else\n\n    evutil_socket_t s;\n    struct sockaddr_in sock;\n    const int type = AF_INET;\n#if defined( SO_REUSEADDR ) || defined( SO_REUSEPORT )\n    int optval;\n#endif\n\n    if ((s = createSocket(type, nonblock)) < 0)\n        return -1;\n\n    optval = 1;\n    setsockopt (s, SOL_SOCKET, SO_REUSEADDR, (char*)&optval, sizeof(optval));\n\n    memset(&sock, 0, sizeof(sock));\n    sock.sin_family      = AF_INET;\n    sock.sin_addr.s_addr = INADDR_ANY;\n    sock.sin_port        = htons(port);\n\n    if ( bind(s, (struct sockaddr *)&sock, sizeof(struct sockaddr_in)) < 0)\n    {\n        ccnet_warning (\"bind fails: %s\\n\", strerror(errno));\n        evutil_closesocket (s);\n        return -1;\n    }\n    if (nonblock)\n        s = makeSocketNonBlocking (s);\n     \n    return s;\n#endif\n}\n\nint\nccnet_net_make_socket_blocking(evutil_socket_t fd)\n{\n#ifdef WIN32\n\t{\n\t\tu_long nonblocking = 0;\n\t\tif (ioctlsocket(fd, FIONBIO, &nonblocking) == SOCKET_ERROR) {\n\t\t\tccnet_warning (\"fcntl(%d, F_GETFL)\", (int)fd);\n\t\t\treturn -1;\n\t\t}\n\t}\n#else\n\t{\n\t\tint flags;\n\t\tif ((flags = fcntl(fd, F_GETFL, NULL)) < 0) {\n\t\t\tccnet_warning (\"fcntl(%d, F_GETFL)\", fd);\n\t\t\treturn -1;\n\t\t}\n\t\tif (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) == -1) {\n\t\t\tccnet_warning (\"fcntl(%d, F_SETFL)\", fd);\n\t\t\treturn -1;\n\t\t}\n\t}\n#endif\n\treturn 0;\n}\n\nevutil_socket_t\nccnet_net_accept (evutil_socket_t b, struct sockaddr_storage *cliaddr, \n                  socklen_t *len, int nonblock)\n{\n    evutil_socket_t s;\n    /* int nodelay = 1; */\n    \n    s = accept (b, (struct sockaddr *)cliaddr, len);\n\n    /* setsockopt (s, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)); */\n    if (nonblock)\n        makeSocketNonBlocking(s);\n\n    return s;\n}\n\n\nevutil_socket_t\nccnet_net_bind_v4 (const char *ipaddr, int *port)\n{\n    evutil_socket_t sockfd;\n    struct sockaddr_in addr;\n    int on = 1;\n        \n    sockfd = socket (AF_INET, SOCK_STREAM, 0);\n    if (sockfd < 0) {\n        ccnet_warning(\"create socket failed: %s\\n\", strerror(errno));\n        exit(-1);\n    }\n\n    memset (&addr, 0, sizeof (struct sockaddr_in));\n    addr.sin_family = AF_INET;\n    if (inet_aton(ipaddr, &addr.sin_addr) == 0) {\n        ccnet_warning (\"Bad ip address %s\\n\", ipaddr);\n        return -1;\n    }\n    addr.sin_port = htons (*port);\n\n    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0)\n    {\n        ccnet_warning (\"setsockopt of SO_REUSEADDR error: %s\\n\",\n                       strerror(errno));\n        return -1;\n    }\n\n    if ( bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {\n        ccnet_warning (\"Bind error: %s\\n\", strerror (errno));\n        return -1;\n    }\n\n\n    if (*port == 0) {\n        struct sockaddr_storage ss;\n        socklen_t len;\n\n        len = sizeof(ss);\n        if (getsockname(sockfd, (struct sockaddr *)&ss, &len) < 0) {\n            ccnet_warning (\"getsockname error: %s\\n\", strerror(errno));\n            return -1;\n        }\n        *port = sock_port ((struct sockaddr *)&ss);\n    }\n\n    return sockfd;\n}\n\n\n\nchar *\nsock_ntop(const struct sockaddr *sa, socklen_t salen)\n{\n    static char str[128];       /* Unix domain is largest */\n\n    switch (sa->sa_family) {\n    case AF_INET: {\n        struct sockaddr_in  *sin = (struct sockaddr_in *) sa;\n\n        if (evutil_inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)\n            return(NULL);\n        return(str);\n    }\n\n#ifdef  IPv6\n    case AF_INET6: {\n        struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;\n\n        if (evutil_inet_ntop(AF_INET6, &sin6->sin6_addr, str, sizeof(str) - 1) == NULL)\n            return(NULL);\n        return (str);\n    }\n#endif\n\n#ifndef WIN32\n#ifdef  AF_UNIX \n    case AF_UNIX: {\n        struct sockaddr_un  *unp = (struct sockaddr_un *) sa;\n\n            /* OK to have no pathname bound to the socket: happens on\n               every connect() unless client calls bind() first. */\n        if (unp->sun_path[0] == 0)\n            strcpy(str, \"(no pathname bound)\");\n        else\n            snprintf(str, sizeof(str), \"%s\", unp->sun_path);\n        return(str);\n    }\n#endif\n#endif\n\n    default:\n        snprintf(str, sizeof(str), \"sock_ntop: unknown AF_xxx: %d, len %d\",\n                 sa->sa_family, salen);\n        return(str);\n    }\n    return (NULL);\n}\n\nint\nsock_pton (const char *addr_str, uint16_t port, struct sockaddr_storage *sa)\n{\n    struct sockaddr_in  *saddr  = (struct sockaddr_in *) sa;\n\n#ifndef WIN32\n    struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *) sa;\n#endif\n\n    if (evutil_inet_pton (AF_INET, addr_str, &saddr->sin_addr) == 1 ) {\n        saddr->sin_family = AF_INET;\n        saddr->sin_port = htons (port);\n        return 0;\n    } \n#ifndef WIN32\n    else if (evutil_inet_pton (AF_INET6, addr_str, &saddr6->sin6_addr) == 1)\n    {\n        saddr6->sin6_family = AF_INET6;\n        saddr6->sin6_port = htons (port);\n        return 0;\n    }\n#endif\n\n    return -1;\n}\n\n/* return 1 if addr_str is a valid ipv4 or ipv6 address */\nint\nis_valid_ipaddr (const char *addr_str)\n{\n    struct sockaddr_storage addr;\n    if (!addr_str)\n        return 0;\n    if (sock_pton(addr_str, 0, &addr) < 0)\n        return 0;\n    return 1;\n}\n\nuint16_t\nsock_port (const struct sockaddr *sa)\n{\n    switch (sa->sa_family) {\n    case AF_INET: {\n        struct sockaddr_in  *sin = (struct sockaddr_in *) sa;\n        return ntohs(sin->sin_port);\n    }\n#ifdef  IPv6\n    case AF_INET6: {\n        struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;\n\n        return ntohs(sin6->sin6_port);\n    }\n#endif\n    default:\n        return 0;\n    }\n    return 0;\n}\n\n\nevutil_socket_t\nudp_client (const char *host, const char *serv,\n            struct sockaddr **saptr, socklen_t *lenp)\n{\n\tevutil_socket_t sockfd;\n    int n;\n\tstruct addrinfo\thints, *res, *ressave;\n\n\tmemset (&hints, 0, sizeof(struct addrinfo));\n\thints.ai_family = AF_UNSPEC;\n\thints.ai_socktype = SOCK_DGRAM;\n\n\tif ((n = getaddrinfo(host, serv, &hints, &res)) != 0) {\n        ccnet_warning (\"udp_client error for %s, %s: %s\",\n                       host, serv, gai_strerror(n));\n        return -1;\n    }\n\tressave = res;\n\n\tdo {\n\t\tsockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);\n\t\tif (sockfd >= 0)\n\t\t\tbreak;\t\t/* success */\n\t} while ( (res = res->ai_next) != NULL);\n\n\tif (res == NULL) {\t/* errno set from final socket() */\n\t\tccnet_warning (\"udp_client error for %s, %s\", host, serv);\n        freeaddrinfo (ressave);\n        return -1;\n    }\n\n\t*saptr = malloc(res->ai_addrlen);\n\tmemcpy(*saptr, res->ai_addr, res->ai_addrlen);\n\t*lenp = res->ai_addrlen;\n\n\tfreeaddrinfo(ressave);\n\n\treturn (sockfd);\n}\n\n\nint\nfamily_to_level(int family)\n{\n\tswitch (family) {\n\tcase AF_INET:\n\t\treturn IPPROTO_IP;\n#ifdef\tIPV6\n\tcase AF_INET6:\n\t\treturn IPPROTO_IPV6;\n#endif\n\tdefault:\n\t\treturn -1;\n\t}\n}\n\n#ifdef WIN32\nstatic int\nmcast_join(evutil_socket_t sockfd, const struct sockaddr *grp, socklen_t grplen,\n\t\t   const char *ifname, u_int ifindex)\n{\n    int optval = 3;\n    int sockm;\n    if (setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL,\n                  (char *)&optval, sizeof(int)) == SOCKET_ERROR) {\n        ccnet_warning(\"Fail to set socket multicast TTL, LastError=%d\\n\",\n                      WSAGetLastError());\n        return -1;\n    }\n    optval = 0;\n    if (setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP,\n                  (char *)&optval, sizeof(int)) == SOCKET_ERROR) {\n        ccnet_warning(\"Fail to set socket multicast LOOP, LastError=%d\\n\",\n                      WSAGetLastError());\n        return -1;\n    }\n    sockm = WSAJoinLeaf (sockfd, grp, grplen, NULL, NULL, NULL, NULL, JL_BOTH);\n    if (sockm == INVALID_SOCKET) {\n        ccnet_warning(\"Fail to join multicast group, LastError=%d\\n\",\n                      WSAGetLastError());\n        return -1;\n    }\n    return sockm;\n}\n\nevutil_socket_t\ncreate_multicast_sock (struct sockaddr *sasend, socklen_t salen)\n{\n    int                 ret;\n    const int           on = 1;\n    evutil_socket_t     recvfd;\n    struct sockaddr    *sarecv;\n\n    recvfd = WSASocket (AF_INET, SOCK_DGRAM, 0, NULL, 0,\n                        WSA_FLAG_MULTIPOINT_C_LEAF|WSA_FLAG_MULTIPOINT_D_LEAF\n                        |WSA_FLAG_OVERLAPPED);\n    if (recvfd < 0) {\n        ccnet_warning (\"Create multicast listen socket fails: %d\\n\",\n                       WSAGetLastError());\n        return -1;\n    }\n    ret = setsockopt(recvfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));\n    if (ret != 0) {\n        ccnet_warning(\"Failed to setsockopt SO_REUSEADDR, WSAGetLastError=%d\\n\",\n                      WSAGetLastError());\n        return -1;\n    }\n\n    sarecv = malloc(salen);\n    memcpy(sarecv, sasend, salen);\n    struct sockaddr_in *saddr = (struct sockaddr_in *)sarecv;\n    saddr->sin_addr.s_addr = INADDR_ANY;\n\n    if (bind(recvfd, sarecv, salen) < 0) {\n        ccnet_warning(\"Bind multicast bind socket failed LastError=%d\\n\",\n                      WSAGetLastError());\n        free (sarecv);\n        return -1;;\n    }\n    free (sarecv);\n\n    if (mcast_join(recvfd, sasend, salen, NULL, 0) < 0) {\n        ccnet_warning (\"mcast_join error: %s\\n\", strerror(errno));\n        return -1;\n    }\n\n    return recvfd;\n}\n#else\nstatic int\nmcast_join(evutil_socket_t sockfd, const struct sockaddr *grp, socklen_t grplen,\n\t\t   const char *ifname, u_int ifindex)\n{\n#if (defined MCAST_JOIN_GROUP) && (! defined __APPLE__)\n\tstruct group_req req;\n\tif (ifindex > 0) {\n\t\treq.gr_interface = ifindex;\n\t} else if (ifname != NULL) {\n\t\tif ( (req.gr_interface = if_nametoindex(ifname)) == 0) {\n\t\t\terrno = ENXIO;\t/* i/f name not found */\n\t\t\treturn(-1);\n\t\t}\n\t} else\n\t\treq.gr_interface = 0;\n\tif (grplen > sizeof(req.gr_group)) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\tmemcpy(&req.gr_group, grp, grplen);\n\treturn (setsockopt(sockfd, family_to_level(grp->sa_family),\n\t\t\tMCAST_JOIN_GROUP, &req, sizeof(req)));\n#else\n/* end mcast_join1 */\n\n/* include mcast_join2 */\n\tswitch (grp->sa_family) {\n\tcase AF_INET: {\n\t\tstruct ip_mreq\t\tmreq;\n\t\tstruct ifreq\t\tifreq;\n\n\t\tmemcpy(&mreq.imr_multiaddr.s_addr,\n\t\t\t   &((const struct sockaddr_in *) grp)->sin_addr,\n\t\t\t   sizeof(struct in_addr));\n\n\t\tif (ifindex > 0) {\n\t\t\tif (if_indextoname(ifindex, ifreq.ifr_name) == NULL) {\n\t\t\t\terrno = ENXIO;\t/* i/f index not found */\n\t\t\t\treturn(-1);\n\t\t\t}\n\t\t\tgoto doioctl;\n\t\t} else if (ifname != NULL) {\n\t\t\tstrncpy(ifreq.ifr_name, ifname, IFNAMSIZ);\ndoioctl:\n\t\t\tif (ioctl(sockfd, SIOCGIFADDR, &ifreq) < 0)\n\t\t\t\treturn(-1);\n\t\t\tmemcpy(&mreq.imr_interface,\n\t\t\t\t   &((struct sockaddr_in *) &ifreq.ifr_addr)->sin_addr,\n\t\t\t\t   sizeof(struct in_addr));\n\t\t} else\n\t\t\tmreq.imr_interface.s_addr = htonl(INADDR_ANY);\n\n\t\treturn(setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,\n\t\t\t\t\t\t  &mreq, sizeof(mreq)));\n\t}\n/* end mcast_join2 */\n\n/* include mcast_join3 */\n#ifdef\tIPV6\n#ifndef\tIPV6_JOIN_GROUP\t\t/* APIv0 compatibility */\n#define\tIPV6_JOIN_GROUP\t\tIPV6_ADD_MEMBERSHIP\n#endif\n\tcase AF_INET6: {\n\t\tstruct ipv6_mreq\tmreq6;\n\n\t\tmemcpy(&mreq6.ipv6mr_multiaddr,\n\t\t\t   &((const struct sockaddr_in6 *) grp)->sin6_addr,\n\t\t\t   sizeof(struct in6_addr));\n\n\t\tif (ifindex > 0) {\n\t\t\tmreq6.ipv6mr_interface = ifindex;\n\t\t} else if (ifname != NULL) {\n\t\t\tif ( (mreq6.ipv6mr_interface = if_nametoindex(ifname)) == 0) {\n\t\t\t\terrno = ENXIO;\t/* i/f name not found */\n\t\t\t\treturn(-1);\n\t\t\t}\n\t\t} else\n\t\t\tmreq6.ipv6mr_interface = 0;\n\n\t\treturn(setsockopt(sockfd, IPPROTO_IPV6, IPV6_JOIN_GROUP,\n\t\t\t\t\t\t  &mreq6, sizeof(mreq6)));\n\t}\n#endif\n\n\tdefault:\n\t\terrno = EAFNOSUPPORT;\n\t\treturn(-1);\n\t}\n#endif\n\n    return -1;\n}\n\nevutil_socket_t\ncreate_multicast_sock (struct sockaddr *sasend, socklen_t salen)\n{\n    int                 ret;\n    const int           on = 1;\n    evutil_socket_t     recvfd;\n    struct sockaddr    *sarecv;\n\n    if ( (recvfd = socket (sasend->sa_family, SOCK_DGRAM, 0)) < 0) {\n        ccnet_warning (\"Create multicast listen socket fails: %s\\n\",\n                     strerror(errno));\n        return -1;\n    }\n    ret = setsockopt(recvfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));\n    if (ret < 0)\n        ccnet_warning(\"Failed to setsockopt SO_REUSEADDR\\n\");\n    sarecv = malloc(salen);\n    memcpy(sarecv, sasend, salen);\n\n    if (bind(recvfd, sarecv, salen) < 0) {\n        ccnet_warning (\"Bind multicast listen socket fails: %s\\n\",\n                       strerror(errno));\n        free (sarecv);\n        return -1;\n    }\n    free (sarecv);\n\n    if (mcast_join(recvfd, sasend, salen, NULL, 0) < 0) {\n        ccnet_warning (\"mcast_join error: %s\\n\", strerror(errno));\n        return -1;\n    }\n\n    return recvfd;\n}\n\n#endif\n\nint\nsockfd_to_family(evutil_socket_t sockfd)\n{\n\tstruct sockaddr_storage ss;\n\tsocklen_t\tlen;\n\n\tlen = sizeof(ss);\n\tif (getsockname(sockfd, (struct sockaddr *) &ss, &len) < 0)\n\t\treturn(-1);\n\treturn(ss.ss_family);\n}\n\nint\nmcast_set_loop(evutil_socket_t sockfd, int onoff)\n{\n#ifndef WIN32\n\n\tswitch (sockfd_to_family(sockfd)) {\n\tcase AF_INET: {\n\t\tu_char\t\tflag;\n\n\t\tflag = onoff;\n\t\treturn(setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP,\n\t\t\t\t\t\t  &flag, sizeof(flag)));\n\t}\n\n#ifdef\tIPV6\n\tcase AF_INET6: {\n\t\tu_int\t\tflag;\n\n\t\tflag = onoff;\n\t\treturn(setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,\n\t\t\t\t\t\t  &flag, sizeof(flag)));\n\t}\n#endif\n\n\tdefault:\n\t\terrno = EAFNOSUPPORT;\n\t\treturn(-1);\n\t}\n\n#else\n    return -1;\n#endif  /* WIN32 */\n}\n"
  },
  {
    "path": "lib/net.h",
    "content": "\n#ifndef CCNET_NET_H\n#define CCNET_NET_H\n\n#ifdef WIN32\n    #include <inttypes.h>\n    #include <winsock2.h>\n    #include <ws2tcpip.h>\n    typedef int socklen_t;\n    #define UNUSED \n#else\n    #include <sys/types.h>\n    #include <sys/socket.h>\n    #include <netinet/in.h>\n    #include <arpa/inet.h>\n    #include <netdb.h>\n    #include <sys/un.h>\n    #include <net/if.h>\n    #include <netinet/tcp.h>\n#endif\n\n#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)\n#include <event2/util.h>\n#else\n#include <evutil.h>\n#endif\n\n#ifdef WIN32\n    /* #define ECONNREFUSED WSAECONNREFUSED */\n    /* #define ECONNRESET   WSAECONNRESET */\n    /* #define EHOSTUNREACH WSAEHOSTUNREACH */\n    /* #define EINPROGRESS  WSAEINPROGRESS */\n    /* #define ENOTCONN     WSAENOTCONN */\n    /* #define EWOULDBLOCK  WSAEWOULDBLOCK */\n    #define sockerrno WSAGetLastError( )\n#else\n    #include <errno.h>\n    #define sockerrno errno\n#endif\n\n#ifdef WIN32\nextern int inet_aton(const char *string, struct in_addr *addr);\n#endif\n\nevutil_socket_t ccnet_net_open_tcp (const struct sockaddr *sa, int nonblock);\nevutil_socket_t ccnet_net_bind_tcp (int port, int nonblock);\nevutil_socket_t ccnet_net_accept (evutil_socket_t b, \n                                  struct sockaddr_storage *cliaddr,\n                                  socklen_t *len, int nonblock);\n\nint ccnet_net_make_socket_blocking (evutil_socket_t fd);\n\n/* bind to an IPv4 address, if (*port == 0) the port number will be returned */\nevutil_socket_t ccnet_net_bind_v4 (const char *ipaddr, int *port);\n\nint  ccnet_netSetTOS   ( evutil_socket_t s, int tos );\n\nchar *sock_ntop(const struct sockaddr *sa, socklen_t salen);\nuint16_t sock_port (const struct sockaddr *sa);\n\n/* return 1 if addr_str is a valid ipv4 or ipv6 address */\nint is_valid_ipaddr (const char *addr_str);\n\n\n/* return 0 if success, -1 if error */\nint sock_pton (const char *addr_str, uint16_t port, \n               struct sockaddr_storage *sa);\n\nevutil_socket_t udp_client (const char *host, const char *serv,\n                struct sockaddr **saptr, socklen_t *lenp);\n\nint mcast_set_loop(evutil_socket_t sockfd, int onoff);\n\nevutil_socket_t create_multicast_sock (struct sockaddr *sasend, socklen_t salen);\n\n#endif\n"
  },
  {
    "path": "lib/repo.c",
    "content": "/* repo.c generated by valac 0.56.7, the Vala compiler\n * generated from repo.vala, do not modify */\n\n#include <glib-object.h>\n#include <glib.h>\n#include <string.h>\n\n#if !defined(VALA_EXTERN)\n#if defined(_MSC_VER)\n#define VALA_EXTERN __declspec(dllexport) extern\n#elif __GNUC__ >= 4\n#define VALA_EXTERN __attribute__((visibility(\"default\"))) extern\n#else\n#define VALA_EXTERN extern\n#endif\n#endif\n\n#define SEAFILE_TYPE_REPO (seafile_repo_get_type ())\n#define SEAFILE_REPO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_REPO, SeafileRepo))\n#define SEAFILE_REPO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_REPO, SeafileRepoClass))\n#define SEAFILE_IS_REPO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_REPO))\n#define SEAFILE_IS_REPO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_REPO))\n#define SEAFILE_REPO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_REPO, SeafileRepoClass))\n\ntypedef struct _SeafileRepo SeafileRepo;\ntypedef struct _SeafileRepoClass SeafileRepoClass;\ntypedef struct _SeafileRepoPrivate SeafileRepoPrivate;\nenum  {\n\tSEAFILE_REPO_0_PROPERTY,\n\tSEAFILE_REPO_ID_PROPERTY,\n\tSEAFILE_REPO_NAME_PROPERTY,\n\tSEAFILE_REPO_DESC_PROPERTY,\n\tSEAFILE_REPO_VERSION_PROPERTY,\n\tSEAFILE_REPO_LAST_MODIFY_PROPERTY,\n\tSEAFILE_REPO_SIZE_PROPERTY,\n\tSEAFILE_REPO_FILE_COUNT_PROPERTY,\n\tSEAFILE_REPO_HEAD_CMMT_ID_PROPERTY,\n\tSEAFILE_REPO_ROOT_PROPERTY,\n\tSEAFILE_REPO_REPO_ID_PROPERTY,\n\tSEAFILE_REPO_REPO_NAME_PROPERTY,\n\tSEAFILE_REPO_REPO_DESC_PROPERTY,\n\tSEAFILE_REPO_LAST_MODIFIED_PROPERTY,\n\tSEAFILE_REPO_ENCRYPTED_PROPERTY,\n\tSEAFILE_REPO_MAGIC_PROPERTY,\n\tSEAFILE_REPO_ENC_VERSION_PROPERTY,\n\tSEAFILE_REPO_RANDOM_KEY_PROPERTY,\n\tSEAFILE_REPO_SALT_PROPERTY,\n\tSEAFILE_REPO_WORKTREE_PROPERTY,\n\tSEAFILE_REPO_RELAY_ID_PROPERTY,\n\tSEAFILE_REPO_LAST_SYNC_TIME_PROPERTY,\n\tSEAFILE_REPO_AUTO_SYNC_PROPERTY,\n\tSEAFILE_REPO_WORKTREE_INVALID_PROPERTY,\n\tSEAFILE_REPO_IS_VIRTUAL_PROPERTY,\n\tSEAFILE_REPO_ORIGIN_REPO_ID_PROPERTY,\n\tSEAFILE_REPO_ORIGIN_REPO_NAME_PROPERTY,\n\tSEAFILE_REPO_ORIGIN_PATH_PROPERTY,\n\tSEAFILE_REPO_IS_ORIGINAL_OWNER_PROPERTY,\n\tSEAFILE_REPO_VIRTUAL_PERM_PROPERTY,\n\tSEAFILE_REPO_STORE_ID_PROPERTY,\n\tSEAFILE_REPO_IS_CORRUPTED_PROPERTY,\n\tSEAFILE_REPO_REPAIRED_PROPERTY,\n\tSEAFILE_REPO_SHARE_TYPE_PROPERTY,\n\tSEAFILE_REPO_PERMISSION_PROPERTY,\n\tSEAFILE_REPO_USER_PROPERTY,\n\tSEAFILE_REPO_GROUP_ID_PROPERTY,\n\tSEAFILE_REPO_IS_SHARED_PROPERTY,\n\tSEAFILE_REPO_NUM_PROPERTIES\n};\nstatic GParamSpec* seafile_repo_properties[SEAFILE_REPO_NUM_PROPERTIES];\n#define _g_free0(var) (var = (g_free (var), NULL))\n\n#define SEAFILE_TYPE_SYNC_TASK (seafile_sync_task_get_type ())\n#define SEAFILE_SYNC_TASK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_SYNC_TASK, SeafileSyncTask))\n#define SEAFILE_SYNC_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_SYNC_TASK, SeafileSyncTaskClass))\n#define SEAFILE_IS_SYNC_TASK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_SYNC_TASK))\n#define SEAFILE_IS_SYNC_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_SYNC_TASK))\n#define SEAFILE_SYNC_TASK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_SYNC_TASK, SeafileSyncTaskClass))\n\ntypedef struct _SeafileSyncTask SeafileSyncTask;\ntypedef struct _SeafileSyncTaskClass SeafileSyncTaskClass;\ntypedef struct _SeafileSyncTaskPrivate SeafileSyncTaskPrivate;\nenum  {\n\tSEAFILE_SYNC_TASK_0_PROPERTY,\n\tSEAFILE_SYNC_TASK_FORCE_UPLOAD_PROPERTY,\n\tSEAFILE_SYNC_TASK_REPO_ID_PROPERTY,\n\tSEAFILE_SYNC_TASK_STATE_PROPERTY,\n\tSEAFILE_SYNC_TASK_ERROR_PROPERTY,\n\tSEAFILE_SYNC_TASK_NUM_PROPERTIES\n};\nstatic GParamSpec* seafile_sync_task_properties[SEAFILE_SYNC_TASK_NUM_PROPERTIES];\n\n#define SEAFILE_TYPE_SESSION_INFO (seafile_session_info_get_type ())\n#define SEAFILE_SESSION_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_SESSION_INFO, SeafileSessionInfo))\n#define SEAFILE_SESSION_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_SESSION_INFO, SeafileSessionInfoClass))\n#define SEAFILE_IS_SESSION_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_SESSION_INFO))\n#define SEAFILE_IS_SESSION_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_SESSION_INFO))\n#define SEAFILE_SESSION_INFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_SESSION_INFO, SeafileSessionInfoClass))\n\ntypedef struct _SeafileSessionInfo SeafileSessionInfo;\ntypedef struct _SeafileSessionInfoClass SeafileSessionInfoClass;\ntypedef struct _SeafileSessionInfoPrivate SeafileSessionInfoPrivate;\nenum  {\n\tSEAFILE_SESSION_INFO_0_PROPERTY,\n\tSEAFILE_SESSION_INFO_DATADIR_PROPERTY,\n\tSEAFILE_SESSION_INFO_NUM_PROPERTIES\n};\nstatic GParamSpec* seafile_session_info_properties[SEAFILE_SESSION_INFO_NUM_PROPERTIES];\n\n#define SEAFILE_TYPE_DIFF_ENTRY (seafile_diff_entry_get_type ())\n#define SEAFILE_DIFF_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_DIFF_ENTRY, SeafileDiffEntry))\n#define SEAFILE_DIFF_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_DIFF_ENTRY, SeafileDiffEntryClass))\n#define SEAFILE_IS_DIFF_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_DIFF_ENTRY))\n#define SEAFILE_IS_DIFF_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_DIFF_ENTRY))\n#define SEAFILE_DIFF_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_DIFF_ENTRY, SeafileDiffEntryClass))\n\ntypedef struct _SeafileDiffEntry SeafileDiffEntry;\ntypedef struct _SeafileDiffEntryClass SeafileDiffEntryClass;\ntypedef struct _SeafileDiffEntryPrivate SeafileDiffEntryPrivate;\nenum  {\n\tSEAFILE_DIFF_ENTRY_0_PROPERTY,\n\tSEAFILE_DIFF_ENTRY_STATUS_PROPERTY,\n\tSEAFILE_DIFF_ENTRY_NAME_PROPERTY,\n\tSEAFILE_DIFF_ENTRY_NEW_NAME_PROPERTY,\n\tSEAFILE_DIFF_ENTRY_NUM_PROPERTIES\n};\nstatic GParamSpec* seafile_diff_entry_properties[SEAFILE_DIFF_ENTRY_NUM_PROPERTIES];\n\n#define SEAFILE_TYPE_ENCRYPTION_INFO (seafile_encryption_info_get_type ())\n#define SEAFILE_ENCRYPTION_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_ENCRYPTION_INFO, SeafileEncryptionInfo))\n#define SEAFILE_ENCRYPTION_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_ENCRYPTION_INFO, SeafileEncryptionInfoClass))\n#define SEAFILE_IS_ENCRYPTION_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_ENCRYPTION_INFO))\n#define SEAFILE_IS_ENCRYPTION_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_ENCRYPTION_INFO))\n#define SEAFILE_ENCRYPTION_INFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_ENCRYPTION_INFO, SeafileEncryptionInfoClass))\n\ntypedef struct _SeafileEncryptionInfo SeafileEncryptionInfo;\ntypedef struct _SeafileEncryptionInfoClass SeafileEncryptionInfoClass;\ntypedef struct _SeafileEncryptionInfoPrivate SeafileEncryptionInfoPrivate;\nenum  {\n\tSEAFILE_ENCRYPTION_INFO_0_PROPERTY,\n\tSEAFILE_ENCRYPTION_INFO_REPO_ID_PROPERTY,\n\tSEAFILE_ENCRYPTION_INFO_PASSWD_PROPERTY,\n\tSEAFILE_ENCRYPTION_INFO_ENC_VERSION_PROPERTY,\n\tSEAFILE_ENCRYPTION_INFO_MAGIC_PROPERTY,\n\tSEAFILE_ENCRYPTION_INFO_PWD_HASH_PROPERTY,\n\tSEAFILE_ENCRYPTION_INFO_RANDOM_KEY_PROPERTY,\n\tSEAFILE_ENCRYPTION_INFO_SALT_PROPERTY,\n\tSEAFILE_ENCRYPTION_INFO_NUM_PROPERTIES\n};\nstatic GParamSpec* seafile_encryption_info_properties[SEAFILE_ENCRYPTION_INFO_NUM_PROPERTIES];\n\n#define SEAFILE_TYPE_FILE_SYNC_ERROR (seafile_file_sync_error_get_type ())\n#define SEAFILE_FILE_SYNC_ERROR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_FILE_SYNC_ERROR, SeafileFileSyncError))\n#define SEAFILE_FILE_SYNC_ERROR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_FILE_SYNC_ERROR, SeafileFileSyncErrorClass))\n#define SEAFILE_IS_FILE_SYNC_ERROR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_FILE_SYNC_ERROR))\n#define SEAFILE_IS_FILE_SYNC_ERROR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_FILE_SYNC_ERROR))\n#define SEAFILE_FILE_SYNC_ERROR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_FILE_SYNC_ERROR, SeafileFileSyncErrorClass))\n\ntypedef struct _SeafileFileSyncError SeafileFileSyncError;\ntypedef struct _SeafileFileSyncErrorClass SeafileFileSyncErrorClass;\ntypedef struct _SeafileFileSyncErrorPrivate SeafileFileSyncErrorPrivate;\nenum  {\n\tSEAFILE_FILE_SYNC_ERROR_0_PROPERTY,\n\tSEAFILE_FILE_SYNC_ERROR_ID_PROPERTY,\n\tSEAFILE_FILE_SYNC_ERROR_REPO_ID_PROPERTY,\n\tSEAFILE_FILE_SYNC_ERROR_REPO_NAME_PROPERTY,\n\tSEAFILE_FILE_SYNC_ERROR_PATH_PROPERTY,\n\tSEAFILE_FILE_SYNC_ERROR_ERR_ID_PROPERTY,\n\tSEAFILE_FILE_SYNC_ERROR_TIMESTAMP_PROPERTY,\n\tSEAFILE_FILE_SYNC_ERROR_NUM_PROPERTIES\n};\nstatic GParamSpec* seafile_file_sync_error_properties[SEAFILE_FILE_SYNC_ERROR_NUM_PROPERTIES];\n\nstruct _SeafileRepo {\n\tGObject parent_instance;\n\tSeafileRepoPrivate * priv;\n\tgchar _id[37];\n\tgchar* _name;\n\tgchar* _desc;\n\tgchar* _worktree;\n\tgchar* _relay_id;\n};\n\nstruct _SeafileRepoClass {\n\tGObjectClass parent_class;\n};\n\nstruct _SeafileRepoPrivate {\n\tgint _version;\n\tgint _last_modify;\n\tgint64 _size;\n\tgint64 _file_count;\n\tgchar* _head_cmmt_id;\n\tgchar* _root;\n\tgchar* _repo_id;\n\tgchar* _repo_name;\n\tgchar* _repo_desc;\n\tgint _last_modified;\n\tgboolean _encrypted;\n\tgchar* _magic;\n\tgint _enc_version;\n\tgchar* _random_key;\n\tgchar* _salt;\n\tgint _last_sync_time;\n\tgboolean _auto_sync;\n\tgboolean _worktree_invalid;\n\tgboolean _is_virtual;\n\tgchar* _origin_repo_id;\n\tgchar* _origin_repo_name;\n\tgchar* _origin_path;\n\tgboolean _is_original_owner;\n\tgchar* _virtual_perm;\n\tgchar* _store_id;\n\tgboolean _is_corrupted;\n\tgboolean _repaired;\n\tgchar* _share_type;\n\tgchar* _permission;\n\tgchar* _user;\n\tgint _group_id;\n\tgboolean _is_shared;\n};\n\nstruct _SeafileSyncTask {\n\tGObject parent_instance;\n\tSeafileSyncTaskPrivate * priv;\n};\n\nstruct _SeafileSyncTaskClass {\n\tGObjectClass parent_class;\n};\n\nstruct _SeafileSyncTaskPrivate {\n\tgboolean _force_upload;\n\tgchar* _repo_id;\n\tgchar* _state;\n\tgint _error;\n};\n\nstruct _SeafileSessionInfo {\n\tGObject parent_instance;\n\tSeafileSessionInfoPrivate * priv;\n};\n\nstruct _SeafileSessionInfoClass {\n\tGObjectClass parent_class;\n};\n\nstruct _SeafileSessionInfoPrivate {\n\tgchar* _datadir;\n};\n\nstruct _SeafileDiffEntry {\n\tGObject parent_instance;\n\tSeafileDiffEntryPrivate * priv;\n};\n\nstruct _SeafileDiffEntryClass {\n\tGObjectClass parent_class;\n};\n\nstruct _SeafileDiffEntryPrivate {\n\tgchar* _status;\n\tgchar* _name;\n\tgchar* _new_name;\n};\n\nstruct _SeafileEncryptionInfo {\n\tGObject parent_instance;\n\tSeafileEncryptionInfoPrivate * priv;\n};\n\nstruct _SeafileEncryptionInfoClass {\n\tGObjectClass parent_class;\n};\n\nstruct _SeafileEncryptionInfoPrivate {\n\tgchar* _repo_id;\n\tgchar* _passwd;\n\tgint _enc_version;\n\tgchar* _magic;\n\tgchar* _pwd_hash;\n\tgchar* _random_key;\n\tgchar* _salt;\n};\n\nstruct _SeafileFileSyncError {\n\tGObject parent_instance;\n\tSeafileFileSyncErrorPrivate * priv;\n};\n\nstruct _SeafileFileSyncErrorClass {\n\tGObjectClass parent_class;\n};\n\nstruct _SeafileFileSyncErrorPrivate {\n\tgint _id;\n\tgchar* _repo_id;\n\tgchar* _repo_name;\n\tgchar* _path;\n\tgint _err_id;\n\tgint64 _timestamp;\n};\n\nstatic gint SeafileRepo_private_offset;\nstatic gpointer seafile_repo_parent_class = NULL;\nstatic gint SeafileSyncTask_private_offset;\nstatic gpointer seafile_sync_task_parent_class = NULL;\nstatic gint SeafileSessionInfo_private_offset;\nstatic gpointer seafile_session_info_parent_class = NULL;\nstatic gint SeafileDiffEntry_private_offset;\nstatic gpointer seafile_diff_entry_parent_class = NULL;\nstatic gint SeafileEncryptionInfo_private_offset;\nstatic gpointer seafile_encryption_info_parent_class = NULL;\nstatic gint SeafileFileSyncError_private_offset;\nstatic gpointer seafile_file_sync_error_parent_class = NULL;\n\nVALA_EXTERN GType seafile_repo_get_type (void) G_GNUC_CONST ;\nG_DEFINE_AUTOPTR_CLEANUP_FUNC (SeafileRepo, g_object_unref)\nVALA_EXTERN SeafileRepo* seafile_repo_new (void);\nVALA_EXTERN SeafileRepo* seafile_repo_construct (GType object_type);\nVALA_EXTERN const gchar* seafile_repo_get_id (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_id (SeafileRepo* self,\n                          const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_name (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_name (SeafileRepo* self,\n                            const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_desc (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_desc (SeafileRepo* self,\n                            const gchar* value);\nVALA_EXTERN gint seafile_repo_get_version (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_version (SeafileRepo* self,\n                               gint value);\nVALA_EXTERN gint seafile_repo_get_last_modify (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_last_modify (SeafileRepo* self,\n                                   gint value);\nVALA_EXTERN gint64 seafile_repo_get_size (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_size (SeafileRepo* self,\n                            gint64 value);\nVALA_EXTERN gint64 seafile_repo_get_file_count (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_file_count (SeafileRepo* self,\n                                  gint64 value);\nVALA_EXTERN const gchar* seafile_repo_get_head_cmmt_id (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_head_cmmt_id (SeafileRepo* self,\n                                    const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_root (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_root (SeafileRepo* self,\n                            const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_repo_id (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_repo_id (SeafileRepo* self,\n                               const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_repo_name (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_repo_name (SeafileRepo* self,\n                                 const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_repo_desc (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_repo_desc (SeafileRepo* self,\n                                 const gchar* value);\nVALA_EXTERN gint seafile_repo_get_last_modified (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_last_modified (SeafileRepo* self,\n                                     gint value);\nVALA_EXTERN gboolean seafile_repo_get_encrypted (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_encrypted (SeafileRepo* self,\n                                 gboolean value);\nVALA_EXTERN const gchar* seafile_repo_get_magic (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_magic (SeafileRepo* self,\n                             const gchar* value);\nVALA_EXTERN gint seafile_repo_get_enc_version (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_enc_version (SeafileRepo* self,\n                                   gint value);\nVALA_EXTERN const gchar* seafile_repo_get_random_key (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_random_key (SeafileRepo* self,\n                                  const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_salt (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_salt (SeafileRepo* self,\n                            const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_worktree (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_worktree (SeafileRepo* self,\n                                const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_relay_id (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_relay_id (SeafileRepo* self,\n                                const gchar* value);\nVALA_EXTERN gint seafile_repo_get_last_sync_time (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_last_sync_time (SeafileRepo* self,\n                                      gint value);\nVALA_EXTERN gboolean seafile_repo_get_auto_sync (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_auto_sync (SeafileRepo* self,\n                                 gboolean value);\nVALA_EXTERN gboolean seafile_repo_get_worktree_invalid (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_worktree_invalid (SeafileRepo* self,\n                                        gboolean value);\nVALA_EXTERN gboolean seafile_repo_get_is_virtual (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_is_virtual (SeafileRepo* self,\n                                  gboolean value);\nVALA_EXTERN const gchar* seafile_repo_get_origin_repo_id (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_origin_repo_id (SeafileRepo* self,\n                                      const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_origin_repo_name (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_origin_repo_name (SeafileRepo* self,\n                                        const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_origin_path (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_origin_path (SeafileRepo* self,\n                                   const gchar* value);\nVALA_EXTERN gboolean seafile_repo_get_is_original_owner (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_is_original_owner (SeafileRepo* self,\n                                         gboolean value);\nVALA_EXTERN const gchar* seafile_repo_get_virtual_perm (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_virtual_perm (SeafileRepo* self,\n                                    const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_store_id (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_store_id (SeafileRepo* self,\n                                const gchar* value);\nVALA_EXTERN gboolean seafile_repo_get_is_corrupted (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_is_corrupted (SeafileRepo* self,\n                                    gboolean value);\nVALA_EXTERN gboolean seafile_repo_get_repaired (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_repaired (SeafileRepo* self,\n                                gboolean value);\nVALA_EXTERN const gchar* seafile_repo_get_share_type (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_share_type (SeafileRepo* self,\n                                  const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_permission (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_permission (SeafileRepo* self,\n                                  const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_user (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_user (SeafileRepo* self,\n                            const gchar* value);\nVALA_EXTERN gint seafile_repo_get_group_id (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_group_id (SeafileRepo* self,\n                                gint value);\nVALA_EXTERN gboolean seafile_repo_get_is_shared (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_is_shared (SeafileRepo* self,\n                                 gboolean value);\nstatic void seafile_repo_finalize (GObject * obj);\nstatic GType seafile_repo_get_type_once (void);\nstatic void _vala_seafile_repo_get_property (GObject * object,\n                                      guint property_id,\n                                      GValue * value,\n                                      GParamSpec * pspec);\nstatic void _vala_seafile_repo_set_property (GObject * object,\n                                      guint property_id,\n                                      const GValue * value,\n                                      GParamSpec * pspec);\nVALA_EXTERN GType seafile_sync_task_get_type (void) G_GNUC_CONST ;\nG_DEFINE_AUTOPTR_CLEANUP_FUNC (SeafileSyncTask, g_object_unref)\nVALA_EXTERN SeafileSyncTask* seafile_sync_task_new (void);\nVALA_EXTERN SeafileSyncTask* seafile_sync_task_construct (GType object_type);\nVALA_EXTERN gboolean seafile_sync_task_get_force_upload (SeafileSyncTask* self);\nVALA_EXTERN void seafile_sync_task_set_force_upload (SeafileSyncTask* self,\n                                         gboolean value);\nVALA_EXTERN const gchar* seafile_sync_task_get_repo_id (SeafileSyncTask* self);\nVALA_EXTERN void seafile_sync_task_set_repo_id (SeafileSyncTask* self,\n                                    const gchar* value);\nVALA_EXTERN const gchar* seafile_sync_task_get_state (SeafileSyncTask* self);\nVALA_EXTERN void seafile_sync_task_set_state (SeafileSyncTask* self,\n                                  const gchar* value);\nVALA_EXTERN gint seafile_sync_task_get_error (SeafileSyncTask* self);\nVALA_EXTERN void seafile_sync_task_set_error (SeafileSyncTask* self,\n                                  gint value);\nstatic void seafile_sync_task_finalize (GObject * obj);\nstatic GType seafile_sync_task_get_type_once (void);\nstatic void _vala_seafile_sync_task_get_property (GObject * object,\n                                           guint property_id,\n                                           GValue * value,\n                                           GParamSpec * pspec);\nstatic void _vala_seafile_sync_task_set_property (GObject * object,\n                                           guint property_id,\n                                           const GValue * value,\n                                           GParamSpec * pspec);\nVALA_EXTERN GType seafile_session_info_get_type (void) G_GNUC_CONST ;\nG_DEFINE_AUTOPTR_CLEANUP_FUNC (SeafileSessionInfo, g_object_unref)\nVALA_EXTERN SeafileSessionInfo* seafile_session_info_new (void);\nVALA_EXTERN SeafileSessionInfo* seafile_session_info_construct (GType object_type);\nVALA_EXTERN const gchar* seafile_session_info_get_datadir (SeafileSessionInfo* self);\nVALA_EXTERN void seafile_session_info_set_datadir (SeafileSessionInfo* self,\n                                       const gchar* value);\nstatic void seafile_session_info_finalize (GObject * obj);\nstatic GType seafile_session_info_get_type_once (void);\nstatic void _vala_seafile_session_info_get_property (GObject * object,\n                                              guint property_id,\n                                              GValue * value,\n                                              GParamSpec * pspec);\nstatic void _vala_seafile_session_info_set_property (GObject * object,\n                                              guint property_id,\n                                              const GValue * value,\n                                              GParamSpec * pspec);\nVALA_EXTERN GType seafile_diff_entry_get_type (void) G_GNUC_CONST ;\nG_DEFINE_AUTOPTR_CLEANUP_FUNC (SeafileDiffEntry, g_object_unref)\nVALA_EXTERN SeafileDiffEntry* seafile_diff_entry_new (void);\nVALA_EXTERN SeafileDiffEntry* seafile_diff_entry_construct (GType object_type);\nVALA_EXTERN const gchar* seafile_diff_entry_get_status (SeafileDiffEntry* self);\nVALA_EXTERN void seafile_diff_entry_set_status (SeafileDiffEntry* self,\n                                    const gchar* value);\nVALA_EXTERN const gchar* seafile_diff_entry_get_name (SeafileDiffEntry* self);\nVALA_EXTERN void seafile_diff_entry_set_name (SeafileDiffEntry* self,\n                                  const gchar* value);\nVALA_EXTERN const gchar* seafile_diff_entry_get_new_name (SeafileDiffEntry* self);\nVALA_EXTERN void seafile_diff_entry_set_new_name (SeafileDiffEntry* self,\n                                      const gchar* value);\nstatic void seafile_diff_entry_finalize (GObject * obj);\nstatic GType seafile_diff_entry_get_type_once (void);\nstatic void _vala_seafile_diff_entry_get_property (GObject * object,\n                                            guint property_id,\n                                            GValue * value,\n                                            GParamSpec * pspec);\nstatic void _vala_seafile_diff_entry_set_property (GObject * object,\n                                            guint property_id,\n                                            const GValue * value,\n                                            GParamSpec * pspec);\nVALA_EXTERN GType seafile_encryption_info_get_type (void) G_GNUC_CONST ;\nG_DEFINE_AUTOPTR_CLEANUP_FUNC (SeafileEncryptionInfo, g_object_unref)\nVALA_EXTERN SeafileEncryptionInfo* seafile_encryption_info_new (void);\nVALA_EXTERN SeafileEncryptionInfo* seafile_encryption_info_construct (GType object_type);\nVALA_EXTERN const gchar* seafile_encryption_info_get_repo_id (SeafileEncryptionInfo* self);\nVALA_EXTERN void seafile_encryption_info_set_repo_id (SeafileEncryptionInfo* self,\n                                          const gchar* value);\nVALA_EXTERN const gchar* seafile_encryption_info_get_passwd (SeafileEncryptionInfo* self);\nVALA_EXTERN void seafile_encryption_info_set_passwd (SeafileEncryptionInfo* self,\n                                         const gchar* value);\nVALA_EXTERN gint seafile_encryption_info_get_enc_version (SeafileEncryptionInfo* self);\nVALA_EXTERN void seafile_encryption_info_set_enc_version (SeafileEncryptionInfo* self,\n                                              gint value);\nVALA_EXTERN const gchar* seafile_encryption_info_get_magic (SeafileEncryptionInfo* self);\nVALA_EXTERN void seafile_encryption_info_set_magic (SeafileEncryptionInfo* self,\n                                        const gchar* value);\nVALA_EXTERN const gchar* seafile_encryption_info_get_pwd_hash (SeafileEncryptionInfo* self);\nVALA_EXTERN void seafile_encryption_info_set_pwd_hash (SeafileEncryptionInfo* self,\n                                           const gchar* value);\nVALA_EXTERN const gchar* seafile_encryption_info_get_random_key (SeafileEncryptionInfo* self);\nVALA_EXTERN void seafile_encryption_info_set_random_key (SeafileEncryptionInfo* self,\n                                             const gchar* value);\nVALA_EXTERN const gchar* seafile_encryption_info_get_salt (SeafileEncryptionInfo* self);\nVALA_EXTERN void seafile_encryption_info_set_salt (SeafileEncryptionInfo* self,\n                                       const gchar* value);\nstatic void seafile_encryption_info_finalize (GObject * obj);\nstatic GType seafile_encryption_info_get_type_once (void);\nstatic void _vala_seafile_encryption_info_get_property (GObject * object,\n                                                 guint property_id,\n                                                 GValue * value,\n                                                 GParamSpec * pspec);\nstatic void _vala_seafile_encryption_info_set_property (GObject * object,\n                                                 guint property_id,\n                                                 const GValue * value,\n                                                 GParamSpec * pspec);\nVALA_EXTERN GType seafile_file_sync_error_get_type (void) G_GNUC_CONST ;\nG_DEFINE_AUTOPTR_CLEANUP_FUNC (SeafileFileSyncError, g_object_unref)\nVALA_EXTERN SeafileFileSyncError* seafile_file_sync_error_new (void);\nVALA_EXTERN SeafileFileSyncError* seafile_file_sync_error_construct (GType object_type);\nVALA_EXTERN gint seafile_file_sync_error_get_id (SeafileFileSyncError* self);\nVALA_EXTERN void seafile_file_sync_error_set_id (SeafileFileSyncError* self,\n                                     gint value);\nVALA_EXTERN const gchar* seafile_file_sync_error_get_repo_id (SeafileFileSyncError* self);\nVALA_EXTERN void seafile_file_sync_error_set_repo_id (SeafileFileSyncError* self,\n                                          const gchar* value);\nVALA_EXTERN const gchar* seafile_file_sync_error_get_repo_name (SeafileFileSyncError* self);\nVALA_EXTERN void seafile_file_sync_error_set_repo_name (SeafileFileSyncError* self,\n                                            const gchar* value);\nVALA_EXTERN const gchar* seafile_file_sync_error_get_path (SeafileFileSyncError* self);\nVALA_EXTERN void seafile_file_sync_error_set_path (SeafileFileSyncError* self,\n                                       const gchar* value);\nVALA_EXTERN gint seafile_file_sync_error_get_err_id (SeafileFileSyncError* self);\nVALA_EXTERN void seafile_file_sync_error_set_err_id (SeafileFileSyncError* self,\n                                         gint value);\nVALA_EXTERN gint64 seafile_file_sync_error_get_timestamp (SeafileFileSyncError* self);\nVALA_EXTERN void seafile_file_sync_error_set_timestamp (SeafileFileSyncError* self,\n                                            gint64 value);\nstatic void seafile_file_sync_error_finalize (GObject * obj);\nstatic GType seafile_file_sync_error_get_type_once (void);\nstatic void _vala_seafile_file_sync_error_get_property (GObject * object,\n                                                 guint property_id,\n                                                 GValue * value,\n                                                 GParamSpec * pspec);\nstatic void _vala_seafile_file_sync_error_set_property (GObject * object,\n                                                 guint property_id,\n                                                 const GValue * value,\n                                                 GParamSpec * pspec);\n\nstatic inline gpointer\nseafile_repo_get_instance_private (SeafileRepo* self)\n{\n\treturn G_STRUCT_MEMBER_P (self, SeafileRepo_private_offset);\n}\n\nSeafileRepo*\nseafile_repo_construct (GType object_type)\n{\n\tSeafileRepo * self = NULL;\n\tself = (SeafileRepo*) g_object_new (object_type, NULL);\n\treturn self;\n}\n\nSeafileRepo*\nseafile_repo_new (void)\n{\n\treturn seafile_repo_construct (SEAFILE_TYPE_REPO);\n}\n\nconst gchar*\nseafile_repo_get_id (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tg_return_val_if_fail (self != NULL, NULL);\n\tresult = (const gchar*) self->_id;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_id (SeafileRepo* self,\n                     const gchar* value)\n{\n\tg_return_if_fail (self != NULL);\n\tmemcpy (self->_id, value, (gsize) 36);\n\tself->_id[36] = '\\0';\n\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_ID_PROPERTY]);\n}\n\nconst gchar*\nseafile_repo_get_name (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->_name;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_name (SeafileRepo* self,\n                       const gchar* value)\n{\n\tgchar* _tmp0_;\n\tg_return_if_fail (self != NULL);\n\t_tmp0_ = g_strdup (value);\n\t_g_free0 (self->_name);\n\tself->_name = _tmp0_;\n\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_NAME_PROPERTY]);\n}\n\nconst gchar*\nseafile_repo_get_desc (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->_desc;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_desc (SeafileRepo* self,\n                       const gchar* value)\n{\n\tgchar* _tmp0_;\n\tg_return_if_fail (self != NULL);\n\t_tmp0_ = g_strdup (value);\n\t_g_free0 (self->_desc);\n\tself->_desc = _tmp0_;\n\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_DESC_PROPERTY]);\n}\n\ngint\nseafile_repo_get_version (SeafileRepo* self)\n{\n\tgint result;\n\tg_return_val_if_fail (self != NULL, 0);\n\tresult = self->priv->_version;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_version (SeafileRepo* self,\n                          gint value)\n{\n\tgint old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_version (self);\n\tif (old_value != value) {\n\t\tself->priv->_version = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_VERSION_PROPERTY]);\n\t}\n}\n\ngint\nseafile_repo_get_last_modify (SeafileRepo* self)\n{\n\tgint result;\n\tg_return_val_if_fail (self != NULL, 0);\n\tresult = self->priv->_last_modify;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_last_modify (SeafileRepo* self,\n                              gint value)\n{\n\tgint old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_last_modify (self);\n\tif (old_value != value) {\n\t\tself->priv->_last_modify = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_LAST_MODIFY_PROPERTY]);\n\t}\n}\n\ngint64\nseafile_repo_get_size (SeafileRepo* self)\n{\n\tgint64 result;\n\tg_return_val_if_fail (self != NULL, 0LL);\n\tresult = self->priv->_size;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_size (SeafileRepo* self,\n                       gint64 value)\n{\n\tgint64 old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_size (self);\n\tif (old_value != value) {\n\t\tself->priv->_size = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_SIZE_PROPERTY]);\n\t}\n}\n\ngint64\nseafile_repo_get_file_count (SeafileRepo* self)\n{\n\tgint64 result;\n\tg_return_val_if_fail (self != NULL, 0LL);\n\tresult = self->priv->_file_count;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_file_count (SeafileRepo* self,\n                             gint64 value)\n{\n\tgint64 old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_file_count (self);\n\tif (old_value != value) {\n\t\tself->priv->_file_count = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_FILE_COUNT_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_repo_get_head_cmmt_id (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_head_cmmt_id;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_head_cmmt_id (SeafileRepo* self,\n                               const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_head_cmmt_id (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_head_cmmt_id);\n\t\tself->priv->_head_cmmt_id = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_HEAD_CMMT_ID_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_repo_get_root (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_root;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_root (SeafileRepo* self,\n                       const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_root (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_root);\n\t\tself->priv->_root = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_ROOT_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_repo_get_repo_id (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_repo_id;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_repo_id (SeafileRepo* self,\n                          const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_repo_id (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_repo_id);\n\t\tself->priv->_repo_id = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_REPO_ID_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_repo_get_repo_name (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_repo_name;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_repo_name (SeafileRepo* self,\n                            const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_repo_name (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_repo_name);\n\t\tself->priv->_repo_name = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_REPO_NAME_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_repo_get_repo_desc (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_repo_desc;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_repo_desc (SeafileRepo* self,\n                            const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_repo_desc (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_repo_desc);\n\t\tself->priv->_repo_desc = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_REPO_DESC_PROPERTY]);\n\t}\n}\n\ngint\nseafile_repo_get_last_modified (SeafileRepo* self)\n{\n\tgint result;\n\tg_return_val_if_fail (self != NULL, 0);\n\tresult = self->priv->_last_modified;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_last_modified (SeafileRepo* self,\n                                gint value)\n{\n\tgint old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_last_modified (self);\n\tif (old_value != value) {\n\t\tself->priv->_last_modified = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_LAST_MODIFIED_PROPERTY]);\n\t}\n}\n\ngboolean\nseafile_repo_get_encrypted (SeafileRepo* self)\n{\n\tgboolean result;\n\tg_return_val_if_fail (self != NULL, FALSE);\n\tresult = self->priv->_encrypted;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_encrypted (SeafileRepo* self,\n                            gboolean value)\n{\n\tgboolean old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_encrypted (self);\n\tif (old_value != value) {\n\t\tself->priv->_encrypted = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_ENCRYPTED_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_repo_get_magic (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_magic;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_magic (SeafileRepo* self,\n                        const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_magic (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_magic);\n\t\tself->priv->_magic = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_MAGIC_PROPERTY]);\n\t}\n}\n\ngint\nseafile_repo_get_enc_version (SeafileRepo* self)\n{\n\tgint result;\n\tg_return_val_if_fail (self != NULL, 0);\n\tresult = self->priv->_enc_version;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_enc_version (SeafileRepo* self,\n                              gint value)\n{\n\tgint old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_enc_version (self);\n\tif (old_value != value) {\n\t\tself->priv->_enc_version = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_ENC_VERSION_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_repo_get_random_key (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_random_key;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_random_key (SeafileRepo* self,\n                             const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_random_key (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_random_key);\n\t\tself->priv->_random_key = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_RANDOM_KEY_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_repo_get_salt (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_salt;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_salt (SeafileRepo* self,\n                       const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_salt (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_salt);\n\t\tself->priv->_salt = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_SALT_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_repo_get_worktree (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->_worktree;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_worktree (SeafileRepo* self,\n                           const gchar* value)\n{\n\tgchar* _tmp0_;\n\tg_return_if_fail (self != NULL);\n\t_tmp0_ = g_strdup (value);\n\t_g_free0 (self->_worktree);\n\tself->_worktree = _tmp0_;\n\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_WORKTREE_PROPERTY]);\n}\n\nconst gchar*\nseafile_repo_get_relay_id (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->_relay_id;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_relay_id (SeafileRepo* self,\n                           const gchar* value)\n{\n\tgchar* _tmp0_;\n\tg_return_if_fail (self != NULL);\n\t_tmp0_ = g_strdup (value);\n\t_g_free0 (self->_relay_id);\n\tself->_relay_id = _tmp0_;\n\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_RELAY_ID_PROPERTY]);\n}\n\ngint\nseafile_repo_get_last_sync_time (SeafileRepo* self)\n{\n\tgint result;\n\tg_return_val_if_fail (self != NULL, 0);\n\tresult = self->priv->_last_sync_time;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_last_sync_time (SeafileRepo* self,\n                                 gint value)\n{\n\tgint old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_last_sync_time (self);\n\tif (old_value != value) {\n\t\tself->priv->_last_sync_time = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_LAST_SYNC_TIME_PROPERTY]);\n\t}\n}\n\ngboolean\nseafile_repo_get_auto_sync (SeafileRepo* self)\n{\n\tgboolean result;\n\tg_return_val_if_fail (self != NULL, FALSE);\n\tresult = self->priv->_auto_sync;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_auto_sync (SeafileRepo* self,\n                            gboolean value)\n{\n\tgboolean old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_auto_sync (self);\n\tif (old_value != value) {\n\t\tself->priv->_auto_sync = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_AUTO_SYNC_PROPERTY]);\n\t}\n}\n\ngboolean\nseafile_repo_get_worktree_invalid (SeafileRepo* self)\n{\n\tgboolean result;\n\tg_return_val_if_fail (self != NULL, FALSE);\n\tresult = self->priv->_worktree_invalid;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_worktree_invalid (SeafileRepo* self,\n                                   gboolean value)\n{\n\tgboolean old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_worktree_invalid (self);\n\tif (old_value != value) {\n\t\tself->priv->_worktree_invalid = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_WORKTREE_INVALID_PROPERTY]);\n\t}\n}\n\ngboolean\nseafile_repo_get_is_virtual (SeafileRepo* self)\n{\n\tgboolean result;\n\tg_return_val_if_fail (self != NULL, FALSE);\n\tresult = self->priv->_is_virtual;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_is_virtual (SeafileRepo* self,\n                             gboolean value)\n{\n\tgboolean old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_is_virtual (self);\n\tif (old_value != value) {\n\t\tself->priv->_is_virtual = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_IS_VIRTUAL_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_repo_get_origin_repo_id (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_origin_repo_id;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_origin_repo_id (SeafileRepo* self,\n                                 const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_origin_repo_id (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_origin_repo_id);\n\t\tself->priv->_origin_repo_id = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_ORIGIN_REPO_ID_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_repo_get_origin_repo_name (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_origin_repo_name;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_origin_repo_name (SeafileRepo* self,\n                                   const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_origin_repo_name (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_origin_repo_name);\n\t\tself->priv->_origin_repo_name = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_ORIGIN_REPO_NAME_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_repo_get_origin_path (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_origin_path;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_origin_path (SeafileRepo* self,\n                              const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_origin_path (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_origin_path);\n\t\tself->priv->_origin_path = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_ORIGIN_PATH_PROPERTY]);\n\t}\n}\n\ngboolean\nseafile_repo_get_is_original_owner (SeafileRepo* self)\n{\n\tgboolean result;\n\tg_return_val_if_fail (self != NULL, FALSE);\n\tresult = self->priv->_is_original_owner;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_is_original_owner (SeafileRepo* self,\n                                    gboolean value)\n{\n\tgboolean old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_is_original_owner (self);\n\tif (old_value != value) {\n\t\tself->priv->_is_original_owner = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_IS_ORIGINAL_OWNER_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_repo_get_virtual_perm (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_virtual_perm;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_virtual_perm (SeafileRepo* self,\n                               const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_virtual_perm (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_virtual_perm);\n\t\tself->priv->_virtual_perm = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_VIRTUAL_PERM_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_repo_get_store_id (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_store_id;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_store_id (SeafileRepo* self,\n                           const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_store_id (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_store_id);\n\t\tself->priv->_store_id = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_STORE_ID_PROPERTY]);\n\t}\n}\n\ngboolean\nseafile_repo_get_is_corrupted (SeafileRepo* self)\n{\n\tgboolean result;\n\tg_return_val_if_fail (self != NULL, FALSE);\n\tresult = self->priv->_is_corrupted;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_is_corrupted (SeafileRepo* self,\n                               gboolean value)\n{\n\tgboolean old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_is_corrupted (self);\n\tif (old_value != value) {\n\t\tself->priv->_is_corrupted = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_IS_CORRUPTED_PROPERTY]);\n\t}\n}\n\ngboolean\nseafile_repo_get_repaired (SeafileRepo* self)\n{\n\tgboolean result;\n\tg_return_val_if_fail (self != NULL, FALSE);\n\tresult = self->priv->_repaired;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_repaired (SeafileRepo* self,\n                           gboolean value)\n{\n\tgboolean old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_repaired (self);\n\tif (old_value != value) {\n\t\tself->priv->_repaired = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_REPAIRED_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_repo_get_share_type (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_share_type;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_share_type (SeafileRepo* self,\n                             const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_share_type (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_share_type);\n\t\tself->priv->_share_type = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_SHARE_TYPE_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_repo_get_permission (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_permission;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_permission (SeafileRepo* self,\n                             const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_permission (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_permission);\n\t\tself->priv->_permission = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_PERMISSION_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_repo_get_user (SeafileRepo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_user;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_user (SeafileRepo* self,\n                       const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_user (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_user);\n\t\tself->priv->_user = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_USER_PROPERTY]);\n\t}\n}\n\ngint\nseafile_repo_get_group_id (SeafileRepo* self)\n{\n\tgint result;\n\tg_return_val_if_fail (self != NULL, 0);\n\tresult = self->priv->_group_id;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_group_id (SeafileRepo* self,\n                           gint value)\n{\n\tgint old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_group_id (self);\n\tif (old_value != value) {\n\t\tself->priv->_group_id = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_GROUP_ID_PROPERTY]);\n\t}\n}\n\ngboolean\nseafile_repo_get_is_shared (SeafileRepo* self)\n{\n\tgboolean result;\n\tg_return_val_if_fail (self != NULL, FALSE);\n\tresult = self->priv->_is_shared;\n\treturn result;\n}\n\nvoid\nseafile_repo_set_is_shared (SeafileRepo* self,\n                            gboolean value)\n{\n\tgboolean old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_repo_get_is_shared (self);\n\tif (old_value != value) {\n\t\tself->priv->_is_shared = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_repo_properties[SEAFILE_REPO_IS_SHARED_PROPERTY]);\n\t}\n}\n\nstatic void\nseafile_repo_class_init (SeafileRepoClass * klass,\n                         gpointer klass_data)\n{\n\tseafile_repo_parent_class = g_type_class_peek_parent (klass);\n\tg_type_class_adjust_private_offset (klass, &SeafileRepo_private_offset);\n\tG_OBJECT_CLASS (klass)->get_property = _vala_seafile_repo_get_property;\n\tG_OBJECT_CLASS (klass)->set_property = _vala_seafile_repo_set_property;\n\tG_OBJECT_CLASS (klass)->finalize = seafile_repo_finalize;\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_ID_PROPERTY, seafile_repo_properties[SEAFILE_REPO_ID_PROPERTY] = g_param_spec_string (\"id\", \"id\", \"id\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_NAME_PROPERTY, seafile_repo_properties[SEAFILE_REPO_NAME_PROPERTY] = g_param_spec_string (\"name\", \"name\", \"name\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_DESC_PROPERTY, seafile_repo_properties[SEAFILE_REPO_DESC_PROPERTY] = g_param_spec_string (\"desc\", \"desc\", \"desc\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_VERSION_PROPERTY, seafile_repo_properties[SEAFILE_REPO_VERSION_PROPERTY] = g_param_spec_int (\"version\", \"version\", \"version\", G_MININT, G_MAXINT, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_LAST_MODIFY_PROPERTY, seafile_repo_properties[SEAFILE_REPO_LAST_MODIFY_PROPERTY] = g_param_spec_int (\"last-modify\", \"last-modify\", \"last-modify\", G_MININT, G_MAXINT, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_SIZE_PROPERTY, seafile_repo_properties[SEAFILE_REPO_SIZE_PROPERTY] = g_param_spec_int64 (\"size\", \"size\", \"size\", G_MININT64, G_MAXINT64, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_FILE_COUNT_PROPERTY, seafile_repo_properties[SEAFILE_REPO_FILE_COUNT_PROPERTY] = g_param_spec_int64 (\"file-count\", \"file-count\", \"file-count\", G_MININT64, G_MAXINT64, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_HEAD_CMMT_ID_PROPERTY, seafile_repo_properties[SEAFILE_REPO_HEAD_CMMT_ID_PROPERTY] = g_param_spec_string (\"head-cmmt-id\", \"head-cmmt-id\", \"head-cmmt-id\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_ROOT_PROPERTY, seafile_repo_properties[SEAFILE_REPO_ROOT_PROPERTY] = g_param_spec_string (\"root\", \"root\", \"root\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_REPO_ID_PROPERTY, seafile_repo_properties[SEAFILE_REPO_REPO_ID_PROPERTY] = g_param_spec_string (\"repo-id\", \"repo-id\", \"repo-id\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_REPO_NAME_PROPERTY, seafile_repo_properties[SEAFILE_REPO_REPO_NAME_PROPERTY] = g_param_spec_string (\"repo-name\", \"repo-name\", \"repo-name\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_REPO_DESC_PROPERTY, seafile_repo_properties[SEAFILE_REPO_REPO_DESC_PROPERTY] = g_param_spec_string (\"repo-desc\", \"repo-desc\", \"repo-desc\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_LAST_MODIFIED_PROPERTY, seafile_repo_properties[SEAFILE_REPO_LAST_MODIFIED_PROPERTY] = g_param_spec_int (\"last-modified\", \"last-modified\", \"last-modified\", G_MININT, G_MAXINT, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_ENCRYPTED_PROPERTY, seafile_repo_properties[SEAFILE_REPO_ENCRYPTED_PROPERTY] = g_param_spec_boolean (\"encrypted\", \"encrypted\", \"encrypted\", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_MAGIC_PROPERTY, seafile_repo_properties[SEAFILE_REPO_MAGIC_PROPERTY] = g_param_spec_string (\"magic\", \"magic\", \"magic\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_ENC_VERSION_PROPERTY, seafile_repo_properties[SEAFILE_REPO_ENC_VERSION_PROPERTY] = g_param_spec_int (\"enc-version\", \"enc-version\", \"enc-version\", G_MININT, G_MAXINT, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_RANDOM_KEY_PROPERTY, seafile_repo_properties[SEAFILE_REPO_RANDOM_KEY_PROPERTY] = g_param_spec_string (\"random-key\", \"random-key\", \"random-key\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_SALT_PROPERTY, seafile_repo_properties[SEAFILE_REPO_SALT_PROPERTY] = g_param_spec_string (\"salt\", \"salt\", \"salt\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_WORKTREE_PROPERTY, seafile_repo_properties[SEAFILE_REPO_WORKTREE_PROPERTY] = g_param_spec_string (\"worktree\", \"worktree\", \"worktree\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_RELAY_ID_PROPERTY, seafile_repo_properties[SEAFILE_REPO_RELAY_ID_PROPERTY] = g_param_spec_string (\"relay-id\", \"relay-id\", \"relay-id\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_LAST_SYNC_TIME_PROPERTY, seafile_repo_properties[SEAFILE_REPO_LAST_SYNC_TIME_PROPERTY] = g_param_spec_int (\"last-sync-time\", \"last-sync-time\", \"last-sync-time\", G_MININT, G_MAXINT, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_AUTO_SYNC_PROPERTY, seafile_repo_properties[SEAFILE_REPO_AUTO_SYNC_PROPERTY] = g_param_spec_boolean (\"auto-sync\", \"auto-sync\", \"auto-sync\", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_WORKTREE_INVALID_PROPERTY, seafile_repo_properties[SEAFILE_REPO_WORKTREE_INVALID_PROPERTY] = g_param_spec_boolean (\"worktree-invalid\", \"worktree-invalid\", \"worktree-invalid\", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_IS_VIRTUAL_PROPERTY, seafile_repo_properties[SEAFILE_REPO_IS_VIRTUAL_PROPERTY] = g_param_spec_boolean (\"is-virtual\", \"is-virtual\", \"is-virtual\", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_ORIGIN_REPO_ID_PROPERTY, seafile_repo_properties[SEAFILE_REPO_ORIGIN_REPO_ID_PROPERTY] = g_param_spec_string (\"origin-repo-id\", \"origin-repo-id\", \"origin-repo-id\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_ORIGIN_REPO_NAME_PROPERTY, seafile_repo_properties[SEAFILE_REPO_ORIGIN_REPO_NAME_PROPERTY] = g_param_spec_string (\"origin-repo-name\", \"origin-repo-name\", \"origin-repo-name\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_ORIGIN_PATH_PROPERTY, seafile_repo_properties[SEAFILE_REPO_ORIGIN_PATH_PROPERTY] = g_param_spec_string (\"origin-path\", \"origin-path\", \"origin-path\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_IS_ORIGINAL_OWNER_PROPERTY, seafile_repo_properties[SEAFILE_REPO_IS_ORIGINAL_OWNER_PROPERTY] = g_param_spec_boolean (\"is-original-owner\", \"is-original-owner\", \"is-original-owner\", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_VIRTUAL_PERM_PROPERTY, seafile_repo_properties[SEAFILE_REPO_VIRTUAL_PERM_PROPERTY] = g_param_spec_string (\"virtual-perm\", \"virtual-perm\", \"virtual-perm\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_STORE_ID_PROPERTY, seafile_repo_properties[SEAFILE_REPO_STORE_ID_PROPERTY] = g_param_spec_string (\"store-id\", \"store-id\", \"store-id\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_IS_CORRUPTED_PROPERTY, seafile_repo_properties[SEAFILE_REPO_IS_CORRUPTED_PROPERTY] = g_param_spec_boolean (\"is-corrupted\", \"is-corrupted\", \"is-corrupted\", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_REPAIRED_PROPERTY, seafile_repo_properties[SEAFILE_REPO_REPAIRED_PROPERTY] = g_param_spec_boolean (\"repaired\", \"repaired\", \"repaired\", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_SHARE_TYPE_PROPERTY, seafile_repo_properties[SEAFILE_REPO_SHARE_TYPE_PROPERTY] = g_param_spec_string (\"share-type\", \"share-type\", \"share-type\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_PERMISSION_PROPERTY, seafile_repo_properties[SEAFILE_REPO_PERMISSION_PROPERTY] = g_param_spec_string (\"permission\", \"permission\", \"permission\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_USER_PROPERTY, seafile_repo_properties[SEAFILE_REPO_USER_PROPERTY] = g_param_spec_string (\"user\", \"user\", \"user\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_GROUP_ID_PROPERTY, seafile_repo_properties[SEAFILE_REPO_GROUP_ID_PROPERTY] = g_param_spec_int (\"group-id\", \"group-id\", \"group-id\", G_MININT, G_MAXINT, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_REPO_IS_SHARED_PROPERTY, seafile_repo_properties[SEAFILE_REPO_IS_SHARED_PROPERTY] = g_param_spec_boolean (\"is-shared\", \"is-shared\", \"is-shared\", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n}\n\nstatic void\nseafile_repo_instance_init (SeafileRepo * self,\n                            gpointer klass)\n{\n\tself->priv = seafile_repo_get_instance_private (self);\n}\n\nstatic void\nseafile_repo_finalize (GObject * obj)\n{\n\tSeafileRepo * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (obj, SEAFILE_TYPE_REPO, SeafileRepo);\n\t_g_free0 (self->_name);\n\t_g_free0 (self->_desc);\n\t_g_free0 (self->priv->_head_cmmt_id);\n\t_g_free0 (self->priv->_root);\n\t_g_free0 (self->priv->_repo_id);\n\t_g_free0 (self->priv->_repo_name);\n\t_g_free0 (self->priv->_repo_desc);\n\t_g_free0 (self->priv->_magic);\n\t_g_free0 (self->priv->_random_key);\n\t_g_free0 (self->priv->_salt);\n\t_g_free0 (self->_worktree);\n\t_g_free0 (self->_relay_id);\n\t_g_free0 (self->priv->_origin_repo_id);\n\t_g_free0 (self->priv->_origin_repo_name);\n\t_g_free0 (self->priv->_origin_path);\n\t_g_free0 (self->priv->_virtual_perm);\n\t_g_free0 (self->priv->_store_id);\n\t_g_free0 (self->priv->_share_type);\n\t_g_free0 (self->priv->_permission);\n\t_g_free0 (self->priv->_user);\n\tG_OBJECT_CLASS (seafile_repo_parent_class)->finalize (obj);\n}\n\nstatic GType\nseafile_repo_get_type_once (void)\n{\n\tstatic const GTypeInfo g_define_type_info = { sizeof (SeafileRepoClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) seafile_repo_class_init, (GClassFinalizeFunc) NULL, NULL, sizeof (SeafileRepo), 0, (GInstanceInitFunc) seafile_repo_instance_init, NULL };\n\tGType seafile_repo_type_id;\n\tseafile_repo_type_id = g_type_register_static (G_TYPE_OBJECT, \"SeafileRepo\", &g_define_type_info, 0);\n\tSeafileRepo_private_offset = g_type_add_instance_private (seafile_repo_type_id, sizeof (SeafileRepoPrivate));\n\treturn seafile_repo_type_id;\n}\n\nGType\nseafile_repo_get_type (void)\n{\n\tstatic volatile gsize seafile_repo_type_id__once = 0;\n\tif (g_once_init_enter (&seafile_repo_type_id__once)) {\n\t\tGType seafile_repo_type_id;\n\t\tseafile_repo_type_id = seafile_repo_get_type_once ();\n\t\tg_once_init_leave (&seafile_repo_type_id__once, seafile_repo_type_id);\n\t}\n\treturn seafile_repo_type_id__once;\n}\n\nstatic void\n_vala_seafile_repo_get_property (GObject * object,\n                                 guint property_id,\n                                 GValue * value,\n                                 GParamSpec * pspec)\n{\n\tSeafileRepo * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (object, SEAFILE_TYPE_REPO, SeafileRepo);\n\tswitch (property_id) {\n\t\tcase SEAFILE_REPO_ID_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_id (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_NAME_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_name (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_DESC_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_desc (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_VERSION_PROPERTY:\n\t\tg_value_set_int (value, seafile_repo_get_version (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_LAST_MODIFY_PROPERTY:\n\t\tg_value_set_int (value, seafile_repo_get_last_modify (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_SIZE_PROPERTY:\n\t\tg_value_set_int64 (value, seafile_repo_get_size (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_FILE_COUNT_PROPERTY:\n\t\tg_value_set_int64 (value, seafile_repo_get_file_count (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_HEAD_CMMT_ID_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_head_cmmt_id (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_ROOT_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_root (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_REPO_ID_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_repo_id (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_REPO_NAME_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_repo_name (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_REPO_DESC_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_repo_desc (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_LAST_MODIFIED_PROPERTY:\n\t\tg_value_set_int (value, seafile_repo_get_last_modified (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_ENCRYPTED_PROPERTY:\n\t\tg_value_set_boolean (value, seafile_repo_get_encrypted (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_MAGIC_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_magic (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_ENC_VERSION_PROPERTY:\n\t\tg_value_set_int (value, seafile_repo_get_enc_version (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_RANDOM_KEY_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_random_key (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_SALT_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_salt (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_WORKTREE_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_worktree (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_RELAY_ID_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_relay_id (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_LAST_SYNC_TIME_PROPERTY:\n\t\tg_value_set_int (value, seafile_repo_get_last_sync_time (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_AUTO_SYNC_PROPERTY:\n\t\tg_value_set_boolean (value, seafile_repo_get_auto_sync (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_WORKTREE_INVALID_PROPERTY:\n\t\tg_value_set_boolean (value, seafile_repo_get_worktree_invalid (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_IS_VIRTUAL_PROPERTY:\n\t\tg_value_set_boolean (value, seafile_repo_get_is_virtual (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_ORIGIN_REPO_ID_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_origin_repo_id (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_ORIGIN_REPO_NAME_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_origin_repo_name (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_ORIGIN_PATH_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_origin_path (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_IS_ORIGINAL_OWNER_PROPERTY:\n\t\tg_value_set_boolean (value, seafile_repo_get_is_original_owner (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_VIRTUAL_PERM_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_virtual_perm (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_STORE_ID_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_store_id (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_IS_CORRUPTED_PROPERTY:\n\t\tg_value_set_boolean (value, seafile_repo_get_is_corrupted (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_REPAIRED_PROPERTY:\n\t\tg_value_set_boolean (value, seafile_repo_get_repaired (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_SHARE_TYPE_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_share_type (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_PERMISSION_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_permission (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_USER_PROPERTY:\n\t\tg_value_set_string (value, seafile_repo_get_user (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_GROUP_ID_PROPERTY:\n\t\tg_value_set_int (value, seafile_repo_get_group_id (self));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_IS_SHARED_PROPERTY:\n\t\tg_value_set_boolean (value, seafile_repo_get_is_shared (self));\n\t\tbreak;\n\t\tdefault:\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);\n\t\tbreak;\n\t}\n}\n\nstatic void\n_vala_seafile_repo_set_property (GObject * object,\n                                 guint property_id,\n                                 const GValue * value,\n                                 GParamSpec * pspec)\n{\n\tSeafileRepo * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (object, SEAFILE_TYPE_REPO, SeafileRepo);\n\tswitch (property_id) {\n\t\tcase SEAFILE_REPO_ID_PROPERTY:\n\t\tseafile_repo_set_id (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_NAME_PROPERTY:\n\t\tseafile_repo_set_name (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_DESC_PROPERTY:\n\t\tseafile_repo_set_desc (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_VERSION_PROPERTY:\n\t\tseafile_repo_set_version (self, g_value_get_int (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_LAST_MODIFY_PROPERTY:\n\t\tseafile_repo_set_last_modify (self, g_value_get_int (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_SIZE_PROPERTY:\n\t\tseafile_repo_set_size (self, g_value_get_int64 (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_FILE_COUNT_PROPERTY:\n\t\tseafile_repo_set_file_count (self, g_value_get_int64 (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_HEAD_CMMT_ID_PROPERTY:\n\t\tseafile_repo_set_head_cmmt_id (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_ROOT_PROPERTY:\n\t\tseafile_repo_set_root (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_REPO_ID_PROPERTY:\n\t\tseafile_repo_set_repo_id (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_REPO_NAME_PROPERTY:\n\t\tseafile_repo_set_repo_name (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_REPO_DESC_PROPERTY:\n\t\tseafile_repo_set_repo_desc (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_LAST_MODIFIED_PROPERTY:\n\t\tseafile_repo_set_last_modified (self, g_value_get_int (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_ENCRYPTED_PROPERTY:\n\t\tseafile_repo_set_encrypted (self, g_value_get_boolean (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_MAGIC_PROPERTY:\n\t\tseafile_repo_set_magic (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_ENC_VERSION_PROPERTY:\n\t\tseafile_repo_set_enc_version (self, g_value_get_int (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_RANDOM_KEY_PROPERTY:\n\t\tseafile_repo_set_random_key (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_SALT_PROPERTY:\n\t\tseafile_repo_set_salt (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_WORKTREE_PROPERTY:\n\t\tseafile_repo_set_worktree (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_RELAY_ID_PROPERTY:\n\t\tseafile_repo_set_relay_id (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_LAST_SYNC_TIME_PROPERTY:\n\t\tseafile_repo_set_last_sync_time (self, g_value_get_int (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_AUTO_SYNC_PROPERTY:\n\t\tseafile_repo_set_auto_sync (self, g_value_get_boolean (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_WORKTREE_INVALID_PROPERTY:\n\t\tseafile_repo_set_worktree_invalid (self, g_value_get_boolean (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_IS_VIRTUAL_PROPERTY:\n\t\tseafile_repo_set_is_virtual (self, g_value_get_boolean (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_ORIGIN_REPO_ID_PROPERTY:\n\t\tseafile_repo_set_origin_repo_id (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_ORIGIN_REPO_NAME_PROPERTY:\n\t\tseafile_repo_set_origin_repo_name (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_ORIGIN_PATH_PROPERTY:\n\t\tseafile_repo_set_origin_path (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_IS_ORIGINAL_OWNER_PROPERTY:\n\t\tseafile_repo_set_is_original_owner (self, g_value_get_boolean (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_VIRTUAL_PERM_PROPERTY:\n\t\tseafile_repo_set_virtual_perm (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_STORE_ID_PROPERTY:\n\t\tseafile_repo_set_store_id (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_IS_CORRUPTED_PROPERTY:\n\t\tseafile_repo_set_is_corrupted (self, g_value_get_boolean (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_REPAIRED_PROPERTY:\n\t\tseafile_repo_set_repaired (self, g_value_get_boolean (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_SHARE_TYPE_PROPERTY:\n\t\tseafile_repo_set_share_type (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_PERMISSION_PROPERTY:\n\t\tseafile_repo_set_permission (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_USER_PROPERTY:\n\t\tseafile_repo_set_user (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_GROUP_ID_PROPERTY:\n\t\tseafile_repo_set_group_id (self, g_value_get_int (value));\n\t\tbreak;\n\t\tcase SEAFILE_REPO_IS_SHARED_PROPERTY:\n\t\tseafile_repo_set_is_shared (self, g_value_get_boolean (value));\n\t\tbreak;\n\t\tdefault:\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);\n\t\tbreak;\n\t}\n}\n\nstatic inline gpointer\nseafile_sync_task_get_instance_private (SeafileSyncTask* self)\n{\n\treturn G_STRUCT_MEMBER_P (self, SeafileSyncTask_private_offset);\n}\n\nSeafileSyncTask*\nseafile_sync_task_construct (GType object_type)\n{\n\tSeafileSyncTask * self = NULL;\n\tself = (SeafileSyncTask*) g_object_new (object_type, NULL);\n\treturn self;\n}\n\nSeafileSyncTask*\nseafile_sync_task_new (void)\n{\n\treturn seafile_sync_task_construct (SEAFILE_TYPE_SYNC_TASK);\n}\n\ngboolean\nseafile_sync_task_get_force_upload (SeafileSyncTask* self)\n{\n\tgboolean result;\n\tg_return_val_if_fail (self != NULL, FALSE);\n\tresult = self->priv->_force_upload;\n\treturn result;\n}\n\nvoid\nseafile_sync_task_set_force_upload (SeafileSyncTask* self,\n                                    gboolean value)\n{\n\tgboolean old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_sync_task_get_force_upload (self);\n\tif (old_value != value) {\n\t\tself->priv->_force_upload = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_sync_task_properties[SEAFILE_SYNC_TASK_FORCE_UPLOAD_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_sync_task_get_repo_id (SeafileSyncTask* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_repo_id;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_sync_task_set_repo_id (SeafileSyncTask* self,\n                               const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_sync_task_get_repo_id (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_repo_id);\n\t\tself->priv->_repo_id = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_sync_task_properties[SEAFILE_SYNC_TASK_REPO_ID_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_sync_task_get_state (SeafileSyncTask* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_state;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_sync_task_set_state (SeafileSyncTask* self,\n                             const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_sync_task_get_state (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_state);\n\t\tself->priv->_state = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_sync_task_properties[SEAFILE_SYNC_TASK_STATE_PROPERTY]);\n\t}\n}\n\ngint\nseafile_sync_task_get_error (SeafileSyncTask* self)\n{\n\tgint result;\n\tg_return_val_if_fail (self != NULL, 0);\n\tresult = self->priv->_error;\n\treturn result;\n}\n\nvoid\nseafile_sync_task_set_error (SeafileSyncTask* self,\n                             gint value)\n{\n\tgint old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_sync_task_get_error (self);\n\tif (old_value != value) {\n\t\tself->priv->_error = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_sync_task_properties[SEAFILE_SYNC_TASK_ERROR_PROPERTY]);\n\t}\n}\n\nstatic void\nseafile_sync_task_class_init (SeafileSyncTaskClass * klass,\n                              gpointer klass_data)\n{\n\tseafile_sync_task_parent_class = g_type_class_peek_parent (klass);\n\tg_type_class_adjust_private_offset (klass, &SeafileSyncTask_private_offset);\n\tG_OBJECT_CLASS (klass)->get_property = _vala_seafile_sync_task_get_property;\n\tG_OBJECT_CLASS (klass)->set_property = _vala_seafile_sync_task_set_property;\n\tG_OBJECT_CLASS (klass)->finalize = seafile_sync_task_finalize;\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_SYNC_TASK_FORCE_UPLOAD_PROPERTY, seafile_sync_task_properties[SEAFILE_SYNC_TASK_FORCE_UPLOAD_PROPERTY] = g_param_spec_boolean (\"force-upload\", \"force-upload\", \"force-upload\", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_SYNC_TASK_REPO_ID_PROPERTY, seafile_sync_task_properties[SEAFILE_SYNC_TASK_REPO_ID_PROPERTY] = g_param_spec_string (\"repo-id\", \"repo-id\", \"repo-id\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_SYNC_TASK_STATE_PROPERTY, seafile_sync_task_properties[SEAFILE_SYNC_TASK_STATE_PROPERTY] = g_param_spec_string (\"state\", \"state\", \"state\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_SYNC_TASK_ERROR_PROPERTY, seafile_sync_task_properties[SEAFILE_SYNC_TASK_ERROR_PROPERTY] = g_param_spec_int (\"error\", \"error\", \"error\", G_MININT, G_MAXINT, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n}\n\nstatic void\nseafile_sync_task_instance_init (SeafileSyncTask * self,\n                                 gpointer klass)\n{\n\tself->priv = seafile_sync_task_get_instance_private (self);\n}\n\nstatic void\nseafile_sync_task_finalize (GObject * obj)\n{\n\tSeafileSyncTask * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (obj, SEAFILE_TYPE_SYNC_TASK, SeafileSyncTask);\n\t_g_free0 (self->priv->_repo_id);\n\t_g_free0 (self->priv->_state);\n\tG_OBJECT_CLASS (seafile_sync_task_parent_class)->finalize (obj);\n}\n\nstatic GType\nseafile_sync_task_get_type_once (void)\n{\n\tstatic const GTypeInfo g_define_type_info = { sizeof (SeafileSyncTaskClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) seafile_sync_task_class_init, (GClassFinalizeFunc) NULL, NULL, sizeof (SeafileSyncTask), 0, (GInstanceInitFunc) seafile_sync_task_instance_init, NULL };\n\tGType seafile_sync_task_type_id;\n\tseafile_sync_task_type_id = g_type_register_static (G_TYPE_OBJECT, \"SeafileSyncTask\", &g_define_type_info, 0);\n\tSeafileSyncTask_private_offset = g_type_add_instance_private (seafile_sync_task_type_id, sizeof (SeafileSyncTaskPrivate));\n\treturn seafile_sync_task_type_id;\n}\n\nGType\nseafile_sync_task_get_type (void)\n{\n\tstatic volatile gsize seafile_sync_task_type_id__once = 0;\n\tif (g_once_init_enter (&seafile_sync_task_type_id__once)) {\n\t\tGType seafile_sync_task_type_id;\n\t\tseafile_sync_task_type_id = seafile_sync_task_get_type_once ();\n\t\tg_once_init_leave (&seafile_sync_task_type_id__once, seafile_sync_task_type_id);\n\t}\n\treturn seafile_sync_task_type_id__once;\n}\n\nstatic void\n_vala_seafile_sync_task_get_property (GObject * object,\n                                      guint property_id,\n                                      GValue * value,\n                                      GParamSpec * pspec)\n{\n\tSeafileSyncTask * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (object, SEAFILE_TYPE_SYNC_TASK, SeafileSyncTask);\n\tswitch (property_id) {\n\t\tcase SEAFILE_SYNC_TASK_FORCE_UPLOAD_PROPERTY:\n\t\tg_value_set_boolean (value, seafile_sync_task_get_force_upload (self));\n\t\tbreak;\n\t\tcase SEAFILE_SYNC_TASK_REPO_ID_PROPERTY:\n\t\tg_value_set_string (value, seafile_sync_task_get_repo_id (self));\n\t\tbreak;\n\t\tcase SEAFILE_SYNC_TASK_STATE_PROPERTY:\n\t\tg_value_set_string (value, seafile_sync_task_get_state (self));\n\t\tbreak;\n\t\tcase SEAFILE_SYNC_TASK_ERROR_PROPERTY:\n\t\tg_value_set_int (value, seafile_sync_task_get_error (self));\n\t\tbreak;\n\t\tdefault:\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);\n\t\tbreak;\n\t}\n}\n\nstatic void\n_vala_seafile_sync_task_set_property (GObject * object,\n                                      guint property_id,\n                                      const GValue * value,\n                                      GParamSpec * pspec)\n{\n\tSeafileSyncTask * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (object, SEAFILE_TYPE_SYNC_TASK, SeafileSyncTask);\n\tswitch (property_id) {\n\t\tcase SEAFILE_SYNC_TASK_FORCE_UPLOAD_PROPERTY:\n\t\tseafile_sync_task_set_force_upload (self, g_value_get_boolean (value));\n\t\tbreak;\n\t\tcase SEAFILE_SYNC_TASK_REPO_ID_PROPERTY:\n\t\tseafile_sync_task_set_repo_id (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_SYNC_TASK_STATE_PROPERTY:\n\t\tseafile_sync_task_set_state (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_SYNC_TASK_ERROR_PROPERTY:\n\t\tseafile_sync_task_set_error (self, g_value_get_int (value));\n\t\tbreak;\n\t\tdefault:\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);\n\t\tbreak;\n\t}\n}\n\nstatic inline gpointer\nseafile_session_info_get_instance_private (SeafileSessionInfo* self)\n{\n\treturn G_STRUCT_MEMBER_P (self, SeafileSessionInfo_private_offset);\n}\n\nSeafileSessionInfo*\nseafile_session_info_construct (GType object_type)\n{\n\tSeafileSessionInfo * self = NULL;\n\tself = (SeafileSessionInfo*) g_object_new (object_type, NULL);\n\treturn self;\n}\n\nSeafileSessionInfo*\nseafile_session_info_new (void)\n{\n\treturn seafile_session_info_construct (SEAFILE_TYPE_SESSION_INFO);\n}\n\nconst gchar*\nseafile_session_info_get_datadir (SeafileSessionInfo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_datadir;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_session_info_set_datadir (SeafileSessionInfo* self,\n                                  const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_session_info_get_datadir (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_datadir);\n\t\tself->priv->_datadir = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_session_info_properties[SEAFILE_SESSION_INFO_DATADIR_PROPERTY]);\n\t}\n}\n\nstatic void\nseafile_session_info_class_init (SeafileSessionInfoClass * klass,\n                                 gpointer klass_data)\n{\n\tseafile_session_info_parent_class = g_type_class_peek_parent (klass);\n\tg_type_class_adjust_private_offset (klass, &SeafileSessionInfo_private_offset);\n\tG_OBJECT_CLASS (klass)->get_property = _vala_seafile_session_info_get_property;\n\tG_OBJECT_CLASS (klass)->set_property = _vala_seafile_session_info_set_property;\n\tG_OBJECT_CLASS (klass)->finalize = seafile_session_info_finalize;\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_SESSION_INFO_DATADIR_PROPERTY, seafile_session_info_properties[SEAFILE_SESSION_INFO_DATADIR_PROPERTY] = g_param_spec_string (\"datadir\", \"datadir\", \"datadir\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n}\n\nstatic void\nseafile_session_info_instance_init (SeafileSessionInfo * self,\n                                    gpointer klass)\n{\n\tself->priv = seafile_session_info_get_instance_private (self);\n}\n\nstatic void\nseafile_session_info_finalize (GObject * obj)\n{\n\tSeafileSessionInfo * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (obj, SEAFILE_TYPE_SESSION_INFO, SeafileSessionInfo);\n\t_g_free0 (self->priv->_datadir);\n\tG_OBJECT_CLASS (seafile_session_info_parent_class)->finalize (obj);\n}\n\nstatic GType\nseafile_session_info_get_type_once (void)\n{\n\tstatic const GTypeInfo g_define_type_info = { sizeof (SeafileSessionInfoClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) seafile_session_info_class_init, (GClassFinalizeFunc) NULL, NULL, sizeof (SeafileSessionInfo), 0, (GInstanceInitFunc) seafile_session_info_instance_init, NULL };\n\tGType seafile_session_info_type_id;\n\tseafile_session_info_type_id = g_type_register_static (G_TYPE_OBJECT, \"SeafileSessionInfo\", &g_define_type_info, 0);\n\tSeafileSessionInfo_private_offset = g_type_add_instance_private (seafile_session_info_type_id, sizeof (SeafileSessionInfoPrivate));\n\treturn seafile_session_info_type_id;\n}\n\nGType\nseafile_session_info_get_type (void)\n{\n\tstatic volatile gsize seafile_session_info_type_id__once = 0;\n\tif (g_once_init_enter (&seafile_session_info_type_id__once)) {\n\t\tGType seafile_session_info_type_id;\n\t\tseafile_session_info_type_id = seafile_session_info_get_type_once ();\n\t\tg_once_init_leave (&seafile_session_info_type_id__once, seafile_session_info_type_id);\n\t}\n\treturn seafile_session_info_type_id__once;\n}\n\nstatic void\n_vala_seafile_session_info_get_property (GObject * object,\n                                         guint property_id,\n                                         GValue * value,\n                                         GParamSpec * pspec)\n{\n\tSeafileSessionInfo * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (object, SEAFILE_TYPE_SESSION_INFO, SeafileSessionInfo);\n\tswitch (property_id) {\n\t\tcase SEAFILE_SESSION_INFO_DATADIR_PROPERTY:\n\t\tg_value_set_string (value, seafile_session_info_get_datadir (self));\n\t\tbreak;\n\t\tdefault:\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);\n\t\tbreak;\n\t}\n}\n\nstatic void\n_vala_seafile_session_info_set_property (GObject * object,\n                                         guint property_id,\n                                         const GValue * value,\n                                         GParamSpec * pspec)\n{\n\tSeafileSessionInfo * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (object, SEAFILE_TYPE_SESSION_INFO, SeafileSessionInfo);\n\tswitch (property_id) {\n\t\tcase SEAFILE_SESSION_INFO_DATADIR_PROPERTY:\n\t\tseafile_session_info_set_datadir (self, g_value_get_string (value));\n\t\tbreak;\n\t\tdefault:\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);\n\t\tbreak;\n\t}\n}\n\nstatic inline gpointer\nseafile_diff_entry_get_instance_private (SeafileDiffEntry* self)\n{\n\treturn G_STRUCT_MEMBER_P (self, SeafileDiffEntry_private_offset);\n}\n\nSeafileDiffEntry*\nseafile_diff_entry_construct (GType object_type)\n{\n\tSeafileDiffEntry * self = NULL;\n\tself = (SeafileDiffEntry*) g_object_new (object_type, NULL);\n\treturn self;\n}\n\nSeafileDiffEntry*\nseafile_diff_entry_new (void)\n{\n\treturn seafile_diff_entry_construct (SEAFILE_TYPE_DIFF_ENTRY);\n}\n\nconst gchar*\nseafile_diff_entry_get_status (SeafileDiffEntry* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_status;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_diff_entry_set_status (SeafileDiffEntry* self,\n                               const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_diff_entry_get_status (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_status);\n\t\tself->priv->_status = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_diff_entry_properties[SEAFILE_DIFF_ENTRY_STATUS_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_diff_entry_get_name (SeafileDiffEntry* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_name;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_diff_entry_set_name (SeafileDiffEntry* self,\n                             const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_diff_entry_get_name (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_name);\n\t\tself->priv->_name = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_diff_entry_properties[SEAFILE_DIFF_ENTRY_NAME_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_diff_entry_get_new_name (SeafileDiffEntry* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_new_name;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_diff_entry_set_new_name (SeafileDiffEntry* self,\n                                 const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_diff_entry_get_new_name (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_new_name);\n\t\tself->priv->_new_name = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_diff_entry_properties[SEAFILE_DIFF_ENTRY_NEW_NAME_PROPERTY]);\n\t}\n}\n\nstatic void\nseafile_diff_entry_class_init (SeafileDiffEntryClass * klass,\n                               gpointer klass_data)\n{\n\tseafile_diff_entry_parent_class = g_type_class_peek_parent (klass);\n\tg_type_class_adjust_private_offset (klass, &SeafileDiffEntry_private_offset);\n\tG_OBJECT_CLASS (klass)->get_property = _vala_seafile_diff_entry_get_property;\n\tG_OBJECT_CLASS (klass)->set_property = _vala_seafile_diff_entry_set_property;\n\tG_OBJECT_CLASS (klass)->finalize = seafile_diff_entry_finalize;\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_DIFF_ENTRY_STATUS_PROPERTY, seafile_diff_entry_properties[SEAFILE_DIFF_ENTRY_STATUS_PROPERTY] = g_param_spec_string (\"status\", \"status\", \"status\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_DIFF_ENTRY_NAME_PROPERTY, seafile_diff_entry_properties[SEAFILE_DIFF_ENTRY_NAME_PROPERTY] = g_param_spec_string (\"name\", \"name\", \"name\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_DIFF_ENTRY_NEW_NAME_PROPERTY, seafile_diff_entry_properties[SEAFILE_DIFF_ENTRY_NEW_NAME_PROPERTY] = g_param_spec_string (\"new-name\", \"new-name\", \"new-name\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n}\n\nstatic void\nseafile_diff_entry_instance_init (SeafileDiffEntry * self,\n                                  gpointer klass)\n{\n\tself->priv = seafile_diff_entry_get_instance_private (self);\n}\n\nstatic void\nseafile_diff_entry_finalize (GObject * obj)\n{\n\tSeafileDiffEntry * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (obj, SEAFILE_TYPE_DIFF_ENTRY, SeafileDiffEntry);\n\t_g_free0 (self->priv->_status);\n\t_g_free0 (self->priv->_name);\n\t_g_free0 (self->priv->_new_name);\n\tG_OBJECT_CLASS (seafile_diff_entry_parent_class)->finalize (obj);\n}\n\nstatic GType\nseafile_diff_entry_get_type_once (void)\n{\n\tstatic const GTypeInfo g_define_type_info = { sizeof (SeafileDiffEntryClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) seafile_diff_entry_class_init, (GClassFinalizeFunc) NULL, NULL, sizeof (SeafileDiffEntry), 0, (GInstanceInitFunc) seafile_diff_entry_instance_init, NULL };\n\tGType seafile_diff_entry_type_id;\n\tseafile_diff_entry_type_id = g_type_register_static (G_TYPE_OBJECT, \"SeafileDiffEntry\", &g_define_type_info, 0);\n\tSeafileDiffEntry_private_offset = g_type_add_instance_private (seafile_diff_entry_type_id, sizeof (SeafileDiffEntryPrivate));\n\treturn seafile_diff_entry_type_id;\n}\n\nGType\nseafile_diff_entry_get_type (void)\n{\n\tstatic volatile gsize seafile_diff_entry_type_id__once = 0;\n\tif (g_once_init_enter (&seafile_diff_entry_type_id__once)) {\n\t\tGType seafile_diff_entry_type_id;\n\t\tseafile_diff_entry_type_id = seafile_diff_entry_get_type_once ();\n\t\tg_once_init_leave (&seafile_diff_entry_type_id__once, seafile_diff_entry_type_id);\n\t}\n\treturn seafile_diff_entry_type_id__once;\n}\n\nstatic void\n_vala_seafile_diff_entry_get_property (GObject * object,\n                                       guint property_id,\n                                       GValue * value,\n                                       GParamSpec * pspec)\n{\n\tSeafileDiffEntry * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (object, SEAFILE_TYPE_DIFF_ENTRY, SeafileDiffEntry);\n\tswitch (property_id) {\n\t\tcase SEAFILE_DIFF_ENTRY_STATUS_PROPERTY:\n\t\tg_value_set_string (value, seafile_diff_entry_get_status (self));\n\t\tbreak;\n\t\tcase SEAFILE_DIFF_ENTRY_NAME_PROPERTY:\n\t\tg_value_set_string (value, seafile_diff_entry_get_name (self));\n\t\tbreak;\n\t\tcase SEAFILE_DIFF_ENTRY_NEW_NAME_PROPERTY:\n\t\tg_value_set_string (value, seafile_diff_entry_get_new_name (self));\n\t\tbreak;\n\t\tdefault:\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);\n\t\tbreak;\n\t}\n}\n\nstatic void\n_vala_seafile_diff_entry_set_property (GObject * object,\n                                       guint property_id,\n                                       const GValue * value,\n                                       GParamSpec * pspec)\n{\n\tSeafileDiffEntry * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (object, SEAFILE_TYPE_DIFF_ENTRY, SeafileDiffEntry);\n\tswitch (property_id) {\n\t\tcase SEAFILE_DIFF_ENTRY_STATUS_PROPERTY:\n\t\tseafile_diff_entry_set_status (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_DIFF_ENTRY_NAME_PROPERTY:\n\t\tseafile_diff_entry_set_name (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_DIFF_ENTRY_NEW_NAME_PROPERTY:\n\t\tseafile_diff_entry_set_new_name (self, g_value_get_string (value));\n\t\tbreak;\n\t\tdefault:\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);\n\t\tbreak;\n\t}\n}\n\nstatic inline gpointer\nseafile_encryption_info_get_instance_private (SeafileEncryptionInfo* self)\n{\n\treturn G_STRUCT_MEMBER_P (self, SeafileEncryptionInfo_private_offset);\n}\n\nSeafileEncryptionInfo*\nseafile_encryption_info_construct (GType object_type)\n{\n\tSeafileEncryptionInfo * self = NULL;\n\tself = (SeafileEncryptionInfo*) g_object_new (object_type, NULL);\n\treturn self;\n}\n\nSeafileEncryptionInfo*\nseafile_encryption_info_new (void)\n{\n\treturn seafile_encryption_info_construct (SEAFILE_TYPE_ENCRYPTION_INFO);\n}\n\nconst gchar*\nseafile_encryption_info_get_repo_id (SeafileEncryptionInfo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_repo_id;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_encryption_info_set_repo_id (SeafileEncryptionInfo* self,\n                                     const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_encryption_info_get_repo_id (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_repo_id);\n\t\tself->priv->_repo_id = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_encryption_info_properties[SEAFILE_ENCRYPTION_INFO_REPO_ID_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_encryption_info_get_passwd (SeafileEncryptionInfo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_passwd;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_encryption_info_set_passwd (SeafileEncryptionInfo* self,\n                                    const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_encryption_info_get_passwd (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_passwd);\n\t\tself->priv->_passwd = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_encryption_info_properties[SEAFILE_ENCRYPTION_INFO_PASSWD_PROPERTY]);\n\t}\n}\n\ngint\nseafile_encryption_info_get_enc_version (SeafileEncryptionInfo* self)\n{\n\tgint result;\n\tg_return_val_if_fail (self != NULL, 0);\n\tresult = self->priv->_enc_version;\n\treturn result;\n}\n\nvoid\nseafile_encryption_info_set_enc_version (SeafileEncryptionInfo* self,\n                                         gint value)\n{\n\tgint old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_encryption_info_get_enc_version (self);\n\tif (old_value != value) {\n\t\tself->priv->_enc_version = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_encryption_info_properties[SEAFILE_ENCRYPTION_INFO_ENC_VERSION_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_encryption_info_get_magic (SeafileEncryptionInfo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_magic;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_encryption_info_set_magic (SeafileEncryptionInfo* self,\n                                   const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_encryption_info_get_magic (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_magic);\n\t\tself->priv->_magic = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_encryption_info_properties[SEAFILE_ENCRYPTION_INFO_MAGIC_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_encryption_info_get_pwd_hash (SeafileEncryptionInfo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_pwd_hash;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_encryption_info_set_pwd_hash (SeafileEncryptionInfo* self,\n                                      const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_encryption_info_get_pwd_hash (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_pwd_hash);\n\t\tself->priv->_pwd_hash = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_encryption_info_properties[SEAFILE_ENCRYPTION_INFO_PWD_HASH_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_encryption_info_get_random_key (SeafileEncryptionInfo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_random_key;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_encryption_info_set_random_key (SeafileEncryptionInfo* self,\n                                        const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_encryption_info_get_random_key (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_random_key);\n\t\tself->priv->_random_key = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_encryption_info_properties[SEAFILE_ENCRYPTION_INFO_RANDOM_KEY_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_encryption_info_get_salt (SeafileEncryptionInfo* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_salt;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_encryption_info_set_salt (SeafileEncryptionInfo* self,\n                                  const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_encryption_info_get_salt (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_salt);\n\t\tself->priv->_salt = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_encryption_info_properties[SEAFILE_ENCRYPTION_INFO_SALT_PROPERTY]);\n\t}\n}\n\nstatic void\nseafile_encryption_info_class_init (SeafileEncryptionInfoClass * klass,\n                                    gpointer klass_data)\n{\n\tseafile_encryption_info_parent_class = g_type_class_peek_parent (klass);\n\tg_type_class_adjust_private_offset (klass, &SeafileEncryptionInfo_private_offset);\n\tG_OBJECT_CLASS (klass)->get_property = _vala_seafile_encryption_info_get_property;\n\tG_OBJECT_CLASS (klass)->set_property = _vala_seafile_encryption_info_set_property;\n\tG_OBJECT_CLASS (klass)->finalize = seafile_encryption_info_finalize;\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_ENCRYPTION_INFO_REPO_ID_PROPERTY, seafile_encryption_info_properties[SEAFILE_ENCRYPTION_INFO_REPO_ID_PROPERTY] = g_param_spec_string (\"repo-id\", \"repo-id\", \"repo-id\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_ENCRYPTION_INFO_PASSWD_PROPERTY, seafile_encryption_info_properties[SEAFILE_ENCRYPTION_INFO_PASSWD_PROPERTY] = g_param_spec_string (\"passwd\", \"passwd\", \"passwd\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_ENCRYPTION_INFO_ENC_VERSION_PROPERTY, seafile_encryption_info_properties[SEAFILE_ENCRYPTION_INFO_ENC_VERSION_PROPERTY] = g_param_spec_int (\"enc-version\", \"enc-version\", \"enc-version\", G_MININT, G_MAXINT, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_ENCRYPTION_INFO_MAGIC_PROPERTY, seafile_encryption_info_properties[SEAFILE_ENCRYPTION_INFO_MAGIC_PROPERTY] = g_param_spec_string (\"magic\", \"magic\", \"magic\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_ENCRYPTION_INFO_PWD_HASH_PROPERTY, seafile_encryption_info_properties[SEAFILE_ENCRYPTION_INFO_PWD_HASH_PROPERTY] = g_param_spec_string (\"pwd-hash\", \"pwd-hash\", \"pwd-hash\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_ENCRYPTION_INFO_RANDOM_KEY_PROPERTY, seafile_encryption_info_properties[SEAFILE_ENCRYPTION_INFO_RANDOM_KEY_PROPERTY] = g_param_spec_string (\"random-key\", \"random-key\", \"random-key\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_ENCRYPTION_INFO_SALT_PROPERTY, seafile_encryption_info_properties[SEAFILE_ENCRYPTION_INFO_SALT_PROPERTY] = g_param_spec_string (\"salt\", \"salt\", \"salt\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n}\n\nstatic void\nseafile_encryption_info_instance_init (SeafileEncryptionInfo * self,\n                                       gpointer klass)\n{\n\tself->priv = seafile_encryption_info_get_instance_private (self);\n}\n\nstatic void\nseafile_encryption_info_finalize (GObject * obj)\n{\n\tSeafileEncryptionInfo * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (obj, SEAFILE_TYPE_ENCRYPTION_INFO, SeafileEncryptionInfo);\n\t_g_free0 (self->priv->_repo_id);\n\t_g_free0 (self->priv->_passwd);\n\t_g_free0 (self->priv->_magic);\n\t_g_free0 (self->priv->_pwd_hash);\n\t_g_free0 (self->priv->_random_key);\n\t_g_free0 (self->priv->_salt);\n\tG_OBJECT_CLASS (seafile_encryption_info_parent_class)->finalize (obj);\n}\n\nstatic GType\nseafile_encryption_info_get_type_once (void)\n{\n\tstatic const GTypeInfo g_define_type_info = { sizeof (SeafileEncryptionInfoClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) seafile_encryption_info_class_init, (GClassFinalizeFunc) NULL, NULL, sizeof (SeafileEncryptionInfo), 0, (GInstanceInitFunc) seafile_encryption_info_instance_init, NULL };\n\tGType seafile_encryption_info_type_id;\n\tseafile_encryption_info_type_id = g_type_register_static (G_TYPE_OBJECT, \"SeafileEncryptionInfo\", &g_define_type_info, 0);\n\tSeafileEncryptionInfo_private_offset = g_type_add_instance_private (seafile_encryption_info_type_id, sizeof (SeafileEncryptionInfoPrivate));\n\treturn seafile_encryption_info_type_id;\n}\n\nGType\nseafile_encryption_info_get_type (void)\n{\n\tstatic volatile gsize seafile_encryption_info_type_id__once = 0;\n\tif (g_once_init_enter (&seafile_encryption_info_type_id__once)) {\n\t\tGType seafile_encryption_info_type_id;\n\t\tseafile_encryption_info_type_id = seafile_encryption_info_get_type_once ();\n\t\tg_once_init_leave (&seafile_encryption_info_type_id__once, seafile_encryption_info_type_id);\n\t}\n\treturn seafile_encryption_info_type_id__once;\n}\n\nstatic void\n_vala_seafile_encryption_info_get_property (GObject * object,\n                                            guint property_id,\n                                            GValue * value,\n                                            GParamSpec * pspec)\n{\n\tSeafileEncryptionInfo * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (object, SEAFILE_TYPE_ENCRYPTION_INFO, SeafileEncryptionInfo);\n\tswitch (property_id) {\n\t\tcase SEAFILE_ENCRYPTION_INFO_REPO_ID_PROPERTY:\n\t\tg_value_set_string (value, seafile_encryption_info_get_repo_id (self));\n\t\tbreak;\n\t\tcase SEAFILE_ENCRYPTION_INFO_PASSWD_PROPERTY:\n\t\tg_value_set_string (value, seafile_encryption_info_get_passwd (self));\n\t\tbreak;\n\t\tcase SEAFILE_ENCRYPTION_INFO_ENC_VERSION_PROPERTY:\n\t\tg_value_set_int (value, seafile_encryption_info_get_enc_version (self));\n\t\tbreak;\n\t\tcase SEAFILE_ENCRYPTION_INFO_MAGIC_PROPERTY:\n\t\tg_value_set_string (value, seafile_encryption_info_get_magic (self));\n\t\tbreak;\n\t\tcase SEAFILE_ENCRYPTION_INFO_PWD_HASH_PROPERTY:\n\t\tg_value_set_string (value, seafile_encryption_info_get_pwd_hash (self));\n\t\tbreak;\n\t\tcase SEAFILE_ENCRYPTION_INFO_RANDOM_KEY_PROPERTY:\n\t\tg_value_set_string (value, seafile_encryption_info_get_random_key (self));\n\t\tbreak;\n\t\tcase SEAFILE_ENCRYPTION_INFO_SALT_PROPERTY:\n\t\tg_value_set_string (value, seafile_encryption_info_get_salt (self));\n\t\tbreak;\n\t\tdefault:\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);\n\t\tbreak;\n\t}\n}\n\nstatic void\n_vala_seafile_encryption_info_set_property (GObject * object,\n                                            guint property_id,\n                                            const GValue * value,\n                                            GParamSpec * pspec)\n{\n\tSeafileEncryptionInfo * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (object, SEAFILE_TYPE_ENCRYPTION_INFO, SeafileEncryptionInfo);\n\tswitch (property_id) {\n\t\tcase SEAFILE_ENCRYPTION_INFO_REPO_ID_PROPERTY:\n\t\tseafile_encryption_info_set_repo_id (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_ENCRYPTION_INFO_PASSWD_PROPERTY:\n\t\tseafile_encryption_info_set_passwd (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_ENCRYPTION_INFO_ENC_VERSION_PROPERTY:\n\t\tseafile_encryption_info_set_enc_version (self, g_value_get_int (value));\n\t\tbreak;\n\t\tcase SEAFILE_ENCRYPTION_INFO_MAGIC_PROPERTY:\n\t\tseafile_encryption_info_set_magic (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_ENCRYPTION_INFO_PWD_HASH_PROPERTY:\n\t\tseafile_encryption_info_set_pwd_hash (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_ENCRYPTION_INFO_RANDOM_KEY_PROPERTY:\n\t\tseafile_encryption_info_set_random_key (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_ENCRYPTION_INFO_SALT_PROPERTY:\n\t\tseafile_encryption_info_set_salt (self, g_value_get_string (value));\n\t\tbreak;\n\t\tdefault:\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);\n\t\tbreak;\n\t}\n}\n\nstatic inline gpointer\nseafile_file_sync_error_get_instance_private (SeafileFileSyncError* self)\n{\n\treturn G_STRUCT_MEMBER_P (self, SeafileFileSyncError_private_offset);\n}\n\nSeafileFileSyncError*\nseafile_file_sync_error_construct (GType object_type)\n{\n\tSeafileFileSyncError * self = NULL;\n\tself = (SeafileFileSyncError*) g_object_new (object_type, NULL);\n\treturn self;\n}\n\nSeafileFileSyncError*\nseafile_file_sync_error_new (void)\n{\n\treturn seafile_file_sync_error_construct (SEAFILE_TYPE_FILE_SYNC_ERROR);\n}\n\ngint\nseafile_file_sync_error_get_id (SeafileFileSyncError* self)\n{\n\tgint result;\n\tg_return_val_if_fail (self != NULL, 0);\n\tresult = self->priv->_id;\n\treturn result;\n}\n\nvoid\nseafile_file_sync_error_set_id (SeafileFileSyncError* self,\n                                gint value)\n{\n\tgint old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_file_sync_error_get_id (self);\n\tif (old_value != value) {\n\t\tself->priv->_id = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_file_sync_error_properties[SEAFILE_FILE_SYNC_ERROR_ID_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_file_sync_error_get_repo_id (SeafileFileSyncError* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_repo_id;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_file_sync_error_set_repo_id (SeafileFileSyncError* self,\n                                     const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_file_sync_error_get_repo_id (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_repo_id);\n\t\tself->priv->_repo_id = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_file_sync_error_properties[SEAFILE_FILE_SYNC_ERROR_REPO_ID_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_file_sync_error_get_repo_name (SeafileFileSyncError* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_repo_name;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_file_sync_error_set_repo_name (SeafileFileSyncError* self,\n                                       const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_file_sync_error_get_repo_name (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_repo_name);\n\t\tself->priv->_repo_name = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_file_sync_error_properties[SEAFILE_FILE_SYNC_ERROR_REPO_NAME_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_file_sync_error_get_path (SeafileFileSyncError* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_path;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_file_sync_error_set_path (SeafileFileSyncError* self,\n                                  const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_file_sync_error_get_path (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_path);\n\t\tself->priv->_path = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_file_sync_error_properties[SEAFILE_FILE_SYNC_ERROR_PATH_PROPERTY]);\n\t}\n}\n\ngint\nseafile_file_sync_error_get_err_id (SeafileFileSyncError* self)\n{\n\tgint result;\n\tg_return_val_if_fail (self != NULL, 0);\n\tresult = self->priv->_err_id;\n\treturn result;\n}\n\nvoid\nseafile_file_sync_error_set_err_id (SeafileFileSyncError* self,\n                                    gint value)\n{\n\tgint old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_file_sync_error_get_err_id (self);\n\tif (old_value != value) {\n\t\tself->priv->_err_id = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_file_sync_error_properties[SEAFILE_FILE_SYNC_ERROR_ERR_ID_PROPERTY]);\n\t}\n}\n\ngint64\nseafile_file_sync_error_get_timestamp (SeafileFileSyncError* self)\n{\n\tgint64 result;\n\tg_return_val_if_fail (self != NULL, 0LL);\n\tresult = self->priv->_timestamp;\n\treturn result;\n}\n\nvoid\nseafile_file_sync_error_set_timestamp (SeafileFileSyncError* self,\n                                       gint64 value)\n{\n\tgint64 old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_file_sync_error_get_timestamp (self);\n\tif (old_value != value) {\n\t\tself->priv->_timestamp = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_file_sync_error_properties[SEAFILE_FILE_SYNC_ERROR_TIMESTAMP_PROPERTY]);\n\t}\n}\n\nstatic void\nseafile_file_sync_error_class_init (SeafileFileSyncErrorClass * klass,\n                                    gpointer klass_data)\n{\n\tseafile_file_sync_error_parent_class = g_type_class_peek_parent (klass);\n\tg_type_class_adjust_private_offset (klass, &SeafileFileSyncError_private_offset);\n\tG_OBJECT_CLASS (klass)->get_property = _vala_seafile_file_sync_error_get_property;\n\tG_OBJECT_CLASS (klass)->set_property = _vala_seafile_file_sync_error_set_property;\n\tG_OBJECT_CLASS (klass)->finalize = seafile_file_sync_error_finalize;\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_FILE_SYNC_ERROR_ID_PROPERTY, seafile_file_sync_error_properties[SEAFILE_FILE_SYNC_ERROR_ID_PROPERTY] = g_param_spec_int (\"id\", \"id\", \"id\", G_MININT, G_MAXINT, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_FILE_SYNC_ERROR_REPO_ID_PROPERTY, seafile_file_sync_error_properties[SEAFILE_FILE_SYNC_ERROR_REPO_ID_PROPERTY] = g_param_spec_string (\"repo-id\", \"repo-id\", \"repo-id\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_FILE_SYNC_ERROR_REPO_NAME_PROPERTY, seafile_file_sync_error_properties[SEAFILE_FILE_SYNC_ERROR_REPO_NAME_PROPERTY] = g_param_spec_string (\"repo-name\", \"repo-name\", \"repo-name\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_FILE_SYNC_ERROR_PATH_PROPERTY, seafile_file_sync_error_properties[SEAFILE_FILE_SYNC_ERROR_PATH_PROPERTY] = g_param_spec_string (\"path\", \"path\", \"path\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_FILE_SYNC_ERROR_ERR_ID_PROPERTY, seafile_file_sync_error_properties[SEAFILE_FILE_SYNC_ERROR_ERR_ID_PROPERTY] = g_param_spec_int (\"err-id\", \"err-id\", \"err-id\", G_MININT, G_MAXINT, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_FILE_SYNC_ERROR_TIMESTAMP_PROPERTY, seafile_file_sync_error_properties[SEAFILE_FILE_SYNC_ERROR_TIMESTAMP_PROPERTY] = g_param_spec_int64 (\"timestamp\", \"timestamp\", \"timestamp\", G_MININT64, G_MAXINT64, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n}\n\nstatic void\nseafile_file_sync_error_instance_init (SeafileFileSyncError * self,\n                                       gpointer klass)\n{\n\tself->priv = seafile_file_sync_error_get_instance_private (self);\n}\n\nstatic void\nseafile_file_sync_error_finalize (GObject * obj)\n{\n\tSeafileFileSyncError * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (obj, SEAFILE_TYPE_FILE_SYNC_ERROR, SeafileFileSyncError);\n\t_g_free0 (self->priv->_repo_id);\n\t_g_free0 (self->priv->_repo_name);\n\t_g_free0 (self->priv->_path);\n\tG_OBJECT_CLASS (seafile_file_sync_error_parent_class)->finalize (obj);\n}\n\nstatic GType\nseafile_file_sync_error_get_type_once (void)\n{\n\tstatic const GTypeInfo g_define_type_info = { sizeof (SeafileFileSyncErrorClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) seafile_file_sync_error_class_init, (GClassFinalizeFunc) NULL, NULL, sizeof (SeafileFileSyncError), 0, (GInstanceInitFunc) seafile_file_sync_error_instance_init, NULL };\n\tGType seafile_file_sync_error_type_id;\n\tseafile_file_sync_error_type_id = g_type_register_static (G_TYPE_OBJECT, \"SeafileFileSyncError\", &g_define_type_info, 0);\n\tSeafileFileSyncError_private_offset = g_type_add_instance_private (seafile_file_sync_error_type_id, sizeof (SeafileFileSyncErrorPrivate));\n\treturn seafile_file_sync_error_type_id;\n}\n\nGType\nseafile_file_sync_error_get_type (void)\n{\n\tstatic volatile gsize seafile_file_sync_error_type_id__once = 0;\n\tif (g_once_init_enter (&seafile_file_sync_error_type_id__once)) {\n\t\tGType seafile_file_sync_error_type_id;\n\t\tseafile_file_sync_error_type_id = seafile_file_sync_error_get_type_once ();\n\t\tg_once_init_leave (&seafile_file_sync_error_type_id__once, seafile_file_sync_error_type_id);\n\t}\n\treturn seafile_file_sync_error_type_id__once;\n}\n\nstatic void\n_vala_seafile_file_sync_error_get_property (GObject * object,\n                                            guint property_id,\n                                            GValue * value,\n                                            GParamSpec * pspec)\n{\n\tSeafileFileSyncError * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (object, SEAFILE_TYPE_FILE_SYNC_ERROR, SeafileFileSyncError);\n\tswitch (property_id) {\n\t\tcase SEAFILE_FILE_SYNC_ERROR_ID_PROPERTY:\n\t\tg_value_set_int (value, seafile_file_sync_error_get_id (self));\n\t\tbreak;\n\t\tcase SEAFILE_FILE_SYNC_ERROR_REPO_ID_PROPERTY:\n\t\tg_value_set_string (value, seafile_file_sync_error_get_repo_id (self));\n\t\tbreak;\n\t\tcase SEAFILE_FILE_SYNC_ERROR_REPO_NAME_PROPERTY:\n\t\tg_value_set_string (value, seafile_file_sync_error_get_repo_name (self));\n\t\tbreak;\n\t\tcase SEAFILE_FILE_SYNC_ERROR_PATH_PROPERTY:\n\t\tg_value_set_string (value, seafile_file_sync_error_get_path (self));\n\t\tbreak;\n\t\tcase SEAFILE_FILE_SYNC_ERROR_ERR_ID_PROPERTY:\n\t\tg_value_set_int (value, seafile_file_sync_error_get_err_id (self));\n\t\tbreak;\n\t\tcase SEAFILE_FILE_SYNC_ERROR_TIMESTAMP_PROPERTY:\n\t\tg_value_set_int64 (value, seafile_file_sync_error_get_timestamp (self));\n\t\tbreak;\n\t\tdefault:\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);\n\t\tbreak;\n\t}\n}\n\nstatic void\n_vala_seafile_file_sync_error_set_property (GObject * object,\n                                            guint property_id,\n                                            const GValue * value,\n                                            GParamSpec * pspec)\n{\n\tSeafileFileSyncError * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (object, SEAFILE_TYPE_FILE_SYNC_ERROR, SeafileFileSyncError);\n\tswitch (property_id) {\n\t\tcase SEAFILE_FILE_SYNC_ERROR_ID_PROPERTY:\n\t\tseafile_file_sync_error_set_id (self, g_value_get_int (value));\n\t\tbreak;\n\t\tcase SEAFILE_FILE_SYNC_ERROR_REPO_ID_PROPERTY:\n\t\tseafile_file_sync_error_set_repo_id (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_FILE_SYNC_ERROR_REPO_NAME_PROPERTY:\n\t\tseafile_file_sync_error_set_repo_name (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_FILE_SYNC_ERROR_PATH_PROPERTY:\n\t\tseafile_file_sync_error_set_path (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_FILE_SYNC_ERROR_ERR_ID_PROPERTY:\n\t\tseafile_file_sync_error_set_err_id (self, g_value_get_int (value));\n\t\tbreak;\n\t\tcase SEAFILE_FILE_SYNC_ERROR_TIMESTAMP_PROPERTY:\n\t\tseafile_file_sync_error_set_timestamp (self, g_value_get_int64 (value));\n\t\tbreak;\n\t\tdefault:\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);\n\t\tbreak;\n\t}\n}\n\n"
  },
  {
    "path": "lib/repo.vala",
    "content": "namespace Seafile {\n\npublic class Repo : Object {\n\n    // Section 1: Basic information\n    // Members in this section should be set for every Repo object\n\n    // _id is for fast access from c code. id is for\n    // vala to automatically generate a property. Note,\n    // if a Vala property is start with _, it is not\n    // translated into a GObject property.\n    public char _id[37];\n    public string id {\n        get { return (string)_id; }\n        set { Posix.memcpy(_id, value, 36); _id[36] = '\\0'; }\n    }\n\n    public string _name;\n    public string name {\n        get { return _name; }\n        set { _name = value; }\n    }\n\n    public string _desc;        // description\n    public string desc {\n        get { return _desc; }\n        set { _desc = value; }\n    }\n\n    // data format version\n    public int version { get; set; }\n\n    public int    last_modify { get; set; }\n    public int64  size { get; set; }\n    public int64  file_count { get; set; }\n    public string head_cmmt_id { get; set; }\n    public string root { get; set; }\n\n    // To be compatible with obsoleted SharedRepo object\n    public string repo_id { get; set; }\n    public string repo_name { get; set; }\n    public string repo_desc { get; set; }\n    public int last_modified { get; set; }\n\n    // Section 2: Encryption related\n    // Members in this section should be set for every Repo object\n\n    public bool encrypted { get; set; }\n    public string magic { get; set; }\n    public int enc_version { get; set; }\n    public string random_key { get; set; }\n    public string salt { get; set; }\n\n    // Section 3: Client only information\n    // Should be set for all client repo objects\n\n    public string _worktree;\n    public string worktree {\n        get { return _worktree; }\n        set { _worktree = value; }\n    }\n    public string _relay_id;\n    public string relay_id {\n        get { return _relay_id; }\n        set { _relay_id = value; }\n    }\n    public int  last_sync_time { get; set; }\n    public bool auto_sync { get; set; }\n    public bool worktree_invalid { get; set; }\n\n    // Section 4: Server only information\n    // Should be set for all server repo objects\n\n    // virutal repo related\n    public bool is_virtual { get; set; }\n    public string origin_repo_id { get; set; }\n    public string origin_repo_name { get; set; }\n    public string origin_path { get; set; }\n    public bool is_original_owner { get; set; }\n    public string virtual_perm { get; set; }\n\n    // Used to access fs objects\n    public string store_id { get; set; }\n\n    public bool is_corrupted { get; set; }\n    public bool repaired { get; set; }\n\n    // Section 5: Share information\n    // Only set in list_share_repos, get_group_repos and get_inner_pub_repos, etc\n\n    public string share_type { get; set; } // personal, group or public\n    public string permission { get; set; }\n    public string user { get; set; } // share from or share to\n    public int group_id { get; set; } // used when shared to group\n\n    // For list_owned_repo\n    public bool is_shared { get; set; }\n}\n\npublic class SyncTask : Object {\n\n    public bool force_upload { get; set; }\n    public string repo_id { get; set; }\n    public string state { get; set; }\n    public int error { get; set; }\n}\n\npublic class SessionInfo : Object {\n    public string datadir { get; set; }\n}\n\npublic class DiffEntry : Object {\n\n    public string status { get; set; }\n    public string name { get; set; }\n    public string new_name { get; set; }\n}\n\npublic class EncryptionInfo: Object {\n    public string repo_id { get; set; }\n    public string passwd { get; set; }\n    public int enc_version { get; set; }\n    public string magic { get; set; }\n    public string pwd_hash { get; set; }\n    public string random_key { get; set; }\n    public string salt { get; set; }\n}\n\npublic class FileSyncError: Object {\n    public int id { get; set; }\n    public string repo_id { get; set; }\n    public string repo_name { get; set; }\n    public string path { get; set; }\n    public int err_id { get; set; }\n    public int64 timestamp { get; set; }\n}\n\n} // namespace\n"
  },
  {
    "path": "lib/rpc_table.py",
    "content": "\"\"\"\nDefine RPC functions needed to generate\n\"\"\"\n\n# [ <ret-type>, [<arg_types>] ]\nfunc_table = [\n    [ \"int\", [] ],\n    [ \"int\", [\"int\"] ],\n    [ \"int\", [\"int\", \"int\"] ],\n    [ \"int\", [\"int\", \"string\"] ],\n    [ \"int\", [\"int\", \"string\", \"int\"] ],\n    [ \"int\", [\"int\", \"string\", \"string\"] ],\n    [ \"int\", [\"int\", \"string\", \"int\", \"int\"] ],    \n    [ \"int\", [\"int\", \"int\", \"string\", \"string\"] ],\n    [ \"int\", [\"string\"] ],\n    [ \"int\", [\"string\", \"int\"] ],\n    [ \"int\", [\"string\", \"int\", \"int\"] ],\n    [ \"int\", [\"string\", \"int\", \"string\"] ],\n    [ \"int\", [\"string\", \"int\", \"string\", \"string\"] ],\n    [ \"int\", [\"string\", \"int\", \"int\", \"string\", \"string\"] ],\n    [ \"int\", [\"string\", \"string\"] ],\n    [ \"int\", [\"string\", \"string\", \"string\"] ],\n    [ \"int\", [\"string\", \"string\", \"int\", \"int\"] ],\n    [ \"int\", [\"string\", \"string\", \"string\", \"int\"] ],\n    [ \"int\", [\"string\", \"string\", \"string\", \"int\", \"string\"] ],\n    [ \"int\", [\"string\", \"string\", \"string\", \"string\"] ],\n    [ \"int\", [\"string\", \"string\", \"string\", \"string\", \"string\"] ],\n    [ \"int\", [\"string\", \"string\", \"string\", \"string\", \"string\", \"string\"] ],\n    [ \"int\", [\"string\", \"string\", \"string\", \"int\", \"string\", \"string\"] ],\n    [ \"int\", [\"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\"] ],\n    [ \"int\", [\"string\", \"int64\"]],\n    [ \"int\", [\"int\", \"int64\"]],\n    [ \"int\", [\"int\", \"string\", \"int64\"]],\n    [ \"int64\", [] ],\n    [ \"int64\", [\"string\"] ],\n    [ \"int64\", [\"int\"]],\n    [ \"int64\", [\"int\", \"string\"]],\n    [ \"int64\", [\"string\", \"int\", \"string\"] ],\n    [ \"string\", [] ],\n    [ \"string\", [\"int\"] ],\n    [ \"string\", [\"int\", \"int\"] ],\n    [ \"string\", [\"int\", \"string\"] ],\n    [ \"string\", [\"int\", \"int\", \"string\"] ],\n    [ \"string\", [\"string\"] ],\n    [ \"string\", [\"string\", \"int\"] ],\n    [ \"string\", [\"string\", \"int\", \"int\"] ],\n    [ \"string\", [\"string\", \"string\"] ],\n    [ \"string\", [\"string\", \"string\", \"int\"] ],\n    [ \"string\", [\"string\", \"string\", \"int\", \"int\"] ],\n    [ \"string\", [\"string\", \"string\", \"string\"] ],\n    [ \"string\", [\"string\", \"string\", \"string\", \"string\"] ],\n    [ \"string\", [\"string\", \"string\", \"string\", \"string\", \"int\"] ],\n    [ \"string\", [\"string\", \"string\", \"string\", \"string\", \"string\"] ],\n    [ \"string\", [\"string\", \"string\", \"string\", \"string\", \"string\", \"int\"] ],\n    [ \"string\", [\"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int\"] ],\n    [ \"string\", [\"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int\", \"int\"] ],\n    [ \"string\", [\"string\", \"string\", \"string\", \"string\", \"string\", \"string\"] ],\n    [ \"string\", [\"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int64\"] ],\n    [ \"string\", [\"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int64\", \"int\"] ],\n    [ \"string\", [\"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\"] ],\n    [ \"string\", [\"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int64\"] ],\n    [ \"string\", [\"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\"] ],\n    [ \"string\", [\"string\", \"int\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int\", \"string\"] ],\n    [ \"string\", [\"string\", \"int\", \"string\", \"int\", \"int\"] ],\n    [ \"string\", [\"string\", \"int\", \"string\", \"string\", \"string\"] ],\n    [ \"objlist\", [] ],\n    [ \"objlist\", [\"int\"] ],\n    [ \"objlist\", [\"int\", \"int\"] ],\n    [ \"objlist\", [\"int\", \"string\"] ],\n    [ \"objlist\", [\"int\", \"int\", \"int\"] ],\n    [ \"objlist\", [\"string\"] ],        \n    [ \"objlist\", [\"string\", \"int\"] ],\n    [ \"objlist\", [\"string\", \"int\", \"int\"] ],\n    [ \"objlist\", [\"string\", \"int\", \"string\"] ],\n    [ \"objlist\", [\"string\", \"string\"] ],        \n    [ \"objlist\", [\"string\", \"string\", \"string\"] ],\n    [ \"objlist\", [\"string\", \"string\", \"int\"] ],\n    [ \"objlist\", [\"string\", \"string\", \"string\", \"int\"] ],\n    [ \"objlist\", [\"string\", \"string\", \"int\", \"int\"] ],\n    [ \"objlist\", [\"string\", \"string\", \"int\", \"int\", \"int\"] ],\n    [ \"objlist\", [\"int\", \"string\", \"string\", \"int\", \"int\"] ],\n    [ \"objlist\", [\"string\", \"int\", \"string\", \"string\", \"string\"] ],\n    [ \"objlist\", [\"string\", \"int\", \"string\", \"int\", \"int\"] ],\n    [ \"objlist\", [\"string\", \"int\", \"string\", \"string\", \"int\"] ],\n    [ \"objlist\", [\"string\", \"string\", \"string\", \"string\", \"int\", \"int\"] ],\n    [ \"object\", [\"int\"] ],\n    [ \"object\", [\"string\"] ],\n    [ \"object\", [\"string\", \"string\"] ],\n    [ \"object\", [\"string\", \"string\", \"string\"] ],\n    [ \"object\", [\"string\", \"int\", \"string\"] ],\n    [ \"object\", [\"int\", \"string\", \"string\"] ],\n    [ \"object\", [\"int\", \"string\", \"string\", \"string\", \"string\"] ],\n    [ \"object\", [\"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int\", \"int\"] ],\n    [ \"object\", [\"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int\", \"string\", \"int\", \"int\"] ],\n    [ \"json\", [] ],\n]\n"
  },
  {
    "path": "lib/seafile-object.h",
    "content": "/* seafile-object.h generated by valac 0.56.7, the Vala compiler, do not modify */\n\n#ifndef __SEAFILE_OBJECT_H__\n#define __SEAFILE_OBJECT_H__\n\n#include <glib-object.h>\n#include <glib.h>\n\nG_BEGIN_DECLS\n\n#if !defined(VALA_EXTERN)\n#if defined(_MSC_VER)\n#define VALA_EXTERN __declspec(dllexport) extern\n#elif __GNUC__ >= 4\n#define VALA_EXTERN __attribute__((visibility(\"default\"))) extern\n#else\n#define VALA_EXTERN extern\n#endif\n#endif\n\n#define SEAFILE_TYPE_REPO (seafile_repo_get_type ())\n#define SEAFILE_REPO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_REPO, SeafileRepo))\n#define SEAFILE_REPO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_REPO, SeafileRepoClass))\n#define SEAFILE_IS_REPO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_REPO))\n#define SEAFILE_IS_REPO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_REPO))\n#define SEAFILE_REPO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_REPO, SeafileRepoClass))\n\ntypedef struct _SeafileRepo SeafileRepo;\ntypedef struct _SeafileRepoClass SeafileRepoClass;\ntypedef struct _SeafileRepoPrivate SeafileRepoPrivate;\n\n#define SEAFILE_TYPE_SYNC_TASK (seafile_sync_task_get_type ())\n#define SEAFILE_SYNC_TASK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_SYNC_TASK, SeafileSyncTask))\n#define SEAFILE_SYNC_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_SYNC_TASK, SeafileSyncTaskClass))\n#define SEAFILE_IS_SYNC_TASK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_SYNC_TASK))\n#define SEAFILE_IS_SYNC_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_SYNC_TASK))\n#define SEAFILE_SYNC_TASK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_SYNC_TASK, SeafileSyncTaskClass))\n\ntypedef struct _SeafileSyncTask SeafileSyncTask;\ntypedef struct _SeafileSyncTaskClass SeafileSyncTaskClass;\ntypedef struct _SeafileSyncTaskPrivate SeafileSyncTaskPrivate;\n\n#define SEAFILE_TYPE_SESSION_INFO (seafile_session_info_get_type ())\n#define SEAFILE_SESSION_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_SESSION_INFO, SeafileSessionInfo))\n#define SEAFILE_SESSION_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_SESSION_INFO, SeafileSessionInfoClass))\n#define SEAFILE_IS_SESSION_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_SESSION_INFO))\n#define SEAFILE_IS_SESSION_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_SESSION_INFO))\n#define SEAFILE_SESSION_INFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_SESSION_INFO, SeafileSessionInfoClass))\n\ntypedef struct _SeafileSessionInfo SeafileSessionInfo;\ntypedef struct _SeafileSessionInfoClass SeafileSessionInfoClass;\ntypedef struct _SeafileSessionInfoPrivate SeafileSessionInfoPrivate;\n\n#define SEAFILE_TYPE_DIFF_ENTRY (seafile_diff_entry_get_type ())\n#define SEAFILE_DIFF_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_DIFF_ENTRY, SeafileDiffEntry))\n#define SEAFILE_DIFF_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_DIFF_ENTRY, SeafileDiffEntryClass))\n#define SEAFILE_IS_DIFF_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_DIFF_ENTRY))\n#define SEAFILE_IS_DIFF_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_DIFF_ENTRY))\n#define SEAFILE_DIFF_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_DIFF_ENTRY, SeafileDiffEntryClass))\n\ntypedef struct _SeafileDiffEntry SeafileDiffEntry;\ntypedef struct _SeafileDiffEntryClass SeafileDiffEntryClass;\ntypedef struct _SeafileDiffEntryPrivate SeafileDiffEntryPrivate;\n\n#define SEAFILE_TYPE_ENCRYPTION_INFO (seafile_encryption_info_get_type ())\n#define SEAFILE_ENCRYPTION_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_ENCRYPTION_INFO, SeafileEncryptionInfo))\n#define SEAFILE_ENCRYPTION_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_ENCRYPTION_INFO, SeafileEncryptionInfoClass))\n#define SEAFILE_IS_ENCRYPTION_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_ENCRYPTION_INFO))\n#define SEAFILE_IS_ENCRYPTION_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_ENCRYPTION_INFO))\n#define SEAFILE_ENCRYPTION_INFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_ENCRYPTION_INFO, SeafileEncryptionInfoClass))\n\ntypedef struct _SeafileEncryptionInfo SeafileEncryptionInfo;\ntypedef struct _SeafileEncryptionInfoClass SeafileEncryptionInfoClass;\ntypedef struct _SeafileEncryptionInfoPrivate SeafileEncryptionInfoPrivate;\n\n#define SEAFILE_TYPE_FILE_SYNC_ERROR (seafile_file_sync_error_get_type ())\n#define SEAFILE_FILE_SYNC_ERROR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_FILE_SYNC_ERROR, SeafileFileSyncError))\n#define SEAFILE_FILE_SYNC_ERROR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_FILE_SYNC_ERROR, SeafileFileSyncErrorClass))\n#define SEAFILE_IS_FILE_SYNC_ERROR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_FILE_SYNC_ERROR))\n#define SEAFILE_IS_FILE_SYNC_ERROR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_FILE_SYNC_ERROR))\n#define SEAFILE_FILE_SYNC_ERROR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_FILE_SYNC_ERROR, SeafileFileSyncErrorClass))\n\ntypedef struct _SeafileFileSyncError SeafileFileSyncError;\ntypedef struct _SeafileFileSyncErrorClass SeafileFileSyncErrorClass;\ntypedef struct _SeafileFileSyncErrorPrivate SeafileFileSyncErrorPrivate;\n\n#define SEAFILE_TYPE_TASK (seafile_task_get_type ())\n#define SEAFILE_TASK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_TASK, SeafileTask))\n#define SEAFILE_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_TASK, SeafileTaskClass))\n#define SEAFILE_IS_TASK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_TASK))\n#define SEAFILE_IS_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_TASK))\n#define SEAFILE_TASK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_TASK, SeafileTaskClass))\n\ntypedef struct _SeafileTask SeafileTask;\ntypedef struct _SeafileTaskClass SeafileTaskClass;\ntypedef struct _SeafileTaskPrivate SeafileTaskPrivate;\n\n#define SEAFILE_TYPE_CLONE_TASK (seafile_clone_task_get_type ())\n#define SEAFILE_CLONE_TASK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_CLONE_TASK, SeafileCloneTask))\n#define SEAFILE_CLONE_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_CLONE_TASK, SeafileCloneTaskClass))\n#define SEAFILE_IS_CLONE_TASK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_CLONE_TASK))\n#define SEAFILE_IS_CLONE_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_CLONE_TASK))\n#define SEAFILE_CLONE_TASK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_CLONE_TASK, SeafileCloneTaskClass))\n\ntypedef struct _SeafileCloneTask SeafileCloneTask;\ntypedef struct _SeafileCloneTaskClass SeafileCloneTaskClass;\ntypedef struct _SeafileCloneTaskPrivate SeafileCloneTaskPrivate;\n\nstruct _SeafileRepo {\n\tGObject parent_instance;\n\tSeafileRepoPrivate * priv;\n\tgchar _id[37];\n\tgchar* _name;\n\tgchar* _desc;\n\tgchar* _worktree;\n\tgchar* _relay_id;\n};\n\nstruct _SeafileRepoClass {\n\tGObjectClass parent_class;\n};\n\nstruct _SeafileSyncTask {\n\tGObject parent_instance;\n\tSeafileSyncTaskPrivate * priv;\n};\n\nstruct _SeafileSyncTaskClass {\n\tGObjectClass parent_class;\n};\n\nstruct _SeafileSessionInfo {\n\tGObject parent_instance;\n\tSeafileSessionInfoPrivate * priv;\n};\n\nstruct _SeafileSessionInfoClass {\n\tGObjectClass parent_class;\n};\n\nstruct _SeafileDiffEntry {\n\tGObject parent_instance;\n\tSeafileDiffEntryPrivate * priv;\n};\n\nstruct _SeafileDiffEntryClass {\n\tGObjectClass parent_class;\n};\n\nstruct _SeafileEncryptionInfo {\n\tGObject parent_instance;\n\tSeafileEncryptionInfoPrivate * priv;\n};\n\nstruct _SeafileEncryptionInfoClass {\n\tGObjectClass parent_class;\n};\n\nstruct _SeafileFileSyncError {\n\tGObject parent_instance;\n\tSeafileFileSyncErrorPrivate * priv;\n};\n\nstruct _SeafileFileSyncErrorClass {\n\tGObjectClass parent_class;\n};\n\nstruct _SeafileTask {\n\tGObject parent_instance;\n\tSeafileTaskPrivate * priv;\n};\n\nstruct _SeafileTaskClass {\n\tGObjectClass parent_class;\n};\n\nstruct _SeafileCloneTask {\n\tGObject parent_instance;\n\tSeafileCloneTaskPrivate * priv;\n};\n\nstruct _SeafileCloneTaskClass {\n\tGObjectClass parent_class;\n};\n\nVALA_EXTERN GType seafile_repo_get_type (void) G_GNUC_CONST ;\nG_DEFINE_AUTOPTR_CLEANUP_FUNC (SeafileRepo, g_object_unref)\nVALA_EXTERN SeafileRepo* seafile_repo_new (void);\nVALA_EXTERN SeafileRepo* seafile_repo_construct (GType object_type);\nVALA_EXTERN const gchar* seafile_repo_get_id (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_id (SeafileRepo* self,\n                          const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_name (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_name (SeafileRepo* self,\n                            const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_desc (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_desc (SeafileRepo* self,\n                            const gchar* value);\nVALA_EXTERN gint seafile_repo_get_version (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_version (SeafileRepo* self,\n                               gint value);\nVALA_EXTERN gint seafile_repo_get_last_modify (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_last_modify (SeafileRepo* self,\n                                   gint value);\nVALA_EXTERN gint64 seafile_repo_get_size (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_size (SeafileRepo* self,\n                            gint64 value);\nVALA_EXTERN gint64 seafile_repo_get_file_count (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_file_count (SeafileRepo* self,\n                                  gint64 value);\nVALA_EXTERN const gchar* seafile_repo_get_head_cmmt_id (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_head_cmmt_id (SeafileRepo* self,\n                                    const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_root (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_root (SeafileRepo* self,\n                            const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_repo_id (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_repo_id (SeafileRepo* self,\n                               const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_repo_name (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_repo_name (SeafileRepo* self,\n                                 const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_repo_desc (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_repo_desc (SeafileRepo* self,\n                                 const gchar* value);\nVALA_EXTERN gint seafile_repo_get_last_modified (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_last_modified (SeafileRepo* self,\n                                     gint value);\nVALA_EXTERN gboolean seafile_repo_get_encrypted (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_encrypted (SeafileRepo* self,\n                                 gboolean value);\nVALA_EXTERN const gchar* seafile_repo_get_magic (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_magic (SeafileRepo* self,\n                             const gchar* value);\nVALA_EXTERN gint seafile_repo_get_enc_version (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_enc_version (SeafileRepo* self,\n                                   gint value);\nVALA_EXTERN const gchar* seafile_repo_get_random_key (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_random_key (SeafileRepo* self,\n                                  const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_salt (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_salt (SeafileRepo* self,\n                            const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_worktree (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_worktree (SeafileRepo* self,\n                                const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_relay_id (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_relay_id (SeafileRepo* self,\n                                const gchar* value);\nVALA_EXTERN gint seafile_repo_get_last_sync_time (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_last_sync_time (SeafileRepo* self,\n                                      gint value);\nVALA_EXTERN gboolean seafile_repo_get_auto_sync (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_auto_sync (SeafileRepo* self,\n                                 gboolean value);\nVALA_EXTERN gboolean seafile_repo_get_worktree_invalid (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_worktree_invalid (SeafileRepo* self,\n                                        gboolean value);\nVALA_EXTERN gboolean seafile_repo_get_is_virtual (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_is_virtual (SeafileRepo* self,\n                                  gboolean value);\nVALA_EXTERN const gchar* seafile_repo_get_origin_repo_id (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_origin_repo_id (SeafileRepo* self,\n                                      const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_origin_repo_name (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_origin_repo_name (SeafileRepo* self,\n                                        const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_origin_path (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_origin_path (SeafileRepo* self,\n                                   const gchar* value);\nVALA_EXTERN gboolean seafile_repo_get_is_original_owner (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_is_original_owner (SeafileRepo* self,\n                                         gboolean value);\nVALA_EXTERN const gchar* seafile_repo_get_virtual_perm (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_virtual_perm (SeafileRepo* self,\n                                    const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_store_id (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_store_id (SeafileRepo* self,\n                                const gchar* value);\nVALA_EXTERN gboolean seafile_repo_get_is_corrupted (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_is_corrupted (SeafileRepo* self,\n                                    gboolean value);\nVALA_EXTERN gboolean seafile_repo_get_repaired (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_repaired (SeafileRepo* self,\n                                gboolean value);\nVALA_EXTERN const gchar* seafile_repo_get_share_type (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_share_type (SeafileRepo* self,\n                                  const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_permission (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_permission (SeafileRepo* self,\n                                  const gchar* value);\nVALA_EXTERN const gchar* seafile_repo_get_user (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_user (SeafileRepo* self,\n                            const gchar* value);\nVALA_EXTERN gint seafile_repo_get_group_id (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_group_id (SeafileRepo* self,\n                                gint value);\nVALA_EXTERN gboolean seafile_repo_get_is_shared (SeafileRepo* self);\nVALA_EXTERN void seafile_repo_set_is_shared (SeafileRepo* self,\n                                 gboolean value);\nVALA_EXTERN GType seafile_sync_task_get_type (void) G_GNUC_CONST ;\nG_DEFINE_AUTOPTR_CLEANUP_FUNC (SeafileSyncTask, g_object_unref)\nVALA_EXTERN SeafileSyncTask* seafile_sync_task_new (void);\nVALA_EXTERN SeafileSyncTask* seafile_sync_task_construct (GType object_type);\nVALA_EXTERN gboolean seafile_sync_task_get_force_upload (SeafileSyncTask* self);\nVALA_EXTERN void seafile_sync_task_set_force_upload (SeafileSyncTask* self,\n                                         gboolean value);\nVALA_EXTERN const gchar* seafile_sync_task_get_repo_id (SeafileSyncTask* self);\nVALA_EXTERN void seafile_sync_task_set_repo_id (SeafileSyncTask* self,\n                                    const gchar* value);\nVALA_EXTERN const gchar* seafile_sync_task_get_state (SeafileSyncTask* self);\nVALA_EXTERN void seafile_sync_task_set_state (SeafileSyncTask* self,\n                                  const gchar* value);\nVALA_EXTERN gint seafile_sync_task_get_error (SeafileSyncTask* self);\nVALA_EXTERN void seafile_sync_task_set_error (SeafileSyncTask* self,\n                                  gint value);\nVALA_EXTERN GType seafile_session_info_get_type (void) G_GNUC_CONST ;\nG_DEFINE_AUTOPTR_CLEANUP_FUNC (SeafileSessionInfo, g_object_unref)\nVALA_EXTERN SeafileSessionInfo* seafile_session_info_new (void);\nVALA_EXTERN SeafileSessionInfo* seafile_session_info_construct (GType object_type);\nVALA_EXTERN const gchar* seafile_session_info_get_datadir (SeafileSessionInfo* self);\nVALA_EXTERN void seafile_session_info_set_datadir (SeafileSessionInfo* self,\n                                       const gchar* value);\nVALA_EXTERN GType seafile_diff_entry_get_type (void) G_GNUC_CONST ;\nG_DEFINE_AUTOPTR_CLEANUP_FUNC (SeafileDiffEntry, g_object_unref)\nVALA_EXTERN SeafileDiffEntry* seafile_diff_entry_new (void);\nVALA_EXTERN SeafileDiffEntry* seafile_diff_entry_construct (GType object_type);\nVALA_EXTERN const gchar* seafile_diff_entry_get_status (SeafileDiffEntry* self);\nVALA_EXTERN void seafile_diff_entry_set_status (SeafileDiffEntry* self,\n                                    const gchar* value);\nVALA_EXTERN const gchar* seafile_diff_entry_get_name (SeafileDiffEntry* self);\nVALA_EXTERN void seafile_diff_entry_set_name (SeafileDiffEntry* self,\n                                  const gchar* value);\nVALA_EXTERN const gchar* seafile_diff_entry_get_new_name (SeafileDiffEntry* self);\nVALA_EXTERN void seafile_diff_entry_set_new_name (SeafileDiffEntry* self,\n                                      const gchar* value);\nVALA_EXTERN GType seafile_encryption_info_get_type (void) G_GNUC_CONST ;\nG_DEFINE_AUTOPTR_CLEANUP_FUNC (SeafileEncryptionInfo, g_object_unref)\nVALA_EXTERN SeafileEncryptionInfo* seafile_encryption_info_new (void);\nVALA_EXTERN SeafileEncryptionInfo* seafile_encryption_info_construct (GType object_type);\nVALA_EXTERN const gchar* seafile_encryption_info_get_repo_id (SeafileEncryptionInfo* self);\nVALA_EXTERN void seafile_encryption_info_set_repo_id (SeafileEncryptionInfo* self,\n                                          const gchar* value);\nVALA_EXTERN const gchar* seafile_encryption_info_get_passwd (SeafileEncryptionInfo* self);\nVALA_EXTERN void seafile_encryption_info_set_passwd (SeafileEncryptionInfo* self,\n                                         const gchar* value);\nVALA_EXTERN gint seafile_encryption_info_get_enc_version (SeafileEncryptionInfo* self);\nVALA_EXTERN void seafile_encryption_info_set_enc_version (SeafileEncryptionInfo* self,\n                                              gint value);\nVALA_EXTERN const gchar* seafile_encryption_info_get_magic (SeafileEncryptionInfo* self);\nVALA_EXTERN void seafile_encryption_info_set_magic (SeafileEncryptionInfo* self,\n                                        const gchar* value);\nVALA_EXTERN const gchar* seafile_encryption_info_get_pwd_hash (SeafileEncryptionInfo* self);\nVALA_EXTERN void seafile_encryption_info_set_pwd_hash (SeafileEncryptionInfo* self,\n                                           const gchar* value);\nVALA_EXTERN const gchar* seafile_encryption_info_get_random_key (SeafileEncryptionInfo* self);\nVALA_EXTERN void seafile_encryption_info_set_random_key (SeafileEncryptionInfo* self,\n                                             const gchar* value);\nVALA_EXTERN const gchar* seafile_encryption_info_get_salt (SeafileEncryptionInfo* self);\nVALA_EXTERN void seafile_encryption_info_set_salt (SeafileEncryptionInfo* self,\n                                       const gchar* value);\nVALA_EXTERN GType seafile_file_sync_error_get_type (void) G_GNUC_CONST ;\nG_DEFINE_AUTOPTR_CLEANUP_FUNC (SeafileFileSyncError, g_object_unref)\nVALA_EXTERN SeafileFileSyncError* seafile_file_sync_error_new (void);\nVALA_EXTERN SeafileFileSyncError* seafile_file_sync_error_construct (GType object_type);\nVALA_EXTERN gint seafile_file_sync_error_get_id (SeafileFileSyncError* self);\nVALA_EXTERN void seafile_file_sync_error_set_id (SeafileFileSyncError* self,\n                                     gint value);\nVALA_EXTERN const gchar* seafile_file_sync_error_get_repo_id (SeafileFileSyncError* self);\nVALA_EXTERN void seafile_file_sync_error_set_repo_id (SeafileFileSyncError* self,\n                                          const gchar* value);\nVALA_EXTERN const gchar* seafile_file_sync_error_get_repo_name (SeafileFileSyncError* self);\nVALA_EXTERN void seafile_file_sync_error_set_repo_name (SeafileFileSyncError* self,\n                                            const gchar* value);\nVALA_EXTERN const gchar* seafile_file_sync_error_get_path (SeafileFileSyncError* self);\nVALA_EXTERN void seafile_file_sync_error_set_path (SeafileFileSyncError* self,\n                                       const gchar* value);\nVALA_EXTERN gint seafile_file_sync_error_get_err_id (SeafileFileSyncError* self);\nVALA_EXTERN void seafile_file_sync_error_set_err_id (SeafileFileSyncError* self,\n                                         gint value);\nVALA_EXTERN gint64 seafile_file_sync_error_get_timestamp (SeafileFileSyncError* self);\nVALA_EXTERN void seafile_file_sync_error_set_timestamp (SeafileFileSyncError* self,\n                                            gint64 value);\nVALA_EXTERN GType seafile_task_get_type (void) G_GNUC_CONST ;\nG_DEFINE_AUTOPTR_CLEANUP_FUNC (SeafileTask, g_object_unref)\nVALA_EXTERN SeafileTask* seafile_task_new (void);\nVALA_EXTERN SeafileTask* seafile_task_construct (GType object_type);\nVALA_EXTERN const gchar* seafile_task_get_ttype (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_ttype (SeafileTask* self,\n                             const gchar* value);\nVALA_EXTERN const gchar* seafile_task_get_repo_id (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_repo_id (SeafileTask* self,\n                               const gchar* value);\nVALA_EXTERN const gchar* seafile_task_get_state (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_state (SeafileTask* self,\n                             const gchar* value);\nVALA_EXTERN const gchar* seafile_task_get_rt_state (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_rt_state (SeafileTask* self,\n                                const gchar* value);\nVALA_EXTERN gint64 seafile_task_get_block_total (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_block_total (SeafileTask* self,\n                                   gint64 value);\nVALA_EXTERN gint64 seafile_task_get_block_done (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_block_done (SeafileTask* self,\n                                  gint64 value);\nVALA_EXTERN gint seafile_task_get_fs_objects_total (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_fs_objects_total (SeafileTask* self,\n                                        gint value);\nVALA_EXTERN gint seafile_task_get_fs_objects_done (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_fs_objects_done (SeafileTask* self,\n                                       gint value);\nVALA_EXTERN gint seafile_task_get_rate (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_rate (SeafileTask* self,\n                            gint value);\nVALA_EXTERN GType seafile_clone_task_get_type (void) G_GNUC_CONST ;\nG_DEFINE_AUTOPTR_CLEANUP_FUNC (SeafileCloneTask, g_object_unref)\nVALA_EXTERN SeafileCloneTask* seafile_clone_task_new (void);\nVALA_EXTERN SeafileCloneTask* seafile_clone_task_construct (GType object_type);\nVALA_EXTERN const gchar* seafile_clone_task_get_state (SeafileCloneTask* self);\nVALA_EXTERN void seafile_clone_task_set_state (SeafileCloneTask* self,\n                                   const gchar* value);\nVALA_EXTERN gint seafile_clone_task_get_error (SeafileCloneTask* self);\nVALA_EXTERN void seafile_clone_task_set_error (SeafileCloneTask* self,\n                                   gint value);\nVALA_EXTERN const gchar* seafile_clone_task_get_repo_id (SeafileCloneTask* self);\nVALA_EXTERN void seafile_clone_task_set_repo_id (SeafileCloneTask* self,\n                                     const gchar* value);\nVALA_EXTERN const gchar* seafile_clone_task_get_repo_name (SeafileCloneTask* self);\nVALA_EXTERN void seafile_clone_task_set_repo_name (SeafileCloneTask* self,\n                                       const gchar* value);\nVALA_EXTERN const gchar* seafile_clone_task_get_worktree (SeafileCloneTask* self);\nVALA_EXTERN void seafile_clone_task_set_worktree (SeafileCloneTask* self,\n                                      const gchar* value);\n\nG_END_DECLS\n\n#endif\n"
  },
  {
    "path": "lib/seafile-rpc-wrapper.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#if !defined(_MSC_VER)\n#include <config.h>\n#endif\n#include <stdint.h>\n#if !defined(_MSC_VER)\n#include <unistd.h>\n#endif\n#include <stdlib.h>\n#include <string.h>\n#include <errno.h>\n\n#include <glib.h>\n\n#include <searpc-client.h>\n#include \"seafile-object.h\"\n\nchar *\nseafile_get_config (SearpcClient *client, const char *key)\n{\n    if (!key)\n        return NULL;\n\n    return searpc_client_call__string (\n        client, \"seafile_get_config\", NULL, \n        1, \"string\", key);\n}\n\nint\nseafile_set_config (SearpcClient *client, const char *key, const char *value)\n{\n    if (!key || !value)\n        return -1;\n\n    return searpc_client_call__int (\n        client, \"seafile_set_config\", NULL,\n        2, \"string\", key, \"string\", value);\n}\n\nint\nseafile_destroy_repo (SearpcClient *client,\n                      const char *repo_id, GError **error)\n{\n    g_return_val_if_fail (client && repo_id, -1);\n\n    return searpc_client_call__int (\n        client, \"seafile_destroy_repo\", error,\n        1, \"string\", repo_id);\n}\n\nint\nseafile_set_repo_token (SearpcClient *client,\n                        const char *repo_id,\n                        const char *token,\n                        GError **error)\n{\n    g_return_val_if_fail (client && repo_id && token, -1);\n\n    return searpc_client_call__int (\n        client, \"seafile_set_repo_token\", error,\n        2, \"string\", repo_id, \"string\", token);\n}\n\nchar *\nseafile_get_repo_token (SearpcClient *client,\n                        const char *repo_id,\n                        GError **error)\n{\n    g_return_val_if_fail (client && repo_id, NULL);\n\n    return searpc_client_call__string (\n        client, \"seafile_get_repo_token\", error,\n        1, \"string\", repo_id);\n}\n\nGList *\nseafile_get_repo_list (SearpcClient *client,\n                       int offset,\n                       int limit, GError **error)\n{\n    return searpc_client_call__objlist (\n        client, \"seafile_get_repo_list\", SEAFILE_TYPE_REPO, error,\n        2, \"int\", offset, \"int\", limit);\n}\n\nGObject *\nseafile_get_repo (SearpcClient *client,\n                  const char *repo_id,\n                  GError **error)\n{\n    g_return_val_if_fail (client && repo_id, NULL);\n\n    return searpc_client_call__object (\n        client, \"seafile_get_repo\", SEAFILE_TYPE_REPO, error,\n        1, \"string\", repo_id);\n}\n\nint\nseafile_set_repo_property (SearpcClient *client,\n                           const char *repo_id,\n                           const char *key,\n                           const char *value,\n                           GError **error)\n{\n    g_return_val_if_fail (client && repo_id && key, -1);\n\n    return searpc_client_call__int (\n        client, \"seafile_set_repo_property\", error,\n        3, \"string\", repo_id, \"string\", key, \"string\", value);\n}\n\nchar *\nseafile_get_repo_property (SearpcClient *client,\n                           const char *repo_id,\n                           const char *key,\n                           GError **error)\n{\n    g_return_val_if_fail (client && repo_id, NULL);\n\n    return searpc_client_call__string (\n        client, \"seafile_get_repo_property\", error,\n        2, \"string\", repo_id, \"string\", key);\n}\n\n\nint\nseafile_calc_dir_size (SearpcClient *client, const char *path, GError **error)\n{\n    return searpc_client_call__int (client, \"seafile_calc_dir_size\", error,\n                                    1, \"string\", path);\n}\n"
  },
  {
    "path": "lib/searpc-marshal.h",
    "content": "\nstatic char *\nmarshal_int__void (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n\n    int ret = ((int (*)(GError **))func) (&error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n\n    int ret = ((int (*)(int, GError **))func) (param1, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n\n    int ret = ((int (*)(int, int, GError **))func) (param1, param2, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__int_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n\n    int ret = ((int (*)(int, const char*, GError **))func) (param1, param2, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__int_string_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    int param3 = json_array_get_int_element (param_array, 3);\n\n    int ret = ((int (*)(int, const char*, int, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__int_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n\n    int ret = ((int (*)(int, const char*, const char*, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__int_string_int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    int param3 = json_array_get_int_element (param_array, 3);\n    int param4 = json_array_get_int_element (param_array, 4);\n\n    int ret = ((int (*)(int, const char*, int, int, GError **))func) (param1, param2, param3, param4, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__int_int_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n\n    int ret = ((int (*)(int, int, const char*, const char*, GError **))func) (param1, param2, param3, param4, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n\n    int ret = ((int (*)(const char*, GError **))func) (param1, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__string_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n\n    int ret = ((int (*)(const char*, int, GError **))func) (param1, param2, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__string_int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    int param3 = json_array_get_int_element (param_array, 3);\n\n    int ret = ((int (*)(const char*, int, int, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__string_int_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n\n    int ret = ((int (*)(const char*, int, const char*, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__string_int_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n\n    int ret = ((int (*)(const char*, int, const char*, const char*, GError **))func) (param1, param2, param3, param4, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__string_int_int_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    int param3 = json_array_get_int_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n\n    int ret = ((int (*)(const char*, int, int, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n\n    int ret = ((int (*)(const char*, const char*, GError **))func) (param1, param2, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__string_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n\n    int ret = ((int (*)(const char*, const char*, const char*, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__string_string_int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    int param3 = json_array_get_int_element (param_array, 3);\n    int param4 = json_array_get_int_element (param_array, 4);\n\n    int ret = ((int (*)(const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__string_string_string_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    int param4 = json_array_get_int_element (param_array, 4);\n\n    int ret = ((int (*)(const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__string_string_string_int_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    int param4 = json_array_get_int_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n\n    int ret = ((int (*)(const char*, const char*, const char*, int, const char*, GError **))func) (param1, param2, param3, param4, param5, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__string_string_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n\n    int ret = ((int (*)(const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n\n    int ret = ((int (*)(const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__string_string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n    const char* param6 = json_array_get_string_or_null_element (param_array, 6);\n\n    int ret = ((int (*)(const char*, const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, param6, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__string_string_string_int_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    int param4 = json_array_get_int_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n    const char* param6 = json_array_get_string_or_null_element (param_array, 6);\n\n    int ret = ((int (*)(const char*, const char*, const char*, int, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, param6, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__string_string_string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n    const char* param6 = json_array_get_string_or_null_element (param_array, 6);\n    const char* param7 = json_array_get_string_or_null_element (param_array, 7);\n\n    int ret = ((int (*)(const char*, const char*, const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, param6, param7, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__string_int64 (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    gint64 param2 = json_array_get_int_element (param_array, 2);\n\n    int ret = ((int (*)(const char*, gint64, GError **))func) (param1, param2, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__int_int64 (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    gint64 param2 = json_array_get_int_element (param_array, 2);\n\n    int ret = ((int (*)(int, gint64, GError **))func) (param1, param2, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int__int_string_int64 (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    gint64 param3 = json_array_get_int_element (param_array, 3);\n\n    int ret = ((int (*)(int, const char*, gint64, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int64__void (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n\n    gint64 ret = ((gint64 (*)(GError **))func) (&error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int64__string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n\n    gint64 ret = ((gint64 (*)(const char*, GError **))func) (param1, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int64__int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n\n    gint64 ret = ((gint64 (*)(int, GError **))func) (param1, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int64__int_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n\n    gint64 ret = ((gint64 (*)(int, const char*, GError **))func) (param1, param2, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_int64__string_int_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n\n    gint64 ret = ((gint64 (*)(const char*, int, const char*, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_int_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__void (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n\n    char* ret = ((char* (*)(GError **))func) (&error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n\n    char* ret = ((char* (*)(int, GError **))func) (param1, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n\n    char* ret = ((char* (*)(int, int, GError **))func) (param1, param2, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__int_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n\n    char* ret = ((char* (*)(int, const char*, GError **))func) (param1, param2, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__int_int_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n\n    char* ret = ((char* (*)(int, int, const char*, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n\n    char* ret = ((char* (*)(const char*, GError **))func) (param1, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n\n    char* ret = ((char* (*)(const char*, int, GError **))func) (param1, param2, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    int param3 = json_array_get_int_element (param_array, 3);\n\n    char* ret = ((char* (*)(const char*, int, int, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n\n    char* ret = ((char* (*)(const char*, const char*, GError **))func) (param1, param2, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_string_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    int param3 = json_array_get_int_element (param_array, 3);\n\n    char* ret = ((char* (*)(const char*, const char*, int, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_string_int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    int param3 = json_array_get_int_element (param_array, 3);\n    int param4 = json_array_get_int_element (param_array, 4);\n\n    char* ret = ((char* (*)(const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n\n    char* ret = ((char* (*)(const char*, const char*, const char*, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_string_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n\n    char* ret = ((char* (*)(const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_string_string_string_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    int param5 = json_array_get_int_element (param_array, 5);\n\n    char* ret = ((char* (*)(const char*, const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, param5, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n\n    char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_string_string_string_string_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n    int param6 = json_array_get_int_element (param_array, 6);\n\n    char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, param5, param6, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_string_string_string_string_string_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n    const char* param6 = json_array_get_string_or_null_element (param_array, 6);\n    int param7 = json_array_get_int_element (param_array, 7);\n\n    char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, param5, param6, param7, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_string_string_string_string_string_int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n    const char* param6 = json_array_get_string_or_null_element (param_array, 6);\n    int param7 = json_array_get_int_element (param_array, 7);\n    int param8 = json_array_get_int_element (param_array, 8);\n\n    char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, param5, param6, param7, param8, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n    const char* param6 = json_array_get_string_or_null_element (param_array, 6);\n\n    char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, param6, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_string_string_string_string_string_int64 (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n    const char* param6 = json_array_get_string_or_null_element (param_array, 6);\n    gint64 param7 = json_array_get_int_element (param_array, 7);\n\n    char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, const char*, gint64, GError **))func) (param1, param2, param3, param4, param5, param6, param7, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_string_string_string_string_string_int64_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n    const char* param6 = json_array_get_string_or_null_element (param_array, 6);\n    gint64 param7 = json_array_get_int_element (param_array, 7);\n    int param8 = json_array_get_int_element (param_array, 8);\n\n    char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, const char*, gint64, int, GError **))func) (param1, param2, param3, param4, param5, param6, param7, param8, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_string_string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n    const char* param6 = json_array_get_string_or_null_element (param_array, 6);\n    const char* param7 = json_array_get_string_or_null_element (param_array, 7);\n\n    char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, param6, param7, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_string_string_string_string_string_string_int64 (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n    const char* param6 = json_array_get_string_or_null_element (param_array, 6);\n    const char* param7 = json_array_get_string_or_null_element (param_array, 7);\n    gint64 param8 = json_array_get_int_element (param_array, 8);\n\n    char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, const char*, const char*, gint64, GError **))func) (param1, param2, param3, param4, param5, param6, param7, param8, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_string_string_string_string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n    const char* param6 = json_array_get_string_or_null_element (param_array, 6);\n    const char* param7 = json_array_get_string_or_null_element (param_array, 7);\n    const char* param8 = json_array_get_string_or_null_element (param_array, 8);\n    const char* param9 = json_array_get_string_or_null_element (param_array, 9);\n\n    char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, param6, param7, param8, param9, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_int_string_string_string_string_string_string_string_int_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n    const char* param6 = json_array_get_string_or_null_element (param_array, 6);\n    const char* param7 = json_array_get_string_or_null_element (param_array, 7);\n    const char* param8 = json_array_get_string_or_null_element (param_array, 8);\n    const char* param9 = json_array_get_string_or_null_element (param_array, 9);\n    int param10 = json_array_get_int_element (param_array, 10);\n    const char* param11 = json_array_get_string_or_null_element (param_array, 11);\n\n    char* ret = ((char* (*)(const char*, int, const char*, const char*, const char*, const char*, const char*, const char*, const char*, int, const char*, GError **))func) (param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_int_string_int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    int param4 = json_array_get_int_element (param_array, 4);\n    int param5 = json_array_get_int_element (param_array, 5);\n\n    char* ret = ((char* (*)(const char*, int, const char*, int, int, GError **))func) (param1, param2, param3, param4, param5, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_string__string_int_string_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n\n    char* ret = ((char* (*)(const char*, int, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, &error);\n\n    json_t *object = json_object ();\n    searpc_set_string_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__void (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n\n    GList* ret = ((GList* (*)(GError **))func) (&error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n\n    GList* ret = ((GList* (*)(int, GError **))func) (param1, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n\n    GList* ret = ((GList* (*)(int, int, GError **))func) (param1, param2, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__int_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n\n    GList* ret = ((GList* (*)(int, const char*, GError **))func) (param1, param2, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__int_int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    int param3 = json_array_get_int_element (param_array, 3);\n\n    GList* ret = ((GList* (*)(int, int, int, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n\n    GList* ret = ((GList* (*)(const char*, GError **))func) (param1, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__string_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n\n    GList* ret = ((GList* (*)(const char*, int, GError **))func) (param1, param2, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__string_int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    int param3 = json_array_get_int_element (param_array, 3);\n\n    GList* ret = ((GList* (*)(const char*, int, int, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__string_int_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n\n    GList* ret = ((GList* (*)(const char*, int, const char*, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n\n    GList* ret = ((GList* (*)(const char*, const char*, GError **))func) (param1, param2, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__string_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n\n    GList* ret = ((GList* (*)(const char*, const char*, const char*, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__string_string_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    int param3 = json_array_get_int_element (param_array, 3);\n\n    GList* ret = ((GList* (*)(const char*, const char*, int, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__string_string_string_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    int param4 = json_array_get_int_element (param_array, 4);\n\n    GList* ret = ((GList* (*)(const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__string_string_int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    int param3 = json_array_get_int_element (param_array, 3);\n    int param4 = json_array_get_int_element (param_array, 4);\n\n    GList* ret = ((GList* (*)(const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__string_string_int_int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    int param3 = json_array_get_int_element (param_array, 3);\n    int param4 = json_array_get_int_element (param_array, 4);\n    int param5 = json_array_get_int_element (param_array, 5);\n\n    GList* ret = ((GList* (*)(const char*, const char*, int, int, int, GError **))func) (param1, param2, param3, param4, param5, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__int_string_string_int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    int param4 = json_array_get_int_element (param_array, 4);\n    int param5 = json_array_get_int_element (param_array, 5);\n\n    GList* ret = ((GList* (*)(int, const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, param5, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__string_int_string_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n\n    GList* ret = ((GList* (*)(const char*, int, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__string_int_string_int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    int param4 = json_array_get_int_element (param_array, 4);\n    int param5 = json_array_get_int_element (param_array, 5);\n\n    GList* ret = ((GList* (*)(const char*, int, const char*, int, int, GError **))func) (param1, param2, param3, param4, param5, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__string_int_string_string_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    int param5 = json_array_get_int_element (param_array, 5);\n\n    GList* ret = ((GList* (*)(const char*, int, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, param5, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_objlist__string_string_string_string_int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    int param5 = json_array_get_int_element (param_array, 5);\n    int param6 = json_array_get_int_element (param_array, 6);\n\n    GList* ret = ((GList* (*)(const char*, const char*, const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, param5, param6, &error);\n\n    json_t *object = json_object ();\n    searpc_set_objlist_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_object__int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n\n    GObject* ret = ((GObject* (*)(int, GError **))func) (param1, &error);\n\n    json_t *object = json_object ();\n    searpc_set_object_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_object__string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n\n    GObject* ret = ((GObject* (*)(const char*, GError **))func) (param1, &error);\n\n    json_t *object = json_object ();\n    searpc_set_object_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_object__string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n\n    GObject* ret = ((GObject* (*)(const char*, const char*, GError **))func) (param1, param2, &error);\n\n    json_t *object = json_object ();\n    searpc_set_object_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_object__string_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n\n    GObject* ret = ((GObject* (*)(const char*, const char*, const char*, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_object_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_object__string_int_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    int param2 = json_array_get_int_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n\n    GObject* ret = ((GObject* (*)(const char*, int, const char*, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_object_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_object__int_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n\n    GObject* ret = ((GObject* (*)(int, const char*, const char*, GError **))func) (param1, param2, param3, &error);\n\n    json_t *object = json_object ();\n    searpc_set_object_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_object__int_string_string_string_string (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    int param1 = json_array_get_int_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n\n    GObject* ret = ((GObject* (*)(int, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, &error);\n\n    json_t *object = json_object ();\n    searpc_set_object_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_object__string_string_string_string_string_string_string_int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n    const char* param6 = json_array_get_string_or_null_element (param_array, 6);\n    const char* param7 = json_array_get_string_or_null_element (param_array, 7);\n    int param8 = json_array_get_int_element (param_array, 8);\n    int param9 = json_array_get_int_element (param_array, 9);\n\n    GObject* ret = ((GObject* (*)(const char*, const char*, const char*, const char*, const char*, const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, param5, param6, param7, param8, param9, &error);\n\n    json_t *object = json_object ();\n    searpc_set_object_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_object__string_string_string_string_string_string_int_string_int_int (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n    const char* param1 = json_array_get_string_or_null_element (param_array, 1);\n    const char* param2 = json_array_get_string_or_null_element (param_array, 2);\n    const char* param3 = json_array_get_string_or_null_element (param_array, 3);\n    const char* param4 = json_array_get_string_or_null_element (param_array, 4);\n    const char* param5 = json_array_get_string_or_null_element (param_array, 5);\n    const char* param6 = json_array_get_string_or_null_element (param_array, 6);\n    int param7 = json_array_get_int_element (param_array, 7);\n    const char* param8 = json_array_get_string_or_null_element (param_array, 8);\n    int param9 = json_array_get_int_element (param_array, 9);\n    int param10 = json_array_get_int_element (param_array, 10);\n\n    GObject* ret = ((GObject* (*)(const char*, const char*, const char*, const char*, const char*, const char*, int, const char*, int, int, GError **))func) (param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, &error);\n\n    json_t *object = json_object ();\n    searpc_set_object_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\n\nstatic char *\nmarshal_json__void (void *func, json_t *param_array, gsize *ret_len)\n{\n    GError *error = NULL;\n\n    json_t* ret = ((json_t* (*)(GError **))func) (&error);\n\n    json_t *object = json_object ();\n    searpc_set_json_to_ret_object (object, ret);\n    return searpc_marshal_set_ret_common (object, ret_len, error);\n}\n\nstatic void register_marshals(void)\n{\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__void(), marshal_int__void);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__int(), marshal_int__int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__int_int(), marshal_int__int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__int_string(), marshal_int__int_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__int_string_int(), marshal_int__int_string_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__int_string_string(), marshal_int__int_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__int_string_int_int(), marshal_int__int_string_int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__int_int_string_string(), marshal_int__int_int_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__string(), marshal_int__string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__string_int(), marshal_int__string_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__string_int_int(), marshal_int__string_int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__string_int_string(), marshal_int__string_int_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__string_int_string_string(), marshal_int__string_int_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__string_int_int_string_string(), marshal_int__string_int_int_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__string_string(), marshal_int__string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__string_string_string(), marshal_int__string_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__string_string_int_int(), marshal_int__string_string_int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__string_string_string_int(), marshal_int__string_string_string_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__string_string_string_int_string(), marshal_int__string_string_string_int_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__string_string_string_string(), marshal_int__string_string_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__string_string_string_string_string(), marshal_int__string_string_string_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__string_string_string_string_string_string(), marshal_int__string_string_string_string_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__string_string_string_int_string_string(), marshal_int__string_string_string_int_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__string_string_string_string_string_string_string(), marshal_int__string_string_string_string_string_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__string_int64(), marshal_int__string_int64);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__int_int64(), marshal_int__int_int64);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int__int_string_int64(), marshal_int__int_string_int64);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int64__void(), marshal_int64__void);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int64__string(), marshal_int64__string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int64__int(), marshal_int64__int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int64__int_string(), marshal_int64__int_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_int64__string_int_string(), marshal_int64__string_int_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__void(), marshal_string__void);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__int(), marshal_string__int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__int_int(), marshal_string__int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__int_string(), marshal_string__int_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__int_int_string(), marshal_string__int_int_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string(), marshal_string__string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_int(), marshal_string__string_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_int_int(), marshal_string__string_int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_string(), marshal_string__string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_string_int(), marshal_string__string_string_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_string_int_int(), marshal_string__string_string_int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_string_string(), marshal_string__string_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_string_string_string(), marshal_string__string_string_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_string_string_string_int(), marshal_string__string_string_string_string_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string(), marshal_string__string_string_string_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_int(), marshal_string__string_string_string_string_string_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string_int(), marshal_string__string_string_string_string_string_string_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string_int_int(), marshal_string__string_string_string_string_string_string_int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string(), marshal_string__string_string_string_string_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string_int64(), marshal_string__string_string_string_string_string_string_int64);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string_int64_int(), marshal_string__string_string_string_string_string_string_int64_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string_string(), marshal_string__string_string_string_string_string_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string_string_int64(), marshal_string__string_string_string_string_string_string_string_int64);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string_string_string_string(), marshal_string__string_string_string_string_string_string_string_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_int_string_string_string_string_string_string_string_int_string(), marshal_string__string_int_string_string_string_string_string_string_string_int_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_int_string_int_int(), marshal_string__string_int_string_int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_string__string_int_string_string_string(), marshal_string__string_int_string_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__void(), marshal_objlist__void);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__int(), marshal_objlist__int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__int_int(), marshal_objlist__int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__int_string(), marshal_objlist__int_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__int_int_int(), marshal_objlist__int_int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__string(), marshal_objlist__string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__string_int(), marshal_objlist__string_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__string_int_int(), marshal_objlist__string_int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__string_int_string(), marshal_objlist__string_int_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__string_string(), marshal_objlist__string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__string_string_string(), marshal_objlist__string_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__string_string_int(), marshal_objlist__string_string_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__string_string_string_int(), marshal_objlist__string_string_string_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__string_string_int_int(), marshal_objlist__string_string_int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__string_string_int_int_int(), marshal_objlist__string_string_int_int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__int_string_string_int_int(), marshal_objlist__int_string_string_int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__string_int_string_string_string(), marshal_objlist__string_int_string_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__string_int_string_int_int(), marshal_objlist__string_int_string_int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__string_int_string_string_int(), marshal_objlist__string_int_string_string_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_objlist__string_string_string_string_int_int(), marshal_objlist__string_string_string_string_int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_object__int(), marshal_object__int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_object__string(), marshal_object__string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_object__string_string(), marshal_object__string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_object__string_string_string(), marshal_object__string_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_object__string_int_string(), marshal_object__string_int_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_object__int_string_string(), marshal_object__int_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_object__int_string_string_string_string(), marshal_object__int_string_string_string_string);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_object__string_string_string_string_string_string_string_int_int(), marshal_object__string_string_string_string_string_string_string_int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_object__string_string_string_string_string_string_int_string_int_int(), marshal_object__string_string_string_string_string_string_int_string_int_int);\n    }\n\n\n    {\n        searpc_server_register_marshal (searpc_signature_json__void(), marshal_json__void);\n    }\n\n}\n"
  },
  {
    "path": "lib/searpc-signature.h",
    "content": "\ninline static gchar *\nsearpc_signature_int__void(void)\n{\n    return searpc_compute_signature (\"int\", 0);\n}\n\n\ninline static gchar *\nsearpc_signature_int__int(void)\n{\n    return searpc_compute_signature (\"int\", 1, \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__int_int(void)\n{\n    return searpc_compute_signature (\"int\", 2, \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__int_string(void)\n{\n    return searpc_compute_signature (\"int\", 2, \"int\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__int_string_int(void)\n{\n    return searpc_compute_signature (\"int\", 3, \"int\", \"string\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__int_string_string(void)\n{\n    return searpc_compute_signature (\"int\", 3, \"int\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__int_string_int_int(void)\n{\n    return searpc_compute_signature (\"int\", 4, \"int\", \"string\", \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__int_int_string_string(void)\n{\n    return searpc_compute_signature (\"int\", 4, \"int\", \"int\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__string(void)\n{\n    return searpc_compute_signature (\"int\", 1, \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__string_int(void)\n{\n    return searpc_compute_signature (\"int\", 2, \"string\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__string_int_int(void)\n{\n    return searpc_compute_signature (\"int\", 3, \"string\", \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__string_int_string(void)\n{\n    return searpc_compute_signature (\"int\", 3, \"string\", \"int\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__string_int_string_string(void)\n{\n    return searpc_compute_signature (\"int\", 4, \"string\", \"int\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__string_int_int_string_string(void)\n{\n    return searpc_compute_signature (\"int\", 5, \"string\", \"int\", \"int\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__string_string(void)\n{\n    return searpc_compute_signature (\"int\", 2, \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__string_string_string(void)\n{\n    return searpc_compute_signature (\"int\", 3, \"string\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__string_string_int_int(void)\n{\n    return searpc_compute_signature (\"int\", 4, \"string\", \"string\", \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__string_string_string_int(void)\n{\n    return searpc_compute_signature (\"int\", 4, \"string\", \"string\", \"string\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__string_string_string_int_string(void)\n{\n    return searpc_compute_signature (\"int\", 5, \"string\", \"string\", \"string\", \"int\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__string_string_string_string(void)\n{\n    return searpc_compute_signature (\"int\", 4, \"string\", \"string\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__string_string_string_string_string(void)\n{\n    return searpc_compute_signature (\"int\", 5, \"string\", \"string\", \"string\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__string_string_string_string_string_string(void)\n{\n    return searpc_compute_signature (\"int\", 6, \"string\", \"string\", \"string\", \"string\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__string_string_string_int_string_string(void)\n{\n    return searpc_compute_signature (\"int\", 6, \"string\", \"string\", \"string\", \"int\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__string_string_string_string_string_string_string(void)\n{\n    return searpc_compute_signature (\"int\", 7, \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__string_int64(void)\n{\n    return searpc_compute_signature (\"int\", 2, \"string\", \"int64\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__int_int64(void)\n{\n    return searpc_compute_signature (\"int\", 2, \"int\", \"int64\");\n}\n\n\ninline static gchar *\nsearpc_signature_int__int_string_int64(void)\n{\n    return searpc_compute_signature (\"int\", 3, \"int\", \"string\", \"int64\");\n}\n\n\ninline static gchar *\nsearpc_signature_int64__void(void)\n{\n    return searpc_compute_signature (\"int64\", 0);\n}\n\n\ninline static gchar *\nsearpc_signature_int64__string(void)\n{\n    return searpc_compute_signature (\"int64\", 1, \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_int64__int(void)\n{\n    return searpc_compute_signature (\"int64\", 1, \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_int64__int_string(void)\n{\n    return searpc_compute_signature (\"int64\", 2, \"int\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_int64__string_int_string(void)\n{\n    return searpc_compute_signature (\"int64\", 3, \"string\", \"int\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__void(void)\n{\n    return searpc_compute_signature (\"string\", 0);\n}\n\n\ninline static gchar *\nsearpc_signature_string__int(void)\n{\n    return searpc_compute_signature (\"string\", 1, \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__int_int(void)\n{\n    return searpc_compute_signature (\"string\", 2, \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__int_string(void)\n{\n    return searpc_compute_signature (\"string\", 2, \"int\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__int_int_string(void)\n{\n    return searpc_compute_signature (\"string\", 3, \"int\", \"int\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string(void)\n{\n    return searpc_compute_signature (\"string\", 1, \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_int(void)\n{\n    return searpc_compute_signature (\"string\", 2, \"string\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_int_int(void)\n{\n    return searpc_compute_signature (\"string\", 3, \"string\", \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_string(void)\n{\n    return searpc_compute_signature (\"string\", 2, \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_string_int(void)\n{\n    return searpc_compute_signature (\"string\", 3, \"string\", \"string\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_string_int_int(void)\n{\n    return searpc_compute_signature (\"string\", 4, \"string\", \"string\", \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_string_string(void)\n{\n    return searpc_compute_signature (\"string\", 3, \"string\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_string_string_string(void)\n{\n    return searpc_compute_signature (\"string\", 4, \"string\", \"string\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_string_string_string_int(void)\n{\n    return searpc_compute_signature (\"string\", 5, \"string\", \"string\", \"string\", \"string\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_string_string_string_string(void)\n{\n    return searpc_compute_signature (\"string\", 5, \"string\", \"string\", \"string\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_string_string_string_string_int(void)\n{\n    return searpc_compute_signature (\"string\", 6, \"string\", \"string\", \"string\", \"string\", \"string\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_string_string_string_string_string_int(void)\n{\n    return searpc_compute_signature (\"string\", 7, \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_string_string_string_string_string_int_int(void)\n{\n    return searpc_compute_signature (\"string\", 8, \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_string_string_string_string_string(void)\n{\n    return searpc_compute_signature (\"string\", 6, \"string\", \"string\", \"string\", \"string\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_string_string_string_string_string_int64(void)\n{\n    return searpc_compute_signature (\"string\", 7, \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int64\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_string_string_string_string_string_int64_int(void)\n{\n    return searpc_compute_signature (\"string\", 8, \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int64\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_string_string_string_string_string_string(void)\n{\n    return searpc_compute_signature (\"string\", 7, \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_string_string_string_string_string_string_int64(void)\n{\n    return searpc_compute_signature (\"string\", 8, \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int64\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_string_string_string_string_string_string_string_string(void)\n{\n    return searpc_compute_signature (\"string\", 9, \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_int_string_string_string_string_string_string_string_int_string(void)\n{\n    return searpc_compute_signature (\"string\", 11, \"string\", \"int\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_int_string_int_int(void)\n{\n    return searpc_compute_signature (\"string\", 5, \"string\", \"int\", \"string\", \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_string__string_int_string_string_string(void)\n{\n    return searpc_compute_signature (\"string\", 5, \"string\", \"int\", \"string\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__void(void)\n{\n    return searpc_compute_signature (\"objlist\", 0);\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__int(void)\n{\n    return searpc_compute_signature (\"objlist\", 1, \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__int_int(void)\n{\n    return searpc_compute_signature (\"objlist\", 2, \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__int_string(void)\n{\n    return searpc_compute_signature (\"objlist\", 2, \"int\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__int_int_int(void)\n{\n    return searpc_compute_signature (\"objlist\", 3, \"int\", \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__string(void)\n{\n    return searpc_compute_signature (\"objlist\", 1, \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__string_int(void)\n{\n    return searpc_compute_signature (\"objlist\", 2, \"string\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__string_int_int(void)\n{\n    return searpc_compute_signature (\"objlist\", 3, \"string\", \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__string_int_string(void)\n{\n    return searpc_compute_signature (\"objlist\", 3, \"string\", \"int\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__string_string(void)\n{\n    return searpc_compute_signature (\"objlist\", 2, \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__string_string_string(void)\n{\n    return searpc_compute_signature (\"objlist\", 3, \"string\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__string_string_int(void)\n{\n    return searpc_compute_signature (\"objlist\", 3, \"string\", \"string\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__string_string_string_int(void)\n{\n    return searpc_compute_signature (\"objlist\", 4, \"string\", \"string\", \"string\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__string_string_int_int(void)\n{\n    return searpc_compute_signature (\"objlist\", 4, \"string\", \"string\", \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__string_string_int_int_int(void)\n{\n    return searpc_compute_signature (\"objlist\", 5, \"string\", \"string\", \"int\", \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__int_string_string_int_int(void)\n{\n    return searpc_compute_signature (\"objlist\", 5, \"int\", \"string\", \"string\", \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__string_int_string_string_string(void)\n{\n    return searpc_compute_signature (\"objlist\", 5, \"string\", \"int\", \"string\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__string_int_string_int_int(void)\n{\n    return searpc_compute_signature (\"objlist\", 5, \"string\", \"int\", \"string\", \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__string_int_string_string_int(void)\n{\n    return searpc_compute_signature (\"objlist\", 5, \"string\", \"int\", \"string\", \"string\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_objlist__string_string_string_string_int_int(void)\n{\n    return searpc_compute_signature (\"objlist\", 6, \"string\", \"string\", \"string\", \"string\", \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_object__int(void)\n{\n    return searpc_compute_signature (\"object\", 1, \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_object__string(void)\n{\n    return searpc_compute_signature (\"object\", 1, \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_object__string_string(void)\n{\n    return searpc_compute_signature (\"object\", 2, \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_object__string_string_string(void)\n{\n    return searpc_compute_signature (\"object\", 3, \"string\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_object__string_int_string(void)\n{\n    return searpc_compute_signature (\"object\", 3, \"string\", \"int\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_object__int_string_string(void)\n{\n    return searpc_compute_signature (\"object\", 3, \"int\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_object__int_string_string_string_string(void)\n{\n    return searpc_compute_signature (\"object\", 5, \"int\", \"string\", \"string\", \"string\", \"string\");\n}\n\n\ninline static gchar *\nsearpc_signature_object__string_string_string_string_string_string_string_int_int(void)\n{\n    return searpc_compute_signature (\"object\", 9, \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_object__string_string_string_string_string_string_int_string_int_int(void)\n{\n    return searpc_compute_signature (\"object\", 10, \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int\", \"string\", \"int\", \"int\");\n}\n\n\ninline static gchar *\nsearpc_signature_json__void(void)\n{\n    return searpc_compute_signature (\"json\", 0);\n}\n\n"
  },
  {
    "path": "lib/task.c",
    "content": "/* task.c generated by valac 0.56.7, the Vala compiler\n * generated from task.vala, do not modify */\n\n#include <glib-object.h>\n#include <stdlib.h>\n#include <string.h>\n#include <glib.h>\n\n#if !defined(VALA_EXTERN)\n#if defined(_MSC_VER)\n#define VALA_EXTERN __declspec(dllexport) extern\n#elif __GNUC__ >= 4\n#define VALA_EXTERN __attribute__((visibility(\"default\"))) extern\n#else\n#define VALA_EXTERN extern\n#endif\n#endif\n\n#define SEAFILE_TYPE_TASK (seafile_task_get_type ())\n#define SEAFILE_TASK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_TASK, SeafileTask))\n#define SEAFILE_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_TASK, SeafileTaskClass))\n#define SEAFILE_IS_TASK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_TASK))\n#define SEAFILE_IS_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_TASK))\n#define SEAFILE_TASK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_TASK, SeafileTaskClass))\n\ntypedef struct _SeafileTask SeafileTask;\ntypedef struct _SeafileTaskClass SeafileTaskClass;\ntypedef struct _SeafileTaskPrivate SeafileTaskPrivate;\nenum  {\n\tSEAFILE_TASK_0_PROPERTY,\n\tSEAFILE_TASK_TTYPE_PROPERTY,\n\tSEAFILE_TASK_REPO_ID_PROPERTY,\n\tSEAFILE_TASK_STATE_PROPERTY,\n\tSEAFILE_TASK_RT_STATE_PROPERTY,\n\tSEAFILE_TASK_BLOCK_TOTAL_PROPERTY,\n\tSEAFILE_TASK_BLOCK_DONE_PROPERTY,\n\tSEAFILE_TASK_FS_OBJECTS_TOTAL_PROPERTY,\n\tSEAFILE_TASK_FS_OBJECTS_DONE_PROPERTY,\n\tSEAFILE_TASK_RATE_PROPERTY,\n\tSEAFILE_TASK_NUM_PROPERTIES\n};\nstatic GParamSpec* seafile_task_properties[SEAFILE_TASK_NUM_PROPERTIES];\n#define _g_free0(var) (var = (g_free (var), NULL))\n\n#define SEAFILE_TYPE_CLONE_TASK (seafile_clone_task_get_type ())\n#define SEAFILE_CLONE_TASK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_CLONE_TASK, SeafileCloneTask))\n#define SEAFILE_CLONE_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_CLONE_TASK, SeafileCloneTaskClass))\n#define SEAFILE_IS_CLONE_TASK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_CLONE_TASK))\n#define SEAFILE_IS_CLONE_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_CLONE_TASK))\n#define SEAFILE_CLONE_TASK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_CLONE_TASK, SeafileCloneTaskClass))\n\ntypedef struct _SeafileCloneTask SeafileCloneTask;\ntypedef struct _SeafileCloneTaskClass SeafileCloneTaskClass;\ntypedef struct _SeafileCloneTaskPrivate SeafileCloneTaskPrivate;\nenum  {\n\tSEAFILE_CLONE_TASK_0_PROPERTY,\n\tSEAFILE_CLONE_TASK_STATE_PROPERTY,\n\tSEAFILE_CLONE_TASK_ERROR_PROPERTY,\n\tSEAFILE_CLONE_TASK_REPO_ID_PROPERTY,\n\tSEAFILE_CLONE_TASK_REPO_NAME_PROPERTY,\n\tSEAFILE_CLONE_TASK_WORKTREE_PROPERTY,\n\tSEAFILE_CLONE_TASK_NUM_PROPERTIES\n};\nstatic GParamSpec* seafile_clone_task_properties[SEAFILE_CLONE_TASK_NUM_PROPERTIES];\n\nstruct _SeafileTask {\n\tGObject parent_instance;\n\tSeafileTaskPrivate * priv;\n};\n\nstruct _SeafileTaskClass {\n\tGObjectClass parent_class;\n};\n\nstruct _SeafileTaskPrivate {\n\tgchar* _ttype;\n\tgchar* _repo_id;\n\tgchar* _state;\n\tgchar* _rt_state;\n\tgint64 _block_total;\n\tgint64 _block_done;\n\tgint _fs_objects_total;\n\tgint _fs_objects_done;\n\tgint _rate;\n};\n\nstruct _SeafileCloneTask {\n\tGObject parent_instance;\n\tSeafileCloneTaskPrivate * priv;\n};\n\nstruct _SeafileCloneTaskClass {\n\tGObjectClass parent_class;\n};\n\nstruct _SeafileCloneTaskPrivate {\n\tgchar* _state;\n\tgint _error;\n\tgchar* _repo_id;\n\tgchar* _repo_name;\n\tgchar* _worktree;\n};\n\nstatic gint SeafileTask_private_offset;\nstatic gpointer seafile_task_parent_class = NULL;\nstatic gint SeafileCloneTask_private_offset;\nstatic gpointer seafile_clone_task_parent_class = NULL;\n\nVALA_EXTERN GType seafile_task_get_type (void) G_GNUC_CONST ;\nG_DEFINE_AUTOPTR_CLEANUP_FUNC (SeafileTask, g_object_unref)\nVALA_EXTERN SeafileTask* seafile_task_new (void);\nVALA_EXTERN SeafileTask* seafile_task_construct (GType object_type);\nVALA_EXTERN const gchar* seafile_task_get_ttype (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_ttype (SeafileTask* self,\n                             const gchar* value);\nVALA_EXTERN const gchar* seafile_task_get_repo_id (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_repo_id (SeafileTask* self,\n                               const gchar* value);\nVALA_EXTERN const gchar* seafile_task_get_state (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_state (SeafileTask* self,\n                             const gchar* value);\nVALA_EXTERN const gchar* seafile_task_get_rt_state (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_rt_state (SeafileTask* self,\n                                const gchar* value);\nVALA_EXTERN gint64 seafile_task_get_block_total (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_block_total (SeafileTask* self,\n                                   gint64 value);\nVALA_EXTERN gint64 seafile_task_get_block_done (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_block_done (SeafileTask* self,\n                                  gint64 value);\nVALA_EXTERN gint seafile_task_get_fs_objects_total (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_fs_objects_total (SeafileTask* self,\n                                        gint value);\nVALA_EXTERN gint seafile_task_get_fs_objects_done (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_fs_objects_done (SeafileTask* self,\n                                       gint value);\nVALA_EXTERN gint seafile_task_get_rate (SeafileTask* self);\nVALA_EXTERN void seafile_task_set_rate (SeafileTask* self,\n                            gint value);\nstatic void seafile_task_finalize (GObject * obj);\nstatic GType seafile_task_get_type_once (void);\nstatic void _vala_seafile_task_get_property (GObject * object,\n                                      guint property_id,\n                                      GValue * value,\n                                      GParamSpec * pspec);\nstatic void _vala_seafile_task_set_property (GObject * object,\n                                      guint property_id,\n                                      const GValue * value,\n                                      GParamSpec * pspec);\nVALA_EXTERN GType seafile_clone_task_get_type (void) G_GNUC_CONST ;\nG_DEFINE_AUTOPTR_CLEANUP_FUNC (SeafileCloneTask, g_object_unref)\nVALA_EXTERN SeafileCloneTask* seafile_clone_task_new (void);\nVALA_EXTERN SeafileCloneTask* seafile_clone_task_construct (GType object_type);\nVALA_EXTERN const gchar* seafile_clone_task_get_state (SeafileCloneTask* self);\nVALA_EXTERN void seafile_clone_task_set_state (SeafileCloneTask* self,\n                                   const gchar* value);\nVALA_EXTERN gint seafile_clone_task_get_error (SeafileCloneTask* self);\nVALA_EXTERN void seafile_clone_task_set_error (SeafileCloneTask* self,\n                                   gint value);\nVALA_EXTERN const gchar* seafile_clone_task_get_repo_id (SeafileCloneTask* self);\nVALA_EXTERN void seafile_clone_task_set_repo_id (SeafileCloneTask* self,\n                                     const gchar* value);\nVALA_EXTERN const gchar* seafile_clone_task_get_repo_name (SeafileCloneTask* self);\nVALA_EXTERN void seafile_clone_task_set_repo_name (SeafileCloneTask* self,\n                                       const gchar* value);\nVALA_EXTERN const gchar* seafile_clone_task_get_worktree (SeafileCloneTask* self);\nVALA_EXTERN void seafile_clone_task_set_worktree (SeafileCloneTask* self,\n                                      const gchar* value);\nstatic void seafile_clone_task_finalize (GObject * obj);\nstatic GType seafile_clone_task_get_type_once (void);\nstatic void _vala_seafile_clone_task_get_property (GObject * object,\n                                            guint property_id,\n                                            GValue * value,\n                                            GParamSpec * pspec);\nstatic void _vala_seafile_clone_task_set_property (GObject * object,\n                                            guint property_id,\n                                            const GValue * value,\n                                            GParamSpec * pspec);\n\nstatic inline gpointer\nseafile_task_get_instance_private (SeafileTask* self)\n{\n\treturn G_STRUCT_MEMBER_P (self, SeafileTask_private_offset);\n}\n\nSeafileTask*\nseafile_task_construct (GType object_type)\n{\n\tSeafileTask * self = NULL;\n\tself = (SeafileTask*) g_object_new (object_type, NULL);\n\treturn self;\n}\n\nSeafileTask*\nseafile_task_new (void)\n{\n\treturn seafile_task_construct (SEAFILE_TYPE_TASK);\n}\n\nconst gchar*\nseafile_task_get_ttype (SeafileTask* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_ttype;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_task_set_ttype (SeafileTask* self,\n                        const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_task_get_ttype (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_ttype);\n\t\tself->priv->_ttype = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_task_properties[SEAFILE_TASK_TTYPE_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_task_get_repo_id (SeafileTask* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_repo_id;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_task_set_repo_id (SeafileTask* self,\n                          const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_task_get_repo_id (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_repo_id);\n\t\tself->priv->_repo_id = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_task_properties[SEAFILE_TASK_REPO_ID_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_task_get_state (SeafileTask* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_state;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_task_set_state (SeafileTask* self,\n                        const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_task_get_state (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_state);\n\t\tself->priv->_state = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_task_properties[SEAFILE_TASK_STATE_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_task_get_rt_state (SeafileTask* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_rt_state;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_task_set_rt_state (SeafileTask* self,\n                           const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_task_get_rt_state (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_rt_state);\n\t\tself->priv->_rt_state = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_task_properties[SEAFILE_TASK_RT_STATE_PROPERTY]);\n\t}\n}\n\ngint64\nseafile_task_get_block_total (SeafileTask* self)\n{\n\tgint64 result;\n\tg_return_val_if_fail (self != NULL, 0LL);\n\tresult = self->priv->_block_total;\n\treturn result;\n}\n\nvoid\nseafile_task_set_block_total (SeafileTask* self,\n                              gint64 value)\n{\n\tgint64 old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_task_get_block_total (self);\n\tif (old_value != value) {\n\t\tself->priv->_block_total = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_task_properties[SEAFILE_TASK_BLOCK_TOTAL_PROPERTY]);\n\t}\n}\n\ngint64\nseafile_task_get_block_done (SeafileTask* self)\n{\n\tgint64 result;\n\tg_return_val_if_fail (self != NULL, 0LL);\n\tresult = self->priv->_block_done;\n\treturn result;\n}\n\nvoid\nseafile_task_set_block_done (SeafileTask* self,\n                             gint64 value)\n{\n\tgint64 old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_task_get_block_done (self);\n\tif (old_value != value) {\n\t\tself->priv->_block_done = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_task_properties[SEAFILE_TASK_BLOCK_DONE_PROPERTY]);\n\t}\n}\n\ngint\nseafile_task_get_fs_objects_total (SeafileTask* self)\n{\n\tgint result;\n\tg_return_val_if_fail (self != NULL, 0);\n\tresult = self->priv->_fs_objects_total;\n\treturn result;\n}\n\nvoid\nseafile_task_set_fs_objects_total (SeafileTask* self,\n                                   gint value)\n{\n\tgint old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_task_get_fs_objects_total (self);\n\tif (old_value != value) {\n\t\tself->priv->_fs_objects_total = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_task_properties[SEAFILE_TASK_FS_OBJECTS_TOTAL_PROPERTY]);\n\t}\n}\n\ngint\nseafile_task_get_fs_objects_done (SeafileTask* self)\n{\n\tgint result;\n\tg_return_val_if_fail (self != NULL, 0);\n\tresult = self->priv->_fs_objects_done;\n\treturn result;\n}\n\nvoid\nseafile_task_set_fs_objects_done (SeafileTask* self,\n                                  gint value)\n{\n\tgint old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_task_get_fs_objects_done (self);\n\tif (old_value != value) {\n\t\tself->priv->_fs_objects_done = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_task_properties[SEAFILE_TASK_FS_OBJECTS_DONE_PROPERTY]);\n\t}\n}\n\ngint\nseafile_task_get_rate (SeafileTask* self)\n{\n\tgint result;\n\tg_return_val_if_fail (self != NULL, 0);\n\tresult = self->priv->_rate;\n\treturn result;\n}\n\nvoid\nseafile_task_set_rate (SeafileTask* self,\n                       gint value)\n{\n\tgint old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_task_get_rate (self);\n\tif (old_value != value) {\n\t\tself->priv->_rate = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_task_properties[SEAFILE_TASK_RATE_PROPERTY]);\n\t}\n}\n\nstatic void\nseafile_task_class_init (SeafileTaskClass * klass,\n                         gpointer klass_data)\n{\n\tseafile_task_parent_class = g_type_class_peek_parent (klass);\n\tg_type_class_adjust_private_offset (klass, &SeafileTask_private_offset);\n\tG_OBJECT_CLASS (klass)->get_property = _vala_seafile_task_get_property;\n\tG_OBJECT_CLASS (klass)->set_property = _vala_seafile_task_set_property;\n\tG_OBJECT_CLASS (klass)->finalize = seafile_task_finalize;\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_TASK_TTYPE_PROPERTY, seafile_task_properties[SEAFILE_TASK_TTYPE_PROPERTY] = g_param_spec_string (\"ttype\", \"ttype\", \"ttype\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_TASK_REPO_ID_PROPERTY, seafile_task_properties[SEAFILE_TASK_REPO_ID_PROPERTY] = g_param_spec_string (\"repo-id\", \"repo-id\", \"repo-id\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_TASK_STATE_PROPERTY, seafile_task_properties[SEAFILE_TASK_STATE_PROPERTY] = g_param_spec_string (\"state\", \"state\", \"state\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_TASK_RT_STATE_PROPERTY, seafile_task_properties[SEAFILE_TASK_RT_STATE_PROPERTY] = g_param_spec_string (\"rt-state\", \"rt-state\", \"rt-state\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_TASK_BLOCK_TOTAL_PROPERTY, seafile_task_properties[SEAFILE_TASK_BLOCK_TOTAL_PROPERTY] = g_param_spec_int64 (\"block-total\", \"block-total\", \"block-total\", G_MININT64, G_MAXINT64, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_TASK_BLOCK_DONE_PROPERTY, seafile_task_properties[SEAFILE_TASK_BLOCK_DONE_PROPERTY] = g_param_spec_int64 (\"block-done\", \"block-done\", \"block-done\", G_MININT64, G_MAXINT64, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_TASK_FS_OBJECTS_TOTAL_PROPERTY, seafile_task_properties[SEAFILE_TASK_FS_OBJECTS_TOTAL_PROPERTY] = g_param_spec_int (\"fs-objects-total\", \"fs-objects-total\", \"fs-objects-total\", G_MININT, G_MAXINT, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_TASK_FS_OBJECTS_DONE_PROPERTY, seafile_task_properties[SEAFILE_TASK_FS_OBJECTS_DONE_PROPERTY] = g_param_spec_int (\"fs-objects-done\", \"fs-objects-done\", \"fs-objects-done\", G_MININT, G_MAXINT, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_TASK_RATE_PROPERTY, seafile_task_properties[SEAFILE_TASK_RATE_PROPERTY] = g_param_spec_int (\"rate\", \"rate\", \"rate\", G_MININT, G_MAXINT, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n}\n\nstatic void\nseafile_task_instance_init (SeafileTask * self,\n                            gpointer klass)\n{\n\tself->priv = seafile_task_get_instance_private (self);\n}\n\nstatic void\nseafile_task_finalize (GObject * obj)\n{\n\tSeafileTask * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (obj, SEAFILE_TYPE_TASK, SeafileTask);\n\t_g_free0 (self->priv->_ttype);\n\t_g_free0 (self->priv->_repo_id);\n\t_g_free0 (self->priv->_state);\n\t_g_free0 (self->priv->_rt_state);\n\tG_OBJECT_CLASS (seafile_task_parent_class)->finalize (obj);\n}\n\nstatic GType\nseafile_task_get_type_once (void)\n{\n\tstatic const GTypeInfo g_define_type_info = { sizeof (SeafileTaskClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) seafile_task_class_init, (GClassFinalizeFunc) NULL, NULL, sizeof (SeafileTask), 0, (GInstanceInitFunc) seafile_task_instance_init, NULL };\n\tGType seafile_task_type_id;\n\tseafile_task_type_id = g_type_register_static (G_TYPE_OBJECT, \"SeafileTask\", &g_define_type_info, 0);\n\tSeafileTask_private_offset = g_type_add_instance_private (seafile_task_type_id, sizeof (SeafileTaskPrivate));\n\treturn seafile_task_type_id;\n}\n\nGType\nseafile_task_get_type (void)\n{\n\tstatic volatile gsize seafile_task_type_id__once = 0;\n\tif (g_once_init_enter (&seafile_task_type_id__once)) {\n\t\tGType seafile_task_type_id;\n\t\tseafile_task_type_id = seafile_task_get_type_once ();\n\t\tg_once_init_leave (&seafile_task_type_id__once, seafile_task_type_id);\n\t}\n\treturn seafile_task_type_id__once;\n}\n\nstatic void\n_vala_seafile_task_get_property (GObject * object,\n                                 guint property_id,\n                                 GValue * value,\n                                 GParamSpec * pspec)\n{\n\tSeafileTask * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (object, SEAFILE_TYPE_TASK, SeafileTask);\n\tswitch (property_id) {\n\t\tcase SEAFILE_TASK_TTYPE_PROPERTY:\n\t\tg_value_set_string (value, seafile_task_get_ttype (self));\n\t\tbreak;\n\t\tcase SEAFILE_TASK_REPO_ID_PROPERTY:\n\t\tg_value_set_string (value, seafile_task_get_repo_id (self));\n\t\tbreak;\n\t\tcase SEAFILE_TASK_STATE_PROPERTY:\n\t\tg_value_set_string (value, seafile_task_get_state (self));\n\t\tbreak;\n\t\tcase SEAFILE_TASK_RT_STATE_PROPERTY:\n\t\tg_value_set_string (value, seafile_task_get_rt_state (self));\n\t\tbreak;\n\t\tcase SEAFILE_TASK_BLOCK_TOTAL_PROPERTY:\n\t\tg_value_set_int64 (value, seafile_task_get_block_total (self));\n\t\tbreak;\n\t\tcase SEAFILE_TASK_BLOCK_DONE_PROPERTY:\n\t\tg_value_set_int64 (value, seafile_task_get_block_done (self));\n\t\tbreak;\n\t\tcase SEAFILE_TASK_FS_OBJECTS_TOTAL_PROPERTY:\n\t\tg_value_set_int (value, seafile_task_get_fs_objects_total (self));\n\t\tbreak;\n\t\tcase SEAFILE_TASK_FS_OBJECTS_DONE_PROPERTY:\n\t\tg_value_set_int (value, seafile_task_get_fs_objects_done (self));\n\t\tbreak;\n\t\tcase SEAFILE_TASK_RATE_PROPERTY:\n\t\tg_value_set_int (value, seafile_task_get_rate (self));\n\t\tbreak;\n\t\tdefault:\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);\n\t\tbreak;\n\t}\n}\n\nstatic void\n_vala_seafile_task_set_property (GObject * object,\n                                 guint property_id,\n                                 const GValue * value,\n                                 GParamSpec * pspec)\n{\n\tSeafileTask * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (object, SEAFILE_TYPE_TASK, SeafileTask);\n\tswitch (property_id) {\n\t\tcase SEAFILE_TASK_TTYPE_PROPERTY:\n\t\tseafile_task_set_ttype (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_TASK_REPO_ID_PROPERTY:\n\t\tseafile_task_set_repo_id (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_TASK_STATE_PROPERTY:\n\t\tseafile_task_set_state (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_TASK_RT_STATE_PROPERTY:\n\t\tseafile_task_set_rt_state (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_TASK_BLOCK_TOTAL_PROPERTY:\n\t\tseafile_task_set_block_total (self, g_value_get_int64 (value));\n\t\tbreak;\n\t\tcase SEAFILE_TASK_BLOCK_DONE_PROPERTY:\n\t\tseafile_task_set_block_done (self, g_value_get_int64 (value));\n\t\tbreak;\n\t\tcase SEAFILE_TASK_FS_OBJECTS_TOTAL_PROPERTY:\n\t\tseafile_task_set_fs_objects_total (self, g_value_get_int (value));\n\t\tbreak;\n\t\tcase SEAFILE_TASK_FS_OBJECTS_DONE_PROPERTY:\n\t\tseafile_task_set_fs_objects_done (self, g_value_get_int (value));\n\t\tbreak;\n\t\tcase SEAFILE_TASK_RATE_PROPERTY:\n\t\tseafile_task_set_rate (self, g_value_get_int (value));\n\t\tbreak;\n\t\tdefault:\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);\n\t\tbreak;\n\t}\n}\n\nstatic inline gpointer\nseafile_clone_task_get_instance_private (SeafileCloneTask* self)\n{\n\treturn G_STRUCT_MEMBER_P (self, SeafileCloneTask_private_offset);\n}\n\nSeafileCloneTask*\nseafile_clone_task_construct (GType object_type)\n{\n\tSeafileCloneTask * self = NULL;\n\tself = (SeafileCloneTask*) g_object_new (object_type, NULL);\n\treturn self;\n}\n\nSeafileCloneTask*\nseafile_clone_task_new (void)\n{\n\treturn seafile_clone_task_construct (SEAFILE_TYPE_CLONE_TASK);\n}\n\nconst gchar*\nseafile_clone_task_get_state (SeafileCloneTask* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_state;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_clone_task_set_state (SeafileCloneTask* self,\n                              const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_clone_task_get_state (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_state);\n\t\tself->priv->_state = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_clone_task_properties[SEAFILE_CLONE_TASK_STATE_PROPERTY]);\n\t}\n}\n\ngint\nseafile_clone_task_get_error (SeafileCloneTask* self)\n{\n\tgint result;\n\tg_return_val_if_fail (self != NULL, 0);\n\tresult = self->priv->_error;\n\treturn result;\n}\n\nvoid\nseafile_clone_task_set_error (SeafileCloneTask* self,\n                              gint value)\n{\n\tgint old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_clone_task_get_error (self);\n\tif (old_value != value) {\n\t\tself->priv->_error = value;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_clone_task_properties[SEAFILE_CLONE_TASK_ERROR_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_clone_task_get_repo_id (SeafileCloneTask* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_repo_id;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_clone_task_set_repo_id (SeafileCloneTask* self,\n                                const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_clone_task_get_repo_id (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_repo_id);\n\t\tself->priv->_repo_id = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_clone_task_properties[SEAFILE_CLONE_TASK_REPO_ID_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_clone_task_get_repo_name (SeafileCloneTask* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_repo_name;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_clone_task_set_repo_name (SeafileCloneTask* self,\n                                  const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_clone_task_get_repo_name (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_repo_name);\n\t\tself->priv->_repo_name = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_clone_task_properties[SEAFILE_CLONE_TASK_REPO_NAME_PROPERTY]);\n\t}\n}\n\nconst gchar*\nseafile_clone_task_get_worktree (SeafileCloneTask* self)\n{\n\tconst gchar* result;\n\tconst gchar* _tmp0_;\n\tg_return_val_if_fail (self != NULL, NULL);\n\t_tmp0_ = self->priv->_worktree;\n\tresult = _tmp0_;\n\treturn result;\n}\n\nvoid\nseafile_clone_task_set_worktree (SeafileCloneTask* self,\n                                 const gchar* value)\n{\n\tgchar* old_value;\n\tg_return_if_fail (self != NULL);\n\told_value = seafile_clone_task_get_worktree (self);\n\tif (g_strcmp0 (value, old_value) != 0) {\n\t\tgchar* _tmp0_;\n\t\t_tmp0_ = g_strdup (value);\n\t\t_g_free0 (self->priv->_worktree);\n\t\tself->priv->_worktree = _tmp0_;\n\t\tg_object_notify_by_pspec ((GObject *) self, seafile_clone_task_properties[SEAFILE_CLONE_TASK_WORKTREE_PROPERTY]);\n\t}\n}\n\nstatic void\nseafile_clone_task_class_init (SeafileCloneTaskClass * klass,\n                               gpointer klass_data)\n{\n\tseafile_clone_task_parent_class = g_type_class_peek_parent (klass);\n\tg_type_class_adjust_private_offset (klass, &SeafileCloneTask_private_offset);\n\tG_OBJECT_CLASS (klass)->get_property = _vala_seafile_clone_task_get_property;\n\tG_OBJECT_CLASS (klass)->set_property = _vala_seafile_clone_task_set_property;\n\tG_OBJECT_CLASS (klass)->finalize = seafile_clone_task_finalize;\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_CLONE_TASK_STATE_PROPERTY, seafile_clone_task_properties[SEAFILE_CLONE_TASK_STATE_PROPERTY] = g_param_spec_string (\"state\", \"state\", \"state\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_CLONE_TASK_ERROR_PROPERTY, seafile_clone_task_properties[SEAFILE_CLONE_TASK_ERROR_PROPERTY] = g_param_spec_int (\"error\", \"error\", \"error\", G_MININT, G_MAXINT, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_CLONE_TASK_REPO_ID_PROPERTY, seafile_clone_task_properties[SEAFILE_CLONE_TASK_REPO_ID_PROPERTY] = g_param_spec_string (\"repo-id\", \"repo-id\", \"repo-id\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_CLONE_TASK_REPO_NAME_PROPERTY, seafile_clone_task_properties[SEAFILE_CLONE_TASK_REPO_NAME_PROPERTY] = g_param_spec_string (\"repo-name\", \"repo-name\", \"repo-name\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n\tg_object_class_install_property (G_OBJECT_CLASS (klass), SEAFILE_CLONE_TASK_WORKTREE_PROPERTY, seafile_clone_task_properties[SEAFILE_CLONE_TASK_WORKTREE_PROPERTY] = g_param_spec_string (\"worktree\", \"worktree\", \"worktree\", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_WRITABLE));\n}\n\nstatic void\nseafile_clone_task_instance_init (SeafileCloneTask * self,\n                                  gpointer klass)\n{\n\tself->priv = seafile_clone_task_get_instance_private (self);\n}\n\nstatic void\nseafile_clone_task_finalize (GObject * obj)\n{\n\tSeafileCloneTask * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (obj, SEAFILE_TYPE_CLONE_TASK, SeafileCloneTask);\n\t_g_free0 (self->priv->_state);\n\t_g_free0 (self->priv->_repo_id);\n\t_g_free0 (self->priv->_repo_name);\n\t_g_free0 (self->priv->_worktree);\n\tG_OBJECT_CLASS (seafile_clone_task_parent_class)->finalize (obj);\n}\n\nstatic GType\nseafile_clone_task_get_type_once (void)\n{\n\tstatic const GTypeInfo g_define_type_info = { sizeof (SeafileCloneTaskClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) seafile_clone_task_class_init, (GClassFinalizeFunc) NULL, NULL, sizeof (SeafileCloneTask), 0, (GInstanceInitFunc) seafile_clone_task_instance_init, NULL };\n\tGType seafile_clone_task_type_id;\n\tseafile_clone_task_type_id = g_type_register_static (G_TYPE_OBJECT, \"SeafileCloneTask\", &g_define_type_info, 0);\n\tSeafileCloneTask_private_offset = g_type_add_instance_private (seafile_clone_task_type_id, sizeof (SeafileCloneTaskPrivate));\n\treturn seafile_clone_task_type_id;\n}\n\nGType\nseafile_clone_task_get_type (void)\n{\n\tstatic volatile gsize seafile_clone_task_type_id__once = 0;\n\tif (g_once_init_enter (&seafile_clone_task_type_id__once)) {\n\t\tGType seafile_clone_task_type_id;\n\t\tseafile_clone_task_type_id = seafile_clone_task_get_type_once ();\n\t\tg_once_init_leave (&seafile_clone_task_type_id__once, seafile_clone_task_type_id);\n\t}\n\treturn seafile_clone_task_type_id__once;\n}\n\nstatic void\n_vala_seafile_clone_task_get_property (GObject * object,\n                                       guint property_id,\n                                       GValue * value,\n                                       GParamSpec * pspec)\n{\n\tSeafileCloneTask * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (object, SEAFILE_TYPE_CLONE_TASK, SeafileCloneTask);\n\tswitch (property_id) {\n\t\tcase SEAFILE_CLONE_TASK_STATE_PROPERTY:\n\t\tg_value_set_string (value, seafile_clone_task_get_state (self));\n\t\tbreak;\n\t\tcase SEAFILE_CLONE_TASK_ERROR_PROPERTY:\n\t\tg_value_set_int (value, seafile_clone_task_get_error (self));\n\t\tbreak;\n\t\tcase SEAFILE_CLONE_TASK_REPO_ID_PROPERTY:\n\t\tg_value_set_string (value, seafile_clone_task_get_repo_id (self));\n\t\tbreak;\n\t\tcase SEAFILE_CLONE_TASK_REPO_NAME_PROPERTY:\n\t\tg_value_set_string (value, seafile_clone_task_get_repo_name (self));\n\t\tbreak;\n\t\tcase SEAFILE_CLONE_TASK_WORKTREE_PROPERTY:\n\t\tg_value_set_string (value, seafile_clone_task_get_worktree (self));\n\t\tbreak;\n\t\tdefault:\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);\n\t\tbreak;\n\t}\n}\n\nstatic void\n_vala_seafile_clone_task_set_property (GObject * object,\n                                       guint property_id,\n                                       const GValue * value,\n                                       GParamSpec * pspec)\n{\n\tSeafileCloneTask * self;\n\tself = G_TYPE_CHECK_INSTANCE_CAST (object, SEAFILE_TYPE_CLONE_TASK, SeafileCloneTask);\n\tswitch (property_id) {\n\t\tcase SEAFILE_CLONE_TASK_STATE_PROPERTY:\n\t\tseafile_clone_task_set_state (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_CLONE_TASK_ERROR_PROPERTY:\n\t\tseafile_clone_task_set_error (self, g_value_get_int (value));\n\t\tbreak;\n\t\tcase SEAFILE_CLONE_TASK_REPO_ID_PROPERTY:\n\t\tseafile_clone_task_set_repo_id (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_CLONE_TASK_REPO_NAME_PROPERTY:\n\t\tseafile_clone_task_set_repo_name (self, g_value_get_string (value));\n\t\tbreak;\n\t\tcase SEAFILE_CLONE_TASK_WORKTREE_PROPERTY:\n\t\tseafile_clone_task_set_worktree (self, g_value_get_string (value));\n\t\tbreak;\n\t\tdefault:\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);\n\t\tbreak;\n\t}\n}\n\n"
  },
  {
    "path": "lib/task.vala",
    "content": "namespace Seafile {\n\npublic class Task : Object {\n\n    public string ttype { get; set; }\n\n\tpublic string repo_id { get; set; }\n\n\tpublic string state { get; set; }\n\n\tpublic string rt_state { get; set; }\n\n    public int64 block_total { get; set; }\n    public int64 block_done { get; set; } // the number of blocks sent or received\n\n    public int fs_objects_total { get; set; }\n    public int fs_objects_done { get; set; }\n\n\tpublic int rate { get; set; }\n}\n\npublic class CloneTask : Object {\n       public string state { get; set; }\n       public int error { get; set; }\n       public string repo_id { get; set; }\n       public string repo_name { get; set; }\n       public string worktree { get; set; }\n}\n\n} // namespace\n"
  },
  {
    "path": "lib/utils.c",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#include \"common.h\"\n\n#ifdef WIN32\n#ifndef _WIN32_WINNT\n#define _WIN32_WINNT 0x500\n#endif\n#endif\n\n#include \"utils.h\"\n\n#ifdef WIN32\n\n#include <winsock2.h>\n#include <windows.h>\n#include <ws2tcpip.h>\n#include <Rpc.h>\n#include <shlobj.h>\n#include <psapi.h>\n#include <openssl/applink.c>\n\n#else\n#include <arpa/inet.h>\n#endif\n\n#ifndef WIN32\n#include <config.h>\n#include <pwd.h>\n#include <uuid/uuid.h>\n#endif\n\n#ifndef WIN32\n#include <unistd.h>\n#include <dirent.h>\n#endif\n#include <sys/types.h>\n#include <fcntl.h>\n#include <sys/stat.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdarg.h>\n#include <string.h>\n\n#include <glib.h>\n#include <glib/gstdio.h>\n#include <searpc-utils.h>\n\n#include <jansson.h>\n\n#ifndef WIN32\n#include <utime.h>\n#endif\n\n#if defined __linux__ || defined __APPLE__\n#include <sys/xattr.h>\n#endif\n\n#if defined __FreeBSD__ || defined __NetBSD__\n#include <sys/extattr.h>\n#endif\n\n#include <zlib.h>\n\n#include \"log.h\"\n\n\n\nvoid\nrawdata_to_hex (const unsigned char *rawdata, char *hex_str, int n_bytes)\n{\n    static const char hex[] = \"0123456789abcdef\";\n    int i;\n\n    for (i = 0; i < n_bytes; i++) {\n        unsigned int val = *rawdata++;\n        *hex_str++ = hex[val >> 4];\n        *hex_str++ = hex[val & 0xf];\n    }\n    *hex_str = '\\0';\n}\n\nstatic unsigned hexval(char c)\n{\n    if (c >= '0' && c <= '9')\n        return c - '0';\n    if (c >= 'a' && c <= 'f')\n        return c - 'a' + 10;\n    if (c >= 'A' && c <= 'F')\n        return c - 'A' + 10;\n    return ~0;\n}\n\nint\nhex_to_rawdata (const char *hex_str, unsigned char *rawdata, int n_bytes)\n{\n    int i;\n    for (i = 0; i < n_bytes; i++) {\n        unsigned int val = (hexval(hex_str[0]) << 4) | hexval(hex_str[1]);\n        if (val & ~0xff)\n            return -1;\n        *rawdata++ = val;\n        hex_str += 2;\n    }\n    return 0;\n}\n\nsize_t\nccnet_strlcpy (char *dest, const char *src, size_t size)\n{\n    size_t ret = strlen(src);\n\n    if (size) {\n        size_t len = (ret >= size) ? size - 1 : ret;\n        memcpy(dest, src, len);\n        dest[len] = '\\0';\n    }\n    return ret;\n}\n\n\nint\ncheckdir (const char *dir)\n{\n    SeafStat st;\n\n#ifdef WIN32\n    /* remove trailing '\\\\' */\n    char *path = g_strdup(dir);\n    char *p = (char *)path + strlen(path) - 1;\n    while (*p == '\\\\' || *p == '/') *p-- = '\\0';\n    if ((seaf_stat(dir, &st) < 0) || !S_ISDIR(st.st_mode)) {\n        g_free (path);\n        return -1;\n    }\n    g_free (path);\n    return 0;\n#else\n    if ((seaf_stat(dir, &st) < 0) || !S_ISDIR(st.st_mode))\n        return -1;\n    return 0;\n#endif\n}\n\nint\ncheckdir_with_mkdir (const char *dir)\n{\n    int ret;\n#ifdef WIN32\n    char *path = g_strdup(dir);\n    char *p = (char *)path + strlen(path) - 1;\n    while (*p == '\\\\' || *p == '/') *p-- = '\\0';\n    ret = g_mkdir_with_parents(path, 0755);\n    g_free (path);\n    return ret;\n#elif defined __APPLE__\n    char *dir_nfd = g_utf8_normalize (dir, -1, G_NORMALIZE_NFD);\n    ret = g_mkdir_with_parents(dir_nfd, 0755);\n    g_free (dir_nfd);\n    return ret;\n#else\n    return g_mkdir_with_parents(dir, 0755);\n#endif\n}\n\nint\nobjstore_mkdir (const char *base)\n{\n    int ret;\n    int i, j, len;\n    static const char hex[] = \"0123456789abcdef\";\n    char subdir[SEAF_PATH_MAX];\n\n    if ( (ret = checkdir_with_mkdir(base)) < 0)\n        return ret;\n\n    len = strlen(base);\n    memcpy(subdir, base, len);\n    subdir[len] = G_DIR_SEPARATOR;\n    subdir[len+3] = '\\0';\n\n    for (i = 0; i < 16; i++) {\n        subdir[len+1] = hex[i];\n        for (j = 0; j < 16; j++) {\n            subdir[len+2] = hex[j];\n            if ( (ret = checkdir_with_mkdir(subdir)) < 0)\n                return ret;\n        }\n    }\n    return 0;\n}\n\nvoid\nobjstore_get_path (char *path, const char *base, const char *obj_id)\n{\n    int len;\n\n    len = strlen(base);\n    memcpy(path, base, len);\n    path[len] = G_DIR_SEPARATOR;\n    path[len+1] = obj_id[0];\n    path[len+2] = obj_id[1];\n    path[len+3] = G_DIR_SEPARATOR;\n    strcpy(path+len+4, obj_id+2);\n}\n\n#ifdef WIN32\n\n/* UNIX epoch expressed in Windows time, the unit is 100 nanoseconds.\n * See http://msdn.microsoft.com/en-us/library/ms724228\n */\n#define UNIX_EPOCH 116444736000000000ULL\n\n__time64_t\nfile_time_to_unix_time (FILETIME *ftime)\n{\n    guint64 win_time, unix_time;\n\n    win_time = (guint64)ftime->dwLowDateTime + (((guint64)ftime->dwHighDateTime)<<32);\n    unix_time = (win_time - UNIX_EPOCH)/10000000;\n\n    return (__time64_t)unix_time;\n}\n\nstatic int\nget_utc_file_time_fd (int fd, __time64_t *mtime, __time64_t *ctime)\n{\n    HANDLE handle;\n    FILETIME write_time, create_time;\n\n    handle = (HANDLE)_get_osfhandle (fd);\n    if (handle == INVALID_HANDLE_VALUE) {\n        g_warning (\"Failed to get handle from fd: %lu.\\n\", GetLastError());\n        return -1;\n    }\n\n    if (!GetFileTime (handle, &create_time, NULL, &write_time)) {\n        g_warning (\"Failed to get file time: %lu.\\n\", GetLastError());\n        return -1;\n    }\n\n    *mtime = file_time_to_unix_time (&write_time);\n    *ctime = file_time_to_unix_time (&create_time);\n\n    return 0;\n}\n\n#define EPOCH_DIFF 11644473600ULL\n\ninline static void\nunix_time_to_file_time (guint64 unix_time, FILETIME *ftime)\n{\n    guint64 win_time;\n\n    win_time = (unix_time + EPOCH_DIFF) * 10000000;\n    ftime->dwLowDateTime = win_time & 0xFFFFFFFF;\n    ftime->dwHighDateTime = (win_time >> 32) & 0xFFFFFFFF;\n}\n\nstatic int\nset_utc_file_time (const char *path, const wchar_t *wpath, guint64 mtime)\n{\n    HANDLE handle;\n    FILETIME write_time;\n\n    handle = CreateFileW (wpath,\n                          GENERIC_WRITE,\n                          FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                          NULL,\n                          OPEN_EXISTING,\n                          FILE_FLAG_BACKUP_SEMANTICS,\n                          NULL);\n    if (handle == INVALID_HANDLE_VALUE) {\n        g_warning (\"Failed to open %s: %lu.\\n\", path, GetLastError());\n        return -1;\n    }\n\n    unix_time_to_file_time (mtime, &write_time);\n\n    if (!SetFileTime (handle, NULL, NULL, &write_time)) {\n        g_warning (\"Failed to set file time for %s: %lu.\\n\", path, GetLastError());\n        CloseHandle (handle);\n        return -1;\n    }\n    CloseHandle (handle);\n\n    return 0;\n}\n\nwchar_t *\nwin32_long_path (const char *path)\n{\n    char *long_path, *p;\n    wchar_t *long_path_w;\n\n    if (strncmp(path, \"//\", 2) == 0)\n        long_path = g_strconcat (\"\\\\\\\\?\\\\UNC\\\\\", path + 2, NULL);\n    else\n        long_path = g_strconcat (\"\\\\\\\\?\\\\\", path, NULL);\n    for (p = long_path; *p != 0; ++p)\n        if (*p == '/')\n            *p = '\\\\';\n\n    long_path_w = g_utf8_to_utf16 (long_path, -1, NULL, NULL, NULL);\n\n    g_free (long_path);\n    return long_path_w;\n}\n\n/* Convert a (possible) 8.3 format path to long path */\nwchar_t *\nwin32_83_path_to_long_path (const char *worktree, const wchar_t *path, int path_len)\n{\n    wchar_t *worktree_w = g_utf8_to_utf16 (worktree, -1, NULL, NULL, NULL);\n    int wt_len;\n    wchar_t *p;\n    wchar_t *fullpath_w = NULL;\n    wchar_t *fullpath_long = NULL;\n    wchar_t *ret = NULL;\n    char *fullpath;\n\n    for (p = worktree_w; *p != L'\\0'; ++p)\n        if (*p == L'/')\n            *p = L'\\\\';\n\n    wt_len = wcslen(worktree_w);\n\n    fullpath_w = g_new0 (wchar_t, wt_len + path_len + 6);\n    wcscpy (fullpath_w, L\"\\\\\\\\?\\\\\");\n    wcscat (fullpath_w, worktree_w);\n    wcscat (fullpath_w, L\"\\\\\");\n    wcsncat (fullpath_w, path, path_len);\n\n    fullpath_long = g_new0 (wchar_t, SEAF_PATH_MAX);\n\n    DWORD n = GetLongPathNameW (fullpath_w, fullpath_long, SEAF_PATH_MAX);\n    if (n == 0) {\n        /* Failed. */\n        fullpath = g_utf16_to_utf8 (fullpath_w, -1, NULL, NULL, NULL);\n        g_free (fullpath);\n\n        goto out;\n    } else if (n > SEAF_PATH_MAX) {\n        /* In this case n is the necessary length for the buf. */\n        g_free (fullpath_long);\n        fullpath_long = g_new0 (wchar_t, n);\n\n        if (GetLongPathNameW (fullpath_w, fullpath_long, n) != (n - 1)) {\n            fullpath = g_utf16_to_utf8 (fullpath_w, -1, NULL, NULL, NULL);\n            g_free (fullpath);\n\n            goto out;\n        }\n    }\n\n    /* Remove \"\\\\?\\worktree\\\" from the beginning. */\n    ret = wcsdup (fullpath_long + wt_len + 5);\n\nout:\n    g_free (worktree_w);\n    g_free (fullpath_w);\n    g_free (fullpath_long);\n\n    return ret;\n}\n\nstatic int\nwindows_error_to_errno (DWORD error)\n{\n    switch (error) {\n    case ERROR_FILE_NOT_FOUND:\n    case ERROR_PATH_NOT_FOUND:\n        return ENOENT;\n    case ERROR_ALREADY_EXISTS:\n        return EEXIST;\n    case ERROR_ACCESS_DENIED:\n    case ERROR_SHARING_VIOLATION:\n        return EACCES;\n    case ERROR_DIR_NOT_EMPTY:\n        return ENOTEMPTY;\n    default:\n        return 0;\n    }\n}\n\n#endif\n\nint\nseaf_stat (const char *path, SeafStat *st)\n{\n#ifdef WIN32\n    wchar_t *wpath = win32_long_path (path);\n    WIN32_FILE_ATTRIBUTE_DATA attrs;\n    int ret = 0;\n\n    if (!GetFileAttributesExW (wpath, GetFileExInfoStandard, &attrs)) {\n        ret = -1;\n        errno = windows_error_to_errno (GetLastError());\n        goto out;\n    }\n\n    memset (st, 0, sizeof(SeafStat));\n\n    if (attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)\n        st->st_mode = S_IFDIR;\n    else\n        st->st_mode = S_IFREG;\n\n    st->st_atime = file_time_to_unix_time (&attrs.ftLastAccessTime);\n    st->st_ctime = file_time_to_unix_time (&attrs.ftCreationTime);\n    st->st_mtime = file_time_to_unix_time (&attrs.ftLastWriteTime);\n\n    st->st_size = ((((__int64)attrs.nFileSizeHigh)<<32) + attrs.nFileSizeLow);\n\nout:\n    g_free (wpath);\n\n    return ret;\n#else\n    return stat (path, st);\n#endif\n}\n\nint\nseaf_fstat (int fd, SeafStat *st)\n{\n#ifdef WIN32\n    if (_fstat64 (fd, st) < 0)\n        return -1;\n\n    if (get_utc_file_time_fd (fd, &st->st_mtime, &st->st_ctime) < 0)\n        return -1;\n\n    return 0;\n#else\n    return fstat (fd, st);\n#endif\n}\n\n#ifdef WIN32\n\nvoid\nseaf_stat_from_find_data (WIN32_FIND_DATAW *fdata, SeafStat *st)\n{\n    memset (st, 0, sizeof(SeafStat));\n\n    if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)\n        st->st_mode = S_IFDIR;\n    else\n        st->st_mode = S_IFREG;\n\n    st->st_atime = file_time_to_unix_time (&fdata->ftLastAccessTime);\n    st->st_ctime = file_time_to_unix_time (&fdata->ftCreationTime);\n    st->st_mtime = file_time_to_unix_time (&fdata->ftLastWriteTime);\n\n    st->st_size = ((((__int64)fdata->nFileSizeHigh)<<32) + fdata->nFileSizeLow);\n}\n\n#endif\n\nint\nseaf_set_file_time (const char *path, guint64 mtime)\n{\n#ifndef WIN32\n    struct stat st;\n    struct utimbuf times;\n\n    if (stat (path, &st) < 0) {\n        g_warning (\"Failed to stat %s: %s.\\n\", path, strerror(errno));\n        return -1;\n    }\n\n    times.actime = st.st_atime;\n    times.modtime = (time_t)mtime;\n\n    return utime (path, &times);\n#else\n    wchar_t *wpath = win32_long_path (path);\n    int ret = 0;\n\n    if (set_utc_file_time (path, wpath, mtime) < 0)\n        ret = -1;\n\n    g_free (wpath);\n    return ret;\n#endif\n}\n\nint\nseaf_util_unlink (const char *path)\n{\n    int ret = 0;\n#ifdef WIN32\n    wchar_t *wpath = win32_long_path (path);\n\n    if (!DeleteFileW (wpath)) {\n        ret = -1;\n        errno = windows_error_to_errno (GetLastError());\n    }\n\n    g_free (wpath);\n    return ret;\n#elif defined __APPLE__\n    char *path_nfd = g_utf8_normalize (path, -1, G_NORMALIZE_NFD);\n    ret = unlink (path_nfd);\n    g_free (path_nfd);\n    return ret;\n#else\n    return unlink (path);\n#endif\n}\n\nint\nseaf_util_rmdir (const char *path)\n{\n    int ret = 0;\n#ifdef WIN32\n    wchar_t *wpath = win32_long_path (path);\n\n    if (!RemoveDirectoryW (wpath)) {\n        ret = -1;\n        errno = windows_error_to_errno (GetLastError());\n    }\n\n    g_free (wpath);\n    return ret;\n#elif defined __APPLE__\n    char *path_nfd = g_utf8_normalize (path, -1, G_NORMALIZE_NFD);\n    ret = rmdir (path_nfd);\n    g_free (path_nfd);\n    return ret;\n#else\n    return rmdir (path);\n#endif\n}\n\nint\nseaf_util_mkdir (const char *path, mode_t mode)\n{\n    int ret = 0;\n#ifdef WIN32\n    wchar_t *wpath = win32_long_path (path);\n\n    if (!CreateDirectoryW (wpath, NULL)) {\n        ret = -1;\n        errno = windows_error_to_errno (GetLastError());\n    }\n\n    g_free (wpath);\n    return ret;\n#elif defined __APPLE__\n    char *path_nfd = g_utf8_normalize (path, -1, G_NORMALIZE_NFD);\n    ret = mkdir (path_nfd, mode);\n    g_free (path_nfd);\n    return ret;\n#else\n    return mkdir (path, mode);\n#endif\n}\n\nint\nseaf_util_open (const char *path, int flags)\n{\n#ifdef WIN32\n    wchar_t *wpath;\n    DWORD access = 0;\n    HANDLE handle;\n    int fd;\n\n    access |= GENERIC_READ;\n    if (flags & (O_WRONLY | O_RDWR))\n        access |= GENERIC_WRITE;\n\n    wpath = win32_long_path (path);\n\n    handle = CreateFileW (wpath,\n                          access,\n                          FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,\n                          NULL,\n                          OPEN_EXISTING,\n                          0,\n                          NULL);\n    if (handle == INVALID_HANDLE_VALUE) {\n        errno = windows_error_to_errno (GetLastError());\n        g_free (wpath);\n        return -1;\n    }\n\n    fd = _open_osfhandle ((intptr_t)handle, 0);\n\n    g_free (wpath);\n    return fd;\n#elif defined __APPLE__\n    int ret = 0;\n    char *path_nfd = g_utf8_normalize (path, -1, G_NORMALIZE_NFD);\n    ret = open (path_nfd, flags);\n    g_free (path_nfd);\n    return ret;\n#else\n    return open (path, flags);\n#endif\n}\n\nint\nseaf_util_create (const char *path, int flags, mode_t mode)\n{\n#ifdef WIN32\n    wchar_t *wpath;\n    DWORD access = 0;\n    HANDLE handle;\n    int fd;\n\n    access |= GENERIC_READ;\n    if (flags & (O_WRONLY | O_RDWR))\n        access |= GENERIC_WRITE;\n\n    wpath = win32_long_path (path);\n\n    handle = CreateFileW (wpath,\n                          access,\n                          FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,\n                          NULL,\n                          CREATE_ALWAYS,\n                          0,\n                          NULL);\n    if (handle == INVALID_HANDLE_VALUE) {\n        errno = windows_error_to_errno (GetLastError());\n        g_free (wpath);\n        return -1;\n    }\n\n    fd = _open_osfhandle ((intptr_t)handle, 0);\n\n    g_free (wpath);\n    return fd;\n#elif defined __APPLE__\n    int ret = 0;\n    char *path_nfd = g_utf8_normalize (path, -1, G_NORMALIZE_NFD);\n    ret = open (path_nfd, flags, mode);\n    g_free (path_nfd);\n    return ret;\n#else\n    return open (path, flags, mode);\n#endif\n}\n\nint\nseaf_util_rename (const char *oldpath, const char *newpath)\n{\n    int ret = 0;\n#ifdef WIN32\n    wchar_t *oldpathw = win32_long_path (oldpath);\n    wchar_t *newpathw = win32_long_path (newpath);\n\n    if (!MoveFileExW (oldpathw, newpathw, MOVEFILE_REPLACE_EXISTING)) {\n        ret = -1;\n        errno = windows_error_to_errno (GetLastError());\n    }\n\n    g_free (oldpathw);\n    g_free (newpathw);\n    return ret;\n#elif defined __APPLE__\n    char *oldpath_nfd = g_utf8_normalize (oldpath, -1, G_NORMALIZE_NFD);\n    char *newpath_nfd = g_utf8_normalize (newpath, -1, G_NORMALIZE_NFD);\n    ret = rename (oldpath_nfd, newpath_nfd);\n    g_free (oldpath_nfd);\n    g_free (newpath_nfd);\n    return ret;\n#else\n    return rename (oldpath, newpath);\n#endif\n}\n\ngboolean\nseaf_util_exists (const char *path)\n{\n#ifdef WIN32\n    wchar_t *wpath = win32_long_path (path);\n    DWORD attrs;\n    gboolean ret;\n\n    attrs = GetFileAttributesW (wpath);\n    ret = (attrs != INVALID_FILE_ATTRIBUTES);\n\n    g_free (wpath);\n    return ret;\n#elif defined __APPLE__\n    int ret = 0;\n    char *path_nfd = g_utf8_normalize (path, -1, G_NORMALIZE_NFD);\n    ret = access (path_nfd, F_OK);\n    g_free (path_nfd);\n    return (ret == 0);\n#else\n    return (access (path, F_OK) == 0);\n#endif\n}\n\ngint64\nseaf_util_lseek (int fd, gint64 offset, int whence)\n{\n#ifdef WIN32\n    return _lseeki64 (fd, offset, whence);\n#else\n    return lseek (fd, offset, whence);\n#endif\n}\n\n#ifdef WIN32\n\nint\ntraverse_directory_win32 (wchar_t *path_w,\n                          DirentCallback callback,\n                          void *user_data)\n{\n    WIN32_FIND_DATAW fdata;\n    HANDLE handle;\n    wchar_t *pattern;\n    char *path;\n    int path_len_w;\n    DWORD error;\n    gboolean stop;\n    int ret = 0;\n\n    path = g_utf16_to_utf8 (path_w, -1, NULL, NULL, NULL);\n    if (!path)\n        return -1;\n\n    path_len_w = wcslen(path_w);\n\n    pattern = g_new0 (wchar_t, (path_len_w + 3));\n    wcscpy (pattern, path_w);\n    wcscat (pattern, L\"\\\\*\");\n\n    handle = FindFirstFileW (pattern, &fdata);\n    if (handle == INVALID_HANDLE_VALUE) {\n        g_warning (\"FindFirstFile failed %s: %lu.\\n\",\n                   path, GetLastError());\n        ret = -1;\n        goto out;\n    }\n\n    do {\n        if (wcscmp (fdata.cFileName, L\".\") == 0 ||\n            wcscmp (fdata.cFileName, L\"..\") == 0)\n            continue;\n\n        ++ret;\n\n        stop = FALSE;\n        if (callback (path_w, &fdata, user_data, &stop) < 0) {\n            ret = -1;\n            FindClose (handle);\n            goto out;\n        }\n        if (stop) {\n            FindClose (handle);\n            goto out;\n        }\n    } while (FindNextFileW (handle, &fdata) != 0);\n\n    error = GetLastError();\n    if (error != ERROR_NO_MORE_FILES) {\n        g_warning (\"FindNextFile failed %s: %lu.\\n\",\n                   path, error);\n        ret = -1;\n    }\n\n    FindClose (handle);\n\nout:\n    g_free (path);\n    g_free (pattern);\n    return ret;\n}\n\n#endif\n\n#ifdef WIN32\nstatic inline gboolean\nhas_trailing_space_or_period (const char *path)\n{\n    int len = strlen(path);\n    if (path[len - 1] == ' ' || path[len - 1] == '.') {\n        return TRUE;\n    }\n\n    return FALSE;\n}\n#endif\n\ngboolean\nshould_ignore_on_checkout (const char *file_path, IgnoreReason *ignore_reason)\n{\n    gboolean ret = FALSE;\n\n#ifdef WIN32\n    static char illegals[] = {'\\\\', ':', '*', '?', '\"', '<', '>', '|', '\\b', '\\t'};\n    char **components = g_strsplit (file_path, \"/\", -1);\n    int n_comps = g_strv_length (components);\n    int j = 0;\n    char *file_name;\n    int i;\n    char c;\n\n    for (; j < n_comps; ++j) {\n        file_name = components[j];\n\n        if (g_strcmp0(file_name, \".\") == 0 || g_strcmp0(file_name, \"..\") == 0) {\n            if (ignore_reason)\n                *ignore_reason = IGNORE_REASON_INVALID_CHARACTER;\n            ret = TRUE;\n            goto out;\n        }\n\n        if (has_trailing_space_or_period (file_name)) {\n            /* Ignore files/dir whose path has trailing spaces. It would cause\n             * problem on windows. */\n            /* g_debug (\"ignore '%s' which contains trailing space in path\\n\", path); */\n            ret = TRUE;\n            if (ignore_reason)\n                *ignore_reason = IGNORE_REASON_END_SPACE_PERIOD;\n            goto out;\n        }\n\n        for (i = 0; i < G_N_ELEMENTS(illegals); i++) {\n            if (strchr (file_name, illegals[i])) {\n                ret = TRUE;\n                if (ignore_reason)\n                    *ignore_reason = IGNORE_REASON_INVALID_CHARACTER;\n                goto out;\n            }\n        }\n\n        for (c = 1; c <= 31; c++) {\n            if (strchr (file_name, c)) {\n                ret = TRUE;\n                if (ignore_reason)\n                    *ignore_reason = IGNORE_REASON_INVALID_CHARACTER;\n                goto out;\n            }\n        }\n    }\n\nout:\n    g_strfreev (components);\n    return ret;\n#else\n    char **components = g_strsplit (file_path, \"/\", -1);\n    int n_comps = g_strv_length (components);\n    int j = 0;\n    char *file_name;\n\n    for (; j < n_comps; ++j) {\n        file_name = components[j];\n        if (g_strcmp0(file_name, \".\") == 0 || g_strcmp0(file_name, \"..\") == 0) {\n            ret = TRUE;\n            if (ignore_reason)\n                *ignore_reason = IGNORE_REASON_INVALID_CHARACTER;\n            break;\n        }\n    }\n    g_strfreev (components);\n    return ret;\n#endif\n}\n\nssize_t\t\t\t\t\t\t/* Read \"n\" bytes from a descriptor. */\nreadn(int fd, void *vptr, size_t n)\n{\n\tsize_t\tnleft;\n\tssize_t\tnread;\n\tchar\t*ptr;\n\n\tptr = vptr;\n\tnleft = n;\n\twhile (nleft > 0) {\n\t\tif ( (nread = read(fd, ptr, nleft)) < 0) {\n\t\t\tif (errno == EINTR)\n\t\t\t\tnread = 0;\t\t/* and call read() again */\n\t\t\telse\n\t\t\t\treturn(-1);\n\t\t} else if (nread == 0)\n\t\t\tbreak;\t\t\t\t/* EOF */\n\n\t\tnleft -= nread;\n\t\tptr   += nread;\n\t}\n\treturn(n - nleft);\t\t/* return >= 0 */\n}\n\nssize_t\t\t\t\t\t\t/* Write \"n\" bytes to a descriptor. */\nwriten(int fd, const void *vptr, size_t n)\n{\n\tsize_t\t\tnleft;\n\tssize_t\t\tnwritten;\n\tconst char\t*ptr;\n\n\tptr = vptr;\n\tnleft = n;\n\twhile (nleft > 0) {\n\t\tif ( (nwritten = write(fd, ptr, nleft)) <= 0) {\n\t\t\tif (nwritten < 0 && errno == EINTR)\n\t\t\t\tnwritten = 0;\t\t/* and call write() again */\n\t\t\telse\n\t\t\t\treturn(-1);\t\t\t/* error */\n\t\t}\n\n\t\tnleft -= nwritten;\n\t\tptr   += nwritten;\n\t}\n\treturn(n);\n}\n\n\nssize_t\t\t\t\t\t\t/* Read \"n\" bytes from a descriptor. */\nrecvn(evutil_socket_t fd, void *vptr, size_t n)\n{\n\tsize_t\tnleft;\n\tssize_t\tnread;\n\tchar\t*ptr;\n\n\tptr = vptr;\n\tnleft = n;\n\twhile (nleft > 0) {\n#ifndef WIN32\n        if ( (nread = read(fd, ptr, nleft)) < 0)\n#else\n        if ( (nread = recv(fd, ptr, nleft, 0)) < 0)\n#endif\n        {\n\t\t\tif (errno == EINTR)\n\t\t\t\tnread = 0;\t\t/* and call read() again */\n\t\t\telse\n\t\t\t\treturn(-1);\n\t\t} else if (nread == 0)\n\t\t\tbreak;\t\t\t\t/* EOF */\n\n\t\tnleft -= nread;\n\t\tptr   += nread;\n\t}\n\treturn(n - nleft);\t\t/* return >= 0 */\n}\n\nssize_t\t\t\t\t\t\t/* Write \"n\" bytes to a descriptor. */\nsendn(evutil_socket_t fd, const void *vptr, size_t n)\n{\n\tsize_t\t\tnleft;\n\tssize_t\t\tnwritten;\n\tconst char\t*ptr;\n\n\tptr = vptr;\n\tnleft = n;\n\twhile (nleft > 0) {\n#ifndef WIN32\n        if ( (nwritten = write(fd, ptr, nleft)) <= 0)\n#else\n        if ( (nwritten = send(fd, ptr, nleft, 0)) <= 0)\n#endif\n        {\n\t\t\tif (nwritten < 0 && errno == EINTR)\n\t\t\t\tnwritten = 0;\t\t/* and call write() again */\n\t\t\telse\n\t\t\t\treturn(-1);\t\t\t/* error */\n\t\t}\n\n\t\tnleft -= nwritten;\n\t\tptr   += nwritten;\n\t}\n\treturn(n);\n}\n\n#ifdef WIN32\nstatic SOCKET pg_serv_sock = INVALID_SOCKET;\nstatic struct sockaddr_in pg_serv_addr;\n\n/* seaf_pipe() should only be called in the main loop,\n * since it accesses the static global socket.\n */\nint\nseaf_pipe (seaf_pipe_t handles[2])\n{\n    int len = sizeof( pg_serv_addr );\n\n    handles[0] = handles[1] = INVALID_SOCKET;\n\n    if (pg_serv_sock == INVALID_SOCKET) {\n        if ((pg_serv_sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {\n            seaf_warning(\"seaf_pipe failed to create socket: %d\\n\", WSAGetLastError());\n            return -1;\n        }\n\n        memset(&pg_serv_addr, 0, sizeof(pg_serv_addr));\n        pg_serv_addr.sin_family = AF_INET;\n        pg_serv_addr.sin_port = htons(0);\n        pg_serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n\n        if (bind(pg_serv_sock, (SOCKADDR *)&pg_serv_addr, len) == SOCKET_ERROR) {\n            seaf_warning(\"seaf_pipe failed to bind: %d\\n\", WSAGetLastError());\n            closesocket(pg_serv_sock);\n            pg_serv_sock = INVALID_SOCKET;\n            return -1;\n        }\n\n        if (listen(pg_serv_sock, SOMAXCONN) == SOCKET_ERROR) {\n            seaf_warning(\"seaf_pipe failed to listen: %d\\n\", WSAGetLastError());\n            closesocket(pg_serv_sock);\n            pg_serv_sock = INVALID_SOCKET;\n            return -1;\n        }\n\n        struct sockaddr_in tmp_addr;\n        int tmp_len = sizeof(tmp_addr);\n        if (getsockname(pg_serv_sock, (SOCKADDR *)&tmp_addr, &tmp_len) == SOCKET_ERROR) {\n            seaf_warning(\"seaf_pipe failed to getsockname: %d\\n\", WSAGetLastError());\n            closesocket(pg_serv_sock);\n            pg_serv_sock = INVALID_SOCKET;\n            return -1;\n        }\n        pg_serv_addr.sin_port = tmp_addr.sin_port;\n    }\n\n    if ((handles[1] = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)\n    {\n        seaf_warning(\"seaf_pipe failed to create socket 2: %d\\n\", WSAGetLastError());\n        closesocket(pg_serv_sock);\n        pg_serv_sock = INVALID_SOCKET;\n        return -1;\n    }\n\n    if (connect(handles[1], (SOCKADDR *)&pg_serv_addr, len) == SOCKET_ERROR)\n    {\n        seaf_warning(\"seaf_pipe failed to connect socket: %d\\n\", WSAGetLastError());\n        closesocket(handles[1]);\n        handles[1] = INVALID_SOCKET;\n        closesocket(pg_serv_sock);\n        pg_serv_sock = INVALID_SOCKET;\n        return -1;\n    }\n\n    struct sockaddr_in client_addr;\n    int client_len = sizeof(client_addr);\n    if ((handles[0] = accept(pg_serv_sock, (SOCKADDR *)&client_addr, &client_len)) == INVALID_SOCKET)\n    {\n        seaf_warning(\"seaf_pipe failed to accept socket: %d\\n\", WSAGetLastError());\n        closesocket(handles[1]);\n        handles[1] = INVALID_SOCKET;\n        closesocket(pg_serv_sock);\n        pg_serv_sock = INVALID_SOCKET;\n        return -1;\n    }\n\n    return 0;\n}\n\nint\nseaf_pipe_read (seaf_pipe_t fd, char *buf, int len)\n{\n    return recv (fd, buf, len, 0);\n}\n\nint\nseaf_pipe_write (seaf_pipe_t fd, const char *buf, int len)\n{\n    return send (fd, buf, len, 0);\n}\n\nint\nseaf_pipe_close (seaf_pipe_t fd)\n{\n    return closesocket (fd);\n}\n\nssize_t seaf_pipe_readn (seaf_pipe_t fd, void *vptr, size_t n)\n{\n    return recvn (fd, vptr, n);\n}\n\nssize_t seaf_pipe_writen (seaf_pipe_t fd, const void *vptr, size_t n)\n{\n    return sendn (fd, vptr, n);\n}\n\n#else\n\nint\nseaf_pipe (seaf_pipe_t handles[2])\n{\n    return pipe (handles);\n}\n\nint\nseaf_pipe_read (seaf_pipe_t fd, char *buf, int len)\n{\n    return read (fd, buf, len);\n}\n\nint\nseaf_pipe_write (seaf_pipe_t fd, const char *buf, int len)\n{\n    return write (fd, buf, len);\n}\n\nint\nseaf_pipe_close (seaf_pipe_t fd)\n{\n    return close (fd);\n}\n\nssize_t seaf_pipe_readn (seaf_pipe_t fd, void *vptr, size_t n)\n{\n    return readn (fd, vptr, n);\n}\n\nssize_t seaf_pipe_writen (seaf_pipe_t fd, const void *vptr, size_t n)\n{\n    return writen (fd, vptr, n);\n}\n\n#endif\n\nint copy_fd (int ifd, int ofd)\n{\n    while (1) {\n        char buffer[8192];\n        ssize_t len = readn (ifd, buffer, sizeof(buffer));\n        if (!len)\n            break;\n        if (len < 0) {\n            close (ifd);\n            return -1;\n        }\n        if (writen (ofd, buffer, len) < 0) {\n            close (ofd);\n            return -1;\n        }\n    }\n    close(ifd);\n    return 0;\n}\n\nint copy_file (const char *dst, const char *src, int mode)\n{\n    int fdi, fdo, status;\n\n    if ((fdi = g_open (src, O_RDONLY | O_BINARY, 0)) < 0)\n        return fdi;\n\n    fdo = g_open (dst, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, mode);\n    if (fdo < 0 && errno == EEXIST) {\n        close (fdi);\n        return 0;\n    } else if (fdo < 0){\n        close (fdi);\n        return -1;\n    }\n\n    status = copy_fd (fdi, fdo);\n    if (close (fdo) != 0)\n        return -1;\n\n    return status;\n}\n\nchar*\nccnet_expand_path (const char *src)\n{\n#ifdef WIN32\n    char new_path[SEAF_PATH_MAX + 1];\n    char *p = new_path;\n    const char *q = src;\n\n    memset(new_path, 0, sizeof(new_path));\n    if (*src == '~') {\n        const char *home = g_get_home_dir();\n        memcpy(new_path, home, strlen(home));\n        p += strlen(new_path);\n        q++;\n    }\n    memcpy(p, q, strlen(q));\n\n    /* delete the charactor '\\' or '/' at the end of the path\n     * because the function stat faied to deal with directory names\n     * with '\\' or '/' in the end */\n    p = new_path + strlen(new_path) - 1;\n    while(*p == '\\\\' || *p == '/') *p-- = '\\0';\n\n    return strdup (new_path);\n#else\n    const char *next_in, *ntoken;\n    char new_path[SEAF_PATH_MAX + 1];\n    char *next_out;\n    int len;\n\n   /* special cases */\n    if (!src || *src == '\\0')\n        return NULL;\n    if (strlen(src) > SEAF_PATH_MAX)\n        return NULL;\n\n    next_in = src;\n    next_out = new_path;\n    *next_out = '\\0';\n\n    if (*src == '~') {\n        /* handle src start with '~' or '~<user>' like '~plt' */\n        struct passwd *pw = NULL;\n\n        for ( ; *next_in != '/' && *next_in != '\\0'; next_in++) ;\n        \n        len = next_in - src;\n        if (len == 1) {\n            pw = getpwuid (geteuid());\n        } else {\n            /* copy '~<user>' to new_path */\n            memcpy (new_path, src, len);\n            new_path[len] = '\\0';\n            pw = getpwnam (new_path + 1);\n        }\n        if (pw == NULL)\n            return NULL;\n       \n        len = strlen (pw->pw_dir);\n        memcpy (new_path, pw->pw_dir, len);\n        next_out = new_path + len;\n        *next_out = '\\0';\n\n        if (*next_in == '\\0')\n            return strdup (new_path);\n    } else if (*src != '/') {\n        getcwd (new_path, SEAF_PATH_MAX);\n        for ( ; *next_out; next_out++) ; /* to '\\0' */\n    }\n    \n    while (*next_in != '\\0') {\n        /* move ntoken to the next not '/' char  */\n        for (ntoken = next_in; *ntoken == '/'; ntoken++) ;\n\n        for (next_in = ntoken; *next_in != '/' \n                 && *next_in != '\\0'; next_in++) ;\n \n        len = next_in - ntoken;\n\n        if (len == 0) {\n            /* the path ends with '/', keep it */\n            *next_out++ = '/';\n            *next_out = '\\0';\n            break;\n        }\n\n        if (len == 2 && ntoken[0] == '.' && ntoken[1] == '.') \n        {\n            /* '..' */\n            for (; next_out > new_path && *next_out != '/'; next_out--)\n                ;\n            *next_out = '\\0';\n        } else if (ntoken[0] != '.' || len != 1) {\n            /* not '.' */\n            *next_out++ = '/';\n            memcpy (next_out, ntoken, len);\n            next_out += len;\n            *next_out = '\\0';\n        }\n    }\n\n    /* the final special case */\n    if (new_path[0] == '\\0') {\n        new_path[0] = '/';\n        new_path[1] = '\\0';\n    }\n    return strdup (new_path);\n#endif\n}\n\n\nint\ncalculate_sha1 (unsigned char *sha1, const char *msg, int len)\n{\n    GChecksum *c;\n    gsize cs_len = 20;\n\n    if (len < 0)\n        len = strlen(msg);\n\n    c = g_checksum_new (G_CHECKSUM_SHA1);\n    g_checksum_update(c, (const unsigned char *)msg, len);    \n    g_checksum_get_digest (c, sha1, &cs_len);\n    g_checksum_free (c);\n    return 0;\n}\n\nuint32_t\nccnet_sha1_hash (const void *v)\n{\n    /* 31 bit hash function */\n    const unsigned char *p = v;\n    uint32_t h = 0;\n    int i;\n\n    for (i = 0; i < 20; i++)\n        h = (h << 5) - h + p[i];\n\n    return h;\n}\n\nint\nccnet_sha1_equal (const void *v1,\n                  const void *v2)\n{\n    const unsigned char *p1 = v1;\n    const unsigned char *p2 = v2;\n    int i;\n\n    for (i = 0; i < 20; i++)\n        if (p1[i] != p2[i])\n            return 0;\n    \n    return 1;\n}\n\n#ifndef WIN32\nchar* gen_uuid ()\n{\n    char *uuid_str = g_malloc (37);\n    uuid_t uuid;\n\n    uuid_generate (uuid);\n    uuid_unparse_lower (uuid, uuid_str);\n\n    return uuid_str;\n}\n\nvoid gen_uuid_inplace (char *buf)\n{\n    uuid_t uuid;\n\n    uuid_generate (uuid);\n    uuid_unparse_lower (uuid, buf);\n}\n\ngboolean\nis_uuid_valid (const char *uuid_str)\n{\n    uuid_t uuid;\n\n    if (!uuid_str)\n        return FALSE;\n\n    if (uuid_parse (uuid_str, uuid) < 0)\n        return FALSE;\n    return TRUE;\n}\n\n#else\nchar* gen_uuid ()\n{\n    char *uuid_str = g_malloc (37);\n    unsigned char *str = NULL;\n    UUID uuid;\n\n    UuidCreate(&uuid);\n    UuidToStringA(&uuid, &str);\n    memcpy(uuid_str, str, 37);\n    RpcStringFreeA(&str);\n    return uuid_str;\n}\n\nvoid gen_uuid_inplace (char *buf)\n{\n    unsigned char *str = NULL;\n    UUID uuid;\n\n    UuidCreate(&uuid);\n    UuidToStringA(&uuid, &str);\n    memcpy(buf, str, 37);\n    RpcStringFreeA(&str);\n}\n\ngboolean\nis_uuid_valid (const char *uuid_str)\n{\n    if (!uuid_str)\n        return FALSE;\n\n    UUID uuid;\n    if (UuidFromStringA((unsigned char *)uuid_str, &uuid) != RPC_S_OK)\n        return FALSE;\n    return TRUE;\n}\n\n#endif\n\ngboolean\nis_object_id_valid (const char *obj_id)\n{\n    if (!obj_id)\n        return FALSE;\n\n    int len = strlen(obj_id);\n    int i;\n    char c;\n\n    if (len != 40)\n        return FALSE;\n\n    for (i = 0; i < len; ++i) {\n        c = obj_id[i];\n        if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))\n            continue;\n        return FALSE;\n    }\n\n    return TRUE;\n}\n\nchar** strsplit_by_space (char *string, int *length)\n{\n    char *remainder, *s;\n    int size = 8, num = 0, done = 0;\n    char **array;\n    \n    if (string == NULL || string[0] == '\\0') {\n        if (length != NULL) {\n          *length = 0;\n        }\n        return NULL;\n    }\n\n    array = malloc (sizeof(char *) * size);\n    if (array == NULL) {\n      return NULL;\n    }\n    \n    remainder = string;\n    while (!done) {\n        for (s = remainder; *s != ' ' && *s != '\\0'; ++s) ;\n\n        if (*s == '\\0')\n            done = 1;\n        else\n            *s = '\\0';\n\n        array[num++] = remainder;\n        if (!done && num == size) {\n            size <<= 1;\n            char** tmp = realloc (array, sizeof(char *) * size);\n            if (tmp == NULL) {\n              free(array);\n              return NULL;\n            }\n            array = tmp;\n        }\n\n        remainder = s + 1;\n    }\n    \n    if (length != NULL) {\n      *length = num;\n    }\n\n    return array;\n}\n\nchar** strsplit_by_char (char *string, int *length, char c)\n{\n    char *remainder, *s;\n    int size = 8, num = 0, done = 0;\n    char **array;\n    \n    if (string == NULL || string[0] == '\\0') {\n        *length = 0;\n        return NULL;\n    }\n\n    array = malloc (sizeof(char *) * size);\n    if (array == NULL) {\n      return NULL;\n    }\n    \n    remainder = string;\n    while (!done) {\n        for (s = remainder; *s != c && *s != '\\0'; ++s) ;\n\n        if (*s == '\\0')\n            done = 1;\n        else\n            *s = '\\0';\n\n        array[num++] = remainder;\n        if (!done && num == size) {\n            size <<= 1;\n            char** tmp = realloc (array, sizeof(char *) * size);\n            if (tmp == NULL) {\n              free(array);\n              return NULL;\n            }\n            array = tmp;\n        }\n\n        remainder = s + 1;\n    }\n    \n    if (length != NULL) {\n      *length = num;\n    }\n\n    return array;\n}\n\nchar* strjoin_n (const char *seperator, int argc, char **argv)\n{\n    GString *buf;\n    int i;\n    char *str;\n\n    if (argc == 0)\n        return NULL;\n    \n    buf = g_string_new (argv[0]);\n    for (i = 1; i < argc; ++i) {\n        g_string_append (buf, seperator);\n        g_string_append (buf, argv[i]);\n    }\n\n    str = buf->str;\n    g_string_free (buf, FALSE);\n    return str;\n}\n\n\ngboolean is_ipaddr_valid (const char *ip)\n{\n    unsigned char buf[sizeof(struct in6_addr)];\n\n    if (evutil_inet_pton(AF_INET, ip, buf) == 1)\n        return TRUE;\n\n    if (evutil_inet_pton(AF_INET6, ip, buf) == 1)\n        return TRUE;\n    \n    return FALSE;\n}\n\nvoid parse_key_value_pairs (char *string, KeyValueFunc func, void *data)\n{\n    char *line = string, *next, *space;\n    char *key, *value;\n\n    while (*line) {\n        /* handle empty line */\n        if (*line == '\\n') {\n            ++line;\n            continue;\n        }\n\n        for (next = line; *next != '\\n' && *next; ++next) ;\n        *next = '\\0';\n        \n        for (space = line; space < next && *space != ' '; ++space) ;\n        if (*space != ' ') {\n            g_warning (\"Bad key value format: %s\\n\", line);\n            return;\n        }\n        *space = '\\0';\n        key = line;\n        value = space + 1;\n        \n        func (data, key, value);\n\n        line = next + 1;\n    }\n}\n\nvoid parse_key_value_pairs2 (char *string, KeyValueFunc2 func, void *data)\n{\n    char *line = string, *next, *space;\n    char *key, *value;\n\n    while (*line) {\n        /* handle empty line */\n        if (*line == '\\n') {\n            ++line;\n            continue;\n        }\n\n        for (next = line; *next != '\\n' && *next; ++next) ;\n        *next = '\\0';\n        \n        for (space = line; space < next && *space != ' '; ++space) ;\n        if (*space != ' ') {\n            g_warning (\"Bad key value format: %s\\n\", line);\n            return;\n        }\n        *space = '\\0';\n        key = line;\n        value = space + 1;\n        \n        if (func(data, key, value) == FALSE)\n            break;\n\n        line = next + 1;\n    }\n}\n\n/**\n * handle the empty string problem.\n */\ngchar* \nccnet_key_file_get_string (GKeyFile *keyf,\n                           const char *category,\n                           const char *key)\n{\n    gchar *v;\n\n    if (!g_key_file_has_key (keyf, category, key, NULL))\n        return NULL;\n\n    v = g_key_file_get_string (keyf, category, key, NULL);\n    if (v != NULL && v[0] == '\\0') {\n        g_free(v);\n        return NULL;\n    }\n\n    return v;\n}\n\n/**\n * string_list_is_exists:\n * @str_list: \n * @string: a C string or %NULL\n *\n * Check whether @string is in @str_list.\n *\n * returns: %TRUE if @string is in str_list, %FALSE otherwise\n */\ngboolean\nstring_list_is_exists (GList *str_list, const char *string)\n{\n    GList *ptr;\n    for (ptr = str_list; ptr; ptr = ptr->next) {\n        if (g_strcmp0(string, ptr->data) == 0)\n            return TRUE;\n    }\n    return FALSE;\n}\n\n/**\n * string_list_append:\n * @str_list: \n * @string: a C string (can't be %NULL\n *\n * Append @string to @str_list if it is in the list.\n *\n * returns: the new start of the list\n */\nGList*\nstring_list_append (GList *str_list, const char *string)\n{\n    g_return_val_if_fail (string != NULL, str_list);\n\n    if (string_list_is_exists(str_list, string))\n        return str_list;\n\n    str_list = g_list_append (str_list, g_strdup(string));\n    return str_list;\n}\n\nGList *\nstring_list_append_sorted (GList *str_list, const char *string)\n{\n    g_return_val_if_fail (string != NULL, str_list);\n\n    if (string_list_is_exists(str_list, string))\n        return str_list;\n\n    str_list = g_list_insert_sorted_with_data (str_list, g_strdup(string),\n                                 (GCompareDataFunc)g_strcmp0, NULL);\n    return str_list;\n}\n\n\nGList *\nstring_list_remove (GList *str_list, const char *string)\n{\n    g_return_val_if_fail (string != NULL, str_list);\n\n    GList *ptr;\n\n    for (ptr = str_list; ptr; ptr = ptr->next) {\n        if (strcmp((char *)ptr->data, string) == 0) {\n            g_free (ptr->data);\n            return g_list_delete_link (str_list, ptr);\n        }\n    }\n    return str_list;\n}\n\n\nvoid\nstring_list_free (GList *str_list)\n{\n    GList *ptr = str_list;\n\n    while (ptr) {\n        g_free (ptr->data);\n        ptr = ptr->next;\n    }\n\n    g_list_free (str_list);\n}\n\n\nvoid\nstring_list_join (GList *str_list, GString *str, const char *seperator)\n{\n    GList *ptr;\n    if (!str_list)\n        return;\n\n    ptr = str_list;\n    g_string_append (str, ptr->data);\n\n    for (ptr = ptr->next; ptr; ptr = ptr->next) {\n        g_string_append (str, seperator);\n        g_string_append (str, (char *)ptr->data);\n    }\n}\n\nGList *\nstring_list_parse (const char *list_in_str, const char *seperator)\n{\n    if (!list_in_str)\n        return NULL;\n\n    GList *list = NULL;\n    char **array = g_strsplit (list_in_str, seperator, 0);\n    char **ptr;\n\n    for (ptr = array; *ptr; ptr++) {\n        list = g_list_prepend (list, g_strdup(*ptr));\n    }\n    list = g_list_reverse (list);\n    \n    g_strfreev (array);\n    return list;\n}\n\nGList *\nstring_list_parse_sorted (const char *list_in_str, const char *seperator)\n{\n    GList *list = string_list_parse (list_in_str, seperator);\n\n    return g_list_sort (list, (GCompareFunc)g_strcmp0);\n}\n\ngboolean\nstring_list_sorted_is_equal (GList *list1, GList *list2)\n{\n    GList *ptr1 = list1, *ptr2 = list2;\n\n    while (ptr1 && ptr2) {\n        if (g_strcmp0(ptr1->data, ptr2->data) != 0)\n            break;\n\n        ptr1 = ptr1->next;\n        ptr2 = ptr2->next;\n    }\n\n    if (!ptr1 && !ptr2)\n        return TRUE;\n    return FALSE;\n}\n\nchar **\nncopy_string_array (char **orig, int n)\n{\n    char **ret = g_malloc (sizeof(char *) * n);\n    int i = 0;\n\n    for (; i < n; i++)\n        ret[i] = g_strdup(orig[i]);\n    return ret;\n}\n\nvoid\nnfree_string_array (char **array, int n)\n{\n    int i = 0;\n\n    for (; i < n; i++)\n        g_free (array[i]);\n    g_free (array);\n}\n\ngint64\nget_current_time()\n{\n    GTimeVal tv;\n    gint64 t;\n\n    g_get_current_time (&tv);\n    t = tv.tv_sec * (gint64)1000000 + tv.tv_usec;\n    return t;\n}\n\n/* convert locale specific input to utf8 encoded string  */\nchar *ccnet_locale_to_utf8 (const gchar *src)\n{\n    if (!src)\n        return NULL;\n\n    gsize bytes_read = 0;\n    gsize bytes_written = 0;\n    GError *error = NULL;\n    gchar *dst = NULL;\n\n    dst = g_locale_to_utf8\n        (src,                   /* locale specific string */\n         strlen(src),           /* len of src */\n         &bytes_read,           /* length processed */\n         &bytes_written,        /* output length */\n         &error);\n\n    if (error) {\n        return NULL;\n    }\n\n    return dst;\n}\n\n/* convert utf8 input to locale specific string  */\nchar *ccnet_locale_from_utf8 (const gchar *src)\n{\n    if (!src)\n        return NULL;\n\n    gsize bytes_read = 0;\n    gsize bytes_written = 0;\n    GError *error = NULL;\n    gchar *dst = NULL;\n\n    dst = g_locale_from_utf8\n        (src,                   /* locale specific string */\n         strlen(src),           /* len of src */\n         &bytes_read,           /* length processed */\n         &bytes_written,        /* output length */\n         &error);\n\n    if (error) {\n        return NULL;\n    }\n\n    return dst;\n}\n\n#ifdef WIN32\n\nstatic HANDLE\nget_process_handle (const char *process_name_in)\n{\n    char name[256];\n    if (strstr(process_name_in, \".exe\")) {\n        snprintf (name, sizeof(name), \"%s\", process_name_in);\n    } else {\n        snprintf (name, sizeof(name), \"%s.exe\", process_name_in);\n    }\n\n    DWORD aProcesses[1024], cbNeeded, cProcesses;\n\n    if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded))\n        return NULL;\n\n    /* Calculate how many process identifiers were returned. */\n    cProcesses = cbNeeded / sizeof(DWORD);\n\n    HANDLE hProcess;\n    HMODULE hMod;\n    char process_name[SEAF_PATH_MAX];\n    unsigned int i;\n\n    for (i = 0; i < cProcesses; i++) {\n        if(aProcesses[i] == 0)\n            continue;\n        hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, aProcesses[i]);\n        if (!hProcess)\n            continue;\n            \n        if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded)) {\n            GetModuleBaseNameA(hProcess, hMod, process_name, \n                              sizeof(process_name)/sizeof(char));\n        }\n\n        if (strcasecmp(process_name, name) == 0)\n            return hProcess;\n        else {\n            CloseHandle(hProcess);\n        }\n    }\n    /* Not found */\n    return NULL;\n}\n\nint count_process (const char *process_name_in)\n{\n    char name[SEAF_PATH_MAX];\n    char process_name[SEAF_PATH_MAX];\n    DWORD aProcesses[1024], cbNeeded, cProcesses;\n    HANDLE hProcess;\n    HMODULE hMods[1024];\n    int count = 0;\n    int i, j;\n    \n    if (strstr(process_name_in, \".exe\")) {\n        snprintf (name, sizeof(name), \"%s\", process_name_in);\n    } else {\n        snprintf (name, sizeof(name), \"%s.exe\", process_name_in);\n    }\n\n    if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded)) {\n        return 0;\n    }\n\n    /* Calculate how many process identifiers were returned. */\n    cProcesses = cbNeeded / sizeof(DWORD);\n\n    for (i = 0; i < cProcesses; i++) {\n        if(aProcesses[i] == 0)\n            continue;\n        hProcess = OpenProcess (PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, aProcesses[i]);\n        if (!hProcess) {\n            continue;\n        }\n            \n        if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {\n            for (j = 0; j < cbNeeded / sizeof(HMODULE); j++) {\n                if (GetModuleBaseNameA(hProcess, hMods[j], process_name,\n                                      sizeof(process_name))) {\n                    if (strcasecmp(process_name, name) == 0)\n                        count++;\n                }\n            } \n        }\n\n        CloseHandle(hProcess);\n    }\n    \n    return count;\n}\n\ngboolean\nprocess_is_running (const char *process_name)\n{\n    HANDLE proc_handle = get_process_handle(process_name);\n\n    if (proc_handle) {\n        CloseHandle(proc_handle);\n        return TRUE;\n    } else {\n        return FALSE;\n    }\n}\n\nint\nwin32_kill_process (const char *process_name)\n{\n    HANDLE proc_handle = get_process_handle(process_name);\n\n    if (proc_handle) {\n        TerminateProcess(proc_handle, 0);\n        CloseHandle(proc_handle);\n        return 0;\n    } else {\n        return -1;\n    }\n}\n\nint\nwin32_spawn_process (char *cmdline_in, char *working_directory_in)\n{\n    if (!cmdline_in)\n        return -1;\n\n    wchar_t *cmdline_w = NULL;\n    wchar_t *working_directory_w = NULL;\n\n    cmdline_w = wchar_from_utf8 (cmdline_in);\n    if (!cmdline_in) {\n        g_warning (\"failed to convert cmdline_in\");\n        return -1;\n    }\n    \n    if (working_directory_in) {\n        working_directory_w = wchar_from_utf8 (working_directory_in);\n        if (!working_directory_w) {\n            g_warning (\"failed to convert working_directory_in\");\n            return -1;\n        }\n    }\n\n    STARTUPINFOW si;\n    PROCESS_INFORMATION pi;\n    unsigned flags;\n    BOOL success;\n\n    /* we want to execute seafile without crreating a console window */\n    flags = CREATE_NO_WINDOW;\n\n    memset(&si, 0, sizeof(si));\n    si.cb = sizeof(si);\n    si.dwFlags = STARTF_USESTDHANDLES | STARTF_FORCEOFFFEEDBACK;\n    si.hStdInput = (HANDLE) _get_osfhandle(0);\n    si.hStdOutput = (HANDLE) _get_osfhandle(1);\n    si.hStdError = (HANDLE) _get_osfhandle(2);\n    \n    memset(&pi, 0, sizeof(pi));\n\n    success = CreateProcessW (NULL, cmdline_w, NULL, NULL, TRUE, flags,\n                              NULL, working_directory_w, &si, &pi);\n    free (cmdline_w);\n    if (working_directory_w) free (working_directory_w);\n    \n    if (!success) {\n        g_warning (\"failed to fork_process: GLE=%lu\\n\", GetLastError());\n        return -1;\n    }\n\n    /* close the handle of thread so that the process object can be freed by\n     * system\n     */\n    CloseHandle(pi.hThread);\n    CloseHandle(pi.hProcess);\n    return 0;\n}\n\nchar *\nwchar_to_utf8 (const wchar_t *wch)\n{\n    if (wch == NULL) {\n        return NULL;\n    }\n\n    char *utf8 = NULL;\n    int bufsize, len;\n\n    bufsize = WideCharToMultiByte\n        (CP_UTF8,               /* multibyte code page */\n         0,                     /* flags */\n         wch,                   /* src */\n         -1,                    /* src len, -1 for all includes \\0 */\n         utf8,                  /* dst */\n         0,                     /* dst buf len */\n         NULL,                  /* default char */\n         NULL);                 /* BOOL flag indicates default char is used */\n\n    if (bufsize <= 0) {\n        g_warning (\"failed to convert a string from wchar to utf8 0\");\n        return NULL;\n    }\n\n    utf8 = g_malloc(bufsize);\n    len = WideCharToMultiByte\n        (CP_UTF8,               /* multibyte code page */\n         0,                     /* flags */\n         wch,                   /* src */\n         -1,                    /* src len, -1 for all includes \\0 */\n         utf8,                  /* dst */\n         bufsize,               /* dst buf len */\n         NULL,                  /* default char */\n         NULL);                 /* BOOL flag indicates default char is used */\n\n    if (len != bufsize) {\n        g_free (utf8);\n        g_warning (\"failed to convert a string from wchar to utf8\");\n        return NULL;\n    }\n\n    return utf8;\n}\n\nwchar_t *\nwchar_from_utf8 (const char *utf8)\n{\n    if (utf8 == NULL) {\n        return NULL;\n    }\n\n    wchar_t *wch = NULL;\n    int bufsize, len;\n\n    bufsize = MultiByteToWideChar\n        (CP_UTF8,               /* multibyte code page */\n         0,                     /* flags */\n         utf8,                  /* src */\n         -1,                    /* src len, -1 for all includes \\0 */\n         wch,                   /* dst */\n         0);                    /* dst buf len */\n\n    if (bufsize <= 0) {\n        g_warning (\"failed to convert a string from wchar to utf8 0\");\n        return NULL;\n    }\n\n    wch = g_malloc (bufsize * sizeof(wchar_t));\n    len = MultiByteToWideChar\n        (CP_UTF8,               /* multibyte code page */\n         0,                     /* flags */\n         utf8,                  /* src */\n         -1,                    /* src len, -1 for all includes \\0 */\n         wch,                   /* dst */\n         bufsize);              /* dst buf len */\n\n    if (len != bufsize) {\n        g_free (wch);\n        g_warning (\"failed to convert a string from utf8 to wchar\");\n        return NULL;\n    }\n\n    return wch;\n}\n\n#endif  /* ifdef WIN32 */\n\n#ifdef __linux__\n/* read the link of /proc/123/exe and compare with `process_name' */\nstatic int\nfind_process_in_dirent(struct dirent *dir, const char *process_name)\n{\n    char path[512];\n    /* fisrst construct a path like /proc/123/exe */\n    if (sprintf (path, \"/proc/%s/exe\", dir->d_name) < 0) {\n        return -1;\n    }\n\n    char buf[SEAF_PATH_MAX];\n    /* get the full path of exe */\n    ssize_t l = readlink(path, buf, SEAF_PATH_MAX);\n\n    if (l < 0)\n        return -1;\n    buf[l] = '\\0';\n\n    /* get the base name of exe */\n    char *base = g_path_get_basename(buf);\n    int ret = strcmp(base, process_name);\n    g_free(base);\n\n    if (ret == 0)\n        return atoi(dir->d_name);\n    else\n        return -1;\n}\n\n/* read the /proc fs to determine whether some process is running */\ngboolean process_is_running (const char *process_name)\n{\n    DIR *proc_dir = opendir(\"/proc\");\n    if (!proc_dir) {\n        fprintf (stderr, \"failed to open /proc/ dir\\n\");\n        return FALSE;\n    }\n\n    struct dirent *subdir = NULL;\n    while ((subdir = readdir(proc_dir))) {\n        char first = subdir->d_name[0];\n        /* /proc/[1-9][0-9]* */\n        if (first > '9' || first < '1')\n            continue;\n        int pid = find_process_in_dirent(subdir, process_name);\n        if (pid > 0) {\n            closedir(proc_dir);\n            return TRUE;\n        }\n    }\n\n    closedir(proc_dir);\n    return FALSE;\n}\n\nint count_process(const char *process_name)\n{\n    int count = 0;\n    DIR *proc_dir = opendir(\"/proc\");\n    if (!proc_dir) {\n        g_warning (\"failed to open /proc/ :%s\\n\", strerror(errno));\n        return FALSE;\n    }\n\n    struct dirent *subdir = NULL;\n    while ((subdir = readdir(proc_dir))) {\n        char first = subdir->d_name[0];\n        /* /proc/[1-9][0-9]* */\n        if (first > '9' || first < '1')\n            continue;\n        if (find_process_in_dirent(subdir, process_name) > 0) {\n            count++;\n        }\n    }\n\n    closedir (proc_dir);\n    return count;\n}\n\n#endif\n\n#ifdef __APPLE__\ngboolean process_is_running (const char *process_name)\n{\n    //TODO\n    return FALSE;\n}\n#endif\n\nchar*\nccnet_object_type_from_id (const char *object_id)\n{\n    char *ptr;\n\n    if ( !(ptr = strchr(object_id, '/')) )\n        return NULL;\n\n    return g_strndup(object_id, ptr - object_id);\n}\n\n\n#ifdef WIN32\n/**\n * In Win32 we need to use _stat64 for files larger than 2GB. _stat64 needs\n * the `path' argument in gbk encoding.\n */\n    #define STAT_STRUCT struct __stat64\n    #define STAT_FUNC win_stat64_utf8\n\nstatic inline int\nwin_stat64_utf8 (char *path_utf8, STAT_STRUCT *sb)\n{\n    wchar_t *path_w = wchar_from_utf8 (path_utf8);\n    int result = _wstat64 (path_w, sb);\n    free (path_w);\n    return result;\n}\n\n#else\n    #define STAT_STRUCT struct stat\n    #define STAT_FUNC stat\n#endif\n\nstatic gint64\ncalc_recursively (const char *path, GError **calc_error)\n{\n    gint64 sum = 0;\n\n    GError *error = NULL;\n    GDir *folder = g_dir_open(path, 0, &error);\n    if (!folder) {\n        g_set_error (calc_error, CCNET_DOMAIN, 0,\n                     \"g_open() dir %s failed:%s\\n\", path, error->message);\n        return -1;\n    }\n\n    const char *name = NULL;\n    while ((name = g_dir_read_name(folder)) != NULL) {\n        STAT_STRUCT sb;\n        char *full_path= g_build_filename (path, name, NULL);\n        if (STAT_FUNC(full_path, &sb) < 0) {\n            g_set_error (calc_error, CCNET_DOMAIN, 0, \"failed to stat on %s: %s\\n\",\n                         full_path, strerror(errno));\n            g_free(full_path);\n            g_dir_close(folder);\n            return -1;\n        }\n\n        if (S_ISDIR(sb.st_mode)) {\n            gint64 size = calc_recursively(full_path, calc_error);\n            if (size < 0) {\n                g_free (full_path);\n                g_dir_close (folder);\n                return -1;\n            }\n            sum += size;\n            g_free(full_path);\n        } else if (S_ISREG(sb.st_mode)) {\n            sum += sb.st_size;\n            g_free(full_path);\n        }\n    }\n\n    g_dir_close (folder);\n    return sum;\n}\n\ngint64\nccnet_calc_directory_size (const char *path, GError **error)\n{\n    return calc_recursively (path, error);\n}\n\n#ifdef WIN32\n/*\n * strtok_r code directly from glibc.git /string/strtok_r.c since windows\n * doesn't have it.\n */\nchar *\nstrtok_r(char *s, const char *delim, char **save_ptr)\n{\n    char *token;\n    \n    if(s == NULL)\n        s = *save_ptr;\n    \n    /* Scan leading delimiters.  */\n    s += strspn(s, delim);\n    if(*s == '\\0') {\n        *save_ptr = s;\n        return NULL;\n    }\n    \n    /* Find the end of the token.  */\n    token = s;\n    s = strpbrk(token, delim);\n    \n    if(s == NULL) {\n        /* This token finishes the string.  */\n        *save_ptr = strchr(token, '\\0');\n    } else {\n        /* Terminate the token and make *SAVE_PTR point past it.  */\n        *s = '\\0';\n        *save_ptr = s + 1;\n    }\n    \n    return token;\n}\n#endif\n\n/* JSON related utils. For compatibility with json-glib. */\n\nconst char *\njson_object_get_string_member (json_t *object, const char *key)\n{\n    json_t *string = json_object_get (object, key);\n    if (!string)\n        return NULL;\n    return json_string_value (string);\n}\n\ngboolean\njson_object_has_member (json_t *object, const char *key)\n{\n    return (json_object_get (object, key) != NULL);\n}\n\ngint64\njson_object_get_int_member (json_t *object, const char *key)\n{\n    json_t *integer = json_object_get (object, key);\n    return json_integer_value (integer);\n}\n\nvoid\njson_object_set_string_member (json_t *object, const char *key, const char *value)\n{\n    json_object_set_new (object, key, json_string (value));\n}\n\nvoid\njson_object_set_int_member (json_t *object, const char *key, gint64 value)\n{\n    json_object_set_new (object, key, json_integer (value));\n}\n\nvoid\nclean_utf8_data (char *data, int len)\n{\n    const char *s, *e;\n    char *p;\n    gboolean is_valid;\n\n    s = data;\n    p = data;\n\n    while ((s - data) != len) {\n        is_valid = g_utf8_validate (s, len - (s - data), &e);\n        if (is_valid)\n            break;\n\n        if (s != e)\n            p += (e - s);\n        *p = '?';\n        ++p;\n        s = e + 1;\n    }\n}\n\nchar *\nnormalize_utf8_path (const char *path)\n{\n    if (!g_utf8_validate (path, -1, NULL))\n        return NULL;\n    return g_utf8_normalize (path, -1, G_NORMALIZE_NFC);\n}\n\n/* zlib related wrapper functions. */\n\n#define ZLIB_BUF_SIZE 16384\n\nint\nseaf_compress (guint8 *input, int inlen, guint8 **output, int *outlen)\n{\n    int ret;\n    unsigned have;\n    z_stream strm;\n    guint8 out[ZLIB_BUF_SIZE];\n    GByteArray *barray;\n\n    if (inlen == 0)\n        return -1;\n\n    /* allocate deflate state */\n    strm.zalloc = Z_NULL;\n    strm.zfree = Z_NULL;\n    strm.opaque = Z_NULL;\n    ret = deflateInit(&strm, Z_DEFAULT_COMPRESSION);\n    if (ret != Z_OK) {\n        g_warning (\"deflateInit failed.\\n\");\n        return -1;\n    }\n\n    strm.avail_in = inlen;\n    strm.next_in = input;\n    barray = g_byte_array_new ();\n\n    do {\n        strm.avail_out = ZLIB_BUF_SIZE;\n        strm.next_out = out;\n        ret = deflate(&strm, Z_FINISH);    /* no bad return value */\n        have = ZLIB_BUF_SIZE - strm.avail_out;\n        g_byte_array_append (barray, out, have);\n    } while (ret != Z_STREAM_END);\n\n    *outlen = barray->len;\n    *output = g_byte_array_free (barray, FALSE);\n\n    /* clean up and return */\n    (void)deflateEnd(&strm);\n    return 0;\n}\n\nint\nseaf_decompress (guint8 *input, int inlen, guint8 **output, int *outlen)\n{\n    int ret;\n    unsigned have;\n    z_stream strm;\n    unsigned char out[ZLIB_BUF_SIZE];\n    GByteArray *barray;\n\n    if (inlen == 0) {\n        g_warning (\"Empty input for zlib, invalid.\\n\");\n        return -1;\n    }\n\n    /* allocate inflate state */\n    strm.zalloc = Z_NULL;\n    strm.zfree = Z_NULL;\n    strm.opaque = Z_NULL;\n    strm.avail_in = 0;\n    strm.next_in = Z_NULL;\n    ret = inflateInit(&strm);\n    if (ret != Z_OK) {\n        g_warning (\"inflateInit failed.\\n\");\n        return -1;\n    }\n\n    strm.avail_in = inlen;\n    strm.next_in = input;\n    barray = g_byte_array_new ();\n\n    do {\n        strm.avail_out = ZLIB_BUF_SIZE;\n        strm.next_out = out;\n        ret = inflate(&strm, Z_NO_FLUSH);\n        if (ret < 0) {\n            g_warning (\"Failed to inflate.\\n\");\n            goto out;\n        }\n        have = ZLIB_BUF_SIZE - strm.avail_out;\n        g_byte_array_append (barray, out, have);\n    } while (ret != Z_STREAM_END);\n\nout:\n    /* clean up and return */\n    (void)inflateEnd(&strm);\n\n    if (ret == Z_STREAM_END) {\n        *outlen = barray->len;\n        *output = g_byte_array_free (barray, FALSE);\n        return 0;\n    } else {\n        g_byte_array_free (barray, TRUE);\n        return -1;\n    }\n}\n\nchar*\nformat_dir_path (const char *path)\n{\n    int path_len = strlen (path);\n    char *rpath;\n    if (path[0] != '/') {\n        rpath = g_strconcat (\"/\", path, NULL);\n        path_len++;\n    } else {\n        rpath = g_strdup (path);\n    }\n    while (path_len > 1 && rpath[path_len-1] == '/') {\n        rpath[path_len-1] = '\\0';\n        path_len--;\n    }\n\n    return rpath;\n}\n\ngboolean\nis_empty_string (const char *str)\n{\n    return !str || strcmp (str, \"\") == 0;\n}\n\ngboolean\nis_permission_valid (const char *perm)\n{\n    if (is_empty_string (perm)) {\n        return FALSE;\n    }\n\n    return strcmp (perm, \"r\") == 0 || strcmp (perm, \"rw\") == 0;\n}\n\ngboolean\nis_eml_file (const char *path)\n{\n    int len = strlen(path);\n    const char *ext;\n\n    if (len < 5)\n        return FALSE;\n    ext = &path[len-4];\n    return (strcasecmp (ext, \".eml\") == 0);\n}\n\nchar *\ncanonical_server_url (const char *url_in)\n{\n    char *url = g_strdup(url_in);\n    int len = strlen(url);\n\n    if (url[len - 1] == '/')\n        url[len - 1] = 0;\n\n    return url;\n}\n\n#ifdef __APPLE__\nstatic gboolean\ncase_conflict_recursive (const char *worktree, const char *path, char **conflict_path, GHashTable *no_case_conflict_hash)\n{\n    if (!path || g_strcmp0 (path, \".\") == 0) {\n        return FALSE;\n    }\n\n    if (g_hash_table_lookup (no_case_conflict_hash, path)) {\n        return FALSE;\n    }\n\n    SeafStat st;\n    gboolean ret = FALSE;\n    int fd = -1;\n    char *full_path = g_build_path (\"/\", worktree, path, NULL);\n    char *no_conflict_path = NULL;\n\n    if (seaf_stat (full_path, &st) < 0) {\n        char *sub_path = g_path_get_dirname (path);\n        ret = case_conflict_recursive (worktree, sub_path, conflict_path, no_case_conflict_hash);\n        g_free (sub_path);\n        goto out;\n    }\n\n    int len = strlen (path);\n    fd = open (full_path, O_RDONLY);\n    if (fd < 0) {\n        goto out;\n    }\n    char buffer[SEAF_PATH_MAX];\n    if (fcntl (fd, F_GETPATH, buffer) < 0) {\n        goto out;\n    }\n    int offset = strlen (buffer) - len;\n    if (strcasecmp (buffer + offset, path) == 0 &&\n        strcmp (buffer + offset, path) != 0) {\n        if (conflict_path) {\n            *conflict_path = g_strdup(path);\n        }\n        ret = TRUE;\n        goto out;\n    } else {\n        no_conflict_path = g_strdup (path);\n        g_hash_table_insert (no_case_conflict_hash, no_conflict_path, no_conflict_path);\n    }\n    \nout:\n    if (fd >= 0)\n        close (fd);\n    g_free (full_path);\n    return ret;\n}\n#elif defined WIN32\nstatic gboolean\ncase_conflict_recursive (const char *worktree, const char *path, char **conflict_path, GHashTable *no_case_conflict_hash)\n{\n    if (!path || g_strcmp0 (path, \".\") == 0) {\n        return FALSE;\n    }\n\n    if (g_hash_table_lookup (no_case_conflict_hash, path)) {\n        return FALSE;\n    }\n\n    SeafStat st;\n    gboolean ret = FALSE;\n    int fd = -1;\n    char *full_path = g_build_path (\"/\", worktree, path, NULL);\n    wchar_t *wpath = win32_long_path (full_path);\n    char *no_conflict_path = NULL;\n\n    if (seaf_stat (full_path, &st) < 0) {\n        char *sub_path = g_path_get_dirname (path);\n        ret = case_conflict_recursive (worktree, sub_path, conflict_path, no_case_conflict_hash);\n        if (!ret) {\n            no_conflict_path = g_strdup (sub_path);\n            g_hash_table_insert (no_case_conflict_hash, no_conflict_path, no_conflict_path);\n        }\n        g_free (sub_path);\n        goto out;\n    }\n\n    HANDLE handle;\n    WIN32_FIND_DATAW fdata;\n    handle = FindFirstFileW (wpath, &fdata);\n    if (handle == INVALID_HANDLE_VALUE) {\n        seaf_warning (\"Checkinng path case, FindFirstFile failed %s: %lu.\\n\", full_path, GetLastError());\n        goto out;\n    }\n    char *real_name = g_utf16_to_utf8 (fdata.cFileName, -1, NULL, NULL, NULL);\n    int offset = strlen (path) - strlen(real_name);\n    if (strcasecmp (path + offset, real_name) == 0 &&\n        strcmp (path + offset, real_name) != 0) {\n        if (conflict_path) {\n            *conflict_path = g_strdup(path);\n        }\n        ret = TRUE;\n        g_free (real_name);\n        FindClose (handle);\n        goto out;\n    }\n    g_free (real_name);\n    FindClose (handle);\n\n    char *sub_path = g_path_get_dirname (path);\n    ret = case_conflict_recursive (worktree, sub_path, conflict_path, no_case_conflict_hash);\n    g_free (sub_path);\n\nout:\n    g_free (full_path);\n    g_free (wpath);\n    return ret;\n}\n#else\nstatic gboolean\ncase_conflict_recursive (const char *worktree, const char *path, char **conflict_path, GHashTable *no_case_conflict_hash)\n{\n    return FALSE;\n}\n#endif\n\ngboolean\nis_path_case_conflict (const char *worktree, const char *path, char **conflict_path, GHashTable *no_case_conflict_hash)\n{\n    if (strlen(path) >= SEAF_PATH_MAX) {\n        return FALSE;\n    }\n    if (case_conflict_recursive (worktree, path, conflict_path, no_case_conflict_hash))\n        return TRUE;\n#ifdef WIN32\n    char *no_conflict_path = g_strdup (path);\n    g_hash_table_insert (no_case_conflict_hash, no_conflict_path, no_conflict_path);\n#endif\n    return FALSE;\n}\n\nssize_t\nseaf_getxattr (const char *path, const char *name, void *value, size_t size)\n{\n#ifdef WIN32\n    /* On Windows, use alternate data streams for xattrs. */\n\n    char *stream_path;\n    wchar_t *w_stream_path;\n    HANDLE handle;\n    LARGE_INTEGER stream_size;\n    DWORD n_to_read, n_read;\n    ssize_t ret;\n\n    stream_path = g_strconcat (path, \":\", name, NULL);\n    w_stream_path = win32_long_path (stream_path);\n    g_free (stream_path);\n\n    handle = CreateFileW (w_stream_path,\n                          GENERIC_READ,\n                          FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,\n                          NULL,\n                          OPEN_EXISTING,\n                          0,\n                          NULL);\n    if (handle == INVALID_HANDLE_VALUE) {\n        errno = windows_error_to_errno (GetLastError());\n        ret = -1;\n        goto out;\n    }\n\n    if (!GetFileSizeEx (handle, &stream_size)) {\n        errno = windows_error_to_errno (GetLastError());\n        ret = -1;\n        goto out;\n    }\n\n    n_to_read = MIN (stream_size.LowPart, size);\n    if (!ReadFile (handle, value, n_to_read, &n_read, NULL)) {\n        errno = windows_error_to_errno (GetLastError());\n        ret = -1;\n        goto out;\n    }\n\n    ret = (ssize_t)n_read;\n\nout:\n    g_free (w_stream_path);\n    if (handle != INVALID_HANDLE_VALUE)\n        CloseHandle(handle);\n    return ret;\n#endif\n\n#ifdef __linux__\n    return getxattr (path, name, value, size);\n#endif\n\n#ifdef __APPLE__\n    return getxattr (path, name, value, size, 0, 0);\n#endif\n\n#if defined __FreeBSD__ || defined __NetBSD__\n    return extattr_get_file (path, EXTATTR_NAMESPACE_USER, name, value, size);\n#endif\n\n    return -1;\n}\n\nint\nseaf_setxattr (const char *path, const char *name, const void *value, size_t size)\n{\n#ifdef WIN32\n    char *stream_path;\n    wchar_t *w_stream_path;\n    HANDLE handle;\n    DWORD n_written;\n    ssize_t ret = 0;\n\n    stream_path = g_strconcat (path, \":\", name, NULL);\n    w_stream_path = win32_long_path (stream_path);\n    g_free (stream_path);\n\n    handle = CreateFileW (w_stream_path,\n                          GENERIC_WRITE,\n                          FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,\n                          NULL,\n                          OPEN_ALWAYS,\n                          0,\n                          NULL);\n    if (handle == INVALID_HANDLE_VALUE) {\n        errno = windows_error_to_errno (GetLastError());\n        ret = -1;\n        goto out;\n    }\n\n    if (!WriteFile (handle, value, (DWORD)size, &n_written, NULL)) {\n        errno = windows_error_to_errno (GetLastError());\n        ret = -1;\n    }\n\nout:\n    g_free (w_stream_path);\n    if (handle != INVALID_HANDLE_VALUE)\n        CloseHandle(handle);\n    return ret;\n#endif\n\n#ifdef __linux__\n    return setxattr (path, name, value, size, 0);\n#endif\n\n#ifdef __APPLE__\n    return setxattr (path, name, value, size, 0, 0);\n#endif\n\n#if defined __FreeBSD__ || defined __NetBSD__\n    return extattr_set_file (path, EXTATTR_NAMESPACE_USER, name, value, size);\n#endif\n\n    return -1;\n}\n\nint\nseaf_removexattr (const char *path, const char *name)\n{\n#ifdef WIN32\n    char *stream_path;\n    wchar_t *w_stream_path;\n    int ret = 0;\n\n    stream_path = g_strconcat (path, \":\", name, NULL);\n    w_stream_path = win32_long_path (stream_path);\n    g_free (stream_path);\n\n    if (!DeleteFileW (w_stream_path)) {\n        errno = windows_error_to_errno (GetLastError());\n        ret = -1;\n    }\n\n    g_free (w_stream_path);\n    return ret;\n#else\n\n#ifdef __APPLE__\n    return removexattr (path, name, 0);\n#else\n    return removexattr (path, name);\n#endif\n\n#endif\n}\n"
  },
  {
    "path": "lib/utils.h",
    "content": "/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */\n\n#ifndef CCNET_UTILS_H\n#define CCNET_UTILS_H\n\n#ifdef WIN32\n#ifndef _WIN32_WINNT\n#define _WIN32_WINNT 0x500\n#endif\n#include <winsock2.h>\n#include <windows.h>\n#endif\n\n#ifndef WIN32\n#include <sys/time.h>\n#include <unistd.h>\n#endif\n#include <time.h>\n#include <stdint.h>\n#include <stdarg.h>\n#include <glib.h>\n#include <glib-object.h>\n#include <stdlib.h>\n#include <sys/stat.h>\n\n#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)\n#include <event2/util.h>\n#else\n#include <evutil.h>\n#endif\n\n#ifdef __linux__\n#include <endian.h>\n#endif\n\n#ifdef __OpenBSD__\n#include <machine/endian.h>\n#endif\n\n#ifdef WIN32\n#include <errno.h>\n#include <glib/gstdio.h>\n\n#define mode_t int\n\n#define ssize_t gssize\n\n#ifndef WEXITSTATUS\n#define WEXITSTATUS(status) (((status) & 0xff00) >> 8)\n#endif\n\n#define SeafStat struct __stat64\n\n#else\n\n#define SeafStat struct stat\n\n#endif\n\n#ifndef SEAFILE_CLIENT_VERSION\n#define SEAFILE_CLIENT_VERSION PACKAGE_VERSION\n#endif\n\n#ifdef WIN32\n#define USER_AGENT_OS \"Windows NT\"\n#endif\n\n#ifdef __APPLE__\n#define USER_AGENT_OS \"Apple OS X\"\n#endif\n\n#ifdef __linux__\n#define USER_AGENT_OS \"Linux\"\n#endif\n\n#if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined __DragonFly__\n#define USER_AGENT_OS \"BSD\"\n#endif\n\nint seaf_stat (const char *path, SeafStat *st);\nint seaf_fstat (int fd, SeafStat *st);\n\n#ifdef WIN32\nvoid\nseaf_stat_from_find_data (WIN32_FIND_DATAW *fdata, SeafStat *st);\n#endif\n\nint seaf_set_file_time (const char *path, guint64 mtime);\n\n#ifdef WIN32\nwchar_t *\nwin32_long_path (const char *path);\n\n/* Convert a (possible) 8.3 format path to long path */\nwchar_t *\nwin32_83_path_to_long_path (const char *worktree, const wchar_t *path, int path_len);\n\n__time64_t\nfile_time_to_unix_time (FILETIME *ftime);\n#endif\n\nint\nseaf_util_unlink (const char *path);\n\nint\nseaf_util_rmdir (const char *path);\n\nint\nseaf_util_mkdir (const char *path, mode_t mode);\n\nint\nseaf_util_open (const char *path, int flags);\n\nint\nseaf_util_create (const char *path, int flags, mode_t mode);\n\nint\nseaf_util_rename (const char *oldpath, const char *newpath);\n\ngboolean\nseaf_util_exists (const char *path);\n\ngint64\nseaf_util_lseek (int fd, gint64 offset, int whence);\n\n#ifdef WIN32\n\ntypedef int (*DirentCallback) (wchar_t *parent,\n                               WIN32_FIND_DATAW *fdata,\n                               void *user_data,\n                               gboolean *stop);\n\nint\ntraverse_directory_win32 (wchar_t *path_w,\n                          DirentCallback callback,\n                          void *user_data);\n#endif\n\n#ifndef O_BINARY\n#define O_BINARY 0\n#endif\n\n#ifdef WIN32\n#define seaf_pipe_t intptr_t\n#else\n#define seaf_pipe_t int\n#endif\n\nint\nseaf_pipe (seaf_pipe_t handles[2]);\nint\nseaf_pipe_read (seaf_pipe_t fd, char *buf, int len);\nint\nseaf_pipe_write (seaf_pipe_t fd, const char *buf, int len);\nint\nseaf_pipe_close (seaf_pipe_t fd);\n\nssize_t seaf_pipe_readn (seaf_pipe_t fd, void *vptr, size_t n);\nssize_t seaf_pipe_writen (seaf_pipe_t fd, const void *vptr, size_t n);\n\ntypedef enum IgnoreReason {\n    IGNORE_REASON_END_SPACE_PERIOD = 0,\n    IGNORE_REASON_INVALID_CHARACTER = 1,\n} IgnoreReason;\n\ngboolean\nshould_ignore_on_checkout (const char *file_path, IgnoreReason *ignore_reason);\n\n/* for debug */\n#ifndef ccnet_warning\n#ifndef WIN32\n#define ccnet_warning(fmt, ...) g_warning(\"%s(%d): \" fmt, __FILE__, __LINE__, ##__VA_ARGS__)\n#else\n#define ccnet_warning(...) g_warning (__VA_ARGS__)\n#endif\n#endif\n\n#ifndef ccnet_error\n#ifndef WIN32\n#define ccnet_error(fmt, ...)   g_error(\"%s(%d): \" fmt, __FILE__, __LINE__, ##__VA_ARGS__)\n#else\n#define ccnet_error(...) g_error(__VA_ARGS__)\n#endif\n#endif\n\n#ifndef ccnet_message\n#ifndef WIN32\n#define ccnet_message(fmt, ...) g_message(\"%s(%d): \" fmt, __FILE__, __LINE__, ##__VA_ARGS__)\n#else\n#define ccnet_message(...) g_message(__VA_ARGS__)\n#endif\n#endif\n\n#define CCNET_DOMAIN g_quark_from_string(\"ccnet\")\n\n\nsize_t ccnet_strlcpy (char *dst, const char *src, size_t size);\n\nvoid rawdata_to_hex (const unsigned char *rawdata, char *hex_str, int n_bytes);\nint hex_to_rawdata (const char *hex_str, unsigned char *rawdata, int n_bytes);\n\n#define sha1_to_hex(sha1, hex) rawdata_to_hex((sha1), (hex), 20)\n#define hex_to_sha1(hex, sha1) hex_to_rawdata((hex), (sha1), 20)\n\n/* If msg is NULL-terminated, set len to -1 */\nint calculate_sha1 (unsigned char *sha1, const char *msg, int len);\nint ccnet_sha1_equal (const void *v1, const void *v2);\nunsigned int ccnet_sha1_hash (const void *v);\n\nchar* gen_uuid ();\nvoid gen_uuid_inplace (char *buf);\ngboolean is_uuid_valid (const char *uuid_str);\n\ngboolean\nis_object_id_valid (const char *obj_id);\n\n/* dir operations */\nint checkdir (const char *dir);\nint checkdir_with_mkdir (const char *path);\nchar* ccnet_expand_path (const char *src);\n\n/**\n * Make directory with 256 sub-directories from '00' to 'ff'.\n * `base` and subdir will be created if they are not existing. \n */\nint objstore_mkdir (const char *base);\nvoid objstore_get_path (char *path, const char *base, const char *obj_id);\n\n\nchar** strsplit_by_space (char *string, int *length);\n\n/* Read \"n\" bytes from a descriptor. */\nssize_t\treadn(int fd, void *vptr, size_t n);\nssize_t writen(int fd, const void *vptr, size_t n);\n\n/* Read \"n\" bytes from a socket. */\nssize_t\trecvn(evutil_socket_t fd, void *vptr, size_t n);\nssize_t sendn(evutil_socket_t fd, const void *vptr, size_t n);\n\nint copy_fd (int ifd, int ofd);\nint copy_file (const char *dst, const char *src, int mode);\n\n\n/* string utilities */\n\nchar** strsplit_by_char (char *string, int *length, char c);\n\nchar * strjoin_n (const char *seperator, int argc, char **argv);\n\nint is_ipaddr_valid (const char *ip);\n\ntypedef void (*KeyValueFunc) (void *data, const char *key, char *value);\nvoid parse_key_value_pairs (char *string, KeyValueFunc func, void *data);\n\ntypedef gboolean (*KeyValueFunc2) (void *data, const char *key,\n                                   const char *value);\nvoid parse_key_value_pairs2 (char *string, KeyValueFunc2 func, void *data);\n\ngchar*  ccnet_key_file_get_string (GKeyFile *keyf,\n                                   const char *category,\n                                   const char *key);\n\n\nGList *string_list_append (GList *str_list, const char *string);\nGList *string_list_append_sorted (GList *str_list, const char *string);\nGList *string_list_remove (GList *str_list, const char *string);\nvoid string_list_free (GList *str_list);\ngboolean string_list_is_exists (GList *str_list, const char *string);\nvoid string_list_join (GList *str_list, GString *strbuf, const char *seperator);\nGList *string_list_parse (const char *list_in_str, const char *seperator);\nGList *string_list_parse_sorted (const char *list_in_str, const char *seperator);\ngboolean string_list_sorted_is_equal (GList *list1, GList *list2);\n\nchar** ncopy_string_array (char **orig, int n);\nvoid nfree_string_array (char **array, int n);\n\n/* 64bit time */\ngint64 get_current_time();\n\n/*\n * Utility functions for converting data to/from network byte order.\n */\n\n#if !defined(__NetBSD__)\nstatic inline uint64_t\nbswap64 (uint64_t val)\n{\n    uint64_t ret;\n    uint8_t *ptr = (uint8_t *)&ret;\n\n    ptr[0]=((val)>>56)&0xFF;\n    ptr[1]=((val)>>48)&0xFF;\n    ptr[2]=((val)>>40)&0xFF;\n    ptr[3]=((val)>>32)&0xFF;\n    ptr[4]=((val)>>24)&0xFF;\n    ptr[5]=((val)>>16)&0xFF;\n    ptr[6]=((val)>>8)&0xFF;\n    ptr[7]=(val)&0xFF;\n\n    return ret;\n}\n#endif\n\nstatic inline uint64_t\nhton64(uint64_t val)\n{\n#if __BYTE_ORDER == __LITTLE_ENDIAN || defined WIN32 || defined __APPLE__\n    return bswap64 (val);\n#else\n    return val;\n#endif\n}\n\nstatic inline uint64_t \nntoh64(uint64_t val) \n{\n#if __BYTE_ORDER == __LITTLE_ENDIAN || defined WIN32 || defined __APPLE__\n    return bswap64 (val);\n#else\n    return val;\n#endif\n}\n\nstatic inline void put64bit(uint8_t **ptr,uint64_t val)\n{\n    uint64_t val_n = hton64 (val);\n    *((uint64_t *)(*ptr)) = val_n;\n    (*ptr)+=8;\n}\n\nstatic inline void put32bit(uint8_t **ptr,uint32_t val)\n{\n    uint32_t val_n = htonl (val);\n    *((uint32_t *)(*ptr)) = val_n;\n    (*ptr)+=4;\n}\n\nstatic inline void put16bit(uint8_t **ptr,uint16_t val)\n{\n    uint16_t val_n = htons (val);\n    *((uint16_t *)(*ptr)) = val_n;\n    (*ptr)+=2;\n}\n\nstatic inline uint64_t get64bit(const uint8_t **ptr)\n{\n    uint64_t val_h = ntoh64 (*((uint64_t *)(*ptr)));\n    (*ptr)+=8;\n    return val_h;\n}\n\nstatic inline uint32_t get32bit(const uint8_t **ptr)\n{\n    uint32_t val_h = ntohl (*((uint32_t *)(*ptr)));\n    (*ptr)+=4;\n    return val_h;\n}\n\nstatic inline uint16_t get16bit(const uint8_t **ptr)\n{\n    uint16_t val_h = ntohs (*((uint16_t *)(*ptr)));\n    (*ptr)+=2;\n    return val_h;\n}\n\n/* Convert between local encoding and utf8. Returns the converted\n * string if success, otherwise return NULL\n */\nchar *ccnet_locale_from_utf8 (const gchar *src);\nchar *ccnet_locale_to_utf8 (const gchar *src);\n\n/* Detect whether a process with the given name is running right now. */\ngboolean process_is_running(const char *name);\n\n/* count how much instance of a program is running  */\nint count_process (const char *process_name_in);\n\n#ifdef WIN32\nint win32_kill_process (const char *process_name_in);\nint win32_spawn_process (char *cmd, char *wd);\nchar *wchar_to_utf8 (const wchar_t *src);\nwchar_t *wchar_from_utf8 (const char *src);\n#endif\n\nchar* ccnet_object_type_from_id (const char *object_id);\n\ngint64 ccnet_calc_directory_size (const char *path, GError **error);\n\n#ifdef WIN32\nchar * strtok_r(char *s, const char *delim, char **save_ptr);\n#endif\n\n#include <jansson.h>\n\nconst char *\njson_object_get_string_member (json_t *object, const char *key);\n\ngboolean\njson_object_has_member (json_t *object, const char *key);\n\ngint64\njson_object_get_int_member (json_t *object, const char *key);\n\nvoid\njson_object_set_string_member (json_t *object, const char *key, const char *value);\n\nvoid\njson_object_set_int_member (json_t *object, const char *key, gint64 value);\n\n/* Replace invalid UTF-8 bytes with '?' */\nvoid\nclean_utf8_data (char *data, int len);\n\nchar *\nnormalize_utf8_path (const char *path);\n\n/* zlib related functions. */\n\nint\nseaf_compress (guint8 *input, int inlen, guint8 **output, int *outlen);\n\nint\nseaf_decompress (guint8 *input, int inlen, guint8 **output, int *outlen);\n\nchar*\nformat_dir_path (const char *path);\n\ngboolean\nis_empty_string (const char *str);\n\ngboolean\nis_permission_valid (const char *perm);\n\ngboolean\nis_eml_file (const char *path);\n\nchar *\ncanonical_server_url (const char *url_in);\n\ngboolean\nis_path_case_conflict (const char *full_path, const char *path, char **conflict_path, GHashTable *no_case_conflict_hash);\n\nssize_t\nseaf_getxattr (const char *path, const char *name, void *value, size_t size);\n\nint\nseaf_setxattr (const char *path, const char *name, const void *value, size_t size);\n\nint\nseaf_removexattr (const char *path, const char *name);\n#endif\n"
  },
  {
    "path": "m4/ax_lib_sqlite3.m4",
    "content": "# ===========================================================================\n#         http://www.nongnu.org/autoconf-archive/ax_lib_sqlite3.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_LIB_SQLITE3([MINIMUM-VERSION])\n#\n# DESCRIPTION\n#\n#   Test for the SQLite 3 library of a particular version (or newer)\n#\n#   This macro takes only one optional argument, required version of SQLite\n#   3 library. If required version is not passed, 3.0.0 is used in the test\n#   of existance of SQLite 3.\n#\n#   If no intallation prefix to the installed SQLite library is given the\n#   macro searches under /usr, /usr/local, and /opt.\n#\n#   This macro calls:\n#\n#     AC_SUBST(SQLITE3_CFLAGS)\n#     AC_SUBST(SQLITE3_LDFLAGS)\n#     AC_SUBST(SQLITE3_VERSION)\n#\n#   And sets:\n#\n#     HAVE_SQLITE3\n#\n# LICENSE\n#\n#   Copyright (c) 2008 Mateusz Loskot <mateusz@loskot.net>\n#\n#   Copying and distribution of this file, with or without modification, are\n#   permitted in any medium without royalty provided the copyright notice\n#   and this notice are preserved.\n\nAC_DEFUN([AX_LIB_SQLITE3],\n[\n    AC_ARG_WITH([sqlite3],\n        AC_HELP_STRING(\n            [--with-sqlite3=@<:@ARG@:>@],\n            [use SQLite 3 library @<:@default=yes@:>@, optionally specify the prefix for sqlite3 library]\n        ),\n        [\n        if test \"$withval\" = \"no\"; then\n            WANT_SQLITE3=\"no\"\n        elif test \"$withval\" = \"yes\"; then\n            WANT_SQLITE3=\"yes\"\n            ac_sqlite3_path=\"\"\n        else\n            WANT_SQLITE3=\"yes\"\n            ac_sqlite3_path=\"$withval\"\n        fi\n        ],\n        [WANT_SQLITE3=\"yes\"]\n    )\n\n    SQLITE3_CFLAGS=\"\"\n    SQLITE3_LDFLAGS=\"\"\n    SQLITE3_VERSION=\"\"\n\n    if test \"x$WANT_SQLITE3\" = \"xyes\"; then\n\n        ac_sqlite3_header=\"sqlite3.h\"\n\n        sqlite3_version_req=ifelse([$1], [], [3.0.0], [$1])\n        sqlite3_version_req_shorten=`expr $sqlite3_version_req : '\\([[0-9]]*\\.[[0-9]]*\\)'`\n        sqlite3_version_req_major=`expr $sqlite3_version_req : '\\([[0-9]]*\\)'`\n        sqlite3_version_req_minor=`expr $sqlite3_version_req : '[[0-9]]*\\.\\([[0-9]]*\\)'`\n        sqlite3_version_req_micro=`expr $sqlite3_version_req : '[[0-9]]*\\.[[0-9]]*\\.\\([[0-9]]*\\)'`\n        if test \"x$sqlite3_version_req_micro\" = \"x\" ; then\n            sqlite3_version_req_micro=\"0\"\n        fi\n\n        sqlite3_version_req_number=`expr $sqlite3_version_req_major \\* 1000000 \\\n                                   \\+ $sqlite3_version_req_minor \\* 1000 \\\n                                   \\+ $sqlite3_version_req_micro`\n\n        AC_MSG_CHECKING([for SQLite3 library >= $sqlite3_version_req])\n\n        if test \"$ac_sqlite3_path\" != \"\"; then\n            ac_sqlite3_ldflags=\"-L$ac_sqlite3_path/lib\"\n            ac_sqlite3_cppflags=\"-I$ac_sqlite3_path/include\"\n        else\n            for ac_sqlite3_path_tmp in /usr /usr/local /opt ; do\n                if test -f \"$ac_sqlite3_path_tmp/include/$ac_sqlite3_header\" \\\n                    && test -r \"$ac_sqlite3_path_tmp/include/$ac_sqlite3_header\"; then\n                    ac_sqlite3_path=$ac_sqlite3_path_tmp\n                    ac_sqlite3_cppflags=\"-I$ac_sqlite3_path_tmp/include\"\n                    ac_sqlite3_ldflags=\"-L$ac_sqlite3_path_tmp/lib\"\n                    break;\n                fi\n            done\n        fi\n\n        ac_sqlite3_ldflags=\"$ac_sqlite3_ldflags -lsqlite3\"\n\n        saved_CPPFLAGS=\"$CPPFLAGS\"\n        CPPFLAGS=\"$CPPFLAGS $ac_sqlite3_cppflags\"\n\n\n        AC_COMPILE_IFELSE(\n            [\n            AC_LANG_PROGRAM([[@%:@include <sqlite3.h>]],\n                [[\n#if (SQLITE_VERSION_NUMBER >= $sqlite3_version_req_number)\n// Everything is okay\n#else\n#  error SQLite version is too old\n#endif\n                ]]\n            )\n            ],\n            [\n            AC_MSG_RESULT([yes])\n            success=\"yes\"\n            ],\n            [\n            AC_MSG_RESULT([not found])\n            succees=\"no\"\n            ]\n        )\n\n\n        CPPFLAGS=\"$saved_CPPFLAGS\"\n\n        if test \"$success\" = \"yes\"; then\n\n            SQLITE3_CFLAGS=\"$ac_sqlite3_cppflags\"\n            SQLITE3_LDFLAGS=\"$ac_sqlite3_ldflags\"\n\n            ac_sqlite3_header_path=\"$ac_sqlite3_path/include/$ac_sqlite3_header\"\n\n            dnl Retrieve SQLite release version\n            if test \"x$ac_sqlite3_header_path\" != \"x\"; then\n                ac_sqlite3_version=`cat $ac_sqlite3_header_path \\\n                    | grep '#define.*SQLITE_VERSION.*\\\"' | sed -e 's/.* \"//' \\\n                        | sed -e 's/\"//'`\n                if test $ac_sqlite3_version != \"\"; then\n                    SQLITE3_VERSION=$ac_sqlite3_version\n                else\n                    AC_MSG_WARN([Can not find SQLITE_VERSION macro in sqlite3.h header to retrieve SQLite version!])\n                fi\n            fi\n\n            AC_SUBST(SQLITE3_CFLAGS)\n            AC_SUBST(SQLITE3_LDFLAGS)\n            AC_SUBST(SQLITE3_VERSION)\n            AC_DEFINE([HAVE_SQLITE3], [], [Have the SQLITE3 library])\n        fi\n    fi\n])\n"
  },
  {
    "path": "m4/glib-gettext.m4",
    "content": "# Copyright (C) 1995-2002 Free Software Foundation, Inc.\n# Copyright (C) 2001-2003,2004 Red Hat, Inc.\n#\n# This file is free software, distributed under the terms of the GNU\n# General Public License.  As a special exception to the GNU General\n# Public License, this file may be distributed as part of a program\n# that contains a configuration script generated by Autoconf, under\n# the same distribution terms as the rest of that program.\n#\n# This file can be copied and used freely without restrictions.  It can\n# be used in projects which are not available under the GNU Public License\n# but which still want to provide support for the GNU gettext functionality.\n#\n# Macro to add for using GNU gettext.\n# Ulrich Drepper <drepper@cygnus.com>, 1995, 1996\n#\n# Modified to never use included libintl. \n# Owen Taylor <otaylor@redhat.com>, 12/15/1998\n#\n# Major rework to remove unused code\n# Owen Taylor <otaylor@redhat.com>, 12/11/2002\n#\n# Added better handling of ALL_LINGUAS from GNU gettext version \n# written by Bruno Haible, Owen Taylor <otaylor.redhat.com> 5/30/3002\n#\n# Modified to require ngettext\n# Matthias Clasen <mclasen@redhat.com> 08/06/2004\n#\n# We need this here as well, since someone might use autoconf-2.5x\n# to configure GLib then an older version to configure a package\n# using AM_GLIB_GNU_GETTEXT\nAC_PREREQ(2.53)\n\ndnl\ndnl We go to great lengths to make sure that aclocal won't \ndnl try to pull in the installed version of these macros\ndnl when running aclocal in the glib directory.\ndnl\nm4_copy([AC_DEFUN],[glib_DEFUN])\nm4_copy([AC_REQUIRE],[glib_REQUIRE])\ndnl\ndnl At the end, if we're not within glib, we'll define the public\ndnl definitions in terms of our private definitions.\ndnl\n\n# GLIB_LC_MESSAGES\n#--------------------\nglib_DEFUN([GLIB_LC_MESSAGES],\n  [AC_CHECK_HEADERS([locale.h])\n    if test $ac_cv_header_locale_h = yes; then\n    AC_CACHE_CHECK([for LC_MESSAGES], am_cv_val_LC_MESSAGES,\n      [AC_TRY_LINK([#include <locale.h>], [return LC_MESSAGES],\n       am_cv_val_LC_MESSAGES=yes, am_cv_val_LC_MESSAGES=no)])\n    if test $am_cv_val_LC_MESSAGES = yes; then\n      AC_DEFINE(HAVE_LC_MESSAGES, 1,\n        [Define if your <locale.h> file defines LC_MESSAGES.])\n    fi\n  fi])\n\n# GLIB_PATH_PROG_WITH_TEST\n#----------------------------\ndnl GLIB_PATH_PROG_WITH_TEST(VARIABLE, PROG-TO-CHECK-FOR,\ndnl   TEST-PERFORMED-ON-FOUND_PROGRAM [, VALUE-IF-NOT-FOUND [, PATH]])\nglib_DEFUN([GLIB_PATH_PROG_WITH_TEST],\n[# Extract the first word of \"$2\", so it can be a program name with args.\nset dummy $2; ac_word=[$]2\nAC_MSG_CHECKING([for $ac_word])\nAC_CACHE_VAL(ac_cv_path_$1,\n[case \"[$]$1\" in\n  /*)\n  ac_cv_path_$1=\"[$]$1\" # Let the user override the test with a path.\n  ;;\n  *)\n  IFS=\"${IFS= \t}\"; ac_save_ifs=\"$IFS\"; IFS=\"${IFS}:\"\n  for ac_dir in ifelse([$5], , $PATH, [$5]); do\n    test -z \"$ac_dir\" && ac_dir=.\n    if test -f $ac_dir/$ac_word; then\n      if [$3]; then\n\tac_cv_path_$1=\"$ac_dir/$ac_word\"\n\tbreak\n      fi\n    fi\n  done\n  IFS=\"$ac_save_ifs\"\ndnl If no 4th arg is given, leave the cache variable unset,\ndnl so AC_PATH_PROGS will keep looking.\nifelse([$4], , , [  test -z \"[$]ac_cv_path_$1\" && ac_cv_path_$1=\"$4\"\n])dnl\n  ;;\nesac])dnl\n$1=\"$ac_cv_path_$1\"\nif test ifelse([$4], , [-n \"[$]$1\"], [\"[$]$1\" != \"$4\"]); then\n  AC_MSG_RESULT([$]$1)\nelse\n  AC_MSG_RESULT(no)\nfi\nAC_SUBST($1)dnl\n])\n\n# GLIB_WITH_NLS\n#-----------------\nglib_DEFUN([GLIB_WITH_NLS],\n  dnl NLS is obligatory\n  [USE_NLS=yes\n    AC_SUBST(USE_NLS)\n\n    gt_cv_have_gettext=no\n\n    CATOBJEXT=NONE\n    XGETTEXT=:\n    INTLLIBS=\n\n    AC_CHECK_HEADER(libintl.h,\n     [gt_cv_func_dgettext_libintl=\"no\"\n      libintl_extra_libs=\"\"\n\n      #\n      # First check in libc\n      #\n      AC_CACHE_CHECK([for ngettext in libc], gt_cv_func_ngettext_libc,\n        [AC_TRY_LINK([\n#include <libintl.h>\n],\n         [return !ngettext (\"\",\"\", 1)],\n\t  gt_cv_func_ngettext_libc=yes,\n          gt_cv_func_ngettext_libc=no)\n        ])\n  \n      if test \"$gt_cv_func_ngettext_libc\" = \"yes\" ; then\n\t      AC_CACHE_CHECK([for dgettext in libc], gt_cv_func_dgettext_libc,\n        \t[AC_TRY_LINK([\n#include <libintl.h>\n],\n\t          [return !dgettext (\"\",\"\")],\n\t\t  gt_cv_func_dgettext_libc=yes,\n\t          gt_cv_func_dgettext_libc=no)\n        \t])\n      fi\n  \n      if test \"$gt_cv_func_ngettext_libc\" = \"yes\" ; then\n        AC_CHECK_FUNCS(bind_textdomain_codeset)\n      fi\n\n      #\n      # If we don't have everything we want, check in libintl\n      #\n      if test \"$gt_cv_func_dgettext_libc\" != \"yes\" \\\n\t || test \"$gt_cv_func_ngettext_libc\" != \"yes\" \\\n         || test \"$ac_cv_func_bind_textdomain_codeset\" != \"yes\" ; then\n        \n        AC_CHECK_LIB(intl, bindtextdomain,\n\t    [AC_CHECK_LIB(intl, ngettext,\n\t\t    [AC_CHECK_LIB(intl, dgettext,\n\t\t\t          gt_cv_func_dgettext_libintl=yes)])])\n\n\tif test \"$gt_cv_func_dgettext_libintl\" != \"yes\" ; then\n\t  AC_MSG_CHECKING([if -liconv is needed to use gettext])\n\t  AC_MSG_RESULT([])\n  \t  AC_CHECK_LIB(intl, ngettext,\n          \t[AC_CHECK_LIB(intl, dcgettext,\n\t\t       [gt_cv_func_dgettext_libintl=yes\n\t\t\tlibintl_extra_libs=-liconv],\n\t\t\t:,-liconv)],\n\t\t:,-liconv)\n        fi\n\n        #\n        # If we found libintl, then check in it for bind_textdomain_codeset();\n        # we'll prefer libc if neither have bind_textdomain_codeset(),\n        # and both have dgettext and ngettext\n        #\n        if test \"$gt_cv_func_dgettext_libintl\" = \"yes\" ; then\n          glib_save_LIBS=\"$LIBS\"\n          LIBS=\"$LIBS -lintl $libintl_extra_libs\"\n          unset ac_cv_func_bind_textdomain_codeset\n          AC_CHECK_FUNCS(bind_textdomain_codeset)\n          LIBS=\"$glib_save_LIBS\"\n\n          if test \"$ac_cv_func_bind_textdomain_codeset\" = \"yes\" ; then\n            gt_cv_func_dgettext_libc=no\n          else\n            if test \"$gt_cv_func_dgettext_libc\" = \"yes\" \\\n\t\t&& test \"$gt_cv_func_ngettext_libc\" = \"yes\"; then\n              gt_cv_func_dgettext_libintl=no\n            fi\n          fi\n        fi\n      fi\n\n      if test \"$gt_cv_func_dgettext_libc\" = \"yes\" \\\n\t|| test \"$gt_cv_func_dgettext_libintl\" = \"yes\"; then\n        gt_cv_have_gettext=yes\n      fi\n  \n      if test \"$gt_cv_func_dgettext_libintl\" = \"yes\"; then\n        INTLLIBS=\"-lintl $libintl_extra_libs\"\n      fi\n  \n      if test \"$gt_cv_have_gettext\" = \"yes\"; then\n\tAC_DEFINE(HAVE_GETTEXT,1,\n\t  [Define if the GNU gettext() function is already present or preinstalled.])\n\tGLIB_PATH_PROG_WITH_TEST(MSGFMT, msgfmt,\n\t  [test -z \"`$ac_dir/$ac_word -h 2>&1 | grep 'dv '`\"], no)dnl\n\tif test \"$MSGFMT\" != \"no\"; then\n          glib_save_LIBS=\"$LIBS\"\n          LIBS=\"$LIBS $INTLLIBS\"\n\t  AC_CHECK_FUNCS(dcgettext)\n\t  AC_PATH_PROG(GMSGFMT, gmsgfmt, $MSGFMT)\n\t  GLIB_PATH_PROG_WITH_TEST(XGETTEXT, xgettext,\n\t    [test -z \"`$ac_dir/$ac_word -h 2>&1 | grep '(HELP)'`\"], :)\n\t  AC_TRY_LINK(, [extern int _nl_msg_cat_cntr;\n\t\t\t return _nl_msg_cat_cntr],\n\t    [CATOBJEXT=.gmo \n             DATADIRNAME=share],\n\t    [case $host in\n\t    *-*-solaris*)\n\t    dnl On Solaris, if bind_textdomain_codeset is in libc,\n\t    dnl GNU format message catalog is always supported,\n            dnl since both are added to the libc all together.\n\t    dnl Hence, we'd like to go with DATADIRNAME=share and\n\t    dnl and CATOBJEXT=.gmo in this case.\n            AC_CHECK_FUNC(bind_textdomain_codeset,\n\t      [CATOBJEXT=.gmo \n               DATADIRNAME=share],\n\t      [CATOBJEXT=.mo\n               DATADIRNAME=lib])\n\t    ;;\n\t    *)\n\t    CATOBJEXT=.mo\n            DATADIRNAME=lib\n\t    ;;\n\t    esac])\n          LIBS=\"$glib_save_LIBS\"\n\t  INSTOBJEXT=.mo\n\telse\n\t  gt_cv_have_gettext=no\n\tfi\n      fi\n    ])\n\n    if test \"$gt_cv_have_gettext\" = \"yes\" ; then\n      AC_DEFINE(ENABLE_NLS, 1,\n        [always defined to indicate that i18n is enabled])\n    fi\n\n    dnl Test whether we really found GNU xgettext.\n    if test \"$XGETTEXT\" != \":\"; then\n      dnl If it is not GNU xgettext we define it as : so that the\n      dnl Makefiles still can work.\n      if $XGETTEXT --omit-header /dev/null 2> /dev/null; then\n        : ;\n      else\n        AC_MSG_RESULT(\n\t  [found xgettext program is not GNU xgettext; ignore it])\n        XGETTEXT=\":\"\n      fi\n    fi\n\n    # We need to process the po/ directory.\n    POSUB=po\n\n    AC_OUTPUT_COMMANDS(\n      [case \"$CONFIG_FILES\" in *po/Makefile.in*)\n        sed -e \"/POTFILES =/r po/POTFILES\" po/Makefile.in > po/Makefile\n      esac])\n\n    dnl These rules are solely for the distribution goal.  While doing this\n    dnl we only have to keep exactly one list of the available catalogs\n    dnl in configure.in.\n    for lang in $ALL_LINGUAS; do\n      GMOFILES=\"$GMOFILES $lang.gmo\"\n      POFILES=\"$POFILES $lang.po\"\n    done\n\n    dnl Make all variables we use known to autoconf.\n    AC_SUBST(CATALOGS)\n    AC_SUBST(CATOBJEXT)\n    AC_SUBST(DATADIRNAME)\n    AC_SUBST(GMOFILES)\n    AC_SUBST(INSTOBJEXT)\n    AC_SUBST(INTLLIBS)\n    AC_SUBST(PO_IN_DATADIR_TRUE)\n    AC_SUBST(PO_IN_DATADIR_FALSE)\n    AC_SUBST(POFILES)\n    AC_SUBST(POSUB)\n  ])\n\n# AM_GLIB_GNU_GETTEXT\n# -------------------\n# Do checks necessary for use of gettext. If a suitable implementation \n# of gettext is found in either in libintl or in the C library,\n# it will set INTLLIBS to the libraries needed for use of gettext\n# and AC_DEFINE() HAVE_GETTEXT and ENABLE_NLS. (The shell variable\n# gt_cv_have_gettext will be set to \"yes\".) It will also call AC_SUBST()\n# on various variables needed by the Makefile.in.in installed by \n# glib-gettextize.\ndnl\nglib_DEFUN([GLIB_GNU_GETTEXT],\n  [AC_REQUIRE([AC_PROG_CC])dnl\n   AC_REQUIRE([AC_HEADER_STDC])dnl\n   \n   GLIB_LC_MESSAGES\n   GLIB_WITH_NLS\n\n   if test \"$gt_cv_have_gettext\" = \"yes\"; then\n     if test \"x$ALL_LINGUAS\" = \"x\"; then\n       LINGUAS=\n     else\n       AC_MSG_CHECKING(for catalogs to be installed)\n       NEW_LINGUAS=\n       for presentlang in $ALL_LINGUAS; do\n         useit=no\n         if test \"%UNSET%\" != \"${LINGUAS-%UNSET%}\"; then\n           desiredlanguages=\"$LINGUAS\"\n         else\n           desiredlanguages=\"$ALL_LINGUAS\"\n         fi\n         for desiredlang in $desiredlanguages; do\n \t   # Use the presentlang catalog if desiredlang is\n           #   a. equal to presentlang, or\n           #   b. a variant of presentlang (because in this case,\n           #      presentlang can be used as a fallback for messages\n           #      which are not translated in the desiredlang catalog).\n           case \"$desiredlang\" in\n             \"$presentlang\"*) useit=yes;;\n           esac\n         done\n         if test $useit = yes; then\n           NEW_LINGUAS=\"$NEW_LINGUAS $presentlang\"\n         fi\n       done\n       LINGUAS=$NEW_LINGUAS\n       AC_MSG_RESULT($LINGUAS)\n     fi\n\n     dnl Construct list of names of catalog files to be constructed.\n     if test -n \"$LINGUAS\"; then\n       for lang in $LINGUAS; do CATALOGS=\"$CATALOGS $lang$CATOBJEXT\"; done\n     fi\n   fi\n\n   dnl If the AC_CONFIG_AUX_DIR macro for autoconf is used we possibly\n   dnl find the mkinstalldirs script in another subdir but ($top_srcdir).\n   dnl Try to locate is.\n   MKINSTALLDIRS=\n   if test -n \"$ac_aux_dir\"; then\n     MKINSTALLDIRS=\"$ac_aux_dir/mkinstalldirs\"\n   fi\n   if test -z \"$MKINSTALLDIRS\"; then\n     MKINSTALLDIRS=\"\\$(top_srcdir)/mkinstalldirs\"\n   fi\n   AC_SUBST(MKINSTALLDIRS)\n\n   dnl Generate list of files to be processed by xgettext which will\n   dnl be included in po/Makefile.\n   test -d po || mkdir po\n   if test \"x$srcdir\" != \"x.\"; then\n     if test \"x`echo $srcdir | sed 's@/.*@@'`\" = \"x\"; then\n       posrcprefix=\"$srcdir/\"\n     else\n       posrcprefix=\"../$srcdir/\"\n     fi\n   else\n     posrcprefix=\"../\"\n   fi\n   rm -f po/POTFILES\n   sed -e \"/^#/d\" -e \"/^\\$/d\" -e \"s,.*,\t$posrcprefix& \\\\\\\\,\" -e \"\\$s/\\(.*\\) \\\\\\\\/\\1/\" \\\n\t< $srcdir/po/POTFILES.in > po/POTFILES\n  ])\n\n# AM_GLIB_DEFINE_LOCALEDIR(VARIABLE)\n# -------------------------------\n# Define VARIABLE to the location where catalog files will\n# be installed by po/Makefile.\nglib_DEFUN([GLIB_DEFINE_LOCALEDIR],\n[glib_REQUIRE([GLIB_GNU_GETTEXT])dnl\nglib_save_prefix=\"$prefix\"\nglib_save_exec_prefix=\"$exec_prefix\"\ntest \"x$prefix\" = xNONE && prefix=$ac_default_prefix\ntest \"x$exec_prefix\" = xNONE && exec_prefix=$prefix\nif test \"x$CATOBJEXT\" = \"x.mo\" ; then\n  localedir=`eval echo \"${libdir}/locale\"`\nelse\n  localedir=`eval echo \"${datadir}/locale\"`\nfi\nprefix=\"$glib_save_prefix\"\nexec_prefix=\"$glib_save_exec_prefix\"\nAC_DEFINE_UNQUOTED($1, \"$localedir\",\n  [Define the location where the catalogs will be installed])\n])\n\ndnl\ndnl Now the definitions that aclocal will find\ndnl\nifdef(glib_configure_in,[],[\nAC_DEFUN([AM_GLIB_GNU_GETTEXT],[GLIB_GNU_GETTEXT($@)])\nAC_DEFUN([AM_GLIB_DEFINE_LOCALEDIR],[GLIB_DEFINE_LOCALEDIR($@)])\n])dnl\n"
  },
  {
    "path": "m4/python.m4",
    "content": "## this one is commonly used with AM_PATH_PYTHONDIR ...\ndnl AM_CHECK_PYMOD(MODNAME [,SYMBOL [,ACTION-IF-FOUND [,ACTION-IF-NOT-FOUND]]])\ndnl Check if a module containing a given symbol is visible to python.\nAC_DEFUN([AM_CHECK_PYMOD],\n[AC_REQUIRE([AM_PATH_PYTHON])\npy_mod_var=`echo $1['_']$2 | sed 'y%./+-%__p_%'`\nAC_MSG_CHECKING(for ifelse([$2],[],,[$2 in ])python module $1)\nAC_CACHE_VAL(py_cv_mod_$py_mod_var, [\nifelse([$2],[], [prog=\"\nimport sys\ntry:\n        import $1\nexcept ImportError:\n        sys.exit(1)\nexcept:\n        sys.exit(0)\nsys.exit(0)\"], [prog=\"\nimport $1\n$1.$2\"])\nif $PYTHON -c \"$prog\" 1>&AC_FD_CC 2>&AC_FD_CC\n  then\n    eval \"py_cv_mod_$py_mod_var=yes\"\n  else\n    eval \"py_cv_mod_$py_mod_var=no\"\n  fi\n])\npy_val=`eval \"echo \\`echo '$py_cv_mod_'$py_mod_var\\`\"`\nif test \"x$py_val\" != xno; then\n  AC_MSG_RESULT(yes)\n  ifelse([$3], [],, [$3\n])dnl\nelse\n  AC_MSG_RESULT(no)\n  ifelse([$4], [],, [$4\n])dnl\nfi\n])\n\ndnl a macro to check for ability to create python extensions\ndnl  AM_CHECK_PYTHON_HEADERS([ACTION-IF-POSSIBLE], [ACTION-IF-NOT-POSSIBLE])\ndnl function also defines PYTHON_INCLUDES\nAC_DEFUN([AM_CHECK_PYTHON_HEADERS],\n[AC_REQUIRE([AM_PATH_PYTHON])\nAC_MSG_CHECKING(for headers required to compile python extensions)\ndnl deduce PYTHON_INCLUDES\npy_prefix=`$PYTHON -c \"import sys; print sys.prefix\"`\npy_exec_prefix=`$PYTHON -c \"import sys; print sys.exec_prefix\"`\nif test -x \"$PYTHON-config\"; then\nPYTHON_INCLUDES=`$PYTHON-config --includes 2>/dev/null`\nelse\nPYTHON_INCLUDES=\"-I${py_prefix}/include/python${PYTHON_VERSION}\"\nif test \"$py_prefix\" != \"$py_exec_prefix\"; then\n  PYTHON_INCLUDES=\"$PYTHON_INCLUDES -I${py_exec_prefix}/include/python${PYTHON_VERSION}\"\nfi\nfi\nAC_SUBST(PYTHON_INCLUDES)\ndnl check if the headers exist:\nsave_CPPFLAGS=\"$CPPFLAGS\"\nCPPFLAGS=\"$CPPFLAGS $PYTHON_INCLUDES\"\nAC_TRY_CPP([#include <Python.h>],dnl\n[AC_MSG_RESULT(found)\n$1],dnl\n[AC_MSG_RESULT(not found)\n$2])\nCPPFLAGS=\"$save_CPPFLAGS\"\n])\n"
  },
  {
    "path": "msi/Includes.wxi",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Include Id=\"SeafileInclude\">\n  <?define CurrentSeafileVersion=\"9.0.16\" ?>\n\n  <!-- Update Guid 不能变 -->\n  <?define CurrentUpdateGuid=\"65DED1C8-A5F1-4C49-8E7E-B0A8A5A6535C\" ?>\n\n  <!-- 每个新版本必须重新生成 Product Guid  -->\n  <!-- <?define ProductGuid=\"5D46888B-A960-4E37-9468-54EFB323610F\" ?> (0.9.3) -->\n  <!-- <?define ProductGuid=\"9ADCC6CF-41E8-4CB7-B48B-5CC87867DF20\" ?> (0.9.4) -->\n  <!-- <?define ProductGuid=\"137667A1-02F1-4BE3-9B1A-EC0B3DB23ED2\" ?> (0.9.5) -->\n  <!-- <?define ProductGuid=\"DB1C9EB4-F629-43AE-8B46-F5596AC8B8F1\" ?> (1.0.0 beta) -->\n  <!-- <?define ProductGuid=\"A00383E1-F8CB-11E1-8F71-000C295059FB\" ?> (1.0.0) -->\n  <!-- <?define ProductGuid=\"FC863C7E-32AA-48E2-972F-C8080F54B3CD\" ?> (1.1.0) -->\n  <!-- <?define ProductGuid=\"3DDA4ED6-C4AC-4E11-936C-A2D6664ADA10\" ?> (1.2.0) -->\n  <!-- <?define ProductGuid=\"6de4ed2a-5bdb-4160-bf41-0e003c9509be\" ?> (1.2.1) -->\n  <!-- <?define ProductGuid=\"935C636F-81DF-496C-A2F1-D949069CCF8D\" ?> (1.2.2) -->\n  <!-- <?define ProductGuid=\"E0CE1E1E-96EC-40BF-88D1-A7D85FD82F19\" ?> (1.3.0) -->\n  <!-- <?define ProductGuid=\"A4C87A81-208A-4DDB-850E-E699FC12E893\" ?> (1.3.5) -->\n  <!-- <?define ProductGuid=\"592E73F0-8619-4DE3-80DD-FA6844E64880\" ?> (1.4.0) -->\n  <!-- <?define ProductGuid=\"B2A1DFCB-A235-4B67-AE98-987558BDD334\" ?> (1.4.1) -->\n  <!-- <?define ProductGuid=\"580CBEF0-91A5-46A5-9DAB-25FE8374B705\" ?> (1.4.5) -->\n  <!-- <?define ProductGuid=\"5CD4E6CA-AA2F-4EB4-A5A1-A4650956B003\" ?> (1.5.0) -->\n  <!-- <?define ProductGuid=\"2E37D3C3-084B-45E8-9E9D-04C0152FCA65\" ?> (1.5.1) -->\n  <!-- <?define ProductGuid=\"CBD91D24-6B02-4D20-9297-BD519884079A\" ?> (1.5.2) -->\n  <!-- <?define ProductGuid=\"888653BD-F9E3-4D3C-867F-7382DA31C00B\" ?> (1.5.3) -->\n  <!-- <?define ProductGuid=\"D6BE2CB5-B52A-42BE-AA89-F8C842C7F16B\" ?> (1.6.0) -->\n  <!-- <?define ProductGuid=\"63922BE1-F3A7-4B56-8297-DDF78364048F\" ?> (1.6.1) -->\n  <!-- <?define ProductGuid=\"0C525291-4BAD-42C0-87D0-645804E71132\" ?> (1.7.0) -->\n  <!-- <?define ProductGuid=\"3EF19722-2CD7-423E-9C0A-BFEC8963BB52\" ?> (1.7.1) -->\n  <!-- <?define ProductGuid=\"2AE0933B-F2E9-4937-94A7-9F90BC05A893\" ?> (1.7.2) -->\n  <!-- <?define ProductGuid=\"116D6580-A119-4935-B274-89347C7D5FF1\" ?> (1.7.3) -->\n  <!-- <?define ProductGuid=\"1AE02690-79D7-4BA7-B452-CE7E64ED68ED\" ?> (1.8.0) -->\n  <!-- <?define ProductGuid=\"AA77D6EF-10CB-4D15-BB2E-2428BD1C7923\" ?> (1.8.1) -->\n  <!-- <?define ProductGuid=\"5C7ADC29-0EC5-42D6-93A6-63034AE9D0B9\" ?> (1.8.5) -->\n  <!-- <?define ProductGuid=\"D4943E59-9300-45AB-914D-742948102C14\" ?> (2.0.0) -->\n  <!-- <?define ProductGuid=\"670FEF9B-4631-43AA-9626-9A73724A760F\" ?> (2.0.1) -->\n  <!-- <?define ProductGuid=\"1DC3FDDB-719A-4E1C-B95F-A2579CE0449D\" ?> (2.0.3) -->\n  <!-- <?define ProductGuid=\"10657476-FEAE-46AB-A30D-AF258A76E028\" ?> (2.0.5) -->\n  <!-- <?define ProductGuid=\"9AC602C9-93DB-4B58-B2A9-2A6B322D5A9B\" ?> (2.0.6) -->\n  <!-- <?define ProductGuid=\"07450896-3aaf-4796-a18b-3218a67419c4\" ?> (2.0.7) -->\n  <!-- <?define ProductGuid=\"A616FA7D-C305-400C-A704-E4D7FB397F13\" ?> (2.0.8) -->\n  <!-- <?define ProductGuid=\"30A5BAA2-C88B-423C-8431-1AED54512EE9\" ?> (2.1.0) -->\n  <!-- <?define ProductGuid=\"564A3B78-E686-44FF-8632-A9F122915750\" ?> (2.1.1) -->\n  <!-- <?define ProductGuid=\"AA16C39B-B22C-4883-8E7F-849E869EC5CD\" ?> (2.1.2) -->\n  <!-- <?define ProductGuid=\"FC343A8B-6CBD-4497-9CA6-DB1992C61B4D\" ?> (2.2.0) -->\n  <!-- <?define ProductGuid=\"4F28D533-9075-4065-A5B4-14B7F55BD485\" ?> (3.0.0) -->\n  <!-- <?define ProductGuid=\"9784787F-28AD-4B52-BCC6-34460E24D20E\" ?> (3.0.1) -->\n  <!-- <?define ProductGuid=\"2F2559B7-9B08-4386-B5CD-4466930A9EA6\" ?> (3.0.2) -->\n  <!-- <?define ProductGuid=\"CE1C40E9-F48D-495B-8F45-66C01A6F0EA7\" ?> (3.0.3) -->\n  <!-- <?define ProductGuid=\"0D56A3F4-7600-4280-91F8-0CDC43D5BFE3\" ?> (3.0.4) -->\n  <!-- <?define ProductGuid=\"5EB6EDC8-AF37-4A6E-99F3-E98CFE8A85F1\" ?> (3.1.0) -->\n  <!-- <?define ProductGuid=\"9F0AE791-879D-47F8-8764-C0BEF7FE670D\" ?> (3.1.1) -->\n  <!-- <?define ProductGuid=\"81BBDA6E-7F91-465D-A49B-52FBD9A07F4F\" ?> (3.1.2) -->\n  <!-- <?define ProductGuid=\"3815CFCA-FDFF-4D3C-88E7-EDBEE2560094\" ?> (3.1.3) -->\n  <!-- <?define ProductGuid=\"248D333B-3B95-4E2C-A366-1EAD9A116F87\" ?> (3.1.4) -->\n  <!-- <?define ProductGuid=\"36B756D6-ED10-41E1-A1AF-F6FF413650DE\" ?> (3.1.5) -->\n  <!-- <?define ProductGuid=\"DEBE2F11-4C1F-4616-8105-A3D5CF42F2A2\" ?> (3.1.6) -->\n  <!-- <?define ProductGuid=\"146A15A5-B784-4681-8056-A0F55F743327\" ?> (3.1.7) -->\n  <!-- <?define ProductGuid=\"ACF30931-E8E8-4DBF-AEE5-3E9E7865BCBA\" ?> (3.1.8) -->\n  <!-- <?define ProductGuid=\"0CAD4624-1154-4FC4-A0B7-29BA68EC74D3\" ?> (3.1.9) -->\n  <!-- <?define ProductGuid=\"3EC81DEE-0FC0-46D0-8E4C-244BDA4C2F22\" ?> (3.1.10) -->\n  <!-- <?define ProductGuid=\"6FAA9EDB-6788-41EF-AC09-3C74D39C0C6C\" ?> (3.1.11) -->\n  <!-- <?define ProductGuid=\"ACF30931-E8E8-4DBF-AEE5-3E9E7865BCBA\" ?> (4.0.0) -->\n  <!-- <?define ProductGuid=\"06DB6611-96A3-4006-A2C5-1A13429D1487\" ?> (4.0.1) -->\n  <!-- <?define ProductGuid=\"56A12178-AE8A-4A34-890C-E84B47B3E27E\" ?> (4.0.2) -->\n  <!-- <?define ProductGuid=\"D822AAC1-8042-4757-A45C-B4617F21D254\" ?> (4.0.3) -->\n  <!-- <?define ProductGuid=\"41E18CDA-69BC-4E5E-BB17-424D8D01F1F9\" ?> (4.0.4) -->\n  <!-- <?define ProductGuid=\"AC9AE580-FDE2-472F-8BF1-B14C08F8A654\" ?> (4.0.5) -->\n  <!-- <?define ProductGuid=\"667B2C51-4D80-4682-A377-65A9DD0A596A\" ?> (4.0.6) -->\n  <!-- <?define ProductGuid=\"E71A7DA2-755A-4AD1-8D30-F0010F8921E7\" ?> (4.0.7) -->\n  <!-- <?define ProductGuid=\"A4B22D3C-4F29-4163-8717-5326694313EA\" ?> (4.1.0) -->\n  <!-- <?define ProductGuid=\"337D1D82-548F-48A1-9C34-D9EABD346A4B\" ?> (4.1.1) -->\n  <!-- <?define ProductGuid=\"CB83A552-0214-473B-8FAD-2DADBF4DEA63\" ?> (4.1.2) -->\n  <!-- <?define ProductGuid=\"3AC9D6C4-4F26-4736-BDEB-8FE9D3BB6323\" ?> (4.1.3) -->\n  <!-- <?define ProductGuid=\"5FB6B6A1-37BC-4124-89CB-455A537D7B66\" ?> (4.1.4) -->\n  <!-- <?define ProductGuid=\"219CA573-0DCB-441B-8FCD-F9343442A83A\" ?> (4.1.5) -->\n  <!-- <?define ProductGuid=\"9B1C353D-F554-4C48-8C1D-A07C69877C11\" ?> (4.1.6) -->\n  <!-- <?define ProductGuid=\"886BABA1-811B-4C7C-A775-E22FCF1149A4\" ?> (4.2.0) -->\n  <!-- <?define ProductGuid=\"5D2BBA1C-F9B0-48B2-BDD5-13F4F5F538E0\" ?> (4.2.1) -->\n  <!-- <?define ProductGuid=\"11FDE763-87F3-4335-A408-DD10E1A2DBBE\" ?> (4.2.2) -->\n  <!-- <?define ProductGuid=\"D7DA2D13-3B7D-4418-A3A3-1425241CDCE5\" ?> (4.2.3) -->\n  <!-- <?define ProductGuid=\"B71EF658-343C-4878-9EB6-881BAA97DC6C\" ?> (4.2.4) -->\n  <!-- <?define ProductGuid=\"E4C66301-4D5C-4FF6-AB52-DD085F304182\" ?> (4.2.5) -->\n  <!-- <?define ProductGuid=\"DA4C6BCF-2CE7-4C5C-84F6-D8BE8DF155EE\" ?> (4.2.6) -->\n  <!-- <?define ProductGuid=\"2944E1FE-E3F4-4F0E-ABF1-9806D7831DCE\" ?> (4.2.7) -->\n  <!-- <?define ProductGuid=\"E97F8AB4-568B-48D3-8573-1A12A8F84D5A\" ?> (4.3.0) -->\n  <!-- <?define ProductGuid=\"925C6446-DC8A-410A-B1C1-19929196339A\" ?> (4.3.1) -->\n  <!-- <?define ProductGuid=\"311D27FD-AEC0-49AC-BE02-31F530A3EDEF\" ?> (4.3.2) -->\n  <!-- <?define ProductGuid=\"916A611A-801A-4EBA-9A6E-40C6AF7BB111\" ?> (4.3.3) -->\n  <!-- <?define ProductGuid=\"B8A63E43-798C-4764-9CCD-4B3F5DF71D57\" ?> (4.3.4) -->\n  <!-- <?define ProductGuid=\"547C775A-D7AE-4CB5-9387-694EEC49853A\" ?> (4.4.0) -->\n  <!-- <?define ProductGuid=\"E90F818C-243B-44A2-9A78-A7475152A6B1\" ?> (4.4.1) -->\n  <!-- <?define ProductGuid=\"9EB9A762-9B6F-4D43-84DD-77DFE26B4B6D\" ?> (4.4.2) -->\n  <!-- <?define ProductGuid=\"E9CB3037-1812-445F-A133-F92D40EE6478\" ?> (5.0.0) -->\n  <!-- <?define ProductGuid=\"8CA6D282-E172-4AF1-B71A-9A3F9A50813D\" ?> (5.0.1) -->\n  <!-- <?define ProductGuid=\"E22D308B-761C-4016-926B-B73C1A2FD473\" ?> (5.0.2) -->\n  <!-- <?define ProductGuid=\"C6EB3C44-0E0A-4DDA-99D3-9B41F53DBEAF\" ?> (5.0.3) -->\n  <!-- <?define ProductGuid=\"6B70C0B6-8F5D-4DD3-B5F2-21631FBAD011\" ?> (5.0.4) -->\n  <!-- <?define ProductGuid=\"794D49EC-F828-425B-A714-7E77E7E2843A\" ?> (5.0.5) -->\n  <!-- <?define ProductGuid=\"CDE8D2F5-84FB-45DF-86A8-2D83B38F1F80\" ?> (5.0.6) -->\n  <!-- <?define ProductGuid=\"36FB84CB-22BC-4FA3-8BD2-52604FC97889\" ?> (5.0.7) -->\n  <!-- <?define ProductGuid=\"51E67F11-EFD6-4A14-ACC5-F1BD070E26FF\" ?> (5.1.0) -->\n  <!-- <?define ProductGuid=\"F2F6A6CE-D255-4C77-908A-47DDD2DC8909\" ?> (5.1.1) -->\n  <!-- <?define ProductGuid=\"68916720-E82C-4E1C-9276-C27CC345958F\" ?> (5.1.2) -->\n  <!-- <?define ProductGuid=\"91E61552-02F8-42B1-A07D-3848D26AEDCD\" ?> (5.1.3) -->\n  <!-- <?define ProductGuid=\"8A1698E3-92C5-40BB-A22D-501B42462406\" ?> (5.1.4) -->\n  <!-- <?define ProductGuid=\"3AEA122D-C7E8-45C4-8E5E-44A8EC4367B0\" ?> (6.0.0) -->\n  <!-- <?define ProductGuid=\"B101E268-D594-46F6-B63D-E588EEE8A034\" ?> (6.0.1) -->\n  <!-- <?define ProductGuid=\"8C8B8595-6F49-4330-AF6C-F3C538F86735\" ?> (6.0.2) -->\n  <!-- <?define ProductGuid=\"243265EE-AC28-4DC0-8924-2C053162E7C1\" ?> (6.0.3) -->\n  <!-- <?define ProductGuid=\"AB555D53-FA35-4907-AFEF-BBC2DB45B6F4\" ?> (6.0.4) -->\n  <!-- <?define ProductGuid=\"2B1F1C4C-2E90-478D-B1D2-9CEC389AE0D8\" ?> (6.0.6) -->\n  <!-- <?define ProductGuid=\"1813B8A3-A52B-4293-B166-DF5A517480AC\" ?> (6.0.7) -->\n  <!-- <?define ProductGuid=\"9AF9DA7F-436C-4519-A932-F543165F0E1A\" ?> (6.1.0) -->\n  <!-- <?define ProductGuid=\"38269809-1417-4275-882B-FB92A69904AC\" ?> (6.1.1) -->\n  <!-- <?define ProductGuid=\"FE039BE0-4B78-48AE-8453-A016810303E0\" ?> (6.1.2) -->\n  <!-- <?define ProductGuid=\"49EA51FA-BDA0-44A3-A89A-1F355B8A40D7\" ?> (6.1.3) -->\n  <!-- <?define ProductGuid=\"D984BE8C-C01C-42F3-B5DE-2DB03657E29B\" ?> (6.1.4) -->\n  <!-- <?define ProductGuid=\"0E39500A-BAF7-4201-B955-C4E0762B85F1\" ?> (6.1.5) -->\n  <!-- <?define ProductGuid=\"53BBECBF-F2C6-4B5E-A9A8-A115C513E521\" ?> (6.1.6) -->\n  <!-- <?define ProductGuid=\"D67C7342-2DE3-485C-B8E2-5838EC64F49D\" ?> (6.1.7) -->\n  <!-- <?define ProductGuid=\"7A5BD4DF-4698-415C-ADA8-A16009B73F37\" ?> (6.1.8) -->\n  <!-- <?define ProductGuid=\"30C539BA-90F0-4F06-88E0-6BBEFB038EBE\" ?> (6.2.0) -->\n  <!-- <?define ProductGuid=\"63B2D0B3-2130-4FC5-A2B8-3680399C65B2\" ?> (6.2.1) -->\n  <!-- <?define ProductGuid=\"BA879197-CBF6-4DFB-BD56-E20A2854AFF9\" ?> (6.2.2) -->\n  <!-- <?define ProductGuid=\"93D5D0EB-C2A5-41D3-BCFD-7D876CED74A3\" ?> (6.2.3) -->\n  <!-- <?define ProductGuid=\"51C2F305-9C2A-4DB2-84A0-BE504813ADAF\" ?> (6.2.4) -->\n  <!-- <?define ProductGuid=\"CD288D11-BBB8-4CAB-80E2-25B499F0E0D5\" ?> (6.2.5) -->\n  <!-- <?define ProductGuid=\"1809B194-5373-429E-A32C-5188B3A05679\" ?> (6.2.7) -->\n  <!-- <?define ProductGuid=\"FABC1D45-7C57-4D03-B136-669F66B8CC62\" ?> (6.2.8) -->\n  <!-- <?define ProductGuid=\"4B890A1D-FBC4-427D-AF56-3A4FE393318A\" ?> (6.2.9) -->\n  <!-- <?define ProductGuid=\"8F51E7B1-AE22-4AdC-8375-2675595F6D0B\" ?> (6.2.10) -->\n  <!-- <?define ProductGuid=\"8284058D-781C-48BC-95C3-9B3146AEFA79\" ?> (6.2.11) -->\n  <!-- <?define ProductGuid=\"9486761D-9F70-4E8E-AA1B-D460F1EF4F42\" ?> (7.0.0) -->\n  <!-- <?define ProductGuid=\"706266F6-B2C5-4170-9DB1-0F4A586E614F\" ?> (7.0.1) -->\n  <!-- <?define ProductGuid=\"86D3A1B2-E429-4E75-8367-A6907FFAB6E4\" ?> (7.0.2) -->\n  <!-- <?define ProductGuid=\"DA63DC60-BAAD-45CF-B1CF-AECF7CDD8842\" ?> (7.0.3) -->\n  <!-- <?define ProductGuid=\"A88599F1-1D4D-4E7D-AEFD-53EA5CFDF9E7\" ?> (7.0.4) -->\n  <!-- <?define ProductGuid=\"3A3D3777-A5C1-4089-A2F7-33D55A36E41E\" ?> (7.0.5) -->\n  <!-- <?define ProductGuid=\"9A180890-0EED-4E38-BAB0-A6E148D89F4A\" ?> (7.0.6) -->\n  <!-- <?define ProductGuid=\"1E617234-A273-41F9-8BA7-70C6F1644AC4\" ?> (7.0.7) -->\n  <!-- <?define ProductGuid=\"7944DDB5-1C68-4C9F-9FA5-01224493EFF0\" ?> (7.0.8) -->\n  <!-- <?define ProductGuid=\"C54F03E9-9522-4C70-8134-5199BD74AFBA\" ?> (7.0.9) -->\n  <!-- <?define ProductGuid=\"D21D1108-878F-43D9-A3A2-39F4B38A5694\" ?> (7.0.10) -->\n  <!-- <?define ProductGuid=\"CE032912-6DCF-4C5F-A447-AC303D0530A7\" ?> (8.0.0) -->\n  <!-- <?define ProductGuid=\"93B20E38-EFF9-4CD8-BFF4-F0DF21DEADB8\" ?> (8.0.1) -->\n  <!-- <?define ProductGuid=\"8F8A6FBA-EAD1-4B16-B074-03C1BA4E1109\" ?> (8.0.2) -->\n  <!-- <?define ProductGuid=\"82A1C721-6B4E-473B-B515-8D0B9242C6A6\" ?> (8.0.3) -->\n  <!-- <?define ProductGuid=\"B4ACB0CA-854E-4087-B9F5-7382924D7C49\" ?> (8.0.4) -->\n  <!-- <?define ProductGuid=\"02E0FA9C-6C8A-4F87-924B-D33EF51EB9DE\" ?> (8.0.5) -->\n  <!-- <?define ProductGuid=\"7EB8EE37-06D5-4119-84B4-5369AAF20041\" ?> (8.0.6) -->\n  <!-- <?define ProductGuid=\"A1210D1B-4DE1-4A01-85DF-779BE1C10790\" ?> (8.0.7) -->\n  <!-- <?define ProductGuid=\"B9B16F5B-BA57-4ABF-B9AC-F40C90D9C03D\" ?> (8.0.8) -->\n  <!-- <?define ProductGuid=\"614CE64F-16AE-46B3-9A3F-AA8AD5FCB7A3\" ?> (8.0.9) -->\n  <!-- <?define ProductGuid=\"D917BF55-78C3-4D57-B49E-BF378B8E484D\" ?> (8.0.10) -->\n  <!-- <?define ProductGuid=\"98BDB30E-0C17-4A8D-9B8F-B5B81ADB5E0A\" ?> (9.0.0) -->\n  <!-- <?define ProductGuid=\"DB942AE7-0222-4C1D-B029-705FA8C2A7EB\" ?> (9.0.1) -->\n  <!-- <?define ProductGuid=\"4B47215B-A195-4A79-8C74-9F015E9FC437\" ?> (9.0.2) -->\n  <!-- <?define ProductGuid=\"41DB61D0-22C8-4876-A464-B94A155EFB5F\" ?> (9.0.3) -->\n  <!-- <?define ProductGuid=\"02B98A70-EE81-45EE-9EEE-D73468D10B87\" ?> (9.0.4) -->\n  <!-- <?define ProductGuid=\"42658E02-4559-4B38-9F25-E1F67B75FEE3\" ?> (9.0.5) -->\n  <!-- <?define ProductGuid=\"462278AE-73B4-40DE-8DAE-E97681A06D08\" ?> (9.0.6) -->\n  <!-- <?define ProductGuid=\"1D82B9A4-7A7D-46E6-A12D-8D01728A448A\" ?> (9.0.7) -->\n  <!-- <?define ProductGuid=\"59B5583A-8470-465B-93C7-21DE0DDCD622\" ?> (9.0.8) -->\n  <!-- <?define ProductGuid=\"C458BBC0-423C-47C7-B29C-C7AB7DB7BB18\" ?> (9.0.9) -->\n  <!-- <?define ProductGuid=\"4DDB7C63-311D-466E-A8D6-D1409BE1993D\" ?> (9.0.10) -->\n  <!-- <?define ProductGuid=\"563C2ACE-86E6-42AC-9B1B-CBAEF0934323\" ?> (9.0.11) -->\n  <!-- <?define ProductGuid=\"31149F80-D37D-47FC-BF6C-742246AD97C9\" ?> (9.0.12) -->\n  <!-- <?define ProductGuid=\"FD8A983F-F65D-4D09-9D08-FC669F18032B\" ?> (9.0.13) -->\n  <!-- <?define ProductGuid=\"6E13F539-15B4-4015-BAAC-DC16039FD0AC\" ?> (9.0.14) -->\n  <!-- <?define ProductGuid=\"B2F9660C-638F-498C-A90E-960318D20133\" ?> (9.0.15) -->\n  <?define ProductGuid=\"F9FBBFAF-7E4B-4DD0-BB55-2CC22A91DF6C\" ?>\n\n  <?define GuidOfCustomComponent=\"AD201805-3CBD-4834-9097-5D934F7E0000\" ?>\n  <?define GuidOfAutoStartComponent=\"AD201805-3CBD-4834-9097-5D934F7E0001\" ?>\n  <?define GuidOfStartMenuShortCutComponent=\"AD201805-3CBD-4834-9097-5D934F7E0002\" ?>\n</Include>\n"
  },
  {
    "path": "msi/MyInstallDirDlg.wxs",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n    Copyright (c) Microsoft Corporation.  All rights reserved.\n    \n    The use and distribution terms for this software are covered by the\n    Common Public License 1.0 (http://opensource.org/licenses/cpl1.0.php)\n    which can be found in the file CPL.TXT at the root of this distribution.\n    By using this software in any fashion, you are agreeing to be bound by\n    the terms of this license.\n    \n    You must not remove this notice, or any other, from this software.\n-->\n<Wix xmlns=\"http://schemas.microsoft.com/wix/2006/wi\">\n    <Fragment>\n        <UI>\n            <Dialog Id=\"MyInstallDirDlg\" Width=\"370\" Height=\"270\" Title=\"!(loc.InstallDirDlg_Title)\">\n                <Control Id=\"Next\" Type=\"PushButton\" X=\"236\" Y=\"243\" Width=\"56\" Height=\"17\" Default=\"yes\" Text=\"!(loc.WixUINext)\" />\n                <Control Id=\"Back\" Type=\"PushButton\" X=\"180\" Y=\"243\" Width=\"56\" Height=\"17\" Text=\"!(loc.WixUIBack)\" />\n                <Control Id=\"Cancel\" Type=\"PushButton\" X=\"304\" Y=\"243\" Width=\"56\" Height=\"17\" Cancel=\"yes\" Text=\"!(loc.WixUICancel)\">\n                    <Publish Event=\"SpawnDialog\" Value=\"CancelDlg\">1</Publish>\n                </Control>\n\n                <Control Id=\"Description\" Type=\"Text\" X=\"25\" Y=\"23\" Width=\"280\" Height=\"15\" Transparent=\"yes\" NoPrefix=\"yes\" Text=\"!(loc.InstallDirDlgDescription)\" />\n                <Control Id=\"Title\" Type=\"Text\" X=\"15\" Y=\"6\" Width=\"200\" Height=\"15\" Transparent=\"yes\" NoPrefix=\"yes\" Text=\"!(loc.InstallDirDlgTitle)\" />\n                <Control Id=\"BannerBitmap\" Type=\"Bitmap\" X=\"0\" Y=\"0\" Width=\"370\" Height=\"44\" TabSkip=\"no\" Text=\"!(loc.InstallDirDlgBannerBitmap)\" />\n                <Control Id=\"BannerLine\" Type=\"Line\" X=\"0\" Y=\"44\" Width=\"370\" Height=\"0\" />\n                <Control Id=\"BottomLine\" Type=\"Line\" X=\"0\" Y=\"234\" Width=\"370\" Height=\"0\" />\n\n                <Control Id=\"FolderLabel\" Type=\"Text\" X=\"20\" Y=\"60\" Width=\"290\" Height=\"30\" NoPrefix=\"yes\" Text=\"!(loc.InstallDirDlgFolderLabel)\" />\n                <Control Id=\"Folder\" Type=\"PathEdit\" X=\"20\" Y=\"100\" Width=\"320\" Height=\"18\" Property=\"WIXUI_INSTALLDIR\" Indirect=\"yes\" />\n                <Control Id=\"ChangeFolder\" Type=\"PushButton\" X=\"20\" Y=\"120\" Width=\"56\" Height=\"17\" Text=\"!(loc.InstallDirDlgChange)\" />\n                <Control Id=\"AutomaticStartup\" Type=\"CheckBox\" X=\"20\" Y=\"205\"  Height=\"18\" Width=\"295\" Text=\"!(loc.AutoStartText)\" Property=\"SEAFILE_AUTO_START\" CheckBoxValue=\"1\" />\n            </Dialog>\n        </UI>\n    </Fragment>\n</Wix>"
  },
  {
    "path": "msi/WixUI_InstallDir_NoLicense.wxs",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<!--\n    Copyright (c) Microsoft Corporation.  All rights reserved.\n    \n    The use and distribution terms for this software are covered by the\n    Common Public License 1.0 (http://opensource.org/licenses/cpl1.0.php)\n    which can be found in the file CPL.TXT at the root of this distribution.\n    By using this software in any fashion, you are agreeing to be bound by\n    the terms of this license.\n    \n    You must not remove this notice, or any other, from this software.\n-->\n<!--\nFirst-time install dialog sequence:\n - WixUI_WelcomeDlg\n - WixUI_LicenseAgreementDlg\n - WixUI_InstallDirDlg\n - WixUI_VerifyReadyDlg\n - WixUI_DiskCostDlg\n\nMaintenance dialog sequence:\n - WixUI_MaintenanceWelcomeDlg\n - WixUI_MaintenanceTypeDlg\n - WixUI_InstallDirDlg\n - WixUI_VerifyReadyDlg\n\nPatch dialog sequence:\n - WixUI_WelcomeDlg\n - WixUI_VerifyReadyDlg\n\n-->\n\n<Wix xmlns=\"http://schemas.microsoft.com/wix/2006/wi\">\n    <Fragment>\n        <UI Id=\"WixUI_InstallDir_NoLicense\">\n            <TextStyle Id=\"WixUI_Font_Normal\" FaceName=\"Tahoma\" Size=\"8\" />\n            <TextStyle Id=\"WixUI_Font_Bigger\" FaceName=\"Tahoma\" Size=\"12\" />\n            <TextStyle Id=\"WixUI_Font_Title\" FaceName=\"Tahoma\" Size=\"9\" Bold=\"yes\" />\n\n            <Property Id=\"DefaultUIFont\" Value=\"WixUI_Font_Normal\" />\n            <Property Id=\"WixUI_Mode\" Value=\"InstallDir\" />\n\n            <DialogRef Id=\"BrowseDlg\" />\n            <DialogRef Id=\"DiskCostDlg\" />\n            <DialogRef Id=\"ErrorDlg\" />\n            <DialogRef Id=\"FatalError\" />\n            <DialogRef Id=\"FilesInUse\" />\n            <DialogRef Id=\"MsiRMFilesInUse\" />\n            <DialogRef Id=\"PrepareDlg\" />\n            <DialogRef Id=\"ProgressDlg\" />\n            <DialogRef Id=\"ResumeDlg\" />\n            <DialogRef Id=\"UserExit\" />\n            \n            <Publish Dialog=\"BrowseDlg\" Control=\"OK\" Event=\"DoAction\" Value=\"WixUIValidatePath\" Order=\"3\">1</Publish>\n            <Publish Dialog=\"BrowseDlg\" Control=\"OK\" Event=\"SpawnDialog\" Value=\"InvalidDirDlg\" Order=\"4\"><![CDATA[WIXUI_INSTALLDIR_VALID<>\"1\"]]></Publish>\n\n            <Publish Dialog=\"ExitDialog\" Control=\"Finish\" Event=\"EndDialog\" Value=\"Return\" Order=\"999\">1</Publish>\n\n            <Publish Dialog=\"WelcomeDlg\" Control=\"Next\" Event=\"NewDialog\" Value=\"MyInstallDirDlg\">NOT Installed</Publish>\n            <Publish Dialog=\"WelcomeDlg\" Control=\"Next\" Event=\"NewDialog\" Value=\"VerifyReadyDlg\">Installed AND PATCH</Publish>\n\n            <Publish Dialog=\"LicenseAgreementDlg\" Control=\"Back\" Event=\"NewDialog\" Value=\"WelcomeDlg\">1</Publish>\n            <Publish Dialog=\"LicenseAgreementDlg\" Control=\"Next\" Event=\"NewDialog\" Value=\"MyInstallDirDlg\">LicenseAccepted = \"1\"</Publish>\n\n            <Publish Dialog=\"MyInstallDirDlg\" Control=\"Back\" Event=\"NewDialog\" Value=\"WelcomeDlg\">1</Publish>\n            <Publish Dialog=\"MyInstallDirDlg\" Control=\"Next\" Event=\"SetTargetPath\" Value=\"[WIXUI_INSTALLDIR]\" Order=\"1\">1</Publish>\n            <Publish Dialog=\"MyInstallDirDlg\" Control=\"Next\" Event=\"DoAction\" Value=\"WixUIValidatePath\" Order=\"2\">NOT WIXUI_DONTVALIDATEPATH</Publish>\n            <Publish Dialog=\"MyInstallDirDlg\" Control=\"Next\" Event=\"SpawnDialog\" Value=\"InvalidDirDlg\" Order=\"3\"><![CDATA[NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>\"1\"]]></Publish>\n            <Publish Dialog=\"MyInstallDirDlg\" Control=\"Next\" Event=\"NewDialog\" Value=\"VerifyReadyDlg\" Order=\"4\">WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID=\"1\"</Publish>\n            <Publish Dialog=\"MyInstallDirDlg\" Control=\"ChangeFolder\" Property=\"_BrowseProperty\" Value=\"[WIXUI_INSTALLDIR]\" Order=\"1\">1</Publish>\n            <Publish Dialog=\"MyInstallDirDlg\" Control=\"ChangeFolder\" Event=\"SpawnDialog\" Value=\"BrowseDlg\" Order=\"2\">1</Publish>\n            \n            <Publish Dialog=\"VerifyReadyDlg\" Control=\"Back\" Event=\"NewDialog\" Value=\"MyInstallDirDlg\" Order=\"1\">NOT Installed</Publish>\n            <Publish Dialog=\"VerifyReadyDlg\" Control=\"Back\" Event=\"NewDialog\" Value=\"MaintenanceTypeDlg\" Order=\"2\">Installed AND NOT PATCH</Publish>\n            <Publish Dialog=\"VerifyReadyDlg\" Control=\"Back\" Event=\"NewDialog\" Value=\"WelcomeDlg\" Order=\"2\">Installed AND PATCH</Publish>\n\n            <Publish Dialog=\"MaintenanceWelcomeDlg\" Control=\"Next\" Event=\"NewDialog\" Value=\"MaintenanceTypeDlg\">1</Publish>\n\n            <Publish Dialog=\"MaintenanceTypeDlg\" Control=\"RepairButton\" Event=\"NewDialog\" Value=\"VerifyReadyDlg\">1</Publish>\n            <Publish Dialog=\"MaintenanceTypeDlg\" Control=\"RemoveButton\" Event=\"NewDialog\" Value=\"VerifyReadyDlg\">1</Publish>\n            <Publish Dialog=\"MaintenanceTypeDlg\" Control=\"Back\" Event=\"NewDialog\" Value=\"MaintenanceWelcomeDlg\">1</Publish>\n\n            <Property Id=\"ARPNOMODIFY\" Value=\"1\" />\n        </UI>\n\n        <UIRef Id=\"WixUI_Common\" />\n    </Fragment>\n</Wix>"
  },
  {
    "path": "msi/custom/custom.c",
    "content": "#include <windows.h>\n#include <string.h>\n#include <stdio.h>\n#include <string.h>\n\n#ifndef KEY_WOW64_64KEY\n#define KEY_WOW64_64KEY 0x0100\n#endif\n\n#ifndef KEY_WOW64_32KEY\n#define KEY_WOW64_32KEY 0x0200\n#endif\n\n#define STRINGIFY(x) #x\n#define TOSTRING(x) STRINGIFY(x)\n#define AT __FILE__ \":\" TOSTRING(__LINE__)\n\nvoid msgbox(const char *msg)\n{\n    MessageBox(NULL, msg, \"Seafile Custom\", MB_OK);\n}\n\nBOOL readRegValue(HKEY root, const char *subkey, const char *name, char **value)\n{\n    HKEY hKey;\n    char *buf = NULL;\n    BOOL ret = FALSE;\n    LONG result = RegOpenKeyEx(root,\n                               subkey,\n                               0L,\n                               // KEY_READ | KEY_WOW64_64KEY,\n                               KEY_READ,\n                               &hKey);\n    if (result != ERROR_SUCCESS) {\n        goto out;\n    }\n\n    DWORD len, type;\n    result = RegQueryValueEx(hKey,\n                             name,\n                             NULL,  // reserved\n                             &type, // type\n                             NULL,  // data\n                             &len); // size\n    if (result != ERROR_SUCCESS || type != REG_SZ) {\n        goto out;\n    }\n\n    buf = malloc (len + 1);\n    buf[len] = 0;\n    result = RegQueryValueEx(hKey,\n                             name,\n                             NULL,          // reserved\n                             NULL,          // type\n                             (LPBYTE) buf,  // data\n                             &len);         // size\n    if (result != ERROR_SUCCESS) {\n        goto out;\n    }\n\n    *value = buf;\n    ret = TRUE;\n\nout:\n    RegCloseKey(hKey);\n    return ret;\n}\n\n\n/* Remove auto start entry for seafile when uninstall. Error is ignored. */\nUINT __stdcall RemoveExtDll(HANDLE hModule)\n{\n    const char *dll_path_key = \"SOFTWARE\\\\Classes\\\\CLSID\\\\{AD201805-4E05-4F2F-B0DE-D0381E6AE606}\\\\InProcServer32\";\n    char *path = NULL;\n    if (!readRegValue(HKEY_LOCAL_MACHINE, dll_path_key, \"\", &path)) {\n        return ERROR_SUCCESS;\n    }\n\n    if (!path) {\n        return ERROR_SUCCESS;\n    }\n\n    int n = strlen(path);\n    char *path2 = malloc (n + 3);\n    memcpy (path2, path, strlen(path));\n    path2[n] = '.';\n    path2[n + 1] = '1';\n    path2[n + 2] = 0;\n\n    MoveFileEx(path, path2, MOVEFILE_REPLACE_EXISTING);\n\n    free(path);\n    free(path2);\n\n    return ERROR_SUCCESS;\n}\n"
  },
  {
    "path": "msi/custom/custom.def",
    "content": "LIBRARY seafile_custom\nEXPORTS\nRemoveExtDll\n"
  },
  {
    "path": "msi/custom/seafile_custom.sln",
    "content": "﻿\r\nMicrosoft Visual Studio Solution File, Format Version 12.00\r\n# Visual Studio Version 16\r\nVisualStudioVersion = 16.0.30611.23\r\nMinimumVisualStudioVersion = 10.0.40219.1\r\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"seafile_custom\", \"seafile_custom.vcxproj\", \"{D0B1637D-AF0E-4724-B4E4-DCFF641EBB4B}\"\r\nEndProject\r\nGlobal\r\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\r\n\t\tDebug|x64 = Debug|x64\r\n\t\tDebug|x86 = Debug|x86\r\n\t\tRelease|x64 = Release|x64\r\n\t\tRelease|x86 = Release|x86\r\n\tEndGlobalSection\r\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\r\n\t\t{D0B1637D-AF0E-4724-B4E4-DCFF641EBB4B}.Debug|x64.ActiveCfg = Debug|x64\r\n\t\t{D0B1637D-AF0E-4724-B4E4-DCFF641EBB4B}.Debug|x64.Build.0 = Debug|x64\r\n\t\t{D0B1637D-AF0E-4724-B4E4-DCFF641EBB4B}.Debug|x86.ActiveCfg = Debug|Win32\r\n\t\t{D0B1637D-AF0E-4724-B4E4-DCFF641EBB4B}.Debug|x86.Build.0 = Debug|Win32\r\n\t\t{D0B1637D-AF0E-4724-B4E4-DCFF641EBB4B}.Release|x64.ActiveCfg = Release|x64\r\n\t\t{D0B1637D-AF0E-4724-B4E4-DCFF641EBB4B}.Release|x64.Build.0 = Release|x64\r\n\t\t{D0B1637D-AF0E-4724-B4E4-DCFF641EBB4B}.Release|x86.ActiveCfg = Release|Win32\r\n\t\t{D0B1637D-AF0E-4724-B4E4-DCFF641EBB4B}.Release|x86.Build.0 = Release|Win32\r\n\tEndGlobalSection\r\n\tGlobalSection(SolutionProperties) = preSolution\r\n\t\tHideSolutionNode = FALSE\r\n\tEndGlobalSection\r\n\tGlobalSection(ExtensibilityGlobals) = postSolution\r\n\t\tSolutionGuid = {1F899345-72EA-47B3-B3B9-EB5CA31D6238}\r\n\tEndGlobalSection\r\nEndGlobal\r\n"
  },
  {
    "path": "msi/custom/seafile_custom.vcxproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Project DefaultTargets=\"Build\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\r\n  <ItemGroup Label=\"ProjectConfigurations\">\r\n    <ProjectConfiguration Include=\"Debug|Win32\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>Win32</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|Win32\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>Win32</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Debug|x64\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>x64</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|x64\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>x64</Platform>\r\n    </ProjectConfiguration>\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ClCompile Include=\"custom.c\" />\r\n  </ItemGroup>\r\n  <PropertyGroup Label=\"Globals\">\r\n    <VCProjectVersion>16.0</VCProjectVersion>\r\n    <Keyword>Win32Proj</Keyword>\r\n    <ProjectGuid>{d0b1637d-af0e-4724-b4e4-dcff641ebb4b}</ProjectGuid>\r\n    <RootNamespace>seafilecustom</RootNamespace>\r\n    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\r\n  </PropertyGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>true</UseDebugLibraries>\r\n    <PlatformToolset>v142</PlatformToolset>\r\n    <CharacterSet>Unicode</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>false</UseDebugLibraries>\r\n    <PlatformToolset>v142</PlatformToolset>\r\n    <WholeProgramOptimization>true</WholeProgramOptimization>\r\n    <CharacterSet>Unicode</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>true</UseDebugLibraries>\r\n    <PlatformToolset>v142</PlatformToolset>\r\n    <CharacterSet>Unicode</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>false</UseDebugLibraries>\r\n    <PlatformToolset>v142</PlatformToolset>\r\n    <WholeProgramOptimization>true</WholeProgramOptimization>\r\n    <CharacterSet>Unicode</CharacterSet>\r\n  </PropertyGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\r\n  <ImportGroup Label=\"ExtensionSettings\">\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"Shared\">\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <PropertyGroup Label=\"UserMacros\" />\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <LinkIncremental>true</LinkIncremental>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <LinkIncremental>false</LinkIncremental>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <LinkIncremental>true</LinkIncremental>\r\n    <TargetName>seafile_custom64</TargetName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <LinkIncremental>false</LinkIncremental>\r\n    <OutDir>$(ProjectDir)$(Platform)\\$(Configuration)\\</OutDir>\r\n    <TargetName>seafile_custom64</TargetName>\r\n  </PropertyGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <SDLCheck>true</SDLCheck>\r\n      <PreprocessorDefinitions>WIN32;_DEBUG;SEAFILECUSTOM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PrecompiledHeader>Use</PrecompiledHeader>\r\n      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Windows</SubSystem>\r\n      <GenerateDebugInformation>true</GenerateDebugInformation>\r\n      <EnableUAC>false</EnableUAC>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <FunctionLevelLinking>true</FunctionLevelLinking>\r\n      <IntrinsicFunctions>true</IntrinsicFunctions>\r\n      <SDLCheck>true</SDLCheck>\r\n      <PreprocessorDefinitions>WIN32;NDEBUG;SEAFILECUSTOM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PrecompiledHeader>Use</PrecompiledHeader>\r\n      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Windows</SubSystem>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <GenerateDebugInformation>true</GenerateDebugInformation>\r\n      <EnableUAC>false</EnableUAC>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <SDLCheck>true</SDLCheck>\r\n      <PreprocessorDefinitions>_DEBUG;SEAFILECUSTOM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PrecompiledHeader>NotUsing</PrecompiledHeader>\r\n      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>\r\n      <IntrinsicFunctions>true</IntrinsicFunctions>\r\n      <FunctionLevelLinking>true</FunctionLevelLinking>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Windows</SubSystem>\r\n      <GenerateDebugInformation>true</GenerateDebugInformation>\r\n      <EnableUAC>false</EnableUAC>\r\n      <ModuleDefinitionFile>$(ProjectDir)custom.def</ModuleDefinitionFile>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <FunctionLevelLinking>true</FunctionLevelLinking>\r\n      <IntrinsicFunctions>true</IntrinsicFunctions>\r\n      <SDLCheck>true</SDLCheck>\r\n      <PreprocessorDefinitions>NDEBUG;SEAFILECUSTOM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PrecompiledHeader>NotUsing</PrecompiledHeader>\r\n      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Windows</SubSystem>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <GenerateDebugInformation>true</GenerateDebugInformation>\r\n      <EnableUAC>false</EnableUAC>\r\n      <ModuleDefinitionFile>$(ProjectDir)custom.def</ModuleDefinitionFile>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\r\n  <ImportGroup Label=\"ExtensionTargets\">\r\n  </ImportGroup>\r\n</Project>"
  },
  {
    "path": "msi/custom/seafile_custom.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\r\n  <ItemGroup>\r\n    <Filter Include=\"Source Files\">\r\n      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>\r\n      <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>\r\n    </Filter>\r\n    <Filter Include=\"Header Files\">\r\n      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>\r\n      <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>\r\n    </Filter>\r\n    <Filter Include=\"Resource Files\">\r\n      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>\r\n      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>\r\n    </Filter>\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ClCompile Include=\"custom.c\">\r\n      <Filter>Source Files</Filter>\r\n    </ClCompile>\r\n  </ItemGroup>\r\n</Project>"
  },
  {
    "path": "msi/de_de.wxl",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<WixLocalization Culture=\"de-de\" xmlns=\"http://schemas.microsoft.com/wix/2006/localization\">\n\n  <String Id=\"AppName\">Seafile</String>\n  <String Id=\"PackageDescription\">Seafile Installation</String>\n  <String Id=\"PackageComments\">Seafile Installation</String>\n  <String Id=\"Manufacturer\">HaiWenHuZhi ltd.</String>\n  <String Id=\"DowngradeErrorMessage\">Eine neuere Version von Seafile wurde auf Ihrem System gefunden. Die Installation wird jetzt beendet</String>\n  <String Id=\"UninstallSeafile\">Seafile deinstallieren</String>\n  <String Id=\"StartSeafile\">Seafile starten</String>\n  <String Id=\"AutoStartText\">Seafile beim Systemstart ausführen</String>\n\n</WixLocalization>\n"
  },
  {
    "path": "msi/en_US.wxl",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<WixLocalization Culture=\"en-us\" xmlns=\"http://schemas.microsoft.com/wix/2006/localization\">\n\n  <String Id=\"AppName\">Seafile</String>\n  <String Id=\"PackageDescription\">Seafile Installer</String>\n  <String Id=\"PackageComments\">Seafile Installer</String>\n  <String Id=\"Manufacturer\">HaiWenHuZhi ltd.</String>\n  <String Id=\"DowngradeErrorMessage\">A more up-to-date version of Seafile is detected on your machine. The installer will now quit</String>\n  <String Id=\"UninstallSeafile\">Uninstall Seafile</String>\n  <String Id=\"StartSeafile\">Start Seafile</String>\n  <String Id=\"AutoStartText\">Start Seafile on startup</String>\n\n</WixLocalization>\n"
  },
  {
    "path": "msi/seafile.wxs",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'\n     xmlns:util=\"http://schemas.microsoft.com/wix/UtilExtension\">\n  <?include Includes.wxi?>\n  <?include ext.wxi?>\n  <Product Id=\"$(var.ProductGuid)\"\n           Name='!(loc.AppName) $(var.CurrentSeafileVersion)'\n           Language='1033'\n           Version='$(var.CurrentSeafileVersion)'\n           Manufacturer='!(loc.Manufacturer)'\n           UpgradeCode=\"$(var.CurrentUpdateGuid)\" >\n\n    <!-- We set InstallScope to perMachine to install for all users -->\n    <Package Description='!(loc.PackageDescription)' Comments='!(loc.PackageComments)'\n             Manufacturer='!(loc.Manufacturer)'\n             InstallerVersion='200'\n             InstallPrivileges='elevated' InstallScope='perMachine'\n             Compressed='yes' />\n\n    <!-- http://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/configure_arp_appearance.html -->\n    <Icon Id=\"icon.ico\" SourceFile=\"seafile.ico\"/>\n    <Property Id=\"ARPPRODUCTICON\" Value=\"icon.ico\" />\n\n    <!-- Don't allow downgrade. -->\n    <MajorUpgrade DowngradeErrorMessage='!(loc.DowngradeErrorMessage)' />\n\n    <Property Id=\"SUPPRESS_LAUNCH_SEAFILE_AFTER_INSTALL_FINISH\">\n      <RegistrySearch Id=\"SuppressLaunchSeafileHKCU\"\n                      Root=\"HKCU\"\n                      Key=\"SOFTWARE\\!(loc.AppName)\"\n                      Name=\"PreconfigureSuppressLaunchAfterInstall\"\n                      Type=\"raw\"\n                        />\n\n      <RegistrySearch Id=\"SuppressLaunchSeafileHKCU64\"\n                      Root=\"HKCU\"\n                      Key=\"SOFTWARE\\!(loc.AppName)\"\n                      Name=\"PreconfigureSuppressLaunchAfterInstall\"\n                      Type=\"raw\"\n                      Win64=\"yes\"\n                      />\n\n      <RegistrySearch Id=\"SuppressLaunchSeafileHKLM\"\n                      Root=\"HKLM\"\n                      Key=\"SOFTWARE\\!(loc.AppName)\"\n                      Name=\"PreconfigureSuppressLaunchAfterInstall\"\n                      Type=\"raw\"\n                      />\n\n      <RegistrySearch Id=\"SuppressLaunchSeafileHKLM64\"\n                      Root=\"HKLM\"\n                      Key=\"SOFTWARE\\!(loc.AppName)\"\n                      Name=\"PreconfigureSuppressLaunchAfterInstall\"\n                      Type=\"raw\"\n                      Win64=\"yes\"\n                      />\n    </Property>\n\n    <Media Id='1' Cabinet='seafile.cab' EmbedCab='yes' />\n\n    <Directory Id='TARGETDIR' Name='SourceDir'>\n      <Directory Id=\"DesktopFolder\" Name=\"DesktopFolder\" />\n      <Directory Id=\"ProgramMenuFolder\">\n        <Directory Id=\"SeafileStartMenuFolder\" Name='!(loc.AppName)' />\n      </Directory>\n\n      <Directory Id='CommonAppDataFolder' Name='AllUsersHome'>\n        <Directory Id='SeafileExtDir' Name='$(var.ExtName)'>\n          <Component Id=\"seafile_ext\" Guid=\"$(var.GuidOfShellExtComponent)\">\n            <CreateFolder />\n            <RemoveFolder Id=\"SeafileExtDir\" On=\"uninstall\" />\n            <RegistryValue Root=\"HKCU\" Key=\"Software\\[Manufacturer]\\[ProductName]\"\n                           Name=\"shellext\" Type=\"integer\" Value=\"1\" KeyPath='yes' />\n\n            <!--<File Id=\"seafile_ext.dll\" Name=\"seafile_ext.dll\" Source=\"custom\\seafile_ext.dll\" />-->\n            <File Id=\"seafile_ext64.dll\" Name=\"seafile_ext64.dll\" Source=\"custom\\seafile_ext64.dll\" />\n          </Component>\n        </Directory>\n      </Directory>\n\n      <Directory Id='ProgramFiles64Folder' Name='PFiles'>\n        <Directory Id='INSTALLDIR' Name='!(loc.AppName)'>\n          <Directory Id='bin_Dir' Name='bin'>\n            <Component Id='comp_custom' Guid=\"$(var.GuidOfCustomComponent)\">\n              <RegistryKey Root=\"HKCU\" Key=\"Software\\[Manufacturer]\\[ProductName]\" Action=\"createAndRemoveOnUninstall\">\n                <RegistryValue Name=\"CustomComponent\" Value=\"1\" Type=\"integer\" KeyPath='yes' />\n              </RegistryKey>\n\n              <File Id=\"seafdir.ico\" Name=\"seafdir.ico\" Source=\"seafdir.ico\" />\n\n              <!-- seafile shortcut on desktop -->\n              <Shortcut Id=\"ApplicationDesktopShortCut\" Directory=\"DesktopFolder\"\n                        Name='!(loc.AppName)' Target=\"[#seafileapplet.exe]\"\n                        Hotkey=\"0\" IconIndex=\"0\" Show=\"normal\"\n                        WorkingDirectory=\"bin_Dir\" />\n\n            </Component>\n          </Directory>\n        </Directory>\n      </Directory>\n\n    </Directory>\n\n    <Property Id=\"SEAFILE_AUTO_START\">1</Property>\n\n    <!-- Auto-start via Registry -->\n    <DirectoryRef Id=\"INSTALLDIR\">\n      <Component Id=\"SeafileAutoStart\" Guid=\"$(var.GuidOfAutoStartComponent)\">\n        <RegistryKey Root=\"HKCU\"\n                     Key=\"Software\\Microsoft\\Windows\\CurrentVersion\\Run\"\n                     Action=\"create\">\n          <RegistryValue Name='!(loc.AppName)' Value=\"[#seafileapplet.exe]\" Type=\"string\" KeyPath=\"yes\" />\n        </RegistryKey>\n\n        <Condition>SEAFILE_AUTO_START</Condition>\n      </Component>\n    </DirectoryRef>\n\n\n    <DirectoryRef Id=\"SeafileStartMenuFolder\" >\n      <Component Id=\"Seafile_StartMenuShortCut\" Guid=\"$(var.GuidOfStartMenuShortCutComponent)\" >\n        <RemoveFolder Id=\"SeafileStartMenuFolder\" On=\"uninstall\" />\n        <RegistryValue Root=\"HKCU\" Key=\"Software\\[Manufacturer]\\[ProductName]\"\n                       Name=\"installed\" Type=\"integer\" Value=\"1\" KeyPath='yes' />\n\n        <!-- shortcut to 'start seafile' -->\n        <Shortcut Id=\"ApplicationStartMenuShortCut\" Directory=\"SeafileStartMenuFolder\"\n                  Name=\"!(loc.StartSeafile)\" Target=\"[#seafileapplet.exe]\"\n                  Hotkey=\"0\" IconIndex=\"0\" Show=\"normal\"\n                  WorkingDirectory=\"bin_Dir\" />\n\n        <!-- shortcut to 'Uninstall' -->\n        <Shortcut Id=\"UninstallProduct\" Name=\"!(loc.UninstallSeafile)\"\n                  Target=\"[SystemFolder]msiexec.exe\" IconIndex=\"0\"\n                  Arguments=\"/x [ProductCode]\" Description=\"!(loc.UninstallSeafile)\" />\n      </Component>\n    </DirectoryRef>\n\n    <!-- installer vc merge module -->\n    <DirectoryRef Id=\"TARGETDIR\">\n      <Merge Id=\"VCRedist\" SourceFile=\"bin\\Microsoft_VC142_CRT_x64.msm\" DiskId=\"1\" Language=\"0\"/>\n    </DirectoryRef>\n    <Feature Id=\"VCRedist\" Title=\"Visual C++ 142 x64 Runtime\" AllowAdvertise=\"no\" Display=\"hidden\" Level=\"1\">\n      <MergeRef Id=\"VCRedist\"/>\n    </Feature>\n\n    <!-- <Binary Id='seafile_custom_dll32' SourceFile='custom\\seafile_custom32.dll' /> -->\n    <Binary Id='seafile_custom_dll64' SourceFile='custom\\seafile_custom64.dll' />\n\n    <!-- <CustomAction Id=\"MoveExtDll\" BinaryKey=\"seafile_custom_dll32\" DllEntry=\"RemoveExtDll\" Execute=\"immediate\" /> -->\n    <CustomAction Id=\"MoveExtDll64\" BinaryKey=\"seafile_custom_dll64\" DllEntry=\"RemoveExtDll\" Execute=\"immediate\" />\n\n    <CustomAction Id=\"RemoveSeafileUserData\" FileKey=\"seafileapplet.exe\" ExeCommand=\"--remove-user-data\"\n                  Execute=\"immediate\" Impersonate=\"yes\" Return=\"ignore\" />\n    <CustomAction Id=\"StopSeafile\" FileKey=\"seafileapplet.exe\" ExeCommand=\"--stop\"\n                  Execute=\"immediate\" Impersonate=\"yes\" Return=\"ignore\" />\n\n    <InstallExecuteSequence>\n      <!-- Move away seafile shell extension so we don't need to kill explorer during upgrade or uninstall. -->\n      <!--\n      <Custom Action=\"MoveExtDll\" Before=\"InstallValidate\">\n        ((REMOVE=\"ALL\") OR UPGRADINGPRODUCTCODE OR WIX_UPGRADE_DETECTED) AND (NOT VersionNT64)\n      </Custom>\n      -->\n      <Custom Action=\"MoveExtDll64\" Before=\"InstallValidate\">\n        ((REMOVE=\"ALL\") OR UPGRADINGPRODUCTCODE OR WIX_UPGRADE_DETECTED) AND VersionNT64\n      </Custom>\n      <Custom Action=\"StopSeafile\" Before=\"InstallValidate\">\n        UPGRADINGPRODUCTCODE OR WIX_UPGRADE_DETECTED\n      </Custom>\n\n      <!-- Remove User data, as well as seafile QSettings in registry -->\n      <Custom Action=\"RemoveSeafileUserData\" Before=\"InstallValidate\">\n        (NOT UPGRADINGPRODUCTCODE) AND (REMOVE=\"ALL\")\n      </Custom>\n    </InstallExecuteSequence>\n\n    <!-- UI related -->\n    <Property Id='WIXUI_INSTALLDIR' Value=\"INSTALLDIR\" />\n    <UI>\n      <UIRef Id='WixUI_InstallDir_NoLicense' />\n      <UIRef Id='WixUI_ErrorProgressText' />\n      <Publish Dialog=\"ExitDialog\" Control=\"Finish\" Event=\"DoAction\" Value=\"LaunchApplication\">\n        <!-- Launch Seafile after setup. -->\n        (NOT Installed) AND  (NOT SUPPRESS_LAUNCH_SEAFILE_AFTER_INSTALL_FINISH)\n      </Publish>\n    </UI>\n\n    <Property Id=\"WixShellExecTarget\" Value=\"[#seafileapplet.exe]\" />\n    <CustomAction Id=\"LaunchApplication\" BinaryKey=\"WixCA\" DllEntry=\"WixShellExec\" Impersonate=\"yes\" />\n\n    <Feature Id='Main' Level='1'>\n      <ComponentRef Id='Seafile_StartMenuShortCut' />\n      <ComponentRef Id=\"SeafileAutoStart\" />\n      <ComponentRef Id='comp_custom' />\n      <!-- defined in fragment.wxs -->\n      <ComponentGroupRef Id='group_bin' />\n      <!-- defined in shell.wxs -->\n      <ComponentRef Id='comp_shell' />\n      <ComponentRef Id='comp_shell64' />\n      <ComponentRef Id='seafile_ext' />\n    </Feature>\n\n    <Property Id=\"CHECKBOX_DEL_SEAFILE_DATA\" Secure=\"yes\" />\n\n    <WixVariable Id=\"WixUIBannerBmp\" Value=\"seafile-top-banner.bmp\" />\n    <WixVariable Id=\"WixUIDialogBmp\" Value=\"seafile-background.bmp\" />\n  </Product>\n</Wix>\n"
  },
  {
    "path": "msi/strip-files.py",
    "content": "import glob\nimport os\n\n\nos.chdir('bin')\n\nignored = []\n\ndef do_strip(fn):\n    try:\n        os.system('strip \"%s\"' % fn)\n    except Exception, e:\n        print e\n    else:\n        print 'strip: ', fn\n\nfor dll in glob.glob('*.dll'):\n    if dll.startswith('python') or dll.startswith('msvc'):\n        ignored.append(dll)\n        continue\n    else:\n        do_strip(dll)\n\nfor exe in glob.glob('*.exe'):\n    do_strip(exe)\n    \nprint '----------------------------'\nprint 'ignored:'\nfor i in ignored:\n    print '>> ', i\n"
  },
  {
    "path": "msi/zh_CN.wxl",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<WixLocalization Culture=\"zh-cn\" xmlns=\"http://schemas.microsoft.com/wix/2006/localization\">\n\n  <String Id=\"AppName\">Seafile</String>\n  <String Id=\"PackageDescription\">Seafile 安装包</String>\n  <String Id=\"PackageComments\">Seafile 安装包</String>\n  <String Id=\"Manufacturer\">北京海文互知网络技术有限公司</String>\n  <String Id=\"DowngradeErrorMessage\">检测到您已经安装了更新版本的 Seafile 。安装程序将自动退出</String>\n  <String Id=\"UninstallSeafile\">删除 Seafile</String>\n  <String Id=\"StartSeafile\">启动 Seafile</String>\n  <String Id=\"AutoStartText\">开机自动启动 Seafile</String>\n\n  <!-- https://raw.githubusercontent.com/wixtoolset/wix3/e4d1aab946a1df20f01cbc1c40bee68ffe93e8bf/src/ext/UtilExtension/wixlib/en-us.wxl -->\n  <!-- Offical wix utilextension doesn't have zh_CN translation. We\n       have to put place holders here. -->\n  <String Id=\"msierrUSRFailedUserCreate\" Overridable=\"yes\">Failed to create user.  ([2]   [3]   [4]   [5])</String>\n  <String Id=\"msierrUSRFailedUserCreatePswd\" Overridable=\"yes\">Failed to create user due to invalid password.  ([2]   [3]   [4]   [5])</String>\n  <String Id=\"msierrUSRFailedUserGroupAdd\" Overridable=\"yes\">Failed to add user to group.  ([2]   [3]   [4]   [5])</String>\n  <String Id=\"msierrUSRFailedUserCreateExists\" Overridable=\"yes\">Failed to create user because it already exists.  ([2]   [3]   [4]   [5])</String>\n\n  <String Id=\"msierrSMBFailedCreate\" Overridable=\"yes\">Failed to create network share.  ([2]   [3]   [4]   [5])</String>\n  <String Id=\"msierrSMBFailedDrop\" Overridable=\"yes\">Failed to drop network share.  ([2]   [3]   [4]   [5])</String>\n\n  <String Id=\"msierrPERFMONFailedRegisterDLL\" Overridable=\"yes\">Failed to register DLL with PerfMon.  ([2]   [3]   [4]   [5])</String>\n  <String Id=\"msierrPERFMONFailedUnregisterDLL\" Overridable=\"yes\">Failed to unregister DLL with PerfMon.  ([2]   [3]   [4]   [5])</String>\n\n  <String Id=\"msierrInstallPerfCounterData\" Overridable=\"yes\">Failed to install performance counters.  ([2]   [3]   [4]   [5])</String>\n  <String Id=\"msierrUninstallPerfCounterData\" Overridable=\"yes\">Failed to uninstall performance counters.  ([2]   [3]   [4]   [5])</String>\n\n  <String Id=\"msierrSecureObjectsFailedCreateSD\" Overridable=\"yes\">Failed to create security descriptor for [3]\\[4], system error: [2]</String>\n  <String Id=\"msierrSecureObjectsFailedSet\" Overridable=\"yes\">Failed to set security descriptor on object [3], system error: [2]</String>\n  <String Id=\"msierrSecureObjectsUnknownType\" Overridable=\"yes\">Unknown Object Type [3], system error: [2]</String>\n\n  <String Id=\"msierrXmlFileFailedRead\" Overridable=\"yes\">There was a failure while configuring XML files.</String>\n  <String Id=\"msierrXmlFileFailedOpen\" Overridable=\"yes\">Failed to open XML file [3], system error: [2]</String>\n  <String Id=\"msierrXmlFileFailedSelect\" Overridable=\"yes\">Failed to find node: [3] in XML file: [4], system error: [2]</String>\n  <String Id=\"msierrXmlFileFailedSave\" Overridable=\"yes\">Failed to save changes to XML file [3], system error: [2]</String>\n\n  <String Id=\"msierrXmlConfigFailedRead\" Overridable=\"yes\">There was a failure while configuring XML files.</String>\n  <String Id=\"msierrXmlConfigFailedOpen\" Overridable=\"yes\">Failed to open XML file [3], system error: [2]</String>\n  <String Id=\"msierrXmlConfigFailedSelect\" Overridable=\"yes\">Failed to find node: [3] in XML file: [4], system error: [2]</String>\n  <String Id=\"msierrXmlConfigFailedSave\" Overridable=\"yes\">Failed to save changes to XML file [3], system error: [2]</String>\n\n</WixLocalization>\n"
  },
  {
    "path": "python/Makefile.am",
    "content": "SUBDIRS = seafile\n"
  },
  {
    "path": "python/seafile/Makefile.am",
    "content": "seafiledir=${pyexecdir}/seafile\n\nseafile_PYTHON = __init__.py rpcclient.py\n"
  },
  {
    "path": "python/seafile/__init__.py",
    "content": "\nfrom .rpcclient import SeafileRpcClient as RpcClient\n\nclass TaskType(object):\n    DOWNLOAD = 0\n    UPLOAD = 1\n"
  },
  {
    "path": "python/seafile/rpcclient.py",
    "content": "from pysearpc import searpc_func, SearpcError, NamedPipeClient\n\n\nclass SeafileRpcClient(NamedPipeClient):\n    \"\"\"RPC used in client\"\"\"\n\n    def __init__(self, socket_path, *args, **kwargs):\n         NamedPipeClient.__init__(\n             self,\n             socket_path,\n             \"seafile-rpcserver\",\n             *args,\n             **kwargs\n         )\n\n    @searpc_func(\"string\", [\"int\"])\n    def seafile_sync_error_id_to_str():\n        pass\n    sync_error_id_to_str = seafile_sync_error_id_to_str\n\n    @searpc_func(\"int\", [\"int\"])\n    def seafile_del_file_sync_error_by_id():\n        pass\n    del_file_sync_error_by_id = seafile_del_file_sync_error_by_id\n\n    @searpc_func(\"int\", [\"string\"])\n    def seafile_calc_dir_size(path):\n        pass\n    calc_dir_size = seafile_calc_dir_size\n\n    @searpc_func(\"int64\", [])\n    def seafile_get_total_block_size():\n        pass\n    get_total_block_size = seafile_get_total_block_size;\n\n    @searpc_func(\"string\", [\"string\"])\n    def seafile_get_config(key):\n        pass\n    get_config = seafile_get_config\n\n    @searpc_func(\"int\", [\"string\", \"string\"])\n    def seafile_set_config(key, value):\n        pass\n    set_config = seafile_set_config\n\n    @searpc_func(\"int\", [\"string\"])\n    def seafile_get_config_int(key):\n        pass\n    get_config_int = seafile_get_config_int\n\n    @searpc_func(\"int\", [\"string\", \"int\"])\n    def seafile_set_config_int(key, value):\n        pass\n    set_config_int = seafile_set_config_int\n\n    @searpc_func(\"int\", [\"int\"])\n    def seafile_set_upload_rate_limit(limit):\n        pass\n    set_upload_rate_limit = seafile_set_upload_rate_limit\n\n    @searpc_func(\"int\", [\"int\"])\n    def seafile_set_download_rate_limit(limit):\n        pass\n    set_download_rate_limit = seafile_set_download_rate_limit\n\n    ### repo\n    @searpc_func(\"objlist\", [\"int\", \"int\"])\n    def seafile_get_repo_list():\n        pass\n    get_repo_list = seafile_get_repo_list\n\n    @searpc_func(\"object\", [\"string\"])\n    def seafile_get_repo():\n        pass\n    get_repo = seafile_get_repo\n\n    @searpc_func(\"string\", [\"string\", \"string\", \"string\", \"string\", \"string\", \"int\"])\n    def seafile_create_repo(name, desc, passwd, base, relay_id, keep_history):\n        pass\n    create_repo = seafile_create_repo\n\n    @searpc_func(\"int\", [\"string\"])\n    def seafile_destroy_repo(repo_id):\n        pass\n    remove_repo = seafile_destroy_repo\n\n    @searpc_func(\"objlist\", [\"string\", \"string\", \"string\", \"int\"])\n    def seafile_diff():\n        pass\n    get_diff = seafile_diff\n\n    @searpc_func(\"object\", [\"string\", \"int\", \"string\"])\n    def seafile_get_commit(repo_id, version, commit_id):\n        pass\n    get_commit = seafile_get_commit\n\n    @searpc_func(\"objlist\", [\"string\", \"int\", \"int\"])\n    def seafile_get_commit_list():\n        pass\n    get_commit_list = seafile_get_commit_list\n\n    @searpc_func(\"objlist\", [\"string\"])\n    def seafile_branch_gets(repo_id):\n        pass\n    branch_gets = seafile_branch_gets\n\n    @searpc_func(\"int\", [\"string\", \"string\"])\n    def seafile_branch_add(repo_id, branch):\n        pass\n    branch_add = seafile_branch_add\n\n    ##### clone related\n    @searpc_func(\"string\", [\"string\", \"string\"])\n    def gen_default_worktree(worktree_parent, repo_name):\n        pass\n\n    @searpc_func(\"string\", [\"string\", \"int\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int\", \"string\"])\n    def seafile_clone(repo_id, repo_version, repo_name, worktree, token, password, magic, email, random_key, enc_version, more_info):\n        pass\n    clone = seafile_clone\n\n    @searpc_func(\"string\", [\"string\", \"int\", \"string\", \"string\", \"string\", \"string\", \"string\", \"string\", \"int\", \"string\"])\n    def seafile_download(repo_id, repo_version, repo_name, wt_parent, token, password, magic, email, random_key, enc_version, more_info):\n        pass\n    download = seafile_download\n\n    @searpc_func(\"int\", [\"string\"])\n    def seafile_cancel_clone_task(repo_id):\n        pass\n    cancel_clone_task = seafile_cancel_clone_task\n\n    @searpc_func(\"objlist\", [])\n    def seafile_get_clone_tasks():\n        pass\n    get_clone_tasks = seafile_get_clone_tasks\n\n    @searpc_func(\"object\", [\"string\"])\n    def seafile_find_transfer_task(repo_id):\n        pass\n    find_transfer_task = seafile_find_transfer_task\n\n    ### sync\n    @searpc_func(\"int\", [\"string\", \"string\"])\n    def seafile_sync(repo_id, peer_id):\n        pass\n    sync = seafile_sync\n\n    @searpc_func(\"object\", [\"string\"])\n    def seafile_get_repo_sync_task():\n        pass\n    get_repo_sync_task = seafile_get_repo_sync_task\n\n    @searpc_func(\"int\", [])\n    def seafile_is_auto_sync_enabled():\n        pass\n    is_auto_sync_enabled = seafile_is_auto_sync_enabled\n\n    @searpc_func(\"objlist\", [\"int\", \"int\"])\n    def seafile_get_file_sync_errors():\n        pass\n    get_file_sync_errors = seafile_get_file_sync_errors\n\n    ###### Property Management #########\n\n    @searpc_func(\"int\", [\"string\", \"string\"])\n    def seafile_set_repo_passwd(repo_id, passwd):\n        pass\n    set_repo_passwd = seafile_set_repo_passwd\n\n    @searpc_func(\"int\", [\"string\", \"string\", \"string\"])\n    def seafile_set_repo_property(repo_id, key, value):\n        pass\n    set_repo_property = seafile_set_repo_property\n\n    @searpc_func(\"string\", [\"string\", \"string\"])\n    def seafile_get_repo_property(repo_id, key):\n        pass\n    get_repo_property = seafile_get_repo_property\n\n    @searpc_func(\"string\", [\"string\"])\n    def seafile_get_repo_relay_address(repo_id):\n        pass\n    get_repo_relay_address = seafile_get_repo_relay_address\n\n    @searpc_func(\"string\", [\"string\"])\n    def seafile_get_repo_relay_port(repo_id):\n        pass\n    get_repo_relay_port = seafile_get_repo_relay_port\n\n    @searpc_func(\"int\", [\"string\", \"string\", \"string\"])\n    def seafile_update_repo_relay_info(repo_id, addr, port):\n        pass\n    update_repo_relay_info = seafile_update_repo_relay_info\n\n    @searpc_func(\"int\", [\"string\", \"string\"])\n    def seafile_set_repo_token(repo_id, token):\n        pass\n    set_repo_token = seafile_set_repo_token\n\n    @searpc_func(\"string\", [\"string\"])\n    def seafile_get_repo_token(repo_id):\n        pass\n    get_repo_token = seafile_get_repo_token\n\n    @searpc_func(\"object\", [\"int\", \"string\", \"string\"])\n    def seafile_generate_magic_and_random_key(enc_version, repo_id, password):\n        pass\n    generate_magic_and_random_key = seafile_generate_magic_and_random_key\n\n    @searpc_func(\"int\", [])\n    def seafile_shutdown():\n        pass\n    shutdown = seafile_shutdown\n\n    @searpc_func(\"int\", [\"string\", \"int\"])\n    def seafile_add_del_confirmation(key, value):\n        pass\n    add_del_confirmation = seafile_add_del_confirmation\n"
  },
  {
    "path": "scripts/breakpad.py",
    "content": "#!/usr/bin/env python\n#coding: UTF-8\n\"\"\"Generate the breakpad symbol file and place it in the directory structure\nrequired by breakpad `minidump_stackwalk` tool.\n\nThe directory is ./symbols/seaf-daemon.exe/${symbol_id}/seaf-daemon.exe.sym,\nwhere symbol_id is the first line of the \"dump_syms\" output.\n\"\"\"\n\nfrom __future__ import print_function\nimport os\nfrom os.path import abspath, basename, exists, dirname, join\nimport re\nimport sys\nimport subprocess\nimport optparse\n\n\ndef call(*a, **kw):\n    kw.setdefault('shell', True)\n    subprocess.check_call(*a, **kw)\n\n\ndef get_command_output(cmd, **kw):\n    shell = not isinstance(cmd, list)\n    return subprocess.check_output(cmd, shell=shell, **kw)\n\n\ndef generate_seafile_breakpad_symbol(project_dir, program_name, symbols_output):\n    \"\"\"\n    :param project_dir : [string]\n    :param program_name : [string]\n    :param symbols_output: [string]\n    :return: None\n\n    generate_seafile_breakpad_symbol\n    \"\"\"\n    os.chdir(project_dir)\n    seaf_daemon = join('daemon', '.libs', program_name)\n    if not exists(seaf_daemon):\n        seaf_daemon = join('daemon', program_name)\n        if not exists(seaf_daemon):\n            raise RuntimeError('seaf-daemon executable not found!')\n    symbols = get_command_output('dump_syms {}'.format(seaf_daemon))\n\n    if symbols_output:\n        symbol_file = symbols_output\n    else:\n        symbol_id = symbols.splitlines()[0].split()[3]\n        symbol_dir = join('symbols', program_name, symbol_id)\n        if not exists(symbol_dir):\n            os.makedirs(symbol_dir)\n        symbol_file = join(symbol_dir, '{}.sym'.format(program_name))\n    print('symbols written to {}'.format(symbol_file))\n    with open(symbol_file, 'w') as fp:\n        fp.write(symbols)\n\n\ndef generate_seafile_gui_breakpad_symbol(project_dir, program_name, symbol_output):\n    \"\"\"\n    :param project_dir: [string]\n    :param program_name: [string]\n    :param symbol_output: [string]\n    :return: None\n\n     generate seafile gui breakpad symbol\n     \"\"\"\n\n    os.chdir(project_dir)\n    seafile_gui_path = os.path.join(project_dir, program_name)\n    if not exists(seafile_gui_path):\n        raise RuntimeError('seafile gui executable not found !')\n\n    symbols = get_command_output('dump_syms {}'.format(seafile_gui_path))\n\n    with open(symbol_output, 'w') as fp:\n        fp.write(symbols)\n\n\ndef main():\n    parser = optparse.OptionParser()\n    parser.add_option('--projectSrc', help='the project source file directory')\n    parser.add_option('--name', help='the program name need to generated breakpad symbol')\n    parser.add_option('--output', help='the path of the symbol file.')\n    args, _ = parser.parse_args()\n\n    src_dir = args.projectSrc\n    program_name = args.name\n    symbols_output = args.output\n    if program_name == 'seaf-daemon.exe' or program_name == 'seaf-daemon':\n        # generate seafile breakpad symbols\n        generate_seafile_breakpad_symbol(src_dir, program_name, symbols_output)\n    else:\n        # generate seafile-gui breakpad symbols\n        generate_seafile_gui_breakpad_symbol(src_dir, program_name, symbols_output)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "scripts/build/build-cli.py",
    "content": "#!/usr/bin/env python\n# coding: UTF-8\n\n'''This scirpt builds the seafile command line client (With no gui).\n\nSome notes:\n\n'''\n\nimport sys\n\n####################\n### Requires Python 2.6+\n####################\nif sys.version_info[0] == 3:\n    print 'Python 3 not supported yet. Quit now.'\n    sys.exit(1)\nif sys.version_info[1] < 6:\n    print 'Python 2.6 or above is required. Quit now.'\n    sys.exit(1)\n\nimport os\nimport commands\nimport tempfile\nimport shutil\nimport re\nimport subprocess\nimport optparse\nimport atexit\n\n####################\n### Global variables\n####################\n\n# command line configuartion\nconf = {}\n\n# key names in the conf dictionary.\nCONF_VERSION            = 'version'\nCONF_SEAFILE_VERSION    = 'seafile_version'\nCONF_LIBSEARPC_VERSION  = 'libsearpc_version'\nCONF_CCNET_VERSION      = 'ccnet_version'\nCONF_SRCDIR             = 'srcdir'\nCONF_KEEP               = 'keep'\nCONF_BUILDDIR           = 'builddir'\nCONF_OUTPUTDIR          = 'outputdir'\nCONF_THIRDPARTDIR       = 'thirdpartdir'\nCONF_NO_STRIP           = 'nostrip'\n\n####################\n### Common helper functions\n####################\ndef highlight(content, is_error=False):\n    '''Add ANSI color to content to get it highlighted on terminal'''\n    if is_error:\n        return '\\x1b[1;31m%s\\x1b[m' % content\n    else:\n        return '\\x1b[1;32m%s\\x1b[m' % content\n\ndef info(msg):\n    print highlight('[INFO] ') + msg\n\ndef exist_in_path(prog):\n    '''Test whether prog exists in system path'''\n    dirs = os.environ['PATH'].split(':')\n    for d in dirs:\n        if d == '':\n            continue\n        path = os.path.join(d, prog)\n        if os.path.exists(path):\n            return True\n\n    return False\n\ndef prepend_env_value(name, value, seperator=':'):\n    '''append a new value to a list'''\n    try:\n        current_value = os.environ[name]\n    except KeyError:\n        current_value = ''\n\n    new_value = value\n    if current_value:\n        new_value += seperator + current_value\n\n    os.environ[name] = new_value\n\ndef error(msg=None, usage=None):\n    if msg:\n        print highlight('[ERROR] ') + msg\n    if usage:\n        print usage\n    sys.exit(1)\n\ndef run_argv(argv, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Run a program and wait it to finish, and return its exit code. The\n    standard output of this program is supressed.\n\n    '''\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(argv,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env)\n        return proc.wait()\n\ndef run(cmdline, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Like run_argv but specify a command line string instead of argv'''\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(cmdline,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env,\n                                shell=True)\n        return proc.wait()\n\ndef must_mkdir(path):\n    '''Create a directory, exit on failure'''\n    try:\n        os.mkdir(path)\n    except OSError, e:\n        error('failed to create directory %s:%s' % (path, e))\n\ndef must_copy(src, dst):\n    '''Copy src to dst, exit on failure'''\n    try:\n        shutil.copy(src, dst)\n    except Exception, e:\n        error('failed to copy %s to %s: %s' % (src, dst, e))\n\nclass Project(object):\n    '''Base class for a project'''\n    # Probject name, i.e. libseaprc/ccnet/seafile/\n    name = ''\n\n    # A list of shell commands to configure/build the project\n    build_commands = []\n\n    def __init__(self):\n        # the path to pass to --prefix=/<prefix>\n        self.prefix = os.path.join(conf[CONF_BUILDDIR], 'seafile-cli')\n        self.version = self.get_version()\n        self.src_tarball = os.path.join(conf[CONF_SRCDIR],\n                            '%s-%s.tar.gz' % (self.name, self.version))\n        # project dir, like <builddir>/seafile-1.2.2/\n        self.projdir = os.path.join(conf[CONF_BUILDDIR], '%s-%s' % (self.name, self.version))\n\n    def get_version(self):\n        # libsearpc and ccnet can have different versions from seafile.\n        raise NotImplementedError\n\n    def get_source_commit_id(self):\n        '''By convetion, we record the commit id of the source code in the\n        file \"<projdir>/latest_commit\"\n\n        '''\n        latest_commit_file = os.path.join(self.projdir, 'latest_commit')\n        with open(latest_commit_file, 'r') as fp:\n            commit_id = fp.read().strip('\\n\\r\\t ')\n\n        return commit_id\n\n    def append_cflags(self, macros):\n        cflags = ' '.join([ '-D%s=%s' % (k, macros[k]) for k in macros ])\n        prepend_env_value('CPPFLAGS',\n                          cflags,\n                          seperator=' ')\n\n    def uncompress(self):\n        '''Uncompress the source from the tarball'''\n        info('Uncompressing %s' % self.name)\n\n        if run('tar xf %s' % self.src_tarball) < 0:\n            error('failed to uncompress source of %s' % self.name)\n\n    def before_build(self):\n        '''Hook method to do project-specific stuff before running build commands'''\n        pass\n\n    def build(self):\n        '''Build the source'''\n        self.before_build()\n        info('Building %s' % self.name)\n        for cmd in self.build_commands:\n            if run(cmd, cwd=self.projdir) != 0:\n                error('error when running command:\\n\\t%s\\n' % cmd)\n\nclass Libsearpc(Project):\n    name = 'libsearpc'\n\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            './configure --prefix=%s --disable-compile-demo' % self.prefix,\n            'make',\n            'make install'\n        ]\n\n    def get_version(self):\n        return conf[CONF_LIBSEARPC_VERSION]\n\nclass Ccnet(Project):\n    name = 'ccnet'\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            './configure --prefix=%s --disable-compile-demo' % self.prefix,\n            'make',\n            'make install'\n        ]\n\n    def get_version(self):\n        return conf[CONF_CCNET_VERSION]\n\n    def before_build(self):\n        macros = {}\n        # SET CCNET_SOURCE_COMMIT_ID, so it can be printed in the log\n        macros['CCNET_SOURCE_COMMIT_ID'] = '\\\\\"%s\\\\\"' % self.get_source_commit_id()\n\n        self.append_cflags(macros)\n\nclass Seafile(Project):\n    name = 'seafile'\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            './configure --prefix=%s --disable-gui' % self.prefix,\n            'make',\n            'make install'\n        ]\n\n    def get_version(self):\n        return conf[CONF_SEAFILE_VERSION]\n\n    def update_cli_version(self):\n        '''Substitute the version number in seaf-cli'''\n        cli_py = os.path.join(self.projdir, 'app', 'seaf-cli')\n        with open(cli_py, 'r') as fp:\n            lines = fp.readlines()\n\n        ret = []\n        for line in lines:\n            old = '''SEAF_CLI_VERSION = \"\"'''\n            new = '''SEAF_CLI_VERSION = \"%s\"''' % conf[CONF_VERSION]\n            line = line.replace(old, new)\n            ret.append(line)\n\n        with open(cli_py, 'w') as fp:\n            fp.writelines(ret)\n\n    def before_build(self):\n        self.update_cli_version()\n        macros = {}\n        # SET SEAFILE_SOURCE_COMMIT_ID, so it can be printed in the log\n        macros['SEAFILE_SOURCE_COMMIT_ID'] = '\\\\\"%s\\\\\"' % self.get_source_commit_id()\n        self.append_cflags(macros)\n\ndef check_targz_src(proj, version, srcdir):\n    src_tarball = os.path.join(srcdir, '%s-%s.tar.gz' % (proj, version))\n    if not os.path.exists(src_tarball):\n        error('%s not exists' % src_tarball)\n\ndef validate_args(usage, options):\n    required_args = [\n        CONF_VERSION,\n        CONF_LIBSEARPC_VERSION,\n        CONF_CCNET_VERSION,\n        CONF_SEAFILE_VERSION,\n        CONF_SRCDIR,\n    ]\n\n    # fist check required args\n    for optname in required_args:\n        if getattr(options, optname, None) == None:\n            error('%s must be specified' % optname, usage=usage)\n\n    def get_option(optname):\n        return getattr(options, optname)\n\n    # [ version ]\n    def check_project_version(version):\n        '''A valid version must be like 1.2.2, 1.3'''\n        if not re.match('^[0-9]+(\\.([0-9])+)+$', version):\n            error('%s is not a valid version' % version, usage=usage)\n\n    version = get_option(CONF_VERSION)\n    seafile_version = get_option(CONF_SEAFILE_VERSION)\n    libsearpc_version = get_option(CONF_LIBSEARPC_VERSION)\n    ccnet_version = get_option(CONF_CCNET_VERSION)\n\n    check_project_version(version)\n    check_project_version(libsearpc_version)\n    check_project_version(ccnet_version)\n    check_project_version(seafile_version)\n\n    # [ srcdir ]\n    srcdir = get_option(CONF_SRCDIR)\n    check_targz_src('libsearpc', libsearpc_version, srcdir)\n    check_targz_src('ccnet', ccnet_version, srcdir)\n    check_targz_src('seafile', seafile_version, srcdir)\n\n    # [ builddir ]\n    builddir = get_option(CONF_BUILDDIR)\n    if not os.path.exists(builddir):\n        error('%s does not exist' % builddir, usage=usage)\n\n    builddir = os.path.join(builddir, 'seafile-cli-build')\n\n    # [ outputdir ]\n    outputdir = get_option(CONF_OUTPUTDIR)\n    if outputdir:\n        if not os.path.exists(outputdir):\n            error('outputdir %s does not exist' % outputdir, usage=usage)\n    else:\n        outputdir = os.getcwd()\n\n    # [ keep ]\n    keep = get_option(CONF_KEEP)\n\n    # [ no strip]\n    nostrip = get_option(CONF_NO_STRIP)\n\n    conf[CONF_VERSION] = version\n    conf[CONF_LIBSEARPC_VERSION] = libsearpc_version\n    conf[CONF_SEAFILE_VERSION] = seafile_version\n    conf[CONF_CCNET_VERSION] = ccnet_version\n\n    conf[CONF_BUILDDIR] = builddir\n    conf[CONF_SRCDIR] = srcdir\n    conf[CONF_OUTPUTDIR] = outputdir\n    conf[CONF_KEEP] = keep\n    conf[CONF_NO_STRIP] = nostrip\n\n    prepare_builddir(builddir)\n    show_build_info()\n\ndef show_build_info():\n    '''Print all conf information. Confirm before continue.'''\n    info('------------------------------------------')\n    info('Seafile command line client %s: BUILD INFO' % conf[CONF_VERSION])\n    info('------------------------------------------')\n    info('seafile:          %s' % conf[CONF_SEAFILE_VERSION])\n    info('ccnet:            %s' % conf[CONF_CCNET_VERSION])\n    info('libsearpc:        %s' % conf[CONF_LIBSEARPC_VERSION])\n    info('builddir:         %s' % conf[CONF_BUILDDIR])\n    info('outputdir:        %s' % conf[CONF_OUTPUTDIR])\n    info('source dir:       %s' % conf[CONF_SRCDIR])\n    info('strip symbols:    %s' % (not conf[CONF_NO_STRIP]))\n    info('clean on exit:    %s' % (not conf[CONF_KEEP]))\n    info('------------------------------------------')\n    info('press any key to continue ')\n    info('------------------------------------------')\n    dummy = raw_input()\n\ndef prepare_builddir(builddir):\n    must_mkdir(builddir)\n\n    if not conf[CONF_KEEP]:\n        def remove_builddir():\n            '''Remove the builddir when exit'''\n            info('remove builddir before exit')\n            shutil.rmtree(builddir, ignore_errors=True)\n        atexit.register(remove_builddir)\n\n    os.chdir(builddir)\n\n    must_mkdir(os.path.join(builddir, 'seafile-cli'))\n\ndef parse_args():\n    parser = optparse.OptionParser()\n    def long_opt(opt):\n        return '--' + opt\n\n    parser.add_option(long_opt(CONF_VERSION),\n                      dest=CONF_VERSION,\n                      nargs=1,\n                      help='the version to build. Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_SEAFILE_VERSION),\n                      dest=CONF_SEAFILE_VERSION,\n                      nargs=1,\n                      help='the version of seafile as specified in its \"configure.ac\". Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_LIBSEARPC_VERSION),\n                      dest=CONF_LIBSEARPC_VERSION,\n                      nargs=1,\n                      help='the version of libsearpc as specified in its \"configure.ac\". Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_CCNET_VERSION),\n                      dest=CONF_CCNET_VERSION,\n                      nargs=1,\n                      help='the version of ccnet as specified in its \"configure.ac\". Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_BUILDDIR),\n                      dest=CONF_BUILDDIR,\n                      nargs=1,\n                      help='the directory to build the source. Defaults to /tmp',\n                      default=tempfile.gettempdir())\n\n    parser.add_option(long_opt(CONF_OUTPUTDIR),\n                      dest=CONF_OUTPUTDIR,\n                      nargs=1,\n                      help='the output directory to put the generated tarball. Defaults to the current directory.',\n                      default=os.getcwd())\n\n    parser.add_option(long_opt(CONF_SRCDIR),\n                      dest=CONF_SRCDIR,\n                      nargs=1,\n                      help='''Source tarballs must be placed in this directory.''')\n\n    parser.add_option(long_opt(CONF_KEEP),\n                      dest=CONF_KEEP,\n                      action='store_true',\n                      help='''keep the build directory after the script exits. By default, the script would delete the build directory at exit.''')\n\n    parser.add_option(long_opt(CONF_NO_STRIP),\n                      dest=CONF_NO_STRIP,\n                      action='store_true',\n                      help='''do not strip debug symbols''')\n    usage = parser.format_help()\n    options, remain = parser.parse_args()\n    if remain:\n        error(usage=usage)\n\n    validate_args(usage, options)\n\ndef setup_build_env():\n    '''Setup environment variables, such as export PATH=$BUILDDDIR/bin:$PATH'''\n    prefix = os.path.join(conf[CONF_BUILDDIR], 'seafile-cli')\n\n    prepend_env_value('CPPFLAGS',\n                     '-I%s' % os.path.join(prefix, 'include'),\n                     seperator=' ')\n\n    prepend_env_value('CPPFLAGS',\n                     '-DSEAFILE_CLIENT_VERSION=\\\\\"%s\\\\\"' % conf[CONF_VERSION],\n                     seperator=' ')\n\n    if conf[CONF_NO_STRIP]:\n        prepend_env_value('CPPFLAGS',\n                         '-g -O0',\n                         seperator=' ')\n\n    prepend_env_value('LDFLAGS',\n                     '-L%s' % os.path.join(prefix, 'lib'),\n                     seperator=' ')\n\n    prepend_env_value('LDFLAGS',\n                     '-L%s' % os.path.join(prefix, 'lib64'),\n                     seperator=' ')\n\n    prepend_env_value('PATH', os.path.join(prefix, 'bin'))\n    prepend_env_value('PKG_CONFIG_PATH', os.path.join(prefix, 'lib', 'pkgconfig'))\n    prepend_env_value('PKG_CONFIG_PATH', os.path.join(prefix, 'lib64', 'pkgconfig'))\n\ndef copy_scripts_and_libs():\n    '''Copy scripts and shared libs'''\n    builddir = conf[CONF_BUILDDIR]\n    seafile_dir = os.path.join(builddir, Seafile().projdir)\n    scripts_srcdir = os.path.join(seafile_dir, 'scripts')\n    doc_dir = os.path.join(seafile_dir, 'doc')\n    cli_dir = os.path.join(builddir, 'seafile-cli')\n\n    # copy the wrapper shell script for seaf-cli.py\n    src = os.path.join(scripts_srcdir, 'seaf-cli-wrapper.sh')\n    dst = os.path.join(cli_dir, 'seaf-cli')\n\n    must_copy(src, dst)\n\n    # copy Readme for cli client\n    src = os.path.join(doc_dir, 'cli-readme.txt')\n    dst = os.path.join(cli_dir, 'Readme.txt')\n\n    must_copy(src, dst)\n\n    # rename seaf-cli to seaf-cli.py to avoid confusing users\n    src = os.path.join(cli_dir, 'bin', 'seaf-cli')\n    dst = os.path.join(cli_dir, 'bin', 'seaf-cli.py')\n\n    try:\n        shutil.move(src, dst)\n    except Exception, e:\n        error('failed to move %s to %s: %s' % (src, dst, e))\n\n    # copy shared c libs\n    copy_shared_libs()\n\ndef get_dependent_libs(executable):\n    syslibs = ['libsearpc', 'libccnet', 'libseafile', 'libpthread.so', 'libc.so', 'libm.so', 'librt.so', 'libdl.so', 'libselinux.so']\n    def is_syslib(lib):\n        for syslib in syslibs:\n            if syslib in lib:\n                return True\n        return False\n\n    ldd_output = commands.getoutput('ldd %s' % executable)\n    ret = []\n    for line in ldd_output.splitlines():\n        tokens = line.split()\n        if len(tokens) != 4:\n            continue\n        if is_syslib(tokens[0]):\n            continue\n\n        ret.append(tokens[2])\n\n    return ret\n\ndef copy_shared_libs():\n    '''copy shared c libs, such as libevent, glib, libmysqlclient'''\n    builddir = conf[CONF_BUILDDIR]\n\n    dst_dir = os.path.join(builddir,\n                           'seafile-cli',\n                           'lib')\n\n    ccnet_daemon_path = os.path.join(builddir,\n                                     'seafile-cli',\n                                     'bin',\n                                     'ccnet')\n\n    seaf_daemon_path = os.path.join(builddir,\n                                    'seafile-cli',\n                                    'bin',\n                                    'seaf-daemon')\n\n    ccnet_daemon_libs = get_dependent_libs(ccnet_daemon_path)\n    seaf_daemon_libs = get_dependent_libs(seaf_daemon_path)\n\n    libs = ccnet_daemon_libs\n    for lib in seaf_daemon_libs:\n        if lib not in libs:\n            libs.append(lib)\n\n    for lib in libs:\n        info('Copying %s' % lib)\n        shutil.copy(lib, dst_dir)\n\ndef strip_symbols():\n    def do_strip(fn):\n        run('chmod u+w %s' % fn)\n        info('stripping:    %s' % fn)\n        run('strip \"%s\"' % fn)\n\n    def remove_static_lib(fn):\n        info('removing:     %s' % fn)\n        os.remove(fn)\n\n    builddir = conf[CONF_BUILDDIR]\n    topdir = os.path.join(builddir, 'seafile-cli')\n    for parent, dnames, fnames in os.walk(topdir):\n        dummy = dnames          # avoid pylint 'unused' warning\n        for fname in fnames:\n            fn = os.path.join(parent, fname)\n            if os.path.isdir(fn):\n                continue\n\n            if fn.endswith(\".a\") or fn.endswith(\".la\"):\n                remove_static_lib(fn)\n                continue\n\n            if os.path.islink(fn):\n                continue\n\n            finfo = commands.getoutput('file \"%s\"' % fn)\n\n            if 'not stripped' in finfo:\n                do_strip(fn)\n\ndef create_tarball(tarball_name):\n    '''call tar command to generate a tarball'''\n    version  = conf[CONF_VERSION]\n\n    cli_dir = 'seafile-cli'\n    versioned_cli_dir = 'seafile-cli-' + version\n\n    # move seafile-cli to seafile-cli-${version}\n    try:\n        shutil.move(cli_dir, versioned_cli_dir)\n    except Exception, e:\n        error('failed to move %s to %s: %s' % (cli_dir, versioned_cli_dir, e))\n\n    ignored_patterns = [\n        # common ignored files\n        '*.pyc',\n        '*~',\n        '*#',\n\n        # seafile\n        os.path.join(versioned_cli_dir, 'share*'),\n        os.path.join(versioned_cli_dir, 'include*'),\n        os.path.join(versioned_cli_dir, 'lib', 'pkgconfig*'),\n        os.path.join(versioned_cli_dir, 'lib64', 'pkgconfig*'),\n        os.path.join(versioned_cli_dir, 'bin', 'ccnet-demo*'),\n        os.path.join(versioned_cli_dir, 'bin', 'ccnet-tool'),\n        os.path.join(versioned_cli_dir, 'bin', 'ccnet-servtool'),\n        os.path.join(versioned_cli_dir, 'bin', 'searpc-codegen.py'),\n        os.path.join(versioned_cli_dir, 'bin', 'seafile-admin'),\n        os.path.join(versioned_cli_dir, 'bin', 'seafile'),\n    ]\n\n    excludes_list = [ '--exclude=%s' % pattern for pattern in ignored_patterns ]\n    excludes = ' '.join(excludes_list)\n\n    tar_cmd = 'tar czvf %(tarball_name)s %(versioned_cli_dir)s %(excludes)s' \\\n              % dict(tarball_name=tarball_name,\n                     versioned_cli_dir=versioned_cli_dir,\n                     excludes=excludes)\n\n    if run(tar_cmd) != 0:\n        error('failed to generate the tarball')\n\ndef gen_tarball():\n    # strip symbols of libraries to reduce size\n    if not conf[CONF_NO_STRIP]:\n        try:\n            strip_symbols()\n        except Exception, e:\n            error('failed to strip symbols: %s' % e)\n\n    # determine the output name\n    # 64-bit: seafile-cli_1.2.2_x86-64.tar.gz\n    # 32-bit: seafile-cli_1.2.2_i386.tar.gz\n    version = conf[CONF_VERSION]\n    arch = os.uname()[-1].replace('_', '-')\n    if arch != 'x86-64':\n        arch = 'i386'\n\n    dbg = ''\n    if conf[CONF_NO_STRIP]:\n        dbg = '.dbg'\n\n    tarball_name = 'seafile-cli_%(version)s_%(arch)s%(dbg)s.tar.gz' \\\n                   % dict(version=version, arch=arch, dbg=dbg)\n    dst_tarball = os.path.join(conf[CONF_OUTPUTDIR], tarball_name)\n\n    # generate the tarball\n    try:\n        create_tarball(tarball_name)\n    except Exception, e:\n        error('failed to generate tarball: %s' % e)\n\n    # move tarball to outputdir\n    try:\n        shutil.copy(tarball_name, dst_tarball)\n    except Exception, e:\n        error('failed to copy %s to %s: %s' % (tarball_name, dst_tarball, e))\n\n    print '---------------------------------------------'\n    print 'The build is successfully. Output is:\\t%s' % dst_tarball\n    print '---------------------------------------------'\n\ndef main():\n    parse_args()\n    setup_build_env()\n\n    libsearpc = Libsearpc()\n    ccnet = Ccnet()\n    seafile = Seafile()\n\n    libsearpc.uncompress()\n    libsearpc.build()\n\n    ccnet.uncompress()\n    ccnet.build()\n\n    seafile.uncompress()\n    seafile.build()\n\n    copy_scripts_and_libs()\n    gen_tarball()\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "scripts/build/build-deb.py",
    "content": "#!/usr/bin/env python\n# coding: UTF-8\n\n'''This scirpt builds the seafile server tarball.\n\nSome notes:\n\n1. The working directory is always the 'builddir'. 'os.chdir' is only called\nto change to the 'builddir'. We make use of the 'cwd' argument in\n'subprocess.Popen' to run a command in a specific directory.\n\n'''\n\nimport sys\n\n####################\n### Requires Python 2.6+\n####################\nif sys.version_info[0] == 3:\n    print 'Python 3 not supported yet. Quit now.'\n    sys.exit(1)\nif sys.version_info[1] < 6:\n    print 'Python 2.6 or above is required. Quit now.'\n    sys.exit(1)\n\nimport os\nimport glob\nimport tempfile\nimport shutil\nimport re\nimport subprocess\nimport optparse\nimport atexit\n\n####################\n### Global variables\n####################\n\n# command line configuartion\nconf = {}\n\n# key names in the conf dictionary.\nCONF_VERSION            = 'version'\nCONF_LIBSEARPC_VERSION  = 'libsearpc_version'\nCONF_CCNET_VERSION      = 'ccnet_version'\nCONF_SEAFILE_VERSION    = 'seafile_version'\nCONF_SEAFILE_CLIENT_VERSION    = 'seafile_client_version'\nCONF_SRCDIR             = 'srcdir'\nCONF_KEEP               = 'keep'\nCONF_BUILDDIR           = 'builddir'\nCONF_OUTPUTDIR          = 'outputdir'\nCONF_NO_STRIP           = 'nostrip'\n\n####################\n### Common helper functions\n####################\ndef highlight(content, is_error=False):\n    '''Add ANSI color to content to get it highlighted on terminal'''\n    if is_error:\n        return '\\x1b[1;31m%s\\x1b[m' % content\n    else:\n        return '\\x1b[1;32m%s\\x1b[m' % content\n\ndef info(msg):\n    print highlight('[INFO] ') + msg\n\ndef exist_in_path(prog):\n    '''Test whether prog exists in system path'''\n    dirs = os.environ['PATH'].split(':')\n    for d in dirs:\n        if d == '':\n            continue\n        path = os.path.join(d, prog)\n        if os.path.exists(path):\n            return True\n\n    return False\n\ndef prepend_env_value(name, value, seperator=':'):\n    '''append a new value to a list'''\n    try:\n        current_value = os.environ[name]\n    except KeyError:\n        current_value = ''\n\n    new_value = value\n    if current_value:\n        new_value += seperator + current_value\n\n    os.environ[name] = new_value\n\ndef error(msg=None, usage=None):\n    if msg:\n        print highlight('[ERROR] ') + msg\n    if usage:\n        print usage\n    sys.exit(1)\n\ndef run_argv(argv, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Run a program and wait it to finish, and return its exit code. The\n    standard output of this program is supressed.\n\n    '''\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(argv,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env)\n        return proc.wait()\n\ndef run(cmdline, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Like run_argv but specify a command line string instead of argv'''\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(cmdline,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env,\n                                shell=True)\n        return proc.wait()\n\ndef must_mkdir(path):\n    '''Create a directory, exit on failure'''\n    try:\n        os.mkdir(path)\n    except OSError, e:\n        error('failed to create directory %s:%s' % (path, e))\n\ndef must_copy(src, dst):\n    '''Copy src to dst, exit on failure'''\n    try:\n        shutil.copy(src, dst)\n    except Exception, e:\n        error('failed to copy %s to %s: %s' % (src, dst, e))\n\nclass Project(object):\n    '''Base class for a project'''\n    # Probject name, i.e. libseaprc/ccnet/seafile/seahub\n    name = ''\n\n    # A list of shell commands to configure/build the project\n    build_commands = []\n\n    def __init__(self):\n        self.version = self.get_version()\n        self.src_tarball = os.path.join(conf[CONF_SRCDIR],\n                            '%s-%s.tar.gz' % (self.name, self.version))\n        # project dir, like <builddir>/seafile-1.2.2/\n        self.projdir = os.path.join(conf[CONF_BUILDDIR], '%s-%s' % (self.name, self.version))\n\n    def get_version(self):\n        # libsearpc and ccnet can have different versions from seafile.\n        raise NotImplementedError\n\n    def get_source_commit_id(self):\n        '''By convetion, we record the commit id of the source code in the\n        file \"<projdir>/latest_commit\"\n\n        '''\n        latest_commit_file = os.path.join(self.projdir, 'latest_commit')\n        with open(latest_commit_file, 'r') as fp:\n            commit_id = fp.read().strip('\\n\\r\\t ')\n\n        return commit_id\n\n    def append_cflags(self, macros):\n        cflags = ' '.join([ '-D%s=%s' % (k, macros[k]) for k in macros ])\n        prepend_env_value('DEB_CPPFLAGS_APPEND',\n                          cflags,\n                          seperator=' ')\n\n    def uncompress(self):\n        '''Uncompress the source from the tarball'''\n        info('Uncompressing %s' % self.name)\n\n        if run('tar xf %s' % self.src_tarball) < 0:\n            error('failed to uncompress source of %s' % self.name)\n\n    def before_build(self):\n        '''Hook method to do project-specific stuff before running build commands'''\n        pass\n\n    def build(self):\n        '''Build the source'''\n        self.before_build()\n        info('Building %s' % self.name)\n        for cmd in self.build_commands:\n            if run(cmd, cwd=self.projdir) != 0:\n                error('error when running command:\\n\\t%s\\n' % cmd)\n\nclass Libsearpc(Project):\n    name = 'libsearpc'\n\n    def get_version(self):\n        return conf[CONF_LIBSEARPC_VERSION]\n\nclass Ccnet(Project):\n    name = 'ccnet'\n\n    def get_version(self):\n        return conf[CONF_CCNET_VERSION]\n\n    def before_build(self):\n        macros = {}\n        # SET CCNET_SOURCE_COMMIT_ID, so it can be printed in the log\n        macros['CCNET_SOURCE_COMMIT_ID'] = '\\\\\"%s\\\\\"' % self.get_source_commit_id()\n\n        self.append_cflags(macros)\n\nclass SeafileClient(Project):\n    name = 'seafile-client'\n\n    def get_version(self):\n        return conf[CONF_SEAFILE_CLIENT_VERSION]\n\n    def before_build(self):\n        pass\n\nclass Seafile(Project):\n    name = 'seafile'\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            'dpkg-buildpackage -B -nc -uc -us',\n        ]\n\n    def get_version(self):\n        return conf[CONF_SEAFILE_VERSION]\n\n    def before_build(self):\n        macros = {}\n        # SET SEAFILE_SOURCE_COMMIT_ID, so it can be printed in the log\n        macros['SEAFILE_SOURCE_COMMIT_ID'] = '\\\\\"%s\\\\\"' % self.get_source_commit_id()\n        self.append_cflags(macros)\n\ndef check_targz_src(proj, version, srcdir):\n    src_tarball = os.path.join(srcdir, '%s-%s.tar.gz' % (proj, version))\n    if not os.path.exists(src_tarball):\n        error('%s not exists' % src_tarball)\n\ndef validate_args(usage, options):\n    required_args = [\n        CONF_VERSION,\n        CONF_LIBSEARPC_VERSION,\n        CONF_CCNET_VERSION,\n        CONF_SEAFILE_VERSION,\n        CONF_SEAFILE_CLIENT_VERSION,\n        CONF_SRCDIR,\n    ]\n\n    # fist check required args\n    for optname in required_args:\n        if getattr(options, optname, None) == None:\n            error('%s must be specified' % optname, usage=usage)\n\n    def get_option(optname):\n        return getattr(options, optname)\n\n    # [ version ]\n    def check_project_version(version):\n        '''A valid version must be like 1.2.2, 1.3'''\n        if not re.match('^[0-9]+(\\.([0-9])+)+$', version):\n            error('%s is not a valid version' % version, usage=usage)\n\n    version = get_option(CONF_VERSION)\n    libsearpc_version = get_option(CONF_LIBSEARPC_VERSION)\n    ccnet_version = get_option(CONF_CCNET_VERSION)\n    seafile_version = get_option(CONF_SEAFILE_VERSION)\n    seafile_client_version = get_option(CONF_SEAFILE_CLIENT_VERSION)\n\n    check_project_version(version)\n    check_project_version(libsearpc_version)\n    check_project_version(ccnet_version)\n    check_project_version(seafile_version)\n\n    # [ srcdir ]\n    srcdir = get_option(CONF_SRCDIR)\n    check_targz_src('libsearpc', libsearpc_version, srcdir)\n    check_targz_src('ccnet', ccnet_version, srcdir)\n    check_targz_src('seafile', seafile_version, srcdir)\n    check_targz_src('seafile-client', seafile_client_version, srcdir)\n\n    # [ builddir ]\n    builddir = get_option(CONF_BUILDDIR)\n    if not os.path.exists(builddir):\n        error('%s does not exist' % builddir, usage=usage)\n\n    builddir = os.path.join(builddir, 'seafile-deb-build')\n\n    # [ outputdir ]\n    outputdir = get_option(CONF_OUTPUTDIR)\n    if outputdir:\n        if not os.path.exists(outputdir):\n            error('outputdir %s does not exist' % outputdir, usage=usage)\n    else:\n        outputdir = os.getcwd()\n\n    # [ keep ]\n    keep = get_option(CONF_KEEP)\n\n    # [ no strip]\n    nostrip = get_option(CONF_NO_STRIP)\n\n    conf[CONF_VERSION] = version\n    conf[CONF_LIBSEARPC_VERSION] = libsearpc_version\n    conf[CONF_CCNET_VERSION] = ccnet_version\n    conf[CONF_SEAFILE_VERSION] = seafile_version\n    conf[CONF_SEAFILE_CLIENT_VERSION] = seafile_client_version\n\n    conf[CONF_BUILDDIR] = builddir\n    conf[CONF_SRCDIR] = srcdir\n    conf[CONF_OUTPUTDIR] = outputdir\n    conf[CONF_KEEP] = True\n    conf[CONF_NO_STRIP] = nostrip\n\n    prepare_builddir(builddir)\n    show_build_info()\n\ndef show_build_info():\n    '''Print all conf information. Confirm before continue.'''\n    info('------------------------------------------')\n    info('Seafile debian package: BUILD INFO')\n    info('------------------------------------------')\n    info('seafile:          %s' % conf[CONF_SEAFILE_VERSION])\n    info('seafile-client:   %s' % conf[CONF_SEAFILE_CLIENT_VERSION])\n    info('ccnet:            %s' % conf[CONF_CCNET_VERSION])\n    info('libsearpc:        %s' % conf[CONF_LIBSEARPC_VERSION])\n    info('builddir:         %s' % conf[CONF_BUILDDIR])\n    info('outputdir:        %s' % conf[CONF_OUTPUTDIR])\n    info('source dir:       %s' % conf[CONF_SRCDIR])\n    info('strip symbols:    %s' % (not conf[CONF_NO_STRIP]))\n    info('clean on exit:    %s' % (not conf[CONF_KEEP]))\n    info('------------------------------------------')\n    info('press any key to continue ')\n    info('------------------------------------------')\n    dummy = raw_input()\n\ndef prepare_builddir(builddir):\n    must_mkdir(builddir)\n\n    if not conf[CONF_KEEP]:\n        def remove_builddir():\n            '''Remove the builddir when exit'''\n            info('remove builddir before exit')\n            shutil.rmtree(builddir, ignore_errors=True)\n        atexit.register(remove_builddir)\n\n    os.chdir(builddir)\n\ndef parse_args():\n    parser = optparse.OptionParser()\n    def long_opt(opt):\n        return '--' + opt\n\n    parser.add_option(long_opt(CONF_VERSION),\n                      dest=CONF_VERSION,\n                      nargs=1,\n                      help='the version to build. Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_LIBSEARPC_VERSION),\n                      dest=CONF_LIBSEARPC_VERSION,\n                      nargs=1,\n                      help='the version of libsearpc as specified in its \"configure.ac\". Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_CCNET_VERSION),\n                      dest=CONF_CCNET_VERSION,\n                      nargs=1,\n                      help='the version of ccnet as specified in its \"configure.ac\". Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_SEAFILE_VERSION),\n                      dest=CONF_SEAFILE_VERSION,\n                      nargs=1,\n                      help='the version of ccnet as specified in its \"configure.ac\". Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_SEAFILE_CLIENT_VERSION),\n                      dest=CONF_SEAFILE_CLIENT_VERSION,\n                      nargs=1,\n                      help='the version of seafile-client. Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_BUILDDIR),\n                      dest=CONF_BUILDDIR,\n                      nargs=1,\n                      help='the directory to build the source. Defaults to /tmp',\n                      default=tempfile.gettempdir())\n\n    parser.add_option(long_opt(CONF_OUTPUTDIR),\n                      dest=CONF_OUTPUTDIR,\n                      nargs=1,\n                      help='the output directory to put the generated server tarball. Defaults to the current directory.',\n                      default=os.getcwd())\n\n    parser.add_option(long_opt(CONF_SRCDIR),\n                      dest=CONF_SRCDIR,\n                      nargs=1,\n                      help='''Source tarballs must be placed in this directory.''')\n\n    parser.add_option(long_opt(CONF_KEEP),\n                      dest=CONF_KEEP,\n                      action='store_true',\n                      help='''keep the build directory after the script exits. By default, the script would delete the build directory at exit.''')\n\n    parser.add_option(long_opt(CONF_NO_STRIP),\n                      dest=CONF_NO_STRIP,\n                      action='store_true',\n                      help='''do not strip debug symbols''')\n    usage = parser.format_help()\n    options, remain = parser.parse_args()\n    if remain:\n        error(usage=usage)\n\n    validate_args(usage, options)\n\ndef setup_build_env():\n    '''Setup environment variables, such as export PATH=$BUILDDDIR/bin:$PATH'''\n    prefix = os.path.join(Seafile().projdir, 'debian', 'seafile', 'usr')\n\n    prepend_env_value('DEB_CPPFLAGS_APPEND',\n                     '-DSEAFILE_CLIENT_VERSION=\\\\\"%s\\\\\"' % conf[CONF_VERSION],\n                     seperator=' ')\n\n    if conf[CONF_NO_STRIP]:\n        prepend_env_value('DEB_CPPFLAGS_APPEND',\n                         '-g -O0',\n                         seperator=' ')\n        os.environ['SEAFILE_NOSTRIP'] = 'true'\n        os.environ['DEB_CFLAGS_SET'] = ''\n        os.environ['DEB_CXXFLAGS_SET'] = ''\n\n    prepend_env_value('PATH', os.path.join(prefix, 'bin'))\n    prepend_env_value('PKG_CONFIG_PATH', os.path.join(prefix, 'lib', 'pkgconfig'))\n\n    os.environ['LIBSEARPC_SOURCE_DIR'] = Libsearpc().projdir\n    os.environ['CCNET_SOURCE_DIR'] = Ccnet().projdir\n    os.environ['SEAFILE_CLIENT_SOURCE_DIR'] = SeafileClient().projdir\n\ndef move_deb():\n    builddir = conf[CONF_BUILDDIR]\n    deb_name = glob.glob('*.deb')[0]\n\n    src_deb = os.path.join(builddir, deb_name)\n    dst_deb = os.path.join(conf[CONF_OUTPUTDIR], deb_name)\n\n    # move deb to outputdir\n    try:\n        shutil.move(src_deb, dst_deb)\n    except Exception, e:\n        error('failed to copy %s to %s: %s' % (src_deb, dst_deb, e))\n\n    print '---------------------------------------------'\n    print 'The build is successfully. Output is:\\t%s' % dst_deb\n    print '---------------------------------------------'\n\ndef main():\n    parse_args()\n    setup_build_env()\n\n    libsearpc = Libsearpc()\n    ccnet = Ccnet()\n    seafile = Seafile()\n    seafile_client = SeafileClient()\n\n    libsearpc.uncompress()\n    ccnet.uncompress()\n    seafile.uncompress()\n    seafile_client.uncompress()\n\n    libsearpc.build()\n    ccnet.build()\n    seafile.build()\n    seafile_client.build()\n\n    move_deb()\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "scripts/build/build-mac-local-py3.py",
    "content": "#!/usr/bin/env python\n# coding: UTF-8\n\n'''This script builds the seafile mac client.\n'''\n\nimport atexit\nimport logging\nfrom contextlib import contextmanager\nimport glob\nimport multiprocessing\nimport optparse\nimport os\nfrom os.path import abspath, basename, dirname, join, exists, expanduser\nimport re\nimport shutil\nimport subprocess\nimport sys\nimport tempfile\n\nFINAL_APP = 'Seafile Client.app'\nFSPLUGIN_APPEX_NAME = 'Seafile FinderSync.appex'\nCERT_ID = '7C7618F1448BBC6825B53BFFE21701ACED853DF3'\n\nBUILDDIR = join(os.getcwd(), \"../../../\")\n\n####################\n### Requires Python 2.6+\n####################\n\n####################\n### Global variables\n####################\n# command line configuartion\nconf = {}\n\n# key names in the conf dictionary.\n# pylint: disable=bad-whitespace\nCONF_VERSION            = 'version'\nCONF_NO_STRIP           = 'nostrip'\nCONF_BRAND              = 'brand'\nCONF_UNIVERSAL          = 'universal'\n\nNUM_CPU = multiprocessing.cpu_count()\nPID = os.getpid()\n\n####################\n### Common helper functions\n####################\ndef highlight(content, is_error=False):\n    '''Add ANSI color to content to get it highlighted on terminal'''\n    if is_error:\n        return '\\x1b[1;31m%s\\x1b[m' % content\n    else:\n        return '\\x1b[1;32m%s\\x1b[m' % content\n\ndef info(msg):\n    logging.info(highlight('[INFO][{}] ').format(msg))\n\n@contextmanager\ndef cd(path):\n    oldpwd = os.getcwd()\n    os.chdir(path)\n    try:\n        yield\n    finally:\n        os.chdir(oldpwd)\n\ndef exist_in_path(prog):\n    '''Test whether prog exists in system path'''\n    return bool(find_in_path(prog))\n\ndef prepend_env_value(name, value, seperator=':'):\n    '''append a new value to a list'''\n    try:\n        current_value = os.environ[name]\n    except KeyError:\n        current_value = ''\n\n    new_value = value\n    if current_value:\n        new_value += seperator + current_value\n\n    os.environ[name] = new_value\n\ndef find_in_path(prog):\n    '''Find a file in system path'''\n    dirs = os.environ['PATH'].split(':')\n    for d in dirs:\n        if d == '':\n            continue\n        path = join(d, prog)\n        if exists(path):\n            return path\n\n    return None\n\ndef error(msg=None, usage=None):\n    if msg:\n        print(highlight('[ERROR] {} ').format(msg))\n    if usage:\n        print(usage)\n    sys.exit(1)\n\ndef run_argv(argv, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Run a program and wait it to finish, and return its exit code. The\n    standard output of this program is supressed.\n\n    '''\n    info('running %s, cwd=%s' % (' '.join(['\"{}\"'.format(a) for a in argv]), cwd or os.getcwd()))\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(argv,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env)\n        return proc.wait()\n\ndef run(cmdline, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Like run_argv but specify a command line string instead of argv'''\n    info('running %s, cwd=%s' % (cmdline, cwd or os.getcwd()))\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(cmdline,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env,\n                                shell=True)\n        return proc.wait()\n\ndef must_run(cmdline, *a, **kw):\n    ret = run(cmdline, *a, **kw)\n    if ret != 0:\n        error('failed to run %s' % cmdline)\n\ndef check_remove(path):\n    \"\"\"\n    Remove the file/dir specified by `path`, if exists.\n    \"\"\"\n    path = abspath(path)\n    assert path.count('/') >= 2\n    if exists(path):\n        is_dir = os.path.isdir(path)\n        info('removing {} {}'.format('dir' if is_dir else 'file', path))\n        if is_dir:\n            shutil.rmtree(path)\n        else:\n            os.unlink(path)\n\ndef must_mkdir(path):\n    '''Create a directory, exit on failure'''\n    try:\n        if not exists(path):\n            os.mkdir(path)\n    except OSError as e:\n        error('failed to create directory %s:%s' % (path, e))\n\ndef must_copy(src, dst):\n    '''Copy src to dst, exit on failure'''\n    try:\n        shutil.copy(src, dst)\n    except Exception as e:\n        error('failed to copy %s to %s: %s' % (src, dst, e))\n\ndef must_copytree(src, dst):\n    '''Copy src tree to dst, exit on failure'''\n    try:\n        shutil.copytree(src, dst)\n    except Exception as e:\n        error('failed to copy %s to %s: %s' % (src, dst, e))\n\ndef check_project_version(version):\n    '''A valid version must be like 1.2.2, 1.3'''\n    if not re.match(r'^[0-9]+(\\.[0-9]+)+$', version):\n        error('%s is not a valid version' % version, usage=\"build-mac-local.py 8.0.0\")\n\ndef check_cmd_para():\n    args = sys.argv\n    if len(args) != 2:\n        error('The number of parameters is incorrect', usage=\"build-mac-local.py 8.0.0\")\n    global version\n    version = args[1]\n    check_project_version(version)\n\n\nclass Project(object):\n    '''Base class for a project'''\n    # Probject name, i.e. libseaprc/seafile/\n    name = ''\n\n    # A list of shell commands to configure/build the project\n    build_commands = []\n\n    def __init__(self):\n        # the path to pass to --prefix=/<prefix>\n        self.prefix = join(BUILDDIR, 'usr')\n        # project dir, like <builddir>/seafile/\n        self.projdir = join(BUILDDIR, '{}' .format(self.name))\n\n    def get_version(self):\n        # libsearpc can have different versions from seafile.\n        raise NotImplementedError\n\n    def get_source_commit_id(self):\n        '''By convetion, we record the commit id of the source code in the\n        file \"<projdir>/latest_commit\"\n\n        '''\n        latest_commit_file = join(self.projdir, 'latest_commit')\n        with open(latest_commit_file, 'r') as fp:\n            commit_id = fp.read().strip('\\n\\r\\t ')\n\n        return commit_id\n\n    def append_cflags(self, macros):\n        cflags = ' '.join(['-D%s=%s' % (k, macros[k]) for k in macros])\n        prepend_env_value('CPPFLAGS',\n                          cflags,\n                          seperator=' ')\n\n    def before_build(self):\n        '''Hook method to do project-specific stuff before running build commands'''\n        pass\n\n    def build(self):\n        '''Build the source'''\n        self.before_build()\n        info('Building %s' % self.name)\n        for cmd in self.build_commands:\n            if run(cmd, cwd=self.projdir) != 0:\n                error('error when running command:\\n\\t%s\\n' % cmd)\n\ndef concurrent_make():\n    return 'make -j%s' % NUM_CPU\n\nclass Libsearpc(Project):\n    name = 'libsearpc'\n\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            './autogen.sh',\n            './configure --prefix=%s --disable-compile-demo --enable-compile-universal=%s' % (self.prefix, conf[CONF_UNIVERSAL]),\n            concurrent_make(),\n            'make install'\n        ]\n\nclass Seafile(Project):\n    name = 'seafile'\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            './autogen.sh',\n            './configure --prefix=%s --disable-fuse --enable-compile-universal=%s' % (self.prefix, conf[CONF_UNIVERSAL]),\n            concurrent_make(),\n            'make install'\n        ]\n\n    def update_cli_version(self):\n        '''Substitute the version number in seaf-cli'''\n        cli_py = join(self.projdir, 'app', 'seaf-cli')\n        with open(cli_py, 'r') as fp:\n            lines = fp.readlines()\n\n        ret = []\n        for line in lines:\n            old = '''SEAF_CLI_VERSION = \"\"'''\n            new = '''SEAF_CLI_VERSION = \"%s\"''' %conf[CONF_VERSION]\n            line = line.replace(old, new)\n            ret.append(line)\n\n        with open(cli_py, 'w') as fp:\n            fp.writelines(ret)\n\n    def before_build(self):\n        self.update_cli_version()\n        macros = {}\n        # SET SEAFILE_SOURCE_COMMIT_ID, so it can be printed in the log\n        # macros['SEAFILE_SOURCE_COMMIT_ID'] = '\\\\\"%s\\\\\"' % self.get_source_commit_id()\n        # self.append_cflags(macros)\n\nclass SeafileClient(Project):\n\n    name = 'seafile-client'\n\n    def __init__(self):\n        Project.__init__(self)\n        if conf[CONF_UNIVERSAL] == \"yes\":\n            cmake_defines = {\n                'CMAKE_OSX_ARCHITECTURES': '\"x86_64;arm64\"',\n                'CMAKE_OSX_DEPLOYMENT_TARGET': '10.14',\n                'CMAKE_BUILD_TYPE': 'Release',\n                'BUILD_SHIBBOLETH_SUPPORT': 'ON',\n                'BUILD_SPARKLE_SUPPORT': 'OFF',\n            }\n        else:\n            cmake_defines = {\n                'CMAKE_OSX_ARCHITECTURES': 'x86_64',\n                'CMAKE_OSX_DEPLOYMENT_TARGET': '10.14',\n                'CMAKE_BUILD_TYPE': 'Release',\n                'BUILD_SHIBBOLETH_SUPPORT': 'ON',\n                'BUILD_SPARKLE_SUPPORT': 'OFF',\n            }\n        cmake_defines_formatted = ' '.join(['-D{}={}'.format(k, v) for k, v in cmake_defines.items()])\n        self.build_commands = [\n            'rm -f CMakeCache.txt',\n            'cmake -GXcode {}'.format(cmake_defines_formatted),\n            'xcodebuild -target seafile-applet -configuration Release -jobs {}'.format(NUM_CPU),\n            'rm -rf seafile-applet.app',\n            'cp -r Release/seafile-applet.app seafile-applet.app',\n            'mkdir -p seafile-applet.app/Contents/Frameworks',\n            'macdeployqt seafile-applet.app',\n        ]\n\n    def before_build(self):\n        pass\n\nclass SeafileDMGLayout(Seafile):\n\n    def __init__(self):\n        Seafile.__init__(self)\n        self.build_commands = [\n        ]\n\nclass SeafileFinderSyncPlugin(SeafileClient):\n\n    def __init__(self):\n        SeafileClient.__init__(self)\n        if conf[CONF_UNIVERSAL]:\n            self.build_commands = [\n                'fsplugin/build.sh universal'\n            ]\n        else:\n            self.build_commands = [\n                'fsplugin/build.sh'\n            ]\n\ndef prepare_builddir(builddir):\n    must_mkdir(builddir)\n\n    # if not conf[CONF_KEEP]:\n    #     def remove_builddir():\n    #         '''Remove the builddir when exit'''\n    #         info('remove builddir before exit')\n    #         shutil.rmtree(builddir, ignore_errors=True)\n    #     atexit.register(remove_builddir)\n\n    os.chdir(builddir)\n\n    must_mkdir(join(builddir, 'usr'))\n\ndef parse_args():\n    parser = optparse.OptionParser()\n    def long_opt(opt):\n        return '--' + opt\n\n    parser.add_option(long_opt(CONF_VERSION),\n                      dest=CONF_VERSION,\n                      nargs=1,\n                      help='the version to build. Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_NO_STRIP),\n                      dest=CONF_NO_STRIP,\n                      action='store_true',\n                      help='''do not strip debug symbols''')\n\n    parser.add_option(long_opt(CONF_BRAND),\n                      dest=CONF_BRAND,\n                      help='the brand')\n\n    parser.add_option(long_opt(CONF_UNIVERSAL),\n                      dest=CONF_UNIVERSAL,\n                      action='store_true',\n                      help='''compile universal''')\n\n    usage = parser.format_help()\n    options, remain = parser.parse_args()\n    if remain:\n        error(usage=usage)\n\n    validate_args(usage, options)\n\ndef validate_args(usage, options):\n    required_args = [\n        CONF_VERSION,\n        CONF_NO_STRIP,\n        CONF_BRAND,\n    ]\n\n    # fist check required args\n    for optname in required_args:\n        if getattr(options, optname, None) is None:\n            error('%s must be specified' % optname, usage=usage)\n\n    def get_option(optname):\n        return getattr(options, optname)\n\n    # [ version ]\n    def check_project_version(version):\n        '''A valid version must be like 1.2.2, 1.3'''\n        if not re.match(r'^[0-9]+(\\.[0-9]+)+$', version):\n            error('%s is not a valid version' % version, usage=usage)\n\n    version = get_option(CONF_VERSION)\n    check_project_version(version)\n\n    # [ no strip]\n    nostrip = get_option(CONF_NO_STRIP)\n\n    brand = get_option(CONF_BRAND)\n\n    universal = get_option(CONF_UNIVERSAL)\n\n    conf[CONF_VERSION] = version\n    conf[CONF_NO_STRIP] = nostrip\n    conf[CONF_BRAND] = brand\n    if universal:\n        conf[CONF_UNIVERSAL] = \"yes\"\n    else:\n        conf[CONF_UNIVERSAL] = \"no\"\n\ndef setup_build_env():\n    '''Setup environment variables, such as export PATH=$BUILDDDIR/bin:$PATH'''\n    # os.environ.update({\n    # })\n    prefix = join(BUILDDIR, 'usr')\n\n    prepend_env_value('CFLAGS',\n                      '-Wall -O2 -g -DNDEBUG -I/opt/local/include -mmacosx-version-min=11.0',\n                      seperator=' ')\n\n    prepend_env_value('CXXFLAGS',\n                      '-Wall -O2 -g -DNDEBUG -I/opt/local/include -mmacosx-version-min=11.0',\n                      seperator=' ')\n\n    prepend_env_value('CPPFLAGS',\n                      '-I%s' % join(prefix, 'include'),\n                      seperator=' ')\n\n    prepend_env_value('CPPFLAGS',\n                      '-DSEAFILE_CLIENT_VERSION=\\\\\"%s\\\\\"' % conf[CONF_VERSION],\n                      seperator=' ')\n\n    if conf[CONF_NO_STRIP]:\n        prepend_env_value('CPPFLAGS',\n                          '-g -O0',\n                          seperator=' ')\n\n    prepend_env_value('LDFLAGS',\n                      '-L%s' % join(prefix, 'lib'),\n                      seperator=' ')\n\n    prepend_env_value('LDFLAGS',\n                      '-L%s' % join(prefix, 'lib64'),\n                      seperator=' ')\n\n    prepend_env_value('LDFLAGS',\n                      '-L/opt/local/lib -Wl,-headerpad_max_install_names -mmacosx-version-min=11.0',\n                      seperator=' ')\n\n    prepend_env_value('PATH', join(prefix, 'bin'))\n    prepend_env_value('PKG_CONFIG_PATH', join(prefix, 'lib', 'pkgconfig'))\n    prepend_env_value('PKG_CONFIG_PATH', join(prefix, 'lib64', 'pkgconfig'))\n\npath_pattern = re.compile(r'^\\s+(\\S+)\\s+\\(compatibility.*')\ndef get_dependent_libs(executable):\n    def is_syslib(lib):\n        if lib.startswith('/usr/lib'):\n            return True\n        if lib.startswith('/System/'):\n            return True\n        return False\n\n    otool_output = subprocess.getoutput('otool -L %s' % executable)\n    libs = set()\n    for line in otool_output.splitlines():\n        m = path_pattern.match(line)\n        if not m:\n            continue\n        path = m.group(1)\n        if not is_syslib(path):\n            libs.add(path)\n    return libs\n\ndef copy_shared_libs():\n    '''copy shared c libs, such as libevent, glib'''\n    builddir = BUILDDIR\n    frameworks_dir = join(SeafileClient().projdir, 'seafile-applet.app/Contents/Frameworks')\n\n    must_mkdir(frameworks_dir)\n    seafile_path = join(builddir, 'usr/bin/seaf-daemon')\n\n    libs = set()\n    libs.update(get_dependent_libs(seafile_path))\n\n    # Get deps of deps recursively until no more deps can be included\n    while True:\n        newlibs = set(libs)\n        for lib in libs:\n            newlibs.update(get_dependent_libs(lib))\n        if newlibs == libs:\n            break\n        libs = newlibs\n\n    for lib in libs:\n        dst_file = join(frameworks_dir, basename(lib))\n        if exists(dst_file):\n            continue\n        info('Copying %s' % lib)\n        shutil.copy(lib, frameworks_dir)\n\n    change_rpaths()\n\ndef change_rpaths():\n    \"\"\"\n    Chagne the rpath of the referenced dylibs so the app can be relocated\n    anywhere in the user's system.\n    See:\n      - https://blogs.oracle.com/dipol/entry/dynamic_libraries_rpath_and_mac\n      - https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/RunpathDependentLibraries.html\n    \"\"\"\n    contents_dir = join(SeafileClient().projdir, 'seafile-applet.app/Contents')\n    frameworks_dir = join(contents_dir, 'Frameworks')\n    resources_dir = join(contents_dir, 'Resources')\n    macos_dir = join(contents_dir, 'MacOS')\n\n    seafile_applet = join(macos_dir, 'seafile-applet')\n    binaries = [\n        seafile_applet,\n        join(resources_dir, 'seaf-daemon')\n    ]\n\n    RPATH_RE = re.compile(r'^path\\s+(\\S+)\\s+\\(offset .*$')\n    def get_rpaths(fn):\n        rpaths = []\n        output = subprocess.getoutput('otool -l {} | grep -A2 RPATH || true'.format(fn))\n        # The output is like\n            #           cmd LC_RPATH\n            #       cmdsize 24\n            #          path /usr/local (offset 12)\n            # --\n            #           cmd LC_RPATH\n            #       cmdsize 48\n            #          path @executable_path/../Frameworks (offset 12)\n        for line in output.splitlines():\n            m = RPATH_RE.match(line.strip())\n            if m:\n                rpaths.append(m.group(1))\n        return rpaths\n\n    def has_rpath(fn, path):\n        return path in get_rpaths(fn)\n\n    def add_frameworks_dir_to_rpath(fn, executable=True):\n        relpath = 'executable_path' if executable else 'loader_path'\n        relative_frameworks_dir = '@{}/../Frameworks'.format(relpath)\n        if not has_rpath(fn, relative_frameworks_dir):\n            must_run('install_name_tool -add_rpath {} {}' .format(relative_frameworks_dir, fn))\n\n    def remove_local_rpaths(fn):\n        local_paths = ['/usr/local/lib', '/opt/local/lib']\n        for path in local_paths:\n            if has_rpath(fn, path):\n                must_run('install_name_tool -delete_rpath {} {}'.format(path, fn))\n\n    def change_deps_rpath(fn):\n        deps = get_dependent_libs(fn)\n        for dep in deps:\n            bn = basename(dep)\n            if 'framework' in bn or bn.startswith('Qt') or bn == 'Sparkle':\n                continue\n            must_run('install_name_tool -change {} @rpath/{} {}'.format(dep, basename(dep), fn))\n\n    for binary in binaries:\n        add_frameworks_dir_to_rpath(binary)\n        remove_local_rpaths(binary)\n        change_deps_rpath(binary)\n\n    libs = os.listdir(frameworks_dir)\n    for lib_name in libs:\n        lib = join(frameworks_dir, lib_name)\n        if os.path.isdir(lib):\n            continue\n        must_run('install_name_tool -id @rpath/{} {}'.format(basename(lib), lib))\n        add_frameworks_dir_to_rpath(lib, executable=False)\n        change_deps_rpath(lib)\n        remove_local_rpaths(lib)\n\nDROPDMG = '/Applications/DropDMG.app/Contents/Frameworks/DropDMGFramework.framework/Versions/A/dropdmg'\n\ndef gen_dmg():\n    output_dmg = 'app-{}.dmg'.format(conf[CONF_VERSION])\n    parentdir = 'app-{}'.format(conf[CONF_VERSION])\n    appdir = join(parentdir, 'seafile-applet.app')\n    app_plugins_dir = join(appdir, 'Contents/PlugIns')\n\n    layout = SeafileDMGLayout()\n    layout_folder = join(layout.projdir, 'dmg/seafileLayout')\n\n    args = [\n        DROPDMG,\n        parentdir,\n        '--format', 'bzip2',\n        '--layout-folder', layout_folder,\n        '--volume-name', conf[CONF_BRAND] or 'Seafile Client',\n    ]\n\n    with cd(BUILDDIR):\n        check_remove(parentdir)\n        check_remove(output_dmg)\n        must_mkdir(parentdir)\n        must_run('tar xf seafile-applet.app.tar.gz -C {}'.format(parentdir))\n        # Remove the Qt Bearer plugin because it would run a background thread\n        # to scan wifi networks. See\n        # https://trello.com/c/j28eOIo1/359-explicitly-exclude-qt-bearer-plugins-for-the-windows-and-mac-packages\n        # for details.\n        must_run('rm -rf \"{}\"'.format(join(app_plugins_dir, 'bearer')))\n        # fsplugin must be copied before we sign the final .app dir\n        copy_fsplugin(app_plugins_dir)\n        sign_files(appdir)\n\n        # Rename the .app dir to 'Seafile Client.app', and create the shortcut\n        # to '/Applications' so the user can drag into it when opening the DMG.\n        brand = conf.get(CONF_BRAND, '')\n        if brand:\n            final_app = '{}.app'.format(brand)\n        else:\n            final_app = FINAL_APP\n        must_run('mv {}/seafile-applet.app \"{}/{}\"'.format(parentdir, parentdir, final_app))\n        must_run('ln -sf /Applications {}/'.format(parentdir))\n\n        # Open DropDMG manually, or dropdmg command line may fail.\n        run(''' osascript -e 'tell application \"DropDMG\" to quit' || true ''')\n        run(''' osascript -e 'open application \"DropDMG\"' || true ''')\n        run(''' osascript -e 'activate application \"DropDMG\"' || true ''')\n\n        # Sometimes DropDmg would fail if there are two many Finder windows.\n        run(''' osascript -e 'tell application \"Finder\" to close every window' ''')\n        if run_argv(args) != 0:\n            error('failed to run {}'.format(args))\n\ndef sign_in_parallel(files_to_sign):\n    import threading\n    import queue\n    queue = queue.Queue()\n    POISON_PILL = ''\n\n    class SignThread(threading.Thread):\n        def __init__(self, index):\n            threading.Thread.__init__(self)\n            self.index = index\n\n        def run(self):\n            info('sign thread {} started'.format(self.index))\n            while True:\n                try:\n                    fn = queue.get(timeout=1)\n                except queue.Empty:\n                    continue\n                else:\n                    if fn == POISON_PILL:\n                        break\n                    else:\n                        do_sign(fn)\n            info('sign thread {} stopped'.format(self.index))\n\n    TOTAL_THREADS = int(max(NUM_CPU / 2, 1))\n    threads = []\n    for i in range(TOTAL_THREADS):\n        t = SignThread(i)\n        t.start()\n        threads.append(t)\n\n    for fn in files_to_sign:\n        queue.put(fn)\n\n    for _ in range(TOTAL_THREADS):\n        queue.put(POISON_PILL)\n\n    for i in range(TOTAL_THREADS):\n        threads[i].join()\n\ndef sign_files(appdir):\n    webengine_app = join(\n        appdir,\n        'Contents/Frameworks/QtWebEngineCore.framework/Versions/Current/Helpers/QtWebEngineProcess.app'\n    )\n\n    def _glob(pattern, *a, **kw):\n        return glob.glob(join(appdir, pattern), *a, **kw)\n\n    # The webengine app must be signed first, otherwise the sign of\n    # QtWebengineCore.framework would fail.\n    if exists(webengine_app):\n        entitlements = join(Seafile().projdir, 'scripts/build/osx.entitlements')\n        do_sign(\n            webengine_app,\n            extra_args=['--entitlements', entitlements]\n        )\n\n    # Strip the get-task-allow entitlements for Sparkle binaries\n\n    #for fn in _glob('Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/*'):\n    #    do_sign(fn, preserve_entitlemenets=False)\n\n\n    # Sign the nested contents of Sparkle before we sign\n    # Sparkle.Framework in the thread pool.\n    #for fn in (\n    #        'Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app',\n    #        'Contents/Frameworks/Sparkle.framework/Versions/A/Sparkle',\n    #):\n    #    do_sign(join(appdir, fn))\n\n    patterns = [\n        'Contents/Frameworks/*.framework',\n        'Contents/PlugIns/*/*.dylib',\n        'Contents/Frameworks/*.dylib',\n        'Contents/Resources/seaf-daemon',\n        'Contents/MacOS/seadrive-gui',\n    ]\n\n    files_to_sign = []\n    for p in patterns:\n        files_to_sign.extend(_glob(p))\n\n    info('{} files to sign'.format(len(files_to_sign)))\n\n    sign_in_parallel(files_to_sign)\n    # for fn in files_to_sign:\n    #     do_sign(fn)\n\n    do_sign(appdir, preserve_entitlemenets=False)\n    # do_sign(appdir, extra_args=['--deep'])\n\n_keychain_unlocked = False\ndef unlock_keychain():\n    \"\"\"\n    Unlock the keychain when we're using ssh instead of using the terminal from\n    GUI. See http://stackoverflow.com/a/20208104/1467959\n    \"\"\"\n    global _keychain_unlocked\n    if not _keychain_unlocked:\n        _keychain_unlocked = True\n        run('security -v unlock-keychain -p vagrant || true')\n\ndef do_sign(path, extra_args=None, preserve_entitlemenets=True):\n    unlock_keychain()\n    args = [\n        'codesign',\n        '--verbose=4',\n        '-o', 'runtime',\n        '--timestamp',\n        '--verify',\n        # '--no-strict',\n        '--force',\n        '-s', CERT_ID,\n    ]\n    if preserve_entitlemenets:\n        args += ['--preserve-metadata=entitlements']\n    extra_args = extra_args or []\n    if extra_args:\n        args.extend(extra_args)\n\n    args.append(path)\n\n    if run_argv(args) != 0:\n        error('failed to sign {}'.format(path))\n\ndef copy_dmg():\n    brand = conf[CONF_BRAND] or 'seafile-client'\n    branded_dmg = '{}-{}.dmg'.format(brand, conf[CONF_VERSION])\n    src_dmg = os.path.join(BUILDDIR, 'app-{}.dmg'.format(conf[CONF_VERSION]))\n    dst_dmg = os.path.join(BUILDDIR, branded_dmg)\n\n    # move msi to outputdir\n    must_copy(src_dmg, dst_dmg)\n\n    print ('---------------------------------------------')\n    print ('The build is successfully. Output is:')\n    print ('>>\\t%s' % dst_dmg)\n    print ('---------------------------------------------')\n\ndef notarize_dmg():\n    pkg = os.path.join(BUILDDIR, 'app-{}.dmg'.format(conf[CONF_VERSION]))\n    info('Try to notarize {}'.format(pkg))\n    notarize_script = join(Seafile().projdir, 'scripts/build/notarize-universal.sh')\n    cmdline = '{} {}'.format(notarize_script, pkg)\n    ret = run(cmdline)\n    if ret != 0:\n        error('failed to notarize: %s' % cmdline)\n    info('Successfully notarized {}'.format(pkg))\n\ndef build_and_sign_fsplugin():\n    \"\"\"\n    Build and sign the fsplugin. The final output would be \"${buildder}/Seafile FinderSync.appex\"\n    \"\"\"\n    fsplugin = SeafileFinderSyncPlugin()\n    fsplugin.build()\n    with cd(fsplugin.projdir):\n        appex_src = 'fsplugin/{}'.format(FSPLUGIN_APPEX_NAME)\n        appex_dst = join(BUILDDIR, basename(appex_src))\n\n        check_remove(appex_dst)\n        must_copytree(appex_src, appex_dst)\n\n        entitlements_src = 'fsplugin/seafile-fsplugin.entitlements'\n        entitlements_dst = join(BUILDDIR, basename(entitlements_src))\n\n        check_remove(entitlements_dst)\n        must_copy(entitlements_src, entitlements_dst)\n\n    do_sign(\n        appex_dst,\n        extra_args=['--entitlements', entitlements_dst]\n    )\n\ndef copy_fsplugin(plugins_dir):\n    src = join(BUILDDIR, FSPLUGIN_APPEX_NAME)\n    dst = join(plugins_dir, FSPLUGIN_APPEX_NAME)\n    check_remove(dst)\n    must_copytree(src, dst)\n\ndef copy_sparkle_framework():\n    src = '/usr/local/Sparkle.framework'\n    dst = join(SeafileClient().projdir, 'seafile-applet.app/Contents/Frameworks', basename(src))\n    check_remove(dst)\n    # Here we use the `cp` command instead of shutil to do the copy, because `cp\n    # -P` would keep symlinks as is.\n    must_run('cp -R -P \"{}\" \"{}\"'.format(src, dst))\n\ndef build_projects():\n    prepare_builddir(BUILDDIR)\n    libsearpc = Libsearpc()\n    seafile = Seafile()\n    seafile_client = SeafileClient()\n\n    libsearpc.build()\n\n    seafile.build()\n\n    seafile_client.build()\n\n    #copy_sparkle_framework()\n\n    copy_shared_libs()\n\ndef local_workflow():\n    build_projects()\n    generate_app_tar_gz()\n\n    build_and_sign_fsplugin()\n    gen_dmg()\n    notarize_dmg()\n    copy_dmg()\n\n\ndef generate_app_tar_gz():\n    output_app_tgz = join(BUILDDIR, 'seafile-applet.app.tar.gz')\n    with cd(SeafileClient().projdir):\n        run('tar czf {} seafile-applet.app'.format(output_app_tgz))\n\ndef setup_logging(level=logging.INFO):\n    kw = {\n        'format': '[%(asctime)s][%(module)s]: %(message)s',\n        'datefmt': '%m/%d/%Y %H:%M:%S',\n        'level': level,\n        'stream': sys.stdout\n    }\n\n    logging.basicConfig(**kw)\n    logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(\n        logging.WARNING)\n\ndef main():\n    setup_logging()\n    parse_args()\n    # check_cmd_para()\n    info('{} script started'.format(abspath(__file__)))\n    info('NUM_CPU = {}'.format(NUM_CPU))\n    setup_build_env()\n    local_workflow()\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "scripts/build/build-mac-local.py",
    "content": "#!/usr/bin/env python\n# coding: UTF-8\n\n'''This script builds the seafile mac client.\n'''\n\nimport atexit\nimport logging\nimport commands\nfrom contextlib import contextmanager\nimport glob\nimport multiprocessing\nimport optparse\nimport os\nfrom os.path import abspath, basename, dirname, join, exists, expanduser\nimport re\nimport shutil\nimport subprocess\nimport sys\nimport tempfile\n\nFINAL_APP = 'Seafile Client.app'\nFSPLUGIN_APPEX_NAME = 'Seafile FinderSync.appex'\nCERT_ID = '79AB1AF435DD2CBC5FDB3EBBD45B0DA17727B299'\n\nBUILDDIR = join(os.getcwd(), \"../../../\")\n\n####################\n### Requires Python 2.6+\n####################\nif sys.version_info[0] == 3:\n    print 'Python 3 not supported yet. Quit now.'\n    sys.exit(1)\nif sys.version_info[1] < 6:\n    print 'Python 2.6 or above is required. Quit now.'\n    sys.exit(1)\n\n####################\n### Global variables\n####################\n# command line configuartion\nconf = {}\n\n# key names in the conf dictionary.\n# pylint: disable=bad-whitespace\nCONF_VERSION            = 'version'\nCONF_NO_STRIP           = 'nostrip'\nCONF_BRAND              = 'brand'\nCONF_UNIVERSAL          = 'universal'\n\nNUM_CPU = multiprocessing.cpu_count()\nPID = os.getpid()\n\n####################\n### Common helper functions\n####################\ndef highlight(content, is_error=False):\n    '''Add ANSI color to content to get it highlighted on terminal'''\n    if is_error:\n        return '\\x1b[1;31m%s\\x1b[m' % content\n    else:\n        return '\\x1b[1;32m%s\\x1b[m' % content\n\ndef info(msg):\n    logging.info(highlight('[INFO][{}] ').format(msg))\n\n@contextmanager\ndef cd(path):\n    oldpwd = os.getcwd()\n    os.chdir(path)\n    try:\n        yield\n    finally:\n        os.chdir(oldpwd)\n\ndef exist_in_path(prog):\n    '''Test whether prog exists in system path'''\n    return bool(find_in_path(prog))\n\ndef prepend_env_value(name, value, seperator=':'):\n    '''append a new value to a list'''\n    try:\n        current_value = os.environ[name]\n    except KeyError:\n        current_value = ''\n\n    new_value = value\n    if current_value:\n        new_value += seperator + current_value\n\n    os.environ[name] = new_value\n\ndef find_in_path(prog):\n    '''Find a file in system path'''\n    dirs = os.environ['PATH'].split(':')\n    for d in dirs:\n        if d == '':\n            continue\n        path = join(d, prog)\n        if exists(path):\n            return path\n\n    return None\n\ndef error(msg=None, usage=None):\n    if msg:\n        print highlight('[ERROR] ') + msg\n    if usage:\n        print usage\n    sys.exit(1)\n\ndef run_argv(argv, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Run a program and wait it to finish, and return its exit code. The\n    standard output of this program is supressed.\n\n    '''\n    info('running %s, cwd=%s' % (' '.join(['\"{}\"'.format(a) for a in argv]), cwd or os.getcwd()))\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(argv,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env)\n        return proc.wait()\n\ndef run(cmdline, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Like run_argv but specify a command line string instead of argv'''\n    info('running %s, cwd=%s' % (cmdline, cwd or os.getcwd()))\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(cmdline,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env,\n                                shell=True)\n        return proc.wait()\n\ndef must_run(cmdline, *a, **kw):\n    ret = run(cmdline, *a, **kw)\n    if ret != 0:\n        error('failed to run %s' % cmdline)\n\ndef check_remove(path):\n    \"\"\"\n    Remove the file/dir specified by `path`, if exists.\n    \"\"\"\n    path = abspath(path)\n    assert path.count('/') >= 2\n    if exists(path):\n        is_dir = os.path.isdir(path)\n        info('removing {} {}'.format('dir' if is_dir else 'file', path))\n        if is_dir:\n            shutil.rmtree(path)\n        else:\n            os.unlink(path)\n\ndef must_mkdir(path):\n    '''Create a directory, exit on failure'''\n    try:\n        if not exists(path):\n            os.mkdir(path)\n    except OSError, e:\n        error('failed to create directory %s:%s' % (path, e))\n\ndef must_copy(src, dst):\n    '''Copy src to dst, exit on failure'''\n    try:\n        shutil.copy(src, dst)\n    except Exception, e:\n        error('failed to copy %s to %s: %s' % (src, dst, e))\n\ndef must_copytree(src, dst):\n    '''Copy src tree to dst, exit on failure'''\n    try:\n        shutil.copytree(src, dst)\n    except Exception, e:\n        error('failed to copy %s to %s: %s' % (src, dst, e))\n\ndef check_project_version(version):\n    '''A valid version must be like 1.2.2, 1.3'''\n    if not re.match(r'^[0-9]+(\\.[0-9]+)+$', version):\n        error('%s is not a valid version' % version, usage=\"build-mac-local.py 8.0.0\")\n\ndef check_cmd_para():\n    args = sys.argv\n    if len(args) != 2:\n        error('The number of parameters is incorrect', usage=\"build-mac-local.py 8.0.0\")\n    global version\n    version = args[1]\n    check_project_version(version)\n\n\nclass Project(object):\n    '''Base class for a project'''\n    # Probject name, i.e. libseaprc/seafile/\n    name = ''\n\n    # A list of shell commands to configure/build the project\n    build_commands = []\n\n    def __init__(self):\n        # the path to pass to --prefix=/<prefix>\n        self.prefix = join(BUILDDIR, 'usr')\n        # project dir, like <builddir>/seafile/\n        self.projdir = join(BUILDDIR, '{}' .format(self.name))\n\n    def get_version(self):\n        # libsearpc can have different versions from seafile.\n        raise NotImplementedError\n\n    def get_source_commit_id(self):\n        '''By convetion, we record the commit id of the source code in the\n        file \"<projdir>/latest_commit\"\n\n        '''\n        latest_commit_file = join(self.projdir, 'latest_commit')\n        with open(latest_commit_file, 'r') as fp:\n            commit_id = fp.read().strip('\\n\\r\\t ')\n\n        return commit_id\n\n    def append_cflags(self, macros):\n        cflags = ' '.join(['-D%s=%s' % (k, macros[k]) for k in macros])\n        prepend_env_value('CPPFLAGS',\n                          cflags,\n                          seperator=' ')\n\n    def before_build(self):\n        '''Hook method to do project-specific stuff before running build commands'''\n        pass\n\n    def build(self):\n        '''Build the source'''\n        self.before_build()\n        info('Building %s' % self.name)\n        for cmd in self.build_commands:\n            if run(cmd, cwd=self.projdir) != 0:\n                error('error when running command:\\n\\t%s\\n' % cmd)\n\ndef concurrent_make():\n    return 'make -j%s' % NUM_CPU\n\nclass Libsearpc(Project):\n    name = 'libsearpc'\n\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            './autogen.sh',\n            './configure --prefix=%s --disable-compile-demo --enable-compile-universal=%s' % (self.prefix, conf[CONF_UNIVERSAL]),\n            concurrent_make(),\n            'make install'\n        ]\n\nclass Seafile(Project):\n    name = 'seafile'\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            './autogen.sh',\n            './configure --prefix=%s --disable-fuse --enable-compile-universal=%s' % (self.prefix, conf[CONF_UNIVERSAL]),\n            concurrent_make(),\n            'make install'\n        ]\n\n    def update_cli_version(self):\n        '''Substitute the version number in seaf-cli'''\n        cli_py = join(self.projdir, 'app', 'seaf-cli')\n        with open(cli_py, 'r') as fp:\n            lines = fp.readlines()\n\n        ret = []\n        for line in lines:\n            old = '''SEAF_CLI_VERSION = \"\"'''\n            new = '''SEAF_CLI_VERSION = \"%s\"''' %conf[CONF_VERSION]\n            line = line.replace(old, new)\n            ret.append(line)\n\n        with open(cli_py, 'w') as fp:\n            fp.writelines(ret)\n\n    def before_build(self):\n        self.update_cli_version()\n        macros = {}\n        # SET SEAFILE_SOURCE_COMMIT_ID, so it can be printed in the log\n        # macros['SEAFILE_SOURCE_COMMIT_ID'] = '\\\\\"%s\\\\\"' % self.get_source_commit_id()\n        # self.append_cflags(macros)\n\nclass SeafileClient(Project):\n\n    name = 'seafile-client'\n\n    def __init__(self):\n        Project.__init__(self)\n        if conf[CONF_UNIVERSAL] == \"yes\":\n            cmake_defines = {\n                'CMAKE_OSX_ARCHITECTURES': '\"x86_64;arm64\"',\n                'CMAKE_OSX_DEPLOYMENT_TARGET': '10.14',\n                'CMAKE_BUILD_TYPE': 'Release',\n                'BUILD_SHIBBOLETH_SUPPORT': 'ON',\n                'BUILD_SPARKLE_SUPPORT': 'OFF',\n            }\n        else:\n            cmake_defines = {\n                'CMAKE_OSX_ARCHITECTURES': 'x86_64',\n                'CMAKE_OSX_DEPLOYMENT_TARGET': '10.14',\n                'CMAKE_BUILD_TYPE': 'Release',\n                'BUILD_SHIBBOLETH_SUPPORT': 'ON',\n                'BUILD_SPARKLE_SUPPORT': 'OFF',\n            }\n        cmake_defines_formatted = ' '.join(['-D{}={}'.format(k, v) for k, v in cmake_defines.iteritems()])\n        self.build_commands = [\n            'rm -f CMakeCache.txt',\n            'cmake -GXcode {}'.format(cmake_defines_formatted),\n            'xcodebuild -target seafile-applet -configuration Release -jobs {}'.format(NUM_CPU),\n            'rm -rf seafile-applet.app',\n            'cp -r Release/seafile-applet.app seafile-applet.app',\n            'mkdir -p seafile-applet.app/Contents/Frameworks',\n            'macdeployqt seafile-applet.app',\n        ]\n\n    def before_build(self):\n        pass\n\nclass SeafileDMGLayout(Seafile):\n\n    def __init__(self):\n        Seafile.__init__(self)\n        self.build_commands = [\n        ]\n\nclass SeafileFinderSyncPlugin(SeafileClient):\n\n    def __init__(self):\n        SeafileClient.__init__(self)\n        if conf[CONF_UNIVERSAL]:\n            self.build_commands = [\n                'fsplugin/build.sh universal'\n            ]\n        else:\n            self.build_commands = [\n                'fsplugin/build.sh'\n            ]\n\ndef prepare_builddir(builddir):\n    must_mkdir(builddir)\n\n    # if not conf[CONF_KEEP]:\n    #     def remove_builddir():\n    #         '''Remove the builddir when exit'''\n    #         info('remove builddir before exit')\n    #         shutil.rmtree(builddir, ignore_errors=True)\n    #     atexit.register(remove_builddir)\n\n    os.chdir(builddir)\n\n    must_mkdir(join(builddir, 'usr'))\n\ndef parse_args():\n    parser = optparse.OptionParser()\n    def long_opt(opt):\n        return '--' + opt\n\n    parser.add_option(long_opt(CONF_VERSION),\n                      dest=CONF_VERSION,\n                      nargs=1,\n                      help='the version to build. Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_NO_STRIP),\n                      dest=CONF_NO_STRIP,\n                      action='store_true',\n                      help='''do not strip debug symbols''')\n\n    parser.add_option(long_opt(CONF_BRAND),\n                      dest=CONF_BRAND,\n                      help='the brand')\n\n    parser.add_option(long_opt(CONF_UNIVERSAL),\n                      dest=CONF_UNIVERSAL,\n                      action='store_true',\n                      help='''compile universal''')\n\n    usage = parser.format_help()\n    options, remain = parser.parse_args()\n    if remain:\n        error(usage=usage)\n\n    validate_args(usage, options)\n\ndef validate_args(usage, options):\n    required_args = [\n        CONF_VERSION,\n        CONF_NO_STRIP,\n        CONF_BRAND,\n    ]\n\n    # fist check required args\n    for optname in required_args:\n        if getattr(options, optname, None) is None:\n            error('%s must be specified' % optname, usage=usage)\n\n    def get_option(optname):\n        return getattr(options, optname)\n\n    # [ version ]\n    def check_project_version(version):\n        '''A valid version must be like 1.2.2, 1.3'''\n        if not re.match(r'^[0-9]+(\\.[0-9]+)+$', version):\n            error('%s is not a valid version' % version, usage=usage)\n\n    version = get_option(CONF_VERSION)\n    check_project_version(version)\n\n    # [ no strip]\n    nostrip = get_option(CONF_NO_STRIP)\n\n    brand = get_option(CONF_BRAND)\n\n    universal = get_option(CONF_UNIVERSAL)\n\n    conf[CONF_VERSION] = version\n    conf[CONF_NO_STRIP] = nostrip\n    conf[CONF_BRAND] = brand\n    if universal:\n        conf[CONF_UNIVERSAL] = \"yes\"\n    else:\n        conf[CONF_UNIVERSAL] = \"no\"\n\ndef setup_build_env():\n    '''Setup environment variables, such as export PATH=$BUILDDDIR/bin:$PATH'''\n    # os.environ.update({\n    # })\n    prefix = join(BUILDDIR, 'usr')\n\n    prepend_env_value('CFLAGS',\n                      '-Wall -O2 -g -DNDEBUG -I/opt/local/include -mmacosx-version-min=10.14',\n                      seperator=' ')\n\n    prepend_env_value('CXXFLAGS',\n                      '-Wall -O2 -g -DNDEBUG -I/opt/local/include -mmacosx-version-min=10.14',\n                      seperator=' ')\n\n    prepend_env_value('CPPFLAGS',\n                      '-I%s' % join(prefix, 'include'),\n                      seperator=' ')\n\n    prepend_env_value('CPPFLAGS',\n                      '-DSEAFILE_CLIENT_VERSION=\\\\\"%s\\\\\"' % conf[CONF_VERSION],\n                      seperator=' ')\n\n    if conf[CONF_NO_STRIP]:\n        prepend_env_value('CPPFLAGS',\n                          '-g -O0',\n                          seperator=' ')\n\n    prepend_env_value('LDFLAGS',\n                      '-L%s' % join(prefix, 'lib'),\n                      seperator=' ')\n\n    prepend_env_value('LDFLAGS',\n                      '-L%s' % join(prefix, 'lib64'),\n                      seperator=' ')\n\n    prepend_env_value('LDFLAGS',\n                      '-L/opt/local/lib -Wl,-headerpad_max_install_names -mmacosx-version-min=10.14',\n                      seperator=' ')\n\n    prepend_env_value('PATH', join(prefix, 'bin'))\n    prepend_env_value('PKG_CONFIG_PATH', join(prefix, 'lib', 'pkgconfig'))\n    prepend_env_value('PKG_CONFIG_PATH', join(prefix, 'lib64', 'pkgconfig'))\n\npath_pattern = re.compile(r'^\\s+(\\S+)\\s+\\(compatibility.*')\ndef get_dependent_libs(executable):\n    def is_syslib(lib):\n        if lib.startswith('/usr/lib'):\n            return True\n        if lib.startswith('/System/'):\n            return True\n        return False\n\n    otool_output = commands.getoutput('otool -L %s' % executable)\n    libs = set()\n    for line in otool_output.splitlines():\n        m = path_pattern.match(line)\n        if not m:\n            continue\n        path = m.group(1)\n        if not is_syslib(path):\n            libs.add(path)\n    return libs\n\ndef copy_shared_libs():\n    '''copy shared c libs, such as libevent, glib'''\n    builddir = BUILDDIR\n    frameworks_dir = join(SeafileClient().projdir, 'seafile-applet.app/Contents/Frameworks')\n\n    must_mkdir(frameworks_dir)\n    seafile_path = join(builddir, 'usr/bin/seaf-daemon')\n\n    libs = set()\n    libs.update(get_dependent_libs(seafile_path))\n\n    # Get deps of deps recursively until no more deps can be included\n    while True:\n        newlibs = set(libs)\n        for lib in libs:\n            newlibs.update(get_dependent_libs(lib))\n        if newlibs == libs:\n            break\n        libs = newlibs\n\n    for lib in libs:\n        dst_file = join(frameworks_dir, basename(lib))\n        if exists(dst_file):\n            continue\n        info('Copying %s' % lib)\n        shutil.copy(lib, frameworks_dir)\n\n    change_rpaths()\n\ndef change_rpaths():\n    \"\"\"\n    Chagne the rpath of the referenced dylibs so the app can be relocated\n    anywhere in the user's system.\n    See:\n      - https://blogs.oracle.com/dipol/entry/dynamic_libraries_rpath_and_mac\n      - https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/RunpathDependentLibraries.html\n    \"\"\"\n    contents_dir = join(SeafileClient().projdir, 'seafile-applet.app/Contents')\n    frameworks_dir = join(contents_dir, 'Frameworks')\n    resources_dir = join(contents_dir, 'Resources')\n    macos_dir = join(contents_dir, 'MacOS')\n\n    seafile_applet = join(macos_dir, 'seafile-applet')\n    binaries = [\n        seafile_applet,\n        join(resources_dir, 'seaf-daemon')\n    ]\n\n    RPATH_RE = re.compile(r'^path\\s+(\\S+)\\s+\\(offset .*$')\n    def get_rpaths(fn):\n        rpaths = []\n        output = commands.getoutput('otool -l {} | grep -A2 RPATH || true'.format(fn))\n        # The output is like\n            #           cmd LC_RPATH\n            #       cmdsize 24\n            #          path /usr/local (offset 12)\n            # --\n            #           cmd LC_RPATH\n            #       cmdsize 48\n            #          path @executable_path/../Frameworks (offset 12)\n        for line in output.splitlines():\n            m = RPATH_RE.match(line.strip())\n            if m:\n                rpaths.append(m.group(1))\n        return rpaths\n\n    def has_rpath(fn, path):\n        return path in get_rpaths(fn)\n\n    def add_frameworks_dir_to_rpath(fn, executable=True):\n        relpath = 'executable_path' if executable else 'loader_path'\n        relative_frameworks_dir = '@{}/../Frameworks'.format(relpath)\n        if not has_rpath(fn, relative_frameworks_dir):\n            must_run('install_name_tool -add_rpath {} {}' .format(relative_frameworks_dir, fn))\n\n    def remove_local_rpaths(fn):\n        local_paths = ['/usr/local/lib', '/opt/local/lib']\n        for path in local_paths:\n            if has_rpath(fn, path):\n                must_run('install_name_tool -delete_rpath {} {}'.format(path, fn))\n\n    def change_deps_rpath(fn):\n        deps = get_dependent_libs(fn)\n        for dep in deps:\n            bn = basename(dep)\n            if 'framework' in bn or bn.startswith('Qt') or bn == 'Sparkle':\n                continue\n            must_run('install_name_tool -change {} @rpath/{} {}'.format(dep, basename(dep), fn))\n\n    for binary in binaries:\n        add_frameworks_dir_to_rpath(binary)\n        remove_local_rpaths(binary)\n        change_deps_rpath(binary)\n\n    libs = os.listdir(frameworks_dir)\n    for lib_name in libs:\n        lib = join(frameworks_dir, lib_name)\n        if os.path.isdir(lib):\n            continue\n        must_run('install_name_tool -id @rpath/{} {}'.format(basename(lib), lib))\n        add_frameworks_dir_to_rpath(lib, executable=False)\n        change_deps_rpath(lib)\n        remove_local_rpaths(lib)\n\nDROPDMG = '/Applications/DropDMG.app/Contents/Frameworks/DropDMGFramework.framework/Versions/A/dropdmg'\n\ndef gen_dmg():\n    output_dmg = 'app-{}.dmg'.format(conf[CONF_VERSION])\n    parentdir = 'app-{}'.format(conf[CONF_VERSION])\n    appdir = join(parentdir, 'seafile-applet.app')\n    app_plugins_dir = join(appdir, 'Contents/PlugIns')\n\n    layout = SeafileDMGLayout()\n    layout_folder = join(layout.projdir, 'dmg/seafileLayout')\n\n    args = [\n        DROPDMG,\n        parentdir,\n        '--format', 'bzip2',\n        '--layout-folder', layout_folder,\n        '--volume-name', conf[CONF_BRAND] or 'Seafile Client',\n    ]\n\n    with cd(BUILDDIR):\n        check_remove(parentdir)\n        check_remove(output_dmg)\n        must_mkdir(parentdir)\n        must_run('tar xf seafile-applet.app.tar.gz -C {}'.format(parentdir))\n        # Remove the Qt Bearer plugin because it would run a background thread\n        # to scan wifi networks. See\n        # https://trello.com/c/j28eOIo1/359-explicitly-exclude-qt-bearer-plugins-for-the-windows-and-mac-packages\n        # for details.\n        must_run('rm -rf \"{}\"'.format(join(app_plugins_dir, 'bearer')))\n        # fsplugin must be copied before we sign the final .app dir\n        copy_fsplugin(app_plugins_dir)\n        sign_files(appdir)\n\n        # Rename the .app dir to 'Seafile Client.app', and create the shortcut\n        # to '/Applications' so the user can drag into it when opening the DMG.\n        brand = conf.get(CONF_BRAND, '')\n        if brand:\n            final_app = '{}.app'.format(brand)\n        else:\n            final_app = FINAL_APP\n        must_run('mv {}/seafile-applet.app \"{}/{}\"'.format(parentdir, parentdir, final_app))\n        must_run('ln -sf /Applications {}/'.format(parentdir))\n\n        # Open DropDMG manually, or dropdmg command line may fail.\n        run(''' osascript -e 'tell application \"DropDMG\" to quit' || true ''')\n        run(''' osascript -e 'open application \"DropDMG\"' || true ''')\n        run(''' osascript -e 'activate application \"DropDMG\"' || true ''')\n\n        # Sometimes DropDmg would fail if there are two many Finder windows.\n        run(''' osascript -e 'tell application \"Finder\" to close every window' ''')\n        if run_argv(args) != 0:\n            error('failed to run {}'.format(args))\n\ndef sign_in_parallel(files_to_sign):\n    import threading\n    import Queue\n    queue = Queue.Queue()\n    POISON_PILL = ''\n\n    class SignThread(threading.Thread):\n        def __init__(self, index):\n            threading.Thread.__init__(self)\n            self.index = index\n\n        def run(self):\n            info('sign thread {} started'.format(self.index))\n            while True:\n                try:\n                    fn = queue.get(timeout=1)\n                except Queue.Empty:\n                    continue\n                else:\n                    if fn == POISON_PILL:\n                        break\n                    else:\n                        do_sign(fn)\n            info('sign thread {} stopped'.format(self.index))\n\n    TOTAL_THREADS = max(NUM_CPU / 2, 1)\n    threads = []\n    for i in xrange(TOTAL_THREADS):\n        t = SignThread(i)\n        t.start()\n        threads.append(t)\n\n    for fn in files_to_sign:\n        queue.put(fn)\n\n    for _ in xrange(TOTAL_THREADS):\n        queue.put(POISON_PILL)\n\n    for i in xrange(TOTAL_THREADS):\n        threads[i].join()\n\ndef sign_files(appdir):\n    webengine_app = join(\n        appdir,\n        'Contents/Frameworks/QtWebEngineCore.framework/Versions/Current/Helpers/QtWebEngineProcess.app'\n    )\n\n    def _glob(pattern, *a, **kw):\n        return glob.glob(join(appdir, pattern), *a, **kw)\n\n    # The webengine app must be signed first, otherwise the sign of\n    # QtWebengineCore.framework would fail.\n    if exists(webengine_app):\n        entitlements = join(Seafile().projdir, 'scripts/build/osx.entitlements')\n        do_sign(\n            webengine_app,\n            extra_args=['--entitlements', entitlements]\n        )\n\n    # Strip the get-task-allow entitlements for Sparkle binaries\n    #for fn in _glob('Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/*'):\n    #    do_sign(fn, preserve_entitlemenets=False)\n\n    # Sign the nested contents of Sparkle before we sign\n    # Sparkle.Framework in the thread pool.\n    #for fn in (\n    #        'Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app',\n    #        'Contents/Frameworks/Sparkle.framework/Versions/A/Sparkle',\n    #):\n    #    do_sign(join(appdir, fn))\n\n    patterns = [\n        'Contents/Frameworks/*.framework',\n        'Contents/PlugIns/*/*.dylib',\n        'Contents/Frameworks/*.dylib',\n        'Contents/Resources/seaf-daemon',\n        'Contents/MacOS/seadrive-gui',\n    ]\n\n    files_to_sign = []\n    for p in patterns:\n        files_to_sign.extend(_glob(p))\n\n    info('{} files to sign'.format(len(files_to_sign)))\n\n    sign_in_parallel(files_to_sign)\n    # for fn in files_to_sign:\n    #     do_sign(fn)\n\n    do_sign(appdir)\n    # do_sign(appdir, extra_args=['--deep'])\n\n_keychain_unlocked = False\ndef unlock_keychain():\n    \"\"\"\n    Unlock the keychain when we're using ssh instead of using the terminal from\n    GUI. See http://stackoverflow.com/a/20208104/1467959\n    \"\"\"\n    global _keychain_unlocked\n    if not _keychain_unlocked:\n        _keychain_unlocked = True\n        run('security -v unlock-keychain -p vagrant || true')\n\ndef do_sign(path, extra_args=None, preserve_entitlemenets=True):\n    unlock_keychain()\n    args = [\n        'codesign',\n        '--verbose=4',\n        '-o', 'runtime',\n        '--timestamp',\n        '--verify',\n        # '--no-strict',\n        '--force',\n        '-s', CERT_ID,\n    ]\n    if preserve_entitlemenets:\n        args += ['--preserve-metadata=entitlements']\n    extra_args = extra_args or []\n    if extra_args:\n        args.extend(extra_args)\n\n    args.append(path)\n\n    if run_argv(args) != 0:\n        error('failed to sign {}'.format(path))\n\ndef copy_dmg():\n    brand = conf[CONF_BRAND] or 'seafile-client'\n    branded_dmg = '{}-{}.dmg'.format(brand, conf[CONF_VERSION])\n    src_dmg = os.path.join(BUILDDIR, 'app-{}.dmg'.format(conf[CONF_VERSION]))\n    dst_dmg = os.path.join(BUILDDIR, branded_dmg)\n\n    # move msi to outputdir\n    must_copy(src_dmg, dst_dmg)\n\n    print '---------------------------------------------'\n    print 'The build is successfully. Output is:'\n    print '>>\\t%s' % dst_dmg\n    print '---------------------------------------------'\n\ndef notarize_dmg():\n    pkg = os.path.join(BUILDDIR, 'app-{}.dmg'.format(conf[CONF_VERSION]))\n    info('Try to notarize {}'.format(pkg))\n    notarize_script = join(Seafile().projdir, 'scripts/build/notarize.sh')\n    cmdline = '{} {}'.format(notarize_script, pkg)\n    ret = run(cmdline)\n    if ret != 0:\n        error('failed to notarize: %s' % cmdline)\n    info('Successfully notarized {}'.format(pkg))\n\ndef build_and_sign_fsplugin():\n    \"\"\"\n    Build and sign the fsplugin. The final output would be \"${buildder}/Seafile FinderSync.appex\"\n    \"\"\"\n    fsplugin = SeafileFinderSyncPlugin()\n    fsplugin.build()\n    with cd(fsplugin.projdir):\n        appex_src = 'fsplugin/{}'.format(FSPLUGIN_APPEX_NAME)\n        appex_dst = join(BUILDDIR, basename(appex_src))\n\n        check_remove(appex_dst)\n        must_copytree(appex_src, appex_dst)\n\n        entitlements_src = 'fsplugin/seafile-fsplugin.entitlements'\n        entitlements_dst = join(BUILDDIR, basename(entitlements_src))\n\n        check_remove(entitlements_dst)\n        must_copy(entitlements_src, entitlements_dst)\n\n    do_sign(\n        appex_dst,\n        extra_args=['--entitlements', entitlements_dst]\n    )\n\ndef copy_fsplugin(plugins_dir):\n    src = join(BUILDDIR, FSPLUGIN_APPEX_NAME)\n    dst = join(plugins_dir, FSPLUGIN_APPEX_NAME)\n    check_remove(dst)\n    must_copytree(src, dst)\n\ndef copy_sparkle_framework():\n    src = '/usr/local/Sparkle.framework'\n    dst = join(SeafileClient().projdir, 'seafile-applet.app/Contents/Frameworks', basename(src))\n    check_remove(dst)\n    # Here we use the `cp` command instead of shutil to do the copy, because `cp\n    # -P` would keep symlinks as is.\n    must_run('cp -R -P \"{}\" \"{}\"'.format(src, dst))\n\ndef build_projects():\n    prepare_builddir(BUILDDIR)\n    libsearpc = Libsearpc()\n    seafile = Seafile()\n    seafile_client = SeafileClient()\n\n    libsearpc.build()\n\n    seafile.build()\n\n    seafile_client.build()\n\n    #copy_sparkle_framework()\n\n    copy_shared_libs()\n\ndef local_workflow():\n    build_projects()\n    generate_app_tar_gz()\n\n    build_and_sign_fsplugin()\n    gen_dmg()\n    notarize_dmg()\n    copy_dmg()\n\n\ndef generate_app_tar_gz():\n    output_app_tgz = join(BUILDDIR, 'seafile-applet.app.tar.gz')\n    with cd(SeafileClient().projdir):\n        run('tar czf {} seafile-applet.app'.format(output_app_tgz))\n\ndef setup_logging(level=logging.INFO):\n    kw = {\n        'format': '[%(asctime)s][%(module)s]: %(message)s',\n        'datefmt': '%m/%d/%Y %H:%M:%S',\n        'level': level,\n        'stream': sys.stdout\n    }\n\n    logging.basicConfig(**kw)\n    logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(\n        logging.WARNING)\n\ndef main():\n    setup_logging()\n    parse_args()\n    # check_cmd_para()\n    info('{} script started'.format(abspath(__file__)))\n    info('NUM_CPU = {}'.format(NUM_CPU))\n    setup_build_env()\n    local_workflow()\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "scripts/build/build-mac.py",
    "content": "#!/usr/bin/env python\n# coding: UTF-8\n\n'''This script builds the seafile mac client.\n'''\n\nimport atexit\nimport logging\nimport commands\nfrom contextlib import contextmanager\nimport glob\nimport multiprocessing\nimport optparse\nimport os\nfrom os.path import abspath, basename, dirname, join, exists, expanduser\nimport re\nimport shutil\nimport subprocess\nimport sys\nimport tempfile\n\nFINAL_APP = 'Seafile Client.app'\nFSPLUGIN_APPEX_NAME = 'Seafile FinderSync.appex'\nCERT_ID = '79AB1AF435DD2CBC5FDB3EBBD45B0DA17727B299'\n\nif 'SEAFILE_BUILD_SLAVE' not in os.environ:\n    import fabric\n    from fabric.api import execute, env, settings, run as fabric_run\n    from fabric.operations import put as put_file, get as get_file\n    # fabric login configs\n    env.user = 'vagrant'\n    env.password = 'vagrant'\n    env.use_ssh_config = True\n\n####################\n### Requires Python 2.6+\n####################\nif sys.version_info[0] == 3:\n    print 'Python 3 not supported yet. Quit now.'\n    sys.exit(1)\nif sys.version_info[1] < 6:\n    print 'Python 2.6 or above is required. Quit now.'\n    sys.exit(1)\n\n####################\n### Global variables\n####################\n\n# command line configuartion\nconf = {}\n\n# key names in the conf dictionary.\n# pylint: disable=bad-whitespace\nCONF_CLEAN              = 'clean'\nCONF_MODE               = 'mode'\nCONF_VERSION            = 'version'\nCONF_SEAFILE_VERSION    = 'seafile_version'\nCONF_SEAFILE_CLIENT_VERSION  = 'seafile_client_version'\nCONF_LIBSEARPC_VERSION  = 'libsearpc_version'\nCONF_SRCDIR             = 'srcdir'\nCONF_KEEP               = 'keep'\nCONF_BUILDDIR           = 'builddir'\nCONF_OUTPUTDIR          = 'outputdir'\nCONF_THIRDPARTDIR       = 'thirdpartdir'\nCONF_NO_STRIP           = 'nostrip'\nCONF_SLAVE_HOST         = 'slave_host'\nCONF_BRAND              = 'brand'\nCONF_LOCAL              = 'local'\n# pylint: enable=bad-whitespace\n\nNUM_CPU = multiprocessing.cpu_count()\nPID = os.getpid()\n\nSLAVE_WORKDIR = expanduser('~/tmp/seafile-mac-build-slave')\nSLAVE_SRCDIR = join(SLAVE_WORKDIR, 'srcs')\nSLAVE_BUILDDIR = join(SLAVE_WORKDIR, 'build')\nSLAVE_BUILD_SCRIPT = join(SLAVE_WORKDIR, 'build-mac.py')\n\n####################\n### Common helper functions\n####################\ndef highlight(content, is_error=False):\n    '''Add ANSI color to content to get it highlighted on terminal'''\n    if is_error:\n        return '\\x1b[1;31m%s\\x1b[m' % content\n    else:\n        return '\\x1b[1;32m%s\\x1b[m' % content\n\ndef info(msg):\n    logging.info(highlight('[INFO][{}] ').format(conf.get(CONF_MODE, '')) + msg)\n\n@contextmanager\ndef cd(path):\n    oldpwd = os.getcwd()\n    os.chdir(path)\n    try:\n        yield\n    finally:\n        os.chdir(oldpwd)\n\ndef exist_in_path(prog):\n    '''Test whether prog exists in system path'''\n    return bool(find_in_path(prog))\n\ndef prepend_env_value(name, value, seperator=':'):\n    '''append a new value to a list'''\n    try:\n        current_value = os.environ[name]\n    except KeyError:\n        current_value = ''\n\n    new_value = value\n    if current_value:\n        new_value += seperator + current_value\n\n    os.environ[name] = new_value\n\ndef find_in_path(prog):\n    '''Find a file in system path'''\n    dirs = os.environ['PATH'].split(':')\n    for d in dirs:\n        if d == '':\n            continue\n        path = join(d, prog)\n        if exists(path):\n            return path\n\n    return None\n\ndef error(msg=None, usage=None):\n    if msg:\n        print highlight('[ERROR] ') + msg\n    if usage:\n        print usage\n    sys.exit(1)\n\ndef run_argv(argv, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Run a program and wait it to finish, and return its exit code. The\n    standard output of this program is supressed.\n\n    '''\n    info('running %s, cwd=%s' % (' '.join(['\"{}\"'.format(a) for a in argv]), cwd or os.getcwd()))\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(argv,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env)\n        return proc.wait()\n\ndef run(cmdline, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Like run_argv but specify a command line string instead of argv'''\n    info('running %s, cwd=%s' % (cmdline, cwd or os.getcwd()))\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(cmdline,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env,\n                                shell=True)\n        return proc.wait()\n\ndef must_run(cmdline, *a, **kw):\n    ret = run(cmdline, *a, **kw)\n    if ret != 0:\n        error('failed to run %s' % cmdline)\n\ndef check_remove(path):\n    \"\"\"\n    Remove the file/dir specified by `path`, if exists.\n    \"\"\"\n    path = abspath(path)\n    assert path.count('/') >= 2\n    if exists(path):\n        is_dir = os.path.isdir(path)\n        info('removing {} {}'.format('dir' if is_dir else 'file', path))\n        if is_dir:\n            shutil.rmtree(path)\n        else:\n            os.unlink(path)\n\ndef must_mkdir(path):\n    '''Create a directory, exit on failure'''\n    try:\n        if not exists(path):\n            os.mkdir(path)\n    except OSError, e:\n        error('failed to create directory %s:%s' % (path, e))\n\ndef must_copy(src, dst):\n    '''Copy src to dst, exit on failure'''\n    try:\n        shutil.copy(src, dst)\n    except Exception, e:\n        error('failed to copy %s to %s: %s' % (src, dst, e))\n\ndef must_copytree(src, dst):\n    '''Copy src tree to dst, exit on failure'''\n    try:\n        shutil.copytree(src, dst)\n    except Exception, e:\n        error('failed to copy %s to %s: %s' % (src, dst, e))\n\nclass Project(object):\n    '''Base class for a project'''\n    # Probject name, i.e. libseaprc/seafile/\n    name = ''\n\n    # A list of shell commands to configure/build the project\n    build_commands = []\n\n    def __init__(self):\n        # the path to pass to --prefix=/<prefix>\n        self.prefix = join(conf[CONF_BUILDDIR], 'usr')\n        self.version = self.get_version()\n        self.src_tarball = join(conf[CONF_SRCDIR], '%s-%s.tar.gz' % (self.name, self.version))\n        # project dir, like <builddir>/seafile-1.2.2/\n        self.projdir = join(conf[CONF_BUILDDIR], '%s-%s' % (self.name, self.version))\n\n    def get_version(self):\n        # libsearpc can have different versions from seafile.\n        raise NotImplementedError\n\n    def get_source_commit_id(self):\n        '''By convetion, we record the commit id of the source code in the\n        file \"<projdir>/latest_commit\"\n\n        '''\n        latest_commit_file = join(self.projdir, 'latest_commit')\n        with open(latest_commit_file, 'r') as fp:\n            commit_id = fp.read().strip('\\n\\r\\t ')\n\n        return commit_id\n\n    def append_cflags(self, macros):\n        cflags = ' '.join(['-D%s=%s' % (k, macros[k]) for k in macros])\n        prepend_env_value('CPPFLAGS',\n                          cflags,\n                          seperator=' ')\n\n    def uncompress(self):\n        '''Uncompress the source from the tarball'''\n        info('Uncompressing %s' % self.name)\n\n        if run('tar xf %s' % self.src_tarball) < 0:\n            error('failed to uncompress source of %s' % self.name)\n\n    def before_build(self):\n        '''Hook method to do project-specific stuff before running build commands'''\n        pass\n\n    def build(self):\n        '''Build the source'''\n        self.before_build()\n        info('Building %s' % self.name)\n        for cmd in self.build_commands:\n            if run(cmd, cwd=self.projdir) != 0:\n                error('error when running command:\\n\\t%s\\n' % cmd)\n\ndef concurrent_make():\n    return 'make -j%s' % NUM_CPU\n\nclass Libsearpc(Project):\n    name = 'libsearpc'\n\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            './configure --prefix=%s --disable-compile-demo' % self.prefix,\n            concurrent_make(),\n            'make install'\n        ]\n\n    def get_version(self):\n        return conf[CONF_LIBSEARPC_VERSION]\n\nclass Seafile(Project):\n    name = 'seafile'\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            './configure --prefix=%s --disable-fuse' % self.prefix,\n            concurrent_make(),\n            'make install'\n        ]\n\n    def get_version(self):\n        return conf[CONF_SEAFILE_VERSION]\n\n    def update_cli_version(self):\n        '''Substitute the version number in seaf-cli'''\n        cli_py = join(self.projdir, 'app', 'seaf-cli')\n        with open(cli_py, 'r') as fp:\n            lines = fp.readlines()\n\n        ret = []\n        for line in lines:\n            old = '''SEAF_CLI_VERSION = \"\"'''\n            new = '''SEAF_CLI_VERSION = \"%s\"''' % conf[CONF_VERSION]\n            line = line.replace(old, new)\n            ret.append(line)\n\n        with open(cli_py, 'w') as fp:\n            fp.writelines(ret)\n\n    def before_build(self):\n        self.update_cli_version()\n        macros = {}\n        # SET SEAFILE_SOURCE_COMMIT_ID, so it can be printed in the log\n        macros['SEAFILE_SOURCE_COMMIT_ID'] = '\\\\\"%s\\\\\"' % self.get_source_commit_id()\n        self.append_cflags(macros)\n\nclass SeafileClient(Project):\n\n    name = 'seafile-client'\n\n    def __init__(self):\n        Project.__init__(self)\n        cmake_defines = {\n            'CMAKE_OSX_ARCHITECTURES': 'x86_64',\n            'CMAKE_OSX_DEPLOYMENT_TARGET': '10.9',\n            'CMAKE_BUILD_TYPE': 'Release',\n            'BUILD_SHIBBOLETH_SUPPORT': 'ON',\n            'BUILD_SPARKLE_SUPPORT': 'ON',\n        }\n        cmake_defines_formatted = ' '.join(['-D{}={}'.format(k, v) for k, v in cmake_defines.iteritems()])\n        self.build_commands = [\n            'rm -f CMakeCache.txt',\n            'cmake -GXcode {}'.format(cmake_defines_formatted),\n            'xcodebuild -target seafile-applet -configuration Release -jobs {}'.format(NUM_CPU),\n            'rm -rf seafile-applet.app',\n            'cp -r Release/seafile-applet.app seafile-applet.app',\n            'mkdir -p seafile-applet.app/Contents/Frameworks',\n            'macdeployqt seafile-applet.app',\n        ]\n\n    def get_version(self):\n        return conf[CONF_SEAFILE_CLIENT_VERSION]\n\n    def before_build(self):\n        pass\n\nclass SeafileDMGLayout(Seafile):\n\n    def __init__(self):\n        Seafile.__init__(self)\n        self.build_commands = [\n        ]\n\nclass SeafileFinderSyncPlugin(SeafileClient):\n\n    def __init__(self):\n        SeafileClient.__init__(self)\n        self.build_commands = [\n            'fsplugin/build.sh'\n        ]\n\ndef check_targz_src(proj, version, srcdir):\n    src_tarball = join(srcdir, '%s-%s.tar.gz' % (proj, version))\n    if not exists(src_tarball):\n        error('%s not exists' % src_tarball)\n\ndef validate_args(usage, options):\n    required_args = [\n        CONF_VERSION,\n        CONF_LIBSEARPC_VERSION,\n        CONF_SEAFILE_VERSION,\n        CONF_SEAFILE_CLIENT_VERSION,\n        CONF_SRCDIR,\n    ]\n\n    # fist check required args\n    for optname in required_args:\n        if getattr(options, optname, None) is None:\n            error('%s must be specified' % optname, usage=usage)\n\n    def get_option(optname):\n        return getattr(options, optname)\n\n    # [ version ]\n    def check_project_version(version):\n        '''A valid version must be like 1.2.2, 1.3'''\n        if not re.match(r'^[0-9]+(\\.[0-9]+)+$', version):\n            error('%s is not a valid version' % version, usage=usage)\n\n    mode = get_option(CONF_MODE).lower()\n    assert mode in ('master', 'slave')\n\n    version = get_option(CONF_VERSION)\n    seafile_version = get_option(CONF_SEAFILE_VERSION)\n    seafile_client_version = get_option(CONF_SEAFILE_CLIENT_VERSION)\n    libsearpc_version = get_option(CONF_LIBSEARPC_VERSION)\n\n    check_project_version(version)\n    check_project_version(libsearpc_version)\n    check_project_version(seafile_version)\n    check_project_version(seafile_client_version)\n\n    # [ srcdir ]\n    srcdir = get_option(CONF_SRCDIR)\n    check_targz_src('libsearpc', libsearpc_version, srcdir)\n    check_targz_src('seafile', seafile_version, srcdir)\n\n    # [ keep ]\n    keep = get_option(CONF_KEEP)\n\n    # [ local ]\n    build_local = get_option(CONF_LOCAL)\n\n    # [ builddir ]\n    builddir = get_option(CONF_BUILDDIR)\n    if not exists(builddir):\n        error('%s does not exist' % builddir, usage=usage)\n    # TODO: remove this\n    # if mode == 'master':\n    #     builddir = expanduser('~/tmp')\n    #     if not exists(builddir):\n    #         must_mkdir(builddir)\n\n    builddir = join(builddir, 'seafile-mac-build')\n\n    if not keep:\n        check_remove(builddir)\n        must_mkdir(builddir)\n\n    # [ outputdir ]\n    outputdir = get_option(CONF_OUTPUTDIR)\n    if outputdir:\n        if not exists(outputdir):\n            error('outputdir %s does not exist' % outputdir, usage=usage)\n    else:\n        outputdir = os.getcwd()\n\n    # [ no strip]\n    nostrip = get_option(CONF_NO_STRIP)\n\n    slave_host = get_option(CONF_SLAVE_HOST)\n\n    brand = get_option(CONF_BRAND)\n\n    conf[CONF_MODE] = mode\n    conf[CONF_LOCAL] = build_local\n    conf[CONF_VERSION] = version\n    conf[CONF_LIBSEARPC_VERSION] = libsearpc_version\n    conf[CONF_SEAFILE_VERSION] = seafile_version\n    conf[CONF_SEAFILE_CLIENT_VERSION] = seafile_client_version\n\n    conf[CONF_BUILDDIR] = builddir\n    conf[CONF_SRCDIR] = srcdir\n    conf[CONF_OUTPUTDIR] = outputdir\n    conf[CONF_KEEP] = keep\n    conf[CONF_NO_STRIP] = nostrip\n    conf[CONF_SLAVE_HOST] = slave_host\n    conf[CONF_BRAND] = brand\n    # TODO: remove this\n    # conf[CONF_SLAVE_HOST] = 'lion'\n    # conf[CONF_SLAVE_HOST] = 'sierra'\n\n    prepare_builddir(builddir)\n    # show_build_info()\n\ndef show_build_info():\n    '''Print all conf information. Confirm before continue.'''\n    info('------------------------------------------')\n    info('Seafile command line client %s: BUILD INFO' % conf[CONF_VERSION])\n    info('------------------------------------------')\n    info('libsearpc:        %s' % conf[CONF_LIBSEARPC_VERSION])\n    info('seafile:          %s' % conf[CONF_SEAFILE_VERSION])\n    info('seafile-client:   %s' % conf[CONF_SEAFILE_CLIENT_VERSION])\n    info('builddir:         %s' % conf[CONF_BUILDDIR])\n    info('outputdir:        %s' % conf[CONF_OUTPUTDIR])\n    info('source dir:       %s' % conf[CONF_SRCDIR])\n    info('strip symbols:    %s' % (not conf[CONF_NO_STRIP]))\n    info('clean on exit:    %s' % (not conf[CONF_KEEP]))\n    info('------------------------------------------')\n    info('press any key to continue ')\n    info('------------------------------------------')\n    dummy = raw_input()\n\ndef prepare_builddir(builddir):\n    must_mkdir(builddir)\n\n    # if not conf[CONF_KEEP]:\n    #     def remove_builddir():\n    #         '''Remove the builddir when exit'''\n    #         info('remove builddir before exit')\n    #         shutil.rmtree(builddir, ignore_errors=True)\n    #     atexit.register(remove_builddir)\n\n    os.chdir(builddir)\n\n    must_mkdir(join(builddir, 'usr'))\n\ndef parse_args():\n    parser = optparse.OptionParser()\n    def long_opt(opt):\n        return '--' + opt\n\n    parser.add_option(long_opt(CONF_MODE),\n                      dest=CONF_MODE,\n                      default='master',\n                      help='''The mode: master or slave. Master is osx 10.12, while slave is used when running on osx 10.7 lion.\n                      Master would ssh on to slave to compile the source code.''')\n\n    parser.add_option(long_opt(CONF_VERSION),\n                      dest=CONF_VERSION,\n                      nargs=1,\n                      help='the version to build. Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_SEAFILE_VERSION),\n                      dest=CONF_SEAFILE_VERSION,\n                      nargs=1,\n                      help='the version of seafile as specified in its \"configure.ac\". Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_LIBSEARPC_VERSION),\n                      dest=CONF_LIBSEARPC_VERSION,\n                      nargs=1,\n                      help='the version of libsearpc as specified in its \"configure.ac\". Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_SEAFILE_CLIENT_VERSION),\n                      dest=CONF_SEAFILE_CLIENT_VERSION,\n                      nargs=1,\n                      help='the version of seafile-client. Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_BUILDDIR),\n                      dest=CONF_BUILDDIR,\n                      nargs=1,\n                      help='the directory to build the source. Defaults to /tmp',\n                      default=tempfile.gettempdir())\n\n    parser.add_option(long_opt(CONF_OUTPUTDIR),\n                      dest=CONF_OUTPUTDIR,\n                      nargs=1,\n                      help='the output directory to put the generated tarball. Defaults to the current directory.',\n                      default=os.getcwd())\n\n    parser.add_option(long_opt(CONF_SRCDIR),\n                      dest=CONF_SRCDIR,\n                      nargs=1,\n                      help='''Source tarballs must be placed in this directory.''')\n\n    parser.add_option(long_opt(CONF_KEEP),\n                      dest=CONF_KEEP,\n                      action='store_true',\n                      help='''keep the build directory after the script exits. By default, the script would delete the build directory at exit.''')\n\n    parser.add_option(long_opt(CONF_NO_STRIP),\n                      dest=CONF_NO_STRIP,\n                      action='store_true',\n                      help='''do not strip debug symbols''')\n\n    parser.add_option(long_opt(CONF_SLAVE_HOST),\n                      dest=CONF_SLAVE_HOST,\n                      default='lion',\n                      help='the hostname of the lower version osx slave')\n\n    parser.add_option(long_opt(CONF_BRAND),\n                      dest=CONF_BRAND,\n                      help='the brand')\n\n    parser.add_option(long_opt(CONF_LOCAL),\n                      dest=CONF_LOCAL,\n                      action='store_true',\n                      help='build locally instead of using an old 10.7 vm')\n    usage = parser.format_help()\n    options, remain = parser.parse_args()\n    if remain:\n        error(usage=usage)\n\n    validate_args(usage, options)\n\ndef setup_build_env():\n    '''Setup environment variables, such as export PATH=$BUILDDDIR/bin:$PATH'''\n    # os.environ.update({\n    # })\n    prefix = join(conf[CONF_BUILDDIR], 'usr')\n\n    prepend_env_value('CFLAGS',\n                      '-Wall -O2 -g -DNDEBUG -I/opt/local/include -mmacosx-version-min=10.7',\n                      seperator=' ')\n\n    prepend_env_value('CXXFLAGS',\n                      '-Wall -O2 -g -DNDEBUG -I/opt/local/include -mmacosx-version-min=10.7',\n                      seperator=' ')\n\n    prepend_env_value('CPPFLAGS',\n                      '-I%s' % join(prefix, 'include'),\n                      seperator=' ')\n\n    prepend_env_value('CPPFLAGS',\n                      '-DSEAFILE_CLIENT_VERSION=\\\\\"%s\\\\\"' % conf[CONF_VERSION],\n                      seperator=' ')\n\n    if conf[CONF_NO_STRIP]:\n        prepend_env_value('CPPFLAGS',\n                          '-g -O0',\n                          seperator=' ')\n\n    prepend_env_value('LDFLAGS',\n                      '-L%s' % join(prefix, 'lib'),\n                      seperator=' ')\n\n    prepend_env_value('LDFLAGS',\n                      '-L%s' % join(prefix, 'lib64'),\n                      seperator=' ')\n\n    prepend_env_value('LDFLAGS',\n                      '-L/opt/local/lib -Wl,-headerpad_max_install_names -mmacosx-version-min=10.7',\n                      seperator=' ')\n\n    prepend_env_value('PATH', join(prefix, 'bin'))\n    prepend_env_value('PKG_CONFIG_PATH', join(prefix, 'lib', 'pkgconfig'))\n    prepend_env_value('PKG_CONFIG_PATH', join(prefix, 'lib64', 'pkgconfig'))\n\npath_pattern = re.compile(r'^\\s+(\\S+)\\s+\\(compatibility.*')\ndef get_dependent_libs(executable):\n    def is_syslib(lib):\n        if lib.startswith('/usr/lib'):\n            return True\n        if lib.startswith('/System/'):\n            return True\n        return False\n\n    otool_output = commands.getoutput('otool -L %s' % executable)\n    libs = set()\n    for line in otool_output.splitlines():\n        m = path_pattern.match(line)\n        if not m:\n            continue\n        path = m.group(1)\n        if not is_syslib(path):\n            libs.add(path)\n    return libs\n\ndef copy_shared_libs():\n    '''copy shared c libs, such as libevent, glib'''\n    builddir = conf[CONF_BUILDDIR]\n    frameworks_dir = join(SeafileClient().projdir, 'seafile-applet.app/Contents/Frameworks')\n\n    must_mkdir(frameworks_dir)\n    seafile_path = join(builddir, 'usr/bin/seaf-daemon')\n\n    libs = set()\n    libs.update(get_dependent_libs(seafile_path))\n\n    # Get deps of deps recursively until no more deps can be included\n    while True:\n        newlibs = set(libs)\n        for lib in libs:\n            newlibs.update(get_dependent_libs(lib))\n        if newlibs == libs:\n            break\n        libs = newlibs\n\n    for lib in libs:\n        dst_file = join(frameworks_dir, basename(lib))\n        if exists(dst_file):\n            continue\n        info('Copying %s' % lib)\n        shutil.copy(lib, frameworks_dir)\n\n    change_rpaths()\n\ndef change_rpaths():\n    \"\"\"\n    Chagne the rpath of the referenced dylibs so the app can be relocated\n    anywhere in the user's system.\n    See:\n      - https://blogs.oracle.com/dipol/entry/dynamic_libraries_rpath_and_mac\n      - https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/RunpathDependentLibraries.html\n    \"\"\"\n    contents_dir = join(SeafileClient().projdir, 'seafile-applet.app/Contents')\n    frameworks_dir = join(contents_dir, 'Frameworks')\n    resources_dir = join(contents_dir, 'Resources')\n    macos_dir = join(contents_dir, 'MacOS')\n\n    seafile_applet = join(macos_dir, 'seafile-applet')\n    binaries = [\n        seafile_applet,\n        join(resources_dir, 'seaf-daemon')\n    ]\n\n    RPATH_RE = re.compile(r'^path\\s+(\\S+)\\s+\\(offset .*$')\n    def get_rpaths(fn):\n        rpaths = []\n        output = commands.getoutput('otool -l {} | grep -A2 RPATH || true'.format(fn))\n        # The output is like\n            #           cmd LC_RPATH\n            #       cmdsize 24\n            #          path /usr/local (offset 12)\n            # --\n            #           cmd LC_RPATH\n            #       cmdsize 48\n            #          path @executable_path/../Frameworks (offset 12)\n        for line in output.splitlines():\n            m = RPATH_RE.match(line.strip())\n            if m:\n                rpaths.append(m.group(1))\n        return rpaths\n\n    def has_rpath(fn, path):\n        return path in get_rpaths(fn)\n\n    def add_frameworks_dir_to_rpath(fn, executable=True):\n        relpath = 'executable_path' if executable else 'loader_path'\n        relative_frameworks_dir = '@{}/../Frameworks'.format(relpath)\n        if not has_rpath(fn, relative_frameworks_dir):\n            must_run('install_name_tool -add_rpath {} {}' .format(relative_frameworks_dir, fn))\n\n    def remove_local_rpaths(fn):\n        local_paths = ['/usr/local/lib', '/opt/local/lib']\n        for path in local_paths:\n            if has_rpath(fn, path):\n                must_run('install_name_tool -delete_rpath {} {}'.format(path, fn))\n\n    def change_deps_rpath(fn):\n        deps = get_dependent_libs(fn)\n        for dep in deps:\n            bn = basename(dep)\n            if 'framework' in bn or bn.startswith('Qt') or bn == 'Sparkle':\n                continue\n            must_run('install_name_tool -change {} @rpath/{} {}'.format(dep, basename(dep), fn))\n\n    for binary in binaries:\n        add_frameworks_dir_to_rpath(binary)\n        remove_local_rpaths(binary)\n        change_deps_rpath(binary)\n\n    libs = os.listdir(frameworks_dir)\n    for lib_name in libs:\n        lib = join(frameworks_dir, lib_name)\n        if os.path.isdir(lib):\n            continue\n        must_run('install_name_tool -id @rpath/{} {}'.format(basename(lib), lib))\n        add_frameworks_dir_to_rpath(lib, executable=False)\n        change_deps_rpath(lib)\n        remove_local_rpaths(lib)\n\nDROPDMG = '/Applications/DropDMG.app/Contents/Frameworks/DropDMGFramework.framework/Versions/A/dropdmg'\n\ndef gen_dmg():\n    output_dmg = 'app-{}.dmg'.format(conf[CONF_VERSION])\n    parentdir = 'app-{}'.format(conf[CONF_VERSION])\n    appdir = join(parentdir, 'seafile-applet.app')\n    app_plugins_dir = join(appdir, 'Contents/PlugIns')\n\n    layout = SeafileDMGLayout()\n    layout.uncompress()\n    layout_folder = join(layout.projdir, 'dmg/seafileLayout')\n\n    args = [\n        DROPDMG,\n        parentdir,\n        '--format', 'bzip2',\n        '--layout-folder', layout_folder,\n        '--volume-name', conf[CONF_BRAND] or 'Seafile Client',\n    ]\n\n    with cd(conf[CONF_BUILDDIR]):\n        check_remove(parentdir)\n        check_remove(output_dmg)\n        must_mkdir(parentdir)\n        must_run('tar xf seafile-applet.app.tar.gz -C {}'.format(parentdir))\n        # Remove the Qt Bearer plugin because it would run a background thread\n        # to scan wifi networks. See\n        # https://trello.com/c/j28eOIo1/359-explicitly-exclude-qt-bearer-plugins-for-the-windows-and-mac-packages\n        # for details.\n        must_run('rm -rf \"{}\"'.format(join(app_plugins_dir, 'bearer')))\n        # fsplugin must be copied before we sign the final .app dir\n        copy_fsplugin(app_plugins_dir)\n        sign_files(appdir)\n\n        # Rename the .app dir to 'Seafile Client.app', and create the shortcut\n        # to '/Applications' so the user can drag into it when opening the DMG.\n        brand = conf.get(CONF_BRAND, '')\n        if brand:\n            final_app = '{}.app'.format(brand)\n        else:\n            final_app = FINAL_APP\n        must_run('mv {}/seafile-applet.app \"{}/{}\"'.format(parentdir, parentdir, final_app))\n        must_run('ln -sf /Applications {}/'.format(parentdir))\n\n        # Open DropDMG manually, or dropdmg command line may fail.\n        run(''' osascript -e 'tell application \"DropDMG\" to quit' || true ''')\n        run(''' osascript -e 'open application \"DropDMG\"' || true ''')\n        run(''' osascript -e 'activate application \"DropDMG\"' || true ''')\n\n        # Sometimes DropDmg would fail if there are two many Finder windows.\n        run(''' osascript -e 'tell application \"Finder\" to close every window' ''')\n        if run_argv(args) != 0:\n            error('failed to run {}'.format(args))\n\ndef sign_in_parallel(files_to_sign):\n    import threading\n    import Queue\n    queue = Queue.Queue()\n    POISON_PILL = ''\n\n    class SignThread(threading.Thread):\n        def __init__(self, index):\n            threading.Thread.__init__(self)\n            self.index = index\n\n        def run(self):\n            info('sign thread {} started'.format(self.index))\n            while True:\n                try:\n                    fn = queue.get(timeout=1)\n                except Queue.Empty:\n                    continue\n                else:\n                    if fn == POISON_PILL:\n                        break\n                    else:\n                        do_sign(fn)\n            info('sign thread {} stopped'.format(self.index))\n\n    TOTAL_THREADS = max(NUM_CPU / 2, 1)\n    threads = []\n    for i in xrange(TOTAL_THREADS):\n        t = SignThread(i)\n        t.start()\n        threads.append(t)\n\n    for fn in files_to_sign:\n        queue.put(fn)\n\n    for _ in xrange(TOTAL_THREADS):\n        queue.put(POISON_PILL)\n\n    for i in xrange(TOTAL_THREADS):\n        threads[i].join()\n\ndef sign_files(appdir):\n    webengine_app = join(\n        appdir,\n        'Contents/Frameworks/QtWebEngineCore.framework/Versions/5/Helpers/QtWebEngineProcess.app'\n    )\n\n    def _glob(pattern, *a, **kw):\n        return glob.glob(join(appdir, pattern), *a, **kw)\n\n    # The webengine app must be signed first, otherwise the sign of\n    # QtWebengineCore.framework would fail.\n    if exists(webengine_app):\n        entitlements = join(Seafile().projdir, 'scripts/build/osx.entitlements')\n        do_sign(\n            webengine_app,\n            extra_args=['--entitlements', entitlements]\n        )\n\n    # Strip the get-task-allow entitlements for Sparkle binaries\n    for fn in _glob('Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/*'):\n        do_sign(fn, preserve_entitlemenets=False)\n\n    # Sign the nested contents of Sparkle before we sign\n    # Sparkle.Framework in the thread pool.\n    for fn in (\n            'Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app',\n            'Contents/Frameworks/Sparkle.framework/Versions/A/Sparkle',\n    ):\n        do_sign(join(appdir, fn))\n\n    patterns = [\n        'Contents/Frameworks/*.framework',\n        'Contents/PlugIns/*/*.dylib',\n        'Contents/Frameworks/*.dylib',\n        'Contents/Resources/seaf-daemon',\n        'Contents/MacOS/seadrive-gui',\n    ]\n\n    files_to_sign = []\n    for p in patterns:\n        files_to_sign.extend(_glob(p))\n\n    info('{} files to sign'.format(len(files_to_sign)))\n\n    sign_in_parallel(files_to_sign)\n    # for fn in files_to_sign:\n    #     do_sign(fn)\n\n    do_sign(appdir)\n    # do_sign(appdir, extra_args=['--deep'])\n\n_keychain_unlocked = False\ndef unlock_keychain():\n    \"\"\"\n    Unlock the keychain when we're using ssh instead of using the terminal from\n    GUI. See http://stackoverflow.com/a/20208104/1467959\n    \"\"\"\n    global _keychain_unlocked\n    if not _keychain_unlocked:\n        _keychain_unlocked = True\n        run('security -v unlock-keychain -p vagrant || true')\n\ndef do_sign(path, extra_args=None, preserve_entitlemenets=True):\n    unlock_keychain()\n    args = [\n        'codesign',\n        '--verbose=4',\n        '-o', 'runtime',\n        '--timestamp',\n        '--verify',\n        # '--no-strict',\n        '--force',\n        '-s', CERT_ID,\n    ]\n    if preserve_entitlemenets:\n        args += ['--preserve-metadata=entitlements']\n    extra_args = extra_args or []\n    if extra_args:\n        args.extend(extra_args)\n\n    args.append(path)\n\n    if run_argv(args) != 0:\n        error('failed to sign {}'.format(path))\n\ndef send_file_to_slave(src, dst):\n    info('putting file {} => {}'.format(src, dst))\n    with settings(host_string=conf[CONF_SLAVE_HOST]):\n        put_file(local_path=src, remote_path=dst)\n\ndef get_file_from_slave(src, dst):\n    host = conf[CONF_SLAVE_HOST]\n    info('getting file {}:{} => {}'.format(host, src, dst))\n    with settings(host_string=host):\n        get_file(src, dst)\n\ndef send_project_tarball(project):\n    info('sending tarball of {}'.format(project.name))\n    src = project.src_tarball\n    dst = join(SLAVE_SRCDIR, basename(src))\n    send_file_to_slave(src, dst)\n\ndef slave_run(cmd, **kw):\n    info('running cmd: ' + cmd)\n    kw['host'] = conf[CONF_SLAVE_HOST]\n    cmd = '. ~/.bash_profile; export SEAFILE_BUILD_SLAVE=1; ' + cmd\n    execute(fabric_run, cmd, **kw)\n\ndef prepare_dirs_on_slave():\n    info('creating work directory on the slave')\n    slave_run('mkdir -p {}'.format(SLAVE_SRCDIR))\n    slave_run('mkdir -p {}'.format(SLAVE_BUILDDIR))\n\ndef send_sources_to_slave():\n    info('sending sources to the slave')\n    prepare_dirs_on_slave()\n    send_file_to_slave(abspath(__file__), SLAVE_BUILD_SCRIPT)\n    projects = [\n        Libsearpc(),\n        Seafile(),\n        SeafileClient(),\n    ]\n    for p in projects:\n        send_project_tarball(p)\n\ndef build_on_slave():\n    args = [\n        'python',\n        SLAVE_BUILD_SCRIPT,\n        '--mode=slave',\n        '--version={}'.format(conf[CONF_VERSION]),\n        '--libsearpc_version={}'.format(conf[CONF_LIBSEARPC_VERSION]),\n        '--seafile_version={}'.format(conf[CONF_SEAFILE_VERSION]),\n        '--seafile_client_version={}'.format(conf[CONF_SEAFILE_CLIENT_VERSION]),\n        '--srcdir={}'.format(SLAVE_SRCDIR),\n        '--builddir={}' .format(SLAVE_BUILDDIR),\n    ]\n    info('packaging on the slave')\n    slave_run(' '.join(args))\n\ndef get_app_tgz_from_slave():\n    fn = 'seafile-applet.app.tar.gz'\n    src = join(SLAVE_BUILDDIR, 'seafile-mac-build', fn)\n    dst = join(conf[CONF_BUILDDIR], fn)\n    get_file_from_slave(src, dst)\n\ndef copy_dmg():\n    brand = conf[CONF_BRAND] or 'seafile-client'\n    branded_dmg = '{}-{}.dmg'.format(brand, conf[CONF_VERSION])\n    src_dmg = os.path.join(conf[CONF_BUILDDIR], 'app-{}.dmg'.format(conf[CONF_VERSION]))\n    dst_dmg = os.path.join(conf[CONF_OUTPUTDIR], branded_dmg)\n\n    # move msi to outputdir\n    must_copy(src_dmg, dst_dmg)\n\n    print '---------------------------------------------'\n    print 'The build is successfully. Output is:'\n    print '>>\\t%s' % dst_dmg\n    print '---------------------------------------------'\n\ndef notarize_dmg():\n    pkg = os.path.join(conf[CONF_BUILDDIR], 'app-{}.dmg'.format(conf[CONF_VERSION]))\n    info('Try to notarize {}'.format(pkg))\n    notarize_script = join(Seafile().projdir, 'scripts/build/notarize.sh')\n    cmdline = '{} {}'.format(notarize_script, pkg)\n    ret = run(cmdline)\n    if ret != 0:\n        error('failed to notarize: %s' % cmdline)\n    info('Successfully notarized {}'.format(pkg))\n\ndef build_and_sign_fsplugin():\n    \"\"\"\n    Build and sign the fsplugin. The final output would be \"${buildder}/Seafile FinderSync.appex\"\n    \"\"\"\n    fsplugin = SeafileFinderSyncPlugin()\n    fsplugin.uncompress()\n    fsplugin.build()\n    with cd(fsplugin.projdir):\n        appex_src = 'fsplugin/{}'.format(FSPLUGIN_APPEX_NAME)\n        appex_dst = join(conf[CONF_BUILDDIR], basename(appex_src))\n\n        check_remove(appex_dst)\n        must_copytree(appex_src, appex_dst)\n\n        entitlements_src = 'fsplugin/seafile-fsplugin.entitlements'\n        entitlements_dst = join(conf[CONF_BUILDDIR], basename(entitlements_src))\n\n        check_remove(entitlements_dst)\n        must_copy(entitlements_src, entitlements_dst)\n\n    do_sign(\n        appex_dst,\n        extra_args=['--entitlements', entitlements_dst]\n    )\n\ndef copy_fsplugin(plugins_dir):\n    src = join(conf[CONF_BUILDDIR], FSPLUGIN_APPEX_NAME)\n    dst = join(plugins_dir, FSPLUGIN_APPEX_NAME)\n    check_remove(dst)\n    must_copytree(src, dst)\n\ndef copy_sparkle_framework():\n    src = '/usr/local/Sparkle.framework'\n    dst = join(SeafileClient().projdir, 'seafile-applet.app/Contents/Frameworks', basename(src))\n    check_remove(dst)\n    # Here we use the `cp` command instead of shutil to do the copy, because `cp\n    # -P` would keep symlinks as is.\n    must_run('cp -R -P \"{}\" \"{}\"'.format(src, dst))\n\ndef build_projects():\n    libsearpc = Libsearpc()\n    seafile = Seafile()\n    seafile_client = SeafileClient()\n\n    libsearpc.uncompress()\n    libsearpc.build()\n\n    seafile.uncompress()\n    seafile.build()\n\n    seafile_client.uncompress()\n    seafile_client.build()\n\n    copy_sparkle_framework()\n\n    copy_shared_libs()\n\ndef local_workflow():\n    build_projects()\n    generate_app_tar_gz()\n\n    build_and_sign_fsplugin()\n    gen_dmg()\n    notarize_dmg()\n    copy_dmg()\n\ndef master_workflow():\n    send_sources_to_slave()\n    build_on_slave()\n    get_app_tgz_from_slave()\n\n    build_and_sign_fsplugin()\n    gen_dmg()\n    notarize_dmg()\n    copy_dmg()\n\ndef slave_workflow():\n    build_projects()\n    generate_app_tar_gz()\n\ndef generate_app_tar_gz():\n    # output_app_tgz = join(conf[CONF_BUILDDIR], '..', 'seafile-applet.app.tar.gz')\n    output_app_tgz = join(conf[CONF_BUILDDIR], 'seafile-applet.app.tar.gz')\n    with cd(SeafileClient().projdir):\n        run('tar czf {} seafile-applet.app'.format(output_app_tgz))\n\ndef setup_logging(level=logging.INFO):\n    kw = {\n        'format': '[%(asctime)s][%(module)s]: %(message)s',\n        'datefmt': '%m/%d/%Y %H:%M:%S',\n        'level': level,\n        'stream': sys.stdout\n    }\n\n    logging.basicConfig(**kw)\n    logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(\n        logging.WARNING)\n\ndef main():\n    setup_logging()\n    parse_args()\n    info('{} script started'.format(abspath(__file__)))\n    info('NUM_CPU = {}'.format(NUM_CPU))\n    setup_build_env()\n\n    if conf[CONF_LOCAL]:\n        local_workflow()\n    elif conf[CONF_MODE] == 'master':\n        # info('entering master workflow')\n        # master_workflow()\n        local_workflow()\n    else:\n        info('entering slave workflow')\n        slave_workflow()\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "scripts/build/build-msi-vs-cn.py",
    "content": "#!/usr/bin/env python\n# coding: UTF-8\n\nimport sys\n\n####################\n### Requires Python 3+\n####################\nif sys.version_info[0] == 2:\n    print('Python 2 not be supported, require Python 3. Quit now.')\n    sys.exit(1)\n\nimport os\nimport subprocess\nimport shutil\nimport time\nimport glob\nimport re\n\nBUILDDIR = os.path.join(os.getcwd(), \"..\\\\..\\\\..\\\\\")\n\n##################\n### Configure\n##################\n# Certificate file and password for signing seafile exe and msi package\nCERT_ID = os.environ[\"CERT_ID\"]\nCERT_PASSWORD = os.environ[\"CERT_PASSWORD\"]\n\n# Qt library directory\nQT_DIR = \"C:/Qt/6.5.2/msvc2019_64\"\n\n# Wix install directory\nWIX_BIN = \"C:/wix/bin\"\n\n# Openssl lib directory\nOPENSSL_DIR = \"C:/packagelib\"\n\n#####################\n# Work path : seafile library and program tmp directory\n# and wix build path\n#####################\n# Package directory\nSLNOUTPUTDIR = os.path.join(BUILDDIR, \"pack\")\n\n# Wix package directory\nWIX_PACKAGE_DIR = os.path.join(BUILDDIR, \"wix_pack\")\n\n####################\n### Global variables\n###################\nRETRY_COUNT = 3\nerror_exit = False\nversion = ''\n\n####################\n### Common helper functions\n###################\n\ndef highlight(content, is_error=False):\n    '''Add ANSI color to content to get it highlighted on terminal'''\n    dummy = is_error\n    return content\n    # if is_error:\n    #     return '\\x1b[1;31m%s\\x1b[m' % content\n    # else:\n    #     return '\\x1b[1;32m%s\\x1b[m' % content\n\ndef info(msg):\n    print(highlight('[INFO] ') + msg)\n\ndef error(msg=None, usage=None):\n    if msg:\n        print(highlight('[ERROR] ') + msg)\n    if usage:\n        print(usage)\n    sys.exit(1)\n\ndef find_in_path(prog):\n    '''Test whether prog exists in system path'''\n    dirs = os.environ['PATH'].split(';')\n    for d in dirs:\n        if d == '':\n            continue\n        path = os.path.join(d, prog)\n        if os.path.exists(path):\n            return path\n\n    return None\n\ndef run(cmdline, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Specify a command line string'''\n    info('running %s, cwd=%s' % (cmdline, cwd if cwd else os.getcwd()))\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(cmdline,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env,\n                                shell=True)\n        ret = proc.wait()\n        if ret != 0:\n            global error_exit\n            error_exit = True\n        return ret\n\ndef must_copy(src, dst):\n    '''Copy src to dst, exit on failure'''\n    try:\n        shutil.copy(src, dst)\n    except Exception as e:\n        error('failed to copy %s to %s: %s' % (src, dst, e))\n\ndef must_copytree(src, dst):\n    '''Copy dir src to dst, exit on failure'''\n    try:\n        shutil.copytree(src, dst)\n    except Exception as e:\n        error('failed to copy dir %s to %s: %s' % (src, dst, e))\n\ndef must_rmtree(path):\n    '''Recurse rm dir, exit on failure'''\n    try:\n        shutil.rmtree(path)\n    except Exception as e:\n        error('failed rm dir %s : %s' % (path, e))\n\ndef must_rename(src, dst):\n    '''Rename src to dst, exit on failure'''\n    try:\n        os.rename(src,dst)\n\n    except Exception as e:\n        error('failed to rename %s to %s: %s' % (src, dst, e))\n\ndef must_mkdir(path):\n    '''Creating directories recursively, exit on failure'''\n    if os.path.exists(path):\n        return\n    try:\n        os.makedirs(path)\n    except OSError as e:\n        error('failed to create directory %s:%s' % (path, e))\n\ndef dump_env():\n    print('Dumping environment variables:')\n    for k, v in os.environ.items():\n        print('%s: %s' % (k, v)\n)\n\ndef do_sign(fn, desc=None):\n    info('signing file {}'.format(fn))\n\n    if desc:\n        desc_flags = '/d \"{}\"'.format(desc)\n    else:\n        desc_flags = ''\n\n    signcmd = 'C:\\\\Users\\\\vs\\\\bin\\\\wosigncodecmd.exe sign /tp {} -p {} /hide /c /dig sha256 {} /tr http://timestamp.digicert.com/ /file {}'.format(CERT_ID, CERT_PASSWORD, desc_flags, fn)\n    i = 0\n    while i < RETRY_COUNT:\n        time.sleep(30)\n        ret = run(signcmd, cwd=os.path.dirname(fn))\n        if ret == 0:\n            break\n        i = i + 1\n        if i == RETRY_COUNT:\n            error('Failed to sign file \"{}\"'.format(fn))\n\ndef initworkspace():\n    # Clear build file cache\n    if os.path.exists(SLNOUTPUTDIR) :\n        must_rmtree(SLNOUTPUTDIR)\n\n    # Create a package directory\n    must_mkdir(SLNOUTPUTDIR)\n\ndef check_project_version(version):\n    '''A valid version must be like 1.2.2, 1.3'''\n    if not re.match(r'^[0-9]+(\\.[0-9]+)+$', version):\n        error('%s is not a valid version' % version, usage=\"vs-build.py 2.0.0\")\n\ndef check_cmd_para():\n    args = sys.argv\n    if len(args) != 2:\n        error('The number of parameters is incorrect', usage=\"vs-build.py 2.0.0\")\n    global version\n    version = args[1]\n    check_project_version(version)\n\n\nclass Project(object):\n    '''Base class for a project'''\n    # Probject name, i.e. libseaprc/seafile/seafile-gui\n    name = ''\n\n    # A list of shell commands to configure/build the project\n    build_commands = []\n\n    def __init__(self):\n        self.prefix = BUILDDIR\n        self.projdir = os.path.join(self.prefix, self.name)\n        self.outdir = os.path.join(self.projdir, 'x64', 'Release')\n\n    def before_build(self):\n        '''Hook method to do project-specific stuff before running build commands'''\n        pass\n\n    def build(self):\n        '''Build the source'''\n        self.before_build()\n        info('Building %s' % self.name)\n        # dump_env()\n        for cmd in self.build_commands:\n            if run(cmd, cwd=self.projdir) != 0:\n                error('error when running command:\\n\\t%s\\n' % cmd)\n        self.after_build()\n\n    def after_build(self):\n        pass\n\n\nclass Libsearpc(Project):\n    name = 'libsearpc'\n\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            'devenv  \"%s/libsearpc.sln\" /Rebuild \"Release|x64\"' %(self.projdir),\n        ]\n\n    def after_build(self):\n        libsearpc_path = os.path.join(self.outdir, 'libsearpc.dll')\n        must_copy(libsearpc_path, SLNOUTPUTDIR)\n\n\nclass Seafile(Project):\n    name = 'seafile'\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            'devenv %s/seafile.sln /Rebuild \"Release|x64\"' %(self.projdir),\n            'devenv %s/msi/custom/seafile_custom.sln /Rebuild \"Release|x64\"' %(self.projdir),\n        ]\n\n    def before_build(self):\n        pass\n\n    def after_build(self):\n\n        # Copy seafile dll file to SLNOUTPUTDIR\n        dlls = glob.glob(os.path.join(self.outdir, '*.dll'))\n        for dll in dlls:\n            must_copy(dll, SLNOUTPUTDIR)\n\n        # Copy seafile.exe file to SLNOUTPUTDIR\n        must_copy(os.path.join(self.outdir, 'seaf-daemon.exe'), SLNOUTPUTDIR)\n\n        # Generate breakpad symbol\n        dump_syms_path = os.path.join(BUILDDIR, 'breakpad', 'src', 'tools', 'windows', 'Release', 'dump_syms.exe')\n        pdb_path = os.path.join(self.outdir, 'seaf-daemon.pdb')\n        sym_path = os.path.join(self.outdir, 'seaf-daemon.sym')\n\n        cmd = '%s %s > %s' %(dump_syms_path, pdb_path, sym_path)\n        if run(cmd, BUILDDIR) != 0:\n            error('error when running command:\\n\\t%s\\n' % cmd)\n\n\nclass SeafileGUI(Project):\n    name = 'seafile-client'\n    target_name = 'seafile-applet.exe'\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            'devenv %s/seafile-client.sln /Rebuild \"Release|x64\"' %(self.projdir) ,\n        ]\n\n    def before_build(self):\n        pass\n\n    def after_build(self):\n        # Copy WinSparkle.dll to SLNOUTPUTDIR\n        must_copy(os.path.join(self.projdir, 'third_party', 'WinSparkle-0.5.3', 'x64', 'Release', 'WinSparkle.dll'), SLNOUTPUTDIR)\n\n        # Copy dll to SLNOUTPUTDIR\n        dlls = glob.glob(os.path.join(self.outdir, '*.dll'))\n        for dll in dlls:\n            if not os.path.exists(dll) :\n                must_copy(dll, SLNOUTPUTDIR)\n\n        # Copy openssl lib to package dir\n        # openssl_lib_path_list = glob.glob(os.path.join(OPENSSL_DIR, '*.dll'))\n        # for lib in openssl_lib_path_list :\n        #   must_copy(lib, SLNOUTPUTDIR)\n\n        # Copy seafile-applet.exe to SLNOUTPUTDIR\n        must_copy(os.path.join(self.outdir, self.target_name), SLNOUTPUTDIR)\n\n        # Use windeloyqt.exe to copy qt resource file and lib\n        windeployqt_path = os.path.join(QT_DIR, 'bin', 'windeployqt.exe')\n        seafile_exe_path = os.path.join(SLNOUTPUTDIR, self.target_name)\n        cmd = \"%s --no-compiler-runtime %s\" % (windeployqt_path, seafile_exe_path)\n        if run(cmd, cwd = SLNOUTPUTDIR) != 0:\n            error('error when running command:\\n\\t%s\\n' % cmd)\n\n        # Sign seafile exe\n        need_sign_exe = [\n            os.path.join(SLNOUTPUTDIR, self.target_name),\n            os.path.join(SLNOUTPUTDIR, 'seaf-daemon.exe')\n        ]\n\n        for fn in need_sign_exe:\n            do_sign(fn)\n\n        # Generate breakpad symbol\n        dump_syms_path = os.path.join(BUILDDIR, 'breakpad', 'src', 'tools', 'windows', 'Release', 'dump_syms.exe')\n        pdb_path = os.path.join(self.outdir, 'seafile-applet.pdb')\n        sym_path = os.path.join(self.outdir, 'seafile-applet.sym')\n\n        cmd = '%s %s > %s' %(dump_syms_path, pdb_path, sym_path)\n        if run(cmd, BUILDDIR) != 0:\n            error('error when running command:\\n\\t%s\\n' % cmd)\n\n\nclass SeafileShellExt(Project):\n    name = 'seafile-shell-ext'\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            'devenv %s/windows/extensions/seafile_ext.sln /Rebuild \"Release|x64\"' %(self.projdir),\n            'devenv %s/shellext-fix/shellext-fix.sln /Rebuild \"Release|x64\"' %(self.projdir),\n        ]\n    def before_build(self):\n        pass\n\n    def after_build(self):\n        # Copy shellext-fix.exe to SLNOUTPUTDIR\n        shellext_fix_target = os.path.join(self.projdir, 'shellext-fix', 'x64\\\\Release', 'shellext-fix.exe')\n        must_copy(shellext_fix_target, SLNOUTPUTDIR)\n\n        # Sign seafileext-fix.exe\n        do_sign(os.path.join(SLNOUTPUTDIR, 'shellext-fix.exe'))\n\n\ndef wix_build(language):\n    \"\"\" Use wix command to build windows msi install package\"\"\"\n\n    CULTURE = 'zh-cn'\n    LANG_FILE = 'zh_CN.wxl'\n    TARGET = 'seafile.msi'\n\n    if language == 'en':\n        CULTURE = 'en-us'\n        LANG_FILE = 'en_US.wxl'\n        TARGET = 'seafile-en.msi'\n\n    CC = '%s/candle.exe' %(WIX_BIN)\n    LD = '%s/light.exe' %(WIX_BIN)\n\n    CFLAGS = '-arch \"x64\" -nologo -ext WixUIExtension -ext WixUtilExtension'\n    LDFLAGS  = '-nologo -spdb -ext  WixUIExtension -ext WixUtilExtension' + \\\n                 ' -loc %s -cultures:%s -sice:ICE80' % (LANG_FILE, CULTURE)\n\n\n    generator_fragment_cmd = \"%s/Paraffin.exe -dir bin -alias bin -gn group_bin \\\n        fragment.wxs\" %(WIX_BIN)\n    if run(generator_fragment_cmd, cwd=WIX_PACKAGE_DIR) != 0:\n        error('error wherunning command:\\n\\t%s\\n' % generator_fragment_cmd)\n\n    edit_fragment_wxs()\n\n    build_command = [\n        '%s %s WixUI_InstallDir_NoLicense.wxs -o WixUI_InstallDir_NoLicense.wixobj'  % (CC, CFLAGS),\n        '%s %s MyInstallDirDlg.wxs -o MyInstallDirDlg.wixobj' % (CC, CFLAGS),\n        '%s %s fragment.wxs -o fragment.wixobj' % (CC, CFLAGS),\n        '%s %s shell.wxs -o shell.wixobj' % (CC, CFLAGS),\n        '%s %s seafile.wxs -o seafile.wixobj' % (CC, CFLAGS),\n        '%s %s WixUI_InstallDir_NoLicense.wixobj MyInstallDirDlg.wixobj fragment.wixobj shell.wixobj seafile.wixobj -o %s' %(LD, LDFLAGS, TARGET),\n    ]\n    for cmd in build_command:\n        if run(cmd, cwd=WIX_PACKAGE_DIR) != 0:\n            error('error when running command:\\n\\t%s\\n' % cmd)\n\n    # Digitally sign the msi package\n    msi_path = os.path.join(WIX_PACKAGE_DIR, TARGET)\n    signinstaller(msi_path, language)\n\n\ndef prepare_msi():\n    if os.path.exists(WIX_PACKAGE_DIR) :\n       must_rmtree(WIX_PACKAGE_DIR)\n\n\n    msi_dir = os.path.join(Seafile().projdir, 'msi')\n\n    # These files are in seafile-shell-ext because they're shared between seafile/seafile\n    ext_wxi = os.path.join(SeafileShellExt().projdir, 'msi', 'ext.wxi')\n    must_copy(ext_wxi, msi_dir)\n    shell_wxs = os.path.join(SeafileShellExt().projdir, 'msi', 'shell.wxs')\n    must_copy(shell_wxs, msi_dir)\n\n    # Copy msi to wix package directory\n    if not os.path.exists(WIX_PACKAGE_DIR):\n        must_copytree(msi_dir, WIX_PACKAGE_DIR)\n\n\n    wix_pack_bin = os.path.join(WIX_PACKAGE_DIR, 'bin')\n    if os.path.exists(wix_pack_bin) :\n        os.rmdir(wix_pack_bin)\n\n    # Copy vc runtimer merge module\n    must_copy(os.path.join(OPENSSL_DIR, 'Microsoft_VC142_CRT_x64.msm'), SLNOUTPUTDIR)\n\n    must_copytree(SLNOUTPUTDIR, wix_pack_bin)\n\n    # Copy seafile_ext64.dll  to WIX_PACKAGE_DIR/custom\n    seafile_extension_target_path = os.path.join(SeafileShellExt().projdir, 'windows', 'extensions', 'x64\\\\Release', 'seafile_ext64.dll')\n    seafile_extension_dst_path = os.path.join(WIX_PACKAGE_DIR, 'custom')\n    must_copy(seafile_extension_target_path, seafile_extension_dst_path)\n\n    # Copy seafile_custom64.dll to WIX_PACKAGE_DIR/custom\n    seafile_custom_target_path = os.path.join(Seafile().projdir, 'msi\\\\custom\\\\x64\\\\Release', 'seafile_custom64.dll')\n    must_copy(seafile_custom_target_path, seafile_extension_dst_path)\n\n\ndef edit_fragment_wxs():\n    '''In the main wxs file(seafile.wxs) we need to reference to the id of\n    seafile-applet.exe, which is listed in fragment.wxs. Since fragments.wxs is\n    auto generated, the id is sequentially generated, so we need to change the\n    id of seafile-applet.exe manually.\n\n    '''\n    file_path = os.path.join(WIX_PACKAGE_DIR, 'fragment.wxs')\n    new_lines = []\n    with open(file_path, 'r', encoding='utf-8') as fp:\n        for line in fp:\n            if 'seafile-applet.exe' in line:\n                # change the id of 'seafile-applet.exe' to 'seafileapplet.exe'\n                new_line = re.sub(r'file_[\\w]+', 'seafileapplet.exe', line)\n                new_lines.append(new_line)\n            else:\n                new_lines.append(line)\n\n    content = '\\r\\n'.join(new_lines)\n    with open(file_path, 'w', encoding='utf-8') as fp:\n        fp.write(content)\n\ndef signinstaller(msi_file_path, language):\n    global version\n    msi_name = ''\n    if language != 'cn':\n        msi_name = 'seafile-{}-{}.msi' .format(version, language)\n    else:\n        msi_name = 'seafile-{}.msi' .format(version)\n    do_sign(msi_file_path, msi_name)\n    must_rename(msi_file_path, os.path.join(WIX_PACKAGE_DIR, msi_name))\n\n\ndef build_and_sign_msi():\n    prepare_msi()\n\n    # Build seafile msi english and chinese version\n    wix_build('en')\n    wix_build('cn')\n\n\ndef main():\n    check_cmd_para()\n\n    # Construct seafile build folder\n    initworkspace()\n\n    libsearpc = Libsearpc()\n    seafile = Seafile()\n    seafile_gui = SeafileGUI()\n    seafile_shell_ext = SeafileShellExt()\n\n    # Build Seadrive project\n    libsearpc.build()\n    seafile.build()\n    seafile_gui.build()\n    seafile_shell_ext.build()\n\n    # Build seafile msi installer use wix\n    build_and_sign_msi()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/build/build-msi-vs.py",
    "content": "#!/usr/bin/env python\n# coding: UTF-8\n\nimport sys\n\n####################\n### Requires Python 3+\n####################\nif sys.version_info[0] == 2:\n    print('Python 2 not be supported, require Python 3. Quit now.')\n    sys.exit(1)\n\nimport os\nimport subprocess\nimport shutil\nimport time\nimport glob\nimport re\n\nBUILDDIR = os.path.join(os.getcwd(), \"..\\\\..\\\\..\\\\\")\n\n##################\n### Configure\n##################\n# The seafile package project directory\n# Directory where the signing certificate is located\nCERTFILE = \"C:/certs/seafile.pfx\"\n\n# Qt library directory\nQT_DIR = \"C:/Qt/6.5.2/msvc2019_64\"\n\n# Wix install directory\nWIX_BIN = \"C:/wix/bin\"\n\n# Openssl lib directory\nOPENSSL_DIR = \"C:/packagelib\"\n\n#####################\n# Work path : seafile library and program tmp directory\n# and wix build path\n#####################\n# Package directory\nSLNOUTPUTDIR = os.path.join(BUILDDIR, \"pack\")\n\n# Wix package directory\nWIX_PACKAGE_DIR = os.path.join(BUILDDIR, \"wix_pack\")\n\n####################\n### Global variables\n###################\nRETRY_COUNT = 3\nerror_exit = False\nversion = ''\n\n####################\n### Common helper functions\n###################\n\ndef highlight(content, is_error=False):\n    '''Add ANSI color to content to get it highlighted on terminal'''\n    dummy = is_error\n    return content\n    # if is_error:\n    #     return '\\x1b[1;31m%s\\x1b[m' % content\n    # else:\n    #     return '\\x1b[1;32m%s\\x1b[m' % content\n\ndef info(msg):\n    print(highlight('[INFO] ') + msg)\n\ndef error(msg=None, usage=None):\n    if msg:\n        print(highlight('[ERROR] ') + msg)\n    if usage:\n        print(usage)\n    sys.exit(1)\n\ndef find_in_path(prog):\n    '''Test whether prog exists in system path'''\n    dirs = os.environ['PATH'].split(';')\n    for d in dirs:\n        if d == '':\n            continue\n        path = os.path.join(d, prog)\n        if os.path.exists(path):\n            return path\n\n    return None\n\ndef run(cmdline, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Specify a command line string'''\n    info('running %s, cwd=%s' % (cmdline, cwd if cwd else os.getcwd()))\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(cmdline,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env,\n                                shell=True)\n        ret = proc.wait()\n        if ret != 0:\n            global error_exit\n            error_exit = True\n        return ret\n\ndef must_copy(src, dst):\n    '''Copy src to dst, exit on failure'''\n    try:\n        shutil.copy(src, dst)\n    except Exception as e:\n        error('failed to copy %s to %s: %s' % (src, dst, e))\n\ndef must_copytree(src, dst):\n    '''Copy dir src to dst, exit on failure'''\n    try:\n        shutil.copytree(src, dst)\n    except Exception as e:\n        error('failed to copy dir %s to %s: %s' % (src, dst, e))\n\ndef must_rmtree(path):\n    '''Recurse rm dir, exit on failure'''\n    try:\n        shutil.rmtree(path)\n    except Exception as e:\n        error('failed rm dir %s : %s' % (path, e))\n\ndef must_rename(src, dst):\n    '''Rename src to dst, exit on failure'''\n    try:\n        os.rename(src,dst)\n\n    except Exception as e:\n        error('failed to rename %s to %s: %s' % (src, dst, e))\n\ndef must_mkdir(path):\n    '''Creating directories recursively, exit on failure'''\n    if os.path.exists(path):\n        return\n    try:\n        os.makedirs(path)\n    except OSError as e:\n        error('failed to create directory %s:%s' % (path, e))\n\ndef dump_env():\n    print('Dumping environment variables:')\n    for k, v in os.environ.items():\n        print('%s: %s' % (k, v)\n)\n\ndef do_sign(certfile, fn, desc=None):\n    info('signing file {} using cert \"{}\"'.format(fn, certfile))\n\n    if desc:\n        desc_flags = '-d \"{}\"'.format(desc)\n    else:\n        desc_flags = ''\n\n    signcmd = 'signtool.exe sign -fd sha256 -t http://timestamp.comodoca.com -f {} {} {}'.format(certfile, desc_flags, fn)\n    i = 0\n    while i < RETRY_COUNT:\n        time.sleep(30)\n        ret = run(signcmd, cwd=os.path.dirname(fn))\n        if ret == 0:\n            break\n        i = i + 1\n        if i == RETRY_COUNT:\n            error('Failed to sign file \"{}\"'.format(fn))\n\ndef initworkspace():\n    # Clear build file cache\n    if os.path.exists(SLNOUTPUTDIR) :\n        must_rmtree(SLNOUTPUTDIR)\n\n    # Create a package directory\n    must_mkdir(SLNOUTPUTDIR)\n\ndef check_project_version(version):\n    '''A valid version must be like 1.2.2, 1.3'''\n    if not re.match(r'^[0-9]+(\\.[0-9]+)+$', version):\n        error('%s is not a valid version' % version, usage=\"vs-build.py 2.0.0\")\n\ndef check_cmd_para():\n    args = sys.argv\n    if len(args) != 2:\n        error('The number of parameters is incorrect', usage=\"vs-build.py 2.0.0\")\n    global version\n    version = args[1]\n    check_project_version(version)\n\n\nclass Project(object):\n    '''Base class for a project'''\n    # Probject name, i.e. libseaprc/seafile/seafile-gui\n    name = ''\n\n    # A list of shell commands to configure/build the project\n    build_commands = []\n\n    def __init__(self):\n        self.prefix = BUILDDIR\n        self.projdir = os.path.join(self.prefix, self.name)\n        self.outdir = os.path.join(self.projdir, 'x64', 'Release')\n\n    def before_build(self):\n        '''Hook method to do project-specific stuff before running build commands'''\n        pass\n\n    def build(self):\n        '''Build the source'''\n        self.before_build()\n        info('Building %s' % self.name)\n        # dump_env()\n        for cmd in self.build_commands:\n            if run(cmd, cwd=self.projdir) != 0:\n                error('error when running command:\\n\\t%s\\n' % cmd)\n        self.after_build()\n\n    def after_build(self):\n        pass\n\n\nclass Libsearpc(Project):\n    name = 'libsearpc'\n\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            'devenv  \"%s/libsearpc.sln\" /Rebuild \"Release|x64\"' %(self.projdir),\n        ]\n\n    def after_build(self):\n        libsearpc_path = os.path.join(self.outdir, 'libsearpc.dll')\n        must_copy(libsearpc_path, SLNOUTPUTDIR)\n\n\nclass Seafile(Project):\n    name = 'seafile'\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            'devenv %s/seafile.sln /Rebuild \"Release|x64\"' %(self.projdir),\n            'devenv %s/msi/custom/seafile_custom.sln /Rebuild \"Release|x64\"' %(self.projdir),\n        ]\n\n    def before_build(self):\n        pass\n\n    def after_build(self):\n\n        # Copy seafile dll file to SLNOUTPUTDIR\n        dlls = glob.glob(os.path.join(self.outdir, '*.dll'))\n        for dll in dlls:\n            must_copy(dll, SLNOUTPUTDIR)\n\n        # Copy seafile.exe file to SLNOUTPUTDIR\n        must_copy(os.path.join(self.outdir, 'seaf-daemon.exe'), SLNOUTPUTDIR)\n\n        # Generate breakpad symbol\n        dump_syms_path = os.path.join(BUILDDIR, 'breakpad', 'src', 'tools', 'windows', 'Release', 'dump_syms.exe')\n        pdb_path = os.path.join(self.outdir, 'seaf-daemon.pdb')\n        sym_path = os.path.join(self.outdir, 'seaf-daemon.sym')\n\n        cmd = '%s %s > %s' %(dump_syms_path, pdb_path, sym_path)\n        if run(cmd, BUILDDIR) != 0:\n            error('error when running command:\\n\\t%s\\n' % cmd)\n\n\nclass SeafileGUI(Project):\n    name = 'seafile-client'\n    target_name = 'seafile-applet.exe'\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            'devenv %s/seafile-client.sln /Rebuild \"Release|x64\"' %(self.projdir) ,\n        ]\n\n    def before_build(self):\n        pass\n\n    def after_build(self):\n        # Copy WinSparkle.dll to SLNOUTPUTDIR\n        must_copy(os.path.join(self.projdir, 'third_party', 'WinSparkle-0.5.3', 'x64', 'Release', 'WinSparkle.dll'), SLNOUTPUTDIR)\n\n        # Copy dll to SLNOUTPUTDIR\n        dlls = glob.glob(os.path.join(self.outdir, '*.dll'))\n        for dll in dlls:\n            if not os.path.exists(dll) :\n                must_copy(dll, SLNOUTPUTDIR)\n\n        # Copy openssl lib to package dir\n        # openssl_lib_path_list = glob.glob(os.path.join(OPENSSL_DIR, '*.dll'))\n        # for lib in openssl_lib_path_list :\n        #   must_copy(lib, SLNOUTPUTDIR)\n\n        # Copy seafile-applet.exe to SLNOUTPUTDIR\n        must_copy(os.path.join(self.outdir, self.target_name), SLNOUTPUTDIR)\n\n        # Use windeloyqt.exe to copy qt resource file and lib\n        windeployqt_path = os.path.join(QT_DIR, 'bin', 'windeployqt.exe')\n        seafile_exe_path = os.path.join(SLNOUTPUTDIR, self.target_name)\n        cmd = \"%s --no-compiler-runtime %s\" % (windeployqt_path, seafile_exe_path)\n        if run(cmd, cwd = SLNOUTPUTDIR) != 0:\n            error('error when running command:\\n\\t%s\\n' % cmd)\n\n        # Sign seafile exe\n        need_sign_exe = [\n            os.path.join(SLNOUTPUTDIR, self.target_name),\n            os.path.join(SLNOUTPUTDIR, 'seaf-daemon.exe')\n        ]\n\n        for fn in need_sign_exe:\n            do_sign(CERTFILE, fn)\n\n        # Generate breakpad symbol\n        dump_syms_path = os.path.join(BUILDDIR, 'breakpad', 'src', 'tools', 'windows', 'Release', 'dump_syms.exe')\n        pdb_path = os.path.join(self.outdir, 'seafile-applet.pdb')\n        sym_path = os.path.join(self.outdir, 'seafile-applet.sym')\n\n        cmd = '%s %s > %s' %(dump_syms_path, pdb_path, sym_path)\n        if run(cmd, BUILDDIR) != 0:\n            error('error when running command:\\n\\t%s\\n' % cmd)\n\n\nclass SeafileShellExt(Project):\n    name = 'seafile-shell-ext'\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            'devenv %s/windows/extensions/seafile_ext.sln /Rebuild \"Release|x64\"' %(self.projdir),\n            'devenv %s/shellext-fix/shellext-fix.sln /Rebuild \"Release|x64\"' %(self.projdir),\n        ]\n    def before_build(self):\n        pass\n\n    def after_build(self):\n        # Copy shellext-fix.exe to SLNOUTPUTDIR\n        shellext_fix_target = os.path.join(self.projdir, 'shellext-fix', 'x64\\\\Release', 'shellext-fix.exe')\n        must_copy(shellext_fix_target, SLNOUTPUTDIR)\n\n        # Sign seafileext-fix.exe\n        do_sign(CERTFILE, os.path.join(SLNOUTPUTDIR, 'shellext-fix.exe'))\n\n\ndef wix_build(language):\n    \"\"\" Use wix command to build windows msi install package\"\"\"\n\n    CULTURE = 'zh-cn'\n    LANG_FILE = 'zh_CN.wxl'\n    TARGET = 'seafile.msi'\n\n    if language == 'en':\n        CULTURE = 'en-us'\n        LANG_FILE = 'en_US.wxl'\n        TARGET = 'seafile-en.msi'\n\n    CC = '%s/candle.exe' %(WIX_BIN)\n    LD = '%s/light.exe' %(WIX_BIN)\n\n    CFLAGS = '-arch \"x64\" -nologo -ext WixUIExtension -ext WixUtilExtension'\n    LDFLAGS  = '-nologo -spdb -ext  WixUIExtension -ext WixUtilExtension' + \\\n                 ' -loc %s -cultures:%s -sice:ICE80' % (LANG_FILE, CULTURE)\n\n\n    generator_fragment_cmd = \"%s/Paraffin.exe -dir bin -alias bin -gn group_bin \\\n        fragment.wxs\" %(WIX_BIN)\n    if run(generator_fragment_cmd, cwd=WIX_PACKAGE_DIR) != 0:\n        error('error wherunning command:\\n\\t%s\\n' % generator_fragment_cmd)\n\n    edit_fragment_wxs()\n\n    build_command = [\n        '%s %s WixUI_InstallDir_NoLicense.wxs -o WixUI_InstallDir_NoLicense.wixobj'  % (CC, CFLAGS),\n        '%s %s MyInstallDirDlg.wxs -o MyInstallDirDlg.wixobj' % (CC, CFLAGS),\n        '%s %s fragment.wxs -o fragment.wixobj' % (CC, CFLAGS),\n        '%s %s shell.wxs -o shell.wixobj' % (CC, CFLAGS),\n        '%s %s seafile.wxs -o seafile.wixobj' % (CC, CFLAGS),\n        '%s %s WixUI_InstallDir_NoLicense.wixobj MyInstallDirDlg.wixobj fragment.wixobj shell.wixobj seafile.wixobj -o %s' %(LD, LDFLAGS, TARGET),\n    ]\n    for cmd in build_command:\n        if run(cmd, cwd=WIX_PACKAGE_DIR) != 0:\n            error('error when running command:\\n\\t%s\\n' % cmd)\n\n    # Digitally sign the msi package\n    msi_path = os.path.join(WIX_PACKAGE_DIR, TARGET)\n    signinstaller(msi_path, language)\n\n\ndef prepare_msi():\n    if os.path.exists(WIX_PACKAGE_DIR) :\n       must_rmtree(WIX_PACKAGE_DIR)\n\n\n    msi_dir = os.path.join(Seafile().projdir, 'msi')\n\n    # These files are in seafile-shell-ext because they're shared between seafile/seafile\n    ext_wxi = os.path.join(SeafileShellExt().projdir, 'msi', 'ext.wxi')\n    must_copy(ext_wxi, msi_dir)\n    shell_wxs = os.path.join(SeafileShellExt().projdir, 'msi', 'shell.wxs')\n    must_copy(shell_wxs, msi_dir)\n\n    # Copy msi to wix package directory\n    if not os.path.exists(WIX_PACKAGE_DIR):\n        must_copytree(msi_dir, WIX_PACKAGE_DIR)\n\n\n    wix_pack_bin = os.path.join(WIX_PACKAGE_DIR, 'bin')\n    if os.path.exists(wix_pack_bin) :\n        os.rmdir(wix_pack_bin)\n\n    # Copy vc runtimer merge module\n    must_copy(os.path.join(OPENSSL_DIR, 'Microsoft_VC142_CRT_x64.msm'), SLNOUTPUTDIR)\n\n    must_copytree(SLNOUTPUTDIR, wix_pack_bin)\n\n    # Copy seafile_ext64.dll  to WIX_PACKAGE_DIR/custom\n    seafile_extension_target_path = os.path.join(SeafileShellExt().projdir, 'windows', 'extensions', 'x64\\\\Release', 'seafile_ext64.dll')\n    seafile_extension_dst_path = os.path.join(WIX_PACKAGE_DIR, 'custom')\n    must_copy(seafile_extension_target_path, seafile_extension_dst_path)\n\n    # Copy seafile_custom64.dll to WIX_PACKAGE_DIR/custom\n    seafile_custom_target_path = os.path.join(Seafile().projdir, 'msi\\\\custom\\\\x64\\\\Release', 'seafile_custom64.dll')\n    must_copy(seafile_custom_target_path, seafile_extension_dst_path)\n\n\ndef edit_fragment_wxs():\n    '''In the main wxs file(seafile.wxs) we need to reference to the id of\n    seafile-applet.exe, which is listed in fragment.wxs. Since fragments.wxs is\n    auto generated, the id is sequentially generated, so we need to change the\n    id of seafile-applet.exe manually.\n\n    '''\n    file_path = os.path.join(WIX_PACKAGE_DIR, 'fragment.wxs')\n    new_lines = []\n    with open(file_path, 'r', encoding='utf-8') as fp:\n        for line in fp:\n            if 'seafile-applet.exe' in line:\n                # change the id of 'seafile-applet.exe' to 'seafileapplet.exe'\n                new_line = re.sub(r'file_[\\w]+', 'seafileapplet.exe', line)\n                new_lines.append(new_line)\n            else:\n                new_lines.append(line)\n\n    content = '\\r\\n'.join(new_lines)\n    with open(file_path, 'w', encoding='utf-8') as fp:\n        fp.write(content)\n\ndef signinstaller(msi_file_path, language):\n    global version\n    msi_name = ''\n    if language != 'cn':\n        msi_name = 'seafile-{}-{}.msi' .format(version, language)\n    else:\n        msi_name = 'seafile-{}.msi' .format(version)\n    do_sign(CERTFILE, msi_file_path, msi_name)\n    must_rename(msi_file_path, os.path.join(WIX_PACKAGE_DIR, msi_name))\n\n\ndef build_and_sign_msi():\n    prepare_msi()\n\n    # Build seafile msi english and chinese version\n    wix_build('en')\n    wix_build('cn')\n\n\ndef main():\n    check_cmd_para()\n\n    # Construct seafile build folder\n    initworkspace()\n\n    libsearpc = Libsearpc()\n    seafile = Seafile()\n    seafile_gui = SeafileGUI()\n    seafile_shell_ext = SeafileShellExt()\n\n    # Build Seadrive project\n    libsearpc.build()\n    seafile.build()\n    seafile_gui.build()\n    seafile_shell_ext.build()\n\n    # Build seafile msi installer use wix\n    build_and_sign_msi()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/build/build-msi.py",
    "content": "#!/usr/bin/env python\n# coding: UTF-8\n\n'''This scirpt builds the seafile windows msi installer.\n\nSome notes:\n\n1. The working directory is always the 'builddir'. 'os.chdir' is only called\nto change to the 'builddir'. We make use of the 'cwd' argument in\n'subprocess.Popen' to run a command in a specific directory.\n\n2. When invoking commands like 'tar', we must convert the path to posix path with the function to_mingw_path. E.g., 'c:\\\\seafile' should be converted to '/c/seafile'.\n\n'''\n\nimport sys\n\n####################\n### Requires Python 2.6+\n####################\nif sys.version_info[0] == 3:\n    print 'Python 3 not supported yet. Quit now.'\n    sys.exit(1)\nif sys.version_info[1] < 6:\n    print 'Python 2.6 or above is required. Quit now.'\n    sys.exit(1)\n\nimport multiprocessing\nimport os\nimport glob\nimport shutil\nimport re\nimport subprocess\nimport optparse\nimport atexit\nimport csv\nimport time\n\nerror_exit = False\n####################\n### Global variables\n####################\n\n# command line configuartion\nconf = {}\n\n# The retry times when sign programs\nRETRY_COUNT = 3\n\n# key names in the conf dictionary.\nCONF_VERSION            = 'version'\nCONF_LIBSEARPC_VERSION  = 'libsearpc_version'\nCONF_SEAFILE_VERSION    = 'seafile_version'\nCONF_SEAFILE_CLIENT_VERSION  = 'seafile_client_version'\nCONF_SRCDIR             = 'srcdir'\nCONF_KEEP               = 'keep'\nCONF_BUILDDIR           = 'builddir'\nCONF_OUTPUTDIR          = 'outputdir'\nCONF_DEBUG              = 'debug'\nCONF_ONLY_CHINESE       = 'onlychinese'\nCONF_QT_ROOT            = 'qt_root'\nCONF_EXTRA_LIBS_DIR     = 'extra_libs_dir'\nCONF_QT5                = 'qt5'\nCONF_BRAND              = 'brand'\nCONF_CERTFILE           = 'certfile'\nCONF_NO_STRIP           = 'nostrip'\n\n####################\n### Common helper functions\n####################\ndef to_mingw_path(path):\n    if len(path) < 2 or path[1] != ':' :\n        return path.replace('\\\\', '/')\n\n    drive = path[0]\n    return '/%s%s' % (drive.lower(), path[2:].replace('\\\\', '/'))\n\ndef to_win_path(path):\n    if len(path) < 2 or path[1] == ':' :\n        return path.replace('/', '\\\\')\n\n    drive = path[1]\n    return '%s:%s' % (drive.lower(), path[2:].replace('/', '\\\\'))\n\ndef highlight(content, is_error=False):\n    '''Add ANSI color to content to get it highlighted on terminal'''\n    dummy = is_error\n    return content\n    # if is_error:\n    #     return '\\x1b[1;31m%s\\x1b[m' % content\n    # else:\n    #     return '\\x1b[1;32m%s\\x1b[m' % content\n\ndef info(msg):\n    print highlight('[INFO] ') + msg\n\ndef find_in_path(prog):\n    '''Test whether prog exists in system path'''\n    dirs = os.environ['PATH'].split(';')\n    for d in dirs:\n        if d == '':\n            continue\n        path = os.path.join(d, prog)\n        if os.path.exists(path):\n            return path\n\n    return None\n\ndef prepend_env_value(name, value, seperator=':'):\n    '''prepend a new value to a list'''\n    try:\n        current_value = os.environ[name]\n    except KeyError:\n        current_value = ''\n\n    new_value = value\n    if current_value:\n        new_value += seperator + current_value\n\n    os.environ[name] = new_value\n\ndef error(msg=None, usage=None):\n    if msg:\n        print highlight('[ERROR] ') + msg\n    if usage:\n        print usage\n    sys.exit(1)\n\ndef run_argv(argv, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Run a program and wait it to finish, and return its exit code. The\n    standard output of this program is supressed.\n\n    '''\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(argv,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env)\n        return proc.wait()\n\ndef run(cmdline, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Like run_argv but specify a command line string instead of argv'''\n    info('running %s, cwd=%s' % (cmdline, cwd if cwd else os.getcwd()))\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(cmdline,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env,\n                                shell=True)\n        ret = proc.wait()\n        if 'depend' not in cmdline and ret != 0:\n            global error_exit\n            error_exit = True\n        return ret\n\ndef must_mkdir(path):\n    '''Create a directory, exit on failure'''\n    try:\n        os.makedirs(path)\n    except OSError, e:\n        error('failed to create directory %s:%s' % (path, e))\n\ndef must_copy(src, dst):\n    '''Copy src to dst, exit on failure'''\n    try:\n        shutil.copy(src, dst)\n    except Exception, e:\n        error('failed to copy %s to %s: %s' % (src, dst, e))\n\ndef must_copytree(src, dst):\n    '''Copy dir src to dst, exit on failure'''\n    try:\n        shutil.copytree(src, dst)\n    except Exception, e:\n        error('failed to copy dir %s to %s: %s' % (src, dst, e))\n\ndef must_move(src, dst):\n    '''Move src to dst, exit on failure'''\n    try:\n        shutil.move(src, dst)\n    except Exception, e:\n        error('failed to move %s to %s: %s' % (src, dst, e))\n\nclass Project(object):\n    '''Base class for a project'''\n    # Probject name, i.e. libseaprc/seafile/seahub\n    name = ''\n\n    # A list of shell commands to configure/build the project\n    build_commands = []\n\n    def __init__(self):\n        self.prefix = os.path.join(conf[CONF_BUILDDIR], 'usr')\n        self.version = self.get_version()\n        self.src_tarball = os.path.join(conf[CONF_SRCDIR],\n                            '%s-%s.tar.gz' % (self.name, self.version))\n\n        # project dir, like <builddir>/seafile-1.2.2/\n        self.projdir = os.path.join(conf[CONF_BUILDDIR], '%s-%s' % (self.name, self.version))\n\n    def get_version(self):\n        # libsearpc can have different versions from seafile.\n        raise NotImplementedError\n\n    def get_source_commit_id(self):\n        '''By convetion, we record the commit id of the source code in the\n        file \"<projdir>/latest_commit\"\n\n        '''\n        latest_commit_file = os.path.join(self.projdir, 'latest_commit')\n        with open(latest_commit_file, 'r') as fp:\n            commit_id = fp.read().strip('\\n\\r\\t ')\n\n        return commit_id\n\n    def append_cflags(self, macros):\n        cflags = ' '.join([ '-D%s=%s' % (k, macros[k]) for k in macros ])\n        prepend_env_value('CPPFLAGS',\n                          cflags,\n                          seperator=' ')\n\n    def uncompress(self):\n        '''Uncompress the source from the tarball'''\n        info('Uncompressing %s' % self.name)\n\n        tarball = to_mingw_path(self.src_tarball)\n        if run('tar xf %s' % tarball) != 0:\n            error('failed to uncompress source of %s' % self.name)\n\n    def before_build(self):\n        '''Hook method to do project-specific stuff before running build commands'''\n        pass\n\n    def build(self):\n        '''Build the source'''\n        self.before_build()\n        info('Building %s' % self.name)\n        dump_env()\n        for cmd in self.build_commands:\n            if run(cmd, cwd=self.projdir) != 0:\n                error('error when running command:\\n\\t%s\\n' % cmd)\n\ndef get_make_path():\n    return find_in_path('make.exe')\n\ndef concurrent_make():\n    return '%s -j%s' % (get_make_path(), multiprocessing.cpu_count())\n\nclass Libsearpc(Project):\n    name = 'libsearpc'\n\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            'sh ./configure --prefix=%s --disable-compile-demo' % to_mingw_path(self.prefix),\n            concurrent_make(),\n            '%s install' % get_make_path(),\n        ]\n\n    def get_version(self):\n        return conf[CONF_LIBSEARPC_VERSION]\n\nclass Seafile(Project):\n    name = 'seafile'\n    def __init__(self):\n        Project.__init__(self)\n        enable_breakpad = '--enable-breakpad'\n        self.build_commands = [\n            'sh ./configure %s --prefix=%s' % (enable_breakpad, to_mingw_path(self.prefix)),\n            concurrent_make(),\n            '%s install' % get_make_path(),\n        ]\n\n    def get_version(self):\n        return conf[CONF_SEAFILE_VERSION]\n\n    def before_build(self):\n        macros = {}\n        # SET SEAFILE_SOURCE_COMMIT_ID, so it can be printed in the log\n        macros['SEAFILE_SOURCE_COMMIT_ID'] = '\\\\\"%s\\\\\"' % self.get_source_commit_id()\n        self.append_cflags(macros)\n\nclass SeafileClient(Project):\n    name = 'seafile-client'\n    def __init__(self):\n        Project.__init__(self)\n        ninja = find_in_path('ninja.exe')\n        seafile_prefix = Seafile().prefix\n        generator = 'Ninja' if ninja else 'MSYS Makefiles'\n        build_type = 'Debug' if conf[CONF_DEBUG] else 'Release'\n        flags = {\n            'BUILD_SPARKLE_SUPPORT': 'ON',\n            'USE_QT5': 'ON' if conf[CONF_QT5] else 'OFF',\n            'BUILD_SHIBBOLETH_SUPPORT': 'ON',\n            'CMAKE_BUILD_TYPE': build_type,\n            'CMAKE_INSTALL_PREFIX': to_mingw_path(self.prefix),\n            # ninja invokes cmd.exe which doesn't support msys/mingw path\n            # change the value but don't override CMAKE_EXE_LINKER_FLAGS,\n            # which is in use\n            'CMAKE_EXE_LINKER_FLAGS_%s' % build_type.upper(): '-L%s' % (os.path.join(seafile_prefix, 'lib') if ninja else to_mingw_path(os.path.join(seafile_prefix, 'lib'))),\n        }\n        flags_str = ' '.join(['-D%s=%s' % (k, v) for k, v in flags.iteritems()])\n        make = ninja or concurrent_make()\n        self.build_commands = [\n            'cmake -G \"%s\" %s .' % (generator, flags_str),\n            make,\n            '%s install' % make,\n            \"bash extensions/build.sh\",\n        ]\n\n    def get_version(self):\n        return conf[CONF_SEAFILE_CLIENT_VERSION]\n\n    def before_build(self):\n        shutil.copy(os.path.join(conf[CONF_EXTRA_LIBS_DIR], 'winsparkle.lib'), self.projdir)\n\nclass SeafileShellExt(Project):\n    name = 'seafile-shell-ext'\n    def __init__(self):\n        Project.__init__(self)\n        self.build_commands = [\n            \"bash extensions/build.sh\",\n            \"bash shellext-fix/build.sh\",\n        ]\n\n    def get_version(self):\n        return conf[CONF_SEAFILE_CLIENT_VERSION]\n\ndef check_targz_src(proj, version, srcdir):\n    src_tarball = os.path.join(srcdir, '%s-%s.tar.gz' % (proj, version))\n    if not os.path.exists(src_tarball):\n        error('%s not exists' % src_tarball)\n\ndef validate_args(usage, options):\n    required_args = [\n        CONF_VERSION,\n        CONF_LIBSEARPC_VERSION,\n        CONF_SEAFILE_VERSION,\n        CONF_SEAFILE_CLIENT_VERSION,\n        CONF_SRCDIR,\n        CONF_QT_ROOT,\n        CONF_EXTRA_LIBS_DIR,\n    ]\n\n    # fist check required args\n    for optname in required_args:\n        if getattr(options, optname, None) == None:\n            error('%s must be specified' % optname, usage=usage)\n\n    def get_option(optname):\n        return getattr(options, optname)\n\n    # [ version ]\n    def check_project_version(version):\n        '''A valid version must be like 1.2.2, 1.3'''\n        if not re.match(r'^[0-9]+(\\.[0-9]+)+$', version):\n            error('%s is not a valid version' % version, usage=usage)\n\n    version = get_option(CONF_VERSION)\n    libsearpc_version = get_option(CONF_LIBSEARPC_VERSION)\n    seafile_version = get_option(CONF_SEAFILE_VERSION)\n    seafile_client_version = get_option(CONF_SEAFILE_CLIENT_VERSION)\n    seafile_shell_ext_version = get_option(CONF_SEAFILE_CLIENT_VERSION)\n\n    check_project_version(version)\n    check_project_version(libsearpc_version)\n    check_project_version(seafile_version)\n    check_project_version(seafile_client_version)\n    check_project_version(seafile_shell_ext_version)\n\n    # [ srcdir ]\n    srcdir = to_win_path(get_option(CONF_SRCDIR))\n    check_targz_src('libsearpc', libsearpc_version, srcdir)\n    check_targz_src('seafile', seafile_version, srcdir)\n    check_targz_src('seafile-client', seafile_client_version, srcdir)\n    check_targz_src('seafile-shell-ext', seafile_shell_ext_version, srcdir)\n\n    # [ builddir ]\n    builddir = to_win_path(get_option(CONF_BUILDDIR))\n    if not os.path.exists(builddir):\n        error('%s does not exist' % builddir, usage=usage)\n\n    builddir = os.path.join(builddir, 'seafile-msi-build')\n\n    # [ outputdir ]\n    outputdir = to_win_path(get_option(CONF_OUTPUTDIR))\n    if not os.path.exists(outputdir):\n        error('outputdir %s does not exist' % outputdir, usage=usage)\n\n    # [ keep ]\n    keep = get_option(CONF_KEEP)\n\n    # [ no strip]\n    debug = get_option(CONF_DEBUG)\n\n    # [ no strip]\n    nostrip = get_option(CONF_NO_STRIP)\n\n    # [only chinese]\n    onlychinese = get_option(CONF_ONLY_CHINESE)\n\n    # [ qt root]\n    qt_root = get_option(CONF_QT_ROOT)\n    def check_qt_root(qt_root):\n        if not os.path.exists(os.path.join(qt_root, 'plugins')):\n            error('%s is not a valid qt root' % qt_root)\n    check_qt_root(qt_root)\n\n    # [ sparkle dir]\n    extra_libs_dir = get_option(CONF_EXTRA_LIBS_DIR)\n    def check_extra_libs_dir(extra_libs_dir):\n        for fn in ['winsparkle.lib']:\n            if not os.path.exists(os.path.join(extra_libs_dir, fn)):\n                error('%s is missing in %s' % (fn, extra_libs_dir))\n    check_extra_libs_dir(extra_libs_dir)\n\n    # [qt5]\n    qt5 = get_option(CONF_QT5)\n    brand = get_option(CONF_BRAND)\n    cert = get_option(CONF_CERTFILE)\n    if cert is not None:\n        if not os.path.exists(cert):\n            error('cert file \"{}\" does not exist'.format(cert))\n\n    conf[CONF_VERSION] = version\n    conf[CONF_LIBSEARPC_VERSION] = libsearpc_version\n    conf[CONF_SEAFILE_VERSION] = seafile_version\n    conf[CONF_SEAFILE_CLIENT_VERSION] = seafile_client_version\n\n    conf[CONF_BUILDDIR] = builddir\n    conf[CONF_SRCDIR] = srcdir\n    conf[CONF_OUTPUTDIR] = outputdir\n    conf[CONF_KEEP] = True\n    conf[CONF_DEBUG] = debug or nostrip\n    conf[CONF_NO_STRIP] = debug or nostrip\n    conf[CONF_ONLY_CHINESE] = onlychinese\n    conf[CONF_QT_ROOT] = qt_root\n    conf[CONF_EXTRA_LIBS_DIR] = extra_libs_dir\n    conf[CONF_QT5] = qt5\n    conf[CONF_BRAND] = brand\n    conf[CONF_CERTFILE] = cert\n\n    prepare_builddir(builddir)\n    show_build_info()\n\ndef show_build_info():\n    '''Print all conf information. Confirm before continue.'''\n    info('------------------------------------------')\n    info('Seafile msi installer: BUILD INFO')\n    info('------------------------------------------')\n    info('seafile:                  %s' % conf[CONF_VERSION])\n    info('libsearpc:                %s' % conf[CONF_LIBSEARPC_VERSION])\n    info('seafile:                  %s' % conf[CONF_SEAFILE_VERSION])\n    info('seafile-client:           %s' % conf[CONF_SEAFILE_CLIENT_VERSION])\n    info('qt-root:                  %s' % conf[CONF_QT_ROOT])\n    info('builddir:                 %s' % conf[CONF_BUILDDIR])\n    info('outputdir:                %s' % conf[CONF_OUTPUTDIR])\n    info('source dir:               %s' % conf[CONF_SRCDIR])\n    info('debug:                    %s' % conf[CONF_DEBUG])\n    info('build english version:    %s' % (not conf[CONF_ONLY_CHINESE]))\n    info('clean on exit:            %s' % (not conf[CONF_KEEP]))\n    info('------------------------------------------')\n    info('press any key to continue ')\n    info('------------------------------------------')\n    dummy = raw_input()\n\ndef prepare_builddir(builddir):\n    must_mkdir(builddir)\n\n    if not conf[CONF_KEEP]:\n        def remove_builddir():\n            '''Remove the builddir when exit'''\n            if not error_exit:\n                info('remove builddir before exit')\n                shutil.rmtree(builddir, ignore_errors=True)\n        atexit.register(remove_builddir)\n\n    os.chdir(builddir)\n\ndef parse_args():\n    parser = optparse.OptionParser()\n    def long_opt(opt):\n        return '--' + opt\n\n    parser.add_option(long_opt(CONF_VERSION),\n                      dest=CONF_VERSION,\n                      nargs=1,\n                      help='the version to build. Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_LIBSEARPC_VERSION),\n                      dest=CONF_LIBSEARPC_VERSION,\n                      nargs=1,\n                      help='the version of libsearpc as specified in its \"configure.ac\". Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_SEAFILE_VERSION),\n                      dest=CONF_SEAFILE_VERSION,\n                      nargs=1,\n                      help='the version of seafile as specified in its \"configure.ac\". Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_SEAFILE_CLIENT_VERSION),\n                      dest=CONF_SEAFILE_CLIENT_VERSION,\n                      nargs=1,\n                      help='the version of seafile-client. Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_BUILDDIR),\n                      dest=CONF_BUILDDIR,\n                      nargs=1,\n                      help='the directory to build the source. Defaults to /c',\n                      default='c:\\\\')\n\n    parser.add_option(long_opt(CONF_OUTPUTDIR),\n                      dest=CONF_OUTPUTDIR,\n                      nargs=1,\n                      help='the output directory to put the generated server tarball. Defaults to the current directory.',\n                      default=os.getcwd())\n\n    parser.add_option(long_opt(CONF_SRCDIR),\n                      dest=CONF_SRCDIR,\n                      nargs=1,\n                      help='''Source tarballs must be placed in this directory.''')\n\n    parser.add_option(long_opt(CONF_QT_ROOT),\n                      dest=CONF_QT_ROOT,\n                      nargs=1,\n                      help='''qt root directory.''')\n\n    parser.add_option(long_opt(CONF_EXTRA_LIBS_DIR),\n                      dest=CONF_EXTRA_LIBS_DIR,\n                      nargs=1,\n                      help='''where we can find winsparkle.lib''')\n\n    parser.add_option(long_opt(CONF_KEEP),\n                      dest=CONF_KEEP,\n                      action='store_true',\n                      help='''keep the build directory after the script exits. By default, the script would delete the build directory at exit.''')\n\n    parser.add_option(long_opt(CONF_DEBUG),\n                      dest=CONF_DEBUG,\n                      action='store_true',\n                      help='''compile in debug mode''')\n\n    parser.add_option(long_opt(CONF_ONLY_CHINESE),\n                      dest=CONF_ONLY_CHINESE,\n                      action='store_true',\n                      help='''only build the Chinese version. By default both Chinese and English versions would be built.''')\n\n    parser.add_option(long_opt(CONF_QT5),\n                      dest=CONF_QT5,\n                      action='store_true',\n                      help='''build seafile client with qt5''')\n\n    parser.add_option(long_opt(CONF_BRAND),\n                      dest=CONF_BRAND,\n                      default='seafile',\n                      help='''brand name of the package''')\n\n    parser.add_option(long_opt(CONF_CERTFILE),\n                      nargs=1,\n                      default=None,\n                      dest=CONF_CERTFILE,\n                      help='''The cert for signing the executables and the installer.''')\n\n    parser.add_option(long_opt(CONF_NO_STRIP),\n                      dest=CONF_NO_STRIP,\n                      action='store_true',\n                      help='''do not strip the symbols.''')\n\n    usage = parser.format_help()\n    options, remain = parser.parse_args()\n    if remain:\n        error(usage=usage)\n\n    validate_args(usage, options)\n\ndef setup_build_env():\n    '''Setup environment variables, such as export PATH=$BUILDDDIR/bin:$PATH'''\n    prefix = Seafile().prefix\n    prepend_env_value('CPPFLAGS',\n                     '-I%s' % to_mingw_path(os.path.join(prefix, 'include')),\n                     seperator=' ')\n\n    prepend_env_value('CPPFLAGS',\n                     '-DSEAFILE_CLIENT_VERSION=\\\\\"%s\\\\\"' % conf[CONF_VERSION],\n                     seperator=' ')\n\n    prepend_env_value('CPPFLAGS',\n                      '-g -fno-omit-frame-pointer',\n                      seperator=' ')\n    if conf[CONF_DEBUG]:\n        prepend_env_value('CPPFLAGS', '-O0', seperator=' ')\n\n    prepend_env_value('LDFLAGS',\n                     '-L%s' % to_mingw_path(os.path.join(prefix, 'lib')),\n                     seperator=' ')\n\n    prepend_env_value('PATH',\n                      os.path.join(prefix, 'bin'),\n                      seperator=';')\n\n    prepend_env_value('PKG_CONFIG_PATH',\n                      os.path.join(prefix, 'lib', 'pkgconfig'),\n                      seperator=';')\n                      # to_mingw_path(os.path.join(prefix, 'lib', 'pkgconfig')))\n\n    # specifiy the directory for wix temporary files\n    wix_temp_dir = os.path.join(conf[CONF_BUILDDIR], 'wix-temp')\n    os.environ['WIX_TEMP'] = wix_temp_dir\n\n    must_mkdir(wix_temp_dir)\n\ndef dependency_walk(applet):\n    output = os.path.join(conf[CONF_BUILDDIR], 'depends.csv')\n    cmd = 'depends.exe -c -f 1 -oc %s %s' % (output, applet)\n\n    # See the manual of Dependency walker\n    if run(cmd) > 0x100:\n        error('failed to run dependency walker for %s' % applet)\n\n    if not os.path.exists(output):\n        error('failed to run dependency walker for %s' % applet)\n\n    shared_libs = parse_depends_csv(output)\n    return shared_libs\n\ndef parse_depends_csv(path):\n    '''parse the output of dependency walker'''\n    libs = set()\n    our_libs = ['libsearpc', 'libseafile']\n    def should_ignore_lib(lib):\n        lib = lib.lower()\n        if not os.path.exists(lib):\n            return True\n\n        if lib.startswith('c:\\\\windows'):\n            return True\n\n        if lib.endswith('exe'):\n            return True\n\n        for name in our_libs:\n            if name in lib:\n                return True\n\n        return False\n\n    with open(path, 'r') as fp:\n        reader = csv.reader(fp)\n        for row in reader:\n            if len(row) < 2:\n                continue\n            lib = row[1]\n            if not should_ignore_lib(lib):\n                libs.add(lib)\n\n    return set(libs)\n\ndef copy_shared_libs(exes):\n    '''Copy shared libs need by seafile-applet.exe, such as libsearpc,\n    libseafile, etc. First we use Dependency walker to analyse\n    seafile-applet.exe, and get an output file in csv format. Then we parse\n    the csv file to get the list of shared libs.\n\n    '''\n\n    shared_libs = set()\n    for exectuable in exes:\n        shared_libs.update(dependency_walk(exectuable))\n\n    pack_bin_dir = os.path.join(conf[CONF_BUILDDIR], 'pack', 'bin')\n    for lib in shared_libs:\n        must_copy(lib, pack_bin_dir)\n\n    if not any([os.path.basename(lib).lower().startswith('libssl') for lib in shared_libs]):\n        ssleay32 = find_in_path('ssleay32.dll')\n        must_copy(ssleay32, pack_bin_dir)\n\ndef copy_dll_exe():\n    prefix = Seafile().prefix\n    destdir = os.path.join(conf[CONF_BUILDDIR], 'pack', 'bin')\n\n    filelist = [\n        os.path.join(prefix, 'bin', 'libsearpc-1.dll'),\n        os.path.join(prefix, 'bin', 'libseafile-0.dll'),\n        os.path.join(prefix, 'bin', 'seaf-daemon.exe'),\n        os.path.join(SeafileClient().projdir, 'seafile-applet.exe'),\n        os.path.join(SeafileShellExt().projdir, 'shellext-fix', 'shellext-fix.exe'),\n    ]\n\n    for name in filelist:\n        must_copy(name, destdir)\n\n    extdlls = [\n        os.path.join(SeafileShellExt().projdir, 'extensions', 'lib', 'seafile_ext.dll'),\n        os.path.join(SeafileShellExt().projdir, 'extensions', 'lib', 'seafile_ext64.dll'),\n    ]\n\n    customdir = os.path.join(conf[CONF_BUILDDIR], 'pack', 'custom')\n    for dll in extdlls:\n        must_copy(dll, customdir)\n\n    copy_shared_libs([ f for f in filelist if f.endswith('.exe') ])\n    copy_qt_plugins_imageformats()\n    copy_qt_plugins_platforms()\n    copy_qt_translations()\n\ndef copy_qt_plugins_imageformats():\n    destdir = os.path.join(conf[CONF_BUILDDIR], 'pack', 'bin', 'imageformats')\n    must_mkdir(destdir)\n\n    qt_plugins_srcdir = os.path.join(conf[CONF_QT_ROOT], 'plugins', 'imageformats')\n\n    src = os.path.join(qt_plugins_srcdir, 'qico4.dll')\n    if conf[CONF_QT5]:\n        src = os.path.join(qt_plugins_srcdir, 'qico.dll')\n    must_copy(src, destdir)\n\n    src = os.path.join(qt_plugins_srcdir, 'qgif4.dll')\n    if conf[CONF_QT5]:\n        src = os.path.join(qt_plugins_srcdir, 'qgif.dll')\n    must_copy(src, destdir)\n\n    src = os.path.join(qt_plugins_srcdir, 'qjpeg.dll')\n    if conf[CONF_QT5]:\n        src = os.path.join(qt_plugins_srcdir, 'qjpeg.dll')\n    must_copy(src, destdir)\n\ndef copy_qt_plugins_platforms():\n    if not conf[CONF_QT5]:\n        return\n\n    destdir = os.path.join(conf[CONF_BUILDDIR], 'pack', 'bin', 'platforms')\n    must_mkdir(destdir)\n\n    qt_plugins_srcdir = os.path.join(conf[CONF_QT_ROOT], 'plugins', 'platforms')\n\n    src = os.path.join(qt_plugins_srcdir, 'qwindows.dll')\n    must_copy(src, destdir)\n\n    src = os.path.join(qt_plugins_srcdir, 'qminimal.dll')\n    must_copy(src, destdir)\n\ndef copy_qt_translations():\n    destdir = os.path.join(conf[CONF_BUILDDIR], 'pack', 'bin')\n\n    qt_translation_dir = os.path.join(conf[CONF_QT_ROOT], 'translations')\n\n    i18n_dir = os.path.join(SeafileClient().projdir, 'i18n')\n    qm_pattern = os.path.join(i18n_dir, 'seafile_*.qm')\n\n    qt_qms = set()\n    def add_lang(lang):\n        if not lang:\n            return\n        qt_qm = os.path.join(qt_translation_dir, 'qt_%s.qm' % lang)\n        if os.path.exists(qt_qm):\n            qt_qms.add(qt_qm)\n        elif '_' in lang:\n            add_lang(lang[:lang.index('_')])\n\n    for fn in glob.glob(qm_pattern):\n        name = os.path.basename(fn)\n        m = re.match(r'seafile_(.*)\\.qm', name)\n        lang = m.group(1)\n        add_lang(lang)\n\n    for src in qt_qms:\n        must_copy(src, destdir)\n\ndef prepare_msi():\n    pack_dir = os.path.join(conf[CONF_BUILDDIR], 'pack')\n\n    msi_dir = os.path.join(Seafile().projdir, 'msi')\n\n    # These files are in seafile-shell-ext because they're shared between seafile/seadrive\n    ext_wxi = os.path.join(SeafileShellExt().projdir, 'msi', 'ext.wxi')\n    must_copy(ext_wxi, msi_dir)\n    shell_wxs = os.path.join(SeafileShellExt().projdir, 'msi', 'shell.wxs')\n    must_copy(shell_wxs, msi_dir)\n\n    must_copytree(msi_dir, pack_dir)\n    must_mkdir(os.path.join(pack_dir, 'bin'))\n\n    if run('make', cwd=os.path.join(pack_dir, 'custom')) != 0:\n        error('Error when compiling seafile msi custom dlls')\n\n    copy_dll_exe()\n\ndef sign_executables():\n    certfile = conf.get(CONF_CERTFILE)\n    if certfile is None:\n        info('exectuable signing is skipped since no cert is provided.')\n        return\n\n    pack_dir = os.path.join(conf[CONF_BUILDDIR], 'pack')\n    exectuables = glob.glob(os.path.join(pack_dir, 'bin', '*.exe'))\n    for exe in exectuables:\n        do_sign(certfile, exe)\n\ndef sign_installers():\n    certfile = conf.get(CONF_CERTFILE)\n    if certfile is None:\n        info('msi signing is skipped since no cert is provided.')\n        return\n\n    pack_dir = os.path.join(conf[CONF_BUILDDIR], 'pack')\n    installers = glob.glob(os.path.join(pack_dir, '*.msi'))\n    for fn in installers:\n        name = conf[CONF_BRAND]\n        if name == 'seafile':\n            name = 'Seafile'\n        do_sign(certfile, fn, desc='{} Installer'.format(name))\n\ndef do_sign(certfile, fn, desc=None):\n    certfile = to_win_path(certfile)\n    fn = to_win_path(fn)\n    info('signing file {} using cert \"{}\"'.format(fn, certfile))\n\n    if desc:\n        desc_flags = '-d \"{}\"'.format(desc)\n    else:\n        desc_flags = ''\n\n    # https://support.comodo.com/index.php?/Knowledgebase/Article/View/68/0/time-stamping-server\n    signcmd = 'signtool.exe sign -fd sha256 -t http://timestamp.comodoca.com -f {} {} {}'.format(certfile, desc_flags, fn)\n    i = 0\n    while i < RETRY_COUNT:\n        time.sleep(30)\n        ret = run(signcmd, cwd=os.path.dirname(fn))\n        if ret == 0:\n            break\n        i = i + 1\n        if i == RETRY_COUNT:\n            error('Failed to sign file \"{}\"'.format(fn))\n\ndef strip_symbols():\n    bin_dir = os.path.join(conf[CONF_BUILDDIR], 'pack', 'bin')\n    def do_strip(fn, stripcmd='strip'):\n        run('%s \"%s\"' % (stripcmd, fn))\n        info('stripping: %s' % fn)\n\n    for dll in glob.glob(os.path.join(bin_dir, '*.dll')):\n        name = os.path.basename(dll).lower()\n        if 'qt' in name:\n            do_strip(dll)\n        if name == 'seafile_ext.dll':\n            do_strip(dll)\n        elif name == 'seafile_ext64.dll':\n            do_strip(dll, stripcmd='x86_64-w64-mingw32-strip')\n\ndef edit_fragment_wxs():\n    '''In the main wxs file(seafile.wxs) we need to reference to the id of\n    seafile-applet.exe, which is listed in fragment.wxs. Since fragments.wxs is\n    auto generated, the id is sequentially generated, so we need to change the\n    id of seafile-applet.exe manually.\n\n    '''\n    file_path = os.path.join(conf[CONF_BUILDDIR], 'pack', 'fragment.wxs')\n    new_lines = []\n    with open(file_path, 'r') as fp:\n        for line in fp:\n            if 'seafile-applet.exe' in line:\n                # change the id of 'seafile-applet.exe' to 'seafileapplet.exe'\n                new_line = re.sub('file_bin_[\\d]+', 'seafileapplet.exe', line)\n                new_lines.append(new_line)\n            else:\n                new_lines.append(line)\n\n    content = '\\r\\n'.join(new_lines)\n    with open(file_path, 'w') as fp:\n        fp.write(content)\n\n\ndef generate_breakpad_symbols():\n    \"\"\"\n    Generate seafile and seafile-gui breakpad symbols\n    :return: None\n    \"\"\"\n    seafile_src = Seafile().projdir\n    seafile_gui_src = SeafileClient().projdir\n    generate_breakpad_symbols_script = os.path.join(seafile_src, 'scripts/breakpad.py')\n\n    # generate seafile the breakpad symbols\n    seafile_name = 'seaf-daemon.exe'\n    seafile_symbol_name = 'seaf-daemon.exe.sym-%s' % conf[CONF_VERSION]\n    seafile_symbol_output = os.path.join(seafile_src, seafile_symbol_name)\n\n    if run('python %s  --projectSrc %s --name %s --output %s'\n           % (generate_breakpad_symbols_script, seafile_src, seafile_name, seafile_symbol_output)) != 0:\n        error('Error when generating breakpad symbols')\n\n    # generate seafile gui breakpad symbols\n    seafile_gui_name = 'seafile-applet.exe'\n    seafile_gui_symbol_name = 'seafile-applet.exe.sym-%s' % conf[CONF_VERSION]\n    seafile_gui_symbol_output = os.path.join(seafile_gui_src, seafile_gui_symbol_name)\n\n    if run('python %s --projectSrc %s --name %s --output %s'\n            % (generate_breakpad_symbols_script, seafile_gui_src, seafile_gui_name, seafile_gui_symbol_output)) != 0:\n        error('Error when generating seafile gui client breakpad symbol')\n\n    # move symbols to output directory\n    dst_seafile_symbol_file = os.path.join(conf[CONF_OUTPUTDIR], seafile_symbol_name)\n    dst_seafile_gui_symbol_file = os.path.join(conf[CONF_OUTPUTDIR], seafile_gui_symbol_name)\n    must_copy(seafile_symbol_output, dst_seafile_symbol_file)\n    must_copy(seafile_gui_symbol_output, dst_seafile_gui_symbol_file)\n\n\ndef build_msi():\n    prepare_msi()\n    generate_breakpad_symbols()\n    if conf[CONF_DEBUG] or conf[CONF_NO_STRIP]:\n        info('Would not strip exe/dll symbols since --debug or --nostrip is specified')\n    else:\n        strip_symbols()\n\n    # Only sign the exectuables after stripping symbols.\n    if need_sign():\n        sign_executables()\n\n    pack_dir = os.path.join(conf[CONF_BUILDDIR], 'pack')\n    if run('make fragment.wxs', cwd=pack_dir) != 0:\n        error('Error when make fragement.wxs')\n\n    edit_fragment_wxs()\n\n    if run('make', cwd=pack_dir) != 0:\n        error('Error when make seafile.msi')\n\ndef build_english_msi():\n    '''The extra work to build the English msi.'''\n    pack_dir = os.path.join(conf[CONF_BUILDDIR], 'pack')\n\n    if run('make en', cwd=pack_dir) != 0:\n        error('Error when make seafile-en.msi')\n\ndef build_german_msi():\n    '''The extra work to build the German msi.'''\n    pack_dir = os.path.join(conf[CONF_BUILDDIR], 'pack')\n\n    if run('make de', cwd=pack_dir) != 0:\n        error('Error when make seafile-de.msi')\n\ndef move_msi():\n    pack_dir = os.path.join(conf[CONF_BUILDDIR], 'pack')\n    src_msi = os.path.join(pack_dir, 'seafile.msi')\n    brand = conf[CONF_BRAND]\n    dst_msi = os.path.join(conf[CONF_OUTPUTDIR], '%s-%s.msi' % (brand, conf[CONF_VERSION]))\n\n    # move msi to outputdir\n    must_copy(src_msi, dst_msi)\n\n    if not conf[CONF_ONLY_CHINESE]:\n        src_msi_en = os.path.join(pack_dir, 'seafile-en.msi')\n        dst_msi_en = os.path.join(conf[CONF_OUTPUTDIR], '%s-%s-en.msi' % (brand, conf[CONF_VERSION]))\n        must_copy(src_msi_en, dst_msi_en)\n\n    print '---------------------------------------------'\n    print 'The build is successfully. Output is:'\n    print '>>\\t%s' % dst_msi\n    if not conf[CONF_ONLY_CHINESE]:\n        print '>>\\t%s' % dst_msi_en\n        # print '>>\\t%s' % dst_msi_de\n    print '---------------------------------------------'\n\ndef check_tools():\n    tools = [\n        'Paraffin',\n        'candle',\n        'light',\n        'depends',\n    ]\n\n    for prog in tools:\n        if not find_in_path(prog + '.exe'):\n            error('%s not found' % prog)\n\ndef dump_env():\n    print 'Dumping environment variables:'\n    for k, v in os.environ.iteritems():\n        print '%s: %s' % (k, v)\n\ndef need_sign():\n    return conf[CONF_BRAND].lower() == 'seafile'\n\ndef main():\n    dump_env()\n    parse_args()\n    setup_build_env()\n    check_tools()\n\n    libsearpc = Libsearpc()\n    seafile = Seafile()\n    seafile_client = SeafileClient()\n    seafile_shell_ext = SeafileShellExt()\n\n    libsearpc.uncompress()\n    libsearpc.build()\n\n    seafile.uncompress()\n    seafile.build()\n\n    seafile_client.uncompress()\n    seafile_shell_ext.uncompress()\n\n    seafile_client.build()\n    seafile_shell_ext.build()\n\n    build_msi()\n    if not conf[CONF_ONLY_CHINESE]:\n        build_english_msi()\n        # build_german_msi()\n\n    if need_sign():\n        sign_installers()\n    move_msi()\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "scripts/build/gen-deb-src.py",
    "content": "#!/usr/bin/env python\n# coding: UTF-8\n\n'''This scirpt builds the seafile debian source tarball. In this tarball,\nlibsearpc and ccnet is also included.\n\n'''\nimport sys\n\n####################\n### Requires Python 2.6+\n####################\nif sys.version_info[0] == 3:\n    print 'Python 3 not supported yet. Quit now.'\n    sys.exit(1)\nif sys.version_info[1] < 6:\n    print 'Python 2.6 or above is required. Quit now.'\n    sys.exit(1)\n\nimport os\nimport tempfile\nimport glob\nimport shutil\nimport re\nimport subprocess\nimport optparse\nimport atexit\n\n####################\n### Global variables\n####################\n\n# command line configuartion\nconf = {}\n\n# key names in the conf dictionary.\nCONF_VERSION            = 'version'\nCONF_LIBSEARPC_VERSION  = 'libsearpc_version'\nCONF_CCNET_VERSION      = 'ccnet_version'\nCONF_SEAFILE_VERSION    = 'seafile_version'\nCONF_SEAFILE_CLIENT_VERSION    = 'seafile_client_version'\nCONF_SRCDIR             = 'srcdir'\nCONF_KEEP               = 'keep'\nCONF_BUILDDIR           = 'builddir'\nCONF_OUTPUTDIR          = 'outputdir'\n\n####################\n### Common helper functions\n####################\ndef highlight(content, is_error=False):\n    '''Add ANSI color to content to get it highlighted on terminal'''\n    if is_error:\n        return '\\x1b[1;31m%s\\x1b[m' % content\n    else:\n        return '\\x1b[1;32m%s\\x1b[m' % content\n\ndef info(msg):\n    print highlight('[INFO] ') + msg\n\ndef exist_in_path(prog):\n    '''Test whether prog exists in system path'''\n    dirs = os.environ['PATH'].split(':')\n    for d in dirs:\n        if d == '':\n            continue\n        path = os.path.join(d, prog)\n        if os.path.exists(path):\n            return True\n\n    return False\n\ndef error(msg=None, usage=None):\n    if msg:\n        print highlight('[ERROR] ') + msg\n    if usage:\n        print usage\n    sys.exit(1)\n\ndef run_argv(argv, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Run a program and wait it to finish, and return its exit code. The\n    standard output of this program is supressed.\n\n    '''\n\n    info('running %s, cwd=%s' % (' '.join(argv), cwd if cwd else os.getcwd()))\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(argv,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env)\n        return proc.wait()\n\ndef run(cmdline, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Like run_argv but specify a command line string instead of argv'''\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(cmdline,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env,\n                                shell=True)\n        return proc.wait()\n\ndef must_mkdir(path):\n    '''Create a directory, exit on failure'''\n    try:\n        os.mkdir(path)\n    except OSError, e:\n        error('failed to create directory %s:%s' % (path, e))\n\ndef must_copy(src, dst):\n    '''Copy src to dst, exit on failure'''\n    try:\n        shutil.copy(src, dst)\n    except Exception, e:\n        error('failed to copy %s to %s: %s' % (src, dst, e))\n\ndef check_targz_src(proj, version, srcdir):\n    src_tarball = os.path.join(srcdir, '%s-%s.tar.gz' % (proj, version))\n    if not os.path.exists(src_tarball):\n        error('%s not exists' % src_tarball)\n\ndef remove_unused_files():\n    srcdir = os.path.join(conf[CONF_BUILDDIR], 'seafile-%s' % conf[CONF_VERSION])\n    web_sh_files = glob.glob(os.path.join(srcdir, 'web', '*.sh'))\n    files = [\n        os.path.join(srcdir, 'web', 'pygettext.py'),\n    ]\n    files.extend(web_sh_files)\n\n    for f in files:\n        run('rm -f %s' % f)\n\ndef gen_tarball():\n    output = os.path.join(conf[CONF_OUTPUTDIR], 'seafile-client-latest.tar.gz')\n    dirname = 'seafile-%s' % conf[CONF_VERSION]\n\n    ignored_patterns = [\n        # windows msvc dlls\n        os.path.join(dirname, 'msi', 'bin*'),\n    ]\n\n    excludes_list = [ '--exclude=%s' % pattern for pattern in ignored_patterns ]\n    argv = [\n        'tar',\n        'czvf',\n        output,\n        dirname,\n    ]\n\n    argv.append(*excludes_list)\n\n    if run_argv(argv) != 0:\n        error('failed to gen %s' % output)\n\n    print '---------------------------------------------'\n    print 'The build is successfully. Output is:\\t%s' % output\n    print '---------------------------------------------'\n\ndef uncompress_seafile():\n    src = os.path.join(conf[CONF_BUILDDIR], 'seafile-%s' % conf[CONF_SEAFILE_VERSION])\n    dst = os.path.join(conf[CONF_BUILDDIR], 'seafile-%s' % conf[CONF_VERSION])\n\n    if os.path.exists(src):\n        error('dir %s already exists' % src)\n    if os.path.exists(dst):\n        error('dir %s already exists' % dst)\n\n    tarball = os.path.join(conf[CONF_SRCDIR], 'seafile-%s.tar.gz' % conf[CONF_SEAFILE_VERSION])\n    argv = [ 'tar', 'xf',\n             tarball,\n             '-C', conf[CONF_BUILDDIR],\n         ]\n\n    if run_argv(argv) != 0:\n        error('failed to uncompress seafile')\n\n    if conf[CONF_VERSION] != conf[CONF_SEAFILE_VERSION]:\n        shutil.move(src, dst)\n\ndef uncompress_libsearpc():\n    tarball = os.path.join(conf[CONF_SRCDIR], 'libsearpc-%s.tar.gz' % conf[CONF_LIBSEARPC_VERSION])\n    dst_dir = os.path.join(conf[CONF_BUILDDIR], 'seafile-%s' % conf[CONF_VERSION], 'libsearpc')\n    must_mkdir(dst_dir)\n    argv = [ 'tar', 'xf',\n             tarball,\n             '--strip-components=1',\n             '-C', dst_dir,\n         ]\n\n    if run_argv(argv) != 0:\n        error('failed to uncompress libsearpc')\n\ndef uncompress_ccnet():\n    tarball = os.path.join(conf[CONF_SRCDIR], 'ccnet-%s.tar.gz' % conf[CONF_CCNET_VERSION])\n    dst_dir = os.path.join(conf[CONF_BUILDDIR], 'seafile-%s' % conf[CONF_VERSION], 'ccnet')\n    must_mkdir(dst_dir)\n    argv = [ 'tar', 'xf',\n             tarball,\n             '--strip-components=1',\n             '-C', dst_dir,\n         ]\n\n    if run_argv(argv) != 0:\n        error('failed to uncompress ccnet')\n\ndef uncompress_seafile_client():\n    tarball = os.path.join(conf[CONF_SRCDIR], 'seafile-client-%s.tar.gz' % conf[CONF_SEAFILE_CLIENT_VERSION])\n    dst_dir = os.path.join(conf[CONF_BUILDDIR], 'seafile-%s' % conf[CONF_VERSION], 'seafile-client')\n    must_mkdir(dst_dir)\n    argv = [ 'tar', 'xf',\n             tarball,\n             '--strip-components=1',\n             '-C', dst_dir,\n         ]\n\n    if run_argv(argv) != 0:\n        error('failed to uncompress ccnet')\n\ndef remove_debian_subdir():\n    debian_subdir = os.path.join(conf[CONF_BUILDDIR], 'seafile-%s' % conf[CONF_VERSION], 'debian')\n    argv = [ 'rm', '-rf', debian_subdir ]\n    if run_argv(argv) != 0:\n        error('failed to uncompress ccnet')\n\n\ndef parse_args():\n    parser = optparse.OptionParser()\n    def long_opt(opt):\n        return '--' + opt\n\n    parser.add_option(long_opt(CONF_VERSION),\n                      dest=CONF_VERSION,\n                      nargs=1,\n                      help='the version of seafile source. Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_SEAFILE_VERSION),\n                      dest=CONF_SEAFILE_VERSION,\n                      nargs=1,\n                      help='the version of seafile. Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_LIBSEARPC_VERSION),\n                      dest=CONF_LIBSEARPC_VERSION,\n                      nargs=1,\n                      help='the version of libsearpc as specified in its \"configure.ac\". Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_CCNET_VERSION),\n                      dest=CONF_CCNET_VERSION,\n                      nargs=1,\n                      help='the version of ccnet as specified in its \"configure.ac\". Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_SEAFILE_CLIENT_VERSION),\n                      dest=CONF_SEAFILE_CLIENT_VERSION,\n                      nargs=1,\n                      help='the version of seafile-client. Must be digits delimited by dots, like 1.3.0')\n\n    parser.add_option(long_opt(CONF_BUILDDIR),\n                      dest=CONF_BUILDDIR,\n                      nargs=1,\n                      help='the directory to build the source. Defaults to /tmp',\n                      default=tempfile.gettempdir())\n\n    parser.add_option(long_opt(CONF_OUTPUTDIR),\n                      dest=CONF_OUTPUTDIR,\n                      nargs=1,\n                      help='the output directory to put the generated server tarball. Defaults to the current directory.',\n                      default=os.getcwd())\n\n    parser.add_option(long_opt(CONF_SRCDIR),\n                      dest=CONF_SRCDIR,\n                      nargs=1,\n                      help='''Source tarballs must be placed in this directory.''')\n\n    parser.add_option(long_opt(CONF_KEEP),\n                      dest=CONF_KEEP,\n                      action='store_true',\n                      help='''keep the build directory after the script exits. By default, the script would delete the build directory at exit.''')\n\n    usage = parser.format_help()\n    options, remain = parser.parse_args()\n    if remain:\n        error(usage=usage)\n\n    validate_args(usage, options)\n\ndef validate_args(usage, options):\n    required_args = [\n        CONF_VERSION,\n        CONF_SEAFILE_VERSION,\n        CONF_LIBSEARPC_VERSION,\n        CONF_CCNET_VERSION,\n        CONF_SEAFILE_CLIENT_VERSION,\n        CONF_SRCDIR,\n    ]\n\n    # fist check required args\n    for optname in required_args:\n        if getattr(options, optname, None) == None:\n            error('%s must be specified' % optname, usage=usage)\n\n    def get_option(optname):\n        return getattr(options, optname)\n\n    # [ version ]\n    def check_project_version(version):\n        '''A valid version must be like 1.2.2, 1.3'''\n        if not re.match('^[0-9](\\.[0-9])+$', version):\n            error('%s is not a valid version' % version, usage=usage)\n\n    version = get_option(CONF_VERSION)\n    libsearpc_version = get_option(CONF_LIBSEARPC_VERSION)\n    ccnet_version = get_option(CONF_CCNET_VERSION)\n    seafile_version = get_option(CONF_SEAFILE_VERSION)\n    seafile_client_version = get_option(CONF_SEAFILE_CLIENT_VERSION)\n\n    check_project_version(version)\n    check_project_version(libsearpc_version)\n    check_project_version(ccnet_version)\n    check_project_version(seafile_version)\n    check_project_version(seafile_client_version)\n\n    # [ srcdir ]\n    srcdir = get_option(CONF_SRCDIR)\n    check_targz_src('libsearpc', libsearpc_version, srcdir)\n    check_targz_src('ccnet', ccnet_version, srcdir)\n    check_targz_src('seafile', seafile_version, srcdir)\n    check_targz_src('seafile-client', seafile_client_version, srcdir)\n\n    # [ builddir ]\n    builddir = get_option(CONF_BUILDDIR)\n    if not os.path.exists(builddir):\n        error('%s does not exist' % builddir, usage=usage)\n\n    builddir = os.path.join(builddir, 'seafile-deb-src')\n\n    # [ outputdir ]\n    outputdir = get_option(CONF_OUTPUTDIR)\n    if not os.path.exists(outputdir):\n        error('outputdir %s does not exist' % outputdir, usage=usage)\n\n    # [ keep ]\n    keep = get_option(CONF_KEEP)\n\n    conf[CONF_VERSION] = version\n    conf[CONF_LIBSEARPC_VERSION] = libsearpc_version\n    conf[CONF_CCNET_VERSION] = ccnet_version\n    conf[CONF_SEAFILE_VERSION] = seafile_version\n    conf[CONF_SEAFILE_CLIENT_VERSION] = seafile_client_version\n\n    conf[CONF_BUILDDIR] = builddir\n    conf[CONF_SRCDIR] = srcdir\n    conf[CONF_OUTPUTDIR] = outputdir\n    conf[CONF_KEEP] = keep\n\n    prepare_builddir(builddir)\n    show_build_info()\n\ndef prepare_builddir(builddir):\n    must_mkdir(builddir)\n\n    if not conf[CONF_KEEP]:\n        def remove_builddir():\n            '''Remove the builddir when exit'''\n            info('remove builddir before exit')\n            shutil.rmtree(builddir, ignore_errors=True)\n        atexit.register(remove_builddir)\n\n    os.chdir(builddir)\n\ndef show_build_info():\n    '''Print all conf information. Confirm before continue.'''\n    info('------------------------------------------')\n    info('Seafile debian source tarball %s:' % conf[CONF_VERSION])\n    info('------------------------------------------')\n    info('seafile:          %s' % conf[CONF_SEAFILE_VERSION])\n    info('seafile-client:   %s' % conf[CONF_SEAFILE_CLIENT_VERSION])\n    info('ccnet:            %s' % conf[CONF_CCNET_VERSION])\n    info('libsearpc:        %s' % conf[CONF_LIBSEARPC_VERSION])\n    info('builddir:         %s' % conf[CONF_BUILDDIR])\n    info('outputdir:        %s' % conf[CONF_OUTPUTDIR])\n    info('source dir:       %s' % conf[CONF_SRCDIR])\n    info('clean on exit:    %s' % (not conf[CONF_KEEP]))\n    info('------------------------------------------')\n    info('press any key to continue ')\n    info('------------------------------------------')\n    dummy = raw_input()\n\ndef main():\n    parse_args()\n    uncompress_seafile()\n    uncompress_libsearpc()\n    uncompress_ccnet()\n    uncompress_seafile_client()\n    remove_debian_subdir()\n    remove_unused_files()\n    gen_tarball()\n\nif __name__ == '__main__':\n    main()"
  },
  {
    "path": "scripts/build/notarize-universal.sh",
    "content": "#!/bin/bash\n\nset -e\nset -x\n\npkg=${1?:\"You must provide the path to the dmg file\"}\nif [[ ! -e $pkg ]]; then\n    echo \"File $pkg does not exist\"\n    exit 1\nfi\n\nsecurity -v unlock-keychain -p vagrant || true\nsudo security -v unlock-keychain -p vagrant || true\n\nNOTARIZE_APPLE_ID=\"${NOTARIZE_APPLE_ID}\"\nNOTARIZE_PASSWORD=\"${NOTARIZE_PASSWORD}\"\nNOTARIZE_TEAM_ID=\"${NOTARIZE_TEAM_ID}\"\n\nBUNDLE_ID=\"com.seafile.seafile-client\"\n\ncd /tmp/\n\necho \"Uploading $pkg for notarizing ...\"\n\nOPTS=\"--apple-id $NOTARIZE_APPLE_ID --password $NOTARIZE_PASSWORD --team-id $NOTARIZE_TEAM_ID\"\n\nxcrun notarytool submit $pkg --wait $OPTS --verbose --output-format json > notarize.json\nif ! grep -q '\"status\":\"Accepted\"' notarize.json; then\n    echo \"Notarization failed\"\n    exit 1\nfi\n\necho \"Notarization success, now stapling the installer ...\"\n\nxcrun stapler staple $pkg\n\necho \"Notarization & stapling done.\"\n"
  },
  {
    "path": "scripts/build/notarize.sh",
    "content": "#!/bin/bash\n\nset -e\nset -x\n\npkg=${1?:\"You must provide the path to the dmg file\"}\nif [[ ! -e $pkg ]]; then\n    echo \"File $pkg does not exist\"\n    exit 1\nfi\n\nsecurity -v unlock-keychain -p vagrant || true\nsudo security -v unlock-keychain -p vagrant || true\n\nAPPLE_ACCOUNT=$(security find-generic-password -s \"notarize username\" -w)\nAPPLE_PASSWORD=$(security find-generic-password -s \"notarize password\" -w)\n\nBUNDLE_ID=\"com.seafile.seafile-client\"\naltool_exe=\"/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Support/altool\"\n\n_altool() {\n    \"${altool_exe}\" \"$@\"\n}\n\ncd /tmp/\n\necho \"Uploading $pkg for notarizing ...\"\n\n_altool --notarize-app -t osx -f $pkg \\\n       --primary-bundle-id ${BUNDLE_ID} \\\n       -u ${APPLE_ACCOUNT} -p ${APPLE_PASSWORD} \\\n       --output-format xml > UploadInfo.plist\n\nREQUESTID=$(xmllint --xpath \"/plist/dict[key='notarization-upload']/dict/key[.='RequestUUID']/following-sibling::string[1]/node()\" UploadInfo.plist)\necho \"file $pkg uploaded for notarization, waiting for apple ...\"\necho ${REQUESTID}\nsleep 60\nx=1\nwhile [[ $x -le 15 ]]; do\n    _altool --notarization-info ${REQUESTID} -u ${APPLE_ACCOUNT}  -p ${APPLE_PASSWORD} --output-format xml > RequestedInfo.plist\n    ANSWER=$(xmllint --xpath \"/plist/dict[key='notarization-info']/dict/key[.='Status']/following-sibling::string[1]/node()\" RequestedInfo.plist)\n    if [[ \"$ANSWER\" == \"in progress\" ]]; then\n        echo \"notarization in progress\"\n        sleep 60\n        x=$((x+1))\n    elif [[ \"$ANSWER\" == \"success\" ]]; then\n        echo \"notarization success\"\n        break\n    else\n        echo \"notarization failed\"\n        break\n        exit 1\n    fi\ndone\nANSWER=$(xmllint --xpath \"/plist/dict[key='notarization-info']/dict/key[.='Status']/following-sibling::string[1]/node()\" RequestedInfo.plist)\nif [[ \"$ANSWER\" != \"success\" ]]; then\n    echo \"notarization failed\"\n    exit 1\nfi\n\necho \"Notarization success, now stapling the installer ...\"\n\nxcrun stapler staple $pkg\n\necho \"Notarization & stapling done.\"\n"
  },
  {
    "path": "scripts/build/osx.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <!-- Entries are referenced from Qt/6.5.3/macos/lib/QtWebEngineCore.framework/Helpers/QtWebEngineProcess.app/Contents/Resources/QtWebEngineProcess.entitlements -->\n    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>\n    <true/>\n    <key>com.apple.security.cs.disable-library-validation</key>\n    <true/>\n    <key>com.apple.security.cs.allow-jit</key>\n    <true/>\n    <key>com.apple.security.cs.disable-executable-page-protection</key>\n    <true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "scripts/seaf-cli-wrapper.sh",
    "content": "#!/bin/bash\n\n# This is a wrapper shell script for the real seaf-cli command.\n# It prepares necessary environment variables and exec the real script.\n\n# seafile cli client requires python 2.6 or 2.7\nfunction check_python_executable() {\n    if [[ \"$PYTHON\" != \"\" && -x $PYTHON ]]; then\n        return 0\n    fi\n        \n    if which python2.7 2>/dev/null 1>&2; then\n        PYTHON=python2.7\n    elif which python27 2>/dev/null 1>&2; then\n        PYTHON=python27\n    elif which python2.6 2>/dev/null 1>&2; then\n        PYTHON=python2.6\n    elif which python26 2>/dev/null 1>&2; then\n        PYTHON=python26\n    else\n        echo \n        echo \"Can't find a python executable of version 2.6 or above in PATH\"\n        echo \"Install python 2.6+ before continue.\"\n        echo \"Or if you installed it in a non-standard PATH, set the PYTHON enviroment variable to it\"\n        echo \n        exit 1\n    fi\n}\n\ncheck_python_executable\n\n# seafile cli client requires the argparse module\nif ! $PYTHON -c 'import argparse' 2>/dev/null 1>&2; then\n    echo\n    echo \"Python argparse module is required\"\n    echo \"see [https://pypi.python.org/pypi/argparse]\"\n    echo\n    exit 1\nfi\n\nSCRIPT=$(readlink -f \"$0\")\nINSTALLPATH=$(dirname \"${SCRIPT}\")\n\nSEAFILE_BIN_DIR=${INSTALLPATH}/bin\nSEAFILE_LIB_DIR=${INSTALLPATH}/lib:${INSTALLPATH}/lib64\nSEAFILE_PYTHON_PATH=${INSTALLPATH}/lib/python2.6/site-packages:${INSTALLPATH}/lib64/python2.6/site-packages:${INSTALLPATH}/lib/python2.7/site-packages:${INSTALLPATH}/lib64/python2.7/site-packages\n\nSEAF_CLI=${SEAFILE_BIN_DIR}/seaf-cli.py\n\nPATH=${SEAFILE_BIN_DIR}:${PATH} \\\nPYTHONPATH=${SEAFILE_PYTHON_PATH}:${PYTHONPATH} \\\nSEAFILE_LD_LIBRARY_PATH=${SEAFILE_LIB_DIR}:${LD_LIBRARY_PATH} \\\nexec $PYTHON ${SEAF_CLI} \"$@\"\n"
  },
  {
    "path": "seafile.sln",
    "content": "﻿\r\nMicrosoft Visual Studio Solution File, Format Version 12.00\r\n# Visual Studio Version 16\r\nVisualStudioVersion = 16.0.29613.14\r\nMinimumVisualStudioVersion = 10.0.40219.1\r\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"seafile\", \"seafile.vcxproj\", \"{D3B4BCBB-BF84-43DF-9753-A7A0A4288D13}\"\r\nEndProject\r\nGlobal\r\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\r\n\t\tDebug|x64 = Debug|x64\r\n\t\tDebug|x86 = Debug|x86\r\n\t\tRelease|x64 = Release|x64\r\n\t\tRelease|x86 = Release|x86\r\n\tEndGlobalSection\r\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\r\n\t\t{D3B4BCBB-BF84-43DF-9753-A7A0A4288D13}.Debug|x64.ActiveCfg = Debug|x64\r\n\t\t{D3B4BCBB-BF84-43DF-9753-A7A0A4288D13}.Debug|x64.Build.0 = Debug|x64\r\n\t\t{D3B4BCBB-BF84-43DF-9753-A7A0A4288D13}.Debug|x86.ActiveCfg = Debug|Win32\r\n\t\t{D3B4BCBB-BF84-43DF-9753-A7A0A4288D13}.Debug|x86.Build.0 = Debug|Win32\r\n\t\t{D3B4BCBB-BF84-43DF-9753-A7A0A4288D13}.Release|x64.ActiveCfg = Release|x64\r\n\t\t{D3B4BCBB-BF84-43DF-9753-A7A0A4288D13}.Release|x64.Build.0 = Release|x64\r\n\t\t{D3B4BCBB-BF84-43DF-9753-A7A0A4288D13}.Release|x86.ActiveCfg = Release|Win32\r\n\t\t{D3B4BCBB-BF84-43DF-9753-A7A0A4288D13}.Release|x86.Build.0 = Release|Win32\r\n\tEndGlobalSection\r\n\tGlobalSection(SolutionProperties) = preSolution\r\n\t\tHideSolutionNode = FALSE\r\n\tEndGlobalSection\r\n\tGlobalSection(ExtensibilityGlobals) = postSolution\r\n\t\tSolutionGuid = {ACC9413E-DE77-4CF1-9C31-14978D440597}\r\n\tEndGlobalSection\r\nEndGlobal\r\n"
  },
  {
    "path": "seafile.vcxproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Project DefaultTargets=\"Build\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\r\n  <ItemGroup Label=\"ProjectConfigurations\">\r\n    <ProjectConfiguration Include=\"Debug|Win32\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>Win32</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|Win32\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>Win32</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Debug|x64\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>x64</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|x64\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>x64</Platform>\r\n    </ProjectConfiguration>\r\n  </ItemGroup>\r\n  <PropertyGroup Label=\"Globals\">\r\n    <VCProjectVersion>16.0</VCProjectVersion>\r\n    <ProjectGuid>{D3B4BCBB-BF84-43DF-9753-A7A0A4288D13}</ProjectGuid>\r\n    <RootNamespace>seafile</RootNamespace>\r\n    <WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>\r\n  </PropertyGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\" Label=\"Configuration\">\r\n    <ConfigurationType>Application</ConfigurationType>\r\n    <UseDebugLibraries>true</UseDebugLibraries>\r\n    <PlatformToolset>v142</PlatformToolset>\r\n    <CharacterSet>Unicode</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\" Label=\"Configuration\">\r\n    <ConfigurationType>Application</ConfigurationType>\r\n    <UseDebugLibraries>false</UseDebugLibraries>\r\n    <PlatformToolset>v142</PlatformToolset>\r\n    <WholeProgramOptimization>false</WholeProgramOptimization>\r\n    <CharacterSet>NotSet</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\r\n    <ConfigurationType>Application</ConfigurationType>\r\n    <UseDebugLibraries>true</UseDebugLibraries>\r\n    <PlatformToolset>v142</PlatformToolset>\r\n    <CharacterSet>NotSet</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\r\n    <ConfigurationType>Application</ConfigurationType>\r\n    <UseDebugLibraries>false</UseDebugLibraries>\r\n    <PlatformToolset>v142</PlatformToolset>\r\n    <WholeProgramOptimization>false</WholeProgramOptimization>\r\n    <CharacterSet>NotSet</CharacterSet>\r\n  </PropertyGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\r\n  <ImportGroup Label=\"ExtensionSettings\">\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"Shared\">\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <PropertyGroup Label=\"UserMacros\" />\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <LinkIncremental>true</LinkIncremental>\r\n    <TargetName>seaf-daemon</TargetName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <LinkIncremental>true</LinkIncremental>\r\n    <TargetName>seaf-daemon</TargetName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <LinkIncremental>false</LinkIncremental>\r\n    <TargetName>seaf-daemon</TargetName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <LinkIncremental>\r\n    </LinkIncremental>\r\n    <TargetName>seaf-daemon</TargetName>\r\n    <OutDir>$(ProjectDir)$(Platform)\\$(Configuration)\\</OutDir>\r\n  </PropertyGroup>\r\n  <PropertyGroup Label=\"Vcpkg\">\r\n    <VcpkgEnableManifest>true</VcpkgEnableManifest>\r\n  </PropertyGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <SDLCheck>\r\n      </SDLCheck>\r\n      <PreprocessorDefinitions>WIN32;UNICODE;WIN32_LEAN_AND_MEAN;SEAFILE_CLIENT;PACKAGE_VERSION=\"9.0.16\";%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <ConformanceMode>false</ConformanceMode>\r\n      <AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>\r\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\libsearpc\\lib;$(ProjectDir)common;$(ProjectDir)lib;$(ProjectDir)include;$(ProjectDir)daemon;$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r\n      <LanguageStandard>stdcpp17</LanguageStandard>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Console</SubSystem>\r\n      <GenerateDebugInformation>true</GenerateDebugInformation>\r\n      <AdditionalLibraryDirectories>$(ProjectDir)..\\libsearpc\\$(IntDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>\r\n      <AdditionalDependencies>ws2_32.lib;Rpcrt4.lib;Psapi.lib;Crypt32.lib;libsearpc.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level1</WarningLevel>\r\n      <SDLCheck>\r\n      </SDLCheck>\r\n      <PreprocessorDefinitions>WIN32;UNICODE;WIN32_LEAN_AND_MEAN;SEAFILE_CLIENT;PACKAGE_VERSION=\"9.0.16\";%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <ConformanceMode>false</ConformanceMode>\r\n      <LanguageStandard>stdcpp17</LanguageStandard>\r\n      <AdditionalIncludeDirectories>$(ProjectDir)vcpkg_installed\\x64-windows\\x64-windows\\include\\glib-2.0;$(ProjectDir)vcpkg_installed\\x64-windows\\x64-windows\\lib\\glib-2.0\\include;$(ProjectDir)..\\libsearpc\\lib;$(ProjectDir)common;$(ProjectDir)include;$(ProjectDir)daemon;$(ProjectDir)lib;$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r\n      <AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Console</SubSystem>\r\n      <GenerateDebugInformation>DebugFastLink</GenerateDebugInformation>\r\n      <AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>\r\n      <AdditionalDependencies>ws2_32.lib;Psapi.lib;Rpcrt4.lib;Crypt32.lib;libsearpc.lib;websockets.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <FunctionLevelLinking>\r\n      </FunctionLevelLinking>\r\n      <IntrinsicFunctions>false</IntrinsicFunctions>\r\n      <SDLCheck>\r\n      </SDLCheck>\r\n      <PreprocessorDefinitions>WIN32;PACKAGE_VERSION=\"9.0.16\";WIN32_LEAN_AND_MEAN;UNICODE;SEAFILE_CLIENT;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <ConformanceMode>false</ConformanceMode>\r\n      <LanguageStandard>stdcpp17</LanguageStandard>\r\n      <AdditionalIncludeDirectories>$(ProjectDir)..\\libsearpc\\lib;$(ProjectDir);$(ProjectDir)daemon;$(ProjectDir)include;$(ProjectDir)lib;$(ProjectDir)common;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r\n      <SupportJustMyCode>true</SupportJustMyCode>\r\n      <Optimization>Disabled</Optimization>\r\n      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\r\n      <AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>\r\n      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Console</SubSystem>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <GenerateDebugInformation>DebugFastLink</GenerateDebugInformation>\r\n      <AdditionalDependencies>ws2_32.lib;Psapi.lib;Rpcrt4.lib;Crypt32.lib;libsearpc.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n      <AdditionalLibraryDirectories>$(ProjectDir)..\\libsearpc\\Debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level1</WarningLevel>\r\n      <FunctionLevelLinking>\r\n      </FunctionLevelLinking>\r\n      <IntrinsicFunctions>false</IntrinsicFunctions>\r\n      <SDLCheck>\r\n      </SDLCheck>\r\n      <PreprocessorDefinitions>WIN32;PACKAGE_VERSION=\"9.0.16\";WIN32_LEAN_AND_MEAN;UNICODE;SEAFILE_CLIENT;ENABLE_BREAKPAD;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <ConformanceMode>false</ConformanceMode>\r\n      <LanguageStandard>stdcpp17</LanguageStandard>\r\n      <AdditionalIncludeDirectories>$(ProjectDir)vcpkg_installed\\x64-windows\\x64-windows\\include\\glib-2.0;$(ProjectDir)vcpkg_installed\\x64-windows\\x64-windows\\lib\\glib-2.0\\include;$(ProjectDir)..\\libsearpc\\lib;$(ProjectDir);$(ProjectDir)common;$(ProjectDir)lib;$(ProjectDir)include;$(ProjectDir)daemon;$(ProjectDir)..\\breakpad\\src;$(ProjectDir)..\\libwebsockets\\build\\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r\n      <AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>\r\n      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Console</SubSystem>\r\n      <EnableCOMDATFolding>\r\n      </EnableCOMDATFolding>\r\n      <OptimizeReferences>\r\n      </OptimizeReferences>\r\n      <GenerateDebugInformation>true</GenerateDebugInformation>\r\n      <AdditionalLibraryDirectories>$(ProjectDir)..\\libsearpc\\$(IntDir);$(ProjectDir)..\\breakpad\\src\\client\\windows\\Release\\lib\\;$(ProjectDir)..\\libwebsockets\\build\\lib\\Release\\;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>\r\n      <AdditionalDependencies>ws2_32.lib;Psapi.lib;Rpcrt4.lib;Crypt32.lib;libsearpc.lib;common.lib;crash_generation_client.lib;exception_handler.lib;websockets.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n      <PerUserRedirection>false</PerUserRedirection>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemGroup>\r\n    <ClCompile Include=\"common\\block-backend-fs.c\" />\r\n    <ClCompile Include=\"common\\block-backend.c\" />\r\n    <ClCompile Include=\"common\\block-mgr.c\" />\r\n    <ClCompile Include=\"common\\branch-mgr.c\" />\r\n    <ClCompile Include=\"common\\cdc\\cdc.c\" />\r\n    <ClCompile Include=\"common\\cdc\\rabin-checksum.c\" />\r\n    <ClCompile Include=\"common\\commit-mgr.c\" />\r\n    <ClCompile Include=\"common\\curl-init.c\" />\r\n    <ClCompile Include=\"common\\diff-simple.c\" />\r\n    <ClCompile Include=\"common\\fs-mgr.c\" />\r\n    <ClCompile Include=\"common\\index\\cache-tree.c\" />\r\n    <ClCompile Include=\"common\\index\\index.c\" />\r\n    <ClCompile Include=\"common\\log.c\" />\r\n    <ClCompile Include=\"common\\mq-mgr.c\" />\r\n    <ClCompile Include=\"common\\obj-backend-fs.c\" />\r\n    <ClCompile Include=\"common\\obj-store.c\" />\r\n    <ClCompile Include=\"common\\password-hash.c\" />\r\n    <ClCompile Include=\"common\\rpc-service.c\" />\r\n    <ClCompile Include=\"common\\seafile-crypt.c\" />\r\n    <ClCompile Include=\"common\\vc-common.c\" />\r\n    <ClCompile Include=\"daemon\\cevent.c\" />\r\n    <ClCompile Include=\"daemon\\change-set.c\" />\r\n    <ClCompile Include=\"daemon\\clone-mgr.c\" />\r\n    <ClCompile Include=\"daemon\\c_bpwrapper.cpp\" />\r\n    <ClCompile Include=\"daemon\\filelock-mgr.c\" />\r\n    <ClCompile Include=\"daemon\\http-tx-mgr.c\" />\r\n    <ClCompile Include=\"daemon\\job-mgr.c\" />\r\n    <ClCompile Include=\"daemon\\notif-mgr.c\" />\r\n    <ClCompile Include=\"daemon\\repo-mgr.c\" />\r\n    <ClCompile Include=\"daemon\\seaf-daemon.c\" />\r\n    <ClCompile Include=\"daemon\\seafile-config.c\" />\r\n    <ClCompile Include=\"daemon\\seafile-error.c\" />\r\n    <ClCompile Include=\"daemon\\seafile-session.c\" />\r\n    <ClCompile Include=\"daemon\\set-perm.c\" />\r\n    <ClCompile Include=\"daemon\\sync-mgr.c\" />\r\n    <ClCompile Include=\"daemon\\sync-status-tree.c\" />\r\n    <ClCompile Include=\"daemon\\timer.c\" />\r\n    <ClCompile Include=\"daemon\\vc-utils.c\" />\r\n    <ClCompile Include=\"daemon\\wt-monitor-structs.c\" />\r\n    <ClCompile Include=\"daemon\\wt-monitor-win32.c\" />\r\n    <ClCompile Include=\"daemon\\wt-monitor.c\" />\r\n    <ClCompile Include=\"lib\\db.c\" />\r\n    <ClCompile Include=\"lib\\net.c\" />\r\n    <ClCompile Include=\"lib\\repo.c\" />\r\n    <ClCompile Include=\"lib\\task.c\" />\r\n    <ClCompile Include=\"lib\\utils.c\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ClInclude Include=\"common\\block-backend.h\" />\r\n    <ClInclude Include=\"common\\block-mgr.h\" />\r\n    <ClInclude Include=\"common\\block.h\" />\r\n    <ClInclude Include=\"common\\branch-mgr.h\" />\r\n    <ClInclude Include=\"common\\cdc\\cdc.h\" />\r\n    <ClInclude Include=\"common\\cdc\\rabin-checksum.h\" />\r\n    <ClInclude Include=\"common\\commit-mgr.h\" />\r\n    <ClInclude Include=\"common\\common.h\" />\r\n    <ClInclude Include=\"common\\curl-init.h\" />\r\n    <ClInclude Include=\"common\\diff-simple.h\" />\r\n    <ClInclude Include=\"common\\fs-mgr.h\" />\r\n    <ClInclude Include=\"common\\index\\cache-tree.h\" />\r\n    <ClInclude Include=\"common\\index\\index.h\" />\r\n    <ClInclude Include=\"common\\log.h\" />\r\n    <ClInclude Include=\"common\\mq-mgr.h\" />\r\n    <ClInclude Include=\"common\\obj-backend.h\" />\r\n    <ClInclude Include=\"common\\obj-store.h\" />\r\n    <ClInclude Include=\"common\\password-hash.h\" />\r\n    <ClInclude Include=\"common\\seafile-crypt.h\" />\r\n    <ClInclude Include=\"common\\vc-common.h\" />\r\n    <ClInclude Include=\"daemon\\cevent.h\" />\r\n    <ClInclude Include=\"daemon\\change-set.h\" />\r\n    <ClInclude Include=\"daemon\\clone-mgr.h\" />\r\n    <ClInclude Include=\"daemon\\c_bpwrapper.h\" />\r\n    <ClInclude Include=\"daemon\\filelock-mgr.h\" />\r\n    <ClInclude Include=\"daemon\\http-tx-mgr.h\" />\r\n    <ClInclude Include=\"daemon\\job-mgr.h\" />\r\n    <ClInclude Include=\"daemon\\notif-mgr.h\" />\r\n    <ClInclude Include=\"daemon\\repo-mgr.h\" />\r\n    <ClInclude Include=\"daemon\\seafile-config.h\" />\r\n    <ClInclude Include=\"daemon\\seafile-error-impl.h\" />\r\n    <ClInclude Include=\"daemon\\seafile-session.h\" />\r\n    <ClInclude Include=\"daemon\\set-perm.h\" />\r\n    <ClInclude Include=\"daemon\\sync-mgr.h\" />\r\n    <ClInclude Include=\"daemon\\sync-status-tree.h\" />\r\n    <ClInclude Include=\"daemon\\timer.h\" />\r\n    <ClInclude Include=\"daemon\\vc-utils.h\" />\r\n    <ClInclude Include=\"daemon\\wt-monitor-structs.h\" />\r\n    <ClInclude Include=\"daemon\\wt-monitor.h\" />\r\n    <ClInclude Include=\"include\\seafile-error.h\" />\r\n    <ClInclude Include=\"include\\seafile-rpc.h\" />\r\n    <ClInclude Include=\"include\\seafile.h\" />\r\n    <ClInclude Include=\"lib\\db.h\" />\r\n    <ClInclude Include=\"lib\\include.h\" />\r\n    <ClInclude Include=\"lib\\net.h\" />\r\n    <ClInclude Include=\"lib\\seafile-object.h\" />\r\n    <ClInclude Include=\"lib\\searpc-marshal.h\" />\r\n    <ClInclude Include=\"lib\\searpc-signature.h\" />\r\n    <ClInclude Include=\"lib\\utils.h\" />\r\n  </ItemGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\r\n  <ImportGroup Label=\"ExtensionTargets\">\r\n  </ImportGroup>\r\n</Project>\r\n"
  },
  {
    "path": "setupwin.py",
    "content": "#!/usr/bin/env python\n# coding: UTF-8\n\n'''This scirpt is used to bundle all needed files into the destdir to\nfaciliate windows development.\n\n'''\n\nimport sys\n\n####################\n### Requires Python 2.6+\n####################\nif sys.version_info[0] == 3:\n    print 'Python 3 not supported yet. Quit now.'\n    sys.exit(1)\nif sys.version_info[1] < 6:\n    print 'Python 2.6 or above is required. Quit now.'\n    sys.exit(1)\n\nimport os\nimport glob\nimport shutil\nimport subprocess\nimport tempfile\nimport csv\n\nfrom distutils.core import setup as dist_setup\nimport py2exe\n\ndef usage():\n    print '''\\\nUsage:\n    %s <target directory>\n'''\n\n####################\n### Common helper functions\n####################\ndef to_mingw_path(path):\n    if len(path) < 2 or path[1] != ':' :\n        return path.replace('\\\\', '/')\n\n    drive = path[0]\n    return '/%s%s' % (drive.lower(), path[2:].replace('\\\\', '/'))\n\ndef to_win_path(path):\n    if len(path) < 2 or path[1] == ':' :\n        return path.replace('/', '\\\\')\n\n    drive = path[1]\n    return '%s:%s' % (drive.lower(), path[2:].replace('/', '\\\\'))\n\ndef info(msg):\n    print '[INFO] ' + msg\n\ndef error(msg=None, usage=None):\n    if msg:\n        print '[ERROR] ' + msg\n    if usage:\n        print usage\n    sys.exit(1)\n\ndef which(prog):\n    '''Return the path of the file <prog>, if exists in PATH'''\n    dirs = os.environ['PATH'].split(';')\n    for d in dirs:\n        if d == '':\n            continue\n        path = os.path.join(d, prog)\n        if os.path.exists(path):\n            return path\n\n    return None\n\ndef run(cmdline, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):\n    '''Run a program and wait it to finish, and return its exit code. The\n    standard output of this program is supressed.\n\n    '''\n    info('running %s, cwd=%s' % (cmdline, cwd if cwd else os.getcwd()))\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(cmdline,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env,\n                                shell=True)\n        return proc.wait()\n\ndef rmtree(path):\n    '''Remove a directory, ignore errors'''\n    try:\n        shutil.rmtree(path)\n    except:\n        pass\n\ndef must_mkdir(path):\n    '''Create a directory, exit on failure'''\n    try:\n        os.mkdir(path)\n    except OSError, e:\n        error('failed to create directory %s:%s' % (path, e))\n\ndef must_copy(src, dst):\n    '''Copy src to dst, exit on failure'''\n    try:\n        info('copying %s --> %s' % (src, dst))\n        shutil.copy(src, dst)\n    except Exception, e:\n        error('failed to copy %s to %s: %s' % (src, dst, e))\n\ndef must_copytree(src, dst):\n    '''Copy dir src to dst, exit on failure'''\n    try:\n        info('copying directory %s --> %s' % (src, dst))\n        shutil.copytree(src, dst)\n    except Exception, e:\n        error('failed to copy dir %s to %s: %s' % (src, dst, e))\n\ndef must_move(src, dst):\n    '''Move src to dst, exit on failure'''\n    try:\n        info('moving %s --> %s' % (src, dst))\n        shutil.move(src, dst)\n    except Exception, e:\n        error('failed to move %s to %s: %s' % (src, dst, e))\n\ndef web_py2exe():\n    webdir = os.path.join(seafile_srcdir, 'web')\n    dist_dir = os.path.join(webdir, 'dist')\n    build_dir = os.path.join(webdir, 'build')\n\n    rmtree(dist_dir)\n    rmtree(build_dir)\n    \n    os.chdir(webdir)\n\n    original_argv = sys.argv\n    sys.argv = [sys.argv[0], 'py2exe']\n    sys.path.insert(0, webdir)\n\n    targetname = 'seafile-web'\n    targetfile = targetname + '.py'\n    must_copy('main.py', targetfile)\n\n    packages=[\"mako.cache\", \"utils\"]\n    ex_files=[]\n    option = {\"py2exe\":\n              {\"includes\" :[targetname],\n               \"packages\" : packages,\n               \"bundle_files\" : 3}}\n\n    try:\n        dist_setup(name=targetname,\n                   options = option,\n                   windows=[{\"script\":targetfile}],\n                   data_files=ex_files)\n    except Exception as e:\n        error('Error when calling py2exe: %s' % e)\n\n    for name in glob.glob('dist/*'):\n        must_copy(name, bin_dir)\n\n    must_copytree('i18n', os.path.join(bin_dir, 'i18n'))\n    must_copytree('static', os.path.join(bin_dir, 'static'))\n    must_copytree('templates', os.path.join(bin_dir, 'templates'))\n\n    rmtree(dist_dir)\n    rmtree(build_dir)\n\n    sys.path.pop(0)\n    sys.argv = original_argv\n    os.chdir(seafile_srcdir)\n\ndef parse_depends_csv(path):\n    '''parse the output of dependency walker'''\n    libs = []\n    def should_ingore_lib(lib):\n        if not os.path.exists(lib):\n            return True\n\n        if lib.lower().startswith('c:\\\\windows'):\n            return True\n\n        return False\n\n    with open(path, 'r') as fp:\n        reader = csv.reader(fp)\n        for row in reader:\n            if len(row) < 2:\n                continue\n            lib = row[1]\n            if not should_ingore_lib(lib):\n                libs.append(lib)\n\n    return libs\n\ndef copy_shared_libs():\n    '''Copy shared libs need by libccnet, such as libevent, libsqlite, etc.\n    First we use Dependency walker to analyse libccnet-0.dll, and get an\n    output file in csv format. Then we parse the csv file to get the list of\n    shared libs.\n\n    '''\n\n    tempdir = tempfile.gettempdir()\n    output = os.path.join(tempdir, 'depends.csv')\n    applet = os.path.join(seafile_srcdir, 'gui', 'win', 'seafile-applet.exe')\n    cmd = 'depends.exe -c -f 1 -oc %s %s' % (output, applet)\n\n    # See the manual of Dependency walker\n    if run(cmd) > 0x100:\n        error('failed to run dependency walker for libccnet')\n\n    if not os.path.exists(output):\n        error('failed to run dependency walker for libccnet')\n\n    shared_libs = parse_depends_csv(output)\n    for lib in shared_libs:\n        must_copy(lib, bin_dir)\n\n    libsqlite3 = which('libsqlite3-0.dll')\n    must_copy(libsqlite3, bin_dir)\n\ndef copy_dll_exe():\n    filelist = [\n        'libsearpc-1.dll',\n        'libsearpc-json-glib-0.dll',\n        'libccnet-0.dll',\n        'libseafile-0.dll',\n        'ccnet.exe',\n        'seaf-daemon.exe',\n    ]\n\n    filelist = [ which(f) for f in filelist ]\n\n    applet = os.path.join(seafile_srcdir, 'gui', 'win', 'seafile-applet.exe')\n    filelist.append(applet)\n\n    for name in filelist:\n        must_copy(name, bin_dir)\n\n    copy_shared_libs()\n\ndef main():\n    if not os.path.exists(destdir):\n        must_mkdir(destdir)\n    if not os.path.exists(bin_dir):\n        must_mkdir(bin_dir)\n    web_py2exe()\n    copy_dll_exe()\n\nif __name__ == '__main__':\n    if len(sys.argv) != 2:\n        usage()\n        exit(1)\n\n    seafile_srcdir = os.getcwd()\n    destdir = to_win_path(sys.argv[1])\n    bin_dir = os.path.join(destdir, 'bin')\n    main()\n"
  },
  {
    "path": "tests/sync-auto-test/README.md",
    "content": "## Prerequisite\n\npip install -r requirements.txt\n\n## Run\n\n1. cp run.sh.template run.sh && cp test.conf.template test.conf\n2. modify PYTHONPATH, PATH in run.sh\n    * note you must copy seafile related site_packages to test machine and point the path in PYTHONPATH\n    * note you must point seaf-daemon, ccnet file path in PATH\n3. modify server_url, user, password in test.conf\n4. start seahub server\n5. execute ./run.sh test\n"
  },
  {
    "path": "tests/sync-auto-test/__init__.py",
    "content": ""
  },
  {
    "path": "tests/sync-auto-test/cli1/ccnet.conf",
    "content": "[General]\nUSER_NAME = anonymous\nID = dbf3688a7fbddea62ad894b795c4184bfe7bd9f8\nNAME = anonymous\n\n[Client]\nPORT = 13419\n"
  },
  {
    "path": "tests/sync-auto-test/cli1/mykey.peer",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEoQIBAAKCAQEAv6CFpIy8zAhm3kLNbvJUro+6VmVpJSBQFtnjmei+8qvM+C3Q\nuyyln41h+I64nZ3ZbJDCxfckk9c2Gh+qlF17N1ntxphq27UcrO4cGgDwmJ0BUuMd\nG7mqeNRV03Si9VTAr0z4vvHR4su5XAihWvbdtA+1r2/WJmW2bJxswmaq2JMWHuCK\n1+JrFeMFUyxVyKTF77wflu0q6AqqjzyQk9Z03zY+KTRcwDuKHp1tlY3nD8XgliKc\nHA0Rd56CvHZM36cO6C6G5Q9xVk+DfzD6yfBCOlMsbQOeD2mxVtnfNWKU+vrgDK8i\nkhWN7QcB6Scxs53/O4Hhm6a3E3bIZ9OElZEKSQIBIwKCAQAxRojD6azkAikUlNW9\ncYN9WCiZ35AQ3Gxdpb4u5BPYAEqmN6qzyabug3D2xZ0v2CH3WGyZTi36IWWu8jMt\ndx+vJcDMqtmmNeLNYcy2OsGGVEIrQbcHIR01A2aG1NlySP5Y9okbKD1I8o6/5PZK\nl0BS33fcp7q5apyflfAUu1CA1OuEmHOQKW5ZgEhtadAfRrZuFVOXtG8gGe/xt/Jw\nQaRnj3fsWC/IAyaXlfLKGe9jrWxmSjiHL7sZPO3rLVNoAEyOPaKN7dzR4AaJI1Ve\n2jztM4HctQ43sGyw0gpID/OqVRAryiWm0UWj0cbHBt2JlhxvvOOHssT429+ol1Ce\nO3tXAoGBAOL3E1IrtnFf8aDbCZBgA96wAEXbtISZfehjww8q30oN5DAUv4WBKJx+\nJy505/SnGlMoZheJYmAAligu/rOOVrm9wzkpapj2xrOV4aPuw0qi3tpy2bQiNKAF\n3No6g1/PxDmNX9y2gWb0qKP1F4NiZ5ZD2z4/bbARgNPealGiBxTLAoGBANgkJwV7\nitrLqS5c3JwLcXch2rEX7L1DfTl/dURfFFYiJB9Ecznf6jsaEqtkCDMBeuff8Tb2\nuC8xVEuJHKpLFkfk0PIFeZoS6LZ+qXyjMZjgj5HYvyRp+L/2r8CNS9bqJLzxdgjE\nnzSXvMq1Ld8BQ/phjy6FZV5WnuQWOTJTTU67AoGAE3RDfBJgGFiuT52+/b8WRkmZ\nn5Z9L+/mOH2UYGLCrpN59X4fC3F4gnE2jvQT4cUuJGKMaGrci+LLCsIzFrRt1WgJ\na0yxXZF+vu+W/2w8n/9U7icZ+X9GVtvuW9khNBkfcqW3wnYLF3QOdHQX9VGT2a4L\nel01SZsZq8KbZhUzzpUCgYEAxZ1lgVsCnCfrIxMS18/ythBS6w6PTfSPv4p52Bxq\nXWELFUXlsUHANgk1pAOvuZr0BzMlrpht4gE3IH1cCWk48UY7YO8QF9bGIzH6C43k\nNACDQ4RW/Lio6gYdB9GkbLjRIcbSUSjMEtPfz0aQVtyV7Dvwn4/nprWYlgWwowpV\nT0sCgYADpCjBMC9YGiigSh0ss0qqCxS6i76LJikd+HZPeEAVCIO6ljBUkDQspv/m\n2NhefJ/fM/Jpm9koGZqXVwv+Hh3qCAuuY3c28Fehd/e1ATJA6RbS9W8oBwNLgMM0\nTfD83Ye1wWe5m4ab1/2034Nxp33gn/Uhf6OBdgCr5fEUmrqTXw==\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "tests/sync-auto-test/cli2/ccnet.conf",
    "content": "[General]\nUSER_NAME = anonymous\nID = 5525c22aebbc9e115e5a5872e525821c08b20ec3\nNAME = anonymous\n\n[Client]\nPORT = 13420\n"
  },
  {
    "path": "tests/sync-auto-test/cli2/mykey.peer",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAxTsfwh1gLycQgmDM22Y6gonR2EYlz/T3157IyxOipjeEZZVZ\nn2FECRH/j4NFTdCAiZ4DxPIQsYLlbJ6j1lvS8HlVcBtVWbM4Ibs4PPtIqT9MJyYK\nQXkJKG4igiV08wVO/88vrsnQ9IcCbmbMP3y+J9U7aJtp2C+kZGy8jDGd9Ewvi/IO\nPmiuWj27GXI1hnLWN4yzqPXnIUbak7SAbo0pPHldkVmi+mp+eHrfsQiSTko9pvST\n3VVjwYSAn1SLICkEvo8+rsSDkC+ol66M+ObOemSUdh0IWllKt6JSHwf9bFmLG+Mz\nUnZ/zDYJjS/Uow2sxbLRQP8FVi4mX+XenbMHcwIBIwKCAQAWimoHjlQipWD49R65\n/Q4ASkPeM+cQc8SNq78tJtDCigCAo1qzIQ8W+r4fB7AmJnUXCr6aKksM+QRG7Y8R\nLxDLBo1r5d3eXaAD2uHbFWdjzLg+/Qh8gtx5pi/UXA1dmjTqCQzDhMdsZzN6VOQk\ng0junAbKIGPeMVSdw0i/kKRWbuf9W/hUBGm+eep9bzov6hmgB9s52y4lhFO4kFjP\nKezp5BDZ5bhpgoo06JGMBGUQ+nsLt917f2CelHh35snPGbthZ7o6f3S0XPxCXhKG\n20jfChmgXU7msd/2YPUuw/YlaH1lpdiq5DmvQaNvIfgwH9XRmOxRLB2hvzCK3UCO\nQf9rAoGBAOgLZ4n/ZwLSI/s5tc2UGLjadHOjAfZsnal/fl0rxWNUhwRMsBxv745c\nNUabLpR3g+xxjHSrmhQwxSyLOaQxgs76dmpANetzS82bph/QY+VVQDBVezuY1P0r\n/4H5oQ6X+1/f+s6SIWlzuBm5w1AESL+qzkxrFOvlL+d/SV30tSaNAoGBANmXpcdf\n2w5FbvwWLyrPGb0k04HLthzsY1SFRxouGOzpZsXSD3jvRLbNUp+Ms4mCNpyO3005\nIMIlLDX694EahQ4vzTbus1rytju3UUsMhCuFjAPlcVpe7+Xf0m7MNEUNC+ICfTzp\nCB2a/5nHA0j5rl4w6nEPqBYptVYhjIvMPGX/AoGAVjAfJKCxO4/hepHHLxnACiVB\nMkPcKFQ6j2neemC3B6MNk+H4RROiHu8MeU+U9VD2fGSwgxsqmcj4x2bphiEEs0cW\nCjUbV3P3lYL0j3lJrPPHYmjkoRuCT2gdEwTyraYqKuzK3wMTuXQfzwp7vqKBa8MZ\nbNdQ6edpkH+8KjZR6b8CgYEAxvEMmQcurfZW19nEuWzkVSj75GJ6nhn0aoh7haZ9\nMGBd9rFtO15qtcMJtnIDO+TEN14N/XYPUmshG2kczc8akKC7n/Au9BEbpE/VS+4v\nssM+L3Kwx6dBvDpZ/uaWMID06+UEyfmhBR//oooRoc5WR30uHjozR3aXKi1NTJ15\nDMsCgYEAmGa3xJUt+Yzd6zxR0dhYRJeWzH95m6op/KwBwMmohSOiRPDx9B4FPkkp\nDi6j8gRz6TZ42qtXwY7X/tTF1oywY+dM7xOO6XKwntCRstAEly8uaVPtsB/xbl6l\nM4U37zo7tGmVxK31rMjV6/Zn2iRnqKNo6QpcBK4X0hg3scopRdg=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "tests/sync-auto-test/requirements.txt",
    "content": "nose\n"
  },
  {
    "path": "tests/sync-auto-test/run.sh.template",
    "content": "#!/bin/bash\n\nexport PYTHONPATH=\nexport PATH=\n\nOS=`uname -s`\n\nrm_sync_data() {\n    rm -rf $1/ccnet.sock $1/seafile.ini $1/$1/logs $1/misc $1/seafile $1/seafile-data\n}\n\nrun_test() {\n    nosetests -v -s test_cases/test_simple.py test_cases/test_release.py\n    if [ ${OS} != \"Linux\" -a ${OS} != \"Darwin\" ];\n    then\n        rm_sync_data cli1\n        rm_sync_data cli2\n    fi\n}\n\ncase $1 in\n    \"test\")\n        run_test\n        sleep 10\n        export ENCRYPTED_REPO=true\n        run_test;;\n    \"clean\")\n        if [ ${OS} = \"Linux\" -o ${OS} = \"Darwin\" ];\n        then\n            pkill -f \"ccnet --daemon -c $(pwd)/cli1\"\n            pkill -f \"ccnet --daemon -c $(pwd)/cli2\"\n        fi\n        rm -rf worktree1 worktree2\n        rm_sync_data cli1\n        rm_sync_data cli2;;\nesac\n"
  },
  {
    "path": "tests/sync-auto-test/seaf_op.py",
    "content": "'''\n\nseaf-cli is command line interface for seafile client.\n\nSubcommands:\n\n    init:           create config files for seafile client\n    start:          start and run seafile client as daemon\n    stop:           stop seafile client\n    list:           list local liraries\n    status:         show syncing status\n    download:       download a library from seafile server\n    sync:           synchronize an existing folder with a library in\n                        seafile server\n    desync:         desynchronize a library with seafile server\n    create:         create a new library\n\n\nDetail\n======\n\nSeafile client stores all its configure information in a config dir. The default location is `~/.ccnet`. All the commands below accept an option `-c <config-dir>`.\n\ninit\n----\nInitialize seafile client. This command initializes the config dir. It also creates sub-directories `seafile-data` and `seafile` under `parent-dir`. `seafile-data` is used to store internal data, while `seafile` is used as the default location put downloaded libraries.\n\n    seaf-cli init [-c <config-dir>] -d <parent-dir>\n\nstart\n-----\nStart seafile client. This command start `ccnet` and `seaf-daemon`, `ccnet` is the network part of seafile client, `seaf-daemon` manages the files.\n\n    seaf-cli start [-c <config-dir>]\n\nstop\n----\nStop seafile client.\n\n    seaf-cli stop [-c <config-dir>]\n\n\nDownload\n--------\nDownload a library from seafile server\n\n    seaf-cli download -l <library-id> -s <seahub-server-url> -d <parent-directory> -u <username> -p <password>\n\n\nsync\n----\nSynchronize a library with an existing folder.\n\n    seaf-cli sync -l <library-id> -s <seahub-server-url> -d <existing-folder> -u <username> -p <password>\n\ndesync\n------\nDesynchronize a library from seafile server\n\n    seaf-cli desync -d <existing-folder>\n\ncreate\n------\nCreate a new library\n\n    seaf-cli create -s <seahub-server-url> -n <library-name> -u <username> -p <password> -t <description> [-e <library-password>]\n\n'''\n\nimport os\nimport json\nimport subprocess\nimport sys\nimport time\nimport urllib\nimport urllib2\nimport httplib\nfrom urlparse import urlparse\n\nimport ccnet\nimport seafile\n\ndef _check_seafile():\n    ''' Check ccnet and seafile have been installed '''\n\n    sep = ':' if os.name != 'nt' else ';'\n    dirs = os.environ['PATH'].split(sep)\n    def exist_in_path(prog):\n        ''' Check whether 'prog' exists in system path '''\n        for d in dirs:\n            if d == '':\n                continue\n            path = os.path.join(d, prog)\n            if os.path.exists(path):\n                return True\n\n    progs = [ 'ccnet', 'seaf-daemon' ]\n\n    for prog in progs:\n        if os.name == 'nt':\n            prog += '.exe'\n        if not exist_in_path(prog):\n            print \"%s not found in PATH. Have you installed seafile?\" % prog\n            sys.exit(1)\n\ndef run_argv(argv, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False, wait=True):\n    '''Run a program and wait it to finish, and return its exit code. The\n    standard output of this program is supressed.\n\n    '''\n    with open(os.devnull, 'w') as devnull:\n        if suppress_stdout:\n            stdout = devnull\n        else:\n            stdout = sys.stdout\n\n        if suppress_stderr:\n            stderr = devnull\n        else:\n            stderr = sys.stderr\n\n        proc = subprocess.Popen(argv,\n                                cwd=cwd,\n                                stdout=stdout,\n                                stderr=stderr,\n                                env=env)\n        if wait:\n            return proc.wait()\n        return 0\n\ndef get_env():\n    env = dict(os.environ)\n    ld_library_path = os.environ.get('SEAFILE_LD_LIBRARY_PATH', '')\n    if ld_library_path:\n        env['LD_LIBRARY_PATH'] = ld_library_path\n\n    return env\n\nclass NoRedirect(urllib2.HTTPRedirectHandler):\n    def redirect_request(self, req, fp, code, msg, hdrs, newurl):\n        pass\n\ndef urlopen(url, data=None, headers=None, follow_redirect=True):\n    if data:\n        data = urllib.urlencode(data)\n    headers = headers or {}\n    req = urllib2.Request(url, data=data, headers=headers)\n    if follow_redirect:\n        resp = urllib2.urlopen(req)\n    else:\n        try:\n            opener = urllib2.build_opener(NoRedirect())\n            resp = opener.open(req)\n        except urllib2.HTTPError:\n            return None\n    return resp.read()\n\ndef get_token(url, username, password):\n    data = {\n        'username': username,\n        'password': password,\n    }\n    token_json = urlopen(\"%s/api2/auth-token/\" % url, data=data)\n    tmp = json.loads(token_json)\n    token = tmp['token']\n    return token\n\ndef get_repo_downlod_info(url, token):\n    headers = { 'Authorization': 'Token %s' % token }\n    repo_info = urlopen(url, headers=headers)\n    return json.loads(repo_info)\n\ndef seaf_init(conf_dir):\n    ''' initialize config directorys'''\n\n    _check_seafile()\n\n    seafile_ini = os.path.join(conf_dir, \"seafile.ini\")\n    seafile_data = os.path.join(conf_dir, \"seafile-data\")\n    fp = open(seafile_ini, 'w')\n    fp.write(seafile_data)\n    fp.close()\n\n    print 'Init ccnet config success.'\n\ndef seaf_start_all(conf_dir):\n    ''' start ccnet and seafile daemon '''\n\n    seaf_start_ccnet(conf_dir)\n    # wait ccnet process\n    time.sleep(1)\n    seaf_start_seafile(conf_dir)\n\n    print 'Start ccnet, seafile daemon success.'\n\ndef seaf_start_ccnet(conf_dir):\n    ''' start ccnet daemon '''\n\n    cmd = [ \"ccnet\", \"--daemon\", \"-c\", conf_dir ]\n    wait = False if os.name == 'nt' else True\n    if run_argv(cmd, env=get_env(), suppress_stdout=True, wait=wait) != 0:\n        print 'Failed to start ccnet daemon.'\n        sys.exit(1)\n\ndef seaf_start_seafile(conf_dir):\n    ''' start seafile daemon '''\n\n    cmd = [ \"seaf-daemon\", \"--daemon\", \"-c\", conf_dir,\n            \"-d\", os.path.join(conf_dir, 'seafile-data'),\n            \"-w\", os.path.join(conf_dir, 'seafile') ]\n    wait = False if os.name == 'nt' else True\n    if run_argv(cmd, env=get_env(), suppress_stdout=True, wait=wait) != 0:\n        print 'Failed to start seafile daemon'\n        sys.exit(1)\n\ndef seaf_stop(conf_dir):\n    '''stop seafile daemon '''\n\n    pool = ccnet.ClientPool(conf_dir)\n    client = pool.get_client()\n    try:\n        client.send_cmd(\"shutdown\")\n    except:\n        # ignore NetworkError(\"Failed to read from socket\")\n        pass\n\n    print 'Stop ccnet, seafile daemon success.'\n\ndef get_base_url(url):\n    parse_result = urlparse(url)\n    scheme = parse_result.scheme\n    netloc = parse_result.netloc\n\n    if scheme and netloc:\n        return '%s://%s' % (scheme, netloc)\n\n    return None\n\ndef get_netloc(url):\n    parse_result = urlparse(url)\n    return parse_result.netloc\n\ndef seaf_sync(conf_dir, server_url, repo_id, worktree, username, passwd):\n    ''' synchronize a library from seafile server '''\n\n    pool = ccnet.ClientPool(conf_dir)\n    seafile_rpc = seafile.RpcClient(pool, req_pool=False)\n\n    token = get_token(server_url, username, passwd)\n    tmp = get_repo_downlod_info(\"%s/api2/repos/%s/download-info/\" % (server_url, repo_id), token)\n\n    encrypted = tmp['encrypted']\n    magic = tmp.get('magic', None)\n    enc_version = tmp.get('enc_version', None)\n    random_key = tmp.get('random_key', None)\n\n    clone_token = tmp['token']\n    relay_id = tmp['relay_id']\n    relay_addr = tmp['relay_addr']\n    relay_port = str(tmp['relay_port'])\n    email = tmp['email']\n    repo_name = tmp['repo_name']\n    version = tmp.get('repo_version', 0)\n\n    more_info = None\n    base_url = get_base_url(server_url)\n    if base_url:\n        more_info = json.dumps({'server_url': base_url})\n\n    if encrypted == 1:\n        repo_passwd = 's123'\n    else:\n        repo_passwd = None\n\n    seafile_rpc.clone(repo_id,\n                      version,\n                      relay_id,\n                      repo_name.encode('utf-8'),\n                      worktree,\n                      clone_token,\n                      repo_passwd, magic,\n                      relay_addr,\n                      relay_port,\n                      email, random_key, enc_version, more_info)\n\n    print 'Synchronize repo test success.'\n\ndef seaf_desync(conf_dir, repo_path):\n    '''Desynchronize a library from seafile server'''\n\n    pool = ccnet.ClientPool(conf_dir)\n    seafile_rpc = seafile.RpcClient(pool, req_pool=False)\n\n    repos = seafile_rpc.get_repo_list(-1, -1)\n    repo = None\n    for r in repos:\n        if r.worktree.replace('/', '\\\\') == repo_path.decode('utf-8').replace('/', '\\\\'):\n            repo = r\n            break\n\n    if repo:\n        print \"Desynchronize repo test success.\"\n        seafile_rpc.remove_repo(repo.id)\n    else:\n        print \"%s is not a library worktree\" % repo_path\n\ndef seaf_create(conf_dir, server_url, username, passwd, enc_repo):\n    '''Create a library'''\n\n    # curl -d 'username=<USERNAME>&password=<PASSWORD>' http://127.0.0.1:8000/api2/auth-token\n    token = get_token(server_url, username, passwd)\n\n    headers = { 'Authorization': 'Token %s' % token }\n    data = {\n        'name': 'test',\n        'desc': 'test',\n    }\n    if enc_repo:\n        data['passwd'] = 's123'\n\n    repo_info_json =  urlopen(\"%s/api2/repos/\" % server_url, data=data, headers=headers)\n    repo_info = json.loads(repo_info_json)\n\n    if enc_repo:\n        print 'Create encrypted repo test success.'\n    else:\n        print 'Create non encrypted repo test success.'\n\n    return repo_info['repo_id']\n\ndef seaf_delete(conf_dir, server_url, username, passwd, repo_id):\n    '''Delete a library'''\n\n    token = get_token(server_url, username, passwd)\n    headers = { 'Authorization': 'Token %s' % token }\n\n    conn = httplib.HTTPConnection(get_netloc(server_url))\n    conn.request('DELETE', '/api2/repos/%s/' % repo_id, None, headers)\n    resp = conn.getresponse()\n    if resp.status == 200:\n        print 'Delete repo test success.'\n    else:\n        print 'Delete repo test failed: %s.' % resp.reason\n\ndef seaf_get_repo(conf_dir, repo_id):\n    pool = ccnet.ClientPool(conf_dir)\n    seafile_rpc = seafile.RpcClient(pool, req_pool=False)\n    return seafile_rpc.seafile_get_repo(repo_id)\n"
  },
  {
    "path": "tests/sync-auto-test/setting.py",
    "content": "#coding: utf-8\n\nimport os\nimport ConfigParser\n\nclass Setting():\n    def __init__(self):\n        self.server_url = None\n        self.user = None\n        self.password = None\n\n    def parse_config(self):\n        config_path = None\n        if os.environ.has_key('TEST_CONFIFG'):\n            config_path = os.environ['TEST_CONFIG']\n        else:\n            config_path = os.path.join(os.getcwd(), 'test.conf')\n\n        if not os.path.exists(config_path):\n            raise Exception(\"Test config %s doesn't exist\" % config_path)\n\n        parser = ConfigParser.ConfigParser()\n        parser.read(config_path)\n        self.server_url = parser.get('test', 'server_url')\n        self.user = parser.get('test', 'user')\n        self.password = parser.get('test', 'password')\n"
  },
  {
    "path": "tests/sync-auto-test/test.conf.template",
    "content": "[test]\nserver_url =\nuser =\npassword =\n"
  },
  {
    "path": "tests/sync-auto-test/test_cases/__init__.py",
    "content": "from util import TestUtil\nimport time\n\ntest_util = TestUtil()\n\ndef setup():\n    # init sync related stuff\n    test_util.init_conf()\n    test_util.start_daemon()\n    time.sleep(2)\n    test_util.create_repo()\n    test_util.sync_repo()\n    time.sleep(5)\n    print '\\n----------------------------------------------------------------------'\n\ndef teardown():\n    # clean sync related stuf\n    print '----------------------------------------------------------------------\\n'\n    test_util.desync_repo()\n    test_util.stop_daemon()\n    test_util.clean()\n"
  },
  {
    "path": "tests/sync-auto-test/test_cases/test_release.py",
    "content": "#coding: utf-8\n\nimport os\nimport time\nimport glob\nfrom threading import Thread\nfrom seaf_op import get_token, urlopen\nfrom . import test_util\n\n'''\nTest add-delete-add sequence.\n'''\ndef test_add_delete_add():\n    test_util.set_test_root('test_add_delete_add')\n\n    test_util.mkfile(1, 'test/1.txt', 'aaaaaaaa')\n    test_util.rmdir(1, 'test')\n    test_util.mkfile(1, 'test/1.txt', 'aaaaaaaa')\n\n    test_util.verify_result()\n\n'''\nRename test:\necho 111 > 1.txt; sleep 3; mv 1.txt 2.txt\necho 222 > 3.txt; sleep 3; mv 2.txt 3.txt\necho test > test.txt\nmkdir test; echo 444 > 4.txt; sleep 3; mv *.txt test\nmkdir test2; mv test test2\nmv test2/test .\necho 555 >> test/4.txt; mv test test2\nmv test2 test3\n'''\ndef test_rename():\n    test_util.set_test_root('test_rename')\n\n    test_util.mkfile(1, '1.txt', '111')\n    time.sleep(3)\n    test_util.move(1, '1.txt', '2.txt')\n\n    time.sleep(3)\n\n    test_util.mkfile(1, '3.txt', '222')\n    time.sleep(3)\n    test_util.move(1, '2.txt', '3.txt')\n\n    time.sleep(3)\n\n    test_util.mkfile(1, 'test.txt', 'test')\n\n    time.sleep(3)\n\n    test_util.mkdir(1, 'test')\n    test_util.mkfile(1, '4.txt', '444')\n    time.sleep(3)\n    test_util.batchmove(1, '*.txt', 'test')\n\n    time.sleep(3)\n\n    test_util.mkdir(1, 'test2')\n    test_util.move(1, 'test', 'test2')\n\n    time.sleep(3)\n\n    test_util.move(1, 'test2/test', '')\n\n    time.sleep(3)\n\n    test_util.modfile(1, 'test/4.txt', '555')\n    test_util.move(1, 'test', 'test2')\n\n    time.sleep(3)\n\n    test_util.move(1, 'test2', 'test3')\n\n    test_util.verify_result()\n\n'''\nCreate and update test:\necho 111 > test/1.txt\necho 222 >> test/1.txt\ncopy a dir with multiple levels into test dir\ncreate an empty dir\nadd file into an empty folder\n'''\ndef test_create_update():\n    test_util.set_test_root('test_create_update')\n\n    test_util.mkdir(1, 'test')\n    test_util.mkfile(1, 'test/1.txt', '111')\n\n    time.sleep(3)\n\n    test_util.modfile(1, 'test/1.txt', '222')\n\n    time.sleep(3)\n\n    test_util.mkdir(1, '1/2/3/4/5')\n    test_util.mkfile(1, '1/1.txt', '111')\n    test_util.mkfile(1, '1/2/2.txt', '222')\n    test_util.copy(1, '1', 'test/1')\n\n    time.sleep(3)\n\n    test_util.mkdir(1, 'empty')\n\n    time.sleep(3)\n\n    test_util.mkfile(1, 'empty/test.md', 'dddddddddddddddddddddd')\n\n    test_util.verify_result()\n\n'''\nDelete test:\necho 222 > 2.txt\nrm 2.txt\ndelete a dir with multiple levels\ndelete all files under a dir, make it empty.\ndelete empty dir\n'''\ndef test_delete():\n    test_util.set_test_root('test_delete')\n\n    test_util.mkfile(1, '2.txt', '2222')\n    time.sleep(3)\n    test_util.rmfile(1, '2.txt')\n\n    test_util.mkdir(1, '1/2/3/4/5')\n    test_util.mkfile(1, '1/1.txt', '111')\n    test_util.mkfile(1, '1/2/2.txt', '222')\n    test_util.copy(1, '1', 'test/1')\n    time.sleep(3)\n    test_util.rmdir(1, '1')\n\n    time.sleep(3)\n\n    test_util.rmdir(1, 'test/1')\n\n    time.sleep(3)\n\n    test_util.rmdir(1, 'test')\n\n    test_util.verify_result()\n\n'''\nCase rename test:\nrename a dir from 'test' to 'TEST'\ndisalbe auto sync; rename the dir from 'TEST' to 'test'; enable auto sync.\n'''\ndef test_case_rename():\n    test_util.set_test_root('test_case_rename')\n\n    test_util.mkdir(1, 'test')\n    test_util.mkfile(1, 'a.txt', 'aaaa')\n\n    time.sleep(3)\n\n    test_util.move(1, 'test', 'TEST')\n    test_util.verify_result()\n\n    test_util.desync_cli1()\n    test_util.move(1, 'TEST', 'test')\n    test_util.sync_cli1()\n    test_util.verify_result()\n\n'''\nA test set for downloads. The updates are done on cli1 and downloaded on cli2.\nNote that these tests are different from upload tests. In upload tests, we deliberately\ncombine multiple operations into one test; in download tests, we must ensure each\noperation be carried out individually on cli2.\n'''\n\n# Create a new file\ndef test_download_1():\n    test_util.set_test_root('test_download')\n\n    test_util.mkfile(1, '1.txt', '11111')\n\n    test_util.verify_result()\n\n# Update a file\ndef test_download_2():\n    test_util.set_test_root('test_download')\n\n    test_util.modfile(1, '1.txt', '22222')\n\n    test_util.verify_result()\n\n# Create empty dir\ndef test_download_3():\n    test_util.set_test_root('test_download')\n\n    test_util.mkdir(1, 'dir1')\n\n    test_util.verify_result()\n\n# Rename a file\ndef test_download_4():\n    test_util.set_test_root('test_download')\n\n    test_util.move(1, '1.txt', '2.txt')\n\n    test_util.verify_result()\n\n# Rename empty dir\ndef test_download_5():\n    test_util.set_test_root('test_download')\n\n    test_util.move(1, 'dir1', 'dir2')\n\n    test_util.verify_result()\n\n# Create file in empty dir\ndef test_download_6():\n    test_util.set_test_root('test_download')\n\n    test_util.mkfile(1, 'dir2/1.txt', '1111111')\n\n    test_util.verify_result()\n\n# Rename a non-empty dir\ndef test_download_7():\n    test_util.set_test_root('test_download')\n\n    test_util.move(1, 'dir2', 'dir3')\n\n    test_util.verify_result()\n\n# Remove all files in a non-empty dir\ndef test_download_8():\n    test_util.set_test_root('test_download')\n\n    test_util.rmfile(1, 'dir3/1.txt')\n\n    test_util.verify_result()\n\n# Move a non-empty dir into an empty dir\ndef test_download_9():\n    test_util.set_test_root('test_download')\n\n    test_util.mkfile(1, 'dir4/2.txt', '2222222')\n    test_util.verify_result()\n\n    test_util.move(1, 'dir4', 'dir3')\n\n    test_util.verify_result()\n\n# Delete file\ndef test_download_10():\n    test_util.set_test_root('test_download')\n\n    test_util.rmfile(1, '2.txt')\n\n    test_util.verify_result()\n\n# Delete a non-empty dir\ndef test_download_11():\n    test_util.set_test_root('test_download')\n\n    test_util.rmdir(1, 'dir3/dir4')\n\n    test_util.verify_result()\n\n# Delete empty dir\ndef test_download_12():\n    test_util.set_test_root('test_download')\n\n    test_util.rmdir(1, 'dir3')\n\n    test_util.verify_result()\n\n'''\nTest cases for download case rename\n'''\n\ndef test_download_case_rename_1():\n    test_util.set_test_root('test_download_case_rename')\n\n    test_util.mkfile(1, 'abc/test/test.txt', 'testtset')\n    test_util.verify_result()\n\n    test_util.move(1, 'abc/test', 'abc/TEST')\n    test_util.move(1, 'abc', 'ABC')\n    test_util.verify_result()\n\ndef test_download_case_rename_2():\n    test_util.set_test_root('test_download_case_rename')\n\n    test_util.mkfile(1, 'a.txt', 'aaaaaaaaaaaaaaaaaaa')\n    test_util.mkfile(1, 'test/b.txt', 'bbbbbbbbbb')\n    test_util.verify_result()\n\n    test_util.move(1, 'test', 'TEST')\n    test_util.move(1, 'a.txt', 'TEST')\n    test_util.verify_result()\n\n'''create cur.md, wait for synced then concurrent modify cur.md\n'''\n# def mod_file(worktree, fname):\n#     test_util.modfile(worktree, fname, test_util.getpath(worktree, fname))\n\n# def test_concurrent_mod():\n#     test_util.mkfile(1, 'cur.md', 'curcurcurrrrrrrrrrrrrrrrrrrrr')\n#     test_util.verify_result()\n#     thread1 = Thread(target=mod_file, args=(1, 'cur.md'))\n#     thread2 = Thread(target=mod_file, args=(2, 'cur.md'))\n#     thread1.start()\n#     thread2.start()\n#     test_util.verify_result()\n#     files = glob.glob(test_util.getpath(1, 'cur*.md'))\n#     assert len(files) ==  2, 'Should generate conflict file'\n"
  },
  {
    "path": "tests/sync-auto-test/test_cases/test_simple.py",
    "content": "#coding: utf-8\n\nimport os\nimport time\nfrom . import test_util\n\ndef test_add_file():\n    test_util.mkfile(1, 'a.md', 'add a file')\n    test_util.verify_result()\n\ndef test_add_file_t():\n    test_util.mkfile(2, 'l/m/n/test.md', 'add l/m/n/test.md')\n    test_util.verify_result()\n\ndef test_add_dir():\n    test_util.mkdir(1, 'ad')\n    test_util.verify_result()\n\ndef test_add_dir_t():\n    test_util.mkdir(2, 'tt/ee/st')\n    test_util.verify_result()\n\ndef test_modify_file():\n    test_util.modfile(1, 'a.md', 'modify a.md')\n    test_util.verify_result()\n\ndef test_rm_file():\n    test_util.rmfile(1, 'a.md')\n    test_util.verify_result()\n\ndef test_rm_dir():\n    test_util.rmdir(1, 'ad')\n    test_util.verify_result()\n\ndef test_rename_file():\n    test_util.mkfile(2, 'b.md', 'add b.md')\n    time.sleep(1)\n    test_util.move(2, 'b.md', 'b_bak.md')\n    test_util.verify_result()\n\ndef test_rename_dir():\n    test_util.mkdir(2, 'ab')\n    time.sleep(1)\n    test_util.move(2, 'ab', 'ab_bak')\n    test_util.verify_result()\n\ndef test_each():\n    test_util.mkdir(1, 'abc1')\n    test_util.mkfile(1, 'abc1/c.md', 'add abc1/c.md')\n    time.sleep(1)\n\n    test_util.mkdir(2, 'bcd1')\n    test_util.mkfile(2, 'bcd1/d.md', 'add bcd1/d.md')\n    test_util.verify_result()\n\ndef test_unsync_resync():\n    test_util.desync_cli1()\n    test_util.rmdir(1, 'abc1')\n    test_util.modfile(1, 'bcd1/d.md', 'modify bcd1/d.md to test unsync resync')\n    test_util.sync_cli1()\n\n    test_util.verify_result()\n\n    if not os.path.exists(test_util.getpath(1, 'abc1')):\n        assert False, 'dir abc1 should be recreated when resync'\n\n    if len(os.listdir(test_util.getpath(1, 'bcd1'))) != 2:\n        assert False, 'should generate conflict file for bcd1/d.md when resync'\n\ndef test_modify_timestamp():\n    test_util.touch(1, 'bcd1/d.md')\n    test_util.verify_result()\n"
  },
  {
    "path": "tests/sync-auto-test/util.py",
    "content": "#coding: utf-8\n\nimport os\nimport shutil\nimport time\nimport subprocess\nimport glob\nfrom setting import Setting\nimport seaf_op\n\ndef call_process(params):\n    with open(os.devnull, 'w') as fd:\n        ret = subprocess.check_output(params, stderr=fd)\n    return ret\n\nclass TestUtil():\n    def __init__(self):\n        self.setting = Setting()\n        self.cli1_dir = os.path.join(os.getcwd(), 'cli1')\n        self.cli2_dir = os.path.join(os.getcwd(), 'cli2')\n        self.worktree1 = os.path.join(os.getcwd(), 'worktree1')\n        self.worktree2 = os.path.join(os.getcwd(), 'worktree2')\n        self.enc_repo = False\n        try:\n            self.enc_repo = bool(os.environ['ENCRYPTED_REPO'])\n        except Exception:\n            pass\n        self.repo_id = None\n        self.test_root = ''\n\n    def set_test_root(self, root):\n        self.test_root = root\n\n    @staticmethod\n    def clean_sync_data(conf):\n        try:\n            os.remove(os.path.join(conf, 'ccnet.sock'))\n            os.remove(os.path.join(conf, 'seafile.ini'))\n            shutil.rmtree(os.path.join(conf, 'logs'))\n            shutil.rmtree(os.path.join(conf, 'misc'))\n            shutil.rmtree(os.path.join(conf, 'seafile'))\n            shutil.rmtree(os.path.join(conf, 'seafile-data'))\n        except Exception:\n            pass\n\n    def init_conf(self):\n        self.setting.parse_config()\n\n        if not os.path.exists(self.cli1_dir) or not os.path.exists(self.cli2_dir):\n            raise Exception('ccnet conf dir is missing')\n\n        if os.name != 'nt':\n            TestUtil.clean_sync_data(self.cli1_dir)\n            TestUtil.clean_sync_data(self.cli2_dir)\n\n        if os.path.exists(self.worktree1):\n            shutil.rmtree(self.worktree1)\n        if os.path.exists(self.worktree2):\n            shutil.rmtree(self.worktree2)\n\n        os.mkdir(self.worktree1)\n        os.mkdir(self.worktree2)\n\n        seaf_op.seaf_init(self.cli1_dir)\n        seaf_op.seaf_init(self.cli2_dir)\n\n    def start_daemon(self):\n        seaf_op.seaf_start_all(self.cli1_dir)\n        seaf_op.seaf_start_all(self.cli2_dir)\n\n    def create_repo(self):\n        self.repo_id = seaf_op.seaf_create(self.cli1_dir, self.setting.server_url,\n                                           self.setting.user, self.setting.password,\n                                           self.enc_repo)\n\n    def sync_cli1(self):\n        seaf_op.seaf_sync(self.cli1_dir, self.setting.server_url, self.repo_id,\n                          self.worktree1, self.setting.user, self.setting.password)\n\n    def sync_cli2(self):\n        seaf_op.seaf_sync(self.cli2_dir, self.setting.server_url, self.repo_id,\n                          self.worktree2, self.setting.user, self.setting.password)\n\n    def sync_repo(self):\n        self.sync_cli1()\n        self.sync_cli2()\n\n    def desync_cli1(self):\n        seaf_op.seaf_desync(self.cli1_dir, self.worktree1)\n\n    def desync_cli2(self):\n        seaf_op.seaf_desync(self.cli2_dir, self.worktree2)\n\n    def desync_repo(self):\n        self.desync_cli1()\n        self.desync_cli2()\n        # delete test repo\n        seaf_op.seaf_delete(self.cli1_dir, self.setting.server_url,\n                            self.setting.user, self.setting.password,\n                            self.repo_id)\n\n    def stop_daemon(self):\n        seaf_op.seaf_stop(self.cli1_dir)\n        seaf_op.seaf_stop(self.cli2_dir)\n\n    def clean(self):\n        try:\n            if os.name != 'nt':\n                TestUtil.clean_sync_data(self.cli1_dir)\n                TestUtil.clean_sync_data(self.cli2_dir)\n            shutil.rmtree(self.worktree1)\n            shutil.rmtree(self.worktree2)\n        except Exception:\n            pass\n\n    def wait_sync(self):\n        while True:\n            time.sleep(5)\n            repo1 = seaf_op.seaf_get_repo(self.cli1_dir, self.repo_id)\n            if repo1 is None:\n                continue\n            repo2 = seaf_op.seaf_get_repo(self.cli2_dir, self.repo_id)\n            if repo2 is None:\n                continue\n            if repo1.head_cmmt_id == repo2.head_cmmt_id:\n                break\n\n    @staticmethod\n    def verify_by_rsync(dir1, dir2):\n        ret = call_process(['rsync', '-acrin', dir1, dir2])\n        if ret:\n            for d in ret.split('\\n'):\n                # omit empty str\n                if not d:\n                    continue\n                # omit directory has almost same result except st_mod\n                items = d.split(' ')\n                dattr = items[0]\n                name = items[1]\n\n                # Output format difference between rsync versions:\n                # rsync 3.1.1 : '.d..t.......'\n                # rsync 3.0.9 : '.d..t......'\n\n                # On Windows, file timestamp may have 1 second difference\n                # between two clients after sync. That's caused by the\n                # precision lose when converting Windows timestamp to\n                # Unix timestamp. So we don't check timestamp difference\n                # for files either.\n                if not all([c in ('f', 'd', 't', '.') for c in dattr]):\n                    assert False, 'Sync with two client have different result: %s %s' % (dattr, name)\n\n    def verify_result(self, callable=None):\n        self.wait_sync()\n        if callable:\n            callable(self.worktree1, self.worktree2)\n        else:\n            dir1 = './worktree1/'\n            dir2 = './worktree2/'\n            TestUtil.verify_by_rsync(dir1, dir2)\n            TestUtil.verify_by_rsync(dir2, dir1)\n\n    # worktree: 1(worktree1), 2(worktree2)\n    def mkdir(self, worktree, path):\n        if worktree == 1:\n            os.makedirs(os.path.join(self.worktree1, self.test_root, path))\n        elif worktree == 2:\n            os.makedirs(os.path.join(self.worktree2, self.test_root, path))\n\n    def rmdir(self, worktree, path):\n        if worktree == 1:\n            shutil.rmtree(os.path.join(self.worktree1, self.test_root, path))\n        elif worktree == 2:\n            shutil.rmtree(os.path.join(self.worktree2, self.test_root, path))\n\n    def mkfile(self, worktree, fpath, con=''):\n        if worktree == 1:\n            pdir = self.worktree1\n        elif worktree == 2:\n            pdir = self.worktree2\n        else:\n            return\n        abs_path = os.path.join(pdir, self.test_root, fpath)\n        dirname = os.path.dirname(abs_path)\n        if not os.path.exists(dirname):\n            os.makedirs(dirname)\n        with open(abs_path, 'w') as fd:\n            fd.write(con)\n\n    def rmfile(self, worktree, fpath):\n        if worktree == 1:\n            os.remove(os.path.join(self.worktree1, self.test_root, fpath))\n        elif worktree == 2:\n            os.remove(os.path.join(self.worktree2, self.test_root, fpath))\n\n    def modfile(self, worktree, fpath, con=''):\n        if worktree == 1:\n            pdir = self.worktree1\n        elif worktree == 2:\n            pdir = self.worktree2\n        else:\n            return\n        abs_path = os.path.join(pdir, self.test_root, fpath)\n        with open(abs_path, 'a') as fd:\n            fd.write(con)\n\n    def move(self, worktree, org_path, dest_path):\n        if worktree == 1:\n            shutil.move(os.path.join(self.worktree1, self.test_root, org_path),\n                        os.path.join(self.worktree1, self.test_root, dest_path))\n        elif worktree == 2:\n            shutil.move(os.path.join(self.worktree2, self.test_root, org_path),\n                        os.path.join(self.worktree2, self.test_root, dest_path))\n\n    def batchmove(self, worktree, regex, dest_path):\n        if worktree == 1:\n            pdir = self.worktree1\n        elif worktree == 2:\n            pdir = self.worktree2\n        else:\n            return\n        files = glob.glob(os.path.join(pdir, self.test_root, regex))\n        dest = os.path.join(pdir, self.test_root, dest_path)\n        for f in files:\n            shutil.move(f, dest)\n\n    def copy(self, worktree, org_path, dest_path):\n        if worktree == 1:\n            shutil.copytree(os.path.join(self.worktree1, self.test_root, org_path),\n                            os.path.join(self.worktree1, self.test_root, dest_path))\n        elif worktree == 2:\n            shutil.copytree(os.path.join(self.worktree2, self.test_root, org_path),\n                            os.path.join(self.worktree2, self.test_root, dest_path))\n\n    def touch(self, worktree, path, time=None):\n        if worktree == 1:\n            os.utime(os.path.join(self.worktree1, self.test_root, path), time)\n        if worktree == 2:\n            os.utime(os.path.join(self.worktree2, self.test_root, path), time)\n\n    def getpath(self, worktree, path):\n        if worktree == 1:\n            return os.path.join(self.worktree1, self.test_root, path)\n        elif worktree == 2:\n            return os.path.join(self.worktree2, self.test_root, path)\n        raise Exception('Invalid worktree')\n"
  },
  {
    "path": "updateversion.sh",
    "content": "#! /bin/sh\n\nif [ $# != \"2\" ]; then\n    echo \"$0 <old_version> <new_version>\"\n    exit\nfi\n\nold_ver=$1\nnew_ver=$2\n\nif test \"$(uname)\" = \"Darwin\"; then\n    sed -i '' -e \"s|$old_ver|$new_ver|\" web/setup_mac.py\n    sed -i '' -e \"s|VERSION=$old_ver|VERSION=$new_ver|\" setupmac.sh\n    sed -i '' -e \"s|<string>$old_ver</string>|<string>$new_ver</string>|\" gui/mac/seafile/seafile/*.plist\nelse\n    sed -i  \"s|$old_ver|$new_ver|\" web/setup_mac.py\n    sed -i \"s|VERSION=$old_ver|VERSION=$new_ver|\" setupmac.sh\n    sed -i \"s|<string>$old_ver</string>|<string>$new_ver</string>|\" gui/mac/seafile/seafile/*.plist\n\nfi\n\n"
  },
  {
    "path": "vcpkg.json",
    "content": "{\r\n    \"builtin-baseline\": \"f63682b9182187131b564c1395e4ac8ecb0c5ea8\",\r\n    \"dependencies\": [\r\n        \"argon2\",\r\n        \"getopt\",\r\n        \"glib\",\r\n        \"jansson\",\r\n        \"libevent\",\r\n        \"libwebsockets\",\r\n        \"openssl\",\r\n        \"pthreads\",\r\n        \"sqlite3\",\r\n        \"zlib\",\r\n        {\r\n            \"name\": \"curl\",\r\n            \"features\": [\r\n                \"openssl\"\r\n            ]\r\n        }\r\n    ],\r\n    \"overrides\": [\r\n        {\r\n            \"name\": \"argon2\",\r\n            \"version\": \"20190702#1\"\r\n        },\r\n        {\r\n            \"name\": \"curl\",\r\n            \"version\": \"8.2.1\"\r\n        },\r\n        {\r\n            \"name\": \"jansson\",\r\n            \"version\": \"2.12-1\"\r\n        },\r\n        {\r\n            \"name\": \"libevent\",\r\n            \"version\": \"2.1.11-5\"\r\n        },\r\n        {\r\n            \"name\": \"libwebsockets\",\r\n            \"version\": \"4.3.2\"\r\n        },\r\n        {\r\n            \"name\": \"pthreads\",\r\n            \"version\": \"3.0.0-4\"\r\n        },\r\n        {\r\n            \"name\": \"sqlite3\",\r\n            \"version\": \"3.31.1\"\r\n        },\r\n        {\r\n            \"name\": \"zlib\",\r\n            \"version\": \"1.2.11-6\"\r\n        }\r\n    ]\r\n}\r\n"
  }
]